1.优化下注接口/api/game/betPlace
2.优化后台/admin/config/gameConfig中新增压注筹码配置
This commit is contained in:
@@ -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 1–6, 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 (1–6), 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',
|
||||
|
||||
@@ -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 档(标识 1–6 不可改),仅可修改每档面额;保存时写入为 JSON,与移动端 lobbyInit 一致',
|
||||
bet_chips_colon: ':',
|
||||
bet_chips_validate_slot: '第 {slot} 档筹码面额须为大于 0 的数字(建议 ≥ 单号最小下注额)',
|
||||
'field default_bet_chip_id': '默认选中筹码',
|
||||
'field_tip default_bet_chip_id': '大厅默认高亮的筹码标识,须为 1–6 且对应 bet_chips 有效面额',
|
||||
'field min_bet_per_number': '单号最小下注额',
|
||||
'field_tip min_bet_per_number': '每个号码允许的最小下注金额',
|
||||
'field max_bet_per_number': '单号最大下注额',
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user