diff --git a/vehicle-data-platform/apps/web/src/v2/auth/AuthGate.test.tsx b/vehicle-data-platform/apps/web/src/v2/auth/AuthGate.test.tsx
index ccdf763b..5de34bcc 100644
--- a/vehicle-data-platform/apps/web/src/v2/auth/AuthGate.test.tsx
+++ b/vehicle-data-platform/apps/web/src/v2/auth/AuthGate.test.tsx
@@ -42,7 +42,7 @@ test('uses a branded Semi UI session state while restoring authentication', () =
test('presents failed credentials as a clear Semi UI alert without losing the login form', async () => {
mocks.session.mockRejectedValue(new Error('未登录'));
- mocks.login.mockRejectedValue(new Error('账号或密码错误'));
+ mocks.login.mockRejectedValue(new Error('账号或密码错误 (traceId: trace-login-test)'));
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
const view = render(受保护工作台
);
@@ -52,6 +52,9 @@ test('presents failed credentials as a clear Semi UI alert without losing the lo
expect(await screen.findByRole('alert')).toHaveTextContent('登录未完成');
expect(screen.getByRole('alert')).toHaveTextContent('账号或密码错误');
+ expect(screen.getByRole('alert')).toHaveTextContent('请确认账号和密码后重试');
+ expect(screen.getByRole('alert')).not.toHaveTextContent('trace-login-test');
+ expect(view.container.querySelector('.v2-auth-error')).toHaveAttribute('data-support-trace', 'trace-login-test');
expect(view.container.querySelector('.v2-auth-error .semi-banner')).toBeInTheDocument();
expect(screen.getByPlaceholderText('请输入用户名')).toBeInTheDocument();
});
diff --git a/vehicle-data-platform/apps/web/src/v2/auth/AuthGate.tsx b/vehicle-data-platform/apps/web/src/v2/auth/AuthGate.tsx
index 41baaad0..5fb5ed9e 100644
--- a/vehicle-data-platform/apps/web/src/v2/auth/AuthGate.tsx
+++ b/vehicle-data-platform/apps/web/src/v2/auth/AuthGate.tsx
@@ -7,6 +7,7 @@ import { PasswordInput } from '../shared/PasswordInput';
import { clearAccessToken, getAccessToken, PLATFORM_UNAUTHORIZED_EVENT, PlatformSession, setAccessToken } from './session';
const { Title, Text } = Typography;
+const TRACE_ID_SUFFIX = /\s*\(traceId:\s*([^)]+)\)\s*$/i;
type AuthContextValue = {
session: PlatformSession;
@@ -55,15 +56,24 @@ function AuthLoadingState() {
;
}
+function authErrorPresentation(message: string) {
+ const match = message.match(TRACE_ID_SUFFIX);
+ return {
+ message: message.replace(TRACE_ID_SUFFIX, '').trim() || '登录失败,请稍后重试',
+ traceId: match?.[1]?.trim() ?? ''
+ };
+}
+
function AuthErrorMessage({ message }: { message: string }) {
- return
+ const presentation = authErrorPresentation(message);
+ return
}
title="登录未完成"
- description={message}
+ description={{presentation.message}请确认账号和密码后重试;连续失败可能会临时锁定账号。}
/>
;
}
diff --git a/vehicle-data-platform/apps/web/src/v2/styles/workspace.css b/vehicle-data-platform/apps/web/src/v2/styles/workspace.css
index 7b099ad7..7ef4de90 100644
--- a/vehicle-data-platform/apps/web/src/v2/styles/workspace.css
+++ b/vehicle-data-platform/apps/web/src/v2/styles/workspace.css
@@ -5853,6 +5853,12 @@
.v2-auth-form-heading {
display: grid;
justify-items: center;
+ margin: -32px -34px 0;
+ border-bottom: 1px solid #e4ebf4;
+ background:
+ radial-gradient(circle at 50% -38%, rgba(18, 104, 243, .15), transparent 62%),
+ linear-gradient(180deg, #fbfdff 0%, #f7faff 100%);
+ padding: 30px 34px 24px;
}
.v2-auth-form-heading > h3.semi-typography {
@@ -5888,10 +5894,18 @@
color: #d92d20;
}
+.v2-auth-error .semi-banner-content-body {
+ display: grid;
+ min-width: 0;
+ flex: 1 1 auto;
+ gap: 3px;
+}
+
.v2-auth-error .semi-banner-title {
color: #9f2d25;
font-size: 11px;
font-weight: 700;
+ white-space: nowrap;
}
.v2-auth-error .semi-banner-description {
@@ -5901,6 +5915,24 @@
line-height: 1.5;
}
+.v2-auth-error .semi-banner-description > span {
+ display: grid;
+ gap: 3px;
+}
+
+.v2-auth-error .semi-banner-description strong {
+ color: #a13d35;
+ font-size: 11px;
+ font-weight: 650;
+ line-height: 1.45;
+}
+
+.v2-auth-error .semi-banner-description small {
+ color: #b26861;
+ font-size: 9px;
+ line-height: 1.5;
+}
+
.v2-auth-form > .v2-auth-error + .semi-button-primary {
margin-top: 16px;
}
@@ -5931,6 +5963,7 @@
.v2-auth-screen:not(.is-session-loading) {
grid-template-columns: minmax(430px, 1.12fr) minmax(420px, 460px);
column-gap: clamp(34px, 5vw, 82px);
+ justify-content: center;
background:
radial-gradient(circle at 18% 14%, rgba(18, 104, 243, .11), transparent 30%),
linear-gradient(135deg, #f7faff 0%, #f2f6fb 56%, #eef3f9 100%);
@@ -6062,8 +6095,9 @@
.v2-auth-screen:not(.is-session-loading) > .v2-auth-card.semi-card {
width: min(460px, 100%);
justify-self: end;
+ overflow: hidden;
border-color: rgba(207, 219, 234, .96);
- border-radius: 24px;
+ border-radius: 22px;
background: rgba(255, 255, 255, .98);
box-shadow: 0 28px 78px rgba(25, 43, 69, .14);
}
@@ -6098,7 +6132,7 @@
}
.v2-auth-form > .v2-auth-fields {
- margin-top: 20px;
+ margin-top: 22px;
}
.v2-auth-fields {
@@ -6113,18 +6147,22 @@
gap: 7px;
margin-top: 14px;
color: #405269;
- font-size: 12px;
+ font-size: 13px;
font-weight: 680;
}
.v2-auth-fields > label .semi-input-wrapper {
- height: 44px;
+ height: 48px;
border-color: #dbe4ef;
border-radius: 10px;
background: #fbfcfe;
box-shadow: none;
}
+.v2-auth-fields > label .semi-input {
+ font-size: 14px;
+}
+
.v2-auth-fields > label .semi-input-wrapper:hover {
border-color: #b8c9dd;
background: #fff;
@@ -6137,16 +6175,20 @@
}
.v2-auth-form > .semi-button-primary {
- height: 46px;
+ height: 48px;
margin-top: 18px;
border-radius: 10px;
- font-size: 13px;
+ font-size: 14px;
font-weight: 750;
box-shadow: 0 10px 22px rgba(18, 104, 243, .2);
}
.v2-auth-trust-note {
- margin-top: 12px;
+ min-height: 48px;
+ margin: 16px -34px -28px;
+ border-top: 1px solid #e8eef5;
+ background: #fbfcfe;
+ padding: 11px 34px;
}
.v2-auth-trust-note > .semi-typography {
@@ -6285,8 +6327,12 @@
@media (max-width: 680px) {
.v2-auth-screen:not(.is-session-loading) {
- display: block;
+ display: grid;
+ width: 100%;
min-height: 100dvh;
+ grid-template-columns: minmax(0, 1fr);
+ place-items: center;
+ overflow: auto;
background:
radial-gradient(circle at 50% 0, rgba(18, 104, 243, .1), transparent 34%),
#f3f6fa;
@@ -6305,6 +6351,11 @@
padding: 24px 18px 20px;
}
+ .v2-auth-form-heading {
+ margin: -24px -18px 0;
+ padding: 24px 18px 20px;
+ }
+
.v2-auth-logo {
width: 132px;
margin-bottom: 14px;
@@ -6371,16 +6422,21 @@
}
.v2-auth-fields > label {
- font-size: 12px;
+ font-size: 13px;
}
.v2-auth-fields > label .semi-input-wrapper {
- height: 46px;
+ height: 48px;
}
.v2-auth-form > .semi-button-primary {
- height: 46px;
- font-size: 13px;
+ height: 48px;
+ font-size: 14px;
+ }
+
+ .v2-auth-trust-note {
+ margin: 14px -18px -20px;
+ padding: 10px 18px;
}
.v2-route-error > .v2-route-error-card.semi-card {