fix: 将会话失效处理集中到路由并避免在登录页刷新循环

This commit is contained in:
mofeng
2026-01-28 21:52:12 +08:00
parent ece0bbdcef
commit 2938af32a9
3 changed files with 32 additions and 21 deletions

View File

@@ -6,7 +6,6 @@ const API_BASE = '/api'
// Toast debounce mechanism - prevent toast spam (5 seconds)
const toastDebounceMap = new Map<string, number>()
const TOAST_DEBOUNCE_TIME = 5000
let sessionExpiredNotified = false
function shouldShowToast(key: string): boolean {
const now = Date.now()
@@ -84,24 +83,10 @@ export async function request<T>(
const message = getErrorMessage(data, `HTTP ${response.status}`)
const normalized = message.toLowerCase()
const isNotAuthenticated = normalized.includes('not authenticated')
if (response.status === 401 && !sessionExpiredNotified) {
const isLoggedInElsewhere = normalized.includes('logged in elsewhere')
const isSessionExpired = normalized.includes('session expired')
if (isLoggedInElsewhere || isSessionExpired) {
sessionExpiredNotified = true
const titleKey = isLoggedInElsewhere ? 'auth.loggedInElsewhere' : 'auth.sessionExpired'
if (toastOnError && shouldShowToast('error_session_expired')) {
toast.error(t(titleKey), {
description: message,
duration: 3000,
})
}
setTimeout(() => {
window.location.reload()
}, 1200)
}
}
if (toastOnError && shouldShowToast(toastKey) && !(response.status === 401 && isNotAuthenticated)) {
const isSessionExpired = normalized.includes('session expired')
const isLoggedInElsewhere = normalized.includes('logged in elsewhere')
const isAuthIssue = response.status === 401 && (isNotAuthenticated || isSessionExpired || isLoggedInElsewhere)
if (toastOnError && shouldShowToast(toastKey) && !isAuthIssue) {
toast.error(t('api.operationFailed'), {
description: message,
duration: 4000,

View File

@@ -1,4 +1,7 @@
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
import { toast } from 'vue-sonner'
import i18n from '@/i18n'
import { ApiError } from '@/api/request'
import { useAuthStore } from '@/stores/auth'
const routes: RouteRecordRaw[] = [
@@ -33,6 +36,12 @@ const router = createRouter({
routes,
})
let sessionExpiredNotified = false
function t(key: string, params?: Record<string, unknown>): string {
return String(i18n.global.t(key, params as any))
}
// Navigation guard
router.beforeEach(async (to, _from, next) => {
const authStore = useAuthStore()
@@ -54,8 +63,21 @@ router.beforeEach(async (to, _from, next) => {
if (!authStore.isAuthenticated) {
try {
await authStore.checkAuth()
} catch {
} catch (e) {
// Not authenticated
if (e instanceof ApiError && e.status === 401 && !sessionExpiredNotified) {
const normalized = e.message.toLowerCase()
const isLoggedInElsewhere = normalized.includes('logged in elsewhere')
const isSessionExpired = normalized.includes('session expired')
if (isLoggedInElsewhere || isSessionExpired) {
sessionExpiredNotified = true
const titleKey = isLoggedInElsewhere ? 'auth.loggedInElsewhere' : 'auth.sessionExpired'
toast.error(t(titleKey), {
description: e.message,
duration: 3000,
})
}
}
}
if (!authStore.isAuthenticated) {

View File

@@ -32,10 +32,14 @@ export const useAuthStore = defineStore('auth', () => {
user.value = result.user || null
isAdmin.value = result.is_admin ?? false
return result
} catch {
} catch (e) {
isAuthenticated.value = false
user.value = null
isAdmin.value = false
error.value = e instanceof Error ? e.message : 'Not authenticated'
if (e instanceof Error) {
throw e
}
throw new Error('Not authenticated')
}
}