From 3995358095ab2725700ebef533a6ccfe724ea472 Mon Sep 17 00:00:00 2001 From: Nixevol Date: Wed, 20 May 2026 11:30:37 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E6=8F=90=E7=A4=BA=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/project_context.md | 7 +++++++ frontend/src/AppShell.vue | 1 - frontend/src/api/client.ts | 33 +++++++++++++++++++++++++++++---- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/docs/project_context.md b/docs/project_context.md index 5a89461..be51b1f 100644 --- a/docs/project_context.md +++ b/docs/project_context.md @@ -1,4 +1,11 @@ # 项目上下文记录 +## 2026-05-20:修复登录失败误提示会话过期 + +- `frontend/src/api/client.ts` 不再把 `/api/login` 的 401 响应当作全局会话过期处理,登录失败会按后端真实错误显示“账号或密码错误”。 +- 已登录业务接口遇到 401 时仍会清理本地 token 并切回登录页,但 `AppShell` 不再额外弹出全局“登录已过期”提示,避免组件自身错误提示和全局提示同时出现。 +- 默认登录密码仍由 `app/auth.py` 定义为 `Capacity`,大小写敏感;如本地 `auth.ini` 未修改,输入小写 `capacity` 会按正常登录失败处理。 +- 已执行 `npm run build`,构建通过;前端构建仅保留 Vite 大 chunk 提示,生成产物随后清理。 + ## 2026-05-20:增加按 ZIP 数据日期校验的使用期限限制 - 新增 `app/services/license.py` 和 `/api/license/status`、`/api/license/activate`:本地 `license.dat` 用 XOR+HMAC 方式加密保存到期日期,缺失时自动初始化为 `2026-06-20`,文件已加入 `.gitignore`。 diff --git a/frontend/src/AppShell.vue b/frontend/src/AppShell.vue index 8c957c1..032322e 100644 --- a/frontend/src/AppShell.vue +++ b/frontend/src/AppShell.vue @@ -194,7 +194,6 @@ watch( setUnauthorizedHandler(() => { token.value = ''; - message.warning('登录已过期'); }); async function handleLogin(payload: { username: string; password: string }) { diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 28f42f9..1a80a6d 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -50,16 +50,24 @@ export async function apiPost(url: string, body?: unknown): Promise { export async function request(url: string, init: RequestInit = {}): Promise { const headers = new Headers(init.headers); - const token = getToken(); + const token = isLoginRequest(url) ? '' : getToken(); if (token) { headers.set('Authorization', `Bearer ${token}`); } const response = await fetch(apiUrl(url), { ...init, headers }); if (response.status === 401) { + if (isLoginRequest(url)) { + const error = await readError(response); + throw new ApiRequestError(error.message, response.status, error.detail); + } + clearToken(); onUnauthorized?.(); - throw new Error('登录已过期,请重新登录'); + throw new ApiRequestError('登录已过期,请重新登录', response.status, { + code: 'UNAUTHORIZED', + message: '登录已过期,请重新登录' + }); } if (!response.ok) { @@ -94,7 +102,12 @@ export function upload( if (xhr.status === 401) { clearToken(); onUnauthorized?.(); - reject(new Error('登录已过期,请重新登录')); + reject( + new ApiRequestError('登录已过期,请重新登录', xhr.status, { + code: 'UNAUTHORIZED', + message: '登录已过期,请重新登录' + }) + ); return; } @@ -143,7 +156,10 @@ export async function downloadGet(url: string, fallbackFilename: string): Promis if (response.status === 401) { clearToken(); onUnauthorized?.(); - throw new Error('登录已过期,请重新登录'); + throw new ApiRequestError('登录已过期,请重新登录', response.status, { + code: 'UNAUTHORIZED', + message: '登录已过期,请重新登录' + }); } if (!response.ok) { @@ -215,3 +231,12 @@ function apiUrl(url: string): string { } return `${API_BASE}${url.startsWith('/') ? url : `/${url}`}`; } + +function isLoginRequest(url: string): boolean { + try { + const parsed = new URL(url, window.location.origin); + return parsed.pathname === '/api/login'; + } catch { + return url.replace(/^https?:\/\/[^/]+/i, '').split('?')[0] === '/api/login'; + } +}