feat: optimize user management and add account deletion
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user