1.优化后台测试推送功能页面

2.优化开奖和实时对局页面
This commit is contained in:
2026-04-18 17:16:13 +08:00
parent 5c07967bf9
commit c184fa8a46
14 changed files with 582 additions and 78 deletions

View File

@@ -4,10 +4,14 @@
<el-alert :type="pushConnected ? 'success' : 'error'" :title="pushConnected ? t('game.live.push_connected') : t('game.live.push_disconnected')" show-icon class="mb-12" />
<el-card shadow="never" class="mb-12">
<el-alert v-if="snapshot.is_payout_phase" type="warning" :title="t('game.live.payout_phase')" show-icon class="mb-12" />
<div class="header-row">
<div>
<div>{{ t('game.live.current_record') }}: {{ snapshot.record?.period_no || '-' }}</div>
<div>{{ t('game.live.ai_default_number') }}: {{ snapshot.ai_default_number ?? '-' }}</div>
<div v-if="snapshot.pending_draw_number != null">
{{ t('game.live.pending_draw') }}: {{ snapshot.pending_draw_number }}
</div>
<div>{{ t('game.live.countdown') }}: {{ countdownText }}</div>
</div>
<div class="header-actions">
@@ -15,7 +19,7 @@
<el-button :loading="calcLoading" :disabled="!snapshot.can_calculate" @click="onCalculate">
{{ t('game.live.btn_calc') }}
</el-button>
<el-button type="primary" :loading="drawLoading" :disabled="!snapshot.can_draw" @click="onDraw">
<el-button type="primary" :loading="drawLoading" :disabled="!snapshot.can_schedule_draw" @click="onDraw">
{{ t('game.live.btn_draw') }}
</el-button>
<el-button :loading="loading" @click="loadSnapshot">{{ t('Refresh') }}</el-button>
@@ -67,6 +71,7 @@ interface Snapshot {
bets: anyObj[]
candidate_numbers: anyObj[]
ai_default_number: number | null
pending_draw_number: number | null
period_seconds?: number
bet_seconds?: number
pick_max_number_count?: number
@@ -74,8 +79,12 @@ interface Snapshot {
draw_number_max?: number
remaining_seconds?: number
bet_remaining_seconds?: number
payout_remaining_seconds?: number
is_payout_phase?: boolean
can_calculate?: boolean
can_draw?: boolean
can_schedule_draw?: boolean
server_time?: number
}
const { t } = useI18n()
@@ -87,14 +96,18 @@ const snapshot = reactive<Snapshot>({
bets: [],
candidate_numbers: [],
ai_default_number: null,
pending_draw_number: null,
period_seconds: 30,
bet_seconds: 20,
pick_max_number_count: 10,
draw_number_max: 36,
remaining_seconds: 0,
bet_remaining_seconds: 0,
payout_remaining_seconds: 0,
is_payout_phase: false,
can_calculate: false,
can_draw: false,
can_schedule_draw: false,
})
const calcLoading = ref(false)
const drawLoading = ref(false)
@@ -102,6 +115,12 @@ const manualNumber = ref<number | null>(1)
const calcResultNumber = ref<number | null>(null)
const calcEstimatedLoss = ref<string>('0.0000')
/** 服务端 Unix 秒 本地 Unix 秒,用于派彩倒计时与服务器对齐 */
const serverSkewSeconds = ref(0)
/** 每秒递增,驱动派彩剩余秒本地刷新 */
const clockTick = ref(0)
let clockTimer: number | null = null
let pushClient: any = null
let pushChannel: any = null
let pollTimer: number | null = null
@@ -113,6 +132,45 @@ function formatPicks(v: unknown): string {
return '-'
}
function syncServerClock(serverTime: unknown): void {
if (typeof serverTime === 'number' && Number.isFinite(serverTime)) {
serverSkewSeconds.value = serverTime - Math.floor(Date.now() / 1000)
snapshot.server_time = serverTime
}
}
function readPayoutUntilUnix(rec: anyObj | null): number | null {
if (!rec) {
return null
}
const v = rec.payout_until
if (v === null || v === undefined || v === '') {
return null
}
if (typeof v === 'number' && Number.isFinite(v)) {
return v
}
if (typeof v === 'string' && /^\d+$/.test(v)) {
return parseInt(v, 10)
}
return null
}
/** 派彩剩余秒:优先用 payout_until 与对时后的「服务器当前秒」计算,便于每秒递减 */
const payoutRemainingLive = computed(() => {
clockTick.value
if (!snapshot.is_payout_phase) {
return null
}
const until = readPayoutUntilUnix(snapshot.record)
if (until !== null) {
const serverNow = Math.floor(Date.now() / 1000) + serverSkewSeconds.value
const diff = until - serverNow
return diff > 0 ? diff : 0
}
return snapshot.payout_remaining_seconds ?? 0
})
async function loadSnapshot() {
loading.value = true
try {
@@ -122,14 +180,20 @@ async function loadSnapshot() {
snapshot.bets = res.data.bets || []
snapshot.candidate_numbers = res.data.candidate_numbers || []
snapshot.ai_default_number = res.data.ai_default_number
snapshot.pending_draw_number =
typeof res.data.pending_draw_number === 'number' ? res.data.pending_draw_number : null
snapshot.period_seconds = res.data.period_seconds ?? 30
snapshot.bet_seconds = res.data.bet_seconds ?? 20
snapshot.pick_max_number_count = res.data.pick_max_number_count ?? 10
snapshot.draw_number_max = res.data.draw_number_max ?? 36
snapshot.remaining_seconds = res.data.remaining_seconds ?? 0
snapshot.bet_remaining_seconds = res.data.bet_remaining_seconds ?? 0
snapshot.payout_remaining_seconds = res.data.payout_remaining_seconds ?? 0
snapshot.is_payout_phase = !!res.data.is_payout_phase
snapshot.can_calculate = !!res.data.can_calculate
snapshot.can_draw = !!res.data.can_draw
snapshot.can_schedule_draw = !!res.data.can_schedule_draw || !!res.data.can_draw
syncServerClock(res.data.server_time)
const dmax = res.data.draw_number_max ?? 36
if (manualNumber.value === null || manualNumber.value < 1 || manualNumber.value > dmax) manualNumber.value = 1
}
@@ -172,14 +236,20 @@ async function initPush() {
snapshot.bets = payload.bets || []
snapshot.candidate_numbers = payload.candidate_numbers || []
snapshot.ai_default_number = payload.ai_default_number ?? null
snapshot.pending_draw_number =
typeof payload.pending_draw_number === 'number' ? payload.pending_draw_number : null
snapshot.period_seconds = payload.period_seconds ?? 30
snapshot.bet_seconds = payload.bet_seconds ?? 20
snapshot.pick_max_number_count = payload.pick_max_number_count ?? 10
snapshot.draw_number_max = payload.draw_number_max ?? 36
snapshot.remaining_seconds = payload.remaining_seconds ?? 0
snapshot.bet_remaining_seconds = payload.bet_remaining_seconds ?? 0
snapshot.payout_remaining_seconds = payload.payout_remaining_seconds ?? 0
snapshot.is_payout_phase = !!payload.is_payout_phase
snapshot.can_calculate = !!payload.can_calculate
snapshot.can_draw = !!payload.can_draw
snapshot.can_schedule_draw = !!payload.can_schedule_draw || !!payload.can_draw
syncServerClock(payload.server_time)
})
} catch {
pushConnected.value = false
@@ -244,12 +314,19 @@ async function onDraw() {
}
const countdownText = computed(() => {
const total = snapshot.remaining_seconds ?? 0
const bet = snapshot.bet_remaining_seconds ?? 0
return `${t('game.live.bet_countdown')} ${bet}s / ${t('game.live.draw_countdown')} ${total}s`
const draw = snapshot.remaining_seconds ?? 0
let payoutPart = t('game.live.payout_na')
if (snapshot.is_payout_phase && payoutRemainingLive.value !== null) {
payoutPart = `${payoutRemainingLive.value}s`
}
return `${t('game.live.bet_countdown')} ${bet}s / ${t('game.live.draw_countdown')} ${draw}s / ${t('game.live.payout_countdown')} ${payoutPart}`
})
onMounted(async () => {
clockTimer = window.setInterval(() => {
clockTick.value++
}, 1000)
await loadSnapshot()
try {
await initPush()
@@ -269,6 +346,10 @@ onUnmounted(() => {
}
stopPolling()
stopPushWatchdog()
if (clockTimer !== null) {
window.clearInterval(clockTimer)
clockTimer = null
}
})
function startPolling() {

View File

@@ -63,7 +63,7 @@ const logText = computed(() => {
})
function appendLog(line: PushTestLogLine) {
logs.value = [...logs.value, line].slice(-200)
logs.value = [line, ...logs.value].slice(0, 200)
}
function clearLogs() {