优化数据归属问题
This commit is contained in:
@@ -6,6 +6,9 @@ export const actionUrl = new Map([
|
||||
['index', url + 'index'],
|
||||
['edit', url + 'edit'],
|
||||
['log', '/admin/auth.AdminLog/index'],
|
||||
['walletSummary', url + 'walletSummary'],
|
||||
['walletRecords', url + 'walletRecords'],
|
||||
['withdrawApply', url + 'withdrawApply'],
|
||||
])
|
||||
|
||||
export function index() {
|
||||
@@ -35,3 +38,31 @@ export function postData(data: anyObj) {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export function walletSummary() {
|
||||
return createAxios({
|
||||
url: actionUrl.get('walletSummary'),
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function walletRecords(filter: anyObj = {}) {
|
||||
return createAxios<TableDefaultData>({
|
||||
url: actionUrl.get('walletRecords'),
|
||||
method: 'get',
|
||||
params: filter,
|
||||
})
|
||||
}
|
||||
|
||||
export function withdrawApply(data: anyObj) {
|
||||
return createAxios(
|
||||
{
|
||||
url: actionUrl.get('withdrawApply'),
|
||||
method: 'post',
|
||||
data,
|
||||
},
|
||||
{
|
||||
showSuccessMessage: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -77,6 +77,16 @@ export default {
|
||||
share_rate_percent: 'Share rate(%)',
|
||||
share_total_enabled: 'Enabled total',
|
||||
share_total_must_100: 'Enabled share total must equal 100%',
|
||||
batch_settle_pending: 'Batch settle pending channels',
|
||||
settle_stats_channel_total: 'Total channels',
|
||||
settle_stats_enabled: 'Enabled channels',
|
||||
settle_stats_pending_dividend: 'Channels pending dividend',
|
||||
settle_stats_pending_amount: 'Pending dividend amount',
|
||||
settle_filter_all: 'All',
|
||||
settle_filter_with_balance: 'With dividend balance',
|
||||
settle_filter_no_balance: 'No dividend balance',
|
||||
settle_filter_enabled: 'Enabled only',
|
||||
settle_filter_disabled: 'Disabled only',
|
||||
admin_id_placeholder: 'Select an admin (within your permission scope)',
|
||||
admin__username: 'Person in charge',
|
||||
admin_group_names: 'Role group',
|
||||
|
||||
37
web/src/lang/backend/en/order/adminWithdrawOrder.ts
Normal file
37
web/src/lang/backend/en/order/adminWithdrawOrder.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export default {
|
||||
'quick Search Fields': 'Order no., receive account, remark',
|
||||
id: 'ID',
|
||||
order_no: 'Order No.',
|
||||
admin_username: 'Admin',
|
||||
channel_name: 'Channel',
|
||||
amount: 'Apply amount',
|
||||
actual_amount: 'Actual amount',
|
||||
status: 'Status',
|
||||
'status 0': 'Pending review',
|
||||
'status 1': 'Approved',
|
||||
'status 2': 'Rejected',
|
||||
receive_type: 'Receive type',
|
||||
receive_account: 'Receive account',
|
||||
review_admin_username: 'Reviewer',
|
||||
remark: 'Remark',
|
||||
create_time: 'Create time',
|
||||
review_btn_approve: 'Approve',
|
||||
review_btn_reject: 'Reject',
|
||||
review_approve_title: 'Approve order',
|
||||
review_reject_title: 'Reject order',
|
||||
review_remark_optional: 'Optional review remark',
|
||||
reject_reason_required: 'Please enter reject reason',
|
||||
stat_total_count: 'Total orders',
|
||||
stat_pending_count: 'Pending orders',
|
||||
stat_pending_amount: 'Pending amount',
|
||||
stat_approved_amount: 'Approved amount',
|
||||
filter_all: 'All',
|
||||
filter_pending: 'Pending',
|
||||
filter_approved: 'Approved',
|
||||
filter_rejected: 'Rejected',
|
||||
filter_receive_type_all: 'All receive types',
|
||||
receive_type_bank: 'Bank card',
|
||||
receive_type_ewallet: 'E-wallet',
|
||||
receive_type_crypto: 'Crypto address',
|
||||
}
|
||||
|
||||
@@ -11,4 +11,30 @@ export default {
|
||||
'Please leave blank if not modified': 'Please leave blank if you do not modify',
|
||||
'Save changes': 'Save changes',
|
||||
'Operation log': 'Operation log',
|
||||
admin_wallet: 'Admin wallet',
|
||||
withdraw: 'Withdraw',
|
||||
wallet_balance: 'Available balance',
|
||||
wallet_frozen_balance: 'Frozen balance',
|
||||
wallet_total_income: 'Total income',
|
||||
wallet_total_withdraw: 'Total withdraw',
|
||||
wallet_records: 'Wallet records',
|
||||
wallet_records_type: 'Type',
|
||||
wallet_records_direction: 'Direction',
|
||||
wallet_direction_in: 'In',
|
||||
wallet_direction_out: 'Out',
|
||||
wallet_records_amount: 'Amount',
|
||||
wallet_records_balance_after: 'Balance after',
|
||||
wallet_records_remark: 'Remark',
|
||||
wallet_records_time: 'Time',
|
||||
withdraw_apply_title: 'Admin withdraw apply',
|
||||
withdraw_coin: 'Withdraw amount',
|
||||
withdraw_coin_placeholder: 'Please input withdraw_coin (2 decimals)',
|
||||
receive_type: 'Receive type',
|
||||
receive_type_placeholder: 'Please select receive_type',
|
||||
receive_account: 'Receive account',
|
||||
receive_account_placeholder: 'Please input receive_account',
|
||||
idempotency_key: 'Idempotency key',
|
||||
idempotency_key_placeholder: 'Please input idempotency_key (optional, auto-generated if empty)',
|
||||
remark: 'Remark',
|
||||
submit_apply: 'Submit apply',
|
||||
}
|
||||
|
||||
@@ -77,6 +77,16 @@ export default {
|
||||
share_rate_percent: '分配比例(%)',
|
||||
share_total_enabled: '启用项合计',
|
||||
share_total_must_100: '启用项分配比例总和必须等于100%',
|
||||
batch_settle_pending: '一键批量结算待结算渠道',
|
||||
settle_stats_channel_total: '渠道总数',
|
||||
settle_stats_enabled: '启用渠道',
|
||||
settle_stats_pending_dividend: '待分红渠道',
|
||||
settle_stats_pending_amount: '待分红总额',
|
||||
settle_filter_all: '全部',
|
||||
settle_filter_with_balance: '有分红余额',
|
||||
settle_filter_no_balance: '无分红余额',
|
||||
settle_filter_enabled: '仅启用',
|
||||
settle_filter_disabled: '仅停用',
|
||||
admin_id_placeholder: '请选择管理员(仅当前权限范围内)',
|
||||
admin__username: '负责人',
|
||||
admin_group_names: '角色组',
|
||||
|
||||
37
web/src/lang/backend/zh-cn/order/adminWithdrawOrder.ts
Normal file
37
web/src/lang/backend/zh-cn/order/adminWithdrawOrder.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export default {
|
||||
'quick Search Fields': '订单号、收款账户、备注',
|
||||
id: 'ID',
|
||||
order_no: '订单号',
|
||||
admin_username: '管理员',
|
||||
channel_name: '渠道',
|
||||
amount: '申请金额',
|
||||
actual_amount: '实际金额',
|
||||
status: '状态',
|
||||
'status 0': '待审核',
|
||||
'status 1': '已通过',
|
||||
'status 2': '已拒绝',
|
||||
receive_type: '收款方式',
|
||||
receive_account: '收款账户',
|
||||
review_admin_username: '审核人',
|
||||
remark: '备注',
|
||||
create_time: '创建时间',
|
||||
review_btn_approve: '通过',
|
||||
review_btn_reject: '拒绝',
|
||||
review_approve_title: '通过审核',
|
||||
review_reject_title: '拒绝审核',
|
||||
review_remark_optional: '可选填写审核备注',
|
||||
reject_reason_required: '请填写拒绝原因',
|
||||
stat_total_count: '提现总单数',
|
||||
stat_pending_count: '待审核单数',
|
||||
stat_pending_amount: '待审核金额',
|
||||
stat_approved_amount: '已通过金额',
|
||||
filter_all: '全部',
|
||||
filter_pending: '待审核',
|
||||
filter_approved: '已通过',
|
||||
filter_rejected: '已拒绝',
|
||||
filter_receive_type_all: '全部收款方式',
|
||||
receive_type_bank: '银行卡',
|
||||
receive_type_ewallet: '电子钱包',
|
||||
receive_type_crypto: '加密地址',
|
||||
}
|
||||
|
||||
@@ -11,4 +11,30 @@ export default {
|
||||
'Please leave blank if not modified': '不修改请留空',
|
||||
'Save changes': '保存修改',
|
||||
'Operation log': '操作日志',
|
||||
admin_wallet: '管理员钱包',
|
||||
withdraw: '提现',
|
||||
wallet_balance: '可用余额',
|
||||
wallet_frozen_balance: '冻结余额',
|
||||
wallet_total_income: '累计入账',
|
||||
wallet_total_withdraw: '累计提现',
|
||||
wallet_records: '钱包流水',
|
||||
wallet_records_type: '类型',
|
||||
wallet_records_direction: '方向',
|
||||
wallet_direction_in: '入账',
|
||||
wallet_direction_out: '出账',
|
||||
wallet_records_amount: '金额',
|
||||
wallet_records_balance_after: '变动后余额',
|
||||
wallet_records_remark: '备注',
|
||||
wallet_records_time: '时间',
|
||||
withdraw_apply_title: '管理员提现申请',
|
||||
withdraw_coin: '提现金额',
|
||||
withdraw_coin_placeholder: '请输入 withdraw_coin(两位小数)',
|
||||
receive_type: '收款方式',
|
||||
receive_type_placeholder: '请选择 receive_type',
|
||||
receive_account: '收款账户',
|
||||
receive_account_placeholder: '请输入 receive_account',
|
||||
idempotency_key: '幂等键',
|
||||
idempotency_key_placeholder: '请输入 idempotency_key(可留空自动生成)',
|
||||
remark: '备注',
|
||||
submit_apply: '提交申请',
|
||||
}
|
||||
|
||||
277
web/src/views/backend/order/adminWithdrawOrder/index.vue
Normal file
277
web/src/views/backend/order/adminWithdrawOrder/index.vue
Normal 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>
|
||||
|
||||
@@ -27,6 +27,20 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-info-form">
|
||||
<el-card shadow="never" class="wallet-card">
|
||||
<template #header>
|
||||
<div class="wallet-card-header">
|
||||
<span>{{ t('routine.adminInfo.admin_wallet') }}</span>
|
||||
<el-button type="primary" link @click="state.withdrawDialogVisible = true">{{ t('routine.adminInfo.withdraw') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div class="wallet-metrics">
|
||||
<div>{{ t('routine.adminInfo.wallet_balance') }}:{{ state.wallet.balance }}</div>
|
||||
<div>{{ t('routine.adminInfo.wallet_frozen_balance') }}:{{ state.wallet.frozen_balance }}</div>
|
||||
<div>{{ t('routine.adminInfo.wallet_total_income') }}:{{ state.wallet.total_income }}</div>
|
||||
<div>{{ t('routine.adminInfo.wallet_total_withdraw') }}:{{ state.wallet.total_withdraw }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-form
|
||||
@keyup.enter="onSubmit()"
|
||||
:key="state.formKey"
|
||||
@@ -99,13 +113,40 @@
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog v-model="state.withdrawDialogVisible" :title="t('routine.adminInfo.withdraw_apply_title')" width="520px" :close-on-click-modal="false">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item :label="t('routine.adminInfo.withdraw_coin')">
|
||||
<el-input v-model="state.withdrawForm.withdraw_coin" :placeholder="t('routine.adminInfo.withdraw_coin_placeholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('routine.adminInfo.receive_type')">
|
||||
<el-select v-model="state.withdrawForm.receive_type" class="w100" :placeholder="t('routine.adminInfo.receive_type_placeholder')">
|
||||
<el-option label="bank" value="bank" />
|
||||
<el-option label="ewallet" value="ewallet" />
|
||||
<el-option label="crypto" value="crypto" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('routine.adminInfo.receive_account')">
|
||||
<el-input v-model="state.withdrawForm.receive_account" :placeholder="t('routine.adminInfo.receive_account_placeholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('routine.adminInfo.idempotency_key')">
|
||||
<el-input v-model="state.withdrawForm.idempotency_key" :placeholder="t('routine.adminInfo.idempotency_key_placeholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('routine.adminInfo.remark')">
|
||||
<el-input v-model="state.withdrawForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="state.withdrawDialogVisible = false">{{ t('Cancel') }}</el-button>
|
||||
<el-button type="primary" :loading="state.withdrawSubmitting" @click="onWithdrawApply">{{ t('routine.adminInfo.submit_apply') }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { index, log, postData } from '/@/api/backend/routine/AdminInfo'
|
||||
import { index, log, postData, walletSummary, withdrawApply } from '/@/api/backend/routine/AdminInfo'
|
||||
import type { FormItemRule } from 'element-plus'
|
||||
import { fullUrl, onResetForm, timeFormat } from '/@/utils/common'
|
||||
import { uuid } from '../../../utils/random'
|
||||
@@ -137,6 +178,10 @@ const state: {
|
||||
logPageSize: number
|
||||
logTotal: number
|
||||
logLoading: boolean
|
||||
wallet: { balance: string; frozen_balance: string; total_income: string; total_withdraw: string }
|
||||
withdrawDialogVisible: boolean
|
||||
withdrawSubmitting: boolean
|
||||
withdrawForm: { withdraw_coin: string; receive_type: string; receive_account: string; idempotency_key: string; remark: string }
|
||||
} = reactive({
|
||||
adminInfo: {},
|
||||
formKey: uuid(),
|
||||
@@ -149,6 +194,10 @@ const state: {
|
||||
logPageSize: 12,
|
||||
logTotal: 100,
|
||||
logLoading: true,
|
||||
wallet: { balance: '0.00', frozen_balance: '0.00', total_income: '0.00', total_withdraw: '0.00' },
|
||||
withdrawDialogVisible: false,
|
||||
withdrawSubmitting: false,
|
||||
withdrawForm: { withdraw_coin: '100.00', receive_type: 'bank', receive_account: '', idempotency_key: '', remark: '' },
|
||||
})
|
||||
|
||||
index().then((res) => {
|
||||
@@ -165,8 +214,15 @@ index().then((res) => {
|
||||
},
|
||||
]
|
||||
getLog()
|
||||
loadWalletSummary()
|
||||
})
|
||||
|
||||
const loadWalletSummary = () => {
|
||||
walletSummary().then((res) => {
|
||||
state.wallet = res.data?.wallet || state.wallet
|
||||
})
|
||||
}
|
||||
|
||||
const getLog = () => {
|
||||
log(state.logFilter)
|
||||
.then((res) => {
|
||||
@@ -233,6 +289,26 @@ const onSubmit = () => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onWithdrawApply = () => {
|
||||
state.withdrawSubmitting = true
|
||||
if (!state.withdrawForm.idempotency_key) {
|
||||
state.withdrawForm.idempotency_key = `admin_withdraw_${Date.now()}_${Math.floor(Math.random() * 100000)}`
|
||||
}
|
||||
withdrawApply({ ...state.withdrawForm })
|
||||
.then(() => {
|
||||
state.withdrawDialogVisible = false
|
||||
state.withdrawForm.withdraw_coin = '100.00'
|
||||
state.withdrawForm.receive_type = 'bank'
|
||||
state.withdrawForm.receive_account = ''
|
||||
state.withdrawForm.idempotency_key = ''
|
||||
state.withdrawForm.remark = ''
|
||||
loadWalletSummary()
|
||||
})
|
||||
.finally(() => {
|
||||
state.withdrawSubmitting = false
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -286,6 +362,24 @@ const onSubmit = () => {
|
||||
padding: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.wallet-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.wallet-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.wallet-metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.el-card :deep(.el-timeline-item__icon) {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user