1.优化后端管理员提现方式

2.优化后端
This commit is contained in:
2026-04-23 14:11:55 +08:00
parent aa1299c018
commit 378be9909d
9 changed files with 362 additions and 175 deletions

View File

@@ -6,17 +6,55 @@
:buttons="['refresh', 'add', 'edit', 'delete', 'comSearch', 'quickSearch', 'columnDisplay']"
:quick-search-placeholder="t('Quick search placeholder', { fields: t('channel.quick Search Fields') })"
></TableHeader>
<div class="channel-top-actions">
<div class="channel-stats-cards">
<el-card shadow="never" class="channel-stat-card">
<div class="label">{{ t('channel.settle_stats_channel_total') }}</div>
<div class="value">{{ settleStats.channel_total }}</div>
</el-card>
<el-card shadow="never" class="channel-stat-card">
<div class="label">{{ t('channel.settle_stats_enabled') }}</div>
<div class="value">{{ settleStats.enabled_count }}</div>
</el-card>
<el-card shadow="never" class="channel-stat-card">
<div class="label">{{ t('channel.settle_stats_pending_dividend') }}</div>
<div class="value">{{ settleStats.carryover_positive_count }}</div>
</el-card>
<el-card shadow="never" class="channel-stat-card">
<div class="label">{{ t('channel.settle_stats_pending_amount') }}</div>
<div class="value">{{ settleStats.carryover_positive_total }}</div>
</el-card>
</div>
<div class="channel-action-row">
<el-radio-group v-model="settleFilterMode" size="small" @change="onSettleFilterChange">
<el-radio-button label="all">{{ t('channel.settle_filter_all') }}</el-radio-button>
<el-radio-button label="with_balance">{{ t('channel.settle_filter_with_balance') }}</el-radio-button>
<el-radio-button label="no_balance">{{ t('channel.settle_filter_no_balance') }}</el-radio-button>
<el-radio-button label="enabled">{{ t('channel.settle_filter_enabled') }}</el-radio-button>
<el-radio-button label="disabled">{{ t('channel.settle_filter_disabled') }}</el-radio-button>
</el-radio-group>
<el-button v-if="auth('batchSettlePending')" type="warning" @click="onBatchSettlePending">
{{ t('channel.batch_settle_pending') }}
</el-button>
</div>
</div>
<Table ref="tableRef"></Table>
<PopupForm />
<el-dialog class="ba-operate-dialog" :close-on-click-modal="false" :model-value="manualSettle.visible" @close="closeManualSettleDialog">
<el-dialog
class="ba-operate-dialog manual-settle-dialog"
:close-on-click-modal="false"
:model-value="manualSettle.visible"
width="860px"
@close="closeManualSettleDialog"
>
<template #header>
<div class="title">{{ t('channel.manual_settle') }}</div>
</template>
<div v-loading="manualSettle.previewLoading" class="manual-settle-dialog-body">
<el-form :model="manualSettle.form" label-width="140px">
<el-form :model="manualSettle.form" label-width="140px" class="manual-settle-form">
<el-form-item :label="t('channel.manual_settle_settlement_no')">
<el-input v-model="manualSettle.form.settlement_no" readonly />
</el-form-item>
@@ -44,8 +82,8 @@
<el-form-item :label="t('channel.manual_settle_commission_amount')">
<el-input v-model="manualSettle.form.commission_amount" readonly />
</el-form-item>
<el-form-item :label="t('channel.share_config')">
<el-table :data="manualSettle.form.commission_split" border size="small" class="w100">
<el-form-item :label="t('channel.share_config')" class="manual-settle-form-item-full">
<el-table :data="manualSettle.form.commission_split" border size="small" class="w100" max-height="220">
<el-table-column prop="admin_username" :label="t('channel.admin__username')" min-width="100" />
<el-table-column prop="share_rate" :label="t('channel.share_rate_percent')" min-width="90">
<template #default="scope">{{ scope.row.share_rate }}%</template>
@@ -53,16 +91,18 @@
<el-table-column prop="commission_amount" :label="t('channel.manual_settle_commission_amount')" min-width="110" />
</el-table>
</el-form-item>
<el-form-item :label="t('channel.manual_settle_remark')">
<el-form-item :label="t('channel.manual_settle_remark')" class="manual-settle-form-item-full">
<el-input v-model="manualSettle.form.remark" type="textarea" :rows="2" />
</el-form-item>
</el-form>
</div>
<template #footer>
<el-button @click="closeManualSettleDialog">{{ t('Cancel') }}</el-button>
<el-button type="primary" :disabled="manualSettle.previewLoading" :loading="manualSettle.loading" @click="submitManualSettle">
{{ t('Save') }}
</el-button>
<div class="manual-settle-footer">
<el-button @click="closeManualSettleDialog">{{ t('Cancel') }}</el-button>
<el-button type="primary" :disabled="manualSettle.previewLoading" :loading="manualSettle.loading" @click="submitManualSettle">
{{ t('Save') }}
</el-button>
</div>
</template>
</el-dialog>
@@ -115,7 +155,7 @@
</template>
<script setup lang="ts">
import { computed, onMounted, provide, reactive, useTemplateRef } from 'vue'
import { computed, onMounted, provide, reactive, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import PopupForm from './popupForm.vue'
@@ -207,6 +247,15 @@ const shareDialog = reactive({
channelId: 0,
list: [] as Array<{ admin_id: number; username: string; role_group_name: string; role_level: number; status: number; share_rate: number | null }>,
})
const settleFilterMode = ref<'all' | 'with_balance' | 'no_balance' | 'enabled' | 'disabled'>('all')
const settleStats = reactive({
channel_total: 0,
enabled_count: 0,
disabled_count: 0,
carryover_positive_count: 0,
carryover_total: '0.00',
carryover_positive_total: '0.00',
})
const shareEnabledTotal = computed(() => {
let sum = 0
@@ -370,6 +419,35 @@ const submitManualSettle = async () => {
}
}
const onBatchSettlePending = async () => {
await createAxios(
{
url: '/admin/channel/batchSettlePending',
method: 'post',
},
{ showSuccessMessage: true }
)
await loadSettleStats()
baTable.onTableHeaderAction('refresh', { event: 'batch-settle' })
}
const onSettleFilterChange = () => {
baTable.getData()
}
const loadSettleStats = async () => {
const res = await createAxios({ url: '/admin/channel/settleStats', method: 'get' })
if (res.code !== 1 || !res.data) {
return
}
settleStats.channel_total = Number(res.data.channel_total ?? 0)
settleStats.enabled_count = Number(res.data.enabled_count ?? 0)
settleStats.disabled_count = Number(res.data.disabled_count ?? 0)
settleStats.carryover_positive_count = Number(res.data.carryover_positive_count ?? 0)
settleStats.carryover_total = String(res.data.carryover_total ?? '0.00')
settleStats.carryover_positive_total = String(res.data.carryover_positive_total ?? '0.00')
}
const baTable = new baTableClass(
new baTableApi('/admin/channel/'),
{
@@ -564,6 +642,27 @@ const baTable = new baTableClass(
}
)
baTable.before.getData = () => {
const filter = baTable.table.filter || {}
const searchRaw = filter.search
const search = Array.isArray(searchRaw) ? searchRaw.filter((item: any) => item && item.field !== 'carryover_balance' && item.field !== 'status') : []
if (settleFilterMode.value === 'with_balance') {
search.push({ field: 'carryover_balance', operator: 'gt', val: 0 })
} else if (settleFilterMode.value === 'no_balance') {
search.push({ field: 'carryover_balance', operator: 'elt', val: 0 })
} else if (settleFilterMode.value === 'enabled') {
search.push({ field: 'status', operator: 'eq', val: 1 })
} else if (settleFilterMode.value === 'disabled') {
search.push({ field: 'status', operator: 'eq', val: 0 })
}
filter.search = search
baTable.table.filter = filter
}
baTable.after.getData = () => {
void loadSettleStats()
}
provide('baTable', baTable)
onMounted(() => {
@@ -573,6 +672,7 @@ onMounted(() => {
baTable.initSort()
baTable.dragSort()
})
void loadSettleStats()
})
</script>
@@ -597,4 +697,71 @@ onMounted(() => {
.share-group-empty {
color: var(--el-text-color-placeholder);
}
.channel-top-actions {
margin: 8px 0 12px;
}
.channel-stats-cards {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
margin-bottom: 10px;
}
.channel-stat-card .label {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.channel-stat-card .value {
margin-top: 6px;
font-size: 20px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.channel-action-row {
display: flex;
justify-content: space-between;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.manual-settle-dialog-body {
max-height: min(70vh, 680px);
overflow: auto;
padding-right: 2px;
}
.manual-settle-form {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
column-gap: 16px;
}
.manual-settle-form :deep(.el-form-item) {
margin-bottom: 12px;
}
.manual-settle-form-item-full {
grid-column: 1 / -1;
}
.manual-settle-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
}
@media (max-width: 900px) {
.channel-stats-cards {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.manual-settle-form {
grid-template-columns: 1fr;
}
}
</style>

View File

@@ -210,6 +210,7 @@ type TreeNode = {
const adminScopeTree = ref<TreeNode[]>([])
const adminIdToChannelId = ref<Record<string, number>>({})
const adminIdToInviteCode = ref<Record<string, string>>({})
const currentAdminId = ref('')
const treeProps = {
value: 'value',
@@ -309,6 +310,8 @@ const loadAdminScopeTree = async () => {
method: 'get',
})
const list = (res.data?.list ?? []) as TreeNode[]
const currentIdRaw = res.data?.current_admin_id
currentAdminId.value = currentIdRaw === undefined || currentIdRaw === null ? '' : String(currentIdRaw)
adminScopeTree.value = list
const { mapCh, mapInv } = buildAdminMapsFromTree(list)
@@ -316,6 +319,14 @@ const loadAdminScopeTree = async () => {
adminIdToInviteCode.value = mapInv
await nextTick()
if (
baTable.form.operate === 'Add' &&
baTable.form.items &&
(baTable.form.items.admin_id === undefined || baTable.form.items.admin_id === null || baTable.form.items.admin_id === '') &&
currentAdminId.value !== ''
) {
baTable.form.items.admin_id = currentAdminId.value
}
const aid = baTable.form.items?.admin_id
if (aid !== undefined && aid !== null && aid !== '') {
onAdminTreeChange(aid as string | number)
@@ -398,6 +409,14 @@ watch(
(op) => {
if (op === 'Add') {
syncRiskFromFlags(0)
if (
baTable.form.items &&
(baTable.form.items.admin_id === undefined || baTable.form.items.admin_id === null || baTable.form.items.admin_id === '') &&
currentAdminId.value !== ''
) {
baTable.form.items.admin_id = currentAdminId.value
onAdminTreeChange(currentAdminId.value)
}
}
}
)