基于Vue3編寫一個打印模板設(shè)計器(print-canvas-designer)
在業(yè)務(wù)系統(tǒng)里,標(biāo)簽打印存在一個常見弊端:它往往不是一次性的輸出功能,而是一套需要持續(xù)維護(hù)的編輯能力。
例如:鞋盒標(biāo)可能需要展示款號、顏色、尺碼、品牌圖片、條形碼和二維碼;不同客戶的布局不同,同一個客戶后續(xù)也可能調(diào)整模板。僅靠開發(fā)人員在頁面里寫死位置,不但迭代慢,而且每一次排版變化都要重新發(fā)版。
我希望解決的是這樣一個問題:
讓業(yè)務(wù)人員通過拖拽搭建打印模板,同時讓開發(fā)人員可以控制數(shù)據(jù)、組件和接入方式。
基于這個目標(biāo),我做了一個 Vue 3 打印模板畫布 :print-canvas-designer,并另外制作了一個真實接入示例項目:print-canvas-examples、演示、參考文檔。

它解決什么問題
print-canvas-designer 主要面向標(biāo)簽、鞋盒標(biāo)、物流面單、商品貼紙等需要自由排版的打印場景。
當(dāng)前版本支持:
- 文本、圖片、矩形、橫線、二維碼、條形碼等基礎(chǔ)組件。
- A4、100 x 60 和自定義紙張。
- 元素選中、拖拽、縮放、旋轉(zhuǎn)、復(fù)制、刪除、鎖定、隱藏和層級調(diào)整。
- 標(biāo)尺、網(wǎng)格、參考線、頁邊距、安全區(qū)和縮放。
- 文本字段渲染和換行策略。
- 圖片地址設(shè)置,以及通過業(yè)務(wù)上傳方法寫入圖片地址。
- 打印和導(dǎo)出 PDF。
- 自定義業(yè)務(wù)組件及對應(yīng)的屬性編輯區(qū)域。
- 完整編輯器接入,或只接入畫布、自行搭建外圍 UI。
注:打印設(shè)計器中最重要的不是固定的一套左側(cè)面板或右側(cè)表單,而是畫布本身。不同業(yè)務(wù)對組件和屬性的要求并不相同,因此畫布提供基礎(chǔ)編輯能力,業(yè)務(wù)決定需要出現(xiàn)什么組件。
三種接入方式
為了演示 npm 包在真實 Vue3 項目中的接入方式,我創(chuàng)建了 print-canvas-examples。其中包括三類場景:
- 完整編輯器:直接使用默認(rèn)工具欄、組件面板、畫布與屬性面板。
- 自定義業(yè)務(wù)組件:將鞋盒標(biāo)信息塊作為業(yè)務(wù)組件注冊到畫布中。
- 只接入畫布:左側(cè)組件區(qū)、頂部工具欄、右側(cè)屬性區(qū)都由業(yè)務(wù)項目自己實現(xiàn)。

最快接入:使用完整編輯器
安裝依賴:
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 保存到業(yè)務(wù)服務(wù)端
console.log('save document', value)
}
const handleChange = (value: PrintDocument) => {
console.log('document changed', value)
}
</script>v-model 對應(yīng)的是模板數(shù)據(jù)。業(yè)務(wù)系統(tǒng)可以把這份 JSON 保存到數(shù)據(jù)庫,之后重新傳給組件即可回顯模板。
為什么支持只接入畫布
完整編輯器適合快速開始,但在真實項目里,已有系統(tǒng)通常有自己的頁面結(jié)構(gòu)和交互方式:
- 左側(cè)可能不是基礎(chǔ)組件列表,而是“商品字段”“訂單字段”“客戶 Logo”等業(yè)務(wù)物料。
- 右側(cè)可能需要結(jié)合權(quán)限、表單校驗、字段綁定和業(yè)務(wù)規(guī)則。
- 頂部操作區(qū)可能需要和系統(tǒng)已有的保存、審核、發(fā)布流程結(jié)合。
這時可以只接入 PrintCanvas,由業(yè)務(wù)自行組織頁面。
<template>
<div class="designer-page">
<aside>
<button @click="addText">添加文本</button>
<button @click="addBarcode">添加條形碼</button>
</aside>
<PrintCanvas :designer="designer" :data="printData" />
<aside>
<!-- 根據(jù) 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 通信。比如選中元素后,業(yè)務(wù)屬性表單可以調(diào)用:
designer.updateElement(activeId, { field: 'styleColorSize' })
designer.updateElementStyle(activeId, { width: 200, height: 42 })
designer.removeElement(activeId)
designer.undo()
designer.redo()
designer.save()
這樣,畫布負(fù)責(zé)編輯交互,業(yè)務(wù)系統(tǒng)負(fù)責(zé) UI 和數(shù)據(jù)規(guī)則。
自定義組件:以鞋盒標(biāo)信息塊為例
基礎(chǔ)文本組件已經(jīng)可以通過字段渲染業(yè)務(wù)內(nèi)容,但一些重復(fù)出現(xiàn)、結(jié)構(gòu)固定的區(qū)域,更適合封裝成業(yè)務(wù)組件。
例如鞋盒標(biāo)中的商品信息區(qū)域,可能固定包含:
- 標(biāo)題,例如
SPORT SERIES。 - 主內(nèi)容,例如
RUNNER-01 / BLACK / 42。 - 副內(nèi)容,例如
STYLE / COLOR / SIZE。 - 強調(diào)顏色或品牌樣式。
業(yè)務(wù)可以定義一個組件,在畫布中負(fù)責(zé)展示結(jié)構(gòu),同時為它提供自己的屬性編輯 UI。組件內(nèi)容、業(yè)務(wù)字段和表單交互由業(yè)務(wù)實現(xiàn),畫布仍然提供選中、移動、縮放、旋轉(zhuǎn)、刪除和保存能力。
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: '鞋盒標(biāo)信息塊',
icon: 'i-lucide-tag',
render: ShoeInfoBlockRender,
inspector: ShoeInfoBlockInspector,
createElement: (point) => ({
id: `shoe_${Date.now()}`,
type: 'shoe-info-block',
name: '鞋盒標(biāo)信息塊',
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
]
將它傳給完整編輯器即可出現(xiàn)在物料列表和畫布中:
<PrintDesigner v-model="document" :components="components" />
在只接入畫布的模式下,也可以把同樣的 components 傳給 createPrintDesigner。因此自定義組件不是固定編輯器才有的能力,而是畫布 SDK 提供給業(yè)務(wù)的擴展機制。

模板數(shù)據(jù)與業(yè)務(wù)數(shù)據(jù)如何結(jié)合
模板本身保存布局與元素配置,實際打印數(shù)據(jù)在運行時傳入。
以文本為例,模板中可以保存字段名:
{
"type": "text",
"name": "款色碼",
"field": "styleColorSize",
"style": {
"left": 24,
"top": 32,
"width": 220,
"height": 42
}
}
業(yè)務(wù)在打印前把多個字段拼接為需要展示的內(nèi)容:
const printData = {
styleColorSize: [product.style, product.color, product.size].join(' / ')
}
這樣可以讓畫布繼續(xù)保持通用,不必為每一種業(yè)務(wù)字段組合設(shè)計專門的布局規(guī)則。
圖片、打印與導(dǎo)出
圖片組件既可以直接填寫圖片地址,也可以將上傳過程交給業(yè)務(wù)系統(tǒng):
const uploadImage = async (file: File) => {
const url = await uploadToObjectStorage(file)
return url
}
輸出方面,完整編輯器提供打印和導(dǎo)出 PDF 的交互。只接入畫布時,也可以通過 designer.print() 和 designer.exportPdf() 接入自己的操作入口和輸出流程。
當(dāng)前階段與后續(xù)計劃
當(dāng)前版本主要聚焦于打印模板畫布的核心能力,以及業(yè)務(wù)擴展所需要的組件機制。它已經(jīng)可以用于搭建標(biāo)簽類模板并驗證實際接入方式。
后續(xù)我計劃繼續(xù)完善:
- 更豐富的自定義業(yè)務(wù)組件示例。
- 模板管理、保存與復(fù)用場景的參考實現(xiàn)。
- 打印與導(dǎo)出流程在真實業(yè)務(wù)中的接入示例。
到此這篇關(guān)于基于Vue3編寫一個打印模板設(shè)計器(print-canvas-designer)的文章就介紹到這了,更多相關(guān)Vue3打印模板設(shè)計器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue中手動封裝iconfont組件解析(三種引用方式的封裝和使用)
這篇文章主要介紹了vue中手動封裝iconfont組件(三種引用方式的封裝和使用),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09
有關(guān)vue 組件切換,動態(tài)組件,組件緩存
這篇文章主要介紹了有關(guān)vue 組件切換,動態(tài)組件,組件緩存,在組件化開發(fā)模式下,我們會把整個項目拆分成很多組件,然后按照合理的方式組織起來,達(dá)到預(yù)期效果,下面來看看文章的詳細(xì)內(nèi)容2021-11-11
Vue對Element中el-tab-pane添加@click事件無效問題解決
這篇文章主要給大家介紹了關(guān)于Vue對Element中el-tab-pane添加@click事件無效問題的解決辦法,文中通過圖文以及代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07
flutter使用tauri實現(xiàn)一個一鍵視頻轉(zhuǎn)4K軟件
這篇文章主要為大家介紹了flutter使用tauri實現(xiàn)一個一鍵視頻轉(zhuǎn)4K軟件詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
解決vue接口數(shù)據(jù)賦值給data沒有反應(yīng)的問題
今天小編就為大家分享一篇解決vue接口數(shù)據(jù)賦值給data沒有反應(yīng)的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08

