feat: optimize user management and add account deletion

This commit is contained in:
巫凤萍
2026-08-07 17:08:56 +08:00
parent 5670a7939b
commit 1f1887c860
5 changed files with 329 additions and 23 deletions

View File

@@ -30,6 +30,7 @@ export default function UsersPage({ currentUser }: { currentUser: AuthUser }) {
const [role, setRole] = useState<"admin" | "user">("user");
const [resetTarget, setResetTarget] = useState<ManagedUser | null>(null);
const [resetPassword, setResetPassword] = useState("");
const [deleteTarget, setDeleteTarget] = useState<ManagedUser | null>(null);
const [message, setMessage] = useState("");
const [working, setWorking] = useState(false);
@@ -100,6 +101,31 @@ export default function UsersPage({ currentUser }: { currentUser: AuthUser }) {
}
};
const deleteUser = async () => {
if (!deleteTarget) return;
setWorking(true);
setMessage("");
try {
const next = await userApi(
await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "delete",
userId: deleteTarget.id,
}),
}),
);
setUsers(next);
setDeleteTarget(null);
setMessage("账号已删除,该账号所有登录设备均已退出");
} catch (reason) {
setMessage(reason instanceof Error ? reason.message : "删除失败");
} finally {
setWorking(false);
}
};
const canReset = (item: ManagedUser) => {
if (currentUser.role === "super_admin") {
return item.role !== "super_admin" || item.id === currentUser.id;
@@ -107,6 +133,11 @@ export default function UsersPage({ currentUser }: { currentUser: AuthUser }) {
return item.role === "user";
};
const canDelete = (item: ManagedUser) => {
if (item.id === currentUser.id || item.role === "super_admin") return false;
return currentUser.role === "super_admin" || item.role === "user";
};
return (
<div className="user-management-layout">
<section className="panel user-create-panel">
@@ -116,7 +147,10 @@ export default function UsersPage({ currentUser }: { currentUser: AuthUser }) {
<p></p>
</div>
</div>
<form className="user-create-form" onSubmit={createUser}>
<form
className={`user-create-form ${currentUser.role === "super_admin" ? "with-role" : "without-role"}`}
onSubmit={createUser}
>
<label>
<span></span>
<input
@@ -140,7 +174,7 @@ export default function UsersPage({ currentUser }: { currentUser: AuthUser }) {
/>
</label>
{currentUser.role === "super_admin" && (
<label>
<label className="user-role-field">
<span></span>
<select value={role} onChange={(event) => setRole(event.target.value as "admin" | "user")}>
<option value="user"></option>
@@ -171,11 +205,25 @@ export default function UsersPage({ currentUser }: { currentUser: AuthUser }) {
<strong>{item.username}</strong>
<span className={`role-pill ${item.role}`}>{ROLE_LABEL[item.role]}</span>
<span>{new Date(item.updated_at).toLocaleDateString("zh-CN", { timeZone: "Asia/Shanghai" })}</span>
{canReset(item) ? (
<button className="text-button" onClick={() => { setResetTarget(item); setResetPassword(""); }}>
</button>
) : <span></span>}
<div className="user-row-actions">
{canReset(item) && (
<button
className="user-action-button"
onClick={() => { setResetTarget(item); setResetPassword(""); }}
>
</button>
)}
{canDelete(item) && (
<button
className="user-action-button danger"
onClick={() => setDeleteTarget(item)}
>
</button>
)}
{!canReset(item) && !canDelete(item) && <span></span>}
</div>
</div>
))}
</div>
@@ -207,6 +255,58 @@ export default function UsersPage({ currentUser }: { currentUser: AuthUser }) {
</form>
</div>
)}
{deleteTarget && (
<div
className="modal-backdrop"
role="presentation"
onMouseDown={() => { if (!working) setDeleteTarget(null); }}
>
<div
className="modal-card compact user-delete-modal"
role="alertdialog"
aria-modal="true"
aria-labelledby="delete-user-title"
onMouseDown={(event) => event.stopPropagation()}
>
<div className="modal-heading">
<div>
<p className="eyebrow danger"></p>
<h2 id="delete-user-title"> {deleteTarget.username}</h2>
</div>
<button
type="button"
onClick={() => setDeleteTarget(null)}
aria-label="关闭"
disabled={working}
>
×
</button>
</div>
<p className="user-delete-warning">
退
</p>
<div className="modal-actions">
<button
type="button"
className="ghost-button"
onClick={() => setDeleteTarget(null)}
disabled={working}
>
</button>
<button
type="button"
className="danger-button"
onClick={() => void deleteUser()}
disabled={working}
>
{working ? "删除中…" : "确认删除"}
</button>
</div>
</div>
</div>
)}
</div>
);
}