feat(error): 实现智能网络错误检测和自动跳转机制

- 新增后端服务可用性检测功能,2秒超时检测
- 实现智能状态检测:用户有认证信息但访问404页面时自动检测
- 添加受保护路径识别逻辑(/system/、/management/、/tools/)
- 后端服务不可用时自动清除认证信息并跳转登录页面
- 完善用户界面提示,显示检测状态和自动跳转信息
- 支持手动重试连接和返回登录页面操作
This commit is contained in:
Leo 2025-07-09 14:50:30 +08:00
parent b6dfde844b
commit 65af9b3a10

View File

@ -18,19 +18,229 @@
alt=""
class="w-1/3"
>
<n-button
type="primary"
@click="router.push('/')"
>
{{ $t('app.backHome') }}
</n-button>
<!-- 网络状态检测提示 -->
<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
v-if="!isOnline || !isBackendAvailable"
type="primary"
:loading="isRetrying"
@click="handleRetry"
>
{{ isRetrying ? '重试中...' : '重试连接' }}
</n-button>
<n-button
v-if="isOnline && isBackendAvailable"
type="primary"
@click="handleBackHome"
>
{{ $t('app.backHome') }}
</n-button>
<n-button
v-if="!isOnline || !isBackendAvailable"
text
@click="handleBackToLogin"
>
返回登录页面
</n-button>
</div>
</div>
</template>
<script setup lang="ts">
import { coiMsgError, coiMsgSuccess } from '@/utils/coi'
import { local } from '@/utils'
defineProps<{
/** 异常类型 403 404 500 */
type: '403' | '404' | '500'
}>()
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>
<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>