Skip to content

对接文档 ​

本组件库面向 Vue 3,提供两个页面级组件(设计器页、预览页)和一组打印 API。对接方式为「预览页 + 设计器页」两段式。

两段式使用模式 ​

宿主系统(勾选数据 → 打印)
        │
        ▼
  PrintPreview(预览页)  ──配置模板──▶  PrintDesigner(设计器页)
   · 切换模板                             · 拖拽/缩放/改属性编排
   · 内嵌预览(模板 + 真实数据)           · 保存为不同模板
   · 打印
组件定位使用方
PrintPreview预览页:切换模板 + 内嵌预览 + 打印业务用户(操作员/文员)
PrintDesigner设计器页:可视化编排模板模板管理员

组件用法 ​

预览页 ​

vue
<template>
  <PrintPreview
    :template="currentTemplate"
    :templates="templateList"        <!-- 该单据类型下可切换的模板 -->
    :data="recordData"               <!-- 勾选单据的真实数据 -->
    @template-change="t => currentTemplate = t"
    @configure="openDesigner"        <!-- 跳到设计器页 -->
    @back="goBack"
    @print="onPrint"
  />
</template>

设计器页 ​

vue
<template>
  <PrintDesigner
    v-model="currentTemplate"
    :data="recordData"
    :data-fields="fields"
    @save="saveTemplate"
    @print="print"
  />
</template>

Props / Events ​

PrintDesigner ​

Props类型说明
modelValueTemplate模板对象(v-model 双向绑定)
dataRecord<string, unknown>单据数据,用于 {字段} 占位与数据表格
dataFieldsDataField[]可绑定字段清单(属性面板「插入字段」下拉)
readonlyboolean只读模式(隐藏编辑面板,仅预览)
Events说明
update:modelValue模板变更
print触发打印
save点击「保存模板」,回调携带模板 JSON

PrintPreview ​

Props类型说明
templateTemplate当前模板(单张模式)
templatesTemplate[]可切换的模板列表
dataRecord<string, unknown>单据真实数据(单张模式)
dataFieldsDataField[]可绑定字段清单(可选)
itemsBatchItem[]批量模式:多张单据 [{ template, data }]
Events说明
template-change切换模板,回调新模板
configure点击「配置模板」(宿主跳转到设计器页)
back点击「返回」
print点击「打印」(组件内部已调打印 API)

打印 API ​

ts
import { printTemplate, printBatch, buildPrintHtml, buildBatchPrintHtml } from 'm7_print_designer'

// 单张打印:生成 HTML 并唤起浏览器打印
await printTemplate(template, data)

// 批量打印:多张单据合并为一次打印任务(每张从新页开始,要求同纸张尺寸)
await printBatch([
  { template, data: data1 },
  { template, data: data2 },
])

// 只生成打印 HTML(不唤起打印,适合自定义打印逻辑 / 导出 PDF)
const { html, pages } = await buildPrintHtml(template, data)

授权与遥测 ​

ts
import { setupLicense } from 'm7_print_designer'

// 生产环境初始化时调用一次(开发/测试可完全不调用)
setupLicense({ code: 'M7PD1.xxxxxxxx.yyyyyyyy' })
配置项说明
code授权码。不配 = 未授权(打印附加水印),功能不受任何限制
report是否上报使用情况,默认 true
endpoint上报地址,一般无需修改
watermark未授权时的水印文案
env显式声明环境;不传则按域名自动判断(localhost / *.local / *.test 视为非生产)
  • 授权校验完全离线(内置公钥本地验签),不发网络请求 —— 断网、内网、无法连接公网时照常工作
  • 未授权只在打印与预览输出铺倾斜重复水印,不做功能限制;开发/测试环境(localhost、*.local、*.test)不加水印,不干扰集成调试
  • 默认开启使用情况上报:仅生产环境、每浏览器每月最多一次、未配置授权码也会上报;上报内容不含用户信息与业务数据
  • 关闭遥测:setupLicense({ report: false })(不影响授权校验与打印)

详见 授权与遥测。


数据绑定 ​

模板只存「绑定关系」({字段} 占位 + 数据表格 dataSource),不存数据本身。同一套模板 + 不同 data 即可打印不同单据。

核心概念(三个对象) ​

① 字段字典  DataField[]   —— 描述「有哪些字段可用」(来自后端表结构)
② 数据实例  DataRecord    —— 一张单据的字段值(键值对 + 列表)
③ 列表字段  (值为数组)    —— data 中值为数组的字段,供数据表格引用

