feat(portal-shell): v2.0 P0 shadcn standardization + security + streaming + error handling

- shadcn/ui 标准化:废弃纸感令牌,统一 bg-background/text-foreground 等
- Tailwind v4 + @theme inline,移除 tailwind.config.js
- React 19 use() + Suspense 流式渲染,首屏骨架秒出
- 三级错误边界:Route → Section → Widget 层层兜底
- 错误上报:useErrorReport → sendBeacon → /api/log mock 端点
- 三层安全边界:L1 角色门禁 / L2 权限点门禁 / L3 数据范围
- 权限位图 base36 压缩:67 权限点 → ~14 字符,JWT 体积减少 ≥ 99%
- notify 统一 Toast 封装,禁止业务直接 import sonner
- PluginBoundary 替代 PluginLoader(错误边界 + Suspense + Skeleton 三件套)

验证:typecheck 0 错误 / lint 0 错误 / build 6 路由生成成功
This commit is contained in:
SpecialX
2026-07-17 16:10:05 +08:00
parent f7e52b5b7f
commit 9cedf0c437
140 changed files with 10872 additions and 3192 deletions

View File

@@ -0,0 +1,135 @@
/**
* Layout 与 Slot 配置类型portal-shell spec §4、§6.2
*
* 对应 config-service PluginConfigResponse三层合并后的插件配置
* 类型与 services/config-service 的 GraphQL schema 对齐,
* 通过 apollo-router GraphQL 查询 pluginConfig(userId, role) 获取。
*
* 关联portal-shell spec §4.1 5 种 Layout 模板、§4.2 Slot 系统、§6.2 PluginConfigResponse
*/
import type { Role } from "./plugin.js";
/** Layout 模板信息(对应 config-service LayoutTemplateInfo */
export interface LayoutTemplateInfo {
/** Layout ID'classic' | 'focus' | 'split' | 'triple' | 'canvas' */
layoutId: string;
/** 显示名 */
displayName: string;
/** 描述 */
description: string;
/** 可用 slot 列表,如 ["top", "side", "main"] */
availableSlots: string[];
/** Layout schema JSON 字符串grid 配置等) */
layoutSchemaJson: string;
}
/** Slot 配置(对应 config-service SlotConfig */
export interface SlotConfig {
/** Slot 名称:'top' | 'side' | 'main' | 'main-left' | 'main-right' | 'right' | 'canvas-grid' */
slotName: string;
/** 导航项列表side slot 的导航菜单项) */
navItems: string[];
}
/** 插件放置(三层合并后,对应 config-service PluginPlacement */
export interface PluginPlacement {
/** 插件 ID */
pluginId: string;
/** 插入的 slot 名称 */
slot: string;
/** 显示顺序(升序) */
sortOrder: number;
/** 尺寸 JSON 字符串({colSpan, rowSpan} */
sizeJson: string;
/** 三层合并后的最终 props JSON 字符串 */
propsJson: string;
/** 是否可见 */
isVisible: boolean;
}
/** 插件注册项(对应 config-service PluginRegistryItem */
export interface PluginRegistryItem {
/** 插件 ID */
pluginId: string;
/** 分类:'universal' | 'sidebar' | 'topbar' | 'teacher' | 'student' | 'parent' | 'admin' */
category: string;
/** 版本semver */
version: string;
/** 显示名 */
displayName: string;
/** 描述 */
description: string;
/** 可访问此插件的角色列表 */
requiredRoles: string[];
/** 是否内置插件 */
isBuiltin: boolean;
/** 是否全局启用 */
isActive: boolean;
}
/**
* 三层合并后的插件配置响应(对应 config-service PluginConfigResponse
*
* 由 config-service 合并三层配置后返回:
* Layer 1: plugin_registry系统默认
* Layer 2: role_plugin_mapping角色模板
* Layer 3: user_layout_override用户覆盖
*/
export interface PluginConfigResponse {
/** 当前启用的 Layout 模板 */
activeLayout: LayoutTemplateInfo | null;
/** Slot 配置列表 */
slots: SlotConfig[];
/** 插件放置列表(三层合并后) */
plugins: PluginPlacement[];
/** 插件注册表(所有可用插件) */
registry: PluginRegistryItem[];
}
/** Layout 模板 ID 枚举portal-shell spec §4.1 */
export const LAYOUT_TEMPLATE_IDS = [
"classic",
"focus",
"split",
"triple",
"canvas",
] as const;
/** Layout 模板 ID 类型 */
export type LayoutTemplateId = (typeof LAYOUT_TEMPLATE_IDS)[number];
/** Slot 名称枚举portal-shell spec §4.2 */
export const SLOT_NAMES = [
"top",
"side",
"main",
"main-left",
"main-right",
"right",
"canvas-grid",
] as const;
/** Slot 名称类型 */
export type SlotName = (typeof SLOT_NAMES)[number];
/** 角色-Layout 默认配置admin 配置角色默认模板) */
export interface RoleLayoutDefault {
role: Role;
layoutId: string;
slotOverrides: Record<string, unknown>;
}
/** 用户布局覆盖(用户自定义) */
export interface UserLayoutOverride {
userId: string;
activeLayout: string;
slotOverrides: Record<string, unknown>;
pluginPlacements: Array<{
pluginId: string;
slot: string;
sortOrder: number;
size: { colSpan: number; rowSpan: number };
props: Record<string, unknown>;
}>;
hiddenPlugins: string[];
}