262 lines
6.7 KiB
Vue
262 lines
6.7 KiB
Vue
<template>
|
|
<div class="play-container">
|
|
<!-- 加载状态 -->
|
|
<LoadingOverlay v-if="state.loading" />
|
|
|
|
<!-- 选区界面 -->
|
|
<SelectRegion
|
|
v-else-if="state.status === 'NEW' && !state.needRefresh"
|
|
:submitting="state.submitting"
|
|
:mecmachine-id="state.mecmachineId || state.machineId"
|
|
@select-region="handleSelectRegion"
|
|
/>
|
|
|
|
<!-- 扫码界面 -->
|
|
<ScanPage
|
|
v-else-if="state.status === 'USING'"
|
|
:region-name="getRegionName()"
|
|
:is-waiting-qr="state.isWaitingQr"
|
|
:qr-info="state.qrInfo"
|
|
:countdown="countdown"
|
|
:qr-delay-seconds="state.qrDelaySeconds"
|
|
:qr-retry-count="state.qrRetryCount"
|
|
:max-qr-retries="state.maxQrRetries"
|
|
:qr-error="state.qrError"
|
|
:submitting="state.submitting"
|
|
:mecmachine-id="state.mecmachineId || state.machineId"
|
|
@qr-image-error="handleQrImageError"
|
|
@qr-image-load="handleQrReadyEarly"
|
|
@retry-qr-code="retryGetQrCode"
|
|
@page-refresh="handlePageRefresh"
|
|
/>
|
|
|
|
<!-- 游戏界面 -->
|
|
<GamePage
|
|
v-else-if="state.status === 'LOGGED_IN'"
|
|
:region="state.region"
|
|
:region-desc="state.regionDesc"
|
|
:machine-id="state.machineId"
|
|
:display-status="getDisplayStatus()"
|
|
:completed-points="state.completedPoints"
|
|
:total-points="state.totalPoints"
|
|
:progress-display-format="state.progressDisplayFormat"
|
|
:status-message="getStatusMessage()"
|
|
:status-message-class="getStatusMessageClass()"
|
|
:assets="state.assets"
|
|
:current-points="state.currentPoints"
|
|
:code-no="state.code"
|
|
/>
|
|
|
|
<!-- 完成状态 -->
|
|
<div v-else-if="state.status === 'COMPLETED'" class="completed-page">
|
|
<div class="completed-text">已打完</div>
|
|
</div>
|
|
|
|
<!-- 刷新等待界面 -->
|
|
<RefreshWaitPage
|
|
v-else-if="state.needRefresh"
|
|
:refresh-cooldown="refreshCooldown"
|
|
:mecmachine-id="state.mecmachineId || state.machineId"
|
|
@refresh="modifiedHandleRefresh"
|
|
/>
|
|
|
|
<!-- 错误界面 -->
|
|
<ErrorPage
|
|
v-else
|
|
:error-title="getErrorTitle()"
|
|
:error-message="getErrorMessage()"
|
|
@retry="handleRetry"
|
|
/>
|
|
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import { usePlayState } from '@/composables/usePlayState'
|
|
import { useTimers } from '@/composables/useTimers'
|
|
import { useQrCode } from '@/composables/useQrCode'
|
|
|
|
import LoadingOverlay from '@/components/play/LoadingOverlay.vue'
|
|
import SelectRegion from '@/components/play/SelectRegion.vue'
|
|
import ScanPage from '@/components/play/ScanPage.vue'
|
|
import GamePage from '@/components/play/GamePage.vue'
|
|
import RefreshWaitPage from '@/components/play/RefreshWaitPage.vue'
|
|
import ErrorPage from '@/components/play/ErrorPage.vue'
|
|
|
|
export default {
|
|
name: 'Play',
|
|
components: {
|
|
LoadingOverlay,
|
|
SelectRegion,
|
|
ScanPage,
|
|
GamePage,
|
|
RefreshWaitPage,
|
|
ErrorPage
|
|
},
|
|
setup() {
|
|
const route = useRoute()
|
|
|
|
const {
|
|
state,
|
|
initializePage,
|
|
updateStateFromResponse,
|
|
handleLoggedInStatus,
|
|
handleCompletedStatus,
|
|
selectRegion,
|
|
handleRefresh,
|
|
handlePageRefresh,
|
|
handleRetry,
|
|
getRegionName,
|
|
getDisplayStatus,
|
|
getStatusMessage,
|
|
getStatusMessageClass,
|
|
getProgressPercent,
|
|
getCurrentGameImage,
|
|
getGameStatus,
|
|
getStatusClass,
|
|
getErrorTitle,
|
|
getErrorMessage
|
|
} = usePlayState()
|
|
|
|
const {
|
|
countdown,
|
|
refreshCooldown,
|
|
clearAllTimers,
|
|
startCountdown,
|
|
startLoginPolling,
|
|
startRefreshCooldown,
|
|
startProgressPolling,
|
|
startQrDelayCountdown,
|
|
clearQrDelayCountdown
|
|
} = useTimers()
|
|
|
|
const {
|
|
processSelectRegionResponse,
|
|
handleQrImageError,
|
|
retryGetQrCode,
|
|
fetchQrCodeAfterDelay,
|
|
clearQrDelayTimeout
|
|
} = useQrCode()
|
|
|
|
onMounted(() => {
|
|
const code = route.query.code
|
|
if (!code) {
|
|
state.error = 'INVALID_CODE'
|
|
state.loading = false
|
|
return
|
|
}
|
|
|
|
state.code = code
|
|
initializePage()
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
clearAllTimers()
|
|
})
|
|
|
|
const handleSelectRegion = async (region) => {
|
|
try {
|
|
const data = await selectRegion(region)
|
|
await processSelectRegionResponse(
|
|
state,
|
|
countdown,
|
|
data,
|
|
() => startCountdown(),
|
|
() => {
|
|
startLoginPolling(state.code, handleLoggedInStatus, handleCompletedStatus)
|
|
startProgressPolling(state.code, (progressData) => {
|
|
state.currentPoints = progressData.currentPoints || state.currentPoints
|
|
state.totalPoints = progressData.totalPoints || state.totalPoints
|
|
if (progressData.completedPoints !== undefined) {
|
|
state.completedPoints = progressData.completedPoints
|
|
}
|
|
})
|
|
},
|
|
updateStateFromResponse,
|
|
startQrDelayCountdown,
|
|
clearQrDelayCountdown
|
|
)
|
|
} catch (error) {
|
|
console.error('处理选择区域失败:', error)
|
|
}
|
|
}
|
|
|
|
const modifiedHandleRefresh = async () => {
|
|
try {
|
|
const data = await handleRefresh(clearAllTimers)
|
|
|
|
if (data.waitSeconds) {
|
|
startRefreshCooldown(data.waitSeconds)
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('刷新失败:', error)
|
|
}
|
|
}
|
|
|
|
const handleQrReadyEarly = async () => {
|
|
// 移除提前显示二维码的逻辑,确保必须等待指定时间后才显示
|
|
console.log('二维码提前就绪事件被忽略,必须等待指定时间后才显示')
|
|
}
|
|
|
|
return {
|
|
state,
|
|
countdown,
|
|
refreshCooldown,
|
|
handleSelectRegion,
|
|
handleRefresh: modifiedHandleRefresh,
|
|
handlePageRefresh,
|
|
handleQrImageError,
|
|
retryGetQrCode,
|
|
handleQrReadyEarly,
|
|
handleRetry,
|
|
getRegionName,
|
|
getDisplayStatus,
|
|
getStatusMessage,
|
|
getStatusMessageClass,
|
|
getProgressPercent,
|
|
getCurrentGameImage,
|
|
getGameStatus,
|
|
getStatusClass,
|
|
getErrorTitle,
|
|
getErrorMessage
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.play-container {
|
|
min-height: 100vh;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: relative;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
}
|
|
|
|
.completed-page {
|
|
flex: 1;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
background: white;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.completed-text {
|
|
font-size: 48px;
|
|
font-weight: bold;
|
|
color: #28a745;
|
|
text-align: center;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.completed-text {
|
|
font-size: 36px;
|
|
}
|
|
}
|
|
</style>
|