优化二维码获取逻辑,新增URL验证和重试机制,提升用户体验;更新Play.vue界面,增加重试信息显示和区域选择处理逻辑。
This commit is contained in:
@@ -38,9 +38,13 @@ http.interceptors.request.use(
|
|||||||
'/api/link/status',
|
'/api/link/status',
|
||||||
'/api/link/select-region',
|
'/api/link/select-region',
|
||||||
'/api/link/poll-login',
|
'/api/link/poll-login',
|
||||||
'/api/link/progress'
|
'/api/link/progress',
|
||||||
|
'/api/link/refresh',
|
||||||
|
'/api/link/qr.png'
|
||||||
]
|
]
|
||||||
const isPublicAPI = publicAPIs.some(api => config.url?.includes(api))
|
// 游戏界面接口使用动态路径,需要特殊处理
|
||||||
|
const isGameInterfaceAPI = /\/api\/link\/[^\/]+\/game-interface/.test(config.url || '')
|
||||||
|
const isPublicAPI = publicAPIs.some(api => config.url?.includes(api)) || isGameInterfaceAPI
|
||||||
|
|
||||||
if (!isPublicAPI) {
|
if (!isPublicAPI) {
|
||||||
const token = getAccessToken()
|
const token = getAccessToken()
|
||||||
@@ -81,11 +85,20 @@ http.interceptors.response.use(
|
|||||||
'/api/link/status',
|
'/api/link/status',
|
||||||
'/api/link/select-region',
|
'/api/link/select-region',
|
||||||
'/api/link/poll-login',
|
'/api/link/poll-login',
|
||||||
'/api/link/progress'
|
'/api/link/progress',
|
||||||
|
'/api/link/refresh',
|
||||||
|
'/api/link/qr.png'
|
||||||
]
|
]
|
||||||
const isPublicAPI = publicAPIs.some(api => url?.includes(api))
|
// 游戏界面接口使用动态路径,需要特殊处理
|
||||||
|
const isGameInterfaceAPI = /\/api\/link\/[^\/]+\/game-interface/.test(url || '')
|
||||||
|
const isPublicAPI = publicAPIs.some(api => url?.includes(api)) || isGameInterfaceAPI
|
||||||
|
|
||||||
if (status === 401 && !isAuthPath && !isPublicAPI) {
|
if (status === 401 && !isAuthPath && !isPublicAPI) {
|
||||||
|
// 阻止浏览器显示基本认证弹窗
|
||||||
|
if (error.response && error.response.headers) {
|
||||||
|
delete error.response.headers['www-authenticate']
|
||||||
|
}
|
||||||
|
|
||||||
if (!isRefreshing) {
|
if (!isRefreshing) {
|
||||||
isRefreshing = true
|
isRefreshing = true
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
<div class="loading-spinner"></div>
|
<div class="loading-spinner"></div>
|
||||||
<p class="waiting-text">正在准备二维码...</p>
|
<p class="waiting-text">正在准备二维码...</p>
|
||||||
<p class="waiting-desc">预计等待 {{ state.qrDelaySeconds }} 秒</p>
|
<p class="waiting-desc">预计等待 {{ state.qrDelaySeconds }} 秒</p>
|
||||||
|
<p v-if="state.qrRetryCount > 0" class="retry-info">重试中... ({{ state.qrRetryCount }}/{{ state.maxQrRetries }})</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 二维码区域 -->
|
<!-- 二维码区域 -->
|
||||||
@@ -172,7 +173,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { reactive, ref, onMounted, onUnmounted } from 'vue'
|
import { reactive, ref, onMounted, onUnmounted } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import {
|
import {
|
||||||
getLinkStatus,
|
getLinkStatus,
|
||||||
@@ -187,6 +188,7 @@ export default {
|
|||||||
name: 'Play',
|
name: 'Play',
|
||||||
setup() {
|
setup() {
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
@@ -202,7 +204,10 @@ export default {
|
|||||||
totalPoints: 1000,
|
totalPoints: 1000,
|
||||||
error: null,
|
error: null,
|
||||||
qrDelaySeconds: 0,
|
qrDelaySeconds: 0,
|
||||||
isWaitingQr: false
|
isWaitingQr: false,
|
||||||
|
qrRetryCount: 0,
|
||||||
|
maxQrRetries: 3,
|
||||||
|
qrRetryDelay: 2000
|
||||||
})
|
})
|
||||||
|
|
||||||
// 计时器
|
// 计时器
|
||||||
@@ -431,12 +436,38 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 延迟获取二维码
|
// 验证二维码URL是否可访问
|
||||||
const fetchQrCodeAfterDelay = async (qrCodeUrl, qrCreatedAt, qrExpireAt) => {
|
const validateQrCodeUrl = async (url) => {
|
||||||
try {
|
try {
|
||||||
// 这里可以添加额外的验证或处理逻辑
|
const controller = new AbortController()
|
||||||
// 比如检查 URL 是否可访问
|
const timeoutId = setTimeout(() => controller.abort(), 5000)
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'HEAD',
|
||||||
|
signal: controller.signal
|
||||||
|
})
|
||||||
|
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
return response.ok
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('二维码URL验证失败:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 延迟获取二维码(带重试机制)
|
||||||
|
const fetchQrCodeAfterDelay = async (qrCodeUrl, qrCreatedAt, qrExpireAt, retryCount = 0) => {
|
||||||
|
try {
|
||||||
|
console.log(`尝试获取二维码 (第${retryCount + 1}次):`, qrCodeUrl)
|
||||||
|
|
||||||
|
// 验证二维码URL是否可访问
|
||||||
|
const isUrlValid = await validateQrCodeUrl(qrCodeUrl)
|
||||||
|
|
||||||
|
if (!isUrlValid) {
|
||||||
|
throw new Error('二维码URL无法访问')
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL验证通过,设置二维码信息
|
||||||
state.qrInfo = {
|
state.qrInfo = {
|
||||||
url: qrCodeUrl,
|
url: qrCodeUrl,
|
||||||
createdAt: qrCreatedAt,
|
createdAt: qrCreatedAt,
|
||||||
@@ -447,25 +478,60 @@ 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
|
||||||
|
|
||||||
ElMessage.success('二维码已准备就绪,请扫码登录')
|
ElMessage.success('二维码已准备就绪,请扫码登录')
|
||||||
|
console.log('二维码获取成功')
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取二维码失败:', error)
|
console.error(`二维码获取失败 (第${retryCount + 1}次):`, error)
|
||||||
ElMessage.error('二维码获取失败,请重试')
|
|
||||||
|
// 如果还有重试次数,则进行重试
|
||||||
|
if (retryCount < state.maxQrRetries) {
|
||||||
|
state.qrRetryCount = retryCount + 1
|
||||||
|
const delay = state.qrRetryDelay * Math.pow(2, retryCount) // 指数退避
|
||||||
|
|
||||||
|
console.log(`${delay}ms后进行第${retryCount + 2}次重试`)
|
||||||
|
ElMessage.warning(`二维码获取失败,${delay/1000}秒后重试...`)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
fetchQrCodeAfterDelay(qrCodeUrl, qrCreatedAt, qrExpireAt, retryCount + 1)
|
||||||
|
}, delay)
|
||||||
|
} else {
|
||||||
|
// 重试次数用完,重新请求新的二维码
|
||||||
|
console.error('二维码获取重试次数用完,重新请求区域选择')
|
||||||
|
ElMessage.error('二维码获取失败,正在重新请求...')
|
||||||
|
state.qrRetryCount = 0
|
||||||
|
|
||||||
|
// 重新调用区域选择API
|
||||||
|
if (state.region) {
|
||||||
|
retrySelectRegion(state.region)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选择区域
|
// 重试选择区域(内部使用,不显示loading状态)
|
||||||
const selectRegion = async (region) => {
|
const retrySelectRegion = async (region) => {
|
||||||
if (state.submitting) return
|
|
||||||
|
|
||||||
state.submitting = true
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log('重试选择区域:', region)
|
||||||
const response = await selectRegionAPI({ code: state.code, region })
|
const response = await selectRegionAPI({ code: state.code, region })
|
||||||
const data = response.data
|
const data = response.data
|
||||||
|
|
||||||
console.log('selectRegion 响应数据:', data)
|
console.log('retrySelectRegion 响应数据:', data)
|
||||||
|
|
||||||
|
// 处理响应数据,使用重试逻辑
|
||||||
|
await processSelectRegionResponse(data)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('重试选择区域失败:', error)
|
||||||
|
ElMessage.error('重新请求二维码失败,请手动刷新页面')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理选择区域响应的通用逻辑
|
||||||
|
const processSelectRegionResponse = async (data) => {
|
||||||
// 如果返回了延迟时间和二维码URL,需要延迟处理
|
// 如果返回了延迟时间和二维码URL,需要延迟处理
|
||||||
if (data.qrDelaySeconds && data.qrDelaySeconds > 0 && data.qrCodeUrl) {
|
if (data.qrDelaySeconds && data.qrDelaySeconds > 0 && data.qrCodeUrl) {
|
||||||
console.log('进入延迟分支')
|
console.log('进入延迟分支')
|
||||||
@@ -480,7 +546,7 @@ export default {
|
|||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
state.isWaitingQr = false
|
state.isWaitingQr = false
|
||||||
|
|
||||||
// 延迟后获取二维码
|
// 延迟后获取二维码(带重试机制)
|
||||||
await fetchQrCodeAfterDelay(data.qrCodeUrl, data.qrCreatedAt, data.qrExpireAt)
|
await fetchQrCodeAfterDelay(data.qrCodeUrl, data.qrCreatedAt, data.qrExpireAt)
|
||||||
|
|
||||||
if (state.status === 'USING') {
|
if (state.status === 'USING') {
|
||||||
@@ -495,11 +561,39 @@ export default {
|
|||||||
state.isWaitingQr = false
|
state.isWaitingQr = false
|
||||||
console.log('设置状态:', { status: state.status, isWaitingQr: state.isWaitingQr, qrInfo: !!state.qrInfo })
|
console.log('设置状态:', { status: state.status, isWaitingQr: state.isWaitingQr, qrInfo: !!state.qrInfo })
|
||||||
|
|
||||||
|
// 对于立即处理的二维码,也要进行验证
|
||||||
|
if (state.qrInfo && state.qrInfo.url) {
|
||||||
|
const isUrlValid = await validateQrCodeUrl(state.qrInfo.url)
|
||||||
|
if (!isUrlValid) {
|
||||||
|
console.warn('立即获取的二维码URL无法访问,尝试重试')
|
||||||
|
// 清除当前二维码信息,触发重试
|
||||||
|
state.qrInfo = null
|
||||||
|
await fetchQrCodeAfterDelay(data.qrCodeUrl, data.qrCreatedAt, data.qrExpireAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (state.status === 'USING') {
|
if (state.status === 'USING') {
|
||||||
startCountdown()
|
startCountdown()
|
||||||
startLoginPolling()
|
startLoginPolling()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择区域
|
||||||
|
const selectRegion = async (region) => {
|
||||||
|
if (state.submitting) return
|
||||||
|
|
||||||
|
state.submitting = true
|
||||||
|
state.qrRetryCount = 0 // 重置重试计数
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await selectRegionAPI({ code: state.code, region })
|
||||||
|
const data = response.data
|
||||||
|
|
||||||
|
console.log('selectRegion 响应数据:', data)
|
||||||
|
|
||||||
|
// 使用通用处理逻辑
|
||||||
|
await processSelectRegionResponse(data)
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error)
|
handleError(error)
|
||||||
@@ -628,8 +722,18 @@ export default {
|
|||||||
// 获取HTTP状态码
|
// 获取HTTP状态码
|
||||||
const status = error?.response?.status
|
const status = error?.response?.status
|
||||||
|
|
||||||
|
// 如果是认证相关错误,跳转到登录页面
|
||||||
|
if (status === 401) {
|
||||||
|
console.log('检测到401错误,跳转到登录页面')
|
||||||
|
router.replace({
|
||||||
|
name: 'Login',
|
||||||
|
query: { redirect: route.fullPath }
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 根据HTTP状态码设置错误状态
|
// 根据HTTP状态码设置错误状态
|
||||||
if (status === 400 || status === 401 || status === 403) {
|
if (status === 400 || status === 403) {
|
||||||
state.error = 'INVALID_CODE'
|
state.error = 'INVALID_CODE'
|
||||||
} else if (status === 410) {
|
} else if (status === 410) {
|
||||||
state.error = 'EXPIRED'
|
state.error = 'EXPIRED'
|
||||||
@@ -956,6 +1060,14 @@ export default {
|
|||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.retry-info {
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 8px 0 0 0;
|
||||||
|
opacity: 0.9;
|
||||||
|
color: #ffd700;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.qr-wrapper {
|
.qr-wrapper {
|
||||||
background: white;
|
background: white;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|||||||
Reference in New Issue
Block a user