feat(web): polish account login workspace

This commit is contained in:
lingniu
2026-07-20 05:57:05 +08:00
parent 270bb163eb
commit 4de487e057
3 changed files with 84 additions and 15 deletions

View File

@@ -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 () => { test('presents failed credentials as a clear Semi UI alert without losing the login form', async () => {
mocks.session.mockRejectedValue(new Error('未登录')); 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 client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
const view = render(<QueryClientProvider client={client}><AuthGate><div></div></AuthGate></QueryClientProvider>); const view = render(<QueryClientProvider client={client}><AuthGate><div></div></AuthGate></QueryClientProvider>);
@@ -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(await screen.findByRole('alert')).toHaveTextContent('登录未完成');
expect(screen.getByRole('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(view.container.querySelector('.v2-auth-error .semi-banner')).toBeInTheDocument();
expect(screen.getByPlaceholderText('请输入用户名')).toBeInTheDocument(); expect(screen.getByPlaceholderText('请输入用户名')).toBeInTheDocument();
}); });

View File

@@ -7,6 +7,7 @@ import { PasswordInput } from '../shared/PasswordInput';
import { clearAccessToken, getAccessToken, PLATFORM_UNAUTHORIZED_EVENT, PlatformSession, setAccessToken } from './session'; import { clearAccessToken, getAccessToken, PLATFORM_UNAUTHORIZED_EVENT, PlatformSession, setAccessToken } from './session';
const { Title, Text } = Typography; const { Title, Text } = Typography;
const TRACE_ID_SUFFIX = /\s*\(traceId:\s*([^)]+)\)\s*$/i;
type AuthContextValue = { type AuthContextValue = {
session: PlatformSession; session: PlatformSession;
@@ -55,15 +56,24 @@ function AuthLoadingState() {
</main>; </main>;
} }
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 }) { function AuthErrorMessage({ message }: { message: string }) {
return <div className="v2-auth-error"> const presentation = authErrorPresentation(message);
return <div className="v2-auth-error" data-support-trace={presentation.traceId || undefined}>
<Banner <Banner
type="danger" type="danger"
bordered bordered
closeIcon={null} closeIcon={null}
icon={<IconAlertTriangle />} icon={<IconAlertTriangle />}
title="登录未完成" title="登录未完成"
description={message} description={<span><strong>{presentation.message}</strong><small></small></span>}
/> />
</div>; </div>;
} }

View File

