// pages/user/printer-commands.js
|
export class PrinterCommands {
|
constructor(model = 'generic') {
|
this.model = model; // 打印机型号(zebra/epson/citizen/generic)
|
// 1. ESC/POS 协议对齐指令(爱普生等非斑马打印机用)
|
this.escPosAlign = {
|
left: [0x1B, 0x61, 0x00],
|
center: [0x1B, 0x61, 0x01],
|
right: [0x1B, 0x61, 0x02]
|
};
|
// 2. ZPL 协议基础指令(斑马打印机专用)
|
this.zplBase = {
|
start: '^XA', // 标签开始
|
end: '^XZ', // 标签结束
|
utf8: '^CI28', // 支持UTF-8编码(解决中文乱码)
|
font: '^CFSIMSUN,', // 中文字体(需提前上传SIMSUN.TTF到斑马打印机)
|
qrCode: '^BQN,2,10' // 二维码配置(QR Code,纠错等级2,大小10)
|
};
|
}
|
|
// ========================== 通用工具方法 ==========================
|
// 拼接多个Uint8Array(兼容两种协议)
|
concatArrays(...arrays) {
|
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
const result = new Uint8Array(totalLength);
|
let offset = 0;
|
arrays.forEach(arr => {
|
result.set(arr, offset);
|
offset += arr.length;
|
});
|
return result;
|
}
|
|
// 文本转Uint8Array(兼容UTF-8)
|
textToUint8(text) {
|
return new TextEncoder('utf-8').encode(text);
|
}
|
|
// ZPL指令字符串转Uint8Array(斑马专用)
|
zplToUint8(zplStr) {
|
// 去除多余空格,避免ZPL解析错误
|
const cleanZpl = zplStr.replace(/\s+/g, '');
|
return this.textToUint8(cleanZpl);
|
}
|
|
|
// ========================== ESC/POS 协议指令(非斑马用) ==========================
|
// 初始化打印机(ESC/POS)
|
escPosInit() {
|
return new Uint8Array([0x1B, 0x40]);
|
}
|
|
// 换行(ESC/POS)
|
escPosNewLine(lines = 1) {
|
const cmd = [];
|
for (let i = 0; i < lines; i++) cmd.push(0x0A);
|
return new Uint8Array(cmd);
|
}
|
|
// 文本加粗(ESC/POS)
|
escPosBold(enable = true) {
|
return new Uint8Array([0x1B, 0x45, enable ? 0x01 : 0x00]);
|
}
|
|
// 设置字号(ESC/POS)
|
escPosSetFontSize(size = 0) {
|
// 0:正常, 1:双倍高, 2:双倍宽, 3:双倍高低
|
return new Uint8Array([0x1D, 0x21, size]);
|
}
|
|
// 切纸(ESC/POS)
|
escPosCutPaper(partial = true) {
|
return new Uint8Array([0x1D, 0x56, partial ? 0x00 : 0x01]);
|
}
|
|
// 打印图片(ESC/POS,非斑马用)
|
escPosPrintImage(buffer) {
|
const header = this.model === 'epson'
|
? [0x1B, 0x2A, 0x00] // 爱普生位图指令
|
: [0x1D, 0x76, 0x30, 0x00]; // 通用位图指令
|
// 图片宽度/高度(示例128x128,实际需根据图片计算)
|
header.push(0x00, 0x80, 0x00, 0x80);
|
return this.concatArrays(new Uint8Array(header), new Uint8Array(buffer));
|
}
|
|
|
// ========================== ZPL 协议指令(斑马专用) ==========================
|
// 构建ZPL文本指令(带坐标定位)
|
zplText(x, y, text, fontSize = 24) {
|
// ^FOx,y: 定位坐标(x横坐标,y纵坐标)
|
// ^CF字体: 字号(默认24)
|
return `${this.zplBase.font}${fontSize}^FO${x},${y}^FD${text}^FS`;
|
}
|
|
// 构建ZPL二维码指令(带坐标定位)
|
zplQrCode(x, y, content) {
|
// ^FOx,y: 二维码坐标
|
// ^BQN,2,10: 二维码类型(QR Code)
|
// ^FDQA,content: QA前缀=QR Code,content=二维码内容
|
return `${this.zplBase.qrCode}^FO${x},${y}^FDQA,${content}^FS`;
|
}
|
|
// 斑马切纸指令(ZPL)
|
zplCutPaper() {
|
// 斑马打印机通过^PQ1(打印1份)+ ^XZ(结束)自动切纸
|
return '^PQ1';
|
}
|
|
|
// ========================== 对外统一接口(自动适配协议) ==========================
|
// 1. 初始化打印机(统一调用)
|
init() {
|
return this.model === 'zebra'
|
? this.textToUint8(this.zplBase.start + this.zplBase.utf8) // 斑马:ZPL开始+UTF-8编码
|
: this.escPosInit(); // 其他:ESC/POS初始化
|
}
|
|
// 2. 切纸(统一调用)
|
cutPaper(partial = true) {
|
if (this.model === 'zebra') {
|
// 斑马:返回ZPL切纸指令转Uint8Array
|
return this.textToUint8(this.zplCutPaper());
|
} else {
|
// 其他:ESC/POS切纸
|
return this.escPosCutPaper(partial);
|
}
|
}
|
|
// 3. 构建测试页(统一调用)
|
buildTestPage(data) {
|
if (this.model === 'zebra') {
|
// 斑马:ZPL测试页(含中文+文本)
|
const zpl = [
|
this.zplBase.start,
|
this.zplBase.utf8,
|
this.zplText(50, 50, '斑马打印机测试页(中文)', 30),
|
this.zplText(50, 100, `测试内容:${data.text}`, 24),
|
this.zplText(50, 150, `打印时间:${new Date().toLocaleString()}`, 24),
|
this.zplCutPaper(),
|
this.zplBase.end
|
].join('');
|
return this.zplToUint8(zpl);
|
} else {
|
// 其他:ESC/POS测试页(原有逻辑)
|
return this.concatArrays(
|
this.escPosInit(),
|
this.escPosSetFontSize(1),
|
this.escPosAlign.center,
|
this.textToUint8('测试打印页\n'),
|
this.escPosSetFontSize(0),
|
this.escPosAlign.left,
|
this.textToUint8(data.text + '\n'),
|
this.escPosNewLine(3),
|
this.escPosCutPaper()
|
);
|
}
|
}
|
|
// 4. 构建成品入库标签(统一调用)
|
buildStockLabel(data) {
|
if (this.model === 'zebra') {
|
// 斑马:ZPL入库标签(含中文+二维码)
|
const qrContent = `物料号:${data.materialNo},批次:${data.batchNo},数量:${data.quantity}`;
|
const zpl = [
|
this.zplBase.start,
|
this.zplBase.utf8,
|
// 标题(居中)
|
this.zplText(100, 30, '成品入库标签', 30),
|
// 标签内容(左对齐,坐标依次向下)
|
this.zplText(30, 80, `销售订单号: ${data.orderNo || '-'}`),
|
this.zplText(30, 120, `客户名称: ${data.customer || '-'}`),
|
this.zplText(30, 160, `客户型号: ${data.model || '-'}`),
|
this.zplText(30, 200, `生产分厂: 五分厂调度员`),
|
this.zplText(30, 240, `检验状态: 合格`),
|
// 重点字段(加粗用大号字体)
|
this.zplText(30, 280, `物料号: ${data.materialNo}`, 28),
|
this.zplText(30, 320, `生产批号: ${data.batchNo}`, 28),
|
// 补充字段
|
this.zplText(30, 360, `规格型号: ${data.spec || '-'}`),
|
this.zplText(30, 400, `数量: ${data.quantity || 0} 件`),
|
this.zplText(30, 440, `日期: ${data.date || new Date().toLocaleDateString()} 表面: 合格`),
|
// 二维码(右侧对齐)
|
this.zplQrCode(250, 80, qrContent),
|
// 切纸+结束
|
this.zplCutPaper(),
|
this.zplBase.end
|
].join('');
|
return this.zplToUint8(zpl);
|
} else {
|
// 其他:ESC/POS入库标签(原有逻辑)
|
return this.concatArrays(
|
this.escPosInit(),
|
this.escPosSetFontSize(2),
|
this.escPosAlign.center,
|
this.textToUint8('成品入库标签\n'),
|
this.escPosSetFontSize(0),
|
this.escPosNewLine(1),
|
this.escPosAlign.left,
|
this.textToUint8(`销售订单号: \n`),
|
this.textToUint8(`生产订单号: ${data.orderNo}\n`),
|
this.textToUint8(`客户名称: ${data.customer}\n`),
|
this.textToUint8(`客户型号: ${data.model}\n`),
|
this.textToUint8(`生产分厂: 五分厂调度员\n`),
|
this.textToUint8(`检验状态: \n`),
|
this.escPosBold(true),
|
this.textToUint8(`物料号: ${data.materialNo}\n`),
|
this.textToUint8(`生产批号: ${data.batchNo}\n`),
|
this.escPosBold(false),
|
this.textToUint8(`规格型号: ${data.spec}\n`),
|
this.textToUint8(`数量: ${data.quantity}\n`),
|
this.textToUint8(`日期: ${data.date} 表面: 合格\n`),
|
this.escPosNewLine(1),
|
this.escPosAlign.center,
|
this.escPosPrintImage(data.qrBuffer),
|
this.escPosNewLine(3),
|
this.escPosCutPaper()
|
);
|
}
|
}
|
|
// 5. 构建移库单(统一调用)
|
buildTransferOrder(data) {
|
if (this.model === 'zebra') {
|
// 斑马:ZPL移库单
|
const qrContent = `移库单:${data.orderNo},物料:${data.materialNo},数量:${data.quantity}`;
|
const zpl = [
|
this.zplBase.start,
|
this.zplBase.utf8,
|
this.zplText(120, 30, '移库单', 30),
|
this.zplText(30, 80, `生产订单号: ${data.orderNo || '-'}`),
|
this.zplText(30, 120, `产品型号: ${data.productModel || '-'}`),
|
this.zplText(30, 160, `客户名称: ${data.customer || '-'}`),
|
this.zplText(30, 200, `物料号: ${data.materialNo || '-'}`),
|
this.zplText(30, 240, `客户型号: ${data.customerModel || '-'}`),
|
this.zplText(30, 280, `生产批号: ${data.batchNo || '-'}`),
|
this.zplText(30, 320, `生产分厂: ${data.factory || '-'}`),
|
this.zplText(30, 360, `数量: ${data.quantity || 0} 件`, 28),
|
this.zplQrCode(250, 80, qrContent),
|
this.zplCutPaper(),
|
this.zplBase.end
|
].join('');
|
return this.zplToUint8(zpl);
|
} else {
|
// 其他:ESC/POS移库单(原有逻辑)
|
return this.concatArrays(
|
this.escPosInit(),
|
this.escPosSetFontSize(2),
|
this.escPosAlign.center,
|
this.textToUint8('移库单\n'),
|
this.escPosSetFontSize(0),
|
this.escPosNewLine(1),
|
this.escPosAlign.left,
|
this.textToUint8(`生产订单号: ${data.orderNo}\n`),
|
this.textToUint8(`产品型号: ${data.productModel}\n`),
|
this.textToUint8(`客户名称: ${data.customer}\n`),
|
this.textToUint8(`物料号: ${data.materialNo}\n`),
|
this.textToUint8(`客户型号: ${data.customerModel}\n`),
|
this.textToUint8(`生产批号: ${data.batchNo}\n`),
|
this.textToUint8(`生产分厂: ${data.factory}\n`),
|
this.textToUint8(`数量: ${data.quantity}\n`),
|
this.escPosNewLine(1),
|
this.escPosAlign.center,
|
this.escPosPrintImage(data.qrBuffer),
|
this.escPosNewLine(3),
|
this.escPosCutPaper()
|
);
|
}
|
}
|
|
// 6. 构建检验单(统一调用)
|
buildInspectionForm(data, type) {
|
if (this.model === 'zebra') {
|
// 斑马:ZPL检验单
|
const formTitle = type === 'inspectionA' ? '检验单A' : '检验单B';
|
const qrContent = `检验单:${data.formNo},物料:${data.materialNo},批次:${data.batchNo}`;
|
// 动态生成检验项(根据data.items)
|
let inspectionItems = '';
|
data.items.forEach((item, idx) => {
|
const yPos = 120 + idx * 40; // 每行检验项向下偏移40
|
inspectionItems += this.zplText(30, yPos, `${item.name}: ${item.result}`);
|
});
|
const zpl = [
|
this.zplBase.start,
|
this.zplBase.utf8,
|
this.zplText(120, 30, formTitle, 30),
|
this.zplText(30, 80, `表单编号: ${data.formNo || '-'}`),
|
inspectionItems, // 动态检验项
|
this.zplText(30, 120 + data.items.length * 40 + 20, `检验员: ${data.inspector || '-'}`),
|
this.zplText(30, 120 + data.items.length * 40 + 60, `检验日期: ${data.date || new Date().toLocaleDateString()}`),
|
this.zplQrCode(250, 80, qrContent),
|
this.zplCutPaper(),
|
this.zplBase.end
|
].join('');
|
return this.zplToUint8(zpl);
|
} else {
|
// 其他:ESC/POS检验单(原有逻辑)
|
let formContent = type === 'inspectionA' ? '检验单A\n' : '检验单B\n';
|
data.items.forEach(item => {
|
formContent += `${item.name}: ${item.result}\n`;
|
});
|
return this.concatArrays(
|
this.escPosInit(),
|
this.escPosSetFontSize(2),
|
this.escPosAlign.center,
|
this.textToUint8(formContent),
|
this.escPosSetFontSize(0),
|
this.escPosNewLine(1),
|
this.escPosAlign.left,
|
this.textToUint8(`表单编号: ${data.formNo}\n`),
|
this.textToUint8(`物料号: ${data.materialNo}\n`),
|
this.textToUint8(`生产批号: ${data.batchNo}\n`),
|
this.textToUint8(`检验员: ${data.inspector}\n`),
|
this.textToUint8(`检验日期: ${data.date}\n`),
|
this.escPosNewLine(1),
|
this.escPosAlign.center,
|
this.escPosPrintImage(data.qrBuffer),
|
this.escPosNewLine(3),
|
this.escPosCutPaper()
|
);
|
}
|
}
|
}
|