Borurio Print Designer
这是一个 Vue 3 标签打印设计器基础项目,同时支持独立 Web/iframe 页面和 npm 组件包两种使用方式。
入口说明
src/main.ts:独立 Web/iframe 入口,创建 Vue 应用并挂载到#app。src/index.ts:npm 包入口,只导出组件和类型,不创建应用。src/PrintDesigner.vue:设计器根组件,持有模板、网格和缩放状态。
目录职责
src/components:可跨业务模块复用的基础组件,目前包含 Symbol 图标组件。src/core:与界面无关的领域类型。src/views/print-designer/components:打印设计器内部使用的界面组件。src/assets/iconfont:Iconfont 平台生成的本地 Symbol 脚本,不建议手动修改。
构建配置
vite.config.ts:生成可部署到服务器并通过 iframe 打开的dist。vite.lib.config.ts:从src/index.ts生成 npm 使用的dist-lib。tsconfig.app.json:检查 Web 应用源码,不输出文件。tsconfig.lib.json:为 npm 包生成 TypeScript 声明文件。tsconfig.node.json:检查 Vite 配置文件。
常用命令
pnpm dev:启动本地 Web 开发服务。pnpm build:web:构建 iframe/Web 版本并执行类型检查。pnpm build:lib:构建 npm 版本并生成类型声明。pnpm build:依次构建 npm 和 Web 两种版本。
npm 包使用
当前包名为 @borurio-print/print-designer。业务项目需要已安装下列 peer dependencies:
- Vue
>=3.2.45 <4.0.0
设计器使用内置原生 UI 控件和固定中文文案,不依赖业务系统的 Element Plus 或多语言配置。
npm install @borurio-print/print-designer
如果业务项目尚未安装 Vue,再按 npm 提示补充安装。 二维码、条形码和 PDF 生成库已包含在设计器产物中,业务项目无需另行安装。
在 Vue 页面中引入组件和独立样式文件:
<script setup lang="ts">
import { PrintDesigner } from '@borurio-print/print-designer'
import '@borurio-print/print-designer/style.css'
</script>
<template>
<div class="print-designer-page">
<PrintDesigner />
</div>
</template>
<style scoped>
.print-designer-page {
width: 100%;
height: 100vh;
}
</style>
设计器界面直接使用中文文案,业务应用无需注册 vue-i18n、合并语言包或传入语言代码。
本地打印控件连接与设置
PrintPlugin 提供通用打印机、纸张设置与本地 WebSocket 连接。它沿用现有 Electron
JSON 协议,独立使用时不需要挂载模板编辑器,也不依赖业务系统的路由、Store 或 Element Plus。
<script setup lang="ts">
import { ref } from 'vue'
import {
PrintPlugin,
type PrintPluginExpose,
type PrintResult,
} from '@borurio-print/print-designer'
import '@borurio-print/print-designer/style.css'
const pluginRef = ref<PrintPluginExpose | null>(null)
function handleResult(result: PrintResult): void {
console.log(result.requestId, result.result, result.message)
}
</script>
<template>
<button @click="pluginRef?.showEdit()">打印机设置</button>
<PrintPlugin
ref="pluginRef"
endpoint="ws://127.0.0.1:9274"
scene-key="inventorySku"
:auto-connect="true"
@print-result="handleResult"
/>
</template>
父组件配置:
| 属性 | 默认值 | 说明 |
|---|---|---|
endpoint | ws://127.0.0.1:9274 | 打印控件地址 |
sceneKey | default | 打印机和纸张配置的保存场景;兼容旧属性 printKey |
client | PC | 协议中的客户端标识 |
autoConnect | false | 挂载后自动连接;否则打开设置或调用 connectPrint() 时连接 |
connectTimeoutMs | 5000 | 连接及保存设置的超时时间 |
printTimeoutMs | 60000 | 等待打印结果的超时时间 |
maxPdfBytes | 67108864 | WebSocket 二进制 PDF 的最大字节数,默认 64 MB |
persistSettings | true | 向打印控件保存和读取场景设置 |
defaultPrinter | 空字符串 | 场景没有已保存打印机时使用,之后回退到系统默认打印机 |
defaultPaper | 空字符串 | 场景没有已保存纸张时使用 |
theme | 内置主题 | 主色配置,同时应用于设置弹窗和下拉浮层 |
暴露方法为 showEdit()、hideEdit()、connectPrint()、disconnect()、
refreshPrinters()、getPrintData()、setPrintUrl() 和 printPdf()。
连接成功表示 WebSocket 已打开;打印机和纸张列表随后异步返回。
对于控件能够访问的 HTTP/HTTPS PDF 地址,可使用:
const requestId = pluginRef.value!.setPrintUrl({
url: 'https://example.com/document.pdf',
requestId: crypto.randomUUID(),
key: 'printUrlList',
})
对于设计器生成的 PDF Blob,直接通过 WebSocket 二进制帧发送给本地打印控件, 不需要 Base64、Data URI 或上传服务器:
const requestId = crypto.randomUUID()
designerRef.value!.generatePdf({
requestId: `${requestId}:generate`,
data: printData,
returnBlob: true,
})
function handleGenerateResult(result: PrintResult): void {
if (result.requestId !== `${requestId}:generate`) return
if (result.result === 'failed' || !result.data?.blob) return
pluginRef.value!.printPdf({
requestId,
blob: result.data.blob,
copies: 1,
orientation: 'portrait',
})
}
二进制任务由打印控件写入临时 PDF 后交给系统打印机,结束后删除临时文件。
业务系统应使用同一个 requestId 关联提交和最终结果。
最终结果通过 print-result 返回 { requestId, result, message }。组件还提供
connection-change、settings-change,以及兼容原封装的 componentOp 原始消息事件。
新打印任务要求控件回传同一个 requestId,每个任务只通知一次终态;超时或断线意味着结果未知,
组件不会自动重新提交打印。卸载时关闭连接、清理监听和计时器。
浏览器的 blob: 地址不能由独立的 Electron 进程读取,因此 printPdf() 发送的是原始
PDF 二进制内容。URL 打印协议继续保留,用于打印控件能够直接访问的 HTTP/HTTPS 文件。
纯浏览器压缩面单
压缩面单不需要先上传服务器,也不依赖本地打印控件完成排版。业务系统把原始面单 PDF
地址和商品明细作为同一条业务记录传给 PrintTemplateRenderer,设计器会在浏览器内把
面单第一页渲染到首页模板的 PDF 区域,并将商品表格分页后生成一个新的 PDF。
在业务字段中注册面单 PDF:
import type { BusinessFieldGroup } from '@borurio-print/print-designer'
const fieldGroups: BusinessFieldGroup[] = [
{
label: '压缩面单',
fields: [
{
field: 'waybillPdfUrl',
label: '面单PDF',
type: 'pdf',
},
{
field: 'recordPage',
label: '单据页码',
type: 'pageNumber',
},
],
},
]
模板应使用“双模板”:
- 首页模板放置“面单PDF”字段和普通表格,PDF 元素的固定区域决定原面单压缩后的尺寸。
- 续页模板只放置表格等补充信息,可以给表格更大高度。
- 首页和续页的表格使用同一个数据字段。分页时会分别根据两个模板中表格的实际高度计算容量。
- 从业务字段添加的页码在没有显式页码字段值时,自动显示当前业务记录内部的
1/N;基础组件中的页码仍显示整个打印任务的全局页码。
调用时一条原始面单对应一条根记录:
const requestId = crypto.randomUUID()
rendererRef.value!.previewPdf({
requestId,
data: [
{
waybillPdfUrl: 'https://example.com/waybill-a.pdf',
skuList: [
{ sku: 'SKU-A', quantity: 2, location: 'A-01' },
{ sku: 'SKU-B', quantity: 1, location: 'A-02' },
],
},
{
waybillPdfUrl: 'https://example.com/waybill-b.pdf',
skuList: [
{ sku: 'SKU-C', quantity: 3, location: 'B-01' },
],
},
],
})
previewPdf() 会先在当前页面完成所有面单 PDF、二维码、条形码和表格的渲染,最终 Blob PDF
生成完成后才打开新的预览页面。每条根记录都会重新使用首页模板并独立计算页码,不会把多个
订单合并成同一个页码序列。浏览器可能拦截异步打开的新页面,业务系统需要允许当前站点打开
弹窗;被拦截时,组件会通过 print-result 返回失败结果。
npm 发布
包名和 npm registry 已在 package.json 中配置:
- 包名:
@borurio-print/print-designer - registry:
https://registry.npmjs.org/ - 公开范围:
public - 发布前钩子:
prepublishOnly会自动执行pnpm build:lib
1. 检查本地和线上版本
进入项目目录,确认当前工作区状态以及本地版本:
git status
node -p "require('./package.json').version"
查询 npm 当前已经发布的版本:
npm view @borurio-print/print-designer version --registry=https://registry.npmjs.org/
同一个版本不能重复发布。如果本地版本与线上版本相同,先升级版本号:
pnpm version patch --no-git-tag-version
根据实际变更范围,也可以把 patch 换成 minor 或 major。如果上一次发布只是因为
登录失效或权限不足而失败,并且线上还没有该版本,不要再次升级版本号。
2. 检查 npm 登录和发布权限
先检查当前 npm 登录状态:
npm whoami --registry=https://registry.npmjs.org/
如果返回 E401 Unauthorized,重新登录:
npm login --auth-type=web --registry=https://registry.npmjs.org/
登录成功后,再检查当前账号是否拥有该包的发布权限:
npm owner ls @borurio-print/print-designer --registry=https://registry.npmjs.org/
当前账号必须出现在 owner 列表中。如果不在,需要使用已有 owner 账号登录,或者由已有 owner 为当前账号授予权限。
3. 执行发布前构建和检查
安装锁文件对应的依赖,并完整构建 Web 与 npm 两种产物:
pnpm install --frozen-lockfile
pnpm build
检查 npm 包最终会包含哪些文件:
npm pack --dry-run
至少应确认以下文件存在:
dist-lib/index.jsdist-lib/index.d.tsdist-lib/style.cssdist-lib/PrintDesigner.vue.d.tsdist-lib/core/types.d.tsdist-lib/core/theme.d.tspackage.json
如果仓库跟踪 dist-lib,发布前应检查并提交最新构建产物。不要在存在来源不明的工作区
改动时直接执行批量提交。
4. 正式发布
确认版本、账号、权限和打包内容无误后执行:
npm publish --access public --registry=https://registry.npmjs.org/
发布时会再次触发 prepublishOnly,自动执行 pnpm build:lib。终端出现下面格式的输出才
表示上传成功:
+ @borurio-print/[email protected]
resolveComponent is imported ... but never used 和 chunk size 属于构建警告,不等于发布
失败;应以命令退出码和最后的 npm 发布结果为准。
5. 发布后验证
npm 新版本可能需要短暂处理时间。查询线上版本:
npm view @borurio-print/print-designer version --registry=https://registry.npmjs.org/
在临时目录或业务系统中安装明确版本进行验证:
npm install @borurio-print/[email protected]
确认线上版本和业务系统验证正常后,再按团队 Git 流程提交版本变更并创建对应版本标签。
常见发布错误
E401 Unauthorized
当前 npm 登录已失效。重新执行:
npm login --auth-type=web --registry=https://registry.npmjs.org/
npm whoami --registry=https://registry.npmjs.org/
scoped 包发布时报 E404 Not Found - PUT
对于已经存在的 scoped 包,这通常表示当前账号没有发布权限,或者认证信息已经失效,并不 代表包真的不存在。依次检查:
npm whoami --registry=https://registry.npmjs.org/
npm owner ls @borurio-print/print-designer --registry=https://registry.npmjs.org/
npm view @borurio-print/print-designer version --registry=https://registry.npmjs.org/
如果 npm whoami 返回 E401,重新登录后再发布。如果账号不在 owner 列表中,需要切换
到有权限的账号或申请权限。权限问题解决前不要继续递增版本号。
版本已经存在
npm 不允许覆盖已经发布的相同版本。确认线上已经存在当前版本后,执行:
pnpm version patch --no-git-tag-version
重新构建、检查并发布新版本。
自动生成文件
src/auto-imports.d.ts 和 src/components.d.ts 由自动按需引入插件生成;pnpm-lock.yaml 由 pnpm 生成。这些文件不应手动添加说明或修改内容。