@@ -5853,6 +5853,12 @@
.v2-auth-form-heading { .v2-auth-form-heading {
display: grid; display: grid;
justify-items: center; 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 { .v2-auth-form-heading > h3.semi-typography {
@@ -5888,10 +5894,18 @@
color: #d92d20; 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 { .v2-auth-error .semi-banner-title {
color: #9f2d25; color: #9f2d25;
font-size: 11px; font-size: 11px;
font-weight: 700; font-weight: 700;
white-space: nowrap;
} }
.v2-auth-error .semi-banner-description { .v2-auth-error .semi-banner-description {
@@ -5901,6 +5915,24 @@
line-height: 1.5; 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 { .v2-auth-form > .v2-auth-error + .semi-button-primary {
margin-top: 16px; margin-top: 16px;
} }
@@ -5931,6 +5963,7 @@
.v2-auth-screen:not(.is-session-loading) { .v2-auth-screen:not(.is-session-loading) {
grid-template-columns: minmax(430px, 1.12fr) minmax(420px, 460px); grid-template-columns: minmax(430px, 1.12fr) minmax(420px, 460px);
column-gap: clamp(34px, 5vw, 82px); column-gap: clamp(34px, 5vw, 82px);
justify-content: center;
background: background:
radial-gradient(circle at 18% 14%, rgba(18, 104, 243, .11), transparent 30%), radial-gradient(circle at 18% 14%, rgba(18, 104, 243, .11), transparent 30%),
linear-gradient(135deg, #f7faff 0%, #f2f6fb 56%, #eef3f9 100%); 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 { .v2-auth-screen:not(.is-session-loading) > .v2-auth-card.semi-card {
width: min(460px, 100%); width: min(460px, 100%);
justify-self: end; justify-self: end;
overflow: hidden;
border-color: rgba(207, 219, 234, .96); border-color: rgba(207, 219, 234, .96);
border-radius: 24px; border-radius: 22px;
background: rgba(255, 255, 255, .98); background: rgba(255, 255, 255, .98);
box-shadow: 0 28px 78px rgba(25, 43, 69, .14); box-shadow: 0 28px 78px rgba(25, 43, 69, .14);
} }
@@ -6098,7 +6132,7 @@
} }
.v2-auth-form > .v2-auth-fields { .v2-auth-form > .v2-auth-fields {
margin-top: 20px; margin-top: 22px;
} }
.v2-auth-fields { .v2-auth-fields {
@@ -6113,18 +6147,22 @@
gap: 7px; gap: 7px;
margin-top: 14px; margin-top: 14px;
color: #405269; color: #405269;
font-size: 12px; font-size: 13px;
font-weight: 680; font-weight: 680;
} }
.v2-auth-fields > label .semi-input-wrapper { .v2-auth-fields > label .semi-input-wrapper {
height: 44px; height: 48px;
border-color: #dbe4ef; border-color: #dbe4ef;
border-radius: 10px; border-radius: 10px;
background: #fbfcfe; background: #fbfcfe;
box-shadow: none; box-shadow: none;
} }
.v2-auth-fields > label .semi-input {
font-size: 14px;
}
.v2-auth-fields > label .semi-input-wrapper:hover { .v2-auth-fields > label .semi-input-wrapper:hover {
border-color: #b8c9dd; border-color: #b8c9dd;
background: #fff; background: #fff;
@@ -6137,16 +6175,20 @@
} }
.v2-auth-form > .semi-button-primary { .v2-auth-form > .semi-button-primary {
height: 46px; height: 48px;
margin-top: 18px; margin-top: 18px;
border-radius: 10px; border-radius: 10px;
font-size: 13px; font-size: 14px;
font-weight: 750; font-weight: 750;
box-shadow: 0 10px 22px rgba(18, 104, 243, .2); box-shadow: 0 10px 22px rgba(18, 104, 243, .2);
} }
.v2-auth-trust-note { .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 { .v2-auth-trust-note > .semi-typography {
@@ -6285,8 +6327,12 @@
@media (max-width: 680px) { @media (max-width: 680px) {
.v2-auth-screen:not(.is-session-loading) { .v2-auth-screen:not(.is-session-loading) {
display: block; display: grid;
width: 100%;
min-height: 100dvh; min-height: 100dvh;
grid-template-columns: minmax(0, 1fr);
place-items: center;
overflow: auto;
background: background:
radial-gradient(circle at 50% 0, rgba(18, 104, 243, .1), transparent 34%), radial-gradient(circle at 50% 0, rgba(18, 104, 243, .1), transparent 34%),
#f3f6fa; #f3f6fa;
@@ -6305,6 +6351,11 @@
padding: 24px 18px 20px; padding: 24px 18px 20px;
} }
.v2-auth-form-heading {
margin: -24px -18px 0;
padding: 24px 18px 20px;
}
.v2-auth-logo { .v2-auth-logo {
width: 132px; width: 132px;
margin-bottom: 14px; margin-bottom: 14px;
@@ -6371,16 +6422,21 @@
} }
.v2-auth-fields > label { .v2-auth-fields > label {
font-size: 12px; font-size: 13px;
} }
.v2-auth-fields > label .semi-input-wrapper { .v2-auth-fields > label .semi-input-wrapper {
height: 46px; height: 48px;
} }
.v2-auth-form > .semi-button-primary { .v2-auth-form > .semi-button-primary {
height: 46px; height: 48px;
font-size: 13px; font-size: 14px;
}
.v2-auth-trust-note {
margin: 14px -18px -20px;
padding: 10px 18px;
} }
.v2-route-error > .v2-route-error-card.semi-card { .v2-route-error > .v2-route-error-card.semi-card {