游戏-用户管理-优化样式增强验证
This commit is contained in:
83
web/src/views/backend/game/user/GameUserTicketJsonCell.vue
Normal file
83
web/src/views/backend/game/user/GameUserTicketJsonCell.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div v-if="tagLabels.length" class="game-user-ticket-tags">
|
||||
<el-tag v-for="(label, idx) in tagLabels" :key="idx" class="m-4" effect="light" type="success" size="default">
|
||||
{{ label }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<span v-else class="game-user-json-plain">{{ plainText }}</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
renderRow: TableRow
|
||||
renderField: TableColumn
|
||||
renderValue: unknown
|
||||
renderColumn: import('element-plus').TableColumnCtx<TableRow>
|
||||
renderIndex: number
|
||||
}>()
|
||||
|
||||
/** [{"ante":1,"count":1},...] */
|
||||
function parseTicketTagLabels(raw: unknown): string[] {
|
||||
if (raw === null || raw === undefined || raw === '') {
|
||||
return []
|
||||
}
|
||||
let arr: unknown[] = []
|
||||
if (typeof raw === 'string') {
|
||||
const s = raw.trim()
|
||||
if (!s) return []
|
||||
try {
|
||||
const parsed = JSON.parse(s)
|
||||
arr = Array.isArray(parsed) ? parsed : []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
} else if (Array.isArray(raw)) {
|
||||
arr = raw
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
|
||||
const labels: string[] = []
|
||||
for (const item of arr) {
|
||||
if (item !== null && typeof item === 'object' && !Array.isArray(item)) {
|
||||
const o = item as Record<string, unknown>
|
||||
const ante = o.ante
|
||||
const count = o.count
|
||||
labels.push(`ante:${String(ante)} count:${String(count)}`)
|
||||
}
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
const tagLabels = computed(() => parseTicketTagLabels(props.renderValue))
|
||||
|
||||
const plainText = computed(() => {
|
||||
const v = props.renderValue
|
||||
if (v === null || v === undefined) return ''
|
||||
if (typeof v === 'object') {
|
||||
try {
|
||||
return JSON.stringify(v)
|
||||
} catch {
|
||||
return String(v)
|
||||
}
|
||||
}
|
||||
return String(v)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.game-user-ticket-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 4px 0;
|
||||
}
|
||||
.m-4 {
|
||||
margin: 4px;
|
||||
}
|
||||
.game-user-json-plain {
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
82
web/src/views/backend/game/user/GameUserWeightJsonCell.vue
Normal file
82
web/src/views/backend/game/user/GameUserWeightJsonCell.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div v-if="tagLabels.length" class="game-user-json-weight-tags">
|
||||
<el-tag v-for="(label, idx) in tagLabels" :key="idx" class="m-4" effect="light" type="primary" size="default">
|
||||
{{ label }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<span v-else class="game-user-json-plain">{{ plainText }}</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
renderRow: TableRow
|
||||
renderField: TableColumn
|
||||
renderValue: unknown
|
||||
renderColumn: import('element-plus').TableColumnCtx<TableRow>
|
||||
renderIndex: number
|
||||
}>()
|
||||
|
||||
/** [{"T1":"5"},{"T2":"20"},...] */
|
||||
function parseWeightTagLabels(raw: unknown): string[] {
|
||||
if (raw === null || raw === undefined || raw === '') {
|
||||
return []
|
||||
}
|
||||
let arr: unknown[] = []
|
||||
if (typeof raw === 'string') {
|
||||
const s = raw.trim()
|
||||
if (!s) return []
|
||||
try {
|
||||
const parsed = JSON.parse(s)
|
||||
arr = Array.isArray(parsed) ? parsed : []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
} else if (Array.isArray(raw)) {
|
||||
arr = raw
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
|
||||
const labels: string[] = []
|
||||
for (const item of arr) {
|
||||
if (item !== null && typeof item === 'object' && !Array.isArray(item)) {
|
||||
for (const [k, v] of Object.entries(item as Record<string, unknown>)) {
|
||||
labels.push(`${k}:${String(v)}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
const tagLabels = computed(() => parseWeightTagLabels(props.renderValue))
|
||||
|
||||
const plainText = computed(() => {
|
||||
const v = props.renderValue
|
||||
if (v === null || v === undefined) return ''
|
||||
if (typeof v === 'object') {
|
||||
try {
|
||||
return JSON.stringify(v)
|
||||
} catch {
|
||||
return String(v)
|
||||
}
|
||||
}
|
||||
return String(v)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.game-user-json-weight-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 4px 0;
|
||||
}
|
||||
.m-4 {
|
||||
margin: 4px;
|
||||
}
|
||||
.game-user-json-plain {
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
@@ -23,11 +23,14 @@
|
||||
import { onMounted, provide, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import PopupForm from './popupForm.vue'
|
||||
import GameUserTicketJsonCell from './GameUserTicketJsonCell.vue'
|
||||
import GameUserWeightJsonCell from './GameUserWeightJsonCell.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 { BIGWIN_WEIGHT_KEYS, TIER_WEIGHT_KEYS, jsonStringFromFixedKeys } from '/@/utils/gameWeightFixed'
|
||||
|
||||
defineOptions({
|
||||
name: 'game/user',
|
||||
@@ -66,6 +69,36 @@ const baTable = new baTableClass(
|
||||
},
|
||||
{ label: t('game.user.phone'), prop: 'phone', align: 'center', operatorPlaceholder: t('Fuzzy query'), sortable: false, operator: 'LIKE' },
|
||||
{ label: t('game.user.coin'), prop: 'coin', align: 'center', sortable: false, operator: 'RANGE' },
|
||||
{
|
||||
label: t('game.user.tier_weight'),
|
||||
prop: 'tier_weight',
|
||||
align: 'center',
|
||||
minWidth: 200,
|
||||
sortable: false,
|
||||
operator: false,
|
||||
render: 'customRender',
|
||||
customRender: GameUserWeightJsonCell,
|
||||
},
|
||||
{
|
||||
label: t('game.user.bigwin_weight'),
|
||||
prop: 'bigwin_weight',
|
||||
align: 'center',
|
||||
minWidth: 200,
|
||||
sortable: false,
|
||||
operator: false,
|
||||
render: 'customRender',
|
||||
customRender: GameUserWeightJsonCell,
|
||||
},
|
||||
{
|
||||
label: t('game.user.ticket_count'),
|
||||
prop: 'ticket_count',
|
||||
align: 'center',
|
||||
minWidth: 220,
|
||||
sortable: false,
|
||||
operator: false,
|
||||
render: 'customRender',
|
||||
customRender: GameUserTicketJsonCell,
|
||||
},
|
||||
{
|
||||
label: t('game.user.status'),
|
||||
prop: 'status',
|
||||
@@ -138,7 +171,12 @@ const baTable = new baTableClass(
|
||||
dblClickNotEditColumn: [undefined, 'status'],
|
||||
},
|
||||
{
|
||||
defaultItems: { status: '1' },
|
||||
defaultItems: {
|
||||
status: '1',
|
||||
tier_weight: jsonStringFromFixedKeys(TIER_WEIGHT_KEYS, {}),
|
||||
bigwin_weight: jsonStringFromFixedKeys(BIGWIN_WEIGHT_KEYS, {}),
|
||||
ticket_count: '[]',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -68,6 +68,65 @@
|
||||
:input-attr="{ step: 1 }"
|
||||
:placeholder="t('Please input field', { field: t('game.user.coin') })"
|
||||
/>
|
||||
<!-- 档位权重:键固定 T1–T5,仅可改值 -->
|
||||
<el-form-item :label="t('game.user.tier_weight')" prop="tier_weight">
|
||||
<div class="weight-value-editor">
|
||||
<div v-for="(row, idx) in tierWeightRows" :key="'tw-' + idx" class="weight-value-row">
|
||||
<el-input v-model="row.key" class="weight-key" readonly tabindex="-1" />
|
||||
<span class="weight-sep">:</span>
|
||||
<el-input
|
||||
v-model="row.val"
|
||||
class="weight-val"
|
||||
:placeholder="t('Please input field', { field: t('game.user.weight value') })"
|
||||
clearable
|
||||
@input="onTierWeightRowChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-help">{{ t('game.user.tier_weight_help') }}</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<!-- 中大奖权重:键固定 5–30,仅可改值 -->
|
||||
<el-form-item :label="t('game.user.bigwin_weight')" prop="bigwin_weight">
|
||||
<div class="weight-value-editor">
|
||||
<div v-for="(row, idx) in bigwinWeightRows" :key="'bw-' + idx" class="weight-value-row">
|
||||
<el-input v-model="row.key" class="weight-key" readonly tabindex="-1" />
|
||||
<span class="weight-sep">:</span>
|
||||
<el-input
|
||||
v-model="row.val"
|
||||
class="weight-val"
|
||||
:placeholder="t('Please input field', { field: t('game.user.weight value') })"
|
||||
clearable
|
||||
:disabled="isBigwinValueLocked(row.key)"
|
||||
@input="onBigwinWeightRowChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-help">{{ t('game.user.bigwin_weight_help') }}</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<!-- 抽奖券:最多一条 JSON 元素 [{"ante":1,"count":1}],不可增行,可删除清空 -->
|
||||
<el-form-item :label="t('game.user.ticket_count')" prop="ticket_count">
|
||||
<div class="ticket-value-editor">
|
||||
<div v-for="(row, idx) in ticketRows" :key="'tk-' + idx" class="ticket-value-row">
|
||||
<span class="ticket-label">{{ t('game.user.ticket_ante') }}</span>
|
||||
<el-input
|
||||
v-model="row.ante"
|
||||
class="ticket-field"
|
||||
clearable
|
||||
:placeholder="t('Please input field', { field: t('game.user.ticket_ante') })"
|
||||
@input="onTicketRowChange"
|
||||
/>
|
||||
<span class="ticket-label">{{ t('game.user.ticket_count_times') }}</span>
|
||||
<el-input
|
||||
v-model="row.count"
|
||||
class="ticket-field"
|
||||
clearable
|
||||
:placeholder="t('Please input field', { field: t('game.user.ticket_count_times') })"
|
||||
@input="onTicketRowChange"
|
||||
/>
|
||||
<el-button type="danger" link @click="removeTicketRow">{{ t('Delete') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<FormItem
|
||||
:label="t('game.user.status')"
|
||||
type="switch"
|
||||
@@ -107,17 +166,30 @@ import type { FormItemRule } from 'element-plus'
|
||||
import { computed, inject, onMounted, reactive, ref, useTemplateRef, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import FormItem from '/@/components/formItem/index.vue'
|
||||
import { useAdminInfo } from '/@/stores/adminInfo'
|
||||
import { useConfig } from '/@/stores/config'
|
||||
import type baTableClass from '/@/utils/baTable'
|
||||
import { buildValidatorData, regularPassword } from '/@/utils/validate'
|
||||
import createAxios from '/@/utils/axios'
|
||||
import {
|
||||
BIGWIN_WEIGHT_KEYS,
|
||||
TIER_WEIGHT_KEYS,
|
||||
fixedRowsFromKeys,
|
||||
jsonStringFromFixedKeys,
|
||||
parseWeightJsonToMap,
|
||||
rowsToMap,
|
||||
type WeightRow,
|
||||
} from '/@/utils/gameWeightFixed'
|
||||
|
||||
const config = useConfig()
|
||||
const formRef = useTemplateRef('formRef')
|
||||
const baTable = inject('baTable') as baTableClass
|
||||
const adminInfo = useAdminInfo()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const isSuperAdmin = computed(() => adminInfo.super === true)
|
||||
|
||||
type TreeNode = {
|
||||
value: string
|
||||
label: string
|
||||
@@ -194,6 +266,209 @@ onMounted(() => {
|
||||
loadChannelAdminTree()
|
||||
})
|
||||
|
||||
type TicketRow = { ante: string; count: string }
|
||||
|
||||
const tierWeightRows = ref<WeightRow[]>(fixedRowsFromKeys(TIER_WEIGHT_KEYS, {}))
|
||||
const bigwinWeightRows = ref<WeightRow[]>(fixedRowsFromKeys(BIGWIN_WEIGHT_KEYS, {}))
|
||||
const ticketRows = ref<TicketRow[]>([{ ante: '', count: '1' }])
|
||||
|
||||
function isBigwinValueLocked(key: string): boolean {
|
||||
return key === '5' || key === '30'
|
||||
}
|
||||
|
||||
function enforceBigwinFixedValues() {
|
||||
for (const r of bigwinWeightRows.value) {
|
||||
if (isBigwinValueLocked(r.key)) {
|
||||
r.val = '10000'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function syncTierWeightToForm() {
|
||||
const items = baTable.form.items
|
||||
if (!items) return
|
||||
items.tier_weight = jsonStringFromFixedKeys(TIER_WEIGHT_KEYS, rowsToMap(tierWeightRows.value))
|
||||
}
|
||||
|
||||
function syncBigwinWeightToForm() {
|
||||
const items = baTable.form.items
|
||||
if (!items) return
|
||||
items.bigwin_weight = jsonStringFromFixedKeys(BIGWIN_WEIGHT_KEYS, rowsToMap(bigwinWeightRows.value))
|
||||
}
|
||||
|
||||
function onTierWeightRowChange() {
|
||||
syncTierWeightToForm()
|
||||
}
|
||||
|
||||
function onBigwinWeightRowChange() {
|
||||
enforceBigwinFixedValues()
|
||||
syncBigwinWeightToForm()
|
||||
}
|
||||
|
||||
function parseTicketRows(raw: unknown): TicketRow[] {
|
||||
if (raw === null || raw === undefined || raw === '') {
|
||||
return [{ ante: '', count: '1' }]
|
||||
}
|
||||
if (typeof raw === 'string') {
|
||||
const s = raw.trim()
|
||||
if (!s) return [{ ante: '', count: '1' }]
|
||||
try {
|
||||
const parsed = JSON.parse(s)
|
||||
return arrayToTicketRows(parsed)
|
||||
} catch {
|
||||
return [{ ante: '', count: '1' }]
|
||||
}
|
||||
}
|
||||
if (Array.isArray(raw)) {
|
||||
return arrayToTicketRows(raw)
|
||||
}
|
||||
return [{ ante: '', count: '1' }]
|
||||
}
|
||||
|
||||
function arrayToTicketRows(arr: unknown): TicketRow[] {
|
||||
if (!Array.isArray(arr)) {
|
||||
return [{ ante: '', count: '1' }]
|
||||
}
|
||||
const out: TicketRow[] = []
|
||||
for (const item of arr) {
|
||||
if (item !== null && typeof item === 'object' && !Array.isArray(item)) {
|
||||
const ante = Reflect.get(item, 'ante')
|
||||
const count = Reflect.get(item, 'count')
|
||||
out.push({
|
||||
ante: ante === null || ante === undefined ? '' : String(ante),
|
||||
count: count === null || count === undefined || String(count) === '' ? '1' : String(count),
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
return out.length ? out : [{ ante: '', count: '1' }]
|
||||
}
|
||||
|
||||
function ticketRowsToJsonString(rows: TicketRow[]): string {
|
||||
const body: { ante: number; count: number }[] = []
|
||||
for (const r of rows) {
|
||||
const a = r.ante.trim()
|
||||
const c = r.count.trim()
|
||||
if (a === '' && c === '') continue
|
||||
if (a === '' || c === '') continue
|
||||
const na = Number(a)
|
||||
const nc = Number(c)
|
||||
body.push({ ante: na, count: nc })
|
||||
}
|
||||
return JSON.stringify(body)
|
||||
}
|
||||
|
||||
function syncTicketToForm() {
|
||||
const items = baTable.form.items
|
||||
if (!items) return
|
||||
const a = ticketRows.value[0]?.ante?.trim() ?? ''
|
||||
const c = ticketRows.value[0]?.count?.trim() ?? ''
|
||||
if (a === '' || c === '') {
|
||||
items.ticket_count = ''
|
||||
return
|
||||
}
|
||||
items.ticket_count = ticketRowsToJsonString(ticketRows.value)
|
||||
}
|
||||
|
||||
function applyTierBigwinFromJson(tierJson: string, bigwinJson: string) {
|
||||
const items = baTable.form.items
|
||||
if (!items) return
|
||||
const tm = parseWeightJsonToMap(tierJson)
|
||||
const bm = parseWeightJsonToMap(bigwinJson)
|
||||
items.tier_weight = jsonStringFromFixedKeys(TIER_WEIGHT_KEYS, tm)
|
||||
items.bigwin_weight = jsonStringFromFixedKeys(BIGWIN_WEIGHT_KEYS, bm)
|
||||
tierWeightRows.value = fixedRowsFromKeys(TIER_WEIGHT_KEYS, tm)
|
||||
bigwinWeightRows.value = fixedRowsFromKeys(BIGWIN_WEIGHT_KEYS, bm)
|
||||
enforceBigwinFixedValues()
|
||||
syncBigwinWeightToForm()
|
||||
}
|
||||
|
||||
async function loadAndApplyDefaultsForChannel(channelId: number) {
|
||||
try {
|
||||
const res = await createAxios(
|
||||
{
|
||||
url: '/admin/game.User/defaultWeightByChannel',
|
||||
method: 'get',
|
||||
params: { channel_id: channelId },
|
||||
},
|
||||
{
|
||||
showErrorMessage: false,
|
||||
showCodeMessage: false,
|
||||
}
|
||||
)
|
||||
applyTierBigwinFromJson(res.data.tier_weight ?? '[]', res.data.bigwin_weight ?? '[]')
|
||||
} catch {
|
||||
// 路由或权限异常时不阻断打开表单,保持可手工编辑
|
||||
}
|
||||
}
|
||||
|
||||
function onTicketRowChange() {
|
||||
syncTicketToForm()
|
||||
}
|
||||
|
||||
/** 最多一条,删除/不填则 ticket_count 为空 */
|
||||
function removeTicketRow() {
|
||||
ticketRows.value = [{ ante: '', count: '1' }]
|
||||
syncTicketToForm()
|
||||
}
|
||||
|
||||
function hydrateJsonFieldsFromForm() {
|
||||
const tm = parseWeightJsonToMap(baTable.form.items?.tier_weight)
|
||||
const bm = parseWeightJsonToMap(baTable.form.items?.bigwin_weight)
|
||||
tierWeightRows.value = fixedRowsFromKeys(TIER_WEIGHT_KEYS, tm)
|
||||
bigwinWeightRows.value = fixedRowsFromKeys(BIGWIN_WEIGHT_KEYS, bm)
|
||||
syncTierWeightToForm()
|
||||
enforceBigwinFixedValues()
|
||||
syncBigwinWeightToForm()
|
||||
ticketRows.value = normalizeTicketRowsToOne(parseTicketRows(baTable.form.items?.ticket_count))
|
||||
syncTicketToForm()
|
||||
}
|
||||
|
||||
function normalizeTicketRowsToOne(rows: TicketRow[]): TicketRow[] {
|
||||
if (rows.length === 0) {
|
||||
return [{ ante: '', count: '1' }]
|
||||
}
|
||||
const first = rows[0]
|
||||
return [
|
||||
{
|
||||
ante: first?.ante ?? '',
|
||||
count: first?.count && String(first.count).trim() !== '' ? String(first.count) : '1',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
watch(
|
||||
() => baTable.form.loading,
|
||||
(loading) => {
|
||||
if (loading === false) {
|
||||
hydrateJsonFieldsFromForm()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => [baTable.form.operate, baTable.form.loading] as const,
|
||||
async ([op, loading]) => {
|
||||
if (op !== 'Add' || loading !== false) return
|
||||
if (!isSuperAdmin.value) return
|
||||
await loadAndApplyDefaultsForChannel(0)
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => baTable.form.items?.game_channel_id,
|
||||
async (ch) => {
|
||||
if (baTable.form.operate !== 'Add') return
|
||||
if (ch === undefined || ch === null || ch === '') return
|
||||
const cid = Number(ch)
|
||||
if (!Number.isFinite(cid)) return
|
||||
if (isSuperAdmin.value) {
|
||||
return
|
||||
}
|
||||
await loadAndApplyDefaultsForChannel(cid)
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => baTable.form.items?.admin_id,
|
||||
(val) => {
|
||||
@@ -202,6 +477,62 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
function validateTierWeightRows(): string | undefined {
|
||||
let sum = 0
|
||||
for (const r of tierWeightRows.value) {
|
||||
const vs = r.val.trim()
|
||||
if (vs === '') {
|
||||
return t('Please input field', { field: t('game.user.weight value') })
|
||||
}
|
||||
const n = Number(vs)
|
||||
if (!Number.isFinite(n)) {
|
||||
return t('game.user.weight value numeric')
|
||||
}
|
||||
if (n > 100) {
|
||||
return t('game.user.weight each max 100')
|
||||
}
|
||||
sum += n
|
||||
}
|
||||
if (sum > 100 + 0.000001) {
|
||||
return t('game.user.tier_weight_sum_max_100')
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function validateBigwinWeightRows(): string | undefined {
|
||||
for (const r of bigwinWeightRows.value) {
|
||||
const vs = r.val.trim()
|
||||
if (vs === '') {
|
||||
return t('Please input field', { field: t('game.user.weight value') })
|
||||
}
|
||||
const n = Number(vs)
|
||||
if (!Number.isFinite(n)) {
|
||||
return t('game.user.weight value numeric')
|
||||
}
|
||||
if (n > 10000) {
|
||||
return t('game.user.bigwin_weight_each_max_10000')
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function validateTicketRowsField(): string | undefined {
|
||||
for (const r of ticketRows.value) {
|
||||
const a = r.ante.trim()
|
||||
const c = r.count.trim()
|
||||
if (a === '' && c === '') continue
|
||||
if (a === '' || c === '') {
|
||||
return t('game.user.ticket row incomplete')
|
||||
}
|
||||
const na = Number(a)
|
||||
const nc = Number(c)
|
||||
if (!Number.isFinite(na) || !Number.isFinite(nc)) {
|
||||
return t('game.user.ticket row numeric')
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const validatorGameUserPassword = (rule: any, val: string, callback: (error?: Error) => void) => {
|
||||
const operate = baTable.form.operate
|
||||
const v = typeof val === 'string' ? val.trim() : ''
|
||||
@@ -224,10 +555,96 @@ const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
||||
password: [{ validator: validatorGameUserPassword, trigger: 'blur' }],
|
||||
phone: [buildValidatorData({ name: 'required', title: t('game.user.phone') })],
|
||||
coin: [buildValidatorData({ name: 'number', title: t('game.user.coin') })],
|
||||
tier_weight: [
|
||||
{
|
||||
validator: (_rule, _val, callback) => {
|
||||
const err = validateTierWeightRows()
|
||||
if (err) {
|
||||
callback(new Error(err))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
bigwin_weight: [
|
||||
{
|
||||
validator: (_rule, _val, callback) => {
|
||||
const err = validateBigwinWeightRows()
|
||||
if (err) {
|
||||
callback(new Error(err))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
ticket_count: [
|
||||
{
|
||||
validator: (_rule, _val, callback) => {
|
||||
const err = validateTicketRowsField()
|
||||
if (err) {
|
||||
callback(new Error(err))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
admin_id: [buildValidatorData({ name: 'required', title: t('game.user.admin_id') })],
|
||||
create_time: [buildValidatorData({ name: 'date', title: t('game.user.create_time') })],
|
||||
update_time: [buildValidatorData({ name: 'date', title: t('game.user.update_time') })],
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
<style scoped lang="scss">
|
||||
.weight-value-editor {
|
||||
width: 100%;
|
||||
}
|
||||
.weight-value-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.weight-key {
|
||||
max-width: 140px;
|
||||
}
|
||||
.weight-val {
|
||||
flex: 1;
|
||||
min-width: 80px;
|
||||
}
|
||||
.weight-sep {
|
||||
flex-shrink: 0;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.ticket-value-editor {
|
||||
width: 100%;
|
||||
}
|
||||
.ticket-value-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.ticket-field {
|
||||
flex: 1;
|
||||
min-width: 100px;
|
||||
max-width: 160px;
|
||||
}
|
||||
.ticket-label {
|
||||
flex-shrink: 0;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
.form-help {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user