更新 Play.vue 组件,增加二维码获取失败提示和重试功能;优化状态显示逻辑,新增机器ID信息展示;更新 .env.production 文件,添加 IMAGE_BASE_URL 配置。
This commit is contained in:
@@ -3,3 +3,5 @@ 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
|
||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user