coder-common-thin-frontend/src/components/common/ErrorTip.vue
Leo 65af9b3a10 feat(error): 实现智能网络错误检测和自动跳转机制
- 新增后端服务可用性检测功能,2秒超时检测
- 实现智能状态检测:用户有认证信息但访问404页面时自动检测
- 添加受保护路径识别逻辑(/system/、/management/、/tools/)
- 后端服务不可用时自动清除认证信息并跳转登录页面
- 完善用户界面提示,显示检测状态和自动跳转信息
- 支持手动重试连接和返回登录页面操作
2025-07-09 14:50:30 +08:00

247 lines
6.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="flex-col-center h-full">
<img
v-if="type === '403'"
src="@/assets/svg/error-403.svg"
alt=""
class="w-1/3"
>
<img
v-if="type === '404'"
src="@/assets/svg/error-404.svg"
alt=""
class="w-1/3"
>
<img
v-if="type === '500'"
src="@/assets/svg/error-500.svg"
alt=""
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
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>