1.优化后台/admin/game/live

This commit is contained in:
2026-05-26 15:10:42 +08:00
parent 1b26539ac5
commit 5cbb7ea485
4 changed files with 144 additions and 12 deletions

View File

@@ -82,7 +82,7 @@
<div class="calc-result-bar">
<span class="calc-result-bar__item">
<span class="calc-result-bar__k">{{ t('game.live.calc_result_number') }}</span>
<span class="calc-result-bar__v">{{ calcResultNumber ?? '—' }}</span>
<span class="calc-result-bar__v">{{ displayResultNumber ?? '—' }}</span>
</span>
<span class="calc-result-bar__item">
<span class="calc-result-bar__k">{{ t('game.live.calc_estimated_loss') }}</span>
@@ -160,6 +160,11 @@
</template>
</el-table-column>
<el-table-column prop="total_amount" :label="t('game.live.total_amount')" width="92" align="center" header-align="center" />
<el-table-column prop="win_amount" :label="t('game.live.win_amount')" width="92" align="center" header-align="center">
<template #default="scope">
<span class="mono">{{ formatWinAmount(scope.row) }}</span>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
@@ -229,6 +234,8 @@ interface Snapshot {
runtime_enabled?: boolean
/** 完整维护 UI关服且当前无进行中/未结清对局(派彩已全部完成) */
maintenance_ui?: boolean
/** 已开奖号码status≥2 时有值) */
result_number?: number | null
}
const { t } = useI18n()
@@ -252,6 +259,7 @@ const snapshot = reactive<Snapshot>({
can_schedule_draw: false,
runtime_enabled: true,
maintenance_ui: false,
result_number: null,
})
const calcLoading = ref(false)
const drawLoading = ref(false)
@@ -271,6 +279,7 @@ const clockTick = ref(0)
let clockTimer: number | null = null
let payoutStuckRefreshTimer: number | null = null
let fallbackPollTimer: number | null = null
let betStreamRefreshTimer: number | null = null
/** 合并并发 snapshot 请求,避免 axios 重复请求取消导致控制台报错 */
let snapshotLoadPromise: Promise<void> | null = null
@@ -333,6 +342,11 @@ function handleWsPayload(raw: unknown): void {
return
}
if (event === 'admin.live.opened') {
const opened = parsed.data as anyObj
if (typeof opened.result_number === 'number') {
snapshot.result_number = opened.result_number
calcResultNumber.value = opened.result_number
}
void loadSnapshot({ force: true })
return
}
@@ -360,13 +374,29 @@ function handleWsPayload(raw: unknown): void {
return
}
if (event === 'bet.accepted' && parsed.data && typeof parsed.data === 'object') {
if (!wsConnected.value) {
void loadSnapshot()
const betData = parsed.data as anyObj
const periodNo = typeof betData.period_no === 'string' ? betData.period_no : ''
const currentNo = typeof snapshot.record?.period_no === 'string' ? snapshot.record.period_no : ''
if (periodNo !== '' && periodNo === currentNo) {
scheduleBetStreamRefresh()
} else if (!wsConnected.value) {
void loadSnapshot({ force: true })
}
return
}
}
/** 有新下注时防抖拉取快照,补全 WS 每秒快照之间的下注列表 */
function scheduleBetStreamRefresh(): void {
if (betStreamRefreshTimer !== null) {
window.clearTimeout(betStreamRefreshTimer)
}
betStreamRefreshTimer = window.setTimeout(() => {
betStreamRefreshTimer = null
void loadSnapshot({ force: true })
}, 600)
}
/** 用 period.tick 轻量字段刷新倒计时(不触发 HTTP避免与 WS 每秒 snapshot 冲突) */
function mergePeriodTickFields(periodData: anyObj): void {
if (typeof periodData.server_time === 'number') {
@@ -568,8 +598,49 @@ function isScheduledNumber(v: unknown): boolean {
return snapshot.pending_draw_number === n
}
const displayResultNumber = computed(() => {
const fromSnap = numberValue(snapshot.result_number)
if (fromSnap !== null) {
return fromSnap
}
const fromRec = numberValue(snapshot.record?.result_number)
if (fromRec !== null) {
return fromRec
}
return calcResultNumber.value
})
function updateCalcLossFromResultNumber(): void {
const rn = displayResultNumber.value
if (rn === null) {
return
}
const row = snapshot.candidate_numbers.find((c) => numberValue(c?.number) === rn)
if (row && row.estimated_loss !== undefined && row.estimated_loss !== null) {
calcEstimatedLoss.value = String(row.estimated_loss)
}
}
function formatWinAmount(row: anyObj): string {
const st = Number(row?.bet_status ?? 0)
const win = row?.win_amount
if (st === 2 || st === 5) {
return win !== undefined && win !== null && String(win) !== '' ? String(win) : '0.00'
}
return '—'
}
function candidateRowClassName(arg: { row: anyObj }): string {
return isScheduledNumber(arg.row?.number) ? 'is-scheduled-row' : ''
const classes: string[] = []
if (isScheduledNumber(arg.row?.number)) {
classes.push('is-scheduled-row')
}
const result = displayResultNumber.value
const num = numberValue(arg.row?.number)
if (result !== null && num !== null && num === result) {
classes.push('is-result-row')
}
return classes.join(' ')
}
async function onPickSwitchChange(val: boolean, rowNumber: unknown): Promise<void> {
@@ -621,11 +692,29 @@ function toBool(v: unknown): boolean | null {
}
function mergeLiveSnapshot(data: anyObj): void {
const prevPeriodId = snapshot.record?.id != null ? Number(snapshot.record.id) : null
let periodChanged = false
if (data.record !== undefined) {
const nextId = data.record?.id != null ? Number(data.record.id) : null
periodChanged = prevPeriodId !== null && nextId !== null && prevPeriodId !== nextId
snapshot.record = data.record
}
snapshot.bets = data.bets || []
snapshot.candidate_numbers = data.candidate_numbers || []
const incomingBets = Array.isArray(data.bets) ? data.bets : null
if (incomingBets !== null) {
if (incomingBets.length > 0 || periodChanged || prevPeriodId === null) {
snapshot.bets = incomingBets
}
}
const incomingCandidates = Array.isArray(data.candidate_numbers) ? data.candidate_numbers : null
if (incomingCandidates !== null) {
if (incomingCandidates.length > 0 || periodChanged || prevPeriodId === null) {
snapshot.candidate_numbers = incomingCandidates
}
}
snapshot.ai_default_number = data.ai_default_number ?? null
snapshot.pending_draw_number = typeof data.pending_draw_number === 'number' ? data.pending_draw_number : null
snapshot.period_seconds = data.period_seconds ?? 30
@@ -654,6 +743,17 @@ function mergeLiveSnapshot(data: anyObj): void {
snapshot.maintenance_ui = data.maintenance_ui
}
}
if (typeof data.result_number === 'number') {
snapshot.result_number = data.result_number
calcResultNumber.value = data.result_number
} else if (periodChanged) {
snapshot.result_number = null
calcResultNumber.value = null
calcEstimatedLoss.value = '0.00'
}
updateCalcLossFromResultNumber()
syncServerClock(data.server_time)
}
@@ -931,6 +1031,10 @@ onUnmounted(() => {
window.clearTimeout(payoutStuckRefreshTimer)
payoutStuckRefreshTimer = null
}
if (betStreamRefreshTimer !== null) {
window.clearTimeout(betStreamRefreshTimer)
betStreamRefreshTimer = null
}
})
</script>
@@ -1166,6 +1270,16 @@ onUnmounted(() => {
background: var(--el-color-primary-light-9);
}
.candidate-table :deep(.is-result-row td) {
background: var(--el-color-success-light-9);
}
.candidate-table :deep(.is-result-row .number-tag) {
border-color: var(--el-color-success);
color: var(--el-color-success);
font-weight: 700;
}
@media (max-width: 768px) {
.live-tables-row .el-col {
margin-bottom: 12px;