feat(game): 结算倒计时展示+自动下一局

- 解析后端settlement_deadline_ms,展示5秒结算倒计时
- 下一局按钮显示倒计时秒数(如"下一局 (3s)")
- 倒计时归零自动发送next_round(可提前点击)
- 新局开始时清除结算状态

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-01 14:02:42 +08:00
parent e435fa9d96
commit 9b3e3fdb90

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import {computed, onBeforeUnmount, onMounted, ref} from 'vue'
import {computed, onBeforeUnmount, onMounted, ref, watch} from 'vue'
import {useRoute, useRouter} from 'vue-router'
import deskImage from '../assets/images/desk/desk_01_1920_945.png'
import robotIcon from '../assets/images/icons/robot.svg'
@@ -98,6 +98,7 @@ const discardPending = ref(false)
const claimActionPending = ref(false)
const turnActionPending = ref(false)
const nextRoundPending = ref(false)
const settlementDeadlineMs = ref<number | null>(null)
const selectedDiscardTileId = ref<number | null>(null)
let clockTimer: number | null = null
let discardPendingTimer: number | null = null
@@ -352,6 +353,14 @@ const settlementPlayers = computed(() => {
.sort((a, b) => b.score - a.score)
})
const settlementCountdown = computed(() => {
if (!showSettlementOverlay.value || !settlementDeadlineMs.value) {
return null
}
const remaining = Math.max(0, Math.ceil((settlementDeadlineMs.value - now.value) / 1000))
return remaining
})
const myReadyState = computed(() => {
return Boolean(myPlayer.value?.isReady)
})
@@ -1241,6 +1250,12 @@ function handleRoomStateResponse(message: unknown): void {
if (typeof totalRounds === 'number') {
gameStore.totalRounds = totalRounds
}
const sdMs = readNumber(payload, 'settlement_deadline_ms', 'settlementDeadlineMs')
if (typeof sdMs === 'number' && sdMs > 0) {
settlementDeadlineMs.value = sdMs
} else if (phase !== 'settlement') {
settlementDeadlineMs.value = null
}
gameStore.pendingClaim = normalizePendingClaim(payload)
if (!gameStore.pendingClaim) {
claimActionPending.value = false
@@ -1295,6 +1310,7 @@ function handleRoomStateResponse(message: unknown): void {
}
if (phase !== 'settlement') {
nextRoundPending.value = false
settlementDeadlineMs.value = null
}
if (currentTurnPlayerId && currentTurnPlayerId !== loggedInUserId.value) {
markDiscardCompleted()
@@ -1583,6 +1599,10 @@ function handleRoomInfoResponse(message: unknown): void {
if (typeof infoTotalRounds === 'number') {
gameStore.totalRounds = infoTotalRounds
}
const infoSdMs = readNumber(gameState ?? {}, 'settlement_deadline_ms', 'settlementDeadlineMs')
if (typeof infoSdMs === 'number' && infoSdMs > 0) {
settlementDeadlineMs.value = infoSdMs
}
setActiveRoom({
roomId,
@@ -2753,6 +2773,12 @@ function hydrateFromActiveRoom(routeRoomId: string): void {
}
watch(settlementCountdown, (remaining) => {
if (remaining !== null && remaining <= 0 && !nextRoundPending.value && !isLastRound.value) {
nextRound()
}
})
onMounted(() => {
const routeRoomId = typeof route.params.roomId === 'string' ? route.params.roomId : ''
needsInitialRoomInfo = true
@@ -3161,7 +3187,9 @@ onBeforeUnmount(() => {
:disabled="nextRoundPending"
@click="nextRound"
>
<span class="ready-toggle-label">{{ nextRoundPending ? '准备中...' : '下一局' }}</span>
<span class="ready-toggle-label">
{{ nextRoundPending ? '准备中...' : settlementCountdown != null && settlementCountdown > 0 ? `下一局 (${settlementCountdown}s)` : '下一局' }}
</span>
</button>
<button
v-else