feat: 实现完整的板块级RBAC内容审核系统

- 新增站长/超级管理员/管理员/板块管理员四级角色体系
- 实现实时权限快照加载与细粒度权限校验
- 新增内容审核队列与前后端页面
- 重构用户权限管理与站点管理逻辑
- 新增评论/帖子审核状态与通知推送
- 优化前端权限控制与角色徽章展示
- 修正数据库迁移与默认值问题
This commit is contained in:
2026-09-15 04:47:34 +08:00
parent 6da4309453
commit a7b6421840
44 changed files with 3149 additions and 585 deletions

127
frontend/lib/roles.ts Normal file
View File

@@ -0,0 +1,127 @@
// 角色与权限中枢:与后端 model.Role / service.Actor 保持单一对应关系。
// 前端只做"是否展示入口/按钮"的判定,所有最终权限以后端校验为准。
export const ROLES = {
USER: "user",
BOARD_ADMIN: "board_admin",
ADMIN: "admin",
SUPER_ADMIN: "super_admin",
OWNER: "owner",
} as const;
export type RoleKey = (typeof ROLES)[keyof typeof ROLES];
// 角色等级(与后端 model.RoleLevel 一致),未知角色按普通用户处理
export const ROLE_LEVEL: Record<string, number> = {
[ROLES.USER]: 0,
[ROLES.BOARD_ADMIN]: 30,
[ROLES.ADMIN]: 50,
[ROLES.SUPER_ADMIN]: 80,
[ROLES.OWNER]: 100,
};
export function roleLevel(role?: string | null): number {
return ROLE_LEVEL[role ?? ""] ?? 0;
}
/** 管理团队成员(板块管理员及以上) */
export function isStaff(role?: string | null): boolean {
return roleLevel(role) >= ROLE_LEVEL[ROLES.BOARD_ADMIN];
}
/** 管理员及以上(全站内容管理:置顶/加精/公告) */
export function isAdminOrAbove(role?: string | null): boolean {
return roleLevel(role) >= ROLE_LEVEL[ROLES.ADMIN];
}
/** 超级管理员及以上(用户与权限、站点设置) */
export function isSuperOrOwner(role?: string | null): boolean {
return roleLevel(role) >= ROLE_LEVEL[ROLES.SUPER_ADMIN];
}
/** 站长(唯一,不可被操作) */
export function isOwner(role?: string | null): boolean {
return role === ROLES.OWNER;
}
/** 携带板块授权的最小用户形状(/me、帖子详情作者等均满足) */
export interface RoleUserLike {
role?: string | null;
board_ids?: number[] | null;
}
/**
* 能否管理指定板块的内容(帖子/评论的编辑删除审核):
* 管理员及以上不限板块;板块管理员仅限被授权板块
*/
export function canModerateBoard(
user: RoleUserLike | null | undefined,
boardId: number | null | undefined
): boolean {
if (!user) return false;
if (isAdminOrAbove(user.role)) return true;
if (user.role !== ROLES.BOARD_ADMIN || !boardId) return false;
return (user.board_ids ?? []).includes(boardId);
}
/** 是否拥有任意板块的内容审核权(后台"内容审核"入口) */
export function canModerateAny(user: RoleUserLike | null | undefined): boolean {
if (!user) return false;
if (isAdminOrAbove(user.role)) return true;
return user.role === ROLES.BOARD_ADMIN && (user.board_ids?.length ?? 0) > 0;
}
/** 角色图标 key(RoleBadge 负责映射到具体图标,集中配置便于替换) */
export type RoleIconKey = "crown" | "shield-star" | "shield" | "shield-board" | "user";
export interface RoleMeta {
key: RoleKey;
label: string;
icon: RoleIconKey;
/** 主色使用的 CSS token 名(RoleBadge 取 var(--token)) */
colorToken: string;
softToken: string;
}
// 各管理角色的图标与配色集中配置;站长固定使用皇冠站长标识
export const ROLE_META: Record<RoleKey, RoleMeta> = {
[ROLES.OWNER]: {
key: ROLES.OWNER,
label: "站长",
icon: "crown",
colorToken: "--gold",
softToken: "--gold-soft",
},
[ROLES.SUPER_ADMIN]: {
key: ROLES.SUPER_ADMIN,
label: "超级管理员",
icon: "shield-star",
colorToken: "--accent",
softToken: "--accent-soft",
},
[ROLES.ADMIN]: {
key: ROLES.ADMIN,
label: "管理员",
icon: "shield",
colorToken: "--accent",
softToken: "--accent-soft",
},
[ROLES.BOARD_ADMIN]: {
key: ROLES.BOARD_ADMIN,
label: "板块管理员",
icon: "shield-board",
colorToken: "--accent",
softToken: "--accent-soft",
},
[ROLES.USER]: {
key: ROLES.USER,
label: "普通用户",
icon: "user",
colorToken: "--ink-3",
softToken: "--panel-2",
},
};
export function roleMeta(role?: string | null): RoleMeta {
return ROLE_META[(role as RoleKey)] ?? ROLE_META.user;
}