2026-06-18 05:43:27 +00:00
|
|
|
|
import {Alert, Button, Form, Input, Typography, message} from "antd";
|
2026-03-17 07:31:09 +00:00
|
|
|
|
import { LockOutlined, LogoutOutlined } from "@ant-design/icons";
|
2026-06-18 05:43:27 +00:00
|
|
|
|
import {useEffect, useMemo, useState} from "react";
|
2026-03-17 07:31:09 +00:00
|
|
|
|
import { useNavigate } from "react-router-dom";
|
2026-05-13 06:31:48 +00:00
|
|
|
|
import { getCurrentUser, updateMyPassword } from "@/api";
|
2026-06-18 05:43:27 +00:00
|
|
|
|
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
|
|
|
|
|
2026-06-18 05:43:27 +00:00
|
|
|
|
const {Text} = Typography;
|
2026-03-17 07:31:09 +00:00
|
|
|
|
|
|
|
|
|
|
type ResetPasswordFormValues = {
|
|
|
|
|
|
oldPassword: string;
|
|
|
|
|
|
newPassword: string;
|
|
|
|
|
|
confirmPassword: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-06-18 05:43:27 +00:00
|
|
|
|
const RESET_HIGHLIGHTS = [
|
|
|
|
|
|
"首次登录必须完成密码更新,避免继续使用初始凭证。",
|
|
|
|
|
|
"修改成功后保持当前登录态,无需再次验证。",
|
|
|
|
|
|
"若不是本人操作,请立即退出并联系管理员。"
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-03-17 07:31:09 +00:00
|
|
|
|
export default function ResetPassword() {
|
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
2026-06-18 05:43:27 +00:00
|
|
|
|
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>();
|
|
|
|
|
|
|
2026-06-18 05:43:27 +00:00
|
|
|
|
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 = () => {
|
2026-05-13 06:31:48 +00:00
|
|
|
|
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
|
|
|
|
|
|
});
|
2026-05-13 06:31:48 +00:00
|
|
|
|
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 (
|
2026-06-18 05:43:27 +00:00
|
|
|
|
<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"
|
2026-06-18 05:43:27 +00:00
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<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
|
|
|
|
|
2026-06-18 05:43:27 +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
|
|
|
|
}
|
2026-06-18 05:43:27 +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
|
|
|
|
|
2026-06-18 05:43:27 +00:00
|
|
|
|
<div className="auth-form__actions">
|
|
|
|
|
|
<Button type="primary" htmlType="submit" block size="large" loading={loading}>
|
2026-05-13 06:31:48 +00:00
|
|
|
|
提交并保持登录
|
2026-03-17 07:31:09 +00:00
|
|
|
|
</Button>
|
2026-06-18 05:43:27 +00:00
|
|
|
|
<Button block size="large" icon={<LogoutOutlined/>} onClick={goToLogin}>
|
2026-03-17 07:31:09 +00:00
|
|
|
|
退出登录
|
|
|
|
|
|
</Button>
|
2026-06-18 05:43:27 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</AuthShell>
|
2026-03-17 07:31:09 +00:00
|
|
|
|
);
|
|
|
|
|
|
}
|