Files
jiang13-bbs/frontend/lib/roles.ts

193 lines
6.7 KiB
TypeScript
Raw Permalink 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;
}
/** 彻底删除帖/评:仅站长 */
export function canPurgeContent(role?: string | null): boolean {
return role === ROLES.OWNER;
}
/** 携带板块授权与消息权限的最小用户形状 */
export interface RoleUserLike {
role?: string | null;
board_ids?: number[] | null;
can_manage_messages?: boolean | null;
/** 是否至少管理一个群(/me 可带;后台入口用) */
chat_admin_room_count?: 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 canModerateAuthor(
actor: RoleUserLike | null | undefined,
authorRole?: string | null
): boolean {
if (!actor) return false;
if (actor.role === ROLES.BOARD_ADMIN) {
return roleLevel(authorRole) < ROLE_LEVEL[ROLES.ADMIN];
}
return true;
}
/** 是否可软删他人内容(板块范围 + 作者角色保护) */
export function canStaffSoftDelete(
actor: RoleUserLike | null | undefined,
boardId: number | null | undefined,
authorRole?: string | null
): boolean {
return canModerateBoard(actor, boardId) && canModerateAuthor(actor, authorRole);
}
/** 是否拥有任意板块的内容审核权(后台「内容管理」入口) */
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;
}
/** 站点级消息管理(站长/超管固有,或 can_manage_messages) */
export function hasSiteMessagePerm(user: RoleUserLike | null | undefined): boolean {
if (!user) return false;
if (canOverseeChat(user.role)) return true;
return !!user.can_manage_messages;
}
/** 后台「消息管理」入口:站点消息权或至少是一个群的管理员 */
export function canAccessAdminMessages(user: RoleUserLike | null | undefined): boolean {
if (!user) return false;
if (hasSiteMessagePerm(user)) return true;
return (user.chat_admin_room_count ?? 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;
}