Schema 参考
Schema 是 EasyInk 的模板模型。Designer、Viewer、打印和导出最终都围绕同一份 Schema 工作。
先看一个最小输入:
import { normalizeDocumentSchema, validateSchemaIssues } from '@easyink/schema'
const schema = normalizeDocumentSchema({
page: { width: 80 },
elements: [],
})
const issues = validateSchemaIssues(schema)归一化后,运行时拿到的是完整 DocumentSchema。宿主可以传宽松输入,但内部消费前应该先归一化。
输入和内部模型
宿主通常传 DocumentSchemaInput:
const input = {
page: {
mode: 'continuous',
width: 80,
height: 200,
},
}
const schema = normalizeDocumentSchema(input)DocumentSchemaInput 允许省略很多顶层字段。normalizeDocumentSchema() 会补齐:
versionunitpageguideselements- 页面模式相关的
pageModel、layout、pagination、reflow
注意
version 不属于宿主可指定输入。归一化会写入当前 Schema 版本。
页面模式默认值
page.mode 会决定一组页面层默认配置:
const fixed = normalizeDocumentSchema({
page: { mode: 'fixed', width: 210, height: 297 },
})
const continuous = normalizeDocumentSchema({
page: { mode: 'continuous', width: 80, height: 200 },
})当前默认值是:
mode | pageModel | layout | pagination | reflow |
|---|---|---|---|---|
fixed | paged-paper | absolute | fixed-sheets | measure-only |
continuous | continuous-paper | stack-flow | none | flow-y |
这就是为什么连续纸不能只改宽高。它还会切换布局、分页和回流策略。
页面级渲染层
page.layers 保存整页级、非元素级的渲染层。它不是物料节点,也不会出现在 schema.elements 里。当前内置支持文字水印层:
{
"page": {
"mode": "fixed",
"width": 210,
"height": 297,
"layers": [
{
"id": "page-watermark",
"kind": "watermark",
"type": "text",
"enabled": true,
"placement": "over-content",
"zIndex": 0,
"text": "DRAFT",
"rotation": -30,
"opacity": 0.1,
"fontSize": 18,
"gap": 60,
"color": "#b8b8b8"
}
]
}
}字段规则:
placement可选under-content、over-content、top,分别位于内容层下方、内容层上方和最上层。zIndex只在所属placementband 内排序,合法范围是0..999。fontSize和gap跟随schema.unit。opacity是0..1。
page.layers 适合文字水印这类整页装饰。如果你需要可选中、可拖拽、可绑定数据或可作为空白页保留条件的页眉、页脚、Logo、页码,请使用普通物料节点,并在需要每页出现时设置 node.repeat.scope='every-output-page'。
校验和序列化
本地保存、导入导出和服务端接收模板时,优先用 schema 包里的入口:
import {
deserializeSchema,
isValidSchema,
serializeSchema,
validateSchemaIssues,
} from '@easyink/schema'
const json = serializeSchema(schema)
const restored = deserializeSchema(json)
if (!isValidSchema(restored)) {
console.warn(validateSchemaIssues(restored))
}常用能力包括:
validateSchema():返回字符串错误数组。validateSchemaIssues():返回结构化问题,包含path、message和code。isValidSchema():类型守卫。serializeSchema()/deserializeSchema():处理持久化 JSON。
外部输入解码
外部输入不要在业务层手写字段映射。先交给 codec:
import { decodeBenchmarkInput } from '@easyink/schema'
const schema = decodeBenchmarkInput(input)Schema codec 里包含 benchmark 输入的解码逻辑。普通 EasyInk JSON 仍然用 deserializeSchema(json)。
元素节点
最常见的元素节点长这样:
{
"id": "text-1",
"type": "text",
"x": 20,
"y": 20,
"width": 170,
"height": 10,
"props": {
"content": "Hello EasyInk",
"fontSize": 24,
"fontFamily": "sans-serif"
}
}type 决定用哪个物料渲染,x/y/width/height 决定画布几何,props 保存物料自己的属性。
支持条件能力的物料还可以在节点根部保存 renderCondition。它会影响布局和分页,因此不属于物料私有 props。完整规则和运行语义见 条件渲染。
自定义物料也走同一套结构:
const node = {
id: 'price-tag-1',
type: 'price-tag',
x: 20,
y: 20,
width: 48,
height: 18,
props: {
label: '价格',
amount: '¥ 99.00',
},
}只要 Designer 和 Viewer 都注册了同一个 type,这份节点就能被两边识别。
数据绑定字段
元素可以保存普通绑定引用。普通绑定适合 text / image / barcode / qrcode 这类“一个字段投影到一个 props”的物料:
{
"id": "total-text",
"type": "text",
"x": 20,
"y": 20,
"width": 60,
"height": 10,
"props": { "content": "" },
"binding": {
"sourceId": "invoice",
"fieldPath": "summary/total",
"fieldLabel": "合计"
}
}Designer 负责把绑定写进节点。Viewer 在 open({ schema, data }) 时解析绑定,然后把结果交给物料渲染器。
结构化物料可以使用 data-contract binding。它不是普通绑定的替代品,而是给 chart-bar 这类物料描述目标数据模型和 source 映射:
{
"id": "sales-chart",
"type": "chart-bar",
"x": 20,
"y": 40,
"width": 120,
"height": 70,
"props": {
"barColor": "#2563eb",
"backgroundColor": "#ffffff"
},
"binding": {
"kind": "data-contract",
"mappings": {
"category": {
"sourceId": "report",
"select": { "path": "monthlySales/month", "label": "月份" }
},
"value": {
"sourceId": "report",
"select": { "path": "monthlySales/revenue", "label": "销售额" }
}
},
"relation": { "kind": "auto" }
}
}mappings 的 key 是物料目标字段 id,不是源数据字段名。select.path 保存完整 source path;Resolver 在运行时推导共享集合或 index 对齐,不把 mode 写入 binding。
绑定格式配置保存在 BindingRef.format 或 DataContractFieldMapping.format。属性面板可编辑哪些格式 tab 不写入 Schema,而由物料注册时的 binding.formatEditor 声明;例如文本开放 preset/custom,自定义 SVG 与 chart 类物料通常只开放 custom。
当前 MaterialNode.binding 的规范形态是:
type MaterialBinding = BindingRef | BindingRef[] | DataContractBinding
interface DataContractBinding {
kind: 'data-contract'
mappings: Record<string, DataContractFieldMapping>
relation?: { kind: 'auto' } | { kind: 'record' } | { kind: 'index' }
}普通文本类旧 schema 仍然有效;只有声明了 binding.kind='data-contract' 的物料才会使用 DataContractBinding。
表格节点
表格节点在 MaterialNode 外多了 table 属性:
interface TableNode extends MaterialNode {
type: 'table-static' | 'table-data'
table: TableSchema
}
interface TableSchema {
kind: 'static' | 'data'
topology: TableTopologySchema
layout: TableLayoutConfig
diagnostics?: LayoutDiagnostic[]
}单元格绑定保存在 cell 上:
interface TableCellSchema {
rowSpan?: number
colSpan?: number
content?: {
text?: string
elements?: MaterialNode[]
editMode?: 'inline-text' | 'rich-text' | 'hosted'
}
props?: Record<string, unknown>
binding?: BindingRef
staticBinding?: BindingRef
}table-data 通常用 binding 表示数据行字段。table-static 可以用 staticBinding 表示独立单元格绑定。
动画字段
动画挂在元素的可选字段里:
interface AnimationSchema {
trigger: string
type: string
duration?: number
delay?: number
options?: Record<string, unknown>
}目前先把它理解成“元素上的播放配置”就够了。不同运行时可以按自己的能力消费这份配置。
工具函数
常用工具可以按用途记:
import {
createDefaultGuides,
createDefaultPage,
createDefaultSchema,
getNodeProps,
isCompatibleVersion,
isTableDataNode,
isTableNode,
normalizeDocumentSchema,
validateSchemaIssues,
} from '@easyink/schema'它们大致分三类:
- 默认值:
createDefaultSchema()、createDefaultPage()、createDefaultGuides()。 - 类型和访问:
isTableNode()、isTableDataNode()、getNodeProps<T>()。 - 校验和归一化:
validateSchemaIssues()、normalizeDocumentSchema()。
完整示例
一份固定 A4 模板可以这样写:
{
"version": "1.0.0",
"unit": "mm",
"page": {
"mode": "fixed",
"width": 210,
"height": 297,
"pageModel": {
"kind": "paged-paper",
"paper": { "width": 210, "height": 297 }
},
"layout": { "strategy": "absolute" },
"pagination": { "strategy": "fixed-sheets" },
"reflow": { "strategy": "measure-only" }
},
"guides": { "x": [], "y": [] },
"elements": [
{
"id": "text-1",
"type": "text",
"x": 20,
"y": 20,
"width": 170,
"height": 10,
"props": {
"content": "Hello EasyInk",
"fontSize": 24,
"fontFamily": "sans-serif"
}
}
]
}如果你手写的是宽松输入,不必把所有默认层都写出来。交给 normalizeDocumentSchema() 补齐即可。
关于 Schema,目前知道这些就够用了。自定义物料如何保存自己的节点,可以继续看 自定义物料开发。