[游戏管理]用户管理-优化钱包操作
This commit is contained in:
@@ -9,7 +9,7 @@ export default {
|
||||
head_image: 'Avatar',
|
||||
remark: 'Remark',
|
||||
coin: 'Coin balance',
|
||||
coin_placeholder: 'decimal(18,4)',
|
||||
coin_placeholder: 'Amounts are displayed with 2 decimals',
|
||||
total_deposit_coin: 'Total deposit (coin)',
|
||||
total_valid_bet_coin: 'Total valid bet (coin)',
|
||||
risk_flags: 'Risk',
|
||||
@@ -42,4 +42,12 @@ export default {
|
||||
section_risk: 'Risk control',
|
||||
section_streak: 'Streak (fallback)',
|
||||
section_other: 'Other',
|
||||
wallet_adjust_title: 'Wallet adjustment',
|
||||
wallet_adjust_op: 'Operation',
|
||||
wallet_adjust_credit: 'Credit',
|
||||
wallet_adjust_deduct: 'Deduct',
|
||||
wallet_adjust_amount: 'Amount',
|
||||
wallet_adjust_amount_invalid: 'Please enter an amount greater than 0',
|
||||
wallet_adjust_operator_admin: 'operator admin',
|
||||
wallet_adjust_default_remark: 'Backend admin ({admin}) {action} {amount} (value)',
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
head_image: '头像',
|
||||
remark: '备注',
|
||||
coin: '余额',
|
||||
coin_placeholder: 'decimal(18,4),禁止业务用浮点存库',
|
||||
coin_placeholder: '金额展示统一两位小数',
|
||||
total_deposit_coin: '累计充值(币)',
|
||||
total_valid_bet_coin: '累计有效投注(币)',
|
||||
risk_flags: '风控',
|
||||
@@ -42,4 +42,12 @@
|
||||
section_risk: '风控',
|
||||
section_streak: '连胜(兜底)',
|
||||
section_other: '其他',
|
||||
wallet_adjust_title: '钱包加减点',
|
||||
wallet_adjust_op: '操作类型',
|
||||
wallet_adjust_credit: '加点',
|
||||
wallet_adjust_deduct: '扣点',
|
||||
wallet_adjust_amount: '操作金额',
|
||||
wallet_adjust_amount_invalid: '请输入大于0的金额',
|
||||
wallet_adjust_operator_admin: '操作管理员',
|
||||
wallet_adjust_default_remark: '后台管理员({admin}){action}{amount}(值)',
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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') })],
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user