宿主只需提供 ① + ②,可打包为一个 DataSource 对象。

如何传入数据 ​

vue
<PrintDesigner v-model="template" :data="data" :data-fields="fields" />

data 对应 DataRecord,data-fields 对应 DataField[]。推荐后端统一封装为 DataSource:

ts
import type { DataSource } from 'm7_print_designer'

const ds: DataSource = await api.getPrintData('workOrder', id)
// → { fields: [...], data: {...} }

<PrintDesigner v-model="template" :data="ds.data" :data-fields="ds.fields" />

传入数据的规范 ​

字段字典 DataField ​

ts
interface DataField {
  key: string        // 必填,唯一标识,点路径支持嵌套,如 'supplier.name'
  label: string      // 必填,显示名(字段选择器、表格列名默认值)
  type?: FieldType   // string | number | date | datetime | boolean | amount | percent | list
  format?: string    // 格式化串,如 date:'YYYY-MM-DD'、amount:'¥#,##0.00'
  group?: string     // 分组名(属性面板按组折叠展示)
  desc?: string      // 字段说明
}

字段字典应由后端从真实数据表动态生成,前端只消费、不硬编码。推荐后端提供统一接口:

GET /api/print/fields?docType=workOrder
→ { fields: [ { key:'workOrderNo', label:'工单号', type:'string', group:'基础信息' }, ... ] }

key 取表字段名(camelCase),label 取字段中文注释。key 是稳定契约,对外后勿随意改动。

数据实例 DataRecord ​

ts
type DataRecord = Record<string, unknown>

约束:

  • 扁平键值为主,嵌套用点路径:{ supplier: { name, contact } } 对应 {supplier.name}。
  • 列表字段的值是数组:items: [...]、processes: [...],供数据表格 dataSource 引用。
  • 值为 null / undefined 时渲染为空串。

命名约定 ​

约定示例
字段 key 用 camelCaseworkOrderNo、supplier、deliveryDate
列表字段用复数items、processes
金额 / 合计amount、total、totalAmount
日期 / 时间xxxDate、xxxTime
编号 / 编码xxxNo、xxxCode

类型与格式化 ​

推荐策略:宿主预格式化——数据实例中直接存放「显示字符串」,设计器原样输出:

类型约定示例(宿主产出)
amount 金额货币符号 + 千分位 + 两位小数¥ 86,540.00
number 数量按业务精度(常 0 或 2 位)500 / 0.02
date 日期YYYY-MM-DD2026-08-29
datetime 时间YYYY-MM-DD HH:mm2026-08-29 08:00
percent 百分比xx%98.5%
boolean 布尔是 / 否是

口径责任在供数侧,保证「同一字段、不同单据」展示一致。

字段如何对齐(绑定语法) ​

  • 文本 / 二维码 / 条码内容用 {字段} 占位,如 工单号:{workOrderNo}。
  • 支持点路径:{supplier.name}。
  • 一个内容可含多个占位:{startDate} ~ {endDate}。
  • 属性面板「插入字段」下拉由 dataFields 字典驱动,选中即插入对应 {字段}。
  • 图片 src 若整串是单个 {字段},取原始值:值为数组时渲染多图并排({imgs} → N 张等宽并排)。

数据表格对齐 ​

dataTable 元素通过 dataSource 指向 data 中的数组字段:

ts
// 元素配置
{ type: 'dataTable', dataSource: 'processes', columns: [
  { field: 'name', title: '工序', width: 60, align: 'left' },
  { field: 'hours', title: '工时', width: 40, align: 'center' },
]}

// 对应数据
{ processes: [ { name: '铣面', hours: 2 }, { name: '钻孔', hours: 3 } ] }
  • 行 = 数组元素(扁平对象),列 field 用点路径取行内值。
  • 列 width 为相对比例:按列宽总和归一化为百分比,各列始终填满表格宽度。
  • 按 rowHeight 计算每页行数并自动分页(单主表分页)。

表头/表尾附加行 ​

数据表格可配置自定义附加行,用于「表格标题」「合计行」「签字/备注行」等:

  • headerRows(表头附加行):仅首页显示,不随分页重复。
  • footerRows(表尾附加行):仅末页显示,不随分页重复。
  • 每行的单元格复用 TableCell:支持合并单元格(rowSpan / colSpan)、{字段} 绑定、对齐、加粗、背景色。
