愚钝的我终于用uniapp实现了热敏打印机的图片打印,也是走了不少弯路,现在来总结一下开发流程,以供他人品鉴这段赤石经历,

热敏打印机这里连接到打印机部分,因为是在一个电子称自带的平板上的安卓系统上的,貌似直接是电子秤已经连好了USB,所以这里用了一个uniapp的打印插件,名字叫:简单打印:https://ext.dcloud.net.cn/plugin?id=12381,而且这个插件是免费的,感谢开源大神,然后这里的插件的试用例子里,可以看到作者在打印命令里放了一个字符串,作者发送的是HEX也就是16进制的请求命令,这里可能新手看的一脸懵逼,实际上我拆分一下就好理解了,运行一下这个print.wirte,这个esc命令值是:1b401d57d0011b61011d21001d286b0400314132001d286b03003143061d286b03003145301d286b0e0031503068656c6c6f20776f726c641d286b03003151301c26c4e3bac31c2e0a1d683c1d77041d48021d6b430d363933373038323238303838311b6402,而我用这个在电子秤的热敏打印机打印出来是"200hellow world0",我们把这个数据问ai,ai解析数据如下•
1B 40 初始化打印机, 1D 57 D0 01 设置左边界, 1B 61 01 设置文本对齐居中,1D 21 00设置字符大小为正常大小......有点长不赘述了
大体使用后会得到如下:
一个二维码图像(扫描结果为 "hello world")。
文本 "你好"(在二维码之后)。
一个条码图像(类型 Code 128),条码下方有文本 "6937082280881"。
最后进纸2行,产生多行空白。

虽然不知道为什么ai说的打印内容比我实际打出来的多那么多(我的只有一行字符串...),但由此我们可知,你传给打印机什么esc数据,就会立即执行什么操作(废话)。但是如果这里依旧想着直接把汉字也转成HEX去执行,我发现没什么用,打出来的要么黑乎乎一片要么还是英文,(这里有人说是要转化成GBK之类的再转回来,但是我试了也没有成功,于是放弃这条路,当然你也可以试试),这里我转而想到打印带着文字的图片去实现打印小票的效果。

最后我寻找了不少教程,终于得到一个具体能走通的打印,不过我这里只做了一些笼统的操作,一些关于宽度适应切纸等东西写的并不完善(因为我自己都不完全懂),下面大体操作如下,choose获取图片,图片转化成数据,数据拼接成带有打印头的数组。

下面这个是我改完后的测试代码,我懒得再做删减了,只要依次点击1234按钮即可在小票上打印选择的图片,这里我写的很史,仅是刚达到能运行的程度,我都不知道这个实现方式是不是弯路。。。。如果你写出了更好的,或者没用安卓原生就能实现热敏打印打出汉字的功能,请大佬告诉我,让我拜读学习一下,有什么问题欢迎留言讨论,就到这里吧喵。

最下面有在实现过程中搜到的几篇好文,可以看看

<template>
	<div id="app">
	    <div class="container">
			
			<view class="print-card">
				<view class="row-between row-margin">
		
					<view class="col-item">
						<canvas   canvas-id="secondCanvas"  id="secondCanvas"
						 :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }">
						</canvas>
					</view>

					<button class="action-btn print-btn" @click="chooseImage()">
					    <i class="fas fa-print"></i> 1
					</button>
					<button class="action-btn print-btn" @click="chooseImage2()">
					    <i class="fas fa-print"></i> 2
					</button>
					
					<button class="action-btn print-btn" @click="chooseImage3EX()">
					    <i class="fas fa-print"></i> 3
					</button>

					<button class="action-btn print-btn" @click="topt()">
					    <i class="fas fa-print"></i> p
					</button>
					
				</view>
				
			</view>
			
			

			
			
	    </div>
	</div>
</template>

<script>
	
const print = uni.requireNativePlugin("XXH-USB-PRINT")

