更新 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

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

View File

@@ -17,18 +17,22 @@
@click="selectRegion('Q')"
class="region-btn qq-btn"
:disabled="state.submitting"
:class="{ 'loading': state.submitting }"
>
<div class="btn-icon">Q</div>
<span class="btn-text">QQ区</span>
<div v-if="state.submitting" class="loading-spinner small"></div>
<div v-else class="btn-icon">Q</div>
<span class="btn-text">{{ state.submitting ? '正在连接...' : 'QQ区' }}</span>
</button>
<button
@click="selectRegion('V')"
class="region-btn wx-btn"
:disabled="state.submitting"
:class="{ 'loading': state.submitting }"
>
<div class="btn-icon">V</div>
<span class="btn-text">微信区</span>
<div v-if="state.submitting" class="loading-spinner small"></div>
<div v-else class="btn-icon">V</div>
<span class="btn-text">{{ state.submitting ? '正在连接...' : '微信区' }}</span>
</button>
</div>
@@ -57,11 +61,25 @@
<!-- 二维码区域 -->
<div v-else-if="state.qrInfo && countdown > 0" class="qr-container">
<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 class="countdown-timer">{{ formatTime(countdown) }}</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 class="warning-icon"></div>
@@ -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;