oa-auth-layout-wecom-login-ux 优化认证页登录布局
这个提交包含在:
@@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom";
|
||||
import { isApiError } from "../api/types";
|
||||
import { mapApiErrorMessage } from "../api/errorMessages";
|
||||
import { useAuth } from "../auth/AuthContext";
|
||||
import { AuthPanel } from "../components/auth/AuthPanel";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Label } from "../components/ui/label";
|
||||
import { PasswordInput } from "../components/ui/password-input";
|
||||
@@ -50,17 +51,9 @@ export function ChangePasswordPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="card-std space-y-6 border-primary-100 p-6">
|
||||
<div className="mb-6 space-y-2">
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-primary">hyb-oa</p>
|
||||
<h1 className="text-2xl font-bold text-zinc-900">修改密码</h1>
|
||||
<p className="text-sm text-secondary">
|
||||
为了账号安全,请先修改初始密码后再进入系统。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<AuthPanel title="修改密码" subtitle="为了账号安全,请先修改初始密码">
|
||||
<form className="space-y-4" onSubmit={handleSubmit}>
|
||||
<div className="space-y-1">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="oldPassword">旧密码</Label>
|
||||
<PasswordInput
|
||||
autoComplete="current-password"
|
||||
@@ -72,7 +65,7 @@ export function ChangePasswordPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="newPassword">新密码</Label>
|
||||
<PasswordInput
|
||||
autoComplete="new-password"
|
||||
@@ -85,16 +78,7 @@ export function ChangePasswordPage() {
|
||||
<p className="text-xs text-zinc-500">密码要求:{PASSWORD_REQUIREMENTS.join(";")}。</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-xs text-zinc-600">
|
||||
<p className="font-medium text-zinc-700">建议的新密码规则</p>
|
||||
<ul className="mt-1 list-disc space-y-1 pl-4">
|
||||
{PASSWORD_REQUIREMENTS.map((item) => (
|
||||
<li key={item}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="confirmPassword">确认新密码</Label>
|
||||
<PasswordInput
|
||||
autoComplete="new-password"
|
||||
@@ -123,6 +107,6 @@ export function ChangePasswordPage() {
|
||||
{submitting ? "提交中..." : "确认修改"}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</AuthPanel>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Link, useNavigate } from "react-router-dom";
|
||||
import { forgotPassword, resetPassword } from "../api/auth";
|
||||
import { mapApiErrorMessage } from "../api/errorMessages";
|
||||
import { isApiError } from "../api/types";
|
||||
import { AuthPanel } from "../components/auth/AuthPanel";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Input } from "../components/ui/input";
|
||||
import { Label } from "../components/ui/label";
|
||||
@@ -93,13 +94,17 @@ export function ForgotPasswordPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm sm:p-6" data-testid="forgot-password-page">
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold text-slate-900">找回密码</h1>
|
||||
<p className="mt-1 text-sm leading-6 text-slate-500">使用账号邮箱接收验证码,设置新密码后重新登录。</p>
|
||||
</div>
|
||||
|
||||
<form className="mt-6 space-y-4" onSubmit={handleRequestCode}>
|
||||
<AuthPanel
|
||||
title="找回密码"
|
||||
subtitle="使用账号邮箱接收验证码"
|
||||
data-testid="forgot-password-page"
|
||||
footer={(
|
||||
<Link className="font-medium text-primary hover:underline" to="/login">
|
||||
返回登录
|
||||
</Link>
|
||||
)}
|
||||
>
|
||||
<form className="space-y-4" onSubmit={handleRequestCode}>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="forgot-password-email">邮箱</Label>
|
||||
<Input
|
||||
@@ -181,12 +186,6 @@ export function ForgotPasswordPage() {
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="mt-5 text-center text-sm">
|
||||
<Link className="font-medium text-primary hover:underline" to="/login">
|
||||
返回登录
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</AuthPanel>
|
||||
);
|
||||
}
|
||||
|
||||
+35
-31
@@ -5,6 +5,7 @@ import { fetchSsoProviders, fetchWecomConfig } from "../api/auth";
|
||||
import { mapApiErrorMessage } from "../api/errorMessages";
|
||||
import { isApiError } from "../api/types";
|
||||
import { useAuth } from "../auth/AuthContext";
|
||||
import { AuthPanel } from "../components/auth/AuthPanel";
|
||||
import { getDeviceFingerprint } from "../auth/deviceFingerprint";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Input } from "../components/ui/input";
|
||||
@@ -108,7 +109,7 @@ export function LoginPage() {
|
||||
|
||||
function renderPasswordPanel(testId: string) {
|
||||
return (
|
||||
<div className="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm" data-testid={testId}>
|
||||
<div data-testid={testId}>
|
||||
<form className="space-y-4" onSubmit={handleSubmit}>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="login-identifier">账号</Label>
|
||||
@@ -150,29 +151,22 @@ export function LoginPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm sm:p-6">
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold text-slate-900">登录工作台</h1>
|
||||
<p className="mt-1 text-sm text-slate-500">
|
||||
{showWecomPrimary ? "企业微信进入优先。" : "账号密码登录。"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 space-y-4">
|
||||
<AuthPanel title="登录工作台" subtitle={showWecomPrimary ? "企业微信进入优先" : "账号密码登录"}>
|
||||
<div className="space-y-5">
|
||||
{wecomNotice ? (
|
||||
<div className={`rounded-2xl border px-4 py-3 text-sm ${noticeClasses(wecomNotice.tone)}`} data-testid="login-wecom-notice">
|
||||
<div className={`rounded-xl border px-4 py-3 text-sm ${noticeClasses(wecomNotice.tone)}`} data-testid="login-wecom-notice">
|
||||
<div className="font-medium">{wecomNotice.title}</div>
|
||||
<div className="mt-1 leading-6">{wecomNotice.description}</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showWecomPrimary ? (
|
||||
<div className="rounded-xl border border-primary/20 bg-primary/5 p-4" data-testid="login-wecom-primary-panel">
|
||||
<div className="space-y-3" data-testid="login-wecom-primary-panel">
|
||||
<Button className="w-full" data-testid="wecom-entry-primary" disabled={!wecomEntry} type="button" onClick={handleWecomLogin}>
|
||||
{wecomConfigQuery.isLoading ? "准备中..." : "进入工作台"}
|
||||
{wecomConfigQuery.isLoading ? "准备中..." : "企业微信一键进入"}
|
||||
</Button>
|
||||
{wecomConfig && !wecomEntry ? (
|
||||
<div className="mt-3 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-sm leading-6 text-amber-700">
|
||||
<div className="rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-sm leading-6 text-amber-700">
|
||||
企业微信入口不可用,请用账号密码。
|
||||
</div>
|
||||
) : null}
|
||||
@@ -192,25 +186,35 @@ export function LoginPage() {
|
||||
? renderPasswordPanel(showWecomPrimary ? "login-password-secondary-panel" : "login-password-primary-panel")
|
||||
: null}
|
||||
|
||||
{!showWecomPrimary && wecomEntry ? (
|
||||
<Button className="w-full" data-testid="wecom-entry-secondary" type="button" variant="secondary" onClick={handleWecomLogin}>
|
||||
企业微信登录
|
||||
</Button>
|
||||
) : null}
|
||||
{!showWecomPrimary && (wecomEntry || oidcProvider) ? (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3 text-xs text-slate-400">
|
||||
<span className="h-px flex-1 bg-slate-100" />
|
||||
<span>其他登录方式</span>
|
||||
<span className="h-px flex-1 bg-slate-100" />
|
||||
</div>
|
||||
|
||||
{!showWecomPrimary && oidcProvider ? (
|
||||
<Button
|
||||
className="w-full"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
window.location.assign(`${oidcProvider.loginUrl}?returnTo=${encodeURIComponent(redirectTo)}`);
|
||||
}}
|
||||
>
|
||||
使用{oidcProvider.label}
|
||||
</Button>
|
||||
{wecomEntry ? (
|
||||
<Button className="w-full" data-testid="wecom-entry-secondary" type="button" variant="secondary" onClick={handleWecomLogin}>
|
||||
企业微信登录
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{oidcProvider ? (
|
||||
<Button
|
||||
className="w-full"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
window.location.assign(`${oidcProvider.loginUrl}?returnTo=${encodeURIComponent(redirectTo)}`);
|
||||
}}
|
||||
>
|
||||
使用{oidcProvider.label}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</AuthPanel>
|
||||
);
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户