基于Vue3编写一个打印模板设计器(print-canvas-designer)

 更新时间:2026年05月30日 09:57:47   作者:吃乔巴的糖  
在业务系统里,标签打印存在一个常见弊端,它往往不是一次性的输出功能,而是一套需要持续维护的编辑能力,本文将用 Vue3 做一个可扩展的打印模板设计器,感兴趣的小伙伴可以了解下

在业务系统里,标签打印存在一个常见弊端:它往往不是一次性的输出功能,而是一套需要持续维护的编辑能力。

例如:鞋盒标可能需要展示款号、颜色、尺码、品牌图片、条形码和二维码;不同客户的布局不同,同一个客户后续也可能调整模板。仅靠开发人员在页面里写死位置,不但迭代慢,而且每一次排版变化都要重新发版。

我希望解决的是这样一个问题:

让业务人员通过拖拽搭建打印模板,同时让开发人员可以控制数据、组件和接入方式。

基于这个目标,我做了一个 Vue 3 打印模板画布 :print-canvas-designer,并另外制作了一个真实接入示例项目:print-canvas-examples、演示、参考文档。

它解决什么问题

print-canvas-designer 主要面向标签、鞋盒标、物流面单、商品贴纸等需要自由排版的打印场景。

当前版本支持:

  • 文本、图片、矩形、横线、二维码、条形码等基础组件。
  • A4、100 x 60 和自定义纸张。
  • 元素选中、拖拽、缩放、旋转、复制、删除、锁定、隐藏和层级调整。
  • 标尺、网格、参考线、页边距、安全区和缩放。
  • 文本字段渲染和换行策略。
  • 图片地址设置,以及通过业务上传方法写入图片地址。
  • 打印和导出 PDF。
  • 自定义业务组件及对应的属性编辑区域。
  • 完整编辑器接入,或只接入画布、自行搭建外围 UI。

注:打印设计器中最重要的不是固定的一套左侧面板或右侧表单,而是画布本身。不同业务对组件和属性的要求并不相同,因此画布提供基础编辑能力,业务决定需要出现什么组件。

三种接入方式

为了演示 npm 包在真实 Vue3 项目中的接入方式,我创建了 print-canvas-examples。其中包括三类场景:

  1. 完整编辑器:直接使用默认工具栏、组件面板、画布与属性面板。
  2. 自定义业务组件:将鞋盒标信息块作为业务组件注册到画布中。
  3. 只接入画布:左侧组件区、顶部工具栏、右侧属性区都由业务项目自己实现。

最快接入:使用完整编辑器

安装依赖:

npm install print-canvas-designer

在入口文件引入样式:

import 'print-canvas-designer/style.css'

页面中使用完整编辑器:

<template>
  <PrintDesigner
    v-model="document"
    :data="printData"
    :upload-image="uploadImage"
    @save="handleSave"
    @change="handleChange"
  />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import {
  PrintDesigner,
  createDefaultDocument,
  type PrintDocument
} from 'print-canvas-designer'
import 'print-canvas-designer/style.css'
const document = ref<PrintDocument>(createDefaultDocument())
const printData = {
  styleColorSize: 'RUNNER-01 / BLACK / 42',
  barcode: '6901234567890'
}
const uploadImage = async (file: File) => {
  const form = new FormData()
  form.append('file', file)
  const response = await fetch('/api/upload', {
    method: 'POST',
    body: form
  })
  const result = await response.json()
  return result.url
}
const handleSave = (value: PrintDocument) => {
  // 将模板 JSON 保存到业务服务端
  console.log('save document', value)
}
const handleChange = (value: PrintDocument) => {
  console.log('document changed', value)
}
</script>

v-model 对应的是模板数据。业务系统可以把这份 JSON 保存到数据库,之后重新传给组件即可回显模板。

为什么支持只接入画布

