Files
workspace/code/one-designer/Web Report Designer 初期架构设计方案 V1.md
T
2026-09-20 17:34:40 +08:00

916 lines
26 KiB
Markdown
Raw 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.
# Web Report Designer 初期架构设计方案 V1
> 文档状态:初期架构设计
>
> 本版本目标是先建立稳定的文档模型、布局管线和编辑器边界,不在第一阶段一次性实现所有复杂报表能力。
## 1. 项目定位
项目暂定名称:
~~~text
Web Report Designer
~~~
包名暂定:
~~~text
@xxx/report-core
@xxx/report-expression
@xxx/report-layout
@xxx/report-renderer
@xxx/report-designer
@xxx/report-designer-vue
~~~
项目定位:
> 一个面向业务系统的、纯 Web 的、可嵌入、可扩展、插件式的报表设计与渲染平台。
重点解决:
- 固定版式业务报表
- 打印单据
- A4/A5/自定义纸张
- 主表 + 子表
- 动态明细
- 多页打印
- 页眉页脚
- 基础分组和合计
- 数据绑定
- 受限表达式
- HTML、SVG 和浏览器打印
- 可扩展的 PDF 输出接口
明确不包含:
~~~text
BI
Dashboard
OLAP
数据分析
数据立方体
指标体系
大屏
~~~
### 1.1 V1 初期范围
V1 优先保证以下能力可用:
1. 文本、图片、线条、矩形等固定元素。
2. A4、A5 和自定义纸张。
3. 基础数据绑定和明细表。
4. 页眉、页脚和基础分页。
5. HTML、SVG 预览以及浏览器打印。
6. 文档序列化、校验和版本迁移。
7. 可注册的元素插件接口。
以下能力先预留模型和接口,放到后续迭代:
- 行列合并、跨页合计的分页级精细控制。
- 图表、条码、二维码等插件实现。
- 服务端 PDF 的具体实现。
- 协同编辑、权限体系和 BI 能力。
> 实现进度补充:基础分组(group-header / group-footer、多级分组、组内聚合)
> 已提前完成,详见 §16 阶段 3。
## 2. 总体架构
整体采用 Monorepo。
~~~text
report-designer/
│
├── apps/
│ └── report-designer/
│
├── packages/
│ ├── report-core/
│ ├── report-expression/
│ ├── report-layout/
│ ├── report-renderer/
│ │ └── src/
│ │ ├── html/
│ │ ├── svg/
│ │ └── pdf/
│ ├── report-designer/
│ ├── report-designer-vue/
│ └── plugins/
│ ├── qrcode/
│ ├── barcode/
│ ├── chart/
│ └── ...
│
├── examples/
├── pnpm-workspace.yaml
├── package.json
└── README.md
~~~
### 2.1 依赖方向
~~~text
report-expression ──> report-core
report-layout ──> report-core + report-expression
report-renderer ──> report-core + report-layout
report-designer ──> report-core + report-layout
report-designer-vue ──> report-designer
~~~
上图箭头表示“依赖”。Core 不依赖上述任何实现包。
Renderer 只消费布局结果,不重新计算分页。插件实现可以依赖 Core、Layout、Renderer 或 Designer 的适配器,但 Core 不反向依赖任何具体插件。
~~~text
plugin-qrcode
├── core element contract
├── layout adapter
├── renderer adapter
└── designer adapter(可选)
~~~
### 2.2 运行时主流程
~~~text
ReportDocument
│
▼
数据提供与参数注入
│
▼
表达式解析与绑定求值
│
▼
LayoutEngine(测量、展开、换行、分页)
│
▼
LayoutDocument(统一的分页结果)
│
├── HTML Renderer
├── SVG Renderer
└── PDF Renderer
~~~
## 3. 核心架构原则
### 3.1 Core 与 UI 完全分离
report-core:
~~~text
纯 TypeScript
无 Vue
无 React
无 DOM
无浏览器 API
无 UI 框架
~~~
Core 可以在 Browser、Node.js、Electron、Server 和 CLI 中复用。
### 3.2 文档模型与运行时状态分离
ReportDocument 只保存可序列化的报表定义,不保存以下编辑器状态:
- 当前选中项。
- 缩放比例和画布滚动位置。
- 辅助线、吸附线和临时选区。
- 撤销栈和 redo 栈。
- 已加载的数据和网络连接。
### 3.3 Layout 与 Renderer 分离
Layout 负责:
~~~text
尺寸计算
文本测量
换行
表格展开
动态高度
分页
~~~
Renderer 负责:
~~~text
把 LayoutDocument 绘制成 HTML、SVG 或 PDF
~~~
这样可以保证不同输出目标使用同一套分页结果。
### 3.4 运行时数据由外部注入
Core 不负责 fetch、数据库连接或 Token 管理。数据源定义和运行时数据提供器分开,避免把敏感配置写入报表文件。
### 3.5 文档必须可校验、可迁移
所有文档都必须带 schemaVersion,反序列化时先校验,再按版本逐步迁移。文档 JSON 中不能保存函数、DOM 节点或运行时对象。
## 4. 包职责
### 4.1 report-core
- 报表文档模型。
- 页面、区域、元素和样式模型。
- 数据源、数据集、参数契约。
- 插件契约和注册表。
- 文档创建、克隆、规范化。
- 序列化、反序列化、校验和迁移。
### 4.2 report-expression
- 表达式词法和语法解析。
- AST 和受限求值器。
- 字段、参数、行、分组和页面作用域。
- 表达式错误和诊断信息。
禁止通过 eval 或 new Function 执行任意 JavaScript。
### 4.3 report-layout
- 元素尺寸测量。
- 文本换行和动态高度。
- 明细数据展开。
- 分页和分页诊断。
- 生成统一的 LayoutDocument。
Layout 通过 TextMeasurer、FontResolver 等接口获得环境能力,不直接依赖 DOM。
### 4.4 report-renderer
- 消费 LayoutDocument。
- 提供 HTML、SVG 和 PDF Renderer。
- 处理目标格式相关的颜色、字体和资源输出。
PDF 的具体实现可以运行在浏览器或 Node/服务端,但必须遵循统一的 Renderer 接口。
### 4.5 report-designer
- 画布和坐标转换。
- 选择、拖拽、缩放、对齐和吸附。
- 属性编辑。
- 复制粘贴。
- 命令、事务和撤销重做。
- 快捷键和编辑器状态。
Designer 通过 Core 的命令接口修改文档,不直接实现报表业务规则。
### 4.6 report-designer-vue
只负责 Vue 组件、面板和 UI 适配,不向 Core 泄漏 Vue 类型。
## 5. report-core 目录
~~~text
packages/report-core/
│
├── src/
│ ├── model/
│ │ ├── report.ts
│ │ ├── page.ts
│ │ ├── section.ts
│ │ ├── element.ts
│ │ ├── table.ts
│ │ ├── style.ts
│ │ ├── dataset.ts
│ │ ├── datasource.ts
│ │ ├── parameter.ts
│ │ └── common.ts
│ │
│ ├── plugin/
│ │ ├── plugin.ts
│ │ ├── context.ts
│ │ ├── registry.ts
│ │ ├── element-plugin.ts
│ │ ├── datasource-plugin.ts
│ │ └── exporter-plugin.ts
│ │
│ ├── document/
│ │ ├── create.ts
│ │ ├── clone.ts
│ │ └── normalize.ts
│ │
│ ├── serialization/
│ │ ├── serialize.ts
│ │ ├── deserialize.ts
│ │ └── migrate.ts
│ │
│ ├── schema/
│ │ ├── version.ts
│ │ ├── defaults.ts
│ │ └── validation.ts
│ │
│ ├── diagnostics/
│ │ └── diagnostic.ts
│ ├── utils/
│ │ ├── id.ts
│ │ └── deep.ts
│ └── index.ts
│
└── tests/
~~~
## 6. Report 文档模型
报表采用“文档 + 区域 + 元素树”模型,不把所有元素平铺在根节点上。
~~~ts
export interface ReportDocument {
schemaVersion: number
id: string
name: string
page: PageSettings
styles: Record<string, StyleDefinition>
dataSources: Record<string, DataSourceDefinition>
datasets: Record<string, DatasetDefinition>
parameters: Record<string, ReportParameter>
sections: ReportSection[]
plugins?: ReportPluginReference[]
metadata?: ReportMetadata
}
~~~
### 6.1 报表区域
~~~ts
export type ReportSectionKind =
| "report-header"
| "page-header"
| "detail"
| "group-header"
| "group-footer"
| "summary"
| "page-footer"
export interface ReportSection {
id: string
kind: ReportSectionKind
datasetId?: string
children: ReportElement[]
options?: {
repeatOnEachPage?: boolean
keepTogether?: boolean
allowSplit?: boolean
}
}
~~~
V1 先实现 page-header、detail 和 page-footer,其他区域只保留模型扩展点。
> 实现进度:group-header / group-footer 已可用(含多级分组与组内聚合)。
> report-header 与 summary 仍只保留模型扩展点。
>
> 分组区域通过 `groupExpression` 指定分组键,`groupLevel` 指定嵌套级别:
>
> ~~~ts
> export interface ReportSection {
> // ...原有字段
> /** 分组键表达式,仅 group-header / group-footer 使用。 */
> groupExpression?: string
> /** 分组级别,从 1 开始;数值小的为外层组。 */
> groupLevel?: number
> }
> ~~~
>
> 分组语义为「**相邻行键值相同即同组**」,不改变行顺序——
> 因此宿主应先按分组键排序再传入数据。组脚可通过 `group` 作用域访问
> 聚合能力,如 `sum(group.amount)`、`group.count`、`group.key`。
### 6.2 元素树
~~~ts
export interface ReportElement {
id: string
type: string
frame: Rect
styleId?: string
binding?: ValueBinding
props?: Record<string, unknown>
children?: ReportElement[]
}
export interface Rect {
x: number
y: number
width: number
height: number
}
export interface ValueBinding {
field?: string
expression?: string
format?: string
}
~~~
内置元素初期包括:
~~~text
text
image
line
rect
container
table
~~~
插件元素使用自定义 type,并通过插件 Schema 验证 props。
### 6.3 明细表
明细表是 V1 的特殊元素,负责把数据集展开成多行。单元格内的元素坐标相对于当前单元格。
~~~ts
export interface TableElement extends ReportElement {
type: "table"
datasetId: string
columns: TableColumn[]
repeatHeaderOnEachPage?: boolean
}
export interface TableColumn {
id: string
width: number
header?: string
binding?: ValueBinding
children?: ReportElement[]
}
~~~
### 6.4 样式
样式通过 ID 复用,并允许有限的继承和元素级覆盖:
~~~ts
export interface StyleDefinition {
id: string
extends?: string
fontFamily?: string
fontSize?: number
color?: string
background?: string
border?: BorderStyle
align?: "left" | "center" | "right"
verticalAlign?: "top" | "middle" | "bottom"
}
~~~
## 7. Page 模型
V1 文档内部统一使用 mm,px 只用于 Designer 显示层,pt 只在 PDF 等输出边界转换。
~~~ts
export interface PageSettings {
paper: "A4" | "A5" | "custom"
orientation: "portrait" | "landscape"
customSize?: {
width: number
height: number
}
margin: PageMargin
background?: string
}
export interface PageMargin {
top: number
right: number
bottom: number
left: number
}
~~~
paper 和 orientation 是页面配置来源;Layout 阶段将其解析为最终的 width、height。自定义纸张必须提供尺寸,尺寸均以 mm 表示。
## 8. 数据和运行时上下文
报表文档只保存数据源定义,不保存连接、Token 和本次查询结果。
~~~ts
export interface DataSourceDefinition {
id: string
type: string
config?: Record<string, unknown>
connectionRef?: string
}
export interface DatasetDefinition {
id: string
sourceId?: string
fields?: DatasetField[]
query?: string
}
export interface RenderContext {
data: Record<string, unknown>
parameters: Record<string, unknown>
locale?: string
timezone?: string
}
export interface DataProvider {
getDataset(
dataset: DatasetDefinition,
context: RenderContext
): Promise<ReadonlyArray<Record<string, unknown>>>
}
~~~
DataProvider 由宿主应用注入,Core 不主动访问网络或数据库。
config 只保存非敏感的设计配置;连接密码、Token 等敏感信息必须通过 connectionRef 或宿主运行时注入。
## 9. Layout 中间结果
Layout Engine 接受报表文档和运行时上下文,输出所有 Renderer 共用的分页结果。
~~~ts
export interface LayoutOptions {
dataProvider?: DataProvider
textMeasurer: TextMeasurer
fontResolver: FontResolver
}
export interface LayoutEngine {
layout(
document: ReportDocument,
context: RenderContext,
options?: LayoutOptions
): Promise<LayoutDocument>
}
export interface LayoutDocument {
pages: LayoutPage[]
diagnostics: LayoutDiagnostic[]
}
export interface LayoutPage {
width: number
height: number
nodes: LayoutNode[]
}
export interface LayoutNode {
id: string
type: string
frame: Rect
style: ResolvedStyle
content?: unknown
children?: LayoutNode[]
}
~~~
Layout 必须支持以下接口:
~~~ts
export interface TextMeasurer {
measure(text: string, style: ResolvedStyle): TextMetrics
}
export interface FontResolver {
resolve(fontFamily: string): ResolvedFont
}
~~~
这样可以分别适配浏览器、Node 和服务端字体环境,同时让分页逻辑保持独立。
## 10. 表达式约定
表达式系统使用受限语法和 AST,不执行任意 JavaScript。
V1 至少支持:
~~~text
字段:row.amount
参数:params.customerName
简单运算:row.price * row.quantity
条件:if(row.amount > 0, "有", "无")
格式化:format(row.date, "YYYY-MM-DD")
~~~
表达式失败时返回带有元素 ID 和表达式内容的诊断信息,单个字段错误不应导致整个文档无结果。
## 11. 插件模型
插件只通过契约接入,不允许 Core 直接导入插件实现。
~~~ts
export interface ReportPlugin {
id: string
version: string
elements?: ElementPlugin[]
datasources?: DataSourcePlugin[]
exporters?: ExporterPlugin[]
}
export interface ElementPlugin {
type: string
schema: unknown
createDefault(): ReportElement
}
~~~
需要针对不同运行环境提供可选适配器:
~~~text
ElementPlugin
├── LayoutAdapter
├── RendererAdapter(html/svg/pdf)
└── DesignerAdapter
~~~
插件引用只保存 id、version 和配置。插件缺失时应产生诊断信息,而不是阻止整个文档被读取。
## 12. 序列化、校验和迁移
- schemaVersion 使用单调递增整数。
- 反序列化顺序为:解析 JSON → 校验结构 → 执行迁移 → 规范化默认值。
- 迁移函数按版本逐步执行,不允许跨版本直接修改内部对象。
- 序列化结果保持稳定排序,便于版本控制和差异比较。
- 校验错误必须指出字段路径和元素 ID。
示例:
~~~text
deserialize(json)
→ validate(json)
→ migrate(json, currentVersion)
→ normalize(document)
~~~
## 13. 诊断和错误处理
统一使用诊断对象表达警告和错误:
~~~ts
export interface Diagnostic {
severity: "info" | "warning" | "error"
code: string
message: string
path?: string
elementId?: string
}
~~~
数据缺失、表达式错误、字体缺失、插件缺失和分页失败都应尽量返回可定位的诊断信息。
## 14. UI 规范
### 14.1 设计目标
Designer 的界面采用现代、简洁、干净的工具型产品风格,参考 shadcn/ui 的设计语言,但不要求 Core 或业务应用必须绑定某一个 UI 框架。
核心原则:
- 画布优先,减少非必要装饰。
- 使用中性背景、细边框和低强度阴影建立层次。
- 通过间距、字号和颜色层级表达信息,不依赖渐变和大面积高亮。
- 常用操作保持短路径,复杂配置分组收起。
- 所有颜色、间距、圆角和阴影使用设计令牌,禁止组件内散落硬编码。
### 14.2 UI 层边界
- report-core 不包含任何 UI、CSS 或主题代码。
- report-designer 负责编辑器状态、命令和交互行为。
- report-designer-vue 负责 Vue 组件、面板和样式实现。
- 组件采用 Headless 思路,宿主应用可以覆盖主题和品牌色。
- 可以使用 shadcn-vue 或同类组件实现,但必须通过 report-designer-vue 隔离依赖。
### 14.3 整体布局
桌面端采用三栏、画布居中的工作区:
~~~text
┌──────────────────────────────────────────────────────────────┐
│ 顶部工具栏:文件、撤销、重做、预览、保存、缩放、主题 │
├──────────────┬───────────────────────────────┬───────────────┤
│ 左侧工具箱 │ │ 右侧属性面板 │
│ 元素 / 数据 │ 报表画布 │ 属性 / 样式 │
│ 模板 / 图层 │ │ 数据绑定 │
├──────────────┴───────────────────────────────┴───────────────┤
│ 底部状态栏:页码、缩放、单位、布局诊断、快捷键提示 │
└──────────────────────────────────────────────────────────────┘
~~~
建议尺寸:
- 左侧工具箱:240px 左右,可折叠。
- 右侧属性面板:320px 左右,可折叠。
- 顶部工具栏:48px。
- 底部状态栏:28px。
- 中间画布区域自适应,并支持拖拽调整两侧面板宽度。
屏幕宽度小于 900px 时,侧栏改为抽屉或浮层;移动端优先保证预览和基础属性编辑,不强行复刻完整桌面编辑器。
### 14.4 设计令牌
使用 CSS 自定义属性作为主题契约,至少提供亮色和暗色两套主题:
~~~css
:root {
--rd-background: 0 0% 100%;
--rd-foreground: 222 47% 11%;
--rd-muted: 210 40% 96%;
--rd-muted-foreground: 215 16% 47%;
--rd-border: 214 32% 91%;
--rd-input: 214 32% 91%;
--rd-primary: 221 83% 53%;
--rd-primary-foreground: 210 40% 98%;
--rd-destructive: 0 72% 51%;
--rd-canvas: 220 14% 96%;
--rd-selection: 221 83% 53%;
--rd-radius: 8px;
}
~~~
具体色值可以由宿主应用覆盖,但必须保持以下语义:
- background:应用和面板背景。
- canvas:画布工作区背景。
- foreground:主要文字和图标。
- muted:次级区域和禁用背景。
- border:分隔线和输入框边框。
- primary:主操作、选中态和焦点环。
- destructive:删除和不可逆操作。
- selection:画布元素选中框和辅助线。
推荐使用 OKLCH 或 HSL 变量,不在组件中直接写十六进制颜色。
### 14.5 字体和间距
~~~css
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI",
"PingFang SC", "Microsoft YaHei", sans-serif;
~~~
推荐字号:12px(辅助信息)、13px(表单和工具栏)、14px(正文)、16px(小标题)、20px(页面标题)。
间距使用 4px 基准:4、8、12、16、20、24、32px。默认圆角 6~8px,面板阴影保持低强度,避免卡片堆叠感。
### 14.6 基础组件风格
基础组件遵循扁平、克制、可聚焦的风格:
- Button:Primary、Secondary、Ghost、Destructive、Icon 五类。
- Input、Textarea、Select、Combobox:1px 边框,聚焦时显示明显但克制的 focus ring。
- Tabs、Segmented Control:用于属性面板和视图切换。
- Popover、Tooltip、Context Menu、Command Menu:用于低频和快捷操作。
- Dialog、Toast、Alert:用于确认、反馈和错误提示。
- Resizable Panel:用于左右侧栏和属性面板。
按钮不使用过度圆润的胶囊形,图标按钮必须提供 Tooltip 和可访问名称。删除等危险操作不能只依赖颜色区分。
### 14.7 画布规范
- 工作区使用浅灰或深灰中性背景,页面使用白色或主题纸张颜色。
- 页面保持轻微阴影和清晰边界,不使用强烈投影。
- 元素 Hover 使用低透明度背景,Selected 使用 1px 主色边框和 8 个缩放控制点。
- 对齐线、吸附线和标尺使用辅助色,不遮挡元素内容。
- 支持 50%、75%、100%、125%、150%、200% 常用缩放。
- 画布操作同时支持鼠标、键盘和上下文菜单,不把拖拽作为唯一入口。
- 选中态、错误态和辅助线不能只通过颜色表达,必要时增加边框、图标或文字提示。
### 14.8 属性面板规范
- 按“布局、内容、样式、数据绑定、高级”分组。
- 分组默认折叠低频配置,保留最近使用状态。
- 数值输入显示单位,支持键盘微调和批量修改。
- 绑定字段提供字段选择器,同时允许直接输入表达式。
- 无选中元素时显示页面属性;多选时只显示共同属性。
- 表单错误就地显示,并关联到对应字段,不使用只在底部出现的泛化错误。
### 14.9 交互和状态
顶部工具栏至少提供:
- 新建、打开、保存。
- 撤销、重做。
- 预览、打印。
- 缩放和适应页面。
- 主题切换和帮助入口。
推荐快捷键:Ctrl/Cmd + S 保存、Ctrl/Cmd + Z 撤销、Ctrl/Cmd + Shift + Z 重做、Delete 删除、Ctrl/Cmd + C/V 复制粘贴、Ctrl/Cmd + K 打开命令菜单。
所有异步操作都要有 Loading、Empty、Error 和 Disabled 状态;保存成功、布局警告和表达式错误使用统一 Toast 或诊断面板反馈。
### 14.10 主题、密度和无障碍
- 默认提供 Light 和 Dark 两套主题。
- 支持 Standard 和 Compact 两种密度,默认 Standard。
- 颜色对比度目标达到 WCAG AA,正文至少 4.5:1。
- 所有交互控件支持键盘访问,焦点状态必须可见。
- 弹窗、抽屉、菜单和属性面板提供正确的 ARIA 语义。
- 点击目标不小于 32px,主要触控目标建议不小于 36px。
- 尊重 prefers-reduced-motion,避免非必要动画。
### 14.11 UI 验收标准
初期 UI 至少应满足:
1. 用户可以在不阅读文档的情况下找到新增元素、保存、预览和撤销操作。
2. 画布、工具箱和属性面板在亮色和暗色主题下都能正常使用。
3. 选中、禁用、错误、加载和空状态具有一致的视觉语言。
4. 关键操作可以只使用键盘完成。
5. 主题颜色可以由宿主应用通过 CSS 变量覆盖。
## 15. 测试策略
初期至少建立以下测试:
- √ 文档 Schema、默认值和版本迁移测试。
- √ 固定元素和明细表的布局快照测试。
- √ 页眉、页脚和多页分页测试。
- HTML、SVG、PDF Renderer 的公共布局结果一致性测试。
- √ 表达式安全性、空值和类型转换测试。
- √ 中文字体、长文本和自定义纸张测试。
- √ 插件缺失和插件版本不兼容测试。
建议准备一个固定的示例报表作为跨包契约测试数据。
> 当前测试规模:`report-core` 139 例、`report-expression` 111 例、
> `report-layout` 124 例、`report-renderer` 12 例、`report-designer` 14 例、
> `report-designer-vue` 11 例,合计 **411** 例。
> 字体相关用例依赖系统字体,在缺少字体的环境中自动跳过。
> HTML/SVG 分页一致性测试已随 Renderer 实现补齐。
## 16. 初期实施顺序
> 进度标记说明:`√` 表示已实现并有测试覆盖;`◐` 表示部分实现;未标记表示尚未开始。
>
> 当前进度:阶段 1、2、3 已完成;阶段 4 主体完成(Renderer、Designer 核心、
> Vue UI 骨架),Vue 层的图层面板、数据绑定面板、快捷键留待后续迭代。
### √ 阶段 1:Core 和文档模型
- √ 完成 ReportDocument、Section、Element、Page、Style。
- √ 完成默认值、校验、序列化和迁移。
- √ 完成基础命令和 ID 生成。
产物:`packages/report-core`(139 个测试用例)。
### √ 阶段 2:基础 Layout
- √ 完成单页固定元素布局。
- √ 完成文本测量、换行和基础分页。
- √ 输出统一的 LayoutDocument。
产物:`packages/report-layout`(含自研 TTF/TTC 字体度量解析)。
### √ 阶段 3:数据和明细表
- √ 接入 RenderContext 和 DataProvider。
- √ 支持字段绑定、简单表达式和明细表展开。
- √ 支持页眉、页脚重复。
产物:`packages/report-expression`(83+28 个测试用例)、`packages/report-layout` 的
数据绑定与分组能力。
补充实现(超出本阶段原始列项):
- √ 受限表达式系统(字段、参数、运算、条件、格式化)。
- √ 聚合函数(sum / avg / count / min / max)。
- √ 基础分组(group-header / group-footer、多级分组、组内聚合)。
- √ 明细表表头每页重复。
### ◐ 阶段 4:Renderer 和 Designer
- √ 实现 SVG、HTML 和浏览器打印(`@report/renderer`,含 HTML/SVG 分页一致性测试)。
- √ 实现 PDF Renderer 契约(可注入 `PdfBackend`,缺失/报错走诊断)。
- √ 完成 Designer 的选择、拖拽、属性编辑和撤销重做(`@report/designer`,纯逻辑层)。
- √ Vue 层 UI 骨架(`@report/designer-vue`,原生 Vue3 + Vite 库模式,无第三方组件库):
三栏布局壳、画布(渲染/选中/新增)、工具栏(撤销重做/缩放)、属性面板(基础编辑)、
状态栏、设计令牌(亮/暗主题 CSS 变量)。
- 待后续迭代:图层面板、数据绑定面板、拖拽移动、快捷键、预览接入 Renderer。
### 阶段 5:插件和 PDF
- 实现二维码、条码等独立插件。
- 根据部署环境确定 PDF 的具体后端。
- 补充字体、资源和输出一致性测试。
## 17. V1 验收标准
一个示例报表应当能够:
1. 在 Designer 中创建并保存。
2. 绑定一组运行时数据。
3. 生成包含页眉、明细和页脚的多页结果。
4. 在 HTML 和 SVG 中得到相同的分页结构。
5. 通过浏览器打印输出。
6. 在升级 Schema 后仍可被迁移和读取。
达到以上标准后,再继续扩展复杂分组、图表、PDF 和更多插件。