1.优化下注接口/api/game/betPlace

2.优化后台/admin/config/gameConfig中新增压注筹码配置
This commit is contained in:
2026-05-14 10:37:21 +08:00
parent c7fc754573
commit 932a433613
10 changed files with 392 additions and 41 deletions

View File

@@ -12,6 +12,12 @@ export default {
'field_tip bet_seconds': 'How many seconds betting stays open in each period',
'field pick_max_number_count': 'Max numbers per ticket',
'field_tip pick_max_number_count': 'Maximum amount of selectable numbers per ticket',
'field bet_chips': 'Quick chip amounts',
'field_tip bet_chips': 'Exactly 6 tiers (ids 16, fixed). Edit amounts only; stored as JSON for lobbyInit.',
bet_chips_colon: ': ',
bet_chips_validate_slot: 'Chip tier {slot} must be a number greater than 0 (prefer ≥ min bet per number)',
'field default_bet_chip_id': 'Default selected chip id',
'field_tip default_bet_chip_id': 'Default highlighted chip id (16), must map to a valid amount in bet_chips',
'field min_bet_per_number': 'Min bet per number',
'field_tip min_bet_per_number': 'Minimum bet amount per selected number',
'field max_bet_per_number': 'Max bet per number',

View File

@@ -12,6 +12,12 @@ export default {
'field_tip bet_seconds': '每一局允许下注的时长(秒)',
'field pick_max_number_count': '单注最多号码个数',
'field_tip pick_max_number_count': '单注最多可选号码数量',
'field bet_chips': '快捷筹码面额',
'field_tip bet_chips': '固定 6 档(标识 16 不可改),仅可修改每档面额;保存时写入为 JSON与移动端 lobbyInit 一致',
bet_chips_colon: '',
bet_chips_validate_slot: '第 {slot} 档筹码面额须为大于 0 的数字(建议 ≥ 单号最小下注额)',
'field default_bet_chip_id': '默认选中筹码',
'field_tip default_bet_chip_id': '大厅默认高亮的筹码标识,须为 16 且对应 bet_chips 有效面额',
'field min_bet_per_number': '单号最小下注额',
'field_tip min_bet_per_number': '每个号码允许的最小下注金额',
'field max_bet_per_number': '单号最大下注额',

View File

@@ -14,14 +14,43 @@
<el-tabs v-model="state.activeTab" type="border-card">
<el-tab-pane :label="t('config.gameConfig.form tab label')" name="game_config">
<div class="config-form-item" v-for="item in state.configList" :key="item.id">
<FormItem
:label="resolveFieldLabel(item.config_key)"
:type="resolveFormType(item.value_type)"
v-model="state.form[item.config_key]"
:input-attr="resolveInputAttr(item.value_type)"
:tip="resolveFieldTip(item.config_key, item.remark)"
/>
<div class="config-form-item-name">{{ item.config_key }}</div>
<template v-if="item.config_key === BET_CHIPS_KEY">
<el-form-item :label="resolveFieldLabel(item.config_key)">
<div class="bet-chips-editor">
<div
v-for="slot in CHIP_SLOTS"
:key="slot"
class="bet-chips-row"
>
<span class="bet-chips-key">{{ slot }}</span>
<span class="bet-chips-sep">{{ t('config.gameConfig.bet_chips_colon') }}</span>
<el-input-number
v-model="state.betChipsAmounts[slot]"
:min="0.01"
:max="99999999"
:precision="2"
:step="1"
controls-position="right"
class="bet-chips-input"
/>
</div>
</div>
<div v-if="resolveFieldTip(item.config_key, item.remark)" class="bet-chips-tip">
{{ resolveFieldTip(item.config_key, item.remark) }}
</div>
</el-form-item>
<div class="config-form-item-name">{{ item.config_key }}</div>
</template>
<template v-else>
<FormItem
:label="resolveFieldLabel(item.config_key)"
:type="resolveFormType(item.value_type)"
v-model="state.form[item.config_key]"
:input-attr="resolveInputAttr(item.value_type)"
:tip="resolveFieldTip(item.config_key, item.remark)"
/>
<div class="config-form-item-name">{{ item.config_key }}</div>
</template>
</div>
<el-button @click="onReset">{{ t('Reset') }}</el-button>
<el-button v-if="canSave" type="primary" :loading="state.submitLoading" @click="onSubmit()">{{ t('Save') }}</el-button>
@@ -34,6 +63,7 @@
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { onMounted, reactive, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { baTableApi } from '/@/api/common'
@@ -53,6 +83,51 @@ interface GameConfigItem {
remark: string
}
const BET_CHIPS_KEY = 'bet_chips'
const CHIP_SLOTS = [1, 2, 3, 4, 5, 6] as const
const defaultBetChipsAmounts = (): Record<number, number> => ({
1: 1,
2: 5,
3: 10,
4: 25,
5: 50,
6: 100,
})
const parseBetChipsFromServer = (raw: string): Record<number, number> => {
const out = defaultBetChipsAmounts()
if (!raw || typeof raw !== 'string') {
return out
}
try {
const o = JSON.parse(raw) as unknown
if (typeof o !== 'object' || o === null) {
return out
}
const rec = o as Record<string, unknown>
for (const slot of CHIP_SLOTS) {
const rawVal = rec[String(slot)]
const n = Number(rawVal)
if (Number.isFinite(n) && n > 0) {
out[slot] = n
}
}
} catch {
/* 使用内置默认 */
}
return out
}
const serializeBetChips = (amounts: Record<number, number>): string => {
const o: Record<string, string> = {}
for (const slot of CHIP_SLOTS) {
const n = Number(amounts[slot])
o[String(slot)] = Number.isFinite(n) ? n.toFixed(2) : '0.00'
}
return JSON.stringify(o)
}
const { t, te } = useI18n()
const formRef = useTemplateRef('formRef')
const api = new baTableApi('/admin/config.GameConfig/')
@@ -66,6 +141,7 @@ const state: {
remark: string
configList: GameConfigItem[]
form: Record<string, string | number>
betChipsAmounts: Record<number, number>
} = reactive({
loading: true,
submitLoading: false,
@@ -73,6 +149,7 @@ const state: {
remark: '',
configList: [],
form: {},
betChipsAmounts: defaultBetChipsAmounts(),
})
const getData = () => {
@@ -84,6 +161,10 @@ const getData = () => {
state.remark = res.data.remark || ''
const nextForm: Record<string, string | number> = {}
for (const item of list) {
if (item.config_key === BET_CHIPS_KEY) {
state.betChipsAmounts = parseBetChipsFromServer(item.config_value ?? '')
continue
}
if (item.value_type === 'int' || item.value_type === 'decimal') {
const parsed = Number(item.config_value)
nextForm[item.config_key] = Number.isNaN(parsed) ? 0 : parsed
@@ -129,17 +210,34 @@ const resolveFieldTip = (configKey: string, fallbackRemark: string) => {
return fallbackRemark || ''
}
const validateBetChips = (): boolean => {
for (const slot of CHIP_SLOTS) {
const v = state.betChipsAmounts[slot]
const n = Number(v)
if (!Number.isFinite(n) || n <= 0) {
ElMessage.error(t('config.gameConfig.bet_chips_validate_slot', { slot }))
return false
}
}
return true
}
const onSubmit = async () => {
const valid = await formRef.value?.validate().catch(() => false)
if (!valid) return
if (!validateBetChips()) {
return
}
state.submitLoading = true
try {
const items = JSON.parse(JSON.stringify(state.form)) as Record<string, string | number>
items[BET_CHIPS_KEY] = serializeBetChips(state.betChipsAmounts)
await createAxios({
url: '/admin/config.GameConfig/save',
method: 'post',
data: {
items: JSON.parse(JSON.stringify(state.form)),
items,
},
showSuccessMessage: true,
})
@@ -179,7 +277,7 @@ onMounted(() => {
}
.config-form-item {
display: flex;
align-items: center;
align-items: flex-start;
.el-form-item {
flex: 13;
}
@@ -189,11 +287,42 @@ onMounted(() => {
color: var(--el-text-color-disabled);
padding-left: 20px;
opacity: 0;
padding-top: 28px;
}
&:hover .config-form-item-name {
opacity: 1;
}
}
.bet-chips-editor {
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
max-width: 420px;
}
.bet-chips-row {
display: flex;
align-items: center;
gap: 6px;
}
.bet-chips-key {
min-width: 18px;
font-variant-numeric: tabular-nums;
font-weight: 600;
color: var(--el-text-color-regular);
}
.bet-chips-sep {
color: var(--el-text-color-secondary);
user-select: none;
}
.bet-chips-input {
flex: 1;
min-width: 0;
}
.bet-chips-tip {
margin-top: 8px;
font-size: 12px;
color: var(--el-text-color-secondary);
line-height: 1.5;
}
</style>