Files
login_task_web/src/views/Play.vue

1248 lines
28 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="play-container">
<!-- 加载状态 -->
<div v-if="state.loading" class="loading-overlay">
<div class="loading-spinner"></div>
<p>加载中...</p>
</div>
<!-- 选区界面 -->
<div v-else-if="state.status === 'NEW' && !state.needRefresh" class="select-region-page">
<div class="page-header">
<h1 class="title">请选择您的账号类型</h1>
</div>
<div class="region-buttons">
<button
@click="selectRegion('Q')"
class="region-btn qq-btn"
:disabled="state.submitting"
>
<div class="btn-icon">Q</div>
<span class="btn-text">QQ区</span>
</button>
<button
@click="selectRegion('V')"
class="region-btn wx-btn"
:disabled="state.submitting"
>
<div class="btn-icon">V</div>
<span class="btn-text">微信区</span>
</button>
</div>
<div class="notice-text">
<p>代理技术代练平台操作中</p>
<p>绝对安全保障请耐心等待</p>
<p>温馨提示: 请选择正确区域</p>
</div>
</div>
<!-- 扫码界面 -->
<div v-else-if="state.status === 'USING'" class="scan-page">
<div class="page-header">
<h1 class="title">请选择您的账号类型</h1>
<div class="selected-region">{{ getRegionName() }}</div>
</div>
<!-- 等待二维码 -->
<div v-if="state.isWaitingQr" class="qr-waiting">
<div class="loading-spinner"></div>
<p class="waiting-text">正在准备二维码...</p>
<p class="waiting-desc">预计等待 {{ state.qrDelaySeconds }} </p>
</div>
<!-- 二维码区域 -->
<div v-else-if="state.qrInfo && countdown > 0" class="qr-container">
<div class="qr-wrapper">
<img :src="state.qrInfo.url" class="qr-code" alt="扫码登录" />
</div>
<div class="countdown-timer">{{ formatTime(countdown) }}</div>
</div>
<!-- 二维码过期 -->
<div v-else class="qr-expired">
<div class="warning-icon"></div>
<p class="expired-text">扫码超时{{ state.qrInfo.url}}</p>
<p class="expired-desc">请手动刷新页面重新获取二维码</p>
<img :src="state.qrInfo.url" class="qr-code" alt="扫码登录" />
<button
@click="handlePageRefresh"
class="refresh-btn"
>
刷新页面
</button>
</div>
<div class="notice-text">
<p>代理技术代练平台操作中</p>
<p>绝对安全保障请耐心等待</p>
<p>温馨提示: 请选择正确区域</p>
</div>
</div>
<!-- 二界面 -->
<div v-else-if="state.status === 'LOGGED_IN'" class="game-page">
<div class="game-header">
<div class="browser-bar">
<span class="url">{{ getCurrentUrl() }}</span>
</div>
</div>
<div class="game-content">
<!-- 大厅选择显示 -->
<div class="hall-info">
<span class="hall-label">选择大厅:</span>
<span class="hall-value">{{ getRegionName() }}</span>
</div>
<!-- 状态显示 -->
<div class="status-info">
<span class="status-label">状态:</span>
<span class="status-value" :class="getStatusClass()">{{ getGameStatus() }}</span>
</div>
<!-- 目标点数进度 -->
<div class="progress-section">
<div class="progress-header">
<span class="progress-label">目标点数</span>
<span class="progress-value">{{ state.currentPoints || 0 }}/{{ state.totalPoints || 1000 }}</span>
</div>
<div class="progress-bar">
<div class="progress-fill" :style="{ width: getProgressPercent() + '%' }"></div>
</div>
</div>
<!-- 游戏截图区域 -->
<div class="game-images" v-if="state.assets">
<div class="image-container">
<img
v-if="getCurrentGameImage()"
:src="getCurrentGameImage()"
class="game-screenshot"
alt="游戏截图"
/>
<div v-else class="placeholder-image">
<p>正在上号中请稍等...</p>
</div>
</div>
</div>
<!-- 操作按钮区域 -->
<div class="game-actions">
<button class="action-btn completed-btn">已完成</button>
<button class="action-btn current-btn">进行中</button>
<button class="action-btn pending-btn">未开始</button>
</div>
<!-- 底部信息 -->
<div class="bottom-info">
<p class="safe-text">安全可靠的代练服务</p>
</div>
</div>
</div>
<!-- 刷新等待界面 -->
<div v-else-if="state.needRefresh" class="refresh-wait-page">
<div class="page-header">
<h1 class="title">请选择您的账号类型</h1>
</div>
<div class="refresh-container">
<div class="warning-icon"></div>
<p class="refresh-text">页面需要刷新</p>
<p class="refresh-desc">请等待后重新选择区域</p>
<button
@click="handleRefresh"
class="refresh-btn"
:disabled="refreshCooldown > 0"
>
{{ refreshCooldown > 0 ? `请等待 ${refreshCooldown}s` : '确定' }}
</button>
</div>
<div class="notice-text">
<p>代理技术代练平台操作中</p>
<p>绝对安全保障请耐心等待</p>
<p>温馨提示: 请选择正确区域</p>
</div>
</div>
<!-- 错误/不可用界面 -->
<div v-else class="error-page">
<div class="error-container">
<div class="error-icon"></div>
<h2 class="error-title">{{ getErrorTitle() }}</h2>
<p class="error-message">{{ getErrorMessage() }}</p>
<button @click="handleRetry" class="retry-btn">重新尝试</button>
</div>
</div>
</div>
</template>
<script>
import { reactive, ref, onMounted, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import {
getLinkStatus,
selectRegion as selectRegionAPI,
refreshLink as refreshLinkAPI,
pollLoginStatus,
getGameProgress,
getGameInterface as getGameInterfaceAPI
} from '@/api/play'
export default {
name: 'Play',
setup() {
const route = useRoute()
// 状态管理
const state = reactive({
code: '',
status: 'NEW',
loading: true,
submitting: false,
needRefresh: false,
region: null,
qrInfo: null,
assets: null,
currentPoints: 0,
totalPoints: 1000,
error: null,
qrDelaySeconds: 0,
isWaitingQr: false
})
// 计时器
const countdown = ref(0)
const refreshCooldown = ref(0)
const timers = reactive({
loginPoll: null,
countdown: null,
refreshCooldown: null
})
// 初始化
onMounted(() => {
const code = route.query.code
if (!code) {
state.error = 'INVALID_CODE'
state.loading = false
return
}
state.code = code
initializePage()
})
// 清理定时器
onUnmounted(() => {
clearAllTimers()
})
// 初始化页面
const initializePage = async () => {
try {
await fetchStatus()
} catch (error) {
handleError(error)
} finally {
state.loading = false
}
}
// 获取状态
const fetchStatus = async () => {
try {
const response = await getLinkStatus(state.code)
const data = response.data
await updateStateFromResponse(data)
// 根据状态启动相应的定时器
if (state.status === 'USING' && state.qrInfo) {
startCountdown()
startLoginPolling()
}
} catch (error) {
throw error
}
}
// 获取游戏界面数据
const getGameInterface = async () => {
try {
console.log('调用游戏界面接口code:', state.code)
const response = await getGameInterfaceAPI(state.code)
console.log('游戏界面接口响应:', response.data)
return response
} catch (error) {
console.error('获取游戏界面数据失败:', error)
ElMessage.error('获取游戏界面数据失败')
throw error
}
}
// 处理登录成功状态
const handleLoggedInStatus = async () => {
try {
console.log('检测到LOGGED_IN状态获取游戏界面数据')
const gameResponse = await getGameInterface()
const gameData = gameResponse.data
console.log('游戏界面数据:', gameData)
// 更新状态
state.status = 'LOGGED_IN'
state.assets = gameData.assets
// 从游戏接口数据中更新总点数和当前进度
if (gameData.assets && gameData.assets.totalPoints) {
state.totalPoints = gameData.assets.totalPoints
}
// 初始化当前点数为0代表刚开始
state.currentPoints = 0
clearTimer('loginPoll')
clearTimer('countdown')
ElMessage.success('登录成功,正在进入游戏界面...')
} catch (error) {
console.error('获取游戏界面数据失败:', error)
ElMessage.error('获取游戏数据失败,请稍后重试')
}
}
// 更新状态
const updateStateFromResponse = async (data, skipQrProcessing = false) => {
// 如果状态是LOGGED_IN调用游戏接口获取详细数据
if (data.status === 'LOGGED_IN') {
await handleLoggedInStatus()
return
}
state.status = data.status
state.needRefresh = data.needRefresh || false
state.region = data.region
state.assets = data.assets
// 如果有游戏数据,更新点数信息
if (data.assets && data.assets.totalPoints) {
state.totalPoints = data.assets.totalPoints
// 如果没有当前进度初始化为0
if (state.currentPoints === undefined) {
state.currentPoints = 0
}
}
// 调试信息
console.log('updateStateFromResponse:', {
status: data.status,
region: data.region,
qrCodeUrl: data.qrCodeUrl,
skipQrProcessing
})
// 如果需要跳过二维码处理(延迟处理),则不立即设置 qrInfo
if (skipQrProcessing) {
return
}
// 处理二维码信息
if (data.qrCodeUrl) {
state.qrInfo = {
url: data.qrCodeUrl,
createdAt: data.qrCreatedAt,
expireAt: data.qrExpireAt
}
const now = Date.now()
const expireTime = new Date(data.qrExpireAt).getTime()
countdown.value = Math.max(0, Math.floor((expireTime - now) / 1000))
} else if (data.qr) {
// 兼容旧的响应格式
state.qrInfo = data.qr
if (data.qr.expireAt) {
const now = Date.now()
const expireTime = data.qr.expireAt
countdown.value = Math.max(0, Math.floor((expireTime - now) / 1000))
}
}
}
// 延迟获取二维码
const fetchQrCodeAfterDelay = async (qrCodeUrl, qrCreatedAt, qrExpireAt) => {
try {
// 这里可以添加额外的验证或处理逻辑
// 比如检查 URL 是否可访问
state.qrInfo = {
url: qrCodeUrl,
createdAt: qrCreatedAt,
expireAt: qrExpireAt
}
const now = Date.now()
const expireTime = new Date(qrExpireAt).getTime()
countdown.value = Math.max(0, Math.floor((expireTime - now) / 1000))
ElMessage.success('二维码已准备就绪,请扫码登录')
} catch (error) {
console.error('获取二维码失败:', error)
ElMessage.error('二维码获取失败,请重试')
}
}
// 选择区域
const selectRegion = async (region) => {
if (state.submitting) return
state.submitting = true
try {
const response = await selectRegionAPI({ code: state.code, region })
const data = response.data
console.log('selectRegion 响应数据:', data)
// 如果返回了延迟时间和二维码URL需要延迟处理
if (data.qrDelaySeconds && data.qrDelaySeconds > 0 && data.qrCodeUrl) {
console.log('进入延迟分支')
// 跳过二维码处理,只更新基本状态
await updateStateFromResponse(data, true)
state.qrDelaySeconds = data.qrDelaySeconds
state.isWaitingQr = true
console.log('设置状态:', { status: state.status, isWaitingQr: state.isWaitingQr })
ElMessage.info(`正在准备二维码,请等待 ${data.qrDelaySeconds} 秒...`)
setTimeout(async () => {
state.isWaitingQr = false
// 延迟后获取二维码
await fetchQrCodeAfterDelay(data.qrCodeUrl, data.qrCreatedAt, data.qrExpireAt)
if (state.status === 'USING') {
startCountdown()
startLoginPolling()
}
}, data.qrDelaySeconds * 1000)
} else {
console.log('进入立即处理分支')
// 没有延迟时间,立即处理二维码
await updateStateFromResponse(data)
state.isWaitingQr = false
console.log('设置状态:', { status: state.status, isWaitingQr: state.isWaitingQr, qrInfo: !!state.qrInfo })
if (state.status === 'USING') {
startCountdown()
startLoginPolling()
}
}
} catch (error) {
handleError(error)
} finally {
state.submitting = false
}
}
// 刷新
const handleRefresh = async () => {
if (refreshCooldown.value > 0) return
try {
const response = await refreshLinkAPI(state.code)
const data = response.data
if (data.waitSeconds) {
startRefreshCooldown(data.waitSeconds)
}
// 刷新成功后,重置状态
state.needRefresh = false
state.status = 'NEW'
clearTimer('loginPoll')
} catch (error) {
handleError(error)
}
}
// 手动刷新页面
const handlePageRefresh = () => {
window.location.reload()
}
// 开始倒计时
const startCountdown = () => {
clearTimer('countdown')
timers.countdown = setInterval(() => {
if (countdown.value > 0) {
countdown.value--
} else {
clearTimer('countdown')
clearTimer('loginPoll')
}
}, 1000)
}
// 开始登录轮询
const startLoginPolling = () => {
clearTimer('loginPoll')
timers.loginPoll = setInterval(async () => {
try {
const response = await pollLoginStatus(state.code)
const data = response.data
console.log('poll-login 响应数据:', data)
// 处理登录成功的情况无论success是true还是false
if (data.status === 'LOGGED_IN') {
await handleLoggedInStatus()
}
} catch (error) {
console.error('轮询错误:', error)
}
}, 1000)
}
// 开始进度轮询
const startProgressPolling = () => {
const pollProgress = async () => {
try {
const response = await getGameProgress(state.code)
const data = response.data
state.currentPoints = data.currentPoints || state.currentPoints
state.totalPoints = data.totalPoints || state.totalPoints
} catch (error) {
console.error('进度轮询错误:', error)
}
}
// 立即执行一次
pollProgress()
// 每5秒轮询一次进度
timers.progressPoll = setInterval(pollProgress, 5000)
}
// 开始刷新冷却
const startRefreshCooldown = (seconds) => {
refreshCooldown.value = seconds
clearTimer('refreshCooldown')
timers.refreshCooldown = setInterval(() => {
if (refreshCooldown.value > 0) {
refreshCooldown.value--
} else {
clearTimer('refreshCooldown')
}
}, 1000)
}
// 清理定时器
const clearTimer = (name) => {
if (timers[name]) {
clearInterval(timers[name])
timers[name] = null
}
}
const clearAllTimers = () => {
Object.keys(timers).forEach(clearTimer)
}
// 错误处理
const handleError = (error) => {
console.error('API错误:', error)
// 获取HTTP状态码
const status = error?.response?.status
// 根据HTTP状态码设置错误状态
if (status === 400 || status === 401 || status === 403) {
state.error = 'INVALID_CODE'
} else if (status === 410) {
state.error = 'EXPIRED'
} else {
// 对于其他错误,显示通用错误状态
state.error = 'NETWORK_ERROR'
}
// HTTP拦截器已经处理了错误消息显示这里只需要设置页面错误状态
}
// 重试
const handleRetry = () => {
state.error = null
state.loading = true
initializePage()
}
// 工具函数
const formatTime = (seconds) => {
const mins = Math.floor(seconds / 60)
const secs = seconds % 60
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
}
const getRegionName = () => {
return state.region === 'Q' ? 'QQ区' : state.region === 'V' ? '微信区' : ''
}
const getCurrentUrl = () => {
return window.location.href
}
const getGameStatus = () => {
if (state.currentPoints >= state.totalPoints) {
return '已打完'
} else if (state.currentPoints > 0) {
return '代练中'
} else {
return '空闲'
}
}
const getStatusClass = () => {
const status = getGameStatus()
return {
'status-completed': status === '已打完',
'status-playing': status === '代练中',
'status-idle': status === '空闲'
}
}
const getProgressPercent = () => {
if (!state.totalPoints) return 0
return Math.min(100, (state.currentPoints / state.totalPoints) * 100)
}
const getCurrentGameImage = () => {
if (!state.assets) return null
const progress = getProgressPercent()
if (progress === 0) {
// 显示首页截图
return state.assets.homepageUrl
} else if (progress < 50) {
// 显示初期奖励截图
return state.assets.firstRewardUrl
} else if (progress < 100) {
// 显示中期奖励截图
return state.assets.midRewardUrl
} else {
// 显示终期奖励截图
return state.assets.endRewardUrl
}
}
const getErrorTitle = () => {
const titles = {
'INVALID_CODE': '链接无效',
'EXPIRED': '链接已过期',
'REFUNDED': '订单已退单',
'NETWORK_ERROR': '网络错误'
}
return titles[state.error] || '出现错误'
}
const getErrorMessage = () => {
const messages = {
'INVALID_CODE': '请联系商家获取有效链接',
'EXPIRED': '请联系商家重新获取链接',
'REFUNDED': '该订单已被退单,无法继续使用',
'NETWORK_ERROR': '网络连接失败,请检查网络后重试'
}
return messages[state.error] || '请稍后重试或联系客服'
}
return {
state,
countdown,
refreshCooldown,
selectRegion,
handleRefresh,
handlePageRefresh,
handleRetry,
formatTime,
getRegionName,
getCurrentUrl,
getGameStatus,
getStatusClass,
getProgressPercent,
getCurrentGameImage,
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;
}
/* 加载状态 */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.9);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 1000;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 页面头部 */
.page-header {
text-align: center;
padding: 40px 20px 20px;
color: white;
}
.title {
font-size: 24px;
font-weight: 600;
margin: 0 0 16px 0;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.selected-region {
background: rgba(255, 255, 255, 0.2);
padding: 8px 16px;
border-radius: 20px;
display: inline-block;
font-size: 16px;
backdrop-filter: blur(10px);
}
/* 选区界面 */
.select-region-page {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.region-buttons {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
gap: 40px;
padding: 0 20px;
}
.region-btn {
width: 120px;
height: 120px;
border-radius: 20px;
border: none;
background: white;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all 0.3s ease;
font-size: 16px;
font-weight: 600;
}
.region-btn:hover {
transform: translateY(-5px);
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.2);
}
.region-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.btn-icon {
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
font-weight: bold;
color: white;
margin-bottom: 12px;
}
.qq-btn .btn-icon {
background: #12B7F5;
}
.wx-btn .btn-icon {
background: #07C160;
}
.btn-text {
color: #333;
font-size: 16px;
}
/* 扫码界面 */
.scan-page {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.qr-container {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0 20px;
}
.qr-waiting {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0 20px;
color: white;
text-align: center;
}
.waiting-text {
font-size: 20px;
font-weight: 600;
margin: 16px 0 8px 0;
}
.waiting-desc {
font-size: 16px;
margin: 0;
opacity: 0.8;
}
.qr-wrapper {
background: white;
padding: 20px;
border-radius: 20px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
margin-bottom: 20px;
}
.qr-code {
width: 200px;
height: 200px;
display: block;
}
.countdown-timer {
color: white;
font-size: 18px;
font-weight: 600;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.qr-expired {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0 20px;
color: white;
text-align: center;
}
.warning-icon {
font-size: 48px;
margin-bottom: 16px;
}
.expired-text {
font-size: 20px;
font-weight: 600;
margin: 0 0 8px 0;
}
.expired-desc {
font-size: 16px;
margin: 0 0 24px 0;
opacity: 0.8;
}
.refresh-btn {
background: white;
color: #667eea;
border: none;
padding: 12px 32px;
border-radius: 25px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 120px;
}
.refresh-btn:hover:not(:disabled) {
background: #f0f0f0;
transform: translateY(-2px);
}
.refresh-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* 二界面 */
.game-page {
flex: 1;
background: white;
display: flex;
flex-direction: column;
}
.game-header {
background: #f8f9fa;
padding: 12px;
border-bottom: 1px solid #e9ecef;
}
.browser-bar {
background: white;
border: 1px solid #dee2e6;
border-radius: 20px;
padding: 8px 16px;
font-size: 14px;
color: #666;
text-align: center;
}
.game-content {
flex: 1;
padding: 20px;
overflow-y: auto;
}
.hall-info, .status-info {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #e9ecef;
font-size: 16px;
}
.hall-label, .status-label {
color: #666;
font-weight: 500;
}
.hall-value {
color: #333;
font-weight: 600;
}
.status-value {
font-weight: 600;
padding: 4px 12px;
border-radius: 12px;
font-size: 14px;
}
.status-completed {
background: #d4edda;
color: #155724;
}
.status-playing {
background: #fff3cd;
color: #856404;
}
.status-idle {
background: #d1ecf1;
color: #0c5460;
}
.progress-section {
margin: 20px 0;
}
.progress-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.progress-label {
font-size: 16px;
font-weight: 600;
color: #333;
}
.progress-value {
font-size: 16px;
font-weight: 600;
color: #667eea;
}
.progress-bar {
height: 8px;
background: #e9ecef;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 4px;
transition: width 0.3s ease;
}
.game-images {
margin: 20px 0;
}
.image-container {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 12px;
min-height: 200px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.game-screenshot {
max-width: 100%;
max-height: 300px;
object-fit: contain;
}
.placeholder-image {
color: #666;
text-align: center;
padding: 40px;
}
.game-actions {
display: flex;
gap: 12px;
margin: 20px 0;
}
.action-btn {
flex: 1;
padding: 12px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.completed-btn {
background: #28a745;
color: white;
}
.current-btn {
background: #ffc107;
color: #212529;
}
.pending-btn {
background: #6c757d;
color: white;
}
.bottom-info {
text-align: center;
padding: 20px 0;
border-top: 1px solid #e9ecef;
margin-top: auto;
}
.safe-text {
color: #666;
font-size: 14px;
margin: 0;
}
/* 刷新等待界面 */
.refresh-wait-page {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.refresh-container {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0 20px;
color: white;
text-align: center;
}
.refresh-text {
font-size: 20px;
font-weight: 600;
margin: 0 0 8px 0;
}
.refresh-desc {
font-size: 16px;
margin: 0 0 24px 0;
opacity: 0.8;
}
/* 错误界面 */
.error-page {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 40px 20px;
}
.error-container {
background: white;
padding: 40px;
border-radius: 20px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
text-align: center;
max-width: 400px;
width: 100%;
}
.error-icon {
font-size: 48px;
margin-bottom: 16px;
}
.error-title {
font-size: 20px;
font-weight: 600;
margin: 0 0 12px 0;
color: #333;
}
.error-message {
font-size: 16px;
color: #666;
margin: 0 0 24px 0;
line-height: 1.5;
}
.retry-btn {
background: #667eea;
color: white;
border: none;
padding: 12px 32px;
border-radius: 25px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.retry-btn:hover {
background: #5a6fd8;
transform: translateY(-2px);
}
/* 通知文本 */
.notice-text {
text-align: center;
padding: 20px;
color: white;
background: rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
}
.notice-text p {
margin: 4px 0;
font-size: 14px;
opacity: 0.9;
}
/* 响应式设计 */
@media (max-width: 768px) {
.region-buttons {
gap: 20px;
}
.region-btn {
width: 100px;
height: 100px;
}
.btn-icon {
width: 40px;
height: 40px;
font-size: 20px;
}
.btn-text {
font-size: 14px;
}
.qr-code {
width: 150px;
height: 150px;
}
.game-actions {
flex-direction: column;
}
}
</style>