ts
{
  type: 'dataTable',
  dataSource: 'items',
  columns: [
    { field: 'name', title: '名称', width: 50, align: 'left' },
    { field: 'amount', title: '金额', width: 50, align: 'right' },
  ],
  // 表头附加行(仅首页):标题横跨两列
  headerRows: [{ cells: [
    { text: '采购明细表', colSpan: 2, align: 'center', bold: true },
    { text: '', colSpan: 0 },   // 被合并覆盖的单元格 colSpan=0
  ]}],
  // 表尾附加行(仅末页):合计横跨两列,绑定 {total}
  footerRows: [{ cells: [
    { text: '合计:{total}', colSpan: 2, align: 'right', bold: true },
    { text: '', colSpan: 0 },
  ]}],
}

属性面板数据表格区块提供「表头附加行 / 表尾附加行」编辑:+ 添加行、删除行、单元格文本(支持 {字段})、合并右侧列数、对齐、加粗。

本页合计 / 总计合计 ​

数据表格支持对数值列自动求和,分两种合计行:

  • showPageTotal(本页合计):每页底部显示一行,对本页数据求和。
  • showGrandTotal(总计合计):末页底部显示一行,对全部数据求和。
  • 列 sum: true 标记该列参与求和;合计行中「本页合计 / 总计」标签显示在第一列,sum 列显示求和值,其余列留空。
ts
{
  type: 'dataTable',
  dataSource: 'items',
  showPageTotal: true,   // 每页底部合计
  showGrandTotal: true,  // 末页底部总计
  columns: [
    { field: 'name', title: '名称', width: 60, align: 'left' },
    { field: 'qty', title: '数量', width: 20, align: 'right', sum: true },
    { field: 'amount', title: '金额', width: 20, align: 'right', sum: true },
  ],
}

求和列应存放数值(或可转数值的字符串);属性面板在列编辑区提供「计」复选框,数据表格区块提供「本页合计 / 总计」开关。

多级嵌套数据 ​

后端数据常有层级结构(如工单详情内含 报工记录、程序单 等多个数组)。组件通过点路径支持任意层级:

  • 嵌套对象:{supplier.name} 取 data.supplier.name。
  • 嵌套数组:dataSource: 'mold.programs' 取 data.mold.programs(对象内的数组)。
  • 设计器选层:属性面板「数据源」下拉列出字段字典中 type: 'list' 的字段,选中即绑定对应层(如「报工记录」「程序单」),无需手写路径。

字段字典中,数组字段应标记 type: 'list',并用 children 描述其元素字段(供数据表格「列字段」下拉);嵌套数组用点路径 key:

ts
const fields: DataField[] = [
  { key: 'workOrderNo', label: '工单号' },
  { key: 'reports', label: '报工记录', type: 'list', children: [
    { key: 'reportTime', label: '报工时间' },
    { key: 'worker', label: '报工人' },
    { key: 'quantity', label: '数量' },
  ]},
  { key: 'mold.programs', label: '程序单', type: 'list', children: [
    { key: 'programNo', label: '程序号' },
    { key: 'step', label: '工序' },
  ]},
]

选层 → 选列,全程免手写 key:选中数据源后,数据表格的「列字段」下拉由该列表字段的 children 驱动;选中列字段后若「列名」为空会自动带出字段 label。

系统占位符(保留字) ​

占位符含义说明
{pageNo}当前页码仅打印时注入,画布预览为空
{pageCount}总页数同上

pageNo / pageCount 为保留字,业务字段避免使用同名 key(打印时会被页码覆盖)。

分页范围 pageScope ​

分页时元素默认每页重复;设置 pageScope: 'last'(属性面板「仅末页」)的元素只在最后一页出现(合计行、签字/盖章区常用)。

完整示例:加工单 ​

ts
// 字段字典
const fields: DataField[] = [
  { key: 'workOrderNo', label: '工单号', group: '基础信息' },
  { key: 'partName', label: '零件名称', group: '基础信息' },
  { key: 'quantity', label: '数量', type: 'number' },
  { key: 'startDate', label: '开工日期', type: 'date' },
  { key: 'totalPrice', label: '加工总价', type: 'amount' },
  { key: 'processes', label: '工序明细', type: 'list' },
]

