imeeting/frontend/src/pages/auth/reset-password/index.tsx

152 lines
5.4 KiB
TypeScript
Raw Normal View History

import {Alert, Button, Form, Input, Typography, message} from "antd";
2026-03-17 07:31:09 +00:00
import { LockOutlined, LogoutOutlined } from "@ant-design/icons";
import {useEffect, useMemo, useState} from "react";
2026-03-17 07:31:09 +00:00
import { useNavigate } from "react-router-dom";
import { getCurrentUser, updateMyPassword } from "@/api";
import {fetchPublicPasswordPolicy, type PasswordPolicyPublic} from "@/api/auth";
import {buildPasswordPolicyValidator, buildPolicyHints} from "@/utils/password";
import AuthShell from "../components/AuthShell";
2026-03-17 07:31:09 +00:00
const {Text} = Typography;
2026-03-17 07:31:09 +00:00
type ResetPasswordFormValues = {
oldPassword: string;
newPassword: string;
confirmPassword: string;
};
const RESET_HIGHLIGHTS = [
"首次登录必须完成密码更新,避免继续使用初始凭证。",
"修改成功后保持当前登录态,无需再次验证。",
"若不是本人操作,请立即退出并联系管理员。"
];
2026-03-17 07:31:09 +00:00
export default function ResetPassword() {
const [loading, setLoading] = useState(false);
const [policy, setPolicy] = useState<PasswordPolicyPublic | null>(null);
const [username, setUsername] = useState<string>();
2026-03-17 07:31:09 +00:00
const navigate = useNavigate();
const [form] = Form.useForm<ResetPasswordFormValues>();
const policyHints = useMemo(() => buildPolicyHints(policy), [policy]);
useEffect(() => {
const init = async () => {
const [profileResult, policyResult] = await Promise.allSettled([getCurrentUser(), fetchPublicPasswordPolicy()]);
if (profileResult.status === "fulfilled") {
setUsername(profileResult.value.username);
}
setPolicy(policyResult.status === "fulfilled" ? policyResult.value : null);
};
void init();
}, []);
2026-03-17 07:31:09 +00:00
const goToLogin = () => {
localStorage.removeItem("accessToken");
localStorage.removeItem("refreshToken");
localStorage.removeItem("displayName");
localStorage.removeItem("username");
localStorage.removeItem("availableTenants");
localStorage.removeItem("activeTenantId");
sessionStorage.removeItem("userProfile");
navigate("/login", { replace: true });
2026-03-17 07:31:09 +00:00
};
const onFinish = async (values: ResetPasswordFormValues) => {
setLoading(true);
try {
await updateMyPassword({
oldPassword: values.oldPassword,
newPassword: values.newPassword
});
const profile = await getCurrentUser();
sessionStorage.setItem("userProfile", JSON.stringify(profile));
window.dispatchEvent(new Event("user-profile-updated"));
message.success("密码已更新");
navigate("/", { replace: true });
2026-03-17 07:31:09 +00:00
} finally {
setLoading(false);
}
};
return (
<AuthShell
eyebrow="首次登录校验"
asideTitle="先完成一次安全换密,再进入系统"
asideDescription="初始密码只用于验证当前账号的合法持有者。设置新的个人密码后,系统会继续保持登录并同步最新的账户资料。"
asideHighlights={RESET_HIGHLIGHTS}
title="修改初始密码"
subtitle="请输入当前密码并设置一个符合策略的新密码。"
footer={<Text type="secondary">退</Text>}
>
<Alert
showIcon
type="info"
message="密码更新后将直接进入系统首页"
description="新密码提交成功后立即生效,旧密码会同时失效。"
2026-06-30 05:37:33 +00:00
className="auth-form__notice"
/>
<Form form={form} layout="vertical" onFinish={onFinish} requiredMark={false} className="auth-form">
<Form.Item label="当前密码" name="oldPassword" rules={[{required: true, message: "请输入当前密码"}]}>
<Input.Password size="large" prefix={<LockOutlined/>} autoComplete="current-password"/>
</Form.Item>
{policyHints.length > 0 ? (
<div className="auth-form__policy-hints">
<span className="auth-form__policy-hints-title"></span>
<ul className="auth-form__policy-list">
{policyHints.map((hint) => (
<li key={hint}>{hint}</li>
))}
</ul>
</div>
) : null}
2026-03-17 07:31:09 +00:00
<Form.Item
label="新密码"
name="newPassword"
validateFirst
rules={[
{required: true, min: 6, message: "新密码至少 6 位"},
{validator: buildPasswordPolicyValidator(policy, () => username)}
]}
>
<Input.Password size="large" prefix={<LockOutlined/>} autoComplete="new-password"/>
</Form.Item>
<Form.Item
label="确认新密码"
name="confirmPassword"
dependencies={["newPassword"]}
rules={[
{required: true, message: "请再次输入新密码"},
({getFieldValue}) => ({
validator(_, value) {
if (!value || getFieldValue("newPassword") === value) {
return Promise.resolve();
2026-03-17 07:31:09 +00:00
}
return Promise.reject(new Error("两次输入的新密码不一致"));
}
})
]}
>
<Input.Password size="large" prefix={<LockOutlined/>} autoComplete="new-password"/>
</Form.Item>
2026-03-17 07:31:09 +00:00
<div className="auth-form__actions">
<Button type="primary" htmlType="submit" block size="large" loading={loading}>
2026-03-17 07:31:09 +00:00
</Button>
<Button block size="large" icon={<LogoutOutlined/>} onClick={goToLogin}>
2026-03-17 07:31:09 +00:00
退
</Button>
</div>
</Form>
</AuthShell>
2026-03-17 07:31:09 +00:00
);
}