[游戏管理]用户管理-优化钱包操作

This commit is contained in:
2026-04-17 14:34:22 +08:00
parent bf3d50a309
commit 2e0bcd3f23
6 changed files with 357 additions and 52 deletions

View File

@@ -10,24 +10,71 @@
<Table ref="tableRef"></Table>
<PopupForm />
<el-dialog v-model="walletDialogVisible" class="ba-operate-dialog wallet-adjust-dialog" :close-on-click-modal="false" width="520px">
<template #header>
<div class="title">{{ t('user.user.wallet_adjust_title') }}</div>
</template>
<el-scrollbar class="ba-table-form-scrollbar">
<div class="ba-operate-form wallet-adjust-form">
<el-form :label-position="config.layout.shrink ? 'top' : 'right'" :label-width="config.layout.shrink ? '' : '110px'">
<el-form-item :label="t('user.user.username')">
<el-input :model-value="walletForm.username" readonly />
</el-form-item>
<el-form-item :label="t('user.user.coin')">
<el-tag type="primary">{{ walletForm.current_coin }}</el-tag>
</el-form-item>
<el-form-item :label="t('user.user.wallet_adjust_op')">
<el-radio-group v-model="walletForm.op" class="wallet-adjust-op-group" @change="syncWalletRemark">
<el-radio label="credit">{{ t('user.user.wallet_adjust_credit') }}</el-radio>
<el-radio label="deduct">{{ t('user.user.wallet_adjust_deduct') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('user.user.wallet_adjust_amount')">
<el-input-number
class="wallet-adjust-amount-input"
v-model="walletForm.amount"
:min="0.01"
:step="0.01"
:precision="2"
@change="syncWalletRemark"
/>
</el-form-item>
<el-form-item :label="t('user.user.remark')">
<el-input v-model="walletForm.remark" type="textarea" :rows="3" />
</el-form-item>
</el-form>
</div>
</el-scrollbar>
<template #footer>
<div class="wallet-adjust-dialog-footer">
<el-button @click="walletDialogVisible = false">{{ t('Cancel') }}</el-button>
<el-button type="primary" :loading="walletSubmitting" @click="submitWalletAdjust">{{ t('Save') }}</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { onMounted, provide, useTemplateRef } from 'vue'
import { onMounted, provide, reactive, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import PopupForm from './popupForm.vue'
import { baTableApi } from '/@/api/common'
import { defaultOptButtons } from '/@/components/table'
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'
import { useConfig } from '/@/stores/config'
defineOptions({
name: 'user/user',
})
const { t } = useI18n()
const config = useConfig()
const tableRef = useTemplateRef('tableRef')
const optButtons: OptButton[] = defaultOptButtons(['edit', 'delete'])
@@ -40,7 +87,7 @@ function formatCoin(_row: anyObj, _column: any, cellValue: unknown) {
if (!Number.isFinite(n)) {
return String(cellValue)
}
return n.toFixed(4)
return n.toFixed(2)
}
/** 杩斿洖澶氭爣绛炬枃妗堟暟缁勶紝渚?render: tags 浣跨敤 */
@@ -55,6 +102,73 @@ function formatRiskFlags(row: anyObj, _column: any, cellValue: unknown) {
return parts
}
function buildDisplayAmount(v: unknown): string {
if (v === null || v === undefined || v === '') return '0.00'
const n = parseFloat(String(v).trim().replace(',', '.'))
if (!Number.isFinite(n)) return '0.00'
return n.toFixed(2)
}
const walletDialogVisible = ref(false)
const walletSubmitting = ref(false)
const walletForm = reactive({
user_id: 0,
username: '',
current_coin: '0.00',
op: 'credit',
amount: 100,
remark: '',
})
function syncWalletRemark() {
const action = walletForm.op === 'credit' ? t('user.user.wallet_adjust_credit') : t('user.user.wallet_adjust_deduct')
walletForm.remark = t('user.user.wallet_adjust_default_remark', {
admin: t('user.user.wallet_adjust_operator_admin'),
action,
amount: Number(walletForm.amount || 0).toFixed(2),
})
}
function openWalletDialog(row: anyObj) {
walletForm.user_id = Number(row.id || 0)
walletForm.username = String(row.username ?? '-')
walletForm.current_coin = buildDisplayAmount(row.coin)
walletForm.op = 'credit'
walletForm.amount = 100
syncWalletRemark()
walletDialogVisible.value = true
}
async function submitWalletAdjust() {
if (!walletForm.user_id) return
if (!(walletForm.amount > 0)) {
ElMessage.error(t('user.user.wallet_adjust_amount_invalid'))
return
}
walletSubmitting.value = true
try {
const res = await createAxios({
url: '/admin/user.User/walletAdjust',
method: 'post',
data: {
user_id: walletForm.user_id,
op: walletForm.op,
amount: Number(walletForm.amount).toFixed(2),
remark: walletForm.remark,
},
})
if (res.code === 1) {
ElMessage.success(res.msg || t('Success'))
walletDialogVisible.value = false
await baTable.getData()
} else {
ElMessage.error(res.msg || t('Unknown error'))
}
} finally {
walletSubmitting.value = false
}
}
const baTable = new baTableClass(
new baTableApi('/admin/user.User/'),
{
@@ -105,7 +219,22 @@ const baTable = new baTableClass(
showOverflowTooltip: true,
operator: 'LIKE',
},
{ label: t('user.user.coin'), prop: 'coin', align: 'center', sortable: false, operator: 'RANGE', formatter: formatCoin },
{
label: t('user.user.coin'),
prop: 'coin',
align: 'center',
minWidth: 100,
sortable: false,
operator: 'RANGE',
render: 'tag',
formatter: formatCoin,
customRenderAttr: {
tag: ({ row }) => ({
class: 'wallet-balance-tag',
onClick: () => openWalletDialog(row),
}),
},
},
{
label: t('user.user.total_deposit_coin'),
prop: 'total_deposit_coin',
@@ -221,9 +350,9 @@ const baTable = new baTableClass(
{
defaultItems: {
status: '1',
coin: '0.0000',
total_deposit_coin: '0.0000',
total_valid_bet_coin: '0.0000',
coin: '0.00',
total_deposit_coin: '0.00',
total_valid_bet_coin: '0.00',
risk_flags: 0,
current_streak: 0,
last_bet_period_no: '',
@@ -246,6 +375,54 @@ onMounted(() => {
})
</script>
<style scoped lang="scss"></style>
<style scoped lang="scss">
.wallet-balance-tag {
cursor: pointer;
}
.wallet-adjust-form :deep(.el-form-item) {
margin-bottom: 14px;
}
.wallet-adjust-op-group {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.wallet-adjust-amount-input {
width: 100%;
}
.wallet-adjust-amount-input :deep(.el-input__wrapper) {
width: 100%;
}
.wallet-adjust-dialog-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
}
@media (max-width: 768px) {
.wallet-adjust-dialog :deep(.el-dialog) {
width: calc(100vw - 16px) !important;
margin-top: 4vh !important;
}
.wallet-adjust-op-group {
gap: 8px 12px;
}
.wallet-adjust-dialog-footer {
display: flex;
gap: 8px;
}
.wallet-adjust-dialog-footer .el-button {
flex: 1;
}
}
</style>

View File

@@ -99,30 +99,6 @@
:placeholder="t('user.user.register_invite_code_auto_placeholder')"
/>
<el-divider content-position="left">{{ t('user.user.section_finance') }}</el-divider>
<FormItem
:label="t('user.user.coin')"
type="number"
v-model="baTable.form.items!.coin"
prop="coin"
:input-attr="{ step: 0.0001, min: 0, precision: 4 }"
:placeholder="t('user.user.coin_placeholder')"
/>
<FormItem
:label="t('user.user.total_deposit_coin')"
type="number"
v-model="baTable.form.items!.total_deposit_coin"
prop="total_deposit_coin"
:input-attr="{ step: 0.0001, min: 0, precision: 4 }"
/>
<FormItem
:label="t('user.user.total_valid_bet_coin')"
type="number"
v-model="baTable.form.items!.total_valid_bet_coin"
prop="total_valid_bet_coin"
:input-attr="{ step: 0.0001, min: 0, precision: 4 }"
/>
<el-divider content-position="left">{{ t('user.user.section_risk') }}</el-divider>
<el-form-item :label="t('user.user.risk_flags')">
<div class="risk-flag-row">
@@ -455,27 +431,10 @@ const validatorGameUserPassword = (rule: any, val: string, callback: (error?: Er
return callback()
}
const decimalRule = (fieldTitle: string): FormItemRule => ({
trigger: 'blur',
validator: (_rule, val, callback) => {
if (val === null || val === undefined || val === '') {
return callback()
}
const n = typeof val === 'number' ? val : parseFloat(String(val).trim().replace(',', '.'))
if (!Number.isFinite(n) || n < 0) {
return callback(new Error(t('Please enter the correct field', { field: fieldTitle })))
}
return callback()
},
})
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
username: [buildValidatorData({ name: 'required', title: t('user.user.username') })],
password: [{ validator: validatorGameUserPassword, trigger: 'blur' }],
phone: [buildValidatorData({ name: 'required', title: t('user.user.phone') })],
coin: [decimalRule(t('user.user.coin'))],
total_deposit_coin: [decimalRule(t('user.user.total_deposit_coin'))],
total_valid_bet_coin: [decimalRule(t('user.user.total_valid_bet_coin'))],
admin_id: [buildValidatorData({ name: 'required', title: t('user.user.admin_affiliation') })],
create_time: [buildValidatorData({ name: 'date', title: t('user.user.create_time') })],
update_time: [buildValidatorData({ name: 'date', title: t('user.user.update_time') })],

View File

@@ -35,7 +35,7 @@ function formatAmount(_row: anyObj, _column: any, cellValue: unknown) {
if (!Number.isFinite(n)) {
return String(cellValue)
}
return n.toFixed(4)
return n.toFixed(2)
}
const bizReplace = {