"use client"; /** * SlotRenderer - 按 Config 渲染插件列表(v2.1 M8) * * 给定一个 slot 名称,从配置中过滤出该 slot 的可见插件,按 sortOrder 排序, * 查 Registry 取组件,注入 PluginProps,经 PluginLoader 挂载(含 ErrorBoundary)。 * * 关联:portal-shell spec §2.2、§5.1 */ import { useMemo, type ReactNode } from "react"; import { REGISTRY, isPluginRegistered } from "./Registry"; import { PluginLoader, PluginSkeleton } from "./PluginLoader"; import { parsePropsJson, parseSizeJson } from "./PropsMerger"; import type { PluginPlacement, PluginProps, Role } from "@/lib/types"; import type { AuthUser } from "@/providers/AuthProvider"; export interface SlotRendererProps { slotName: string; layoutId: string; plugins: PluginPlacement[]; user: AuthUser; role: Role; userId: string; } export function SlotRenderer({ slotName, layoutId, plugins, user, role, userId, }: SlotRendererProps): ReactNode { const visible = useMemo( () => plugins .filter((p) => p.slot === slotName && p.isVisible) .sort((a, b) => a.sortOrder - b.sortOrder), [plugins, slotName], ); if (visible.length === 0) { // 空 slot:main 区显示占位,topbar/side 区不渲染 if (slotName === "top" || slotName === "side") return null; return (
暂无可见插件
); } return (
{visible.map((placement) => { if (!isPluginRegistered(placement.pluginId)) { return (
未注册插件:{placement.pluginId}
); } const manifest = REGISTRY[placement.pluginId]; if (!manifest) { return null; } const pluginProps: PluginProps = { instanceId: `${placement.pluginId}-${slotName}-${placement.sortOrder}`, role, user: { id: userId, name: user.name, email: user.email, dataScope: user.dataScope, }, slot: { name: slotName, layoutId, size: parseSizeJson(placement.sizeJson), }, props: parsePropsJson(placement.propsJson), }; return ( ); })}
); } /** Slot 加载态占位(layout 切换瞬间) */ export function SlotSkeleton({ count = 1 }: { count?: number }): ReactNode { return (
{Array.from({ length: count }).map((_, i) => ( ))}
); }