Files
jiang13-bbs/frontend/lib/roles.ts
freefire a0a9aeed66 feat(admin): 合并内容审核到内容管理,并固定后台侧栏
待审与内容库同一页处理;筛选切换去掉加载占位,避免页面闪动。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 00:10:23 +08:00

148 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 角色与权限中枢:与后端 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;
}
/** 站长/超管:群聊监管旁路(可见全部群、撤回任意消息) */
export function canOverseeChat(role?: string | null): boolean {
return role === ROLES.OWNER || role === ROLES.SUPER_ADMIN;
}
/** 携带板块授权的最小用户形状(/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;
/**
* 授权弹窗用的职责说明(站长不出现在可授权列表)。
* 只描述该角色「有什么」,最终权限以后端为准。
*/
grantDesc?: string;
}
// 各管理角色的图标与配色集中配置;站长固定使用皇冠站长标识
export const ROLE_META: Record<RoleKey, RoleMeta> = {
[ROLES.OWNER]: {
key: ROLES.OWNER,
label: "站长",
icon: "crown",
colorToken: "--gold",
softToken: "--gold-soft",
grantDesc:
"站点所有者:全部后台能力、群聊监管、用户内容档案;身份不可授予或降级。",
},
[ROLES.SUPER_ADMIN]: {
key: ROLES.SUPER_ADMIN,
label: "超级管理员",
icon: "shield-star",
colorToken: "--accent",
softToken: "--accent-soft",
grantDesc:
"用户与角色管理、封禁、外观主题、公告;全站内容审核与置顶/加精;群聊监管(可见全部会话、可撤回任意消息)。不可动站长账号,亦无站长专属的用户内容档案。",
},
[ROLES.ADMIN]: {
key: ROLES.ADMIN,
label: "管理员",
icon: "shield",
colorToken: "--accent",
softToken: "--accent-soft",
grantDesc:
"公告管理;全站帖子/评论审核;前台置顶与加精。不含用户权限、外观设置与群聊监管旁路。",
},
[ROLES.BOARD_ADMIN]: {
key: ROLES.BOARD_ADMIN,
label: "板块管理员",
icon: "shield-board",
colorToken: "--accent",
softToken: "--accent-soft",
grantDesc:
"仅审核并管理被授权板块内的帖子与评论(含删除违规内容)。无公告、置顶/加精、用户管理与外观设置;须至少勾选一个板块。",
},
[ROLES.USER]: {
key: ROLES.USER,
label: "普通用户",
icon: "user",
colorToken: "--ink-3",
softToken: "--panel-2",
grantDesc:
"无任何后台入口。可发帖、评论、私信等;新帖与评论按站点规则进入审核。降级为普通用户时会清空板块授权。",
},
};
export function roleMeta(role?: string | null): RoleMeta {
return ROLE_META[(role as RoleKey)] ?? ROLE_META.user;
}