完整编辑器适合快速开始,但在真实项目里,已有系统通常有自己的页面结构和交互方式:

  • 左侧可能不是基础组件列表,而是“商品字段”“订单字段”“客户 Logo”等业务物料。
  • 右侧可能需要结合权限、表单校验、字段绑定和业务规则。
  • 顶部操作区可能需要和系统已有的保存、审核、发布流程结合。

这时可以只接入 PrintCanvas,由业务自行组织页面。

<template>
  <div class="designer-page">
    <aside>
      <button @click="addText">添加文本</button>
      <button @click="addBarcode">添加条形码</button>
    </aside>
    <PrintCanvas :designer="designer" :data="printData" />
    <aside>
      <!-- 根据 designer.activeElement.value 渲染自己的属性表单 -->
    </aside>
  </div>
</template>
<script setup lang="ts">
import {
  PrintCanvas,
  createDefaultDocument,
  createPrintDesigner,
  providePrintDesigner
} from 'print-canvas-designer'
import 'print-canvas-designer/style.css'
const printData = {
  styleColorSize: 'RUNNER-01 / BLACK / 42'
}
const designer = createPrintDesigner({
  modelValue: createDefaultDocument(),
  data: printData,
  onChange(value) {
    console.log('template changed', value)
  },
  onSave(value) {
    console.log('save template', value)
  }
})
providePrintDesigner(designer)
const addText = () => {
  designer.addElement('text', { x: 24, y: 24 })
}
const addBarcode = () => {
  designer.addElement('barcode', { x: 24, y: 80 })
}
</script>

面板和画布之间通过同一个 designer 通信。比如选中元素后,业务属性表单可以调用:

designer.updateElement(activeId, { field: 'styleColorSize' })
designer.updateElementStyle(activeId, { width: 200, height: 42 })
designer.removeElement(activeId)
designer.undo()
designer.redo()
designer.save()

这样,画布负责编辑交互,业务系统负责 UI 和数据规则。

自定义组件:以鞋盒标信息块为例

基础文本组件已经可以通过字段渲染业务内容,但一些重复出现、结构固定的区域,更适合封装成业务组件。

例如鞋盒标中的商品信息区域,可能固定包含:

  • 标题,例如 SPORT SERIES
  • 主内容,例如 RUNNER-01 / BLACK / 42
  • 副内容,例如 STYLE / COLOR / SIZE
  • 强调颜色或品牌样式。

业务可以定义一个组件,在画布中负责展示结构,同时为它提供自己的属性编辑 UI。组件内容、业务字段和表单交互由业务实现,画布仍然提供选中、移动、缩放、旋转、删除和保存能力。

import {
  defaultPrintComponents,
  type PrintComponentDefinition
} from 'print-canvas-designer'
import ShoeInfoBlockRender from './ShoeInfoBlockRender.vue'
import ShoeInfoBlockInspector from './ShoeInfoBlockInspector.vue'

const shoeInfoBlock: PrintComponentDefinition = {
  type: 'shoe-info-block',
  label: '鞋盒标信息块',
  icon: 'i-lucide-tag',
  render: ShoeInfoBlockRender,
  inspector: ShoeInfoBlockInspector,
  createElement: (point) => ({
    id: `shoe_${Date.now()}`,
    type: 'shoe-info-block',
    name: '鞋盒标信息块',
    props: {
      title: 'SPORT SERIES',
      mainText: 'RUNNER-01 / BLACK / 42',
      subText: 'STYLE / COLOR / SIZE',
      accentColor: '#2563eb'
    },
    style: {
      position: 'absolute',
      left: point.x,
      top: point.y,
      width: 260,
      height: 92,
      rotate: 0
    }
  })
}

export const components = [
  ...defaultPrintComponents,
  shoeInfoBlock
]

将它传给完整编辑器即可出现在物料列表和画布中:

<PrintDesigner
  v-model="document"
  :components="components"
/>