export default {
  data() {
    return {
		lastML:null,
			paperWidth:380,
			
			
			img:null,
			// 这边是进行设置画布高度
			canvasHeight:null,
			canvasWidth:null,
			lastBitmap:null,//得到的位图是也看看效果
			fuck:null,
			fuck2:null,
    };
  },
  onLoad(options) {
  },
  onShow(){
  },
  
  methods: {

	  // 新加一个data里的paperWidth,
chooseImage() {
      
      const ctx = uni.createCanvasContext("secondCanvas", this);
		  uni.chooseImage({
        success: (res) => {
          console.log("res", res);
          // if (res.statusCode === 200) {//fffffffffffffffffffffffffffffffffffffffffff
			  if(true){
			const tempFilePath = res.tempFilePaths[0];
            uni.getImageInfo({
              src: tempFilePath,
              success: (res) => {
                // 打印宽度须是8的整数倍,这里处理掉多余的,使得宽度合适,不然有可能乱码
                // paperWidth 用于设置打印机的宽度 最大打印宽度 产品说明书上有写 我的是576点
                const mw = this.paperWidth % 8; 
                // 计算出宽度以及高度
                const w = mw === 0 ? this.paperWidth : this.paperWidth - mw;
                // 等比算出图片的高度
                const h = Math.floor((res.height * w) / res.width);
                // 设置canvas宽高
				// console.log('mw,w和h')

                this.img = tempFilePath;
                // 这边是进行设置画布高度
                this.canvasHeight = h;
                this.canvasWidth = w;
                console.log("this.canvasHeight", this.canvasHeight);
                console.log("this.canvasWidth", this.canvasWidth);
                // 使用nextTick 让canvas宽度生效
                this.$nextTick(() => {
					console.log('我怎么不知道你画了')
					
                  // 在canvas 画一张图片
                  ctx.fillStyle = "rgba(255,255,255,1)";
                  ctx.clearRect(0, 0, w, h);
                  ctx.fillRect(0, 0, w, h);
                  // 进行绘画图片
                  ctx.drawImage(tempFilePath, 0, 0, w, h);
                  ctx.draw(false, () => {
					  console.log('我画完了,你随意')
				  });
                });
              },
              fail: (res) => {
				  
				  console.log(res)
			  },
            });
          }
        },
      });
    },
	
	
	
	  chooseImage2(){
		  console.log('下面是宽高')
		  console.log( this.canvasWidth)
		  console.log(this.canvasHeight)
		        uni.canvasGetImageData({
		          canvasId: "secondCanvas", // canvas 唯一id
		          x: 0,
		          y: 0,
		          width: this.canvasWidth, // 之前保存的画布宽高
		          height: this.canvasHeight,
		          success: (res) => {
					  console.log('回调走成功')
					  console.log(res)
		            const pix = res.data;
		              // 将图片数据转成位图数据--------------------------------------
		              let fff=this.overwriteImageData({
		                threshold: null,
		                imageData: pix,
		                width: this.canvasWidth,
		                height: this.canvasHeight,
		              })
					  console.log('我要看看ffff')
					  console.log(fff)
					  this.lastBitmap=fff
		            
		          },
		          complete: (e) => {},
				  fali:(e)=>{
					  console.log(e)
				  }
		        });


	  },
	  
	  chooseImage3EX(){
		  console.log('此时的lastBitbam')
		  console.log(this.lastBitmap)
		           const opt = { // 获取蓝牙配置文件
		             deviceId: null, // 设备id
		             serviceId: null, // 设备服务
		             characteristicId: null, // 设备特性
		             printAlign: "left",
		             lineByLine: true,
		             onProgress: (percentage) => {
		               // self.percentage = percentage; // 回调的进度
		             },
		             lasterSuccess: () => {
		             //   console.log("laster success");
		             //   self.printing = false;
		             //   self.percentage = 0;
		               // uni.showModal({
		               //   title: "提示",
		               //   content: "数据已发送完,请检查打印的内容是否正常",
		               //   showCancel: false,
		               //   confirmText: "好的",
		               // });
		             },
		           };
				   
				   let cr=this.getPrintImageWriteArray(opt,this.lastBitmap) 
				   console.log('最后的东西')
				   console.log(cr)
				   // let cr2
				   
				   const hexArray = cr.map(obj => {
				     return Object.keys(obj)
				       .sort((a, b) => parseInt(a) - parseInt(b)) // 按键的数字顺序排序
				       .map(key => {
				         const value = obj[key];
				         // 确保16进制格式为两位数
				         return value.toString(16).padStart(2, '0');
				       })
				       .join(''); // 拼接成连续HEX字符串
				   });
				   console.log('最后的最后的HEX')
				   console.log(hexArray)
				   this.lastML=hexArray

	  },
	  topt(){
	  		  console.log('喵')
	  		  this.lastML.forEach(e =>console.log(e));
	  		  print.initUsbDevice();
	  		  // print.write(this.fuck2)  
	  		  this.lastML.forEach(e => print.write(e));
	  },
	  // ****************************************************************************************************
	  //----------------------------------------------------------------------------------------------------------
	  
	   getImageCommandArray(opt = {}, imageInfo = {}) {
		   console.log('秘密资迎奥')
		   console.log(imageInfo)
	    // const lineByLine =
	    //   typeof opt.lineByLine !== "boolean" ? true : opt.lineByLine;
		  if(opt){
			  const lineByLine=true
		  	// const lineByLine =typeof opt.lineByLine !== "boolean" ? true : opt.lineByLine;
		  }else{
		  	const lineByLine=true
		  }
		  console.log('走完了123')
	    const width = imageInfo.width;
	    const h = imageInfo.height;
	    const xl = width % 256;
	    const xh = (width - xl) / 256;
	    const yl = h % 256;
	    const yh = (h - yl) / 256;
	    //打印图片的十进制指令数组
	    let command = [];
	  console.log(opt)
	    // if (lineByLine) {
			if(true){
	      // 分段逐行的指令
	      command = command.concat([29, 118, 48, 0, xl, xh, 1, 0]);
	    } else {
	      // 非分段逐行的指令
	      command = command.concat([29, 118, 48, 0, xl, xh, yl, yh]);
	    }
	  console.log('我走完了好像没问题')
	  console.log(command)
	    return command;
	  },
	  // 这个函数是做什么处理成unit8的代码,但是我看不出来有啥用----------------------------------------------------
	   getPrintImageWriteArray(opt = {}, imageInfo = {}) {
		   console.log('老的imgifno')
		   console.log(imageInfo)
		    const printCommand = {
		   left: [27, 97, 0], //居左
		   center: [27, 97, 1], //居中
		   right: [27, 97, 2], //居右
		   clear: [27, 64], //初始化
		   enter: [10],
		   };
	    // const lineByLine =
	    //   typeof opt.lineByLine !== "boolean" ? true : opt.lineByLine;
		  if(opt){
		  	const lineByLine =typeof opt.lineByLine !== "boolean" ? true : opt.lineByLine;
		  }else{
		  	const lineByLine=true
		  }
	    const width = imageInfo.width;
	    let arr = imageInfo.array;
	    let writeArray = [];
	    // const iniTcommand = []
	    //   .concat(printCommand.clear)
	    //   .concat(printCommand[opt.printAlign || "left"]);
		const iniTcommand = []
		  .concat(printCommand.clear)
		  .concat(printCommand["left"]);
	    const command = this.getImageCommandArray(opt, imageInfo);
	    writeArray.push(new Uint8Array(iniTcommand));
		console.log('下面是更好的')
	    // 分段逐行打印的数据
	    // if (lineByLine) {
			if(true){
	      for (let i = 0; i < arr.length / width; i++) {
	        const subArr = arr.slice(i * width, i * width + width);
	        const tempArr = command.concat(subArr);
	        writeArray.push(new Uint8Array(tempArr));
	      }
	    } else {
	      // 非逐行打印
	      writeArray.push(new Uint8Array(command.concat(arr)));
	    }
	    // writeArray.push(new Uint8Array([27, 74, 3]));
	    return writeArray;
	  },
	  
	  // convertToHexString(data) {
	  //   // 将对象转换为数组(确保按数字索引顺序)
	  //   const array = [];
	  //   Object.keys(data)
	  //     .filter(key => !isNaN(parseInt(key))) // 过滤数字索引
	  //     .sort((a, b) => parseInt(a) - parseInt(b)) // 按索引升序排序
	  //     .forEach(key => {
	  //       array.push(data[key]);
	  //     });
	    
	  //   // 转换为16进制并拼接
	  //   return array.map(byte => {
	  //     // 确保两位数表示(不足补0)
	  //     return byte.toString(16).padStart(2, '0');
	  //   }).join('');
	  // },

	  
	  overwriteImageData(data) {
	  		   console.log('走到转化里了')
	    let sendWidth = data.width,
	      sendHeight = data.height;
	    const threshold = data.threshold || 180;
	    let sendImageData = new ArrayBuffer((sendWidth * sendHeight) / 8);
	    sendImageData = new Uint8Array(sendImageData);
	    let pix = data.imageData;
	    const part = [];
	    let index = 0;
	    for (let i = 0; i < pix.length; i += 32) {
	      //横向每8个像素点组成一个字节(8位二进制数)。
	      for (let k = 0; k < 8; k++) {
	        const grayPixle1 = this.grayPixle(pix.slice(i + k * 4, i + k * 4 + (4 - 1)));
	        //阈值调整
	        if (grayPixle1 > threshold) {
	          //灰度值大于128位   白色 为第k位0不打印
	          part[k] = 0;
	        } else {
	          part[k] = 1;
	        }
	      }
	      let temp = 0;
	      for (let a = 0; a < part.length; a++) {
	        temp += part[a] * Math.pow(2, part.length - 1 - a);
	      }
	  
	      sendImageData[index++] = temp;
	    }
		console.log('看看转化后的width')
		console.log(sendWidth)
		console.log(sendHeight)
	    return {
	      array: Array.from(sendImageData),
	      // width: sendWidth / 8,
	  		  width: sendWidth / 8,
	      height: sendHeight,
	    };
	  },
	  grayPixle(pix) {
	  const rgbResulut = pix[0] * 0.299 + pix[1] * 0.587 + pix[2] * 0.114
	  		if (rgbResulut === 0) {
	  			return 255
	  		} else {
	  			return rgbResulut
	  		}
	  },
	// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


  }
};
</script>

非常详尽的文
https://juejin.cn/post/7297529039312158730
其他值得一看的,有些可能只起到启发作用,用起来可能失效
https://blog.csdn.net/Linxi_001/article/details/130867268
https://blog.csdn.net/weixin_45615791/article/details/125332309
https://www.jianshu.com/p/dd6ca0054298
https://ask.dcloud.net.cn/article/id-39002__page-5#reply
https://www.jianshu.com/p/4c6cd7563759
https://juejin.cn/post/7362537451170185252
https://blog.csdn.net/qq_44721831/article/details/143208615
转gbk中文转化的
https://www.jianshu.com/p/20419462e381

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