hprint-designer
v0.5.0
Published
Vue 2 print-template designer wrapping vue-plugin-hiprint — scenic tickets & invitations
Readme
hprint-designer
基于 vue-plugin-hiprint 封装的 Vue 2 打印模板设计器 + 打印运行时,专注于纸质景区票、邀请函等票据/卡片类模板。
两个入口
| 入口 | 用途 | 依赖 |
|------|------|------|
| hprint-designer | 设计器 Vue 组件 — 设计模板、保存 JSON | Vue 2 |
| hprint-designer/runtime | 打印运行时 — 静默打印、批量打印、预览 | 无 Vue 依赖 |
设计器只负责模板编辑,打印/预览/回执等全部通过 runtime 函数完成。两者通过模板 JSON 解耦:设计器产出 JSON,runtime 消费 JSON。
特性
- 🎨 开箱即用:
<print-designer>组件即完整设计器界面 - 🚀 Runtime 独立使用:
hprint-designer/runtime无需加载设计器即可打印/预览 - 🧾 逐票回执:
batchPrintSilent+onTicket回调,chunk 自选,支持落库与补打 - 🧩 字段 Schema:模板字段以 Schema 声明,方便对接业务系统
- 🔤 自定义字体:字体上传后自动注入
@font-face - 🔌 后端适配器:
RestAdapter/MockAdapter,也可自定义BackendAdapter - 🎯 Vue 2 兼容:peer dependency
vue@^2.6.0
安装
npm install hprint-designerPeer dependencies:
npm install vue@^2.6.0 ant-design-vue@^1.7.2设计器内部使用 Bootstrap 3 的 glyphicons。请在
index.html中引入:<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css" />
设计器
注册组件
// main.js
import Vue from 'vue'
import { HprintPlugin } from 'hprint-designer'
import 'hprint-designer/dist/style.css'
Vue.use(HprintPlugin)
new Vue({ render: h => h(App) }).$mount('#app')使用设计器
<template>
<div style="height: 100vh;">
<print-designer
ref="designer"
:template-id="templateId"
:adapter="adapter"
@save="onSave"
/>
</div>
</template>
<script>
import { RestAdapter, extractFields } from 'hprint-designer'
export default {
data() {
return {
templateId: '',
adapter: new RestAdapter({ baseURL: 'https://your-server/api' })
}
},
methods: {
onSave(template) {
const fields = extractFields(template)
console.log('模板已保存,字段:', fields)
}
}
}
</script>Props
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| templateId | string | '' | 编辑已有模板时传入 ID;空串新建 |
| adapter | BackendAdapter | null | 后端适配器 |
| readonly | boolean | false | 只读模式 |
| lang | string | 'zh' | 语言 |
Events
| 事件 | 参数 | 说明 |
|------|------|------|
| save | PrintTemplate | 模板保存完成 |
| data-change | { type, json } | 模板数据变化 |
Methods
| 方法 | 签名 | 说明 |
|------|------|------|
| getTemplateJson | () => PrintTemplate | 获取当前模板 JSON |
| loadTemplate | (json: PrintTemplate) => void | 加载模板 JSON 到设计器 |
Runtime
从 hprint-designer/runtime 导入,无需加载设计器组件即可使用打印、预览等功能。
函数
| 函数 | 说明 | 返回值 |
|------|------|--------|
| previewFromTemplate(options) | 从模板生成预览 HTML | string |
| silentPrint(options) | 单次静默打印 | Promise<{ success, templateId }> |
| batchPrintSilent(options) | 批量静默打印 + 逐票回执 | Promise<BatchPrintCompleteSummary> |
| retryFailed(options) | 重打失败票 | Promise<BatchPrintCompleteSummary> |
| browserPrint(options) | 浏览器打印(弹出对话框) | void |
| extractFields(template) | 提取模板字段列表 | Array<{ key, label, type }> |
辅助:createPrintConnection({ host, token? })、waitForPrint2(tplId, timeoutMs?),以及所有批量打印类型。
完整示例
import {
batchPrintSilent,
retryFailed,
browserPrint,
extractFields,
createPrintConnection
} from 'hprint-designer/runtime'
// 1. 创建连接
const connect = createPrintConnection({
host: 'http://localhost:17521',
token: 'hprint-designer'
})
// 2. 从后端获取模板
const template = await api.getTemplate('ticket-001')
// 3. 提取字段(可选)
const fields = extractFields(template)
// 4. 批量静默打印
const result = await batchPrintSilent({
template,
dataList: orders,
pageSize: { widthMm: 80, heightMm: 120 },
connect,
chunkSize: 10,
ticketKeyField: 'orderNo',
onTicket: (r) => api.post('/print-receipts', r)
})
// 5. 重打失败票
if (result.failedTicketKeys.length > 0) {
await retryFailed({
template,
failedReceipts: result.receipts.filter(r => r.status === 'failed' || r.status === 'timeout'),
pageSize: { widthMm: 80, heightMm: 120 },
connect
})
}
// 6. 浏览器打印(无需客户端)
browserPrint({ template, dataList: orders, pageSize: { widthMm: 80, heightMm: 120 } })函数签名
silentPrint(options)
| 选项 | 类型 | 说明 |
|------|------|------|
| template | PrintTemplate \| object | 模板 JSON |
| data | Record<string, any> | 打印数据 |
| connect | { host: string, token?: string } | 客户端连接配置 |
| printer | string? | 打印机名称,空=默认 |
| pageSize | { widthMm, heightMm } | 纸张尺寸(必传) |
| fonts | PrintFont[]? | 自定义字体 |
| hiprint | any? | hiprint 实例 |
| connection | PrintConnection? | 复用已有连接 |
batchPrintSilent(options)
| 选项 | 类型 | 说明 |
|------|------|------|
| template | PrintTemplate \| object | 模板 JSON |
| dataList | Record<string, any>[] | 打印数据数组 |
| pageSize | { widthMm, heightMm } | 纸张尺寸(必传) |
| connect | { host: string, token?: string } | 客户端连接配置 |
| printer | string? | 打印机名称 |
| chunkSize | number? | 每作业票数,默认 20 |
| ticketKeyField | string? | 业务主键字段,默认 orderNo |
| stopOnError | boolean? | 失败后是否中止 |
| includeTicketData | boolean? | 回执是否携带完整数据 |
| onTicket | (receipt) => void \| Promise<void> | 逐票回调 |
| onProgress | (report) => void | 进度回调 |
| fonts | PrintFont[]? | 自定义字体 |
| hiprint | any? | hiprint 实例 |
| connection | PrintConnection? | 复用已有连接 |
retryFailed(options)
| 选项 | 类型 | 说明 |
|------|------|------|
| template | PrintTemplate \| object | 模板 JSON |
| failedReceipts | TicketPrintReceipt[] | 失败回执列表(需含 ticketData) |
| pageSize | { widthMm, heightMm } | 纸张尺寸(必传) |
| connect | { host: string, token?: string } | 客户端连接配置 |
| printer | string? | 打印机名称 |
| onTicket | (receipt) => void \| Promise<void>? | 逐票回调 |
browserPrint(options)
| 选项 | 类型 | 说明 |
|------|------|------|
| template | PrintTemplate \| object | 模板 JSON |
| dataList | Record<string, any>[] | 打印数据数组 |
| pageSize | { widthMm, heightMm } | 纸张尺寸(必传) |
| fonts | PrintFont[]? | 自定义字体 |
| hiprint | any? | hiprint 实例 |
previewFromTemplate(options)
| 选项 | 类型 | 说明 |
|------|------|------|
| template | PrintTemplate \| object | 模板 JSON |
| data | Record<string, any>? | 打印数据 |
| pageSize | { widthMm, heightMm } | 纸张尺寸(必传) |
| fonts | PrintFont[]? | 自定义字体 |
| hiprint | any? | hiprint 实例 |
前提:页面已引入
hiprint.bundle.js(<script src="/vendor/hiprint/hiprint.bundle.js">),或通过hiprint参数显式传入。
逐票回执与 chunk
业务系统通过 onTicket 回调实时落库,无需 CSV 导出。
| chunk | 200 票作业数 | 精度 | 场景 |
|-------|------------|------|------|
| 1 | 200 | 每张独立 ACK | 热敏机、强对账 |
| 10 | 20 | 10 张同批联动 | 平衡 |
| 20 | 10 | 较粗 | 办公机 / PDF 虚拟 |
优先级:batchPrintSilent({ chunkSize }) 参数 > 默认值 20
const result = await batchPrintSilent({
template,
dataList: orders,
pageSize: { widthMm: 80, heightMm: 120 },
connect,
chunkSize: 10,
ticketKeyField: 'orderNo',
onTicket: (r) => api.post('/print-receipts', r)
})完整调用实例见 docs/examples/batch-print-demo.ts。
设计说明见 docs/hiprint/13-批量打印逐票回执设计.md。
第三方接入与库表设计见 docs/hiprint/14-第三方接入与数据库设计.md。
前置条件
electron-hiprint 客户端
静默打印需要 electron-hiprint 客户端。本仓库在 electron-hiprint/ 提供了含热敏机大批量优化补丁的客户端源码,必须使用此修改版(官方 releases 缺少 waitPrinterIdle、HTML 按页拆分、clearPrintDomAfterJob 等优化,批量打印逐票回执等功能将无法正常工作)。
本地调试与打包(内置客户端):
npm run client:install
npm run client:start
# 打包 Windows x64 安装包(输出到 electron-hiprint/out/)
npm run client:build客户端设置 → 打印优化 页签可配置「等待打印机空闲」「HTML 拆页上限」等;热敏机可点「应用一票一作业预设」。
print-lock.css
index.html 中引入 print-lock.css(必须,否则静默打印输出空白):
<link rel="stylesheet" type="text/css" media="print" href="/print-lock.css" />使用 Vite 时推荐
?url导入自动生成正确的 href。本库HprintPlugin.install()会自动调用injectPrintLockCss(),通常无需手动添加。
生产环境无需手动复制文件:injectPrintLockCss() 内置 Blob URL 回退——当 /print-lock.css 不可达时自动用内联 CSS 创建 Blob URL,hiprint XHR 仍可正常获取。如需指定自定义地址(如 CDN),可通过 printLockCssUrl 选项:
// Vue 插件方式
Vue.use(HprintPlugin, { printLockCssUrl: '/cdn/print-lock.css' })
// 编程式调用
const designer = new HPrintDesigner({ printLockCssUrl: '/cdn/print-lock.css' })后端适配器
RestAdapter
import { HprintPlugin, RestAdapter } from 'hprint-designer'
import 'hprint-designer/dist/style.css'
Vue.use(HprintPlugin, { adapter: new RestAdapter({ baseURL: 'https://your-server/api' }) })MockAdapter(零后端)
import { HprintPlugin, MockAdapter } from 'hprint-designer'
import 'hprint-designer/dist/style.css'
Vue.use(HprintPlugin, { adapter: new MockAdapter() })自定义 BackendAdapter
import type { BackendAdapter } from 'hprint-designer'
class MyAdapter implements BackendAdapter {
async listTemplates(filter?) { /* ... */ }
async getTemplate(id) { /* ... */ }
async createTemplate(input) { /* ... */ }
async updateTemplate(id, patch, ifVersion?) { /* ... */ }
async deleteTemplate(id) { /* ... */ }
async listFonts() { /* ... */ }
async uploadFont(file, meta) { /* ... */ }
async updateFont(id, patch) { /* ... */ }
async deleteFont(id) { /* ... */ }
async getClientRelease() { /* ... */ }
// 可选:逐票回执自动落库
async savePrintReceipt?(receipt) { /* POST 业务 API */ }
async getPrintReceipts?(sessionId) { /* GET 按 session 查询 */ }
}REST 路由映射
| 资源 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 模板 | GET | /templates | 列表,支持 filter |
| 模板 | GET | /templates/:id | 详情 |
| 模板 | POST | /templates | 创建 |
| 模板 | PUT | /templates/:id | 更新(If-Match 乐观锁) |
| 模板 | DELETE | /templates/:id | 删除 |
| 字体 | GET | /fonts | 字体列表 |
| 字体 | POST | /fonts | 上传(multipart) |
| 字体 | PUT | /fonts/:id | 修改 |
| 字体 | DELETE | /fonts/:id | 删除 |
| 客户端 | GET | /client-release | 下载信息 |
| 回执 | POST | /print-receipts | 保存回执 |
| 回执 | GET | /print-receipts?sessionId= | 按会话查询 |
常见问题
页面没有样式 / 布局错乱
import 'hprint-designer/dist/style.css'ant-design-vue 样式由设计器自动注入,无需手动引入。
左侧拖拽图标显示为方框或文字
设计器使用 Bootstrap 3 的 glyphicons。在 index.html 中添加:
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css" />静默打印无响应
- 确认已安装并运行 electron-hiprint 客户端(必须使用本仓库内置修改版)
- 确认
createPrintConnection中的host/token与客户端配置一致
静默打印出来是空白页
hiprint 的 print2() 通过 XHR 请求 link[media=print][href*="print-lock"] 的 CSS。请求失败则打印空白。
index.html中必须引入print-lock.css,且<link media="print">的href必须包含print-lockhref必须是可被 XHR 请求的真实 HTTP 地址,不能用 data URI/print-lock.css必须返回Content-Type: text/css。Vite 推荐用?url导入- 本库已自动处理注入(
HprintPlugin.install()调用injectPrintLockCss()),通常无需手动添加 - 如果
/print-lock.css不可达,库会自动回退到 Blob URL,无需手动复制文件;也可通过printLockCssUrl选项指定自定义地址
静默打印纸张尺寸不对
所有 runtime 函数的 pageSize 参数为必传,需根据模板纸张尺寸显式指定:
// 从模板 JSON 中获取纸张尺寸
const pageSize = template.paper
? { widthMm: template.paper.widthMm, heightMm: template.paper.heightMm }
: { widthMm: 210, heightMm: 297 } // fallback A4
await batchPrintSilent({ template, dataList, pageSize, /* ... */ })如需直接调用 hiprint 的 print2:
// 标准纸张
template.print2(data, { pageSize: 'A4' })
// 自定义纸张(80mm × 200mm)
template.print2(data, {
pageSize: { width: 80000, height: 200000 }, // 单位 microns
margins: { marginType: 'none' },
printBackground: true,
silent: true
})浏览器打印多出一页 / 出现页码
本库通过 styleHandler 注入 @page { margin: 0 } 和 .hiprint-paperNumber { display: none }。如仍出现,检查:
styleHandler返回值必须用<style>标签包裹- 宿主项目
index.html中添加@media print样式隐藏非打印区域
开发
npm install
npm run dev # 本地 demo(含回执调试面板)
npm run client:start # 可选:启动内置 electron-hiprint
npm run build # 构建库产物(designer + runtime 双 bundle)
npm run typecheck # TypeScript 类型检查
npm test # 运行测试本地调试示例:dev/batch-print-demo.ts、dev/App.vue
协议
MIT. 本库依赖 vue-plugin-hiprint(hiprint 2.5.4,LGPL)。 本库以类库引用方式使用 hiprint,未修改其源码,符合 LGPL 类库引用豁免。 详见 hiprint 的 LICENSE。
