1.修复游戏实时对局/admin/game/live页面的报错

This commit is contained in:
2026-05-26 13:51:45 +08:00
parent 3a2af4d7c2
commit 40247af0d6
2 changed files with 104 additions and 29 deletions

View File

@@ -57,7 +57,9 @@ class Live extends Backend
'period.locked', 'period.locked',
'period.opened', 'period.opened',
'period.payout', 'period.payout',
'period.payout.tick',
'bet.accepted', 'bet.accepted',
'bet.win',
'wallet.changed', 'wallet.changed',
'auto.spin.progress', 'auto.spin.progress',
]; ];

View File

@@ -37,7 +37,7 @@
> >
{{ t('game.live.void_btn') }} {{ t('game.live.void_btn') }}
</el-button> </el-button>
<el-button :loading="loading" :disabled="asideOperationLocked" @click="loadSnapshot">{{ t('Refresh') }}</el-button> <el-button :loading="loading" :disabled="asideOperationLocked" @click="loadSnapshot({ force: true })">{{ t('Refresh') }}</el-button>
</div> </div>
</div> </div>
</div> </div>
@@ -203,6 +203,7 @@
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue' import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import axios from 'axios'
import createAxios from '/@/utils/axios' import createAxios from '/@/utils/axios'
interface Snapshot { interface Snapshot {
@@ -270,6 +271,8 @@ const clockTick = ref(0)
let clockTimer: number | null = null let clockTimer: number | null = null
let payoutStuckRefreshTimer: number | null = null let payoutStuckRefreshTimer: number | null = null
let fallbackPollTimer: number | null = null let fallbackPollTimer: number | null = null
/** 合并并发 snapshot 请求,避免 axios 重复请求取消导致控制台报错 */
let snapshotLoadPromise: Promise<void> | null = null
const wsLoading = ref(false) const wsLoading = ref(false)
const wsReady = ref(false) const wsReady = ref(false)
@@ -330,7 +333,7 @@ function handleWsPayload(raw: unknown): void {
return return
} }
if (event === 'admin.live.opened') { if (event === 'admin.live.opened') {
void loadSnapshot() void loadSnapshot({ force: true })
return return
} }
if (event === 'jackpot.hit' && parsed.data && typeof parsed.data === 'object') { if (event === 'jackpot.hit' && parsed.data && typeof parsed.data === 'object') {
@@ -341,19 +344,63 @@ function handleWsPayload(raw: unknown): void {
} }
return return
} }
if (event === 'period.payout.tick' && parsed.data && typeof parsed.data === 'object') {
mergePeriodPayoutTick(parsed.data as anyObj)
return
}
if (event === 'period.tick' && parsed.data && typeof parsed.data === 'object') { if (event === 'period.tick' && parsed.data && typeof parsed.data === 'object') {
const periodData = parsed.data as anyObj handlePeriodTickEvent(parsed.data as anyObj)
if (typeof periodData.server_time === 'number') { return
syncServerClock(periodData.server_time) }
} if (event === 'bet.accepted' && parsed.data && typeof parsed.data === 'object') {
const status = typeof periodData.status === 'string' ? periodData.status : '' if (!wsConnected.value) {
const periodNo = typeof periodData.period_no === 'string' ? periodData.period_no : ''
const currentNo = typeof snapshot.record?.period_no === 'string' ? snapshot.record.period_no : ''
if (status === 'betting' && periodNo !== '' && periodNo !== currentNo) {
void loadSnapshot()
} else if (status === 'finished' && snapshot.is_payout_phase) {
void loadSnapshot() void loadSnapshot()
} }
return
}
}
/** 用 period.tick 轻量字段刷新倒计时(不触发 HTTP避免与 WS 每秒 snapshot 冲突) */
function mergePeriodTickFields(periodData: anyObj): void {
if (typeof periodData.server_time === 'number') {
syncServerClock(periodData.server_time)
}
if (typeof periodData.countdown === 'number') {
snapshot.remaining_seconds = Math.max(0, periodData.countdown)
}
if (typeof periodData.bet_close_in === 'number') {
snapshot.bet_remaining_seconds = Math.max(0, periodData.bet_close_in)
}
}
function mergePeriodPayoutTick(data: anyObj): void {
if (typeof data.server_time === 'number') {
syncServerClock(data.server_time)
}
const remain = numberValue(data.payout_remaining_seconds)
if (remain !== null) {
snapshot.payout_remaining_seconds = Math.max(0, remain)
snapshot.is_payout_phase = remain > 0 || snapshot.is_payout_phase === true
}
}
function handlePeriodTickEvent(periodData: anyObj): void {
mergePeriodTickFields(periodData)
const status = typeof periodData.status === 'string' ? periodData.status : ''
const periodNo = typeof periodData.period_no === 'string' ? periodData.period_no : ''
const currentNo = typeof snapshot.record?.period_no === 'string' ? snapshot.record.period_no : ''
if (status === 'betting' && periodNo !== '' && periodNo !== currentNo) {
void loadSnapshot({ force: true })
return
}
if (status === 'finished' && snapshot.is_payout_phase) {
if (!wsConnected.value) {
void loadSnapshot()
}
return
}
if (currentNo === '' && periodNo !== '') {
void loadSnapshot({ force: true })
} }
} }
@@ -643,16 +690,40 @@ const payoutRemainingLive = computed(() => {
return snapshot.payout_remaining_seconds ?? 0 return snapshot.payout_remaining_seconds ?? 0
}) })
async function loadSnapshot() { function isRequestCanceled(err: unknown): boolean {
loading.value = true return axios.isCancel(err) || (err instanceof Error && err.name === 'CanceledError')
try { }
const res = await createAxios({ url: '/admin/game.Live/snapshot', method: 'get', showCodeMessage: false })
if (res.code === 1 && res.data) { async function loadSnapshot(options?: { force?: boolean }): Promise<void> {
mergeLiveSnapshot(res.data as anyObj) if (!options?.force && wsConnected.value && snapshot.record) {
} return
} finally {
loading.value = false
} }
if (snapshotLoadPromise) {
return snapshotLoadPromise
}
snapshotLoadPromise = (async () => {
loading.value = true
try {
const res = await createAxios({
url: '/admin/game.Live/snapshot',
method: 'get',
showCodeMessage: false,
showErrorMessage: false,
cancelDuplicateRequest: false,
})
if (res.code === 1 && res.data) {
mergeLiveSnapshot(res.data as anyObj)
}
} catch (err) {
if (!isRequestCanceled(err)) {
throw err
}
} finally {
loading.value = false
snapshotLoadPromise = null
}
})()
return snapshotLoadPromise
} }
async function onRuntimeSwitch(val: boolean | string | number): void { async function onRuntimeSwitch(val: boolean | string | number): void {
@@ -679,10 +750,10 @@ async function onRuntimeSwitch(val: boolean | string | number): void {
if (res.code === 1 && res.data) { if (res.code === 1 && res.data) {
mergeLiveSnapshot(res.data as anyObj) mergeLiveSnapshot(res.data as anyObj)
} else { } else {
await loadSnapshot() await loadSnapshot({ force: true })
} }
} catch { } catch {
await loadSnapshot() await loadSnapshot({ force: true })
} finally { } finally {
runtimeSwitchLoading.value = false runtimeSwitchLoading.value = false
pendingRuntimeTarget.value = null pendingRuntimeTarget.value = null
@@ -768,7 +839,7 @@ async function onDrawWithNumber(targetNumber: number) {
}, },
showSuccessMessage: true, showSuccessMessage: true,
}) })
await loadSnapshot() await loadSnapshot({ force: true })
} finally { } finally {
drawLoading.value = false drawLoading.value = false
} }
@@ -816,7 +887,9 @@ function schedulePayoutEndRefresh(delayMs: number): void {
if (!snapshot.is_payout_phase) { if (!snapshot.is_payout_phase) {
return return
} }
void loadSnapshot() if (!wsConnected.value) {
void loadSnapshot({ force: true })
}
}, delayMs) }, delayMs)
} }
@@ -827,11 +900,11 @@ onMounted(async () => {
clockTick.value++ clockTick.value++
}, 1000) }, 1000)
fallbackPollTimer = window.setInterval(() => { fallbackPollTimer = window.setInterval(() => {
if (snapshot.is_payout_phase || snapshot.maintenance_ui) { if (!wsConnected.value) {
void loadSnapshot() void loadSnapshot({ force: true })
} }
}, 5000) }, 3000)
await loadSnapshot() await loadSnapshot({ force: true })
await reloadWsConfig() await reloadWsConfig()
connectWs() connectWs()
}) })