引入了broserPrint.js以后。根据我们封装的usePrint这个hooks,我们下一步要做的就是封装一个打印的函数,方便在任意地方调用
首先说明。前端代码要让打印机执行,需要通过把前端要输出的数据塞到ZPL语言中,zpl语言是打印机能识别的指令语音。

我们开始封装代码
export const zplHexEncodeUtf8 = (text: string) => {
const bytes = new TextEncoder().encode(text)
let out = ''
for (const b of bytes) out += `_${b.toString(16).padStart(2, '0').toUpperCase()}`
return out
}
export const sendZplToPrinter = async (
device: any,
zpl: string,
options?: { retries?: number; retryDelayMs?: number; timeoutMs?: number }
): Promise<void> => {
const retries = Math.max(0, Number(options?.retries ?? 0))
const retryDelayMs = Math.max(0, Number(options?.retryDelayMs ?? 0))
const timeoutMs = Math.max(0, Number(options?.timeoutMs ?? 8000))
const sendOnce = () =>
new Promise<void>((resolve, reject) => {
if (!device || typeof device.send !== 'function') {
reject(new Error('Printer device not available'))
return
}
let done = false
let timer: any
if (timeoutMs > 0) {
timer = setTimeout(() => {
if (done) return
done = true
reject(new Error('Printer send timeout'))
}, timeoutMs)
}
const finish = (err?: any) => {
if (done) return
done = true
if (timer) clearTimeout(timer)
if (err) reject(err instanceof Error ? err : new Error(String(err ?? 'send failed')))
else resolve()
}
device.send(
zpl,
() => finish(),
(err: any) => finish(err)
)
})
for (let attempt = 0; attempt <= retries; attempt++) {
try {
await sendOnce()
return
} catch (e) {
if (attempt >= retries) throw e
if (retryDelayMs > 0) await new Promise((r) => setTimeout(r, retryDelayMs))
}
}
}
export const buildOrderLabelZpl = (options: {
cardNo?: string
userId?: string | number
recognizedInfoString?: string
fontFile?: string
xOffset?: number
widthDots?: number
heightDots?: number
}) => {
const cardNo = String(options.cardNo ?? '').trim()
const userId = String(options.userId ?? '').trim()
const recognizedInfoString = String(options.recognizedInfoString ?? '').trim()
const fontFile = String(options.fontFile ?? '').trim()
const x = Math.max(0, Math.round(Number(options.xOffset ?? 0)))
const widthDots = Math.max(1, Math.round(Number(options.widthDots ?? 480)))
const heightDots = Math.max(1, Math.round(Number(options.heightDots ?? 320)))
const safeAscii = (v: string) =>
v
.replace(/[\x00-\x1F\x7F]/g, ' ')
.replace(/[\^~]/g, ' ')
.trim()
const barcodeData = safeAscii(cardNo)
// const title = '订单标签'
// const titleEncoded = zplHexEncodeUtf8(title)
const recognizedEncoded = zplHexEncodeUtf8(recognizedInfoString) //转hex
const lines: string[] = ['^XA', '^CI28', `^PW${widthDots}`, `^LL${heightDots}`, '^LH0,0', '^LS0']
if (barcodeData) {
lines.push('^BY2,2,80')
lines.push(`^FO${x},30^BCN,80,Y,N,N^FD${barcodeData}^FS`)
}
//设置标题--可选操作
// if (fontFile) {
// lines.push(`^FO${x},105^A@N,28,28,${fontFile}^FH_^FD${titleEncoded}^FS`)
// } else {
// lines.push(`^FO${x},105^A0N,28,28^FD${title}^FS`)
// }
if (cardNo) lines.push(`^FO${x},140^A0N,24,24^FDCard: ${safeAscii(cardNo)}^FS`)
if (userId) lines.push(`^FO${x},170^A0N,24,24^FDUserId: ${safeAscii(userId)}^FS`)
if (recognizedInfoString) {
const yStart = 200
const lineHeight = 24
const lineGap = 4
const availableHeight = Math.max(0, heightDots - yStart - 10)
const maxLines = Math.max(1, Math.floor(availableHeight / (lineHeight + lineGap)))
const marginRight = 20
const blockWidth = Math.max(1, widthDots - x - marginRight)
if (fontFile) {
lines.push(
`^FO${x},${yStart}^A@N,24,24,${fontFile}^FB${blockWidth},${maxLines},${lineGap},L,0^FH_^FD${recognizedEncoded}^FS`
)
} else {
lines.push(
`^FO${x},${yStart}^A0N,24,24^FB${blockWidth},${maxLines},${lineGap},L,0^FD${safeAscii(recognizedInfoString)}^FS`
)
}
}
lines.push('^XZ')
return lines.join('\n')
}
const normalizeZplFontFile = (fontFile?: string) => {
const raw = String(fontFile ?? '').trim()
if (!raw) return undefined
if (raw.includes(':')) return raw
return `E:${raw}`
}
export const printOrderLabel = async (options: {
device: any
cardNo?: string
userId?: string | number
recognizedInfoString?: string
fontFile?: string
xOffset?: number
widthDots?: number
heightDots?: number
retries?: number
retryDelayMs?: number
timeoutMs?: number
}) => {
const zpl = buildOrderLabelZpl(options)
await sendZplToPrinter(options.device, zpl, {
retries: options.retries,
retryDelayMs: options.retryDelayMs,
timeoutMs: options.timeoutMs
})
return zpl
}
export const printOrderLabelBatch = async (options: {
device: any
items: Array<{ cardNo?: string; userId?: string | number; recognizedInfoString?: string }>
xOffset?: number
widthDots?: number
heightDots?: number
retries?: number
retryDelayMs?: number
timeoutMs?: number
delayBetweenMs?: number
fontFile?: string
}) => {
const device = options.device
const items = Array.isArray(options.items) ? options.items : []
const delayBetweenMs = Math.max(0, Number(options.delayBetweenMs ?? 800))
const fontFile = normalizeZplFontFile(options.fontFile ?? 'SIMSUN.FNT')
const zplList: string[] = []
for (const item of items) {
const zpl = buildOrderLabelZpl({
cardNo: item?.cardNo,
userId: item?.userId,
recognizedInfoString: item?.recognizedInfoString,
fontFile,
xOffset: options.xOffset,
widthDots: options.widthDots,
heightDots: options.heightDots
})
zplList.push(zpl)
}
for (let i = 0; i < zplList.length; i++) {
await sendZplToPrinter(device, zplList[i], {
retries: options.retries,
retryDelayMs: options.retryDelayMs,
timeoutMs: options.timeoutMs
})
if (delayBetweenMs > 0 && i < zplList.length - 1) {
await new Promise((r) => setTimeout(r, delayBetweenMs))
}
}
return zplList
}
执行打印的核心代码就是这个:
sendZplToPrinter(selected_device, code, { retries: 1, retryDelayMs: 1000 })
其实本质是调用的这个
device.send(
zpl,
() => finish(),
(err: any) => finish(err)
)
找到打印机以后通过打印机对象的send方法发送zpl指令代码。中间我们做了很多的其他逻辑。但是最关键的就是device.send({})
由于我这里需要打印的内容包括
1:barcodeData(条形码,它的数据源是cardNo),zpl有专门的指令生成条形码和二维码。代码中^BY2开头其实就是在告诉打印机生成条形码。
2:cardNo
3:userId
4:recognizedInfoString
当然你可以根据自己的需求动态更改你要输出的数据