// 数据实例
const data = {
  workOrderNo: 'GJ-20260829-001',
  partName: '汽车支架左件',
  quantity: '500',
  startDate: '2026-08-29',
  totalPrice: '¥ 12,600.00',       // 宿主预格式化
  processes: [
    { name: '铣面', content: '精铣定位面', hours: '2' },
    { name: '钻孔', content: '钻 M6 沉孔 ×4', hours: '3' },
  ],
}

// 模板绑定
// 标题文本  content = "工单号:{workOrderNo}"
// 二维码    value = "{workOrderNo}"
// 数据表格  dataSource = "processes",列 name / content / hours

模板管理 ​

模板是纯 JSON,@save 回调把模板 JSON 交给宿主,由宿主负责持久化。一个单据类型可以有多个模板(例如加工单配了「标准版」「精简版」两个样式),宿主系统需要能知道并管理这些模板。

模板结构 ​

ts
interface Template {
  id: string                 // 模板唯一标识
  name: string               // 模板名(如「加工单·标准版」)
  version: string            // 版本号
  paper: PaperConfig         // 纸张尺寸 / 方向 / 页边距
  elements: PrintElement[]   // 元素列表(mm 坐标 + 各自属性)
  dataFields?: DataField[]   // 该模板绑定的字段字典快照
}

一个单据类型 → 多个模板 ​

宿主的「模板库」按单据类型(docType)分组管理:

ts
interface TemplateMeta {
  template: Template
  docType: string       // 单据类型:workOrder / purchaseOrder / ...
  isDefault: boolean    // 是否默认模板
  createdAt: string
}
模板库
├── workOrder(加工单)
│   ├── 加工单·标准版(默认)
│   └── 加工单·精简版
├── purchaseOrder(采购订单)
│   ├── 采购订单·标准版(默认)
│   └── 采购订单·精简版
└── ...

后端接口(建议) ​

GET  /api/print/templates?docType=workOrder   → TemplateMeta[]   // 查某类型的模板列表
POST /api/print/templates                      → TemplateMeta     // 新增模板
PUT  /api/print/templates/:id                  → TemplateMeta     // 更新模板
DEL  /api/print/templates/:id                  → void             // 删除模板

前端对接流程 ​

ts
import { PrintPreview, PrintDesigner, type Template } from 'm7_print_designer'

// ① 加载某单据类型的模板列表(宿主知道加工单有 2 个模板)
const templateList = await api.listTemplates('workOrder')

// ② 打印时选默认模板渲染预览
const currentTemplate = templateList.find(t => t.isDefault)?.template ?? templateList[0].template

// ③ 预览页「切换模板」:在 2 个模板之间切换
function onTemplateChange(t: Template) {
  currentTemplate.value = t
}

// ④ 预览页「配置模板」→ 设计器页编辑 → @save 保存
async function onSave(template: Template) {
  const saved = await api.saveTemplate({ template, docType: 'workOrder' })
  templateList.push(saved)          // 现在加工单有 3 个模板
  currentTemplate.value = saved.template
}

保存为新模板 vs 更新现有模板 ​

场景做法
保存为新模板生成新 id,POST 新增(保留原模板,形成多样式)
更新现有模板沿用原 id,PUT 覆盖(影响所有引用该模板的单据)
设默认模板更新 isDefault 标记,同类型仅一个默认

建议:设计器「保存模板」默认走「保存为新模板」(生成新 id),另提供「覆盖保存」入口更新原模板,避免误改历史模板。

字段契约与版本演进 ​

  • 模板保存时记录 dataFields 快照,字段 key 是稳定契约。
  • 新增字段:向前兼容,旧模板不受影响。
  • 重命名字段:需迁移已存模板中的 {旧字段} 占位与列 field。
  • 删除字段:绑定该字段的元素渲染为空,建议后端保留字段或提供别名。

真实对接案例:MES 加工单 ​

以某 MES 系统的加工任务详情接口为例(moldCode / partCode / basicAffairName / assignedEquipmentName / processTaskStatus / isOutSource 等扁平 camelCase 字段),说明如何适配本组件。

结论:完全可适配。组件绑定「扁平 key + {字段} 占位」,MES 的 camelCase 字段可直接绑定;宿主只需加一层「适配器」完成字段字典 + 预格式化。

字段分类 ​

