Files
webman-buildadmin/web/src/views/backend/user/user/index.vue
2026-04-18 15:19:36 +08:00

439 lines
15 KiB
Vue

<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', 'add', 'edit', 'delete', 'comSearch', 'quickSearch', 'columnDisplay']"
:quick-search-placeholder="t('Quick search placeholder', { fields: t('user.user.quick Search Fields') })"
></TableHeader>
<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, 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'])
function formatCoin(_row: anyObj, _column: any, cellValue: unknown) {
if (cellValue === null || cellValue === undefined || cellValue === '') {
return '-'
}
const s = String(cellValue).trim().replace(',', '.')
const n = parseFloat(s)
if (!Number.isFinite(n)) {
return String(cellValue)
}
return n.toFixed(2)
}
/** 杩斿洖澶氭爣绛炬枃妗堟暟缁勶紝渚?render: tags 浣跨敤 */
function formatRiskFlags(row: anyObj, _column: any, cellValue: unknown) {
const raw = cellValue !== undefined && cellValue !== null && cellValue !== '' ? cellValue : row.risk_flags
const f = typeof raw === 'number' ? raw : parseInt(String(raw ?? '0'), 10) || 0
const parts: string[] = []
if (f & 1) parts.push(t('user.user.risk_no_login'))
if (f & 2) parts.push(t('user.user.risk_no_bet'))
if (f & 4) parts.push(t('user.user.risk_no_withdraw'))
if (!parts.length) parts.push(t('user.user.risk_none'))
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/'),
{
pk: 'id',
column: [
{ type: 'selection', align: 'center', operator: false },
{ label: t('user.user.id'), prop: 'id', align: 'center', width: 70, operator: 'RANGE', sortable: 'custom' },
{
label: t('user.user.username'),
prop: 'username',
align: 'center',
operatorPlaceholder: t('Fuzzy query'),
sortable: false,
operator: 'LIKE',
},
{ label: t('user.user.phone'), prop: 'phone', align: 'center', operatorPlaceholder: t('Fuzzy query'), sortable: false, operator: 'LIKE' },
{
label: t('user.user.email'),
prop: 'email',
align: 'center',
minWidth: 120,
showOverflowTooltip: true,
operatorPlaceholder: t('Fuzzy query'),
operator: 'LIKE',
},
{
label: t('user.user.head_image'),
prop: 'head_image',
align: 'center',
width: 72,
operator: false,
render: 'image',
},
{
label: t('user.user.uuid'),
prop: 'uuid',
align: 'center',
showOverflowTooltip: true,
operatorPlaceholder: t('Fuzzy query'),
sortable: false,
operator: 'LIKE',
},
{
label: t('user.user.register_invite_code'),
prop: 'register_invite_code',
align: 'center',
minWidth: 100,
showOverflowTooltip: true,
operator: 'LIKE',
},
{
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',
align: 'center',
minWidth: 110,
sortable: false,
operator: 'RANGE',
formatter: formatCoin,
},
{
label: t('user.user.total_withdraw_coin'),
prop: 'total_withdraw_coin',
align: 'center',
minWidth: 110,
sortable: false,
operator: 'RANGE',
formatter: formatCoin,
},
{
label: t('user.user.bet_flow_coin'),
prop: 'bet_flow_coin',
align: 'center',
minWidth: 110,
sortable: false,
operator: 'RANGE',
formatter: formatCoin,
},
{
label: t('user.user.risk_flags'),
prop: 'risk_flags',
align: 'center',
render: 'tags',
minWidth: 200,
operator: false,
formatter: formatRiskFlags,
custom: {
[t('user.user.risk_none')]: 'primary',
[t('user.user.risk_no_login')]: 'danger',
[t('user.user.risk_no_bet')]: 'danger',
[t('user.user.risk_no_withdraw')]: 'danger',
},
},
{ label: t('user.user.current_streak'), prop: 'current_streak', align: 'center', width: 90, operator: 'RANGE' },
{
label: t('user.user.last_bet_period_no'),
prop: 'last_bet_period_no',
align: 'center',
minWidth: 120,
showOverflowTooltip: true,
operator: 'LIKE',
},
{
label: t('user.user.status'),
prop: 'status',
align: 'center',
operator: 'eq',
sortable: false,
render: 'switch',
replaceValue: { '0': t('user.user.status 0'), '1': t('user.user.status 1') },
},
{
label: t('user.user.channel__name'),
prop: 'channel.name',
align: 'center',
minWidth: 100,
operatorPlaceholder: t('Fuzzy query'),
render: 'tags',
operator: 'LIKE',
comSearchRender: 'string',
},
{
label: t('user.user.admin__username'),
prop: 'admin.username',
align: 'center',
minWidth: 90,
effect: 'plain',
operatorPlaceholder: t('Fuzzy query'),
render: 'tags',
operator: 'LIKE',
comSearchRender: 'string',
customRenderAttr: {
tag: () => ({
color: '#e8f3ff',
style: { color: '#1677ff', borderColor: '#91caff' },
}),
},
},
{
label: t('user.user.remark'),
prop: 'remark',
align: 'center',
minWidth: 100,
showOverflowTooltip: true,
operatorPlaceholder: t('Fuzzy query'),
},
{
label: t('user.user.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('user.user.update_time'),
prop: 'update_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: 80, render: 'buttons', buttons: optButtons, operator: false, fixed: 'right' },
],
dblClickNotEditColumn: [undefined, 'status'],
},
{
defaultItems: {
status: '1',
coin: '0.00',
total_deposit_coin: '0.00',
total_withdraw_coin: '0.00',
bet_flow_coin: '0.00',
risk_flags: 0,
current_streak: 0,
last_bet_period_no: '',
register_invite_code: '',
email: '',
head_image: '',
},
}
)
provide('baTable', baTable)
onMounted(() => {
baTable.table.ref = tableRef.value
baTable.mount()
baTable.getData()?.then(() => {
baTable.initSort()
baTable.dragSort()
})
})
</script>
<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>