open/pages/printer/printer.js

814 lines
21 KiB
JavaScript
Raw Permalink Normal View History

2025-02-04 10:02:50 +08:00
// index.js
// 获取应用实例\
import Dialog from '@vant/weapp/dialog/dialog';
import Notify from '@vant/weapp/notify/notify';
import util from '../../utils/util.js';
// const imgd=require('./imgd.js')
const printerJobs = require("../../printer/printerjobs")
const printerUtil = require("../../printer/printerutil")
const toArrayBuffer = require("to-array-buffer")
const {
Buffer
} = require("buffer")
let jobIdx = 0
const app = getApp()
let readyJobs=[]
let firstOpenLoad = true
Page({
data: {
dpr:1,
// 打印图片配置
printerImageConfig:{
width:200,
height:200,
},
// 是否连接
isConnect: false,
// 小程序版本
version: "",
// 设备列表
deviceList: [
// {
// name: "MP00",
// deviceId: "86:67:7A:66:88:81",
// RSSI: -99
// }
],
// 连接的设备 service
inactive: [],
// 连接的设备id
id: "",
// 连接的设备id
connectId: "",
// 历史打印
printDataList: [],
// 当前选择的打印数据
currentChosePaper: {
},
// 请求地址
baseUrl: "https://amdog.github.io",
// 是否正在打印
isPrinting: false
},
onHide() {
this.focusDisDevice(() => {
});
},
onShow() {
if (!this.data.id || (this.data.inactive.length == 0)) {
this.queryDeviceList(true);
}
},
onLoad(options) {
// 设置小程序版本
let miniInfo = wx.getAccountInfoSync()
console.log(miniInfo.miniProgram.version, "小程序版本")
this.setData({
version: miniInfo.miniProgram.version || "未知版本"
})
let id = '1111111111'
if (options && options.id) {
id = options.id
}
if (id) {
this.loadBizPaper(id)
}
let paperList = wx.getStorageSync("printerHistoryLocalPaper") || []
this.setData({
printDataList: paperList
})
},
//加载短链接的打印任务
loadBizPaper(id) {
let that = this
// 我用的是测试地址
wx.request({
url: this.data.baseUrl+"/public/test-printer-response.json?v",
method: "get",
success(res) {
// 如果我的链接失效了 返回error 那就看这个目录下的test-response.json文件
console.log("获取打印数据上下文==>", res.data)
// 更新打印历史
let list = that.data.printDataList
list = list.filter(it => {
return it && it.name != res.data.name
})
list.unshift(res.data)
that.setData({
printDataList: list
})
wx.setStorageSync("printerHistoryLocalPaper", list.slice(0, 20))
that.previewPrintPaper({}, res.data)
that.setData({
currentChosePaper:res.data
})
},
fail(err) {
Dialog.alert({
message: '错误码:[10001];获取打印任务失败了,' + JSON.stringify(err),
})
console.log(err);
}
})
},
// 预览打印单 如果没有val 那就是点击触发的
async previewPrintPaper(event, val) {
if (!val) {
val = event.currentTarget.dataset.val
};
await new Promise(res => {
setTimeout(() => {
res(1)
}, 500);
})
this.setData({
currentChosePaper: val,
showPreview: true
})
},
// 切换
closePaperPreview() {
this.setData({
showPreview: false
})
},
// 解析指令 返回解析完成的单子 buffer
async compileContextToBuffer() {
// =>PRINT_TEXT_CENTER_LARGE_BOLD=打印文本 居中 大号字体 加粗字体
// =>PRINT_TEXT_CENTER=
// =>PRINT_IMG_BASE64_URL=打印图片地址 必须为base64 并且直接返回
let util = {
job: null,
setAlign(cmd) {
if (cmd.indexOf("CENTER") > -1) {
return this.job.setAlign("CT")
}
if (cmd.indexOf("LEFT") > -1) {
return this.job.setAlign("LT")
}
if (cmd.indexOf("RIGHT") > -1) {
return this.job.setAlign("RT")
}
return this.job.setAlign("CT")
},
setSize(cmd) {
if (cmd.indexOf("LARGE") > -1) {
return this.job.setSize(2, 2)
}
return this.job.setSize(1, 1)
},
setBold(cmd) {
if (cmd.indexOf("BOLD") > -1) {
return this.job.setBold(true)
}
return this.job.setBold(false)
},
}
// 在这里拿到我的打印逻辑指令
let template =this.data.currentChosePaper.ext_print_template
// 去除空行
let templates = template.split("\n")
// 解析为对象
.map(v => {
let cmds = v.match(/=>([\s\S]*?)->/g);
return {
cmds: (cmds || [])[0],
content: v.split(cmds || "")[1]
}
}).filter(v => {
return v.cmds;
})
console.log("解析后指令=>", templates)
for (const val of templates) {
// 这是文字打印
if (val.cmds.indexOf("TEXT") > -1) {
console.log("文字打印指令",val)
util.job = new printerJobs();
util.setSize(val.cmds)
util.setAlign(val.cmds)
util.setBold(val.cmds)
util.job.print(printerUtil.fillAround(val.content, ""))
let textBuffer=util.job.buffer();
for (let i = 0; i < textBuffer.byteLength; i = i + 20) { //限制输入数据
// 把解析到一行的指令写到任务里面
readyJobs.push(textBuffer.slice(i, i + 20));
}
}
// 这是图片打印
if (val.cmds.indexOf("PRINT_IMAGE_DATA_URL") > -1) {
console.log("读取到打印base64图片" + val.content)
// 使用正则表达式解析高度和宽度
const regex = /H(\d+)_W(\d+)/;
const match = val.cmds.match(regex);
let height = 200; // 高度
let width = 200; // 宽度
if (match) {
height = parseInt(match[1]) ; // 高度
width = parseInt(match[2]); // 宽度
}
// 把宽高丢给全局
let config=this.data.printerImageConfig
config.width=width
config.height=height
this.setData({
printerImageConfig:config
})
// 这这里就重要了 获取图片的imagedata 对象
// imagedata 就是图片的rgba序列 四个代表一个像素 例如[244,211,23,255,r.....g.....b.....a]
let imageDataArr = await this.loadImageData(val.content)
// 等待打印图片
// 获取图片居中方式
let align="RIGHT"
//
if(val.cmds.indexOf("RIGHT") > -1){
align="RIGHT"
}
if(val.cmds.indexOf("LEFT") > -1){
align="LEFT"
}
if(val.cmds.indexOf("CENTER") > -1){
align="CENTER"
}
// 把imagedata对象解析为arraybuffer可打印指令
await this.imageDataPrintBuffer(imageDataArr, width, height,align);
}
}
},
// 加载图片
loadImageData(url) {
return new Promise(next => {
wx.request({
url: this.data.baseUrl + url,
method: "get",
success(res) {
if (res.data) {
// console.log("加载图片响应==>" + res.data)
// 这里返回的是imagedata 对象
// 老办法是用微信小程序canvas对象把图片渲染上去然后再用getimagedata取出来但是这个方法要折腾canvas 小程序太shi了图片渲染上去会错位之类的跟取出来的图片不一致
// 打印时候就会又黑边
// 如果你也一样用spring 这是获取二维码 并转为imagedata的代码
/*
@GetMapping ("/makeQrcodeImageDataJSONArray")
@Anonymous
public JSONArray makeQrcodeImageDataJSONArray(@RequestParam(required = true) String url,
@RequestParam(required = false) int width,
@RequestParam(required = false) int height) throws IOException {
try{
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
BitMatrix bitMatrix = new QRCodeWriter().encode(url, BarcodeFormat.QR_CODE, width, height, hints);
// 将 BitMatrix 转换为 BufferedImage
BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix);
// 获取图像的宽度和高度
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
// 遍历图像的所有像素点,并获取它们的 RGBA 值
List<Object> rgbaValues = new ArrayList<>();
for (int y = 0; y < imageHeight; y++) {
for (int x = 0; x < imageWidth; x++) {
int rgb = image.getRGB(x, y);
int alpha = (rgb >> 24) & 0xFF; // 获取 alpha 值
int red = (rgb >> 16) & 0xFF;
int green = (rgb >> 8) & 0xFF;
int blue = rgb & 0xFF;
rgbaValues.add(red);
rgbaValues.add(green);
rgbaValues.add(blue);
rgbaValues.add(alpha);
}
}
return new JSONArray(rgbaValues);
}catch(Exception e){
return new JSONArray();
}
}
*/
next(res.data)
} else {
Dialog.alert({
message: '错误码:[28001];尝试获取图片编码失败了',
}).then(() => {
// on close
});
}
},
fail(err) {
Dialog.alert({
message: '错误码:[18001];尝试获取图片编码失败了,' + JSON.stringify(err),
})
console.log(err);
}
})
})
},
/*
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
最主要的打印逻辑在这里 主要的逻辑
*/
// 开始打印单据
async toWriteSqPrint() {
if (this.data.isPrinting) {
return Notify({
type: 'warning',
message: '操作频繁'
});;
}
this.setData({
isPrinting: true
})
setTimeout(() => {
this.setData({
isPrinting: false
})
}, 2000);
wx.showLoading({
title: '正在编译打印机指令...',
})
// 这个全局变量是用来收集打印指令buffer的 每个元素都是arraybuffer类型 且不大于20字节
readyJobs=[];
// 编译打印指令 语法是我自己编的 图一乐就好 这个方法就是吧单据数据中的 ext_print_template 字段也就是我编的指令解析为可以直接打印的buffer 并存到readyJobs中
await this.compileContextToBuffer();
// 遍历处理
console.log("打印任务==>",readyJobs)
// 当前打印到哪里了
jobIdx=0;
// 开始去打印
await this.beforePrintClock()
wx.hideLoading()
console.log("结束=>",jobIdx)
},
// 处理图片
async imageDataPrintBuffer(imageDataArr, width, height,align) {
// 这里注释的 是我用的老方法 我打算用base64 直接获取imagedata对象 但是太难了 没有用这种方式
// const binaryString = this.baseAtob(imageDataArring.split(",")[1]);
// const byteArray = new Uint8ClampedArray(binaryString.length);
// for (let i = 0; i < binaryString.length; i++) {
// byteArray[i] = binaryString.charCodeAt(i);
// }
// const imgData = new ImageData(byteArray, width, height);
// // console.log("base64转为Unit8",byteArray);
// //
// imagedata 数组转为unit8array
const byteArray = new Uint8ClampedArray(imageDataArr.length);
for (let i = 0; i < imageDataArr.length; i++) {
byteArray[i] = imageDataArr[i];
}
// 黑白处理之类的 不用管
let arr = this.convert4to2(byteArray);
let data = this.convert8to1(arr);
// 除以8 具体看网上解释
let xl = width / 8
let xh = height
console.log("xlxh",xl,xh)
// 居中指令合集
let printAlignCommand = {
LEFT: [27, 97, 0], //居左
CENTER: [27, 97, 1], //居中
RIGHT: [27, 97, 2], //居右
};
// [27, 64] 代表初始化打印机 [29, 118, 48, 0, xl, 0, xh, 0]预先设置好 要给纸张留的空间 因为要放图片是吧
let beforeCmd= toArrayBuffer(Buffer.from([].concat([27, 64],printAlignCommand[align], [29, 118, 48, 0, xl, 0, xh, 0]), 'gb2312'))
// 打印结束后的指令
let afterCmd= toArrayBuffer(Buffer.from([].concat([27, 74, 3]), 'gb2312'))
const cmds = [].concat(data);
const buffer = toArrayBuffer(Buffer.from(cmds, 'gb2312'));
// console.log("打印图片最终buffer后==>",buffer);
// 先给全局readyJobs 添加开始打印图片指令再添加 20字节一段的图片数据 再添加结束打印指令
readyJobs.push(util.sendDirective([0x1B, 0x40]));
readyJobs.push(beforeCmd);
// 限制20字节
for (let i = 0; i < buffer.byteLength; i = i + 20) { //限制输入数据
readyJobs.push(buffer.slice(i, i + 20));
}
readyJobs.push(afterCmd);
},
// 异步打印 把数据按照切割好 异步发送
async beforePrintClock() {
let that = this;
// 必须要异步, 我看其它博主用的settimeout + for 循环最后发现其实就是同步的, 不用异步会断纸 因为打印机有缓存区 满了就断 不会打印了
for (const iterator of readyJobs) {
await new Promise(y=>{
// 把打印指令发到打印机
that.printBuffer(iterator,y)
})
}
},
// 打印内容
printBuffer(buffer,next) {
console.log('任务索引',jobIdx)
// console.log("打印buffer size",buffer.byteLength)
if (this.data.inactive.length == 0) {
next(++jobIdx);
return Dialog.alert({
message: '当前并无连接打印机',
})
}
let {
deviceId,
serviceId,
characteristicId
} = this.data.inactive[0]
wx.writeBLECharacteristicValue({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: characteristicId,
value: buffer,
success(res) {
next(++jobIdx)
},
fail(err) {
Dialog.alert({
message: '错误码:[12550];蓝牙设备数据写入失败,' + JSON.stringify(err),
})
}
})
},
// 转为黑白
convert4to2(res) {
let arr = []
for (let i = 0; i < res.length; i++) {
if (i % 4 == 0) {
let rule = 0.29900 * res[i] + 0.58700 * res[i + 1] + 0.11400 * res[i + 2];
if (rule > 200) {
res[i] = 0;
} else {
res[i] = 1;
}
arr.push(res[i]);
}
}
return arr;
},
//8合1
convert8to1(arr) {
let data = [];
for (let k = 0; k < arr.length; k += 8) {
let temp = arr[k] * 128 + arr[k + 1] * 64 + arr[k + 2] * 32 + arr[k + 3] * 16 + arr[k + 4] * 8 + arr[k + 5] * 4 + arr[k + 6] * 2 + arr[k + 7] * 1
data.push(temp);
}
return data;
},
/*
* 以下这些是连接打印机逻辑
*
*
*
*
*/
// 打开蓝牙
queryDeviceList(isShowEmit) {
let that = this;
// 先将设备断开
this.focusDisDevice(() => {
wx.openBluetoothAdapter({
mode: "central",
success(res) {
console.log(res, "openBluetoothAdapter success");
that.reflush(isShowEmit)
},
fail(err) {
Dialog.alert({
message: '错误码:[10012];蓝牙适配器打开失败了,' + JSON.stringify(err),
})
},
})
})
},
// 断开设备
disDevice() {
Dialog.confirm({
title: '提示',
message: '你确定要断开该设备连接吗?',
confirmButtonText: "断开"
})
.then(() => {
this.focusDisDevice(() => {
this.queryDeviceList()
})
})
.catch(() => {});
},
//强制断开设备
focusDisDevice(callback) {
let that = this
wx.closeBluetoothAdapter({
success(closres) {
console.log(closres, "closeBluetoothAdapter success");
Notify({
type: 'danger',
message: '设备已断开'
});
that.setData({
id: "",
inactive: [],
connectId: "",
isConnect: false
})
if (callback) {
callback()
}
},
fail(err) {
if (callback) {
callback()
}
},
complete() {
console.log("closeBluetoothAdapter complete");
}
})
},
// 搜索设备,刷新列表
reflush(isShowEmit) {
console.log("搜索设备")
let that = this
wx.startBluetoothDevicesDiscovery({
success(res) {
if (firstOpenLoad || isShowEmit) {
let history = wx.getStorageSync('historyLocalDevice')
if (history) {
Notify({
type: 'warning',
message: '尝试重新连接设备'
});
that.setData({
deviceList: [history]
})
that.concatDevice({}, history)
} else {
that.setData({
deviceList: []
})
}
firstOpenLoad = false;
}
that.listennerNewDevice()
},
fail(res) {
Dialog.alert({
message: '错误码:[10173];蓝牙设备搜索失败了,' + JSON.stringify(err),
})
console.log("startBluetoothDevicesDiscovery失败", res)
}
})
},
//监听新设备
listennerNewDevice() {
let that = this
wx.onBluetoothDeviceFound(function (res) {
let devices = res.devices;
console.log(res, "搜索设备")
if (devices[0].name != '') {
let list = that.data.deviceList
// 当前设备在设备列表中是否存在 如果不存在则添加,存在则修改
let idx = list.findIndex(de => {
return de.deviceId == devices[0].deviceId
})
if (idx >= 0) {
list[idx] = devices[0]
} else {
list.push(devices[0])
}
that.setData({
deviceList: list,
})
}
})
},
// 连接设备
concatDevice(item, history) {
let that = this
let device
if (history) {
device = history
} else {
device = item.currentTarget.dataset.item
}
wx.setStorageSync('historyLocalDevice', {
...device
})
this.setData({
connectId: device.deviceId,
isConnect: true
})
wx.createBLEConnection({
deviceId: device.deviceId,
timeout: 4000,
success(rse1) {
wx.getBLEDeviceServices({
deviceId: device.deviceId,
complete(rse2) {
if (rse2.errCode != 0) {
if (rse2.errCode == 10012) {
Dialog.alert({
message: '错误码:[18332];连接蓝牙超时,' + JSON.stringify(rse2),
})
that.setData({
isConnect: false
})
} else {
Dialog.alert({
message: '错误码:[13332];连接蓝牙异常,' + JSON.stringify(rse2),
})
that.setData({
isConnect: false
})
}
return
}
//查询特征值
that.setData({
inactive: []
})
for (let i in rse2.services) {
wx.getBLEDeviceCharacteristics({
deviceId: device.deviceId,
serviceId: rse2.services[i].uuid,
complete(rse3) {
let arrService = []
for (let j in rse3.characteristics) {
if (rse3.characteristics[j].properties.write) {
arrService.push({
deviceId: device.deviceId,
serviceId: rse2.services[i].uuid,
characteristicId: rse3.characteristics[j].uuid,
name: device.name
})
that.setData({
inactive: arrService,
id: device.deviceId,
isConnect: false
})
Notify({
type: 'success',
message: '蓝牙连接成功'
});
break;
}
}
},
fail(err) {
Dialog.alert({
message: '错误码:[11254];获取蓝牙设备特征值失败了,' + JSON.stringify(err),
})
}
})
}
},
fail(err) {
Dialog.alert({
message: '错误码:[10288];获取蓝牙服务列表失败了,' + JSON.stringify(err),
})
}
})
},
fail(err) {
Dialog.alert({
message: '错误码:[10233];与蓝牙设备创建连接失败了,' + JSON.stringify(err),
})
that.setData({
isConnect: false
})
},
complete() {
// 停止搜索
wx.stopBluetoothDevicesDiscovery({
success() {}
})
}
})
},
})