fix(web): suppress duplicate toasts when auth session expires
Desktop Client Build / Resolve Build Metadata (push) Successful in 32s
Frontend CI / Frontend (push) Successful in 4m4s
Desktop Client Build / Publish Client Artifacts to NAS (push) Has been cancelled
Desktop Client Build / Build Desktop Client (push) Has been cancelled
Desktop Client Build / Resolve Build Metadata (push) Successful in 32s
Frontend CI / Frontend (push) Successful in 4m4s
Desktop Client Build / Publish Client Artifacts to NAS (push) Has been cancelled
Desktop Client Build / Build Desktop Client (push) Has been cancelled
Mark 401/refresh-failure errors as handled so per-view catch blocks fall through silently while a single global "登录已过期" warning is shown. Centralize ops-web error rendering via showOpsError. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
import { isHandledOpsApiError, OpsApiError } from '@/lib/http'
|
||||
|
||||
export function showOpsError(error: unknown, fallback = '请求失败'): void {
|
||||
if (isHandledOpsApiError(error)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (error instanceof OpsApiError) {
|
||||
message.error(error.detail || error.message)
|
||||
return
|
||||
}
|
||||
|
||||
message.error(fallback)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ export class OpsApiError extends Error {
|
||||
code: number
|
||||
detail?: string
|
||||
requestId?: string
|
||||
handled?: boolean
|
||||
|
||||
constructor(input: {
|
||||
message: string
|
||||
@@ -14,6 +15,7 @@ export class OpsApiError extends Error {
|
||||
status?: number
|
||||
detail?: string
|
||||
requestId?: string
|
||||
handled?: boolean
|
||||
}) {
|
||||
super(input.message)
|
||||
this.name = 'OpsApiError'
|
||||
@@ -21,6 +23,7 @@ export class OpsApiError extends Error {
|
||||
this.status = input.status
|
||||
this.detail = input.detail
|
||||
this.requestId = input.requestId
|
||||
this.handled = input.handled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,11 +36,20 @@ interface ApiEnvelope<T> {
|
||||
}
|
||||
|
||||
let onUnauthorized: (() => void) | null = null
|
||||
let unauthorizedHandled = false
|
||||
|
||||
export function bindUnauthorizedHandler(handler: () => void): void {
|
||||
onUnauthorized = handler
|
||||
}
|
||||
|
||||
export function resetUnauthorizedHandling(): void {
|
||||
unauthorizedHandled = false
|
||||
}
|
||||
|
||||
export function isHandledOpsApiError(error: unknown): boolean {
|
||||
return error instanceof OpsApiError && error.handled === true
|
||||
}
|
||||
|
||||
const client: AxiosInstance = axios.create({
|
||||
baseURL: '/api/ops',
|
||||
timeout: 30000,
|
||||
@@ -63,10 +75,14 @@ client.interceptors.response.use(
|
||||
(error: AxiosError<ApiEnvelope<unknown>>) => {
|
||||
const status = error.response?.status
|
||||
const payload = error.response?.data
|
||||
const handled = status === 401 && !isLoginRequest(error.config?.url, error.config?.method)
|
||||
|
||||
if (status === 401 && !isLoginRequest(error.config?.url, error.config?.method)) {
|
||||
if (handled) {
|
||||
clearStoredSession()
|
||||
onUnauthorized?.()
|
||||
if (!unauthorizedHandled) {
|
||||
unauthorizedHandled = true
|
||||
onUnauthorized?.()
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(
|
||||
@@ -76,6 +92,7 @@ client.interceptors.response.use(
|
||||
status,
|
||||
detail: typeof payload?.detail === 'string' ? payload.detail : undefined,
|
||||
requestId: typeof payload?.request_id === 'string' ? payload.request_id : undefined,
|
||||
handled,
|
||||
}),
|
||||
)
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user