From a4a638208e72238053af94e67c5f3fa69baf3e3d Mon Sep 17 00:00:00 2001 From: hectorzhao Date: Wed, 8 Jul 2026 18:03:30 +0800 Subject: [PATCH] fix: correct login entry routing and errors --- src/api/adminApi.ts | 21 +++++++++++++++++++-- src/apps/LoginPage.tsx | 15 ++++++++++++++- src/main.tsx | 11 +++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/api/adminApi.ts b/src/api/adminApi.ts index 04099b3..d7c319f 100644 --- a/src/api/adminApi.ts +++ b/src/api/adminApi.ts @@ -6,6 +6,23 @@ type RequestOptions = RequestInit & { export const DEFAULT_CLIENT_TENANT_ID = 'tenant-a'; +async function readErrorMessage(response: Response) { + const fallback = `请求失败(${response.status})`; + const text = await response.text(); + if (!text) return fallback; + + try { + const parsed = JSON.parse(text) as { message?: string | string[]; error?: string }; + if (Array.isArray(parsed.message)) return parsed.message.join(';'); + if (parsed.message) return parsed.message; + if (parsed.error) return parsed.error; + } catch { + return text; + } + + return text; +} + async function request(path: string, options: RequestOptions = {}): Promise { const headers = new Headers(options.headers); headers.set('Content-Type', 'application/json'); @@ -19,7 +36,7 @@ async function request(path: string, options: RequestOptions = {}): Promise; } @@ -36,7 +53,7 @@ async function requestBlob(path: string, options: RequestOptions = {}): Promise< } const response = await fetch(`/api${path}`, { ...options, headers }); if (!response.ok) { - throw new Error(await response.text()); + throw new Error(await readErrorMessage(response)); } return response.blob(); } diff --git a/src/apps/LoginPage.tsx b/src/apps/LoginPage.tsx index 596da5c..283f37a 100644 --- a/src/apps/LoginPage.tsx +++ b/src/apps/LoginPage.tsx @@ -8,6 +8,19 @@ type LoginPageProps = { portal: Portal; }; +function loginErrorMessage(err: unknown) { + const message = err instanceof Error ? err.message : ''; + if (message.includes('Invalid login or password')) return '用户名或密码错误'; + if (message.includes('login and password are required')) return '请输入用户名和密码'; + if (message.includes('Captcha expired')) return '验证码已过期,请刷新后重试'; + if (message.includes('Captcha is incorrect')) return '验证码错误,请重新输入'; + if (message.includes('User is locked')) return '账号已锁定,请 24 小时后再试'; + if (message.includes('User is disabled or deleted')) return '账号已停用或已删除'; + if (message.includes('Only platform admins can login to admin portal')) return '该账号不是运营端管理员'; + if (message.includes('Only enterprise admins linked to a tenant can login to client portal')) return '该账号不是已绑定企业的客户端管理员'; + return message || '登录失败,请检查账号信息后重试'; +} + export function LoginPage({ portal }: LoginPageProps) { const navigate = useNavigate(); const [login, setLogin] = useState(''); @@ -42,7 +55,7 @@ export function LoginPage({ portal }: LoginPageProps) { writeSession(session); navigate(isAdmin ? '/admin' : '/client', { replace: true }); } catch (err) { - setError(err instanceof Error ? err.message : '登录失败'); + setError(loginErrorMessage(err)); await refreshCaptcha(); } finally { setLoading(false); diff --git a/src/main.tsx b/src/main.tsx index a652551..d5520a6 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -6,6 +6,17 @@ import '@/styles/tokens.css'; import '@/styles/global.css'; import '@/styles/components.css'; +function normalizeInitialHashRoute() { + if (window.location.hash) return; + + const path = window.location.pathname; + if (path === '/admin' || path.startsWith('/admin/') || path === '/client' || path.startsWith('/client/')) { + window.history.replaceState(null, '', `/#${path}${window.location.search}`); + } +} + +normalizeInitialHashRoute(); + ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(