feat(web): polish account login workspace
This commit is contained in:
@@ -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(<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(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();
|
||||
});
|
||||
|
||||
@@ -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() {
|
||||
</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 }) {
|
||||
return <div className="v2-auth-error">
|
||||
const presentation = authErrorPresentation(message);
|
||||
return <div className="v2-auth-error" data-support-trace={presentation.traceId || undefined}>
|
||||
<Banner
|
||||
type="danger"
|
||||
bordered
|
||||
closeIcon={null}
|
||||
icon={<IconAlertTriangle />}
|
||||
title="登录未完成"
|
||||
description={message}
|
||||
description={<span><strong>{presentation.message}</strong><small>请确认账号和密码后重试;连续失败可能会临时锁定账号。</small></span>}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user