zebra通过zpl语言实现中文打印(二)

引入了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

当然你可以根据自己的需求动态更改你要输出的数据

相关推荐
未来之窗软件服务3 小时前
未来之窗昭和仙君(六十五)Vue与跨地区多部门开发—东方仙盟练气
前端·javascript·vue.js·仙盟创梦ide·东方仙盟·昭和仙君
baidu_247438613 小时前
Android ViewModel定时任务
android·开发语言·javascript
VT.馒头3 小时前
【力扣】2721. 并行执行异步函数
前端·javascript·算法·leetcode·typescript
有位神秘人4 小时前
Android中Notification的使用详解
android·java·javascript
phltxy4 小时前
Vue 核心特性实战指南:指令、样式绑定、计算属性与侦听器
前端·javascript·vue.js
Byron07075 小时前
Vue 中使用 Tiptap 富文本编辑器的完整指南
前端·javascript·vue.js
Mr Xu_6 小时前
告别硬编码:前端项目中配置驱动的实战优化指南
前端·javascript·数据结构
Byron07077 小时前
从 0 到 1 搭建 Vue 前端工程化体系:提效、提质、降本实战落地
前端·javascript·vue.js
德育处主任Pro7 小时前
纯前端网格路径规划:PathFinding.js的使用方法
开发语言·前端·javascript