feat: add password recovery flow (#358)

This commit is contained in:
MasterChiefCN
2026-08-31 09:58:36 +08:00
committed by GitHub
parent 8ebceecbb6
commit e124a9ea04
19 changed files with 875 additions and 52 deletions

View File

@@ -141,7 +141,7 @@ export default function EmailSettingsPanel() {
form.setFieldsValue(toEmailFormValues(result.setting));
setDraftEnabled(result.setting.enabled);
setDirty(false);
message.success("注册邮件配置已保存");
message.success("邮件服务配置已保存");
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "保存邮件配置失败";
setSaveError(`${errorMessage}。未自动重试,请重新读取当前配置后再决定是否保存。`);
@@ -219,7 +219,7 @@ export default function EmailSettingsPanel() {
<MailCheck className="size-4" aria-hidden="true" />
</span>
<div>
<strong>{dirty ? "有未保存的邮件调整" : `注册验证码:${setting.enabled ? "已启用" : "未启用"}`}</strong>
<strong>{dirty ? "有未保存的邮件调整" : `账户邮件:${setting.enabled ? "已启用" : "未启用"}`}</strong>
<p>{dirty ? "完成当前配置后保存生效。" : formatSettingTime(setting.updatedAt, "使用系统默认值")}</p>
</div>
</div>
@@ -246,15 +246,15 @@ export default function EmailSettingsPanel() {
<SettingsSectionCard
className="admin-email-section admin-email-delivery-section"
icon={<Send className="size-4" aria-hidden="true" />}
title="1. 是否发送注册邮箱验证码"
description="这是邮件服务的主开关。关闭时不需要配置 SMTP;开启后再填写连接与发件信息。"
title="1. 是否发送账户安全邮件"
description="这是注册验证码和密码找回邮件的主开关。关闭时不需要配置 SMTP;开启后再填写连接与发件信息。"
status={<AdminStatusBadge label={draftEnabled ? (dirty && !setting.enabled ? "待启用" : "已启用") : dirty && setting.enabled ? "待停用" : "未启用"} tone={dirty ? "warning" : draftEnabled ? "success" : "neutral"} />}
footer={
!draftEnabled ? (
<>
<div className="admin-email-footer-note">
<BadgeCheck className="size-4" aria-hidden="true" />
<span>关闭后,普通邮箱注册不再要求邮箱验证码。</span>
<span>关闭后,普通邮箱注册和密码找回都无法发送验证码。</span>
</div>
<div className="flex flex-wrap items-center gap-2">
{dirty ? (
@@ -276,13 +276,13 @@ export default function EmailSettingsPanel() {
</span>
<div className="admin-email-policy-copy">
<div className="flex flex-wrap items-center gap-2">
<strong>发送注册邮箱验证码</strong>
<strong>发送注册与密码找回验证码</strong>
<AdminStatusBadge label="保存后生效" tone="info" />
</div>
<p>启用后,普通邮箱注册需要获取并校验 6 位验证码;邮件发送失败时不会创建可用验证码。</p>
<span>关闭只停止后续验证码邮件,不改变新用户注册开关,也不影响已有账号。</span>
<p>启用后,普通邮箱注册和密码找回使用 6 位验证码;邮件发送失败时不会保留可用验证码。</p>
<span>关闭只停止后续账户邮件,不改变新用户注册开关,也不影响已有登录会话。</span>
</div>
<Switch checked={draftEnabled} disabled={loading || refreshing || saving} aria-label="发送注册邮箱验证码" onChange={toggleEnabled} />
<Switch checked={draftEnabled} disabled={loading || refreshing || saving} aria-label="发送账户安全邮件" onChange={toggleEnabled} />
</div>
</SettingsSectionCard>
</div>
@@ -372,7 +372,7 @@ export default function EmailSettingsPanel() {
</div>
<div className="admin-email-form-section">
<FormSectionTitle icon={<AtSign className="size-4" />} title="发件人身份" description="这组名称和地址会显示在注册验证码邮件的 From 信息中。" />
<FormSectionTitle icon={<AtSign className="size-4" />} title="发件人身份" description="这组名称和地址会显示在注册与密码重置邮件的 From 信息中。" />
<div className="admin-email-form-grid">
<Form.Item
name="fromEmail"
@@ -458,7 +458,7 @@ function validateEmailDraft(values: EmailFormValues, setting: EmailSetting | nul
const draft = normalizeEmailFormValues(values);
if (!draft.enabled) return "";
if (/\r|\n/.test(draft.fromName)) return "发件人名称不能包含换行";
if (!draft.host || draft.port < 1 || draft.port > 65535 || !draft.fromEmail) return "启用注册邮件前请完整填写 SMTP 主机、端口和发件邮箱";
if (!draft.host || draft.port < 1 || draft.port > 65535 || !draft.fromEmail) return "启用邮件服务前请完整填写 SMTP 主机、端口和发件邮箱";
if (!isValidEmail(draft.fromEmail)) return "发件邮箱格式不正确";
if (draft.username && !draft.password && !setting?.hasPassword) return "SMTP 用户名已填写,请同时填写密码或服务商授权码";
return "";