在只接入画布的模式下,也可以把同样的 components 传给 createPrintDesigner。因此自定义组件不是固定编辑器才有的能力,而是画布 SDK 提供给业务的扩展机制。

模板数据与业务数据如何结合

模板本身保存布局与元素配置,实际打印数据在运行时传入。

以文本为例,模板中可以保存字段名:

{
  "type": "text",
  "name": "款色码",
  "field": "styleColorSize",
  "style": {
    "left": 24,
    "top": 32,
    "width": 220,
    "height": 42
  }
}

业务在打印前把多个字段拼接为需要展示的内容:

const printData = {
  styleColorSize: [product.style, product.color, product.size].join(' / ')
}

这样可以让画布继续保持通用,不必为每一种业务字段组合设计专门的布局规则。

图片、打印与导出

图片组件既可以直接填写图片地址,也可以将上传过程交给业务系统:

const uploadImage = async (file: File) => {
  const url = await uploadToObjectStorage(file)
  return url
}

输出方面,完整编辑器提供打印和导出 PDF 的交互。只接入画布时,也可以通过 designer.print()designer.exportPdf() 接入自己的操作入口和输出流程。

当前阶段与后续计划

当前版本主要聚焦于打印模板画布的核心能力,以及业务扩展所需要的组件机制。它已经可以用于搭建标签类模板并验证实际接入方式。

后续我计划继续完善:

  • 更丰富的自定义业务组件示例。
  • 模板管理、保存与复用场景的参考实现。
  • 打印与导出流程在真实业务中的接入示例。

到此这篇关于基于Vue3编写一个打印模板设计器(print-canvas-designer)的文章就介绍到这了,更多相关Vue3打印模板设计器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用Vue3生成二维码和条形码详细图文教程

    使用Vue3生成二维码和条形码详细图文教程

    在Vue项目和微信小程序中生成二维码和条形码是一个常见的需求,主要用于分享、产品识别等场景,这篇文章主要给大家介绍了关于使用Vue3生成二维码和条形码的相关资料,需要的朋友可以参考下
    2024-09-09
  • Vue生命周期深入分析总结

    Vue生命周期深入分析总结

    Vue的生命周期就是vue实例从创建到销毁的全过程,也就是new Vue() 开始就是vue生命周期的开始。Vue 实例有⼀个完整的⽣命周期,也就是从开始创建、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是Vue的⽣命周期
    2022-08-08
  • 关于Element-ui中Table表格无法显示的问题及解决

    关于Element-ui中Table表格无法显示的问题及解决

    这篇文章主要介绍了关于Element-ui中Table表格无法显示的问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • 详解Vue组件之间通信的七种方式

    详解Vue组件之间通信的七种方式

    这篇文章主要介绍了Vue组件之间通信的七种方式,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-04-04
  • Vue精简版风格概述

    Vue精简版风格概述

    本篇文章给大家讲解了一下Vue精简版风格的相关知识点内容以及分享了实例代码,有兴趣的朋友参考下。
    2018-01-01
  • Vue下拉框回显并默认选中随机问题

    Vue下拉框回显并默认选中随机问题

    这篇文章主要介绍了Vue下拉框回显并默认选中随机问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • 如何通过点击按钮切换显示不同echarts图表

    如何通过点击按钮切换显示不同echarts图表

    这篇文章主要介绍了如何通过点击按钮切换显示不同echarts图表,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • Ant design vue table 单击行选中 勾选checkbox教程

    Ant design vue table 单击行选中 勾选checkbox教程

    这篇文章主要介绍了Ant design vue table 单击行选中 勾选checkbox教程,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • Vue.js一个文件对应一个组件实践

    Vue.js一个文件对应一个组件实践

    Vue.js实现"一个文件对应一个组件",无需webpack等工具,按需加载组件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-10-10
  • Vue实现可复用轮播组件的方法

    Vue实现可复用轮播组件的方法

    这篇文章主要为大家详细介绍了Vue实现可复用轮播组件的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07

最新评论