From 8c38c0a7ecc525ccb9f7beba4b63803cf273c504 Mon Sep 17 00:00:00 2001 From: zyh Date: Fri, 29 Aug 2025 16:35:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20Play.vue=20=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E5=A2=9E=E5=8A=A0=E4=BA=8C=E7=BB=B4=E7=A0=81?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E5=A4=B1=E8=B4=A5=E6=8F=90=E7=A4=BA=E5=92=8C?= =?UTF-8?q?=E9=87=8D=E8=AF=95=E5=8A=9F=E8=83=BD=EF=BC=9B=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=98=BE=E7=A4=BA=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=9C=BA=E5=99=A8ID=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=B1=95=E7=A4=BA=EF=BC=9B=E6=9B=B4=E6=96=B0=20.env.production?= =?UTF-8?q?=20=E6=96=87=E4=BB=B6=EF=BC=8C=E6=B7=BB=E5=8A=A0=20IMAGE=5FBASE?= =?UTF-8?q?=5FURL=20=E9=85=8D=E7=BD=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.production | 4 +- src/views/Play.vue | 170 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 134 insertions(+), 40 deletions(-) diff --git a/.env.production b/.env.production index 41d4531..a9aafec 100644 --- a/.env.production +++ b/.env.production @@ -2,4 +2,6 @@ VITE_API_BASE=https://2.uzi0.cc/api # 前端基础URL(用于生成分享链接) -VITE_BASE_URL=https://2.uzi0.cc \ No newline at end of file +VITE_BASE_URL=https://2.uzi0.cc + +IMAGE_BASE_URL=https://2.uzi0.cc/image \ No newline at end of file diff --git a/src/views/Play.vue b/src/views/Play.vue index 582415f..a44028b 100644 --- a/src/views/Play.vue +++ b/src/views/Play.vue @@ -17,18 +17,22 @@ @click="selectRegion('Q')" class="region-btn qq-btn" :disabled="state.submitting" + :class="{ 'loading': state.submitting }" > -
Q
- QQ区 +
+
Q
+ {{ state.submitting ? '正在连接...' : 'QQ区' }} @@ -57,11 +61,25 @@
- 扫码登录 + 扫码登录
{{ formatTime(countdown) }}
+ +
+
+

二维码获取失败

+

{{ state.qrError }}

