diff --git a/pages/index/index.js b/pages/index/index.js
index 57ab3b8..7f7d02a 100644
--- a/pages/index/index.js
+++ b/pages/index/index.js
@@ -1,6 +1,7 @@
const LAST_CONNECTED_DEVICE = 'last_connected_device'
const PrinterJobs = require('../../printer/printerjobs')
const printerUtil = require('../../printer/printerutil')
+const imageUtil = require('../../utils/imageutil')
function inArray(arr, key, val) {
for (let i = 0; i < arr.length; i++) {
@@ -36,7 +37,12 @@ Page({
data: {
devices: [],
connected: false,
- chs: []
+ chs: [],
+ isPrinting: false,
+ printProgress: 0,
+ printStatusText: '',
+ selectedImage: null,
+ imageData: null
},
onUnload() {
this.closeBluetoothAdapter()
@@ -226,67 +232,314 @@ Page({
})
},
writeBLECharacteristicValue() {
- let printerJobs = new PrinterJobs();
- printerJobs
- .print('2018年12月5日17:34')
- .print(printerUtil.fillLine())
- .setAlign('ct')
- .setSize(2, 2)
- .print('#20饿了么外卖')
- .setSize(1, 1)
- .print('切尔西Chelsea')
- .setSize(2, 2)
- .print('在线支付(已支付)')
- .setSize(1, 1)
- .print('订单号:5415221202244734')
- .print('下单时间:2017-07-07 18:08:08')
- .setAlign('lt')
- .print(printerUtil.fillAround('一号口袋'))
- .print(printerUtil.inline('意大利茄汁一面 * 1', '15'))
- .print(printerUtil.fillAround('其他'))
- .print('餐盒费:1')
- .print('[赠送康师傅冰红茶] * 1')
- .print(printerUtil.fillLine())
- .setAlign('rt')
- .print('原价:¥16')
- .print('总价:¥16')
- .setAlign('lt')
- .print(printerUtil.fillLine())
- .print('备注')
- .print("无")
- .print(printerUtil.fillLine())
- .println();
+ this.startPrintJob(() => {
+ let printerJobs = new PrinterJobs();
+ printerJobs
+ .print('2018年12月5日17:34')
+ .print(printerUtil.fillLine())
+ .setAlign('ct')
+ .setSize(2, 2)
+ .print('#20饿了么外卖')
+ .setSize(1, 1)
+ .print('切尔西Chelsea')
+ .setSize(2, 2)
+ .print('在线支付(已支付)')
+ .setSize(1, 1)
+ .print('订单号:5415221202244734')
+ .print('下单时间:2017-07-07 18:08:08')
+ .setAlign('lt')
+ .print(printerUtil.fillAround('一号口袋'))
+ .print(printerUtil.inline('意大利茄汁一面 * 1', '15'))
+ .print(printerUtil.fillAround('其他'))
+ .print('餐盒费:1')
+ .print('[赠送康师傅冰红茶] * 1')
+ .print(printerUtil.fillLine())
+ .setAlign('rt')
+ .print('原价:¥16')
+ .print('总价:¥16')
+ .setAlign('lt')
+ .print(printerUtil.fillLine())
+ .print('备注')
+ .print("无")
+ .print(printerUtil.fillLine())
+ .println();
- let buffer = printerJobs.buffer();
- console.log('ArrayBuffer', 'length: ' + buffer.byteLength, ' hex: ' + ab2hex(buffer));
- // 1.并行调用多次会存在写失败的可能性
- // 2.建议每次写入不超过20字节
- // 分包处理,延时调用
- const maxChunk = 20;
- const delay = 20;
- for (let i = 0, j = 0, length = buffer.byteLength; i < length; i += maxChunk, j++) {
- let subPackage = buffer.slice(i, i + maxChunk <= length ? (i + maxChunk) : length);
- setTimeout(this._writeBLECharacteristicValue, j * delay, subPackage);
- }
+ return printerJobs.buffer();
+ });
},
- _writeBLECharacteristicValue(buffer) {
+ /**
+ * 写入BLE特征值(单包数据发送)
+ *
+ * 详细记录每个数据包的发送情况,包括:
+ * - 包序号和总包数
+ * - 数据包内容(十六进制)
+ * - 发送结果(成功/失败)
+ *
+ * @param {ArrayBuffer} buffer - 要发送的数据包
+ * @param {number} packageNumber - 当前包序号(从1开始)
+ * @param {number} totalPackages - 总包数
+ */
+ _writeBLECharacteristicValue(buffer, packageNumber = 0, totalPackages = 0) {
+ // 详细的发送前日志
+ if (packageNumber > 0) {
+ console.log(`\n--- 发送第${packageNumber}/${totalPackages}包数据 ---`);
+ console.log(`数据长度: ${buffer.byteLength} 字节`);
+ console.log(`数据十六进制: ${ab2hex(buffer)}`);
+ console.log(`时间戳: ${new Date().toLocaleTimeString()}`);
+ }
+
wx.writeBLECharacteristicValue({
deviceId: this._deviceId,
serviceId: this._serviceId,
characteristicId: this._characteristicId,
value: buffer,
- success(res) {
- console.log('writeBLECharacteristicValue success', res)
+ success: (res) => {
+ console.log(`✅ 第${packageNumber}/${totalPackages}包发送成功`, res);
+
+ // 更新打印进度
+ if (packageNumber > 0) {
+ this.updatePrintProgress();
+ }
},
- fail(res) {
- console.log('writeBLECharacteristicValue fail', res)
+ fail: (res) => {
+ console.error(`❌ 第${packageNumber}/${totalPackages}包发送失败`, res);
+ console.error(`错误详情: errCode=${res.errCode}, errMsg=${res.errMsg}`);
+
+ // 可以在这里添加重试逻辑
+ if (packageNumber > 0 && res.errCode === 10008) {
+ console.warn('特征值不支持写入操作,请检查特征值属性');
+ }
}
})
},
+
+ /**
+ * 开始打印任务
+ *
+ * 打印数据分包发送管理:
+ * 1. 获取完整的打印数据Buffer
+ * 2. 计算分包数量和进度
+ * 3. 按顺序发送每个数据包
+ * 4. 实时更新发送进度和状态
+ *
+ * @param {Function} getBufferFunc - 获取打印数据的函数,返回ArrayBuffer
+ *
+ * 分包策略:
+ * - 每包最大20字节(微信小程序BLE限制)
+ * - 包间延时20ms,避免发送过快
+ * - 实时显示发送进度 [当前包/总包数]
+ *
+ * @example
+ * this.startPrintJob(() => {
+ * let printerJobs = new PrinterJobs();
+ * printerJobs.print('测试内容');
+ * return printerJobs.buffer();
+ * });
+ */
+ startPrintJob(getBufferFunc) {
+ try {
+ const buffer = getBufferFunc();
+
+ // 详细的调试信息输出
+ console.log('=== 打印任务开始 ===');
+ console.log(`总数据长度: ${buffer.byteLength} 字节`);
+ console.log(`完整数据十六进制: ${ab2hex(buffer)}`);
+
+ // 设置打印状态
+ this.setData({
+ isPrinting: true,
+ printProgress: 0,
+ printStatusText: '准备打印...'
+ });
+
+ // 计算分包策略
+ const maxChunk = 20; // 微信小程序BLE单次写入最大字节数
+ const totalPackages = Math.ceil(buffer.byteLength / maxChunk);
+ this._totalPackages = totalPackages;
+ this._currentPackage = 0;
+
+ console.log(`分包策略: 每包${maxChunk}字节,共${totalPackages}包`);
+
+ // 分包处理,延时调用
+ const delay = 20; // 包间延时,避免发送过快
+
+ for (let i = 0, j = 0, length = buffer.byteLength; i < length; i += maxChunk, j++) {
+ // 计算当前包的起始和结束位置
+ const startPos = i;
+ const endPos = Math.min(i + maxChunk, length);
+ const subPackage = buffer.slice(startPos, endPos);
+
+ // 调试输出每个分包的信息
+ console.log(`第${j + 1}包: 位置[${startPos}-${endPos}], 长度${subPackage.byteLength}字节`);
+ console.log(`第${j + 1}包十六进制: ${ab2hex(subPackage)}`);
+
+ // 设置定时器发送数据包
+ setTimeout(this._writeBLECharacteristicValue, j * delay, subPackage, j + 1, totalPackages);
+ }
+
+ // 设置完成定时器(预留1秒缓冲时间)
+ const totalTime = totalPackages * delay + 1000;
+ console.log(`预计发送完成时间: ${totalTime}ms`);
+
+ setTimeout(() => {
+ this.completePrintJob();
+ }, totalTime);
+
+ } catch (error) {
+ console.error('打印任务失败:', error);
+ this.setData({
+ isPrinting: false,
+ printStatusText: '打印失败: ' + error.message
+ });
+ }
+ },
+
+ /**
+ * 更新打印进度
+ *
+ * 实时计算并显示当前打印进度:
+ * - 计算百分比进度
+ * - 更新UI状态
+ * - 记录进度日志
+ */
+ updatePrintProgress() {
+ this._currentPackage++;
+ const progress = Math.round((this._currentPackage / this._totalPackages) * 100);
+
+ console.log(`📊 打印进度更新: ${this._currentPackage}/${this._totalPackages} = ${progress}%`);
+
+ this.setData({
+ printProgress: progress,
+ printStatusText: `正在传输 [${this._currentPackage}/${this._totalPackages}]`
+ });
+ },
+
+ /**
+ * 完成打印任务
+ *
+ * 处理打印完成状态:
+ * 1. 设置100%进度
+ * 2. 显示成功消息
+ * 3. 3秒后自动隐藏状态
+ *
+ * 如果所有包都成功发送,显示成功消息;
+ * 否则显示警告信息。
+ */
+ completePrintJob() {
+ const allPackagesSent = this._currentPackage >= this._totalPackages;
+
+ if (allPackagesSent) {
+ console.log('✅ 打印任务完成:所有数据包发送成功');
+ this.setData({
+ printProgress: 100,
+ printStatusText: '打印指令发送成功'
+ });
+ } else {
+ console.warn(`⚠️ 打印任务完成:仅发送了 ${this._currentPackage}/${this._totalPackages} 包`);
+ this.setData({
+ printProgress: Math.round((this._currentPackage / this._totalPackages) * 100),
+ printStatusText: '打印数据发送不完整'
+ });
+ }
+
+ // 3秒后隐藏状态
+ setTimeout(() => {
+ console.log('打印状态UI已隐藏');
+ this.setData({
+ isPrinting: false,
+ printProgress: 0,
+ printStatusText: ''
+ });
+
+ // 重置计数器
+ this._currentPackage = 0;
+ this._totalPackages = 0;
+ }, 3000);
+ },
closeBluetoothAdapter() {
wx.closeBluetoothAdapter()
this._discoveryStarted = false
},
+ /**
+ * 打印二维码
+ */
+ printQRCode() {
+ this.startPrintJob(() => {
+ let printerJobs = new PrinterJobs();
+ printerJobs
+ .setAlign('ct')
+ .print('扫码关注')
+ .qrcode('https://github.com/your-repo', 8, 'M')
+ .lineFeed(2);
+
+ return printerJobs.buffer();
+ });
+ },
+
+ /**
+ * 选择并预览图片
+ */
+ printImage() {
+ imageUtil.chooseAndProcessImage()
+ .then(result => {
+ this.setData({
+ selectedImage: result.imagePath,
+ imageData: {
+ bitmapData: result.bitmapData,
+ width: result.width,
+ height: result.height
+ }
+ });
+ })
+ .catch(error => {
+ wx.showToast({
+ title: '图片处理失败',
+ icon: 'none'
+ });
+ console.error('图片处理失败:', error);
+ });
+ },
+
+ /**
+ * 确认打印图片
+ */
+ confirmPrintImage() {
+ if (!this.data.imageData) {
+ wx.showToast({
+ title: '没有图片数据',
+ icon: 'none'
+ });
+ return;
+ }
+
+ this.startPrintJob(() => {
+ let printerJobs = new PrinterJobs();
+ printerJobs
+ .setAlign('ct')
+ .print('图片打印')
+ .bitmap(this.data.imageData.bitmapData, this.data.imageData.width, this.data.imageData.height)
+ .lineFeed(2);
+
+ return printerJobs.buffer();
+ });
+
+ // 清除预览
+ this.setData({
+ selectedImage: null,
+ imageData: null
+ });
+ },
+
+ /**
+ * 取消打印图片
+ */
+ cancelPrintImage() {
+ this.setData({
+ selectedImage: null,
+ imageData: null
+ });
+ },
+
onLoad(options) {
const lastDevice = wx.getStorageSync(LAST_CONNECTED_DEVICE);
this.setData({
diff --git a/pages/index/index.wxml b/pages/index/index.wxml
index 4ebed7a..b20232e 100644
--- a/pages/index/index.wxml
+++ b/pages/index/index.wxml
@@ -38,11 +38,42 @@
特性值: {{item.value}}
+
+
+
+ 打印状态
+
+
+ {{printStatusText}}
+
+
+
+
+
+
+
+
+
+ 图片预览
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/index/index.wxss b/pages/index/index.wxss
index 7369601..58202e2 100644
--- a/pages/index/index.wxss
+++ b/pages/index/index.wxss
@@ -52,4 +52,62 @@
min-height: 2.58823529em;
line-height: 2.58823529em;
padding: 10rpx;
+}
+
+/* 打印状态管理样式 */
+.print-status {
+ width: 100%;
+ padding: 20rpx;
+ margin: 20rpx 0;
+ background-color: #f5f5f5;
+ border-radius: 10rpx;
+}
+
+.status-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ margin-bottom: 20rpx;
+ text-align: center;
+}
+
+.progress-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.progress-text {
+ margin-top: 10rpx;
+ font-size: 28rpx;
+ color: #666;
+}
+
+/* 图片预览样式 */
+.image-preview {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 20rpx;
+ background-color: #f9f9f9;
+ border-radius: 10rpx;
+ margin: 20rpx 0;
+}
+
+.preview-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ margin-bottom: 20rpx;
+}
+
+.preview-image {
+ width: 300rpx;
+ height: 300rpx;
+ border: 2rpx solid #ddd;
+ border-radius: 10rpx;
+ margin-bottom: 20rpx;
+}
+
+.preview-buttons {
+ display: flex;
+ gap: 20rpx;
}
\ No newline at end of file
diff --git a/printer/commands.js b/printer/commands.js
index 3401e76..a134408 100644
--- a/printer/commands.js
+++ b/printer/commands.js
@@ -186,6 +186,115 @@ _.COLOR = {
1: [0x1b, 0x72, 0x01] // red
};
+/**
+ * [QR_CODE QR Code commands]
+ * ESC/POS二维码打印指令集
+ * 基于ESC/POS标准规范实现
+ *
+ * 指令格式说明:
+ * GS ( k <功能代码> <数据长度> <数据>
+ * 其中:GS = 0x1D, ( = 0x28, k = 0x6B
+ *
+ * @type {Object}
+ */
+_.QR_CODE = {
+ /**
+ * 设置二维码模块大小
+ * 设置QR码中单个模块的像素大小
+ *
+ * @param {number} size - 模块大小,范围1-16,默认推荐6
+ * @returns {Array} ESC/POS指令数组
+ *
+ * 指令格式:GS ( k 3 0 49 67 n
+ * 十六进制:1D 28 6B 03 00 31 43 n
+ *
+ * 示例:
+ * QR_MODULE_SIZE(6) -> [0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x43, 0x06]
+ */
+ QR_MODULE_SIZE: function(size) {
+ // 参数验证
+ if (size < 1 || size > 16) {
+ console.warn(`二维码模块大小 ${size} 超出范围[1-16],使用默认值6`);
+ size = 6;
+ }
+ return [0x1d, 0x28, 0x6b, 0x03, 0x00, 0x31, 0x43, size];
+ },
+
+ /**
+ * 设置二维码错误纠正等级
+ * 设置QR码的错误纠正能力级别
+ *
+ * @param {number} level - 错误纠正等级:48=L(7%), 49=M(15%), 50=Q(25%), 51=H(30%)
+ * @returns {Array} ESC/POS指令数组
+ *
+ * 指令格式:GS ( k 3 0 49 69 n
+ * 十六进制:1D 28 6B 03 00 31 45 n
+ *
+ * 示例:
+ * QR_ERROR_LEVEL(49) -> [0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x45, 0x31]
+ */
+ QR_ERROR_LEVEL: function(level) {
+ // 参数验证
+ const validLevels = [48, 49, 50, 51]; // L, M, Q, H
+ if (!validLevels.includes(level)) {
+ console.warn(`二维码错误纠正等级 ${level} 无效,使用默认值49(M)`);
+ level = 49;
+ }
+ return [0x1d, 0x28, 0x6b, 0x03, 0x00, 0x31, 0x45, level];
+ },
+
+ /**
+ * 存储二维码数据到打印机缓存
+ * 将要打印的QR码数据发送到打印机内存
+ *
+ * @param {string} data - 要编码的字符串数据
+ * @returns {Array} ESC/POS指令数组
+ *
+ * 指令格式:GS ( k pL pH 49 80 48 d1...dk
+ * 十六进制:1D 28 6B pL pH 31 50 30 数据...
+ *
+ * 数据长度计算:pL = (数据长度+3) & 0xFF, pH = (数据长度+3) >> 8
+ *
+ * 示例:存储"ABC"
+ * QR_STORE_DATA("ABC") -> [0x1D, 0x28, 0x6B, 0x06, 0x00, 0x31, 0x50, 0x30, 0x41, 0x42, 0x43]
+ */
+ QR_STORE_DATA: function(data) {
+ const length = data.length + 3; // 包括功能代码(3字节)
+ const lowByte = length & 0xFF; // 低8位
+ const highByte = (length >> 8) & 0xFF; // 高8位
+
+ // 数据长度限制检查(通常不超过7084字节)
+ if (length > 7084) {
+ console.warn(`二维码数据长度 ${length} 可能超出打印机限制`);
+ }
+
+ // 构建完整指令:头部 + 数据
+ const header = [0x1d, 0x28, 0x6b, lowByte, highByte, 0x31, 0x50, 0x30];
+ const dataBytes = Array.from(data).map(char => char.charCodeAt(0));
+
+ console.log(`二维码数据存储: "${data}" (${data.length}字符) -> 指令长度:${length}`);
+ console.log(`数据十六进制: ${dataBytes.map(b => '0x' + b.toString(16).toUpperCase()).join(' ')}`);
+
+ return header.concat(dataBytes);
+ },
+
+ /**
+ * 打印已存储的二维码
+ * 执行QR码打印操作
+ *
+ * @returns {Array} ESC/POS指令数组
+ *
+ * 指令格式:GS ( k 3 0 49 81 48
+ * 十六进制:1D 28 6B 03 00 31 51 30
+ *
+ * 注意:执行此指令前必须先设置模块大小、错误纠正等级并存储数据
+ *
+ * 示例:
+ * QR_PRINT -> [0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x51, 0x30]
+ */
+ QR_PRINT: [0x1d, 0x28, 0x6b, 0x03, 0x00, 0x31, 0x51, 0x30]
+};
+
/**
* [exports description]
* @type {[type]}
diff --git a/printer/printerjobs.js b/printer/printerjobs.js
index fda31bb..63b091c 100644
--- a/printer/printerjobs.js
+++ b/printer/printerjobs.js
@@ -144,6 +144,126 @@ printerJobs.prototype.beep = function (n, t) {
return this;
};
+/**
+ * 打印二维码(QR Code)
+ *
+ * ESC/POS二维码打印完整流程:
+ * 1. 设置二维码模块大小
+ * 2. 设置错误纠正等级
+ * 3. 存储二维码数据到打印机缓存
+ * 4. 执行二维码打印
+ * 5. 换行
+ *
+ * @param {string} content - 要编码到二维码中的文本内容
+ * @param {number} moduleSize - 二维码模块大小,范围1-16,默认6
+ * 数值越大,二维码尺寸越大
+ * 推荐值:4-8(适合58mm打印机)
+ * @param {string} errorLevel - 错误纠正等级,可选值:
+ * 'L' - Low (约7%纠错能力)
+ * 'M' - Medium (约15%纠错能力,默认)
+ * 'Q' - Quartile (约25%纠错能力)
+ * 'H' - High (约30%纠错能力)
+ *
+ * @returns {printerJobs} 返回this,支持链式调用
+ *
+ * @example
+ * // 打印默认大小的二维码
+ * printerJobs.qrcode('https://github.com/your-repo');
+ *
+ * @example
+ * // 打印大尺寸、高纠错等级的二维码
+ * printerJobs.qrcode('Hello World', 8, 'H');
+ *
+ * @example
+ * // 链式调用
+ * printerJobs
+ * .setAlign('ct')
+ * .print('扫码关注')
+ * .qrcode('https://your-website.com', 6, 'M')
+ * .lineFeed(2);
+ */
+printerJobs.prototype.qrcode = function (content, moduleSize = 6, errorLevel = 'M') {
+ // 参数验证和处理
+ if (!content || typeof content !== 'string') {
+ console.error('二维码内容不能为空且必须是字符串');
+ return this;
+ }
+
+ // 错误纠正等级映射:字符到ESC/POS代码
+ const errorLevelMap = {
+ 'L': 48, // 7%纠错能力
+ 'M': 49, // 15%纠错能力(推荐值)
+ 'Q': 50, // 25%纠错能力
+ 'H': 51 // 30%纠错能力
+ };
+
+ // 获取错误纠正等级代码,默认为M(中等)
+ const errorLevelCode = errorLevelMap[errorLevel.toUpperCase()] || 49;
+
+ // 内容长度检查(ESC/POS通常限制在7084字节以内)
+ if (content.length > 2000) {
+ console.warn(`二维码内容长度 ${content.length} 超过推荐值,可能导致打印失败`);
+ }
+
+ console.log('=== ESC/POS二维码打印参数 ===');
+ console.log(`内容: "${content}"`);
+ console.log(`内容长度: ${content.length} 字符`);
+ console.log(`模块大小: ${moduleSize}`);
+ console.log(`错误纠正等级: ${errorLevel} (代码: ${errorLevelCode})`);
+
+ // ESC/POS二维码打印标准流程
+ console.log('=== 开始生成二维码ESC/POS指令 ===');
+
+ // 步骤1:设置二维码模块大小
+ console.log('步骤1:设置模块大小');
+ const sizeCmd = commands.QR_CODE.QR_MODULE_SIZE(moduleSize);
+ console.log(`模块大小指令: ${sizeCmd.map(b => '0x' + b.toString(16).toUpperCase().padStart(2, '0')).join(' ')}`);
+ this._enqueue(sizeCmd);
+
+ // 步骤2:设置错误纠正等级
+ console.log('步骤2:设置错误纠正等级');
+ const levelCmd = commands.QR_CODE.QR_ERROR_LEVEL(errorLevelCode);
+ console.log(`纠错等级指令: ${levelCmd.map(b => '0x' + b.toString(16).toUpperCase().padStart(2, '0')).join(' ')}`);
+ this._enqueue(levelCmd);
+
+ // 步骤3:存储二维码数据到打印机缓存
+ console.log('步骤3:存储二维码数据');
+ const dataCmd = commands.QR_CODE.QR_STORE_DATA(content);
+ console.log(`数据存储指令长度: ${dataCmd.length} 字节`);
+ console.log(`数据存储指令: ${dataCmd.slice(0, 8).map(b => '0x' + b.toString(16).toUpperCase().padStart(2, '0')).join(' ')}...`);
+ this._enqueue(dataCmd);
+
+ // 步骤4:执行二维码打印
+ console.log('步骤4:执行二维码打印');
+ const printCmd = commands.QR_CODE.QR_PRINT;
+ console.log(`打印指令: ${printCmd.map(b => '0x' + b.toString(16).toUpperCase().padStart(2, '0')).join(' ')}`);
+ this._enqueue(printCmd);
+
+ // 步骤5:换行,确保后续内容位置正确
+ console.log('步骤5:添加换行');
+ console.log(`换行指令: 0x${commands.LF[0].toString(16).toUpperCase()}`);
+ this._enqueue(commands.LF);
+
+ console.log('=== 二维码ESC/POS指令生成完成 ===');
+
+ return this;
+};
+
+/**
+ * 打印位图
+ * @param {Uint8Array} bitmapData 位图数据
+ * @param {number} width 图片宽度(像素)
+ * @param {number} height 图片高度(像素)
+ */
+printerJobs.prototype.bitmap = function (bitmapData, width, height) {
+ const imageUtil = require('../utils/imageutil');
+ const commands = imageUtil.bitmapToEscPos(bitmapData, width, height);
+ this._enqueue(commands);
+ // 换行
+ this._enqueue(commands.LF);
+ return this;
+};
+
/**
* 清空任务
*/
diff --git a/utils/imageutil.js b/utils/imageutil.js
new file mode 100644
index 0000000..4cbdc70
--- /dev/null
+++ b/utils/imageutil.js
@@ -0,0 +1,193 @@
+/**
+ * 图片处理工具类
+ * 用于将图片转换为打印机可识别的位图指令
+ */
+
+/**
+ * 将图片转换为位图数据
+ * @param {string} imagePath 图片路径
+ * @param {number} maxWidth 最大宽度(像素)
+ * @param {number} maxHeight 最大高度(像素)
+ * @returns {Promise