类别MES 字段处理方式
直接透传moldCode moldName partCode partName basicAffairName source reason remarks原样传给 data,{字段} 直接绑定
枚举映射processTaskStatus(0 / 1 / 3)宿主映射为中文状态(待加工 / 加工中 / 已完成)
布尔映射isOutSource isMaterialArrived映射为「外协 / 厂内」「已到料 / 未到料」
时间格式化arrangeStartTime startTime materialArrivedTime(ISO 8601)格式化为 YYYY-MM-DD HH:mm
数组处理tags(字符串数组)images(图片数组)tags join 为字符串;images 用图片元素 {images} 多图并排
忽略id moldId partId 等 UUID、concurrencyStamp打印用 Name/Code 字段,ID 不打印

适配器示例 ​

ts
import type { DataField } from 'm7_print_designer'

const STATUS_MAP: Record<number, string> = { 0: '待加工', 1: '加工中', 2: '暂停', 3: '已完成' } // 以 MES 实际枚举为准

function fmtDateTime(iso: string | null) {
  return iso ? iso.replace('T', ' ').slice(0, 16) : ''
}

function adaptWorkOrder(detail: any) {
  return {
    // 直接透传
    moldCode: detail.moldCode,
    moldName: detail.moldName,
    partCode: detail.partCode,
    partName: detail.partName,
    basicAffairName: detail.basicAffairName,
    assignedEquipmentName: detail.assignedEquipmentName || detail.assignedEquipmentCode,
    supplierName: detail.supplierName || '',
    source: detail.source || '',
    reason: detail.reason || '',
    remarks: detail.remarks || '',
    // 枚举 / 布尔映射
    processTaskStatus: STATUS_MAP[detail.processTaskStatus] ?? '',
    isOutSource: detail.isOutSource ? '外协' : '厂内',
    isMaterialArrived: detail.isMaterialArrived ? '已到料' : '未到料',
    // 时间格式化
    arrangeStartTime: fmtDateTime(detail.arrangeStartTime),
    arrangeEndTime: fmtDateTime(detail.arrangeEndTime),
    startTime: fmtDateTime(detail.startTime),
    endTime: fmtDateTime(detail.endTime),
    materialArrivedTime: fmtDateTime(detail.materialArrivedTime),
    // 数量
    estimatedQuantity: detail.estimatedQuantity ?? '',
    actualQuantity: detail.actualQuantity ?? '',
    // 数组
    tags: (detail.tags || []).join('、'),
    images: detail.images || [],
  }
}

// 字段字典(后端从表结构 + 中文注释生成,见「传入数据的规范」)
const fields: DataField[] = [
  { key: 'moldCode', label: '模具编号', group: '模具信息' },
  { key: 'moldName', label: '模具名称', group: '模具信息' },
  { key: 'partCode', label: '零件编号', group: '零件信息' },
  { key: 'partName', label: '零件名称', group: '零件信息' },
  { key: 'basicAffairName', label: '工序', group: '加工信息' },
  { key: 'assignedEquipmentName', label: '设备', group: '加工信息' },
  { key: 'arrangeStartTime', label: '计划开始', type: 'datetime' },
  { key: 'arrangeEndTime', label: '计划结束', type: 'datetime' },
  { key: 'estimatedQuantity', label: '计划数量', type: 'number' },
  { key: 'actualQuantity', label: '实际数量', type: 'number' },
  { key: 'processTaskStatus', label: '状态' },
  { key: 'isOutSource', label: '是否外协' },
  { key: 'supplierName', label: '供应商' },
  { key: 'isMaterialArrived', label: '是否到料' },
  { key: 'materialArrivedTime', label: '到料时间', type: 'datetime' },
]

// 用法
const detail = await api.getWorkOrderDetail(id)
const data = adaptWorkOrder(detail)
// <PrintDesigner v-model="template" :data="data" :data-fields="fields" />

两点注意 ​

  1. 明细表数据来源:上述接口是「工序级任务」的扁平结构,没有嵌套的工序明细数组。若打印「工序流转卡」需要列出一张模具某零件的全部工序,宿主需按 moldId + partId 聚合多个任务组装成数组(数据表格 dataSource 指向该数组),这是数据组装问题、非组件限制。
  2. 枚举口径以 MES 为准:processTaskStatus 的取值映射、isOutSource 的语义请在宿主侧确认,避免状态/外协口径错误。

完整可运行示例见仓库 src/App.vue(单据列表 → 预览页 → 设计器页 → 保存模板 → 批量打印),或直接体验本页 Demo 示例。