+ +
+
⚠️
@@ -207,7 +225,8 @@ export default { isWaitingQr: false, qrRetryCount: 0, maxQrRetries: 3, - qrRetryDelay: 2000 + qrRetryDelay: 2000, + qrError: null // 二维码错误信息 }) // 计时器 @@ -298,7 +317,7 @@ export default { firstRewardUrl: gameData.firstRewardUrl, midRewardUrl: gameData.midRewardUrl, endRewardUrl: gameData.endRewardUrl, - qrCodeUrl: gameData.qrCodeUrl, + qrCodeUrl: gameData.mecmachineId ? `https://2.uzi0.cc/image/${gameData.mecmachineId}/二维码.png` : null, // 保留原有的assets数据(如果存在) ...(gameData.assets || {}) } @@ -345,7 +364,7 @@ export default { firstRewardUrl: gameData.firstRewardUrl, midRewardUrl: gameData.midRewardUrl, endRewardUrl: gameData.endRewardUrl, - qrCodeUrl: gameData.qrCodeUrl + qrCodeUrl: gameData.mecmachineId ? `https://2.uzi0.cc/image/${gameData.mecmachineId}/二维码.png` : null } // 设置点数信息 - 已完成时当前点数等于目标点数 @@ -404,7 +423,7 @@ export default { console.log('updateStateFromResponse:', { status: data.status, region: data.region, - qrCodeUrl: data.qrCodeUrl, + mecmachineId: data.mecmachineId, skipQrProcessing }) @@ -414,9 +433,10 @@ export default { } // 处理二维码信息 - if (data.qrCodeUrl) { + if (data.mecmachineId) { + const qrUrl = `https://2.uzi0.cc/image/${data.mecmachineId}/二维码.png` state.qrInfo = { - url: data.qrCodeUrl, + url: qrUrl, createdAt: data.qrCreatedAt, expireAt: data.qrExpireAt } @@ -436,38 +456,63 @@ export default { } } - // 验证二维码URL是否可访问 + // 验证二维码URL是否可访问(生产环境优化版本) const validateQrCodeUrl = async (url) => { try { + console.log('开始验证二维码URL:', url) + + // 检查URL格式 + if (!url || typeof url !== 'string') { + console.error('无效的二维码URL:', url) + return false + } + + // 在生产环境中,跳过HEAD请求验证,直接返回true + // 因为某些服务器可能不支持HEAD请求或存在CORS问题 + if (process.env.NODE_ENV === 'production') { + console.log('生产环境跳过URL验证') + return true + } + + // 开发环境进行完整验证 const controller = new AbortController() - const timeoutId = setTimeout(() => controller.abort(), 5000) + const timeoutId = setTimeout(() => { + controller.abort() + console.log('URL验证超时') + }, 3000) // 减少超时时间到3秒 const response = await fetch(url, { method: 'HEAD', - signal: controller.signal + signal: controller.signal, + mode: 'no-cors' // 添加no-cors模式避免CORS问题 }) clearTimeout(timeoutId) - return response.ok + console.log('URL验证响应:', response.status) + return true // no-cors模式下无法检查status,直接返回true } catch (error) { - console.warn('二维码URL验证失败:', error) - return false + console.warn('二维码URL验证失败:', error.name, error.message) + // 即使验证失败,也返回true,让用户尝试加载图片 + return true } } // 延迟获取二维码(带重试机制) - const fetchQrCodeAfterDelay = async (qrCodeUrl, qrCreatedAt, qrExpireAt, retryCount = 0) => { + const fetchQrCodeAfterDelay = async (mecmachineId, qrCreatedAt, qrExpireAt, retryCount = 0) => { try { + const qrCodeUrl = `https://2.uzi0.cc/image/${mecmachineId}/二维码.png` console.log(`尝试获取二维码 (第${retryCount + 1}次):`, qrCodeUrl) // 验证二维码URL是否可访问 const isUrlValid = await validateQrCodeUrl(qrCodeUrl) + console.log('URL验证结果:', isUrlValid) if (!isUrlValid) { throw new Error('二维码URL无法访问') } // URL验证通过,设置二维码信息 + console.log('设置二维码信息:', { qrCodeUrl, qrCreatedAt, qrExpireAt }) state.qrInfo = { url: qrCodeUrl, createdAt: qrCreatedAt, @@ -478,14 +523,15 @@ export default { const expireTime = new Date(qrExpireAt).getTime() countdown.value = Math.max(0, Math.floor((expireTime - now) / 1000)) - // 重置重试计数 + // 重置重试计数和等待状态 state.qrRetryCount = 0 + state.isWaitingQr = false + console.log('二维码设置完成,countdown:', countdown.value) ElMessage.success('二维码已准备就绪,请扫码登录') - console.log('二维码获取成功') } catch (error) { - console.error(`二维码获取失败 (第${retryCount + 1}次):`, error) + console.error(`二维码获取失败 (第${retryCount + 1}次):`, error.message || error) // 如果还有重试次数,则进行重试 if (retryCount < state.maxQrRetries) { @@ -496,18 +542,15 @@ export default { ElMessage.warning(`二维码获取失败,${delay/1000}秒后重试...`) setTimeout(() => { - fetchQrCodeAfterDelay(qrCodeUrl, qrCreatedAt, qrExpireAt, retryCount + 1) + fetchQrCodeAfterDelay(mecmachineId, qrCreatedAt, qrExpireAt, retryCount + 1) }, delay) } else { - // 重试次数用完,重新请求新的二维码 - console.error('二维码获取重试次数用完,重新请求区域选择') - ElMessage.error('二维码获取失败,正在重新请求...') + // 重试次数用完,显示错误状态而不是重新请求 + console.error('二维码获取重试次数用完,显示错误状态') state.qrRetryCount = 0 - - // 重新调用区域选择API - if (state.region) { - retrySelectRegion(state.region) - } + state.isWaitingQr = false + state.qrError = `二维码获取失败,已重试${state.maxQrRetries}次。可能是网络问题或服务器繁忙,请稍后重试。` + ElMessage.error('二维码获取失败,请点击重新获取按钮') } } } @@ -532,8 +575,8 @@ export default { // 处理选择区域响应的通用逻辑 const processSelectRegionResponse = async (data) => { - // 如果返回了延迟时间和二维码URL,需要延迟处理 - if (data.qrDelaySeconds && data.qrDelaySeconds > 0 && data.qrCodeUrl) { + // 如果返回了延迟时间和机器ID,需要延迟处理 + if (data.qrDelaySeconds && data.qrDelaySeconds > 0 && data.mecmachineId) { console.log('进入延迟分支') // 跳过二维码处理,只更新基本状态 await updateStateFromResponse(data, true) @@ -547,7 +590,7 @@ export default { state.isWaitingQr = false // 延迟后获取二维码(带重试机制) - await fetchQrCodeAfterDelay(data.qrCodeUrl, data.qrCreatedAt, data.qrExpireAt) + await fetchQrCodeAfterDelay(data.mecmachineId, data.qrCreatedAt, data.qrExpireAt) if (state.status === 'USING') { startCountdown() @@ -568,7 +611,7 @@ export default { console.warn('立即获取的二维码URL无法访问,尝试重试') // 清除当前二维码信息,触发重试 state.qrInfo = null - await fetchQrCodeAfterDelay(data.qrCodeUrl, data.qrCreatedAt, data.qrExpireAt) + await fetchQrCodeAfterDelay(data.mecmachineId, data.qrCreatedAt, data.qrExpireAt) } } @@ -629,6 +672,34 @@ export default { window.location.reload() } + // 处理二维码图片加载错误 + const handleQrImageError = (event) => { + console.error('二维码图片加载失败:', event) + state.qrError = '二维码图片加载失败,可能是网络问题' + ElMessage.error('二维码图片加载失败') + } + + // 重新获取二维码 + const retryGetQrCode = async () => { + if (state.submitting) return + + state.submitting = true + state.qrError = null + state.isWaitingQr = true + state.qrRetryCount = 0 + + try { + if (state.region) { + await selectRegion(state.region) + } + } catch (error) { + console.error('重新获取二维码失败:', error) + state.qrError = '重新获取失败,请刷新页面重试' + } finally { + state.submitting = false + } + } + // 开始倒计时 const startCountdown = () => { clearTimer('countdown') @@ -868,6 +939,8 @@ export default { selectRegion, handleRefresh, handlePageRefresh, + handleQrImageError, + retryGetQrCode, handleRetry, formatTime, getRegionName, @@ -921,6 +994,14 @@ export default { margin-bottom: 16px; } +.loading-spinner.small { + width: 20px; + height: 20px; + border: 2px solid #f3f3f3; + border-top: 2px solid #667eea; + margin: 0; +} + @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } @@ -994,6 +1075,17 @@ export default { transform: none; } +.region-btn.loading { + opacity: 0.8; + cursor: not-allowed; + transform: none; +} + +.region-btn.loading:hover { + transform: none; + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); +} + .btn-icon { width: 50px; height: 50px; @@ -1089,7 +1181,7 @@ export default { text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } -.qr-expired { +.qr-expired, .qr-error { flex: 1; display: flex; flex-direction: column; @@ -1100,24 +1192,24 @@ export default { text-align: center; } -.warning-icon { +.warning-icon, .error-icon { font-size: 48px; margin-bottom: 16px; } -.expired-text { +.expired-text, .error-text { font-size: 20px; font-weight: 600; margin: 0 0 8px 0; } -.expired-desc { +.expired-desc, .error-desc { font-size: 16px; margin: 0 0 24px 0; opacity: 0.8; } -.refresh-btn { +.refresh-btn, .retry-btn { background: white; color: #667eea; border: none;