优化数据归属问题

This commit is contained in:
2026-04-23 15:08:37 +08:00
parent 378be9909d
commit 0373234750
29 changed files with 1993 additions and 75 deletions

View File

@@ -0,0 +1,277 @@
<template>
<div class="default-main ba-table-box">
<el-alert class="ba-table-alert" v-if="baTable.table.remark" :title="baTable.table.remark" type="info" show-icon />
<TableHeader
:buttons="['refresh', 'comSearch', 'quickSearch', 'columnDisplay']"
:quick-search-placeholder="t('Quick search placeholder', { fields: t('order.adminWithdrawOrder.quick Search Fields') })"
/>
<div class="withdraw-stats-cards">
<el-card shadow="never" class="withdraw-stat-card">
<div class="label">{{ t('order.adminWithdrawOrder.stat_total_count') }}</div>
<div class="value">{{ stats.total_count }}</div>
</el-card>
<el-card shadow="never" class="withdraw-stat-card">
<div class="label">{{ t('order.adminWithdrawOrder.stat_pending_count') }}</div>
<div class="value">{{ stats.pending_count }}</div>
</el-card>
<el-card shadow="never" class="withdraw-stat-card">
<div class="label">{{ t('order.adminWithdrawOrder.stat_pending_amount') }}</div>
<div class="value">{{ stats.pending_amount }}</div>
</el-card>
<el-card shadow="never" class="withdraw-stat-card">
<div class="label">{{ t('order.adminWithdrawOrder.stat_approved_amount') }}</div>
<div class="value">{{ stats.approved_amount }}</div>
</el-card>
</div>
<div class="withdraw-filter-row">
<el-radio-group v-model="statusFilterMode" size="small" @change="onStatusFilterChange">
<el-radio-button label="all">{{ t('order.adminWithdrawOrder.filter_all') }}</el-radio-button>
<el-radio-button label="pending">{{ t('order.adminWithdrawOrder.filter_pending') }}</el-radio-button>
<el-radio-button label="approved">{{ t('order.adminWithdrawOrder.filter_approved') }}</el-radio-button>
<el-radio-button label="rejected">{{ t('order.adminWithdrawOrder.filter_rejected') }}</el-radio-button>
</el-radio-group>
<el-select v-model="receiveTypeFilterMode" class="receive-type-filter" @change="onStatusFilterChange">
<el-option :label="t('order.adminWithdrawOrder.filter_receive_type_all')" value="all" />
<el-option :label="t('order.adminWithdrawOrder.receive_type_bank')" value="bank" />
<el-option :label="t('order.adminWithdrawOrder.receive_type_ewallet')" value="ewallet" />
<el-option :label="t('order.adminWithdrawOrder.receive_type_crypto')" value="crypto" />
</el-select>
</div>
<Table ref="tableRef"></Table>
</div>
</template>
<script setup lang="ts">
import { onMounted, provide, reactive, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessageBox } from 'element-plus'
import { baTableApi } from '/@/api/common'
import TableHeader from '/@/components/table/header/index.vue'
import Table from '/@/components/table/index.vue'
import baTableClass from '/@/utils/baTable'
import createAxios from '/@/utils/axios'
defineOptions({
name: 'order/adminWithdrawOrder',
})
const { t } = useI18n()
const tableRef = useTemplateRef('tableRef')
const statusFilterMode = ref<'all' | 'pending' | 'approved' | 'rejected'>('all')
const receiveTypeFilterMode = ref<'all' | 'bank' | 'ewallet' | 'crypto'>('all')
const stats = reactive({
total_count: 0,
pending_count: 0,
approved_count: 0,
rejected_count: 0,
total_amount: '0.00',
pending_amount: '0.00',
approved_amount: '0.00',
})
const onReview = async (row: anyObj, action: 'approve' | 'reject') => {
const needReason = action === 'reject'
const { value } = await ElMessageBox.prompt(
needReason ? t('order.adminWithdrawOrder.reject_reason_required') : t('order.adminWithdrawOrder.review_remark_optional'),
action === 'approve' ? t('order.adminWithdrawOrder.review_approve_title') : t('order.adminWithdrawOrder.review_reject_title'),
{
confirmButtonText: t('Confirm'),
cancelButtonText: t('Cancel'),
inputPlaceholder: needReason ? t('order.adminWithdrawOrder.reject_reason_required') : t('order.adminWithdrawOrder.review_remark_optional'),
inputPattern: needReason ? /.+/ : undefined,
inputErrorMessage: needReason ? t('order.adminWithdrawOrder.reject_reason_required') : '',
})
await createAxios(
{
url: `/admin/order.AdminWithdrawOrder/${action}`,
method: 'post',
data: { id: row.id, remark: value || '' },
},
{ showSuccessMessage: true }
)
await loadStats()
baTable.getData()
}
const optButtons: OptButton[] = [
{
render: 'tipButton',
name: 'approve',
title: 'order.adminWithdrawOrder.review_btn_approve',
text: '',
type: 'success',
icon: 'el-icon-Check',
display: (row: TableRow) => Number(row.status) === 0,
click: (row: TableRow) => void onReview(row as anyObj, 'approve'),
},
{
render: 'tipButton',
name: 'reject',
title: 'order.adminWithdrawOrder.review_btn_reject',
text: '',
type: 'danger',
icon: 'el-icon-Close',
display: (row: TableRow) => Number(row.status) === 0,
click: (row: TableRow) => void onReview(row as anyObj, 'reject'),
},
]
const baTable = new baTableClass(
new baTableApi('/admin/order.AdminWithdrawOrder/'),
{
pk: 'id',
column: [
{ type: 'selection', align: 'center', operator: false },
{ label: t('order.adminWithdrawOrder.id'), prop: 'id', align: 'center', width: 70, operator: 'RANGE', sortable: 'custom' },
{ label: t('order.adminWithdrawOrder.order_no'), prop: 'order_no', align: 'center', minWidth: 170, operator: 'LIKE' },
{ label: t('order.adminWithdrawOrder.admin_username'), prop: 'admin.username', align: 'center', minWidth: 120, operator: 'LIKE' },
{ label: t('order.adminWithdrawOrder.channel_name'), prop: 'channel.name', align: 'center', minWidth: 120, operator: 'LIKE' },
{ label: t('order.adminWithdrawOrder.amount'), prop: 'amount', align: 'center', minWidth: 100, operator: 'RANGE' },
{ label: t('order.adminWithdrawOrder.actual_amount'), prop: 'actual_amount', align: 'center', minWidth: 100, operator: 'RANGE' },
{
label: t('order.adminWithdrawOrder.status'),
prop: 'status',
align: 'center',
operator: 'eq',
render: 'tag',
custom: { 0: 'warning', 1: 'success', 2: 'danger' },
replaceValue: {
0: t('order.adminWithdrawOrder.status 0'),
1: t('order.adminWithdrawOrder.status 1'),
2: t('order.adminWithdrawOrder.status 2'),
},
},
{
label: t('order.adminWithdrawOrder.receive_type'),
prop: 'receive_type',
align: 'center',
minWidth: 110,
operator: 'eq',
render: 'tag',
custom: { bank: 'primary', ewallet: 'success', crypto: 'warning' },
replaceValue: {
bank: t('order.adminWithdrawOrder.receive_type_bank'),
ewallet: t('order.adminWithdrawOrder.receive_type_ewallet'),
crypto: t('order.adminWithdrawOrder.receive_type_crypto'),
},
},
{
label: t('order.adminWithdrawOrder.receive_account'),
prop: 'receive_account',
align: 'center',
minWidth: 160,
operator: 'LIKE',
showOverflowTooltip: true,
},
{ label: t('order.adminWithdrawOrder.review_admin_username'), prop: 'reviewAdmin.username', align: 'center', minWidth: 120, operator: 'LIKE' },
{ label: t('order.adminWithdrawOrder.remark'), prop: 'remark', align: 'center', minWidth: 180, operator: 'LIKE', showOverflowTooltip: true },
{
label: t('order.adminWithdrawOrder.create_time'),
prop: 'create_time',
align: 'center',
render: 'datetime',
operator: 'RANGE',
comSearchRender: 'datetime',
sortable: 'custom',
width: 160,
timeFormat: 'yyyy-mm-dd hh:MM:ss',
},
{ label: t('Operate'), align: 'center', width: 90, render: 'buttons', buttons: optButtons, operator: false, fixed: 'right' },
],
},
{}
)
const onStatusFilterChange = () => {
baTable.getData()
}
const loadStats = async () => {
const res = await createAxios({ url: '/admin/order.AdminWithdrawOrder/stats', method: 'get' })
if (res.code !== 1 || !res.data) {
return
}
stats.total_count = Number(res.data.total_count ?? 0)
stats.pending_count = Number(res.data.pending_count ?? 0)
stats.approved_count = Number(res.data.approved_count ?? 0)
stats.rejected_count = Number(res.data.rejected_count ?? 0)
stats.total_amount = String(res.data.total_amount ?? '0.00')
stats.pending_amount = String(res.data.pending_amount ?? '0.00')
stats.approved_amount = String(res.data.approved_amount ?? '0.00')
}
baTable.before.getData = () => {
const filter = baTable.table.filter || {}
const searchRaw = filter.search
const search = Array.isArray(searchRaw)
? searchRaw.filter((item: any) => item && item.field !== 'status' && item.field !== 'receive_type')
: []
if (statusFilterMode.value === 'pending') {
search.push({ field: 'status', operator: 'eq', val: 0 })
} else if (statusFilterMode.value === 'approved') {
search.push({ field: 'status', operator: 'eq', val: 1 })
} else if (statusFilterMode.value === 'rejected') {
search.push({ field: 'status', operator: 'eq', val: 2 })
}
if (receiveTypeFilterMode.value !== 'all') {
search.push({ field: 'receive_type', operator: 'eq', val: receiveTypeFilterMode.value })
}
filter.search = search
baTable.table.filter = filter
}
baTable.after.getData = () => {
void loadStats()
}
provide('baTable', baTable)
onMounted(() => {
baTable.table.ref = tableRef.value
baTable.mount()
baTable.getData()?.then(() => {
baTable.initSort()
baTable.dragSort()
})
void loadStats()
})
</script>
<style scoped lang="scss">
.withdraw-stats-cards {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
margin: 10px 0;
}
.withdraw-stat-card .label {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.withdraw-stat-card .value {
margin-top: 6px;
font-size: 20px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.withdraw-filter-row {
margin-bottom: 10px;
display: flex;
gap: 10px;
align-items: center;
}
.receive-type-filter {
width: 180px;
}
@media (max-width: 900px) {
.withdraw-stats-cards {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
</style>