feat:账号设置+密码重置

This commit is contained in:
xiaozhu
2026-09-02 10:10:41 +08:00
committed by tao.chen
parent 856bdbae8d
commit 5f5eb739c2
13 changed files with 815 additions and 29 deletions
@@ -0,0 +1,127 @@
import { useEffect, useState } from "react";
import {
AppFormDialog,
dialogPrimaryButtonClass,
dialogSecondaryButtonClass,
} from "~/components/common/AppFormDialog";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import {
formFieldClass,
formHintClass,
formInputClass,
modalFormClass,
} from "../platform/modalUi";
type ResetPasswordDialogProps = {
open: boolean;
displayName: string;
username: string;
saving: boolean;
onSubmit: (newPassword: string) => Promise<void>;
onClose: () => void;
};
export function ResetPasswordDialog({
open,
displayName,
username,
saving,
onSubmit,
onClose,
}: ResetPasswordDialogProps) {
const [password, setPassword] = useState("");
const [confirm, setConfirm] = useState("");
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!open) return;
setPassword("");
setConfirm("");
setError(null);
}, [open]);
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
if (password.length < 8 || password.length > 72) {
setError("密码长度需为 8~72 字符");
return;
}
if (password !== confirm) {
setError("两次输入的密码不一致");
return;
}
setError(null);
await onSubmit(password);
};
return (
<AppFormDialog
open={open}
onOpenChange={(nextOpen) => {
if (!nextOpen) onClose();
}}
eyebrow="SECURITY"
title="重置密码"
>
<form
className={`${modalFormClass} pb-5`}
onSubmit={(event) => void handleSubmit(event)}
>
<p className="mb-4 text-[12px] text-[#587087]">
<strong className="text-[#23384e]">{displayName}</strong>
{username}使
</p>
<label className={formFieldClass}>
<span>
<span className="text-danger">*</span>
</span>
<Input
className={formInputClass}
type="password"
autoFocus
value={password}
onChange={(event) => setPassword(event.target.value)}
autoComplete="new-password"
placeholder="8~72 字符"
/>
</label>
<label className={formFieldClass}>
<span>
<span className="text-danger">*</span>
</span>
<Input
className={formInputClass}
type="password"
value={confirm}
onChange={(event) => setConfirm(event.target.value)}
autoComplete="new-password"
/>
<span className={formHintClass}>线</span>
</label>
{error ? (
<p className="mb-3 text-[11px] text-danger">{error}</p>
) : null}
<div className="flex justify-end gap-2">
<Button
type="button"
variant="outline"
className={dialogSecondaryButtonClass}
onClick={onClose}
disabled={saving}
>
</Button>
<Button
type="submit"
className={dialogPrimaryButtonClass}
disabled={saving}
>
{saving ? "重置中…" : "确认重置"}
</Button>
</div>
</form>
</AppFormDialog>
);
}
@@ -9,6 +9,7 @@ import { Input } from "~/components/ui/input";
import { CreateUserProjectField } from "./CreateUserProjectField";
import {
formFieldClass,
formHintClass,
formInputClass,
modalFormClass,
} from "../platform/modalUi";
@@ -40,6 +41,7 @@ export function UserFormDialog({
onToggleWorkspace,
onSubmit,
onClose,
onResetPassword,
}: {
open: boolean;
editing: Employee | null;
@@ -55,6 +57,7 @@ export function UserFormDialog({
onToggleWorkspace: (workspaceId: string) => void;
onSubmit: (event: React.FormEvent) => void;
onClose: () => void;
onResetPassword?: () => void;
}) {
return (
<AppFormDialog
@@ -189,6 +192,23 @@ export function UserFormDialog({
</select>
</label>
)}
{editing && onResetPassword ? (
<div className="mb-4 rounded-md border border-[#e6edf3] bg-[#f8fafb] px-3.5 py-3">
<div className="text-[12px] font-semibold text-[#3c4e62]"></div>
<p className={`${formHintClass} mt-1.5 mb-2.5 leading-relaxed`}>
使
</p>
<Button
type="button"
variant="outline"
size="sm"
className={dialogSecondaryButtonClass}
onClick={onResetPassword}
>
</Button>
</div>
) : null}
<div className="mt-[3px] flex shrink-0 justify-end gap-2 border-t border-line bg-white -mx-[22px] px-[22px] py-3.5">
<Button
variant="outline"
@@ -16,6 +16,7 @@ import {
import { AdminColgroup, USER_PROJECT_COL_WIDTHS } from "./AdminTable";
import { AdminPagination } from "./AdminPagination";
import { UserFormDialog, type UserFormState } from "./UserFormDialog";
import { ResetPasswordDialog } from "./ResetPasswordDialog";
import { useCursorPage } from "./useCursorPage";
import {
adminEmptyClass,
@@ -61,6 +62,8 @@ export function UserManagementPage({
const [form, setForm] = useState<UserFormState>(EMPTY_FORM);
const [userSearchTerm, setUserSearchTerm] = useState("");
const [deleteTarget, setDeleteTarget] = useState<Employee | null>(null);
const [resetTarget, setResetTarget] = useState<Employee | null>(null);
const [resetSaving, setResetSaving] = useState(false);
const [createProjects, setCreateProjects] = useState<Workspace[]>([]);
const [createProjectsLoading, setCreateProjectsLoading] = useState(false);
const [selectedWorkspaceIds, setSelectedWorkspaceIds] = useState<string[]>([]);
@@ -262,6 +265,23 @@ export function UserManagementPage({
}
};
const executeResetPassword = async (newPassword: string): Promise<void> => {
if (!resetTarget) return;
setResetSaving(true);
try {
await api.resetPlatformEmployeePassword(resetTarget.user_id, newPassword);
onNotify({ tone: "success", message: "密码已重置" });
setResetTarget(null);
} catch (error) {
onNotify({
tone: "error",
message: error instanceof Error ? error.message : "重置密码失败",
});
} finally {
setResetSaving(false);
}
};
const emptyMessage = useMemo(() => {
if (loading) return "正在加载用户…";
if (debouncedSearch.trim()) return "未找到匹配的用户";
@@ -379,6 +399,14 @@ export function UserManagementPage({
onToggleWorkspace={toggleCreateWorkspace}
onSubmit={(event) => void submit(event)}
onClose={() => setDialogOpen(false)}
onResetPassword={
editing
? () => {
setResetTarget(editing);
setDialogOpen(false);
}
: undefined
}
/>
<ConfirmDialog
@@ -400,6 +428,15 @@ export function UserManagementPage({
setDeleteTarget(null);
}}
/>
<ResetPasswordDialog
open={resetTarget !== null}
displayName={resetTarget?.display_name ?? ""}
username={resetTarget?.username ?? ""}
saving={resetSaving}
onSubmit={executeResetPassword}
onClose={() => setResetTarget(null)}
/>
</section>
);
}