第一次提交:Vue3后台前端最新版本
This commit is contained in:
858
src/views/withdraw/Audit.vue
Normal file
858
src/views/withdraw/Audit.vue
Normal file
@@ -0,0 +1,858 @@
|
||||
<template>
|
||||
<div class="withdraw-audit">
|
||||
<!-- 顶部统计卡片 -->
|
||||
<el-row :gutter="20" class="stats-cards">
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
|
||||
<el-card class="stat-card" shadow="hover">
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">总记录</div>
|
||||
<div class="stat-value">{{ stats.total || 0 }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
|
||||
<el-card class="stat-card" shadow="hover" @click="changeStatus(1)">
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">待审核</div>
|
||||
<div class="stat-value warning">{{ stats.awaiting || 0 }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
|
||||
<el-card class="stat-card" shadow="hover" @click="changeStatus(2)">
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">已处理</div>
|
||||
<div class="stat-value success">{{ stats.processed || 0 }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 总金额卡片 -->
|
||||
<el-row :gutter="20" style="margin-bottom: 20px;">
|
||||
<el-col :span="24">
|
||||
<el-card shadow="hover">
|
||||
<div class="total-amount">
|
||||
<span class="total-label">筛选总金额</span>
|
||||
<span class="total-value">¥{{ stats.total_amount || '0.00' }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 筛选区域 -->
|
||||
<el-card class="filter-card" shadow="hover">
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="24" :sm="6" :md="6" :lg="6" :xl="6">
|
||||
<el-input
|
||||
v-model="filters.searchUid"
|
||||
placeholder="用户ID"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="6" :md="6" :lg="6" :xl="6">
|
||||
<el-date-picker
|
||||
v-model="filters.date"
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
@change="handleSearch"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="filter-actions">
|
||||
<el-button type="primary" @click="handleSearch" :loading="loading">搜索</el-button>
|
||||
<el-button @click="resetFilters">重置</el-button>
|
||||
<el-button :icon="Refresh" @click="refreshList" circle />
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 打款方式切换 -->
|
||||
<el-row :gutter="20" class="filter-second-row">
|
||||
<el-col :span="24">
|
||||
<div class="mode-switch">
|
||||
<span class="mode-switch-label">打款方式</span>
|
||||
<el-radio-group v-model="filters.dakuanMode" @change="handleDakuanModeChange">
|
||||
<el-radio-button :label="1">手动打款</el-radio-button>
|
||||
<el-radio-button :label="2">自动打款</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" class="filter-second-row">
|
||||
<el-col :span="24">
|
||||
<div class="status-switch">
|
||||
<el-button
|
||||
:type="statusFilter === 1 ? 'primary' : 'default'"
|
||||
@click="changeStatus(1)"
|
||||
size="large"
|
||||
>
|
||||
待审核 <el-tag size="small" type="warning" v-if="stats.awaiting">{{ stats.awaiting }}</el-tag>
|
||||
</el-button>
|
||||
<el-button
|
||||
:type="statusFilter === 2 ? 'success' : 'default'"
|
||||
@click="changeStatus(2)"
|
||||
size="large"
|
||||
>
|
||||
已处理 <el-tag size="small" type="success" v-if="stats.processed">{{ stats.processed }}</el-tag>
|
||||
</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" class="filter-second-row">
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||
<el-select v-model="filters.type" placeholder="提现类型" clearable @change="handleSearch">
|
||||
<el-option label="打手佣金" :value="1" />
|
||||
<el-option label="管事分红" :value="2" />
|
||||
<el-option label="组长分红" :value="3" />
|
||||
<el-option label="审核官分佣" :value="4" />
|
||||
<el-option label="打手押金" :value="5" />
|
||||
<el-option label="商家余额" :value="6" />
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||
<el-select v-model="filters.payment" placeholder="收款方式" clearable @change="handleSearch">
|
||||
<el-option label="全部" :value="0" />
|
||||
<el-option label="微信收款" :value="1" />
|
||||
<el-option label="支付宝收款" :value="2" />
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||
<el-input-number
|
||||
v-model="filters.minAmount"
|
||||
placeholder="最小金额"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
controls-position="right"
|
||||
@change="handleSearch"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||
<el-input-number
|
||||
v-model="filters.maxAmount"
|
||||
placeholder="最大金额"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
controls-position="right"
|
||||
@change="handleSearch"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<!-- 自动打款批量操作栏 -->
|
||||
<div v-if="isAutoMode && statusFilter === 1" class="batch-bar">
|
||||
<span class="batch-tip">已选 {{ selectedRows.length }} 条</span>
|
||||
<el-button
|
||||
type="success"
|
||||
:disabled="selectedRows.length === 0"
|
||||
:loading="batchOperating"
|
||||
@click="handleBatchApprove"
|
||||
>
|
||||
批量同意打款
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
:disabled="selectedRows.length === 0"
|
||||
:loading="batchOperating"
|
||||
@click="showBatchRejectDialog"
|
||||
>
|
||||
批量驳回
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 提现记录表格 -->
|
||||
<el-card class="table-card" shadow="hover">
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="list"
|
||||
v-loading="loading"
|
||||
border
|
||||
stripe
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
@row-click="handleRowClick"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column
|
||||
v-if="isAutoMode && statusFilter === 1"
|
||||
type="selection"
|
||||
width="48"
|
||||
fixed="left"
|
||||
:selectable="isRowSelectable"
|
||||
/>
|
||||
<el-table-column prop="yonghuid" label="用户ID" width="120" />
|
||||
<el-table-column label="用户信息" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<el-avatar :size="40" :src="getFullImageUrl(row.avatar)" />
|
||||
<div style="margin-left: 12px;">
|
||||
<div>{{ row.nicheng || '--' }}</div>
|
||||
<div style="font-size: 12px; color: #909399;">{{ row.phone }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jine" label="金额" width="120">
|
||||
<template #default="{ row }">¥{{ row.jine }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="提现类型" width="110">
|
||||
<template #default="{ row }">{{ getTypeText(row.leixing) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isAutoMode" prop="shenhe_danhao" label="审核单号" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="打款方式" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.dakuan_mode === 2 ? 'success' : 'info'" size="small">
|
||||
{{ getDakuanModeText(row.dakuan_mode) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收款方式" width="100">
|
||||
<template #default="{ row }">{{ getPaymentText(row.fangshi) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="110">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusType(row.zhuangtai)">
|
||||
{{ getStatusText(row.zhuangtai) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="申请时间" width="160" :formatter="formatTime" />
|
||||
<el-table-column label="操作" :width="isAutoMode && statusFilter === 1 ? 200 : 80" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click.stop="goToDetail(row)">详情</el-button>
|
||||
<template v-if="isAutoMode && statusFilter === 1 && row.zhuangtai === 1">
|
||||
<el-button type="success" link @click.stop="handleInlineApprove(row)">同意</el-button>
|
||||
<el-button type="danger" link @click.stop="showInlineRejectDialog(row)">驳回</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="page"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 30, 50]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 列表页驳回弹窗(自动打款:单条/批量) -->
|
||||
<el-dialog
|
||||
v-model="rejectDialogVisible"
|
||||
:title="rejectMode === 'batch' ? `批量驳回(${selectedRows.length} 条)` : '驳回提现'"
|
||||
width="420px"
|
||||
>
|
||||
<el-form>
|
||||
<el-form-item label="驳回理由" required>
|
||||
<el-input v-model="rejectReason" type="textarea" :rows="4" placeholder="请输入驳回理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="rejectDialogVisible = false">取消</el-button>
|
||||
<el-button type="danger" @click="handleRejectSubmit" :loading="rowOperating || batchOperating">
|
||||
确认驳回
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Refresh } from '@element-plus/icons-vue'
|
||||
import request from '@/utils/request'
|
||||
|
||||
const router = useRouter()
|
||||
const STORAGE_KEY = 'withdraw_audit_filters'
|
||||
|
||||
const loading = ref(false)
|
||||
const batchOperating = ref(false)
|
||||
const rowOperating = ref(false)
|
||||
const list = ref([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const tableRef = ref(null)
|
||||
const selectedRows = ref([])
|
||||
|
||||
const stats = ref({
|
||||
total: 0,
|
||||
awaiting: 0,
|
||||
processed: 0,
|
||||
total_amount: '0.00'
|
||||
})
|
||||
|
||||
const filters = reactive({
|
||||
searchUid: '',
|
||||
date: '',
|
||||
type: '',
|
||||
payment: 0,
|
||||
minAmount: null,
|
||||
maxAmount: null,
|
||||
dakuanMode: 1
|
||||
})
|
||||
|
||||
const statusFilter = ref(1)
|
||||
|
||||
const rejectDialogVisible = ref(false)
|
||||
const rejectReason = ref('')
|
||||
const rejectTargetRow = ref(null)
|
||||
const rejectMode = ref('single')
|
||||
|
||||
const isAutoMode = computed(() => filters.dakuanMode === 2)
|
||||
|
||||
const getFullImageUrl = (url) => {
|
||||
if (!url) return ''
|
||||
const ossUrl = window.$ossURL || ''
|
||||
return url.startsWith('http') ? url : ossUrl + url
|
||||
}
|
||||
|
||||
const formatTime = (row, column, cellValue) => {
|
||||
if (!cellValue) return '--'
|
||||
const date = new Date(cellValue)
|
||||
return `${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2,'0')}-${date.getDate().toString().padStart(2,'0')} ${date.getHours().toString().padStart(2,'0')}:${date.getMinutes().toString().padStart(2,'0')}`
|
||||
}
|
||||
|
||||
const getPaymentText = (val) => {
|
||||
const map = { 1: '微信', 2: '支付宝' }
|
||||
return map[val] || '未知'
|
||||
}
|
||||
|
||||
const getTypeText = (val) => {
|
||||
const map = {
|
||||
1: '打手佣金',
|
||||
2: '管事分红',
|
||||
3: '组长分红',
|
||||
4: '审核官分佣',
|
||||
5: '打手押金',
|
||||
6: '商家余额'
|
||||
}
|
||||
return map[val] || '未知'
|
||||
}
|
||||
|
||||
const getDakuanModeText = (val) => {
|
||||
if (val === 2) return '自动'
|
||||
return '手动'
|
||||
}
|
||||
|
||||
const statusMap = {
|
||||
1: '审核中',
|
||||
2: '提现成功',
|
||||
3: '提现失败',
|
||||
4: '审核成功',
|
||||
5: '审核失败',
|
||||
6: '待收款'
|
||||
}
|
||||
const getStatusText = (code) => statusMap[code] || '未知'
|
||||
const getStatusType = (code) => {
|
||||
if (code === 1 || code === 6) return 'warning'
|
||||
if (code === 2 || code === 4) return 'success'
|
||||
if (code === 3 || code === 5) return 'danger'
|
||||
return ''
|
||||
}
|
||||
|
||||
const isRowSelectable = (row) => row.zhuangtai === 1
|
||||
|
||||
/**
|
||||
* 与后端 kefu_txsh_action 对齐(自动打款必传四项):
|
||||
* tixian_id / yonghuid / leixing / shenhe_danhao
|
||||
*/
|
||||
const buildActionItem = (row) => ({
|
||||
tixian_id: Number(row.id),
|
||||
yonghuid: String(row.yonghuid || '').trim(),
|
||||
leixing: Number(row.leixing),
|
||||
shenhe_danhao: String(row.shenhe_danhao || '').trim()
|
||||
})
|
||||
|
||||
const isAutoRow = (row) => Number(row?.dakuan_mode) === 2 || isAutoMode.value
|
||||
|
||||
const validateActionRows = (rows) => {
|
||||
for (const row of rows) {
|
||||
if (!row?.id) {
|
||||
return '记录缺少 tixian_id(提现记录ID)'
|
||||
}
|
||||
if (!row.yonghuid) {
|
||||
return `记录缺少 yonghuid(用户ID),记录ID=${row.id}`
|
||||
}
|
||||
if (row.leixing == null || row.leixing === '') {
|
||||
return `记录缺少 leixing(提现类型),用户 ${row.yonghuid}`
|
||||
}
|
||||
if (isAutoRow(row) && !row.shenhe_danhao) {
|
||||
return `记录缺少 shenhe_danhao(审核单号),用户 ${row.yonghuid},请确认列表接口已返回该字段`
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const submitWithdrawAction = async (payload) => {
|
||||
const res = await request.post('/yonghu/kefu_txsh_action', {
|
||||
phone: localStorage.getItem('username'),
|
||||
...payload
|
||||
})
|
||||
if (parseApiCode(res) === 0) {
|
||||
ElMessage.success(res?.msg || '处理成功')
|
||||
rejectDialogVisible.value = false
|
||||
rejectTargetRow.value = null
|
||||
selectedRows.value = []
|
||||
fetchData()
|
||||
return true
|
||||
}
|
||||
ElMessage.error(getApiErrorMsg(res, '操作失败'))
|
||||
return false
|
||||
}
|
||||
|
||||
const saveFiltersToStorage = () => {
|
||||
const toStore = {
|
||||
searchUid: filters.searchUid,
|
||||
date: filters.date,
|
||||
type: filters.type,
|
||||
payment: filters.payment,
|
||||
minAmount: filters.minAmount,
|
||||
maxAmount: filters.maxAmount,
|
||||
statusFilter: statusFilter.value,
|
||||
dakuanMode: filters.dakuanMode
|
||||
}
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(toStore))
|
||||
}
|
||||
|
||||
const loadFiltersFromStorage = () => {
|
||||
const stored = localStorage.getItem(STORAGE_KEY)
|
||||
if (!stored) return
|
||||
try {
|
||||
const parsed = JSON.parse(stored)
|
||||
if (parsed.searchUid !== undefined) filters.searchUid = parsed.searchUid
|
||||
if (parsed.date !== undefined) filters.date = parsed.date
|
||||
if (parsed.type !== undefined && [1, 2, 3, 4, 5, 6].includes(parsed.type)) filters.type = parsed.type
|
||||
if (parsed.payment !== undefined && [0, 1, 2].includes(parsed.payment)) filters.payment = parsed.payment
|
||||
if (parsed.minAmount !== undefined) filters.minAmount = parsed.minAmount
|
||||
if (parsed.maxAmount !== undefined) filters.maxAmount = parsed.maxAmount
|
||||
if (parsed.statusFilter !== undefined && [1, 2].includes(parsed.statusFilter)) statusFilter.value = parsed.statusFilter
|
||||
if (parsed.dakuanMode !== undefined && [1, 2].includes(parsed.dakuanMode)) filters.dakuanMode = parsed.dakuanMode
|
||||
} catch (e) {
|
||||
console.error('恢复筛选条件失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
const buildListParams = () => {
|
||||
const params = {
|
||||
phone: localStorage.getItem('username'),
|
||||
page: page.value,
|
||||
page_size: pageSize.value,
|
||||
status: statusFilter.value,
|
||||
dakuan_mode: filters.dakuanMode,
|
||||
type: filters.type || undefined,
|
||||
payment: filters.payment === 0 ? undefined : filters.payment,
|
||||
search_uid: filters.searchUid || undefined,
|
||||
date: filters.date || undefined
|
||||
}
|
||||
if (filters.minAmount !== null && filters.minAmount !== '') {
|
||||
params.min_amount = filters.minAmount
|
||||
}
|
||||
if (filters.maxAmount !== null && filters.maxAmount !== '') {
|
||||
params.max_amount = filters.maxAmount
|
||||
}
|
||||
Object.keys(params).forEach(key => params[key] === undefined && delete params[key])
|
||||
return params
|
||||
}
|
||||
|
||||
const parseApiCode = (res) => Number(res?.code)
|
||||
|
||||
const getApiErrorMsg = (res, fallback = '获取失败') => {
|
||||
return res?.msg || res?.message || fallback
|
||||
}
|
||||
|
||||
const extractRequestError = (error, fallback = '获取失败') => {
|
||||
return error?.response?.data?.msg || error?.response?.data?.message || error?.message || fallback
|
||||
}
|
||||
|
||||
const fetchData = async () => {
|
||||
const phone = localStorage.getItem('username')
|
||||
if (!phone) {
|
||||
ElMessage.error('未登录或缺少客服手机号')
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
selectedRows.value = []
|
||||
try {
|
||||
const res = await request.post('/yonghu/kefu_txsh_list', buildListParams())
|
||||
const code = parseApiCode(res)
|
||||
|
||||
if (code === 0) {
|
||||
const data = res?.data || {}
|
||||
list.value = Array.isArray(data.list) ? data.list : []
|
||||
total.value = Number(data.total) || 0
|
||||
stats.value = {
|
||||
total: 0,
|
||||
awaiting: 0,
|
||||
processed: 0,
|
||||
total_amount: '0.00',
|
||||
...(data.stats || {})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (code === 403) {
|
||||
ElMessage.error(getApiErrorMsg(res, '您无权查看提现列表'))
|
||||
return
|
||||
}
|
||||
|
||||
ElMessage.error(getApiErrorMsg(res))
|
||||
} catch (error) {
|
||||
console.error('获取提现列表失败', error, error?.response?.data)
|
||||
ElMessage.error(extractRequestError(error))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDakuanModeChange = () => {
|
||||
page.value = 1
|
||||
selectedRows.value = []
|
||||
fetchData()
|
||||
saveFiltersToStorage()
|
||||
}
|
||||
|
||||
const changeStatus = (status) => {
|
||||
if (statusFilter.value === status) return
|
||||
statusFilter.value = status
|
||||
page.value = 1
|
||||
selectedRows.value = []
|
||||
fetchData()
|
||||
saveFiltersToStorage()
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
page.value = 1
|
||||
fetchData()
|
||||
saveFiltersToStorage()
|
||||
}
|
||||
|
||||
const resetFilters = () => {
|
||||
filters.searchUid = ''
|
||||
filters.date = ''
|
||||
filters.type = ''
|
||||
filters.payment = 0
|
||||
filters.minAmount = null
|
||||
filters.maxAmount = null
|
||||
filters.dakuanMode = 1
|
||||
statusFilter.value = 1
|
||||
page.value = 1
|
||||
localStorage.removeItem(STORAGE_KEY)
|
||||
fetchData()
|
||||
}
|
||||
|
||||
const refreshList = () => {
|
||||
fetchData()
|
||||
}
|
||||
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
page.value = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
const handlePageChange = (val) => {
|
||||
page.value = val
|
||||
fetchData()
|
||||
}
|
||||
|
||||
const handleRowClick = (row) => {
|
||||
goToDetail(row)
|
||||
}
|
||||
|
||||
const goToDetail = (row) => {
|
||||
const ctx = {
|
||||
dakuan_mode: row.dakuan_mode ?? filters.dakuanMode,
|
||||
shenhe_danhao: row.shenhe_danhao || '',
|
||||
yonghuid: row.yonghuid || '',
|
||||
leixing: row.leixing
|
||||
}
|
||||
sessionStorage.setItem(`withdraw_detail_ctx_${row.id}`, JSON.stringify(ctx))
|
||||
router.push({
|
||||
path: `/withdraw/detail/${row.id}`,
|
||||
query: {
|
||||
dakuan_mode: String(ctx.dakuan_mode),
|
||||
shenhe_danhao: ctx.shenhe_danhao
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleSelectionChange = (rows) => {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
const handleInlineApprove = async (row) => {
|
||||
const validateMsg = validateActionRows([row])
|
||||
if (validateMsg) {
|
||||
ElMessage.error(validateMsg)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确认同意用户 ${row.nicheng || row.yonghuid} 的提现申请(¥${row.jine})吗?`,
|
||||
'确认打款',
|
||||
{ confirmButtonText: '确认同意', cancelButtonText: '取消', type: 'warning' }
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
rowOperating.value = true
|
||||
try {
|
||||
await submitWithdrawAction({
|
||||
action: 1,
|
||||
...buildActionItem(row)
|
||||
})
|
||||
} catch (error) {
|
||||
ElMessage.error('操作失败')
|
||||
} finally {
|
||||
rowOperating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const showInlineRejectDialog = (row) => {
|
||||
rejectMode.value = 'single'
|
||||
rejectTargetRow.value = row
|
||||
rejectReason.value = ''
|
||||
rejectDialogVisible.value = true
|
||||
}
|
||||
|
||||
const showBatchRejectDialog = () => {
|
||||
if (selectedRows.value.length === 0) {
|
||||
ElMessage.warning('请先选择待审核记录')
|
||||
return
|
||||
}
|
||||
const validateMsg = validateActionRows(selectedRows.value)
|
||||
if (validateMsg) {
|
||||
ElMessage.error(validateMsg)
|
||||
return
|
||||
}
|
||||
rejectMode.value = 'batch'
|
||||
rejectTargetRow.value = null
|
||||
rejectReason.value = ''
|
||||
rejectDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleRejectSubmit = async () => {
|
||||
if (!rejectReason.value.trim()) {
|
||||
ElMessage.warning('请输入驳回理由')
|
||||
return
|
||||
}
|
||||
|
||||
const reason = rejectReason.value.trim()
|
||||
const rows = rejectMode.value === 'batch' ? selectedRows.value : [rejectTargetRow.value]
|
||||
const validateMsg = validateActionRows(rows.filter(Boolean))
|
||||
if (validateMsg) {
|
||||
ElMessage.error(validateMsg)
|
||||
return
|
||||
}
|
||||
|
||||
const loadingRef = rejectMode.value === 'batch' ? batchOperating : rowOperating
|
||||
loadingRef.value = true
|
||||
try {
|
||||
if (rejectMode.value === 'batch') {
|
||||
await submitWithdrawAction({
|
||||
action: 2,
|
||||
reason,
|
||||
batch_list: selectedRows.value.map(buildActionItem)
|
||||
})
|
||||
} else {
|
||||
await submitWithdrawAction({
|
||||
action: 2,
|
||||
reason,
|
||||
...buildActionItem(rejectTargetRow.value)
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('操作失败')
|
||||
} finally {
|
||||
loadingRef.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleBatchApprove = async () => {
|
||||
if (selectedRows.value.length === 0) {
|
||||
ElMessage.warning('请先选择待审核记录')
|
||||
return
|
||||
}
|
||||
const validateMsg = validateActionRows(selectedRows.value)
|
||||
if (validateMsg) {
|
||||
ElMessage.error(validateMsg)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确认批量同意 ${selectedRows.value.length} 条提现申请吗?`,
|
||||
'批量同意打款',
|
||||
{ confirmButtonText: '确认同意', cancelButtonText: '取消', type: 'warning' }
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
batchOperating.value = true
|
||||
try {
|
||||
await submitWithdrawAction({
|
||||
action: 1,
|
||||
batch_list: selectedRows.value.map(buildActionItem)
|
||||
})
|
||||
} catch (error) {
|
||||
ElMessage.error('操作失败')
|
||||
} finally {
|
||||
batchOperating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadFiltersFromStorage()
|
||||
page.value = 1
|
||||
fetchData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.withdraw-audit {
|
||||
padding: 20px;
|
||||
background-color: #f5f9fc;
|
||||
min-height: 100%;
|
||||
}
|
||||
.stats-cards {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.stat-card {
|
||||
border-radius: 12px;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 16px rgba(0,0,0,0.05);
|
||||
}
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #6b7a88;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #1a2634;
|
||||
}
|
||||
.stat-value.warning {
|
||||
color: #e6a23c;
|
||||
}
|
||||
.stat-value.success {
|
||||
color: #67c23a;
|
||||
}
|
||||
.total-amount {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.total-label {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #1a2634;
|
||||
}
|
||||
.total-value {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #f56c6c;
|
||||
}
|
||||
.filter-card {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.filter-second-row {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.mode-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
.mode-switch-label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1a2634;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.status-switch {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.status-switch .el-button {
|
||||
flex: 1;
|
||||
}
|
||||
.batch-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 12px;
|
||||
padding: 12px 16px;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.02);
|
||||
}
|
||||
.batch-tip {
|
||||
font-size: 14px;
|
||||
color: #6b7a88;
|
||||
}
|
||||
.table-card {
|
||||
border-radius: 12px;
|
||||
}
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
@media screen and (max-width: 768px) {
|
||||
.withdraw-audit {
|
||||
padding: 12px;
|
||||
}
|
||||
.filter-actions {
|
||||
margin-top: 12px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.mode-switch {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.status-switch {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.pagination {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user