完善角色与消息权限:收紧板管内容处置,彻底删除仅站长,并统一管理删帖/评弹窗。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-20 14:30:08 +08:00
parent 1f2e4b2a33
commit c0462a3500
37 changed files with 1340 additions and 472 deletions

View File

@@ -3,7 +3,7 @@
import { useEffect, useState } from "react";
import Link from "next/link";
import { Ban, ExternalLink, Loader2, RotateCcw, ShieldCheck, Users as UsersIcon } from "lucide-react";
import { apiAdminSetUserBan, apiAdminSetUserRole, type Board } from "@/lib/api";
import { apiAdminSetUserBan, apiAdminSetUserRole, apiAdminSetUserMessages, type Board } from "@/lib/api";
import { ROLES, ROLE_META, roleMeta } from "@/lib/roles";
import { formatDate } from "@/lib/format";
import { toast } from "@/lib/toast";
@@ -19,6 +19,7 @@ export interface RoleAccountTarget {
role: string;
board_ids?: number[];
banned: boolean;
can_manage_messages?: boolean;
avatar?: string;
email?: string;
points?: number;
@@ -75,6 +76,7 @@ export default function RoleAccountModal({
const [role, setRole] = useState<string>(ROLES.USER);
const [boardIds, setBoardIds] = useState<number[]>([]);
const [banned, setBanned] = useState(false);
const [manageMessages, setManageMessages] = useState(false);
const [err, setErr] = useState("");
useEffect(() => {
@@ -82,6 +84,7 @@ export default function RoleAccountModal({
setRole(target.role === ROLES.OWNER ? ROLES.USER : target.role);
setBoardIds(target.board_ids ?? []);
setBanned(!!target.banned);
setManageMessages(!!target.can_manage_messages);
setErr("");
}
}, [target]);
@@ -97,6 +100,11 @@ export default function RoleAccountModal({
JSON.stringify([...boardIds].sort((a, b) => a - b));
const roleChanged = role !== target.role || (role === ROLES.BOARD_ADMIN && !sameBoards);
const banChanged = banned !== !!target.banned;
const messagesChanged =
canGrantSuper &&
target.role !== ROLES.OWNER &&
target.role !== ROLES.SUPER_ADMIN &&
manageMessages !== !!target.can_manage_messages;
const willBan = banChanged && banned;
const willUnban = banChanged && !banned;
@@ -105,7 +113,7 @@ export default function RoleAccountModal({
setErr("请至少选择一个授权板块");
return;
}
if (!roleChanged && !banChanged) {
if (!roleChanged && !banChanged && !messagesChanged) {
onClose();
return;
}
@@ -114,6 +122,7 @@ export default function RoleAccountModal({
let nextRole = target.role;
let nextBoards = target.board_ids ?? [];
let nextBanned = !!target.banned;
let nextMessages = !!target.can_manage_messages;
if (roleChanged) {
const res = await apiAdminSetUserRole(
target.id,
@@ -127,19 +136,26 @@ export default function RoleAccountModal({
const res = await apiAdminSetUserBan(target.id, banned);
nextBanned = !!res.user.banned;
}
if (messagesChanged) {
const res = await apiAdminSetUserMessages(target.id, manageMessages);
nextMessages = !!res.user.can_manage_messages;
}
const parts: string[] = [];
if (roleChanged) parts.push("角色已更新");
if (willBan) parts.push("已封禁");
if (willUnban) parts.push("已解封");
if (messagesChanged) parts.push(manageMessages ? "已授予消息管理" : "已撤销消息管理");
toast(parts.join(" · ") || "已保存", "ok");
onSaved({
...target,
role: nextRole,
board_ids: nextBoards,
banned: nextBanned,
can_manage_messages: nextMessages,
});
onClose();
} catch (e) {
toast(e instanceof Error ? e.message : "操作失败");
setErr(e instanceof Error ? e.message : "保存失败");
} finally {
setActing(false);
}
@@ -279,6 +295,49 @@ export default function RoleAccountModal({
</div>
)}
{canGrantSuper &&
target.role !== ROLES.OWNER &&
target.role !== ROLES.SUPER_ADMIN && (
<div className="mt-5 pt-4" style={{ borderTop: "1px solid var(--line)" }}>
<p className="text-[12px] font-medium tracking-wide mb-2.5" style={{ color: "var(--ink-3)" }}>
消息管理
</p>
<button
type="button"
role="switch"
aria-checked={manageMessages}
onClick={() => setManageMessages((v) => !v)}
className="w-full rounded-xl px-3.5 py-3 text-left transition-colors flex items-center justify-between gap-3"
style={{
border: `1.5px solid ${manageMessages ? "var(--accent)" : "var(--line)"}`,
background: manageMessages ? "var(--accent-soft)" : "var(--panel-2)",
}}
>
<span>
<span
className="block text-[13.5px] font-medium"
style={{ color: manageMessages ? "var(--accent)" : "var(--ink)" }}
>
站点群消息管理
</span>
<span className="block mt-1 text-[11.5px] leading-snug" style={{ color: "var(--ink-3)" }}>
可在后台管理全部群聊消息(不含私聊);站长/超管固有全站监管无需此项。
</span>
</span>
<span
className="shrink-0 w-10 h-6 rounded-full relative transition-colors"
style={{ background: manageMessages ? "var(--accent)" : "var(--line)" }}
aria-hidden
>
<span
className="absolute top-0.5 w-5 h-5 rounded-full bg-white transition-transform"
style={{ left: manageMessages ? "1.125rem" : "0.125rem" }}
/>
</span>
</button>
</div>
)}
<div className="mt-5 pt-4" style={{ borderTop: "1px solid var(--line)" }}>
<p className="text-[12px] font-medium tracking-wide mb-2.5" style={{ color: "var(--ink-3)" }}>
账号状态