JS面向对象封装 ESC/POS 指令打印类

微信小程序蓝牙打印请搜索插件 ESCPOS指令打印 ,先申请,再V我50RMB可永久使用。
JS面向对象封装 ESC/POS 指令打印类_第1张图片
代码中用到的中文转码方法见:gbk.js gb2312编码字符转Uint8Array,解决打印机中文乱码问题

基类

命令规则参考 小程序插件文档 Printer类部分

import {isAscii,U2B} from './gbk.js';

const fontSize=12,
/*计算字符串长度(1个中文=2个英文字符)*/
charLen=str=>{
  let width=0;
  for(let i=0;i<str.length;i++){
    width+=isAscii(str.charCodeAt(i))?1:2;
  }
  return width;
},
ESC_POS={
  ALIGN:{
    C: [0x1b, 0x61, 0x01], // 居中
    L: [0x1b, 0x61, 0x00], // 左对齐
    R: [0x1b, 0x61, 0x02], // 右对齐
  },
  BEEP:[0x1b,0x07], // 蜂鸣器
  COLOR:{
    BLACK:[0x1b,0x72,0x00],
    RED:[0x1b,0x72,0x01]
  },
  /*文本格式*/
  TEXT: {
    NORMAL: [0x1b, 0x21, 0x00], // Normal text
    D_H: [0x1b, 0x21, 0x10], // Double height text
    D_W: [0x1b, 0x21, 0x20], // Double width text
    D_W_H: [0x1b, 0x21, 0x30], // Double width & height text
    UNDERL_OFF: [0x1b, 0x2d, 0x00], // Underline font OFF
    UNDERL_ON: [0x1b, 0x2d, 0x01], // Underline font 1-dot ON
    UNDERL_2: [0x1b, 0x2d, 0x02], // Underline font 2-dot ON
    BOLD_OFF: [0x1b, 0x45, 0x00], // Bold font OFF
    BOLD_ON: [0x1b, 0x45, 0x01], // Bold font ON
    ITALIC_OFF: [0x1b, 0x35], // Italic font ON
    ITALIC_ON: [0x1b, 0x34], // Italic font ON
    FONT_A: [0x1b, 0x4d, 0x00], // Font type A
    FONT_B: [0x1b, 0x4d, 0x01], // Font type B
    FONT_C: [0x1b, 0x4d, 0x02], // Font type C
  },
  LINE_SPACING:{
    LS_DEFAULT:[0x1b,0x32], //默认行高,30点
    LS_SET(size){return [0x1b,0x33,size]} //size点行高
  },
  CUT: {
    FULL: [0x1d, 0x56, 0x00], // 全切
    PART: [0x1d, 0x56, 0x01], // 半切
    FULL_TO: [0x1d, 0x56, 0x40], // 走纸到切纸位置+n/144英寸并全切
    A_TO: [0x1d, 0x56, 0x41], // 走纸到切纸位置+n/144英寸并半切
    B_TO: [0x1d, 0x56, 0x42], // 走纸到切纸位置+n/144英寸并半切
  },
  BARCODE: {
    TXT_OFF: [0x1d, 0x48, 0x00], // HRI barcode chars OFF
    TXT_ABV: [0x1d, 0x48, 0x01], // HRI barcode chars above
    TXT_BLW: [0x1d, 0x48, 0x02], // HRI barcode chars below
    TXT_BTH: [0x1d, 0x48, 0x03], // HRI barcode chars both above and below
    FONT_A: [0x1d, 0x66, 0x00], // Font type A for HRI barcode chars
    FONT_B: [0x1d, 0x66, 0x01], // Font type B for HRI barcode chars
    HEIGHT(h){return [0x1d,0x68,h]},// Barcode Height [1-255]
    WIDTH(w){return [0x1d,0x77,w]},// Barcode Width [2-6]
    HEIGHT_DEFAULT: [0x1d, 0x68, 0x64], // Barcode height default:100
    WIDTH_DEFAULT: [0x1d, 0x77, 0x01], // Barcode width default:1
    UPC_A: [0x1d, 0x6b, 0x00], // 0x41 11,12 48-57
    UPC_E: [0x1d, 0x6b, 0x01], // 0x42 11,12 48-57
    EAN13: [0x1d, 0x6b, 0x02], // 0x43 12,13 48-57
    EAN8: [0x1d, 0x6b, 0x03], // 0x44 7,8 48-57
    CODE39: [0x1d, 0x6b, 0x04], // 0x45 变长 32,36,37,43,45-57,65-90
    I25: [0x1d, 0x6b, 0x05], // 0x46 偶数 48-57 (ITF)
    CODEBAR: [0x1d, 0x6b, 0x06], // 0x47 变长 36,43,45-58,65-68 (NW7)
    CODE93: [0x1d, 0x6b, 0x07], // 0x48 变长 0-127
    CODE128: [0x1d, 0x6b, 0x08], // 0x49 变长 0-127
    CODE11: [0x1d, 0x6b, 0x09], // 0x4a 变长 48-57
    MSI: [0x1d, 0x6b, 0x0a], // 0x4b 变长 48-57
  },
  QRCODE:{
    SIZE(size){return [0x1D,0x28,0x6b,0x03,0x00,0x31,0x43,size]},
    CORRECT_L:[0x1D,0x28,0x6b,0x03,0x00,0x31,0x45,0x30], // 可覆盖%7,默认
    CORRECT_M:[0x1D,0x28,0x6b,0x03,0x00,0x31,0x45,0x31], // 可覆盖%15
    CORRECT_Q:[0x1D,0x28,0x6b,0x03,0x00,0x31,0x45,0x32], // 可覆盖%25
    CORRECT_H:[0x1D,0x28,0x6b,0x03,0x00,0x31,0x45,0x33], // 可覆盖%30
  },

  /**
   * [HARDWARE Printer hardware]
   * @type {Object}
   */
  HARDWARE: {
    INIT: [0x1b, 0x40], // Clear data in buffer and reset modes
    HW_SELECT: [0x1b, 0x3d, 0x01], // Printer select
    HW_RESET: [0x1b, 0x3f, 0x0a, 0x00], // Reset printer hardware
  },
  /**
   * [CASH_DRAWER Cash Drawer]
   * @type {Object}
   */
  CASH_DRAWER: {
    CD_KICK_2: [0x1b, 0x70, 0x00], // Sends a pulse to pin 2 []
    CD_KICK_5: [0x1b, 0x70, 0x01], // Sends a pulse to pin 5 []
  },
  /**
   * [MARGINS Margins sizes]
   * @type {Object}
   */
  MARGINS: {
    BOTTOM: [0x1b, 0x4f], // Fix bottom size
    LEFT: [0x1b, 0x6c], // Fix left size
    RIGHT: [0x1b, 0x51], // Fix right size
  },
  /**
   * [IMAGE_FORMAT Image format]
   * @type {Object}
   */
  IMAGE_FORMAT: {
    S_RASTER_N: [0x1d, 0x76, 0x30, 0x00], // Set raster image normal size
    S_RASTER_2W: [0x1d, 0x76, 0x30, 0x01], // Set raster image double width
    S_RASTER_2H: [0x1d, 0x76, 0x30, 0x02], // Set raster image double height
    S_RASTER_Q: [0x1d, 0x76, 0x30, 0x03], // Set raster image quadruple
  },
  /**
   * [BITMAP_FORMAT description]
   * @type {Object}
   */
  BITMAP_FORMAT: {BITMAP_S8:[0x1b,0x2a,0x00],BITMAP_D8:[0x1b,0x2a,0x01],BITMAP_S24:[0x1b,0x2a,0x20],BITMAP_D24:[0x1b,0x2a,0x21]},
  /**
   * [GSV0_FORMAT description]
   * @type {Object}
   */
  GSV0_FORMAT: {
    GSV0_NORMAL: [0x1d, 0x76, 0x30, 0x00],
    GSV0_DW: [0x1d, 0x76, 0x30, 0x01],
    GSV0_DH: [0x1d, 0x76, 0x30, 0x02],
    GSV0_DWDH: [0x1d, 0x76, 0x30, 0x03]
  },
};

export default class self {
  static PER_MM=8;
  static BRANDS=[
    {
      name:'群索',
      init:[31,27,31,128,4,5,6,68],
      cut:[29,12],
    }
  ];
  #connections=[];

  pushConnection(connection,brand=null){
    this.#connections.push({connection,brand});
  }
  updateConnection(connection,brand=null){
    const row=this.#connections.find(row=>row.connection===connection);
    if(row){
      row.brand=brand;
    }
  }

  printByESCPOS(lines,times=1,width=48){
    if(!this.#connections.length) return;
    let brand=this.#connections[0].brand,
    data=this.#buildESCPOS(lines,width,brand);
    this.#connections.forEach(row=>{
      if(brand!==row.brand){
        brand=row.brand;
        data=this.#buildESCPOS(lines,width,brand);
      }
      row.connection.sendData(data,times,'正在发送数据');
    });
  }
  
  /*构造ESC/POS指令*/
  #buildESCPOS(lines,width,brand){
    const pixels=width*self.PER_MM,
    data=Array.from(ESC_POS.HARDWARE.INIT);
    if(!brand) data.push(29,87,pixels%256,parseInt(pixels/256));
    data.push(27,74,10);
    let fontVolume=parseInt(pixels/fontSize);
    lines.forEach(i=>{
      if(i.align&&ESC_POS.ALIGN[i.align]) data.push(...ESC_POS.ALIGN[i.align]);
      if(i.color&&ESC_POS.COLOR[i.color]) data.push(...ESC_POS.COLOR[i.color]);
      if(i.text||i.fill){
        if(i.size&&ESC_POS.TEXT[i.size]){
          data.push(...ESC_POS.TEXT[i.size]);
          if(i.size=='D_W'||i.size=='D_W_H'){
            fontVolume=parseInt(pixels/fontSize/2);
          }else if(i.size=='NORMAL'||i.size=='D_H'){
            fontVolume=parseInt(pixels/fontSize);
          }
        }
        if(i.blod&&ESC_POS.TEXT['BOLD_'+i.blod]) data.push(...ESC_POS.TEXT['BOLD_'+i.blod]);
        if(i.font&&ESC_POS.TEXT['FONT_'+i.font]) data.push(...ESC_POS.TEXT['FONT_'+i.font]);
        if(i.underl&&ESC_POS.TEXT['UNDERL_'+i.underl]) data.push(...ESC_POS.TEXT['UNDERL_'+i.underl]);
        if(i.italic&&ESC_POS.TEXT['ITALIC_'+i.italic]) data.push(...ESC_POS.TEXT['ITALIC_'+i.italic]);
        if(i.text){
          if(i.r) i.text+=(new Array(fontVolume-(charLen(i.text)+charLen(i.r))%fontVolume).fill(i.fill||' ').join(''))+i.r;
          else if(i.fill){
            let count=fontVolume-charLen(i.text);
            if(count>0) i.text=(new Array(Math.ceil(count/2)).fill(i.fill).join(''))+i.text+(new Array(parseInt(count/2)).fill(i.fill).join(''));
          }
          data.push(...U2B(i.text,fontVolume));
        }else{
          if(fontVolume<charLen(i.fill)) i.fill='-';
          let str=new Array(parseInt(fontVolume/charLen(i.fill))).fill(i.fill).join('')
          if(charLen(str)<fontVolume){
            for(let i=charLen(str);i<fontVolume;i++){
              if(i%2==0) str+=' ';
              else str=' '+str;
            }
          }
          data.push(...U2B(str,fontVolume));
        }
      }else if(typeof i.line==='number') data.push(...(new Array(i.line).fill(10)));
      else if(i.beep){
        // data.push(0x1b,0x28,0x41,0x04,0x00,0x30,0x00,0x09,0x02)
      }else if(typeof i.barcode==='string'){
        let str=i.barcode.replace(/[^\x00-\x7F]/g,'');
        if(!str) return true;
        data.push(...ESC_POS.BARCODE.HEIGHT(60));
        let codeLen=str.length;
        if(/[^\x30-\x39]/.test(str)==false){
          if(codeLen==7||codeLen==8) data.push(...ESC_POS.BARCODE.EAN8);
          else if(codeLen==11) data.push(...ESC_POS.BARCODE.UPC_A);
          else if(codeLen==12||codeLen==13) data.push(...ESC_POS.BARCODE.EAN13);
          else data.push(...ESC_POS.BARCODE[codeLen%2==0?'I25':'CODE11']);
        }else if(/[\x00-\x1F\x21-\x23\x26-\x2A\x2C\x3A-\x40\x5B-\x7F]/.test(str)) data.push(...ESC_POS.BARCODE.CODE93);
        else data.push(...ESC_POS.BARCODE[/[\x20\x25\x45-\x5A]/.test(str)?'CODE39':'CODEBAR']);
        data.push(...str.split('').map(c=>c.charCodeAt(0)),0x00);
      }else if(i.qrcode){
        data.push(27,74,10);
        let poi,
        p=parseInt(i.size)*self.PER_MM,
        buffer=U2B(i.qrcode),
        len=buffer.length+3;
        if(p<80){p=80}else if(p>1000){p=1000}
        if(len<29){poi=19}else if(len<54){poi=23}
        else if(len<85){poi=27}else{poi=len<119?31:35}
        data.push(...ESC_POS.QRCODE.SIZE(p>poi?(p/poi).toFixed():1));
        data.push(...ESC_POS.QRCODE.CORRECT_M);
        data.push(29,40,107,len%256,parseInt(len/256),49,80,48,...buffer) //可能有126字节限制
        data.push(29,40,107,3,0,49,81,48)
        data.push(27,74,10)
      }else if(i.raster) data.push(29,118,48,0,i.x%256,parseInt(i.x/256),i.y%256,parseInt(i.y/256),...i.raster,27,74,10);
      /* // 以光栅格式绘图(Function 112),暂时不可用,可能是设备支持的问题
      data.push(29,40,76,4,0,48,1,51,51)
      data.push(29,40,76,17,0,48,112,48,1,1,49,56,0,1,0,255,255,255,255,255,255,255)
      data.push(29,40,76,2,0,48,2)
      
      //页模式,部分指令目前设备不支持
      data.push(27,76) //进入页模式
      data.push(27,36,0,0) // X归零
      data.push(29,36,0,0) // Y归零,设备不支持Y向位移
      data.push(...ESC_POS.FF) //输出缓冲区并回到标准模式 */
      if(i.next){
        data.push(...(brand?brand.cut:ESC_POS.CUT.FULL));
      }else if(i.cut&&ESC_POS.CUT[i.cut]) data.push(...ESC_POS.CUT[i.cut]);
    });
    return data;
  }
}

基于低功耗蓝牙封装的类

运行在企业微信、飞书等App内的网页可采用平台提供的JS-SDK封装BLEConnect类,具体参考:【附企业微信bridge封装】基于JS-SDK的低功耗蓝牙连接类BLEConnect

import BLEConnect from '../../devices/BLEConnect';
import BasePrinter from '../../devices/Printer';

class Printer extends BasePrinter {
  BLE=null;
  setBLE(...args){
    if(!this.BLE){
      // 第二个参数仅供参考
      this.BLE=new BLEConnect(['write'],async ()=>{
        const ch=await appEnv.popupMenu(
          Printer.BRANDS.map(b=>({label:b.name})),
          '打印机是否下列品牌?'
        );
        if(ch===null){
          this.updateConnection(this.BLE);
          this.BLE.maxPack=200;
          return null;
        }else{
          this.updateConnection(this.BLE,Printer.BRANDS[ch]);
          this.BLE.maxPack=200;
          return Printer.BRANDS[ch].init;
        }
      });
      this.pushConnection(this.BLE);
    }
    return this.BLE.setDevice(...args);
  }
}

你可能感兴趣的:(javascript,前端,node.js)