Files
jiang13-bbs/frontend/components/RoleBadge.tsx
freefire d7c6b3a961 feat: 帖子收藏与用户关注
- 新增收藏功能:favorite 服务与 handler、FavoriteButton 组件
- 新增关注功能:follow 服务与 handler、FollowButton 组件
- 用户主页适配关注/粉丝与收藏展示
- flash 消息工具、广告与侧边栏组件微调
2026-09-26 03:02:40 +08:00

105 lines
3.9 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.
"use client";
import { Crown, BadgeCheck, ShieldCheck, Shield, Users, type LucideIcon } from "lucide-react";
import { roleMeta, type RoleIconKey } from "@/lib/roles";
import { BOARD_COLOR_COUNT } from "@/components/BoardIcon";
// 角色图标集中映射(图标库不含 ShieldStar,超管使用 BadgeCheck 表达"全权限认证")
const ICONS: Record<RoleIconKey, LucideIcon> = {
crown: Crown,
"shield-star": BadgeCheck,
shield: ShieldCheck,
"shield-board": Shield,
user: Users,
};
/** 已知板块信息:名称 + 配色下标(0..15) */
type BoardRef = { name: string; colorIdx: number | null };
/**
* 角色徽章:图标 + 文案。
* - 普通角色:配色走角色元数据中的 CSS token
* - board_admin 提供板块名时逐板块渲染「{板块名} · 板块管理员」,配色对齐板块色
* (av-N,深浅色自适应);truncate 窄列场景折叠为单枚「{首板块}等 N 个板块 · 板块管理员」
* - board_admin 未提供板块名时退回「板块管理员 · N 个板块」
*/
export default function RoleBadge({
role,
boardIds,
boardNames,
boardColors,
size = 12,
className = "",
title,
truncate = false,
}: {
role?: string | null;
boardIds?: number[] | null;
/** id -> 名称映射;提供时板块管理员按板块逐枚展示 */
boardNames?: Map<number, string> | Record<number, string>;
/** id -> 配色下标(boardColor 计算);提供时徽章配色对齐板块色 */
boardColors?: Map<number, number> | Record<number, number>;
size?: number;
className?: string;
title?: string;
/** 窄列场景:限制最大宽度并省略过长文案;多板块折叠为单枚 */
truncate?: boolean;
}) {
const meta = roleMeta(role);
const Icon = ICONS[meta.icon];
// 单枚渲染:有板块配色下标时用 av-N 类(自带深浅色两套值),否则角色配色 token
const renderBadge = (label: string, boardColorIdx: number | null, key?: number) => (
<span
key={key}
className={`inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11.5px] font-medium leading-[1.5] select-none transition-transform duration-200 hover:-translate-y-[1px] ${
truncate ? "max-w-full whitespace-nowrap" : "whitespace-nowrap"
} ${boardColorIdx != null ? `av-${boardColorIdx}` : ""} ${className}`}
style={
boardColorIdx != null
? undefined
: { background: `var(${meta.softToken})`, color: `var(${meta.colorToken})` }
}
data-tip={title ?? label}
>
<Icon size={size} className="shrink-0" />
{truncate ? <span className="overflow-hidden text-ellipsis">{label}</span> : label}
</span>
);
if (role === "board_admin" && boardNames) {
const boards = (boardIds ?? [])
.map((id): BoardRef | null => {
const name = boardNames instanceof Map ? boardNames.get(id) : boardNames[id];
if (!name) return null;
const c = boardColors
? boardColors instanceof Map
? boardColors.get(id)
: boardColors[id]
: undefined;
return { name, colorIdx: typeof c === "number" ? c % BOARD_COLOR_COUNT : null };
})
.filter((b): b is BoardRef => b !== null);
if (boards.length > 0) {
if (truncate) {
// 窄列:折叠为单枚,避免多枚徽章撑破布局
const first = boards[0];
const label =
boards.length === 1
? `${first.name} · ${meta.label}`
: `${first.name} 等 ${boards.length} 个板块 · ${meta.label}`;
return renderBadge(label, first.colorIdx);
}
return <>{boards.map((b, i) => renderBadge(`${b.name} · ${meta.label}`, b.colorIdx, i))}</>;
}
// 板块名缺失(板块被删等):回落通用渲染
}
let label = meta.label;
if (role === "board_admin" && boardIds && boardIds.length > 0) {
label = `${meta.label} · ${boardIds.length} 个板块`;
}
return renderBadge(label, null);
}