后台游戏对局实时显示-优化

This commit is contained in:
2026-04-16 16:36:57 +08:00
parent c7149e7058
commit 015d1e4d5b
12 changed files with 499 additions and 212 deletions

View File

@@ -1,14 +1,29 @@
<template>
<div class="default-main">
<el-alert type="info" :title="t('game.live.tip')" show-icon class="mb-12" />
<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">
<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>{{ t('game.live.countdown') }}: {{ countdownText }}</div>
</div>
<el-button type="primary" :loading="loading" @click="loadSnapshot">{{ t('Refresh') }}</el-button>
<div class="header-actions">
<el-input-number v-model="manualNumber" :min="1" :max="36" :step="1" />
<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">
{{ t('game.live.btn_draw') }}
</el-button>
<el-button :loading="loading" @click="loadSnapshot">{{ t('Refresh') }}</el-button>
</div>
</div>
<div class="result-row">
<span>{{ t('game.live.calc_result_number') }}: {{ calcResultNumber ?? '-' }}</span>
<span>{{ t('game.live.calc_estimated_loss') }}: {{ calcEstimatedLoss }}</span>
</div>
</el-card>
@@ -43,7 +58,7 @@
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import createAxios from '/@/utils/axios'
@@ -52,19 +67,42 @@ interface Snapshot {
bets: anyObj[]
candidate_numbers: anyObj[]
ai_default_number: number | null
period_seconds?: number
bet_seconds?: number
pick_max_number_count?: number
remaining_seconds?: number
bet_remaining_seconds?: number
can_calculate?: boolean
can_draw?: boolean
}
const { t } = useI18n()
const loading = ref(false)
const pushConnected = ref(false)
const lastPushAt = ref(0)
const snapshot = reactive<Snapshot>({
record: null,
bets: [],
candidate_numbers: [],
ai_default_number: null,
period_seconds: 30,
bet_seconds: 20,
pick_max_number_count: 36,
remaining_seconds: 0,
bet_remaining_seconds: 0,
can_calculate: false,
can_draw: false,
})
const calcLoading = ref(false)
const drawLoading = ref(false)
const manualNumber = ref<number | null>(1)
const calcResultNumber = ref<number | null>(null)
const calcEstimatedLoss = ref<string>('0.0000')
let pushClient: any = null
let pushChannel: any = null
let pollTimer: number | null = null
let pushWatchdogTimer: number | null = null
function formatPicks(v: unknown): string {
if (Array.isArray(v)) return JSON.stringify(v)
@@ -81,6 +119,14 @@ 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.period_seconds = res.data.period_seconds ?? 30
snapshot.bet_seconds = res.data.bet_seconds ?? 20
snapshot.pick_max_number_count = 36
snapshot.remaining_seconds = res.data.remaining_seconds ?? 0
snapshot.bet_remaining_seconds = res.data.bet_remaining_seconds ?? 0
snapshot.can_calculate = !!res.data.can_calculate
snapshot.can_draw = !!res.data.can_draw
if (manualNumber.value === null || manualNumber.value < 1 || manualNumber.value > 36) manualNumber.value = 1
}
} finally {
loading.value = false
@@ -90,23 +136,49 @@ async function loadSnapshot() {
async function initPush() {
const cfgRes = await createAxios({ url: '/admin/game.Live/pushConfig', method: 'get', showCodeMessage: false })
if (cfgRes.code !== 1 || !cfgRes.data) {
pushConnected.value = false
return
}
const { url, app_key, channel, event } = cfgRes.data
await loadPushJs()
const PushCtor = (window as any).Push
if (!PushCtor) {
try {
await loadPushJs()
} catch {
pushConnected.value = false
startPolling()
return
}
pushClient = new PushCtor({ url, app_key })
pushChannel = pushClient.subscribe(channel)
pushChannel.on(event, (payload: anyObj) => {
snapshot.record = payload.record || null
snapshot.bets = payload.bets || []
snapshot.candidate_numbers = payload.candidate_numbers || []
snapshot.ai_default_number = payload.ai_default_number ?? null
})
const PushCtor = (window as any).Push
if (!PushCtor) {
pushConnected.value = false
startPolling()
return
}
try {
pushClient = new PushCtor({ url, app_key })
pushChannel = pushClient.subscribe(channel)
pushConnected.value = false
startPushWatchdog()
stopPolling()
pushChannel.on(event, (payload: anyObj) => {
lastPushAt.value = Date.now()
pushConnected.value = true
snapshot.record = payload.record || null
snapshot.bets = payload.bets || []
snapshot.candidate_numbers = payload.candidate_numbers || []
snapshot.ai_default_number = payload.ai_default_number ?? null
snapshot.period_seconds = payload.period_seconds ?? 30
snapshot.bet_seconds = payload.bet_seconds ?? 20
snapshot.pick_max_number_count = 36
snapshot.remaining_seconds = payload.remaining_seconds ?? 0
snapshot.bet_remaining_seconds = payload.bet_remaining_seconds ?? 0
snapshot.can_calculate = !!payload.can_calculate
snapshot.can_draw = !!payload.can_draw
})
} catch {
pushConnected.value = false
startPolling()
}
}
async function loadPushJs() {
@@ -122,10 +194,113 @@ async function loadPushJs() {
})
}
async function onCalculate() {
if (!snapshot.record) return
calcLoading.value = true
try {
const res = await createAxios({
url: '/admin/game.Live/calculate',
method: 'post',
data: {
record_id: snapshot.record.id,
manual_number: manualNumber.value,
},
showSuccessMessage: true,
})
if (res.code === 1 && res.data) {
snapshot.candidate_numbers = res.data.candidate_numbers || []
snapshot.ai_default_number = res.data.ai_default_number ?? null
calcResultNumber.value = res.data.final_number ?? null
calcEstimatedLoss.value = String(res.data.final_estimated_loss ?? '0.0000')
}
} finally {
calcLoading.value = false
}
}
async function onDraw() {
if (!snapshot.record) return
drawLoading.value = true
try {
await createAxios({
url: '/admin/game.Live/draw',
method: 'post',
data: {
record_id: snapshot.record.id,
manual_number: manualNumber.value,
},
showSuccessMessage: true,
})
await loadSnapshot()
} finally {
drawLoading.value = false
}
}
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`
})
onMounted(async () => {
await loadSnapshot()
await initPush()
try {
await initPush()
} catch {
pushConnected.value = false
startPolling()
}
})
onUnmounted(() => {
try {
if (pushClient && typeof pushClient.disconnect === 'function') {
pushClient.disconnect()
}
} catch {
// ignore
}
stopPolling()
stopPushWatchdog()
})
function startPolling() {
if (pollTimer !== null) {
return
}
pollTimer = window.setInterval(() => {
void loadSnapshot()
}, 2000)
}
function stopPolling() {
if (pollTimer !== null) {
window.clearInterval(pollTimer)
pollTimer = null
}
}
function startPushWatchdog() {
if (pushWatchdogTimer !== null) {
return
}
pushWatchdogTimer = window.setInterval(() => {
const state = pushClient && pushClient.connection ? String(pushClient.connection.state || '') : ''
const stateConnected = state === 'connected' || state === 'connecting'
const hasRecentPush = lastPushAt.value > 0 && Date.now() - lastPushAt.value <= 6000
if (!stateConnected || !hasRecentPush) {
pushConnected.value = false
}
}, 1000)
}
function stopPushWatchdog() {
if (pushWatchdogTimer !== null) {
window.clearInterval(pushWatchdogTimer)
pushWatchdogTimer = null
}
}
</script>
<style scoped lang="scss">
@@ -136,5 +311,16 @@ onMounted(async () => {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
}
.header-actions {
display: flex;
align-items: center;
gap: 8px;
}
.result-row {
margin-top: 12px;
display: flex;
gap: 16px;
}
</style>