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