feat(error): 实现智能网络错误检测和自动跳转机制
- 新增后端服务可用性检测功能,2秒超时检测 - 实现智能状态检测:用户有认证信息但访问404页面时自动检测 - 添加受保护路径识别逻辑(/system/、/management/、/tools/) - 后端服务不可用时自动清除认证信息并跳转登录页面 - 完善用户界面提示,显示检测状态和自动跳转信息 - 支持手动重试连接和返回登录页面操作
This commit is contained in:
parent
b6dfde844b
commit
65af9b3a10
@ -18,19 +18,229 @@
|
|||||||
alt=""
|
alt=""
|
||||||
class="w-1/3"
|
class="w-1/3"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
<!-- 网络状态检测提示 -->
|
||||||
|
<div v-if="!isOnline || !isBackendAvailable" class="mt-4 text-center">
|
||||||
|
<n-alert type="warning" :show-icon="false" class="mb-4">
|
||||||
|
<template v-if="!isOnline">
|
||||||
|
网络连接失败,请检查网络状态
|
||||||
|
</template>
|
||||||
|
<template v-else-if="!isBackendAvailable">
|
||||||
|
服务暂时不可用,请稍后重试
|
||||||
|
</template>
|
||||||
|
</n-alert>
|
||||||
|
<p class="text-sm text-gray-500 mb-4">
|
||||||
|
<template v-if="!isOnline">
|
||||||
|
正在检测网络连接...
|
||||||
|
</template>
|
||||||
|
<template v-else-if="!isBackendAvailable">
|
||||||
|
正在检测服务状态...
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
<div class="flex justify-center space-x-1 mb-4">
|
||||||
|
<div class="loading-dot" style="animation-delay: 0s;" />
|
||||||
|
<div class="loading-dot" style="animation-delay: 0.1s;" />
|
||||||
|
<div class="loading-dot" style="animation-delay: 0.2s;" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 服务检测提示 -->
|
||||||
|
<div v-if="(!isBackendAvailable || shouldAutoRedirect) && type === '404'" class="mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
||||||
|
<p class="text-sm text-blue-600 dark:text-blue-400">
|
||||||
|
<template v-if="!isBackendAvailable">
|
||||||
|
检测到后端服务问题,正在重定向到登录页面...
|
||||||
|
</template>
|
||||||
|
<template v-else-if="shouldAutoRedirect">
|
||||||
|
检测到可能的服务问题,即将自动跳转到登录页面...
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="flex flex-col items-center space-y-3">
|
||||||
<n-button
|
<n-button
|
||||||
|
v-if="!isOnline || !isBackendAvailable"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="router.push('/')"
|
:loading="isRetrying"
|
||||||
|
@click="handleRetry"
|
||||||
|
>
|
||||||
|
{{ isRetrying ? '重试中...' : '重试连接' }}
|
||||||
|
</n-button>
|
||||||
|
|
||||||
|
<n-button
|
||||||
|
v-if="isOnline && isBackendAvailable"
|
||||||
|
type="primary"
|
||||||
|
@click="handleBackHome"
|
||||||
>
|
>
|
||||||
{{ $t('app.backHome') }}
|
{{ $t('app.backHome') }}
|
||||||
</n-button>
|
</n-button>
|
||||||
|
|
||||||
|
<n-button
|
||||||
|
v-if="!isOnline || !isBackendAvailable"
|
||||||
|
text
|
||||||
|
@click="handleBackToLogin"
|
||||||
|
>
|
||||||
|
返回登录页面
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { coiMsgError, coiMsgSuccess } from '@/utils/coi'
|
||||||
|
import { local } from '@/utils'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
/** 异常类型 403 404 500 */
|
/** 异常类型 403 404 500 */
|
||||||
type: '403' | '404' | '500'
|
type: '403' | '404' | '500'
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const isOnline = ref(navigator.onLine)
|
||||||
|
const isRetrying = ref(false)
|
||||||
|
const isBackendAvailable = ref(true)
|
||||||
|
const shouldAutoRedirect = ref(false)
|
||||||
|
|
||||||
|
// 监听网络状态变化
|
||||||
|
function updateOnlineStatus() {
|
||||||
|
isOnline.value = navigator.onLine
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测后端服务可用性
|
||||||
|
async function checkBackendAvailability() {
|
||||||
|
try {
|
||||||
|
const controller = new AbortController()
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 2000)
|
||||||
|
|
||||||
|
await fetch('/api/login/captchaPng', {
|
||||||
|
method: 'GET',
|
||||||
|
signal: controller.signal,
|
||||||
|
})
|
||||||
|
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
isBackendAvailable.value = true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
isBackendAvailable.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 智能状态检测
|
||||||
|
async function checkServiceStatus() {
|
||||||
|
const currentRoute = router.currentRoute.value
|
||||||
|
|
||||||
|
// 如果是404页面,很可能是后端服务问题
|
||||||
|
if (currentRoute.name === '404' || currentRoute.name === 'notFound') {
|
||||||
|
// 检查是否有认证信息(说明用户之前是登录状态)
|
||||||
|
const hasAuthToken = Boolean(local.get('accessToken'))
|
||||||
|
|
||||||
|
if (hasAuthToken) {
|
||||||
|
// 先尝试检查后端服务状态
|
||||||
|
try {
|
||||||
|
await checkBackendAvailability()
|
||||||
|
|
||||||
|
// 如果后端服务不可用,立即跳转到登录页面
|
||||||
|
if (!isBackendAvailable.value) {
|
||||||
|
handleBackToLogin()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// 检测异常,忽略错误继续执行后续逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简化方案:如果用户在一个受保护的页面但显示404,直接跳转
|
||||||
|
const isProtectedPath = currentRoute.fullPath.includes('/system/')
|
||||||
|
|| currentRoute.fullPath.includes('/management/')
|
||||||
|
|| currentRoute.fullPath.includes('/tools/')
|
||||||
|
|
||||||
|
if (isProtectedPath) {
|
||||||
|
shouldAutoRedirect.value = true
|
||||||
|
setTimeout(() => {
|
||||||
|
handleBackToLogin()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重试连接
|
||||||
|
async function handleRetry() {
|
||||||
|
isRetrying.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查网络状态
|
||||||
|
updateOnlineStatus()
|
||||||
|
|
||||||
|
// 检查后端服务可用性
|
||||||
|
await checkBackendAvailability()
|
||||||
|
|
||||||
|
if (isOnline.value && isBackendAvailable.value) {
|
||||||
|
coiMsgSuccess('服务连接已恢复')
|
||||||
|
// 尝试重新加载当前页面
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
else if (!isOnline.value) {
|
||||||
|
coiMsgError('网络连接失败,请检查网络设置')
|
||||||
|
}
|
||||||
|
else if (!isBackendAvailable.value) {
|
||||||
|
coiMsgError('后端服务暂时不可用,请稍后重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
coiMsgError('重试失败,请稍后再试')
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
isRetrying.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回首页
|
||||||
|
function handleBackHome() {
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回登录页面
|
||||||
|
function handleBackToLogin() {
|
||||||
|
// 清除认证信息
|
||||||
|
local.remove('accessToken')
|
||||||
|
local.remove('refreshToken')
|
||||||
|
|
||||||
|
// 跳转到登录页面
|
||||||
|
router.push('/login')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 网络状态监听
|
||||||
|
onMounted(async () => {
|
||||||
|
window.addEventListener('online', updateOnlineStatus)
|
||||||
|
window.addEventListener('offline', updateOnlineStatus)
|
||||||
|
|
||||||
|
// 初始检查服务状态
|
||||||
|
await checkServiceStatus()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('online', updateOnlineStatus)
|
||||||
|
window.removeEventListener('offline', updateOnlineStatus)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Loading点动画 */
|
||||||
|
.loading-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: var(--primary-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: bounce 1.4s ease-in-out infinite both;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
0%, 80%, 100% {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user