Viewer
@easyink/viewer 是 EasyInk 的消费端运行时。它接收一份 Schema 和一份运行时数据,然后完成校验、绑定、布局、分页和 DOM 渲染。
如果 Designer 解决的是“怎么编辑模板”,Viewer 解决的就是“怎么把这份模板跑起来”。
最小用法
先看一段最小代码:
import { createViewer } from '@easyink/viewer'
const viewer = createViewer({ iframe: iframeElement })
await viewer.open({
schema: documentSchema,
data: {
title: 'Hello EasyInk',
},
})
viewer.destroy()这段代码做了三件事:
createViewer({ iframe })创建运行时,并把渲染目标放进 iframe。open({ schema, data })校验 Schema、归一化 Schema,然后渲染页面。destroy()清理当前 Host 挂载内容、注册表和字体缓存。
如果你只是想把模板预览出来,这就是最短路径。
创建 Viewer
createViewer() 接收一个 ViewerOptions 对象。
const iframeViewer = createViewer({ iframe: iframeElement })
const domViewer = createViewer({ container: containerElement })
const customViewer = createViewer({ host })常用选项是这几个:
| 选项 | 作用 |
|---|---|
iframe | 快捷创建 Iframe Host |
container | 快捷创建 Browser Host |
host | 传入你自己创建的 ViewerHost |
fontProvider | 提供字体目录和字体资源 |
iframe 和 container 都只是快捷写法。它们最终都会变成一个 ViewerHost,再交给运行时使用。
Host 的差异单独看 ViewerHost 模式。
打开文档
open() 的输入只有 schema + data。
await viewer.open({
schema,
data: {
customer: { name: 'Ada' },
items: [
{ name: 'Paper', qty: 2 },
{ name: 'Ink', qty: 1 },
],
},
onDiagnostic(event) {
console.warn(event.code, event.message)
},
})这段代码会把 data 存到 Viewer 运行时里。后续绑定解析会按节点上的 fieldPath,从这个 data 根对象取值。
Designer 里的 dataSources 不会传给 Viewer。它们是设计态字段树,不是运行时输入。
渲染流程
open() 在有 Host 时会自动调用 render()。
const result = await viewer.render()
console.log(result.pages)
console.log(result.thumbnails)
console.log(result.diagnostics)当前实现的渲染流程是:
校验 Schema
-> beforeSchemaNormalize hook
-> 归一化 Schema
-> 加载字体
-> 解析绑定
-> 测量需要运行时尺寸的物料
-> 布局和分页
-> 复制每页重复元素
-> 渲染页面 DOMrender() 返回 ViewerRenderResult:
interface ViewerRenderResult {
pages: ViewerPageResult[]
thumbnails: ThumbnailResult[]
diagnostics: ViewerDiagnosticEvent[]
}pages 里会带页面尺寸、元素数量和页面 DOM。thumbnails 是基于页面结果生成的 SVG data URL。
更新数据
模板不变、数据变了时,不需要重新创建 Viewer。
await viewer.updateData({
title: 'Updated Title',
})如果当前 Viewer 有 Host,而且已经打开过 Schema,updateData() 会直接触发一次重新渲染。
如果你想显式拿到渲染结果,也可以调用 render():
await viewer.updateData(nextData)
const result = await viewer.render()两种方式都能完成刷新。区别只是你要不要立刻消费 pages、thumbnails 和 diagnostics。
打印与导出入口
Viewer 提供打印和导出的运行时入口。
await viewer.print()
const blob = await viewer.exportDocument({
format: 'pdf',
entry: 'preview',
})这里有两个前提要分清:
print()不传driverId时走浏览器打印。exportDocument()需要先注册能处理对应format的导出器。
如果你只传字符串,exportDocument('pdf') 等价于指定 format: 'pdf',并使用默认入口 entry: 'api'。
完整用法继续看 打印与导出。
注册物料渲染器
Viewer 允许你为某个物料 type 注册运行时渲染器。
import { trustedViewerHtml } from '@easyink/core'
viewer.registerMaterial('my-widget', {
render(node, context) {
return {
html: trustedViewerHtml('<div>Custom Widget</div>'),
}
},
measure(node) {
return {
width: node.width,
height: node.height,
}
},
})render() 返回 html 或 element。返回 HTML 时,当前接口要求你用 trustedViewerHtml() 明确标记这段内容是可信 Viewer HTML。
measure() 是可选的。只有需要运行时测量尺寸的物料才需要实现它。
自定义物料的完整开发方式继续看 自定义物料开发。
注册打印驱动和导出器
打印驱动和导出器都是挂到当前 Viewer 实例上的。
viewer.registerPrintDriver({
id: 'thermal-printer',
async print(context) {
console.log(context.printPolicy)
console.log(context.renderedPages)
console.log(context.container)
},
})
viewer.registerExporter({
id: 'pdf-exporter',
format: 'pdf',
async export(context) {
console.log(context.renderedPages)
console.log(context.container)
return new Blob(['ok'], { type: 'application/pdf' })
},
})同一个 id 重复注册时,后注册的驱动或导出器会替换前一个。
如果你已经有本地打印服务、远程网关或导出链路,这两个接口就是接入点。协议细节继续看 打印与导出。
生命周期清理
Viewer 是有状态运行时。组件卸载或页面离开时,我们建议你显式销毁。
viewer.destroy()destroy() 会把实例标记为已销毁,清理当前 Schema、数据、物料注册表、打印驱动、导出器、字体缓存和 Host 挂载内容。
关于 Viewer,目前知道这些就够用了。接下来可以继续看: