# maic-renderer 设计稿(v1:只读画布) > 配套计划:`docs/superpowers/plans/2026-05-28-maic-renderer-package.md` > 原始 spec:`docs/superpowers/specs/2026-05-28-maic-renderer-package-design.md` ## 1. 目标 把 OpenMAIC 主仓 `components/maic-renderer/` 中的只读画布部分抽成独立 workspace 包,让任意 React + Tailwind 4 项目都能"装包 → 传 Slide → 直接渲染"。 **v1 范围限定为只读画布**(对应主仓 `Editor/ScreenCanvas.tsx` 子树)。编辑能力(`Editor/Canvas/*`、`Operate/*`、ProseMirror live editor)留给 v2。 理由: 1. 闭环最小可用,跟 `pptxtojson-pro` 形成「产 Slide → 渲 Slide」对子。 2. 编辑器接口面(选中态、命令、撤销、剪贴板、协作)定型后难改,第一版强行定下来大概率返工。 3. 只读包本身有独立价值(嵌课件、报告、回放、PDF 导出预览等)。 ## 2. 设计决策 | # | 决策点 | 选择 | 理由 | |---|--------|------|------| | D1 | 范围 | 分两步:v1 只读 + v2 编辑 | 闭环最小,风险可控 | | D2 | 数据 API | **纯 Props 主**;同时导出可选 Provider | 上手最快,进阶用户也照顾到 | | D3 | 样式 | **要求消费者使用 Tailwind 4** | 工作量最小,跟主仓工程实践一致 | | D4 | 特效层 | **包含**,4 个全做成可选 props,默认关 | 还原"OpenMAIC 同款画布"必备,但默认零负担 | | D5 | 业务耦合元素 | **暴露 renderImage / renderVideo 插槽**,包内默认渲原生标签 | 包不感知业务,消费者按需注入 | | D6 | i18n | 包不带文案 | UI 文案下放给消费者 | | D7 | ProseMirror | v1 不依赖 | 只读用 dangerouslySetInnerHTML 就够,体积大幅缩小 | | D8 | 包发布 | workspace 包,未来可发 npm;当前名 `maic-renderer`(同 `pptxtojson-pro` 风格) | 一致性 | ## 3. 对外 API ### 3.1 主入口 ```ts import { SlideCanvas, type SlideCanvasProps } from 'maic-renderer'; interface SlideCanvasProps { /** 单页幻灯片数据(PPTist 风格) */ slide: Slide; /** 画布缩放,默认 1 */ scale?: number; /** 可选:覆盖 slide.background */ background?: SlideBackground; /** 可选:4 个播放特效,默认全关 */ effects?: { laser?: { elementId: string; color?: string; duration?: number }; spotlight?: { elementId: string }; highlight?: { elementId: string }; zoom?: { elementId: string; scale: number }; }; /** 可选:图片渲染插槽,消费者可注入 placeholder/retry 逻辑 */ renderImage?: (el: PPTImageElement, resolvedSrc: string) => React.ReactNode; /** 可选:视频渲染插槽 */ renderVideo?: (el: PPTVideoElement) => React.ReactNode; /** 可选:元素点击 */ onElementClick?: (el: PPTElement, event: React.MouseEvent) => void; /** 透传外层 */ className?: string; style?: React.CSSProperties; } ``` ### 3.2 Provider 高阶模式(可选) ```tsx import { SlideRendererProvider, useSlideContext } from 'maic-renderer'; {/* 子组件 useSlideContext() 拿数据 */} ``` ### 3.3 元素子组件(细粒度复用) ```ts import { BaseTextElement, BaseShapeElement, BaseImageElement, BaseLineElement, BaseChartElement, BaseLatexElement, BaseTableElement, BaseVideoElement, BaseCodeElement, } from 'maic-renderer/elements'; ``` ### 3.4 类型 ```ts import type { Slide, PPTElement, SlideBackground, SlideTheme, PPTTextElement, PPTShapeElement, PPTImageElement, PPTLineElement, PPTChartElement, PPTLatexElement, PPTTableElement, PPTVideoElement, PPTCodeElement, ImageElementClip, ImageElementFilters, Gradient, GradientType, PPTElementOutline, PPTElementShadow, } from 'maic-renderer/types'; ``` ## 4. 包结构 ``` packages/maic-renderer/ ├── package.json ├── README.md ├── DESIGN.md ├── tsconfig.json ├── rollup.config.js └── src/ ├── index.ts # 主入口 ├── SlideCanvas.tsx # 替代 ScreenCanvas(props 驱动) ├── SlideElement.tsx # 替代 ScreenElement ├── context.tsx # Provider + useSlideContext ├── effects/ │ ├── index.ts │ ├── HighlightOverlay.tsx │ ├── SpotlightOverlay.tsx │ ├── LaserOverlay.tsx │ └── ZoomWrapper.tsx ├── elements/ │ ├── index.ts │ ├── text/BaseTextElement.tsx │ ├── shape/ │ │ ├── BaseShapeElement.tsx │ │ ├── GradientDefs.tsx │ │ └── PatternDefs.tsx │ ├── image/ │ │ ├── BaseImageElement.tsx │ │ ├── ImageOutline.tsx │ │ ├── useClipImage.ts │ │ └── useFilter.ts │ ├── line/BaseLineElement.tsx │ ├── chart/ │ │ ├── BaseChartElement.tsx │ │ └── Chart.tsx │ ├── latex/BaseLatexElement.tsx │ ├── table/BaseTableElement.tsx │ ├── video/BaseVideoElement.tsx │ ├── code/BaseCodeElement.tsx │ └── shared/ │ ├── ElementOutline.tsx │ ├── useElementFill.ts │ ├── useElementOutline.ts │ ├── useElementShadow.ts │ └── useElementFlip.ts ├── hooks/ │ ├── useSlideBackgroundStyle.ts │ └── useViewportSize.ts ├── utils/ │ ├── cn.ts │ ├── geometry.ts │ └── element.ts └── types/ ├── slides.ts # 从 lib/types/slides 拷贝 ├── effects.ts # 新增 └── index.ts ``` ## 5. 依赖 **peerDependencies**: ```json { "react": ">=18", "react-dom": ">=18", "motion": ">=11", "tailwindcss": ">=4" } ``` **dependencies**(运行时必备): - `katex` — latex 渲染 - `tinycolor2` — 颜色工具 - `lucide-react` — 图标 - `clsx`、`tailwind-merge` — `cn` helper **chart 库**:复用主仓现用方案,T10 实现阶段确认。开发阶段先 inline 进 dependencies,若体积过大改 peerDep。 **ProseMirror 系列**:v1 完全不依赖。 ## 6. 构建 跟 `pptxtojson-pro` 完全一致: ```bash pnpm build # rollup -c && tsc --emitDeclarationOnly ``` 产物: ``` dist/ ├── index.{js,cjs,d.ts} ├── elements/index.{js,cjs,d.ts} └── types/index.{js,cjs,d.ts} ``` ## 7. 关键解耦点 ### 7.1 ScreenCanvas → SlideCanvas | 原 | 新 | |----|----| | `useCanvasStore.use.canvasScale()` | `props.scale ?? 1` | | `useSceneSelector(c => c.canvas.elements)` | `props.slide.elements` | | `useSceneSelector(c => c.canvas.background)` | `props.background ?? props.slide.background` | | `useCanvasStore.use.laserElementId/Options()` | `props.effects?.laser` | | `useCanvasStore.use.zoomTarget()` | `props.effects?.zoom` | | `findElementGeometry(...)` | 拷贝进 `utils/geometry.ts` | ### 7.2 ScreenElement → SlideElement `theme.fontColor` / `theme.fontName` 改成从 `props.slide.theme` 读取;`useSceneSelector` 移除。 ### 7.3 BaseImageElement 包内退化成纯 `` 版本(含 clip/filter/outline/shadow),所有 placeholder / retry / i18n 逻辑剥离。消费者通过 `renderImage` 注入业务版本。 ### 7.4 BaseVideoElement 同上,纯 `