更新 Play.vue 组件,增加二维码获取失败提示和重试功能;优化状态显示逻辑,新增机器ID信息展示;更新 .env.production 文件,添加 IMAGE_BASE_URL 配置。

This commit is contained in:
zyh
2025-08-29 16:35:10 +08:00
parent 37e56978b1
commit 8c38c0a7ec
2 changed files with 134 additions and 40 deletions

View File

@@ -2,4 +2,6 @@
VITE_API_BASE=https://2.uzi0.cc/api VITE_API_BASE=https://2.uzi0.cc/api
# 前端基础URL用于生成分享链接 # 前端基础URL用于生成分享链接
VITE_BASE_URL=https://2.uzi0.cc VITE_BASE_URL=https://2.uzi0.cc
IMAGE_BASE_URL=https://2.uzi0.cc/image

View File

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