diff --git a/pages/index/index.js b/pages/index/index.js
index 57ab3b8..3d3ef60 100644
--- a/pages/index/index.js
+++ b/pages/index/index.js
@@ -36,7 +36,12 @@ Page({
data: {
devices: [],
connected: false,
- chs: []
+ chs: [],
+ printing: false,
+ progress: 0,
+ totalPackages: 0,
+ sentPackages: 0,
+ statusMessage: ''
},
onUnload() {
this.closeBluetoothAdapter()
@@ -259,16 +264,81 @@ Page({
let buffer = printerJobs.buffer();
console.log('ArrayBuffer', 'length: ' + buffer.byteLength, ' hex: ' + ab2hex(buffer));
- // 1.并行调用多次会存在写失败的可能性
- // 2.建议每次写入不超过20字节
- // 分包处理,延时调用
+ this._sendBuffer(buffer);
+ },
+
+ // 发送Buffer数据并显示进度
+ _sendBuffer(buffer) {
const maxChunk = 20;
const delay = 20;
- for (let i = 0, j = 0, length = buffer.byteLength; i < length; i += maxChunk, j++) {
+ const length = buffer.byteLength;
+ const totalPackages = Math.ceil(length / maxChunk);
+
+ console.log('=== 分包发送开始 ===');
+ console.log('总长度:', length, '字节');
+ console.log('分包大小:', maxChunk, '字节');
+ console.log('总包数:', totalPackages);
+
+ this.setData({
+ printing: true,
+ totalPackages: totalPackages,
+ sentPackages: 0,
+ progress: 0,
+ statusMessage: '正在传输[0/' + totalPackages + ']'
+ });
+
+ for (let i = 0, j = 0; i < length; i += maxChunk, j++) {
let subPackage = buffer.slice(i, i + maxChunk <= length ? (i + maxChunk) : length);
- setTimeout(this._writeBLECharacteristicValue, j * delay, subPackage);
+
+ // 打印分包信息
+ const subPackageArray = Array.from(new Uint8Array(subPackage));
+ console.log(`分包 ${j + 1}/${totalPackages} (${subPackageArray.length}字节):`,
+ subPackageArray.map(b => '0x' + b.toString(16).padStart(2, '0')).join(' '));
+
+ setTimeout(this._writeBLECharacteristicValueWithProgress, j * delay, subPackage, j + 1, totalPackages);
}
},
+
+ // 带进度的蓝牙写入
+ _writeBLECharacteristicValueWithProgress(buffer, packageNumber, totalPackages) {
+ wx.writeBLECharacteristicValue({
+ deviceId: this._deviceId,
+ serviceId: this._serviceId,
+ characteristicId: this._characteristicId,
+ value: buffer,
+ success: (res) => {
+ console.log(`分包 ${packageNumber}/${totalPackages} 发送成功`);
+ this.setData({
+ sentPackages: packageNumber,
+ progress: Math.round((packageNumber / totalPackages) * 100),
+ statusMessage: '正在传输[' + packageNumber + '/' + totalPackages + ']'
+ });
+
+ if (packageNumber === totalPackages) {
+ console.log('=== 分包发送完成 ===');
+ setTimeout(() => {
+ this.setData({
+ printing: false,
+ statusMessage: '打印指令发送成功'
+ });
+ // 3秒后清除状态信息
+ setTimeout(() => {
+ this.setData({
+ statusMessage: ''
+ });
+ }, 3000);
+ }, 500);
+ }
+ },
+ fail: (res) => {
+ console.log(`分包 ${packageNumber}/${totalPackages} 发送失败:`, res);
+ this.setData({
+ printing: false,
+ statusMessage: '打印失败'
+ });
+ }
+ });
+ },
_writeBLECharacteristicValue(buffer) {
wx.writeBLECharacteristicValue({
deviceId: this._deviceId,
@@ -328,5 +398,108 @@ Page({
}
}
})
+ },
+
+ // 选择图片并打印
+ chooseImageAndPrint() {
+ wx.chooseImage({
+ count: 1,
+ sizeType: ['original', 'compressed'],
+ sourceType: ['album'],
+ success: (res) => {
+ const tempFilePath = res.tempFilePaths[0];
+ this._convertImageToBitmap(tempFilePath);
+ },
+ fail: (res) => {
+ console.log('chooseImage fail', res);
+ wx.showToast({
+ title: '选择图片失败',
+ icon: 'none'
+ });
+ }
+ });
+ },
+
+ // 将图片转换为位图
+ _convertImageToBitmap(imagePath) {
+ wx.getImageInfo({
+ src: imagePath,
+ success: (res) => {
+ const canvas = wx.createCanvasContext('printCanvas');
+ const canvasWidth = 384; // 58mm纸宽对应的像素宽度
+ const scale = canvasWidth / res.width;
+ const canvasHeight = Math.round(res.height * scale);
+
+ canvas.drawImage(imagePath, 0, 0, canvasWidth, canvasHeight);
+ canvas.draw(false, () => {
+ wx.canvasGetImageData({
+ canvasId: 'printCanvas',
+ x: 0,
+ y: 0,
+ width: canvasWidth,
+ height: canvasHeight,
+ success: (imageData) => {
+ this._convertToBitmap(imageData, canvasWidth, canvasHeight);
+ },
+ fail: (res) => {
+ console.log('canvasGetImageData fail', res);
+ wx.showToast({
+ title: '图片转换失败',
+ icon: 'none'
+ });
+ }
+ });
+ });
+ },
+ fail: (res) => {
+ console.log('getImageInfo fail', res);
+ wx.showToast({
+ title: '获取图片信息失败',
+ icon: 'none'
+ });
+ }
+ });
+ },
+
+ // 将图片数据转换为打印机可识别的位图
+ _convertToBitmap(imageData, width, height) {
+ const pixels = imageData.data;
+ const bitmap = new Uint8Array(Math.ceil(width * height / 8));
+
+ for (let y = 0; y < height; y++) {
+ for (let x = 0; x < width; x++) {
+ const pixelIndex = (y * width + x) * 4;
+ const r = pixels[pixelIndex];
+ const g = pixels[pixelIndex + 1];
+ const b = pixels[pixelIndex + 2];
+ const a = pixels[pixelIndex + 3];
+
+ // 转换为黑白(阈值128)
+ const gray = (r * 0.299 + g * 0.587 + b * 0.114) * (a / 255);
+ const isBlack = gray < 128;
+
+ if (isBlack) {
+ const byteIndex = Math.floor((y * width + x) / 8);
+ const bitIndex = 7 - ((y * width + x) % 8);
+ bitmap[byteIndex] |= (1 << bitIndex);
+ }
+ }
+ }
+
+ // 创建打印任务
+ const printerJobs = new PrinterJobs();
+ printerJobs.setAlign('ct').printImage(bitmap.buffer, width, height).println();
+
+ const buffer = printerJobs.buffer();
+ this._sendBuffer(buffer);
+ },
+
+ // 打印二维码测试
+ printQRCodeTest() {
+ const printerJobs = new PrinterJobs();
+ printerJobs.setAlign('ct').printQRCode('https://www.example.com', 6).println();
+
+ const buffer = printerJobs.buffer();
+ this._sendBuffer(buffer);
}
})
\ No newline at end of file
diff --git a/pages/index/index.wxml b/pages/index/index.wxml
index 4ebed7a..7fb426c 100644
--- a/pages/index/index.wxml
+++ b/pages/index/index.wxml
@@ -42,7 +42,24 @@
+
+
+
+
+
+ {{statusMessage}}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/index/index.wxss b/pages/index/index.wxss
index 7369601..a9b6c43 100644
--- a/pages/index/index.wxss
+++ b/pages/index/index.wxss
@@ -52,4 +52,35 @@
min-height: 2.58823529em;
line-height: 2.58823529em;
padding: 10rpx;
+}
+
+.print-status {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: rgba(0, 0, 0, 0.8);
+ color: white;
+ padding: 20rpx;
+ text-align: center;
+}
+
+.status-message {
+ font-size: 28rpx;
+ margin-bottom: 10rpx;
+}
+
+.progress-bar {
+ width: 100%;
+ height: 10rpx;
+ background: rgba(255, 255, 255, 0.3);
+ border-radius: 5rpx;
+ overflow: hidden;
+}
+
+.progress-fill {
+ height: 100%;
+ background: #007aff;
+ border-radius: 5rpx;
+ transition: width 0.3s ease;
}
\ No newline at end of file
diff --git a/printer/printerjobs.js b/printer/printerjobs.js
index fda31bb..5fb354d 100644
--- a/printer/printerjobs.js
+++ b/printer/printerjobs.js
@@ -152,6 +152,85 @@ printerJobs.prototype.clear = function () {
return this;
};
+/**
+ * 打印二维码 - 实现ESC/POS二维码打印指令
+ * 参考ESC/POS指令集:https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=143
+ * @param {string} content 二维码内容(支持数字、字母、符号)
+ * @param {number} size 二维码模块大小(1-16),默认值为6
+ * @returns {printerJobs} 返回当前实例,支持链式调用
+ */
+printerJobs.prototype.printQRCode = function (content, size = 6) {
+ // 二维码大小限制在1-16之间,超出范围自动修正
+ size = Math.max(1, Math.min(16, size));
+
+ console.log('=== 二维码打印指令开始 ===');
+ console.log('内容:', content);
+ console.log('模块大小:', size);
+
+ // 1. 设置二维码模块大小 (GS ( k 0x03 0x00 0x31 0x43 n)
+ // n: 模块大小,范围1-16,默认6
+ const setModuleSize = [0x1d, 0x28, 0x6b, 0x03, 0x00, 0x31, 0x43, size];
+ console.log('设置模块大小指令:', setModuleSize.map(b => '0x' + b.toString(16).padStart(2, '0')).join(' '));
+ this._enqueue(setModuleSize);
+
+ // 2. 设置二维码纠错级别 (GS ( k 0x03 0x00 0x31 0x45 n)
+ // n: 纠错级别,'0'=7%, '1'=15%, '2'=25%, '3'=30%,默认'0'
+ const setErrorCorrection = [0x1d, 0x28, 0x6b, 0x03, 0x00, 0x31, 0x45, 0x30];
+ console.log('设置纠错级别指令:', setErrorCorrection.map(b => '0x' + b.toString(16).padStart(2, '0')).join(' '));
+ this._enqueue(setErrorCorrection);
+
+ // 3. 存储二维码数据 (GS ( k pL pH 0x31 0x50 0x30 d1...dn)
+ // pL pH: 数据长度(包括3个控制字节)
+ // d1...dn: 二维码内容
+ let dataLength = content.length + 3; // 3个控制字节
+ let pL = dataLength % 256; // 低字节
+ let pH = Math.floor(dataLength / 256); // 高字节
+
+ const storeQRData = [0x1d, 0x28, 0x6b, pL, pH, 0x31, 0x50, 0x30];
+ console.log('存储二维码数据指令:', storeQRData.map(b => '0x' + b.toString(16).padStart(2, '0')).join(' '));
+ this._enqueue(storeQRData);
+
+ // 4. 添加二维码内容
+ let uint8Array = this._encoder.encode(content);
+ let encoded = Array.from(uint8Array);
+ console.log('二维码内容编码:', encoded.map(b => '0x' + b.toString(16).padStart(2, '0')).join(' '));
+ this._enqueue(encoded);
+
+ // 5. 打印二维码 (GS ( k 0x03 0x00 0x31 0x51 0x30)
+ const printQR = [0x1d, 0x28, 0x6b, 0x03, 0x00, 0x31, 0x51, 0x30];
+ console.log('打印二维码指令:', printQR.map(b => '0x' + b.toString(16).padStart(2, '0')).join(' '));
+ this._enqueue(printQR);
+
+ console.log('=== 二维码打印指令结束 ===');
+
+ return this;
+};
+
+/**
+ * 打印位图
+ * @param {ArrayBuffer} imageData 位图数据
+ * @param {number} width 位图宽度
+ * @param {number} height 位图高度
+ */
+printerJobs.prototype.printImage = function (imageData, width, height) {
+ // 计算位图数据长度
+ let dataLength = width * height;
+ let pL = dataLength % 256;
+ let pH = Math.floor(dataLength / 256);
+
+ // 发送GS v 0指令
+ this._enqueue([0x1d, 0x76, 0x30, 0x00]);
+ this._enqueue([width % 256, Math.floor(width / 256)]);
+ this._enqueue([height % 256, Math.floor(height / 256)]);
+
+ // 添加位图数据
+ let uint8Array = new Uint8Array(imageData);
+ let encoded = Array.from(uint8Array);
+ this._enqueue(encoded);
+
+ return this;
+};
+
/**
* 返回ArrayBuffer
*/