uni-app开发收银机安卓系统,并使用58打印机进行小票打印

945 阅读3分钟

技术要点总结

  • uniApp开发大屏安卓系统
  • 通过usb连接58打印机
  • 发送打印指令,进行小票打印

uni-app开发大屏安卓系统注意点

  • 开发大屏时,可以不拘泥于原有的单位rpx,可以直接使用px进行大屏适配
  • 博主因单位问题想了各种适配方案,最终突破了枷锁,直接使用了px,因为收银机是统一配置的(joke)

通过usb连接58打印机

发送打印指令,进行小票打印,保存即可使用(EscPosUtil.js)

class Esc {
  constructor(arg) {
    // 打印机纸宽58mm,页的宽度384,字符宽度为1,每行最多盛放32个字符
    // 打印机纸宽80mm,页的宽度576,字符宽度为1,每行最多盛放48个字符
    this.PAGE_WIDTH = 576;
    this.MAX_CHAR_COUNT_EACH_LINE = 48;
  }
  //字符串转字节序列
  stringToByte(str) {
    var bytes = new Array();
    var len, c;
    len = str.length;
    for (var i = 0; i < len; i++) {
      c = str.charCodeAt(i);
      if (c >= 0x010000 && c <= 0x10FFFF) {
        bytes.push(((c >> 18) & 0x07) | 0xF0);
        bytes.push(((c >> 12) & 0x3F) | 0x80);
        bytes.push(((c >> 6) & 0x3F) | 0x80);
        bytes.push((c & 0x3F) | 0x80);
      } else if (c >= 0x000800 && c <= 0x00FFFF) {
        bytes.push(((c >> 12) & 0x0F) | 0xE0);
        bytes.push(((c >> 6) & 0x3F) | 0x80);
        bytes.push((c & 0x3F) | 0x80);
      } else if (c >= 0x000080 && c <= 0x0007FF) {
        bytes.push(((c >> 6) & 0x1F) | 0xC0);
        bytes.push((c & 0x3F) | 0x80);
      } else {
        bytes.push(c & 0xFF);
      }
    }
    return bytes;
  }
  //字节序列转ASCII码
  //[0x24, 0x26, 0x28, 0x2A] ==> "$&C*"
  byteToString(arr) {
    if (typeof arr === 'string') {
      return arr;
    }
    var str = '',
      _arr = arr;
    for (var i = 0; i < _arr.length; i++) {
      var one = _arr[i].toString(2),
        v = one.match(/^1+?(?=0)/);
      if (v && one.length == 8) {
        var bytesLength = v[0].length;
        var store = _arr[i].toString(2).slice(7 - bytesLength);
        for (var st = 1; st < bytesLength; st++) {
          store += _arr[st + i].toString(2).slice(2);
        }
        str += String.fromCharCode(parseInt(store, 2));
        i += bytesLength - 1;
      } else {
        str += String.fromCharCode(_arr[i]);
      }
    }
    return str;
  }
  //居中
  Center() {
    var Center = [];
    Center.push(27);
    Center.push(97);
    Center.push(1);
    var strCenter = this.byteToString(Center);
    return strCenter;
  }
  //居左
  Left() {
    var Left = [];
    Left.push(27);
    Left.push(97);
    Left.push(0);
    var strLeft = this.byteToString(Left);
    return strLeft;
  }
  //居右
  Right() {
    var right = [];
    right.push(27);
    right.push(97);
    right.push(2);
    var strRight = this.byteToString(right);
    return strRight;
  }
  //标准字体
  Size1() {
    var Size1 = [];
    Size1.push(29);
    Size1.push(33);
    Size1.push(0);
    var strSize1 = this.byteToString(Size1);
    return strSize1;
  }
  //大号字体
  /*  放大1倍  n = 0
   *  长宽各放大2倍  n = 17 */
  Size2(n) {
    var Size2 = [];
    Size2.push(29);
    Size2.push(33);
    Size2.push(n);
    var strSize2 = this.byteToString(Size2);
    return strSize2;
  }
  // 字体加粗
  boldFontOn() {
    var arr = []
    arr.push(27)
    arr.push(69)
    arr.push(1)
    var cmd = this.byteToString(arr);
    return cmd
  }
  // 取消字体加粗
  boldFontOff() {
    var arr = []
    arr.push(27)
    arr.push(69)
    arr.push(0)
    var cmd = this.byteToString(arr);
    return cmd
  }
  // 打印并走纸n行
  feedLines(n = 1) {
    var feeds = []
    feeds.push(27)
    feeds.push(100)
    feeds.push(n)
    var printFeedsLines = this.byteToString(feeds);
    return printFeedsLines
  }
  // 切纸
  cutPaper() {
    var cut = []
    cut.push(29)
    cut.push(86)
    cut.push(49)
    var cutType = this.byteToString(cut);
    return cutType
  }
  // 开钱箱
  open_money_box() {
    var open = []
    open.push(27)
    open.push(112)
    open.push(0)
    open.push(60)
    open.push(255)
    var openType = this.byteToString(open)
    return openType
  }
  // 初始化打印机
  init() {
    var arr = []
    arr.push(27)
    arr.push(68)
    arr.push(0)
    var str = this.byteToString(arr)
    return str
  }
  /*
   设置左边距
   len:
   */
  setLeftMargin(len = 1) {
    var arr = []
    arr.push(29)
    arr.push(76)
    arr.push(len)
    var str = this.byteToString(arr)
    return str
  }
  // 设置打印区域宽度
  setPrintAreaWidth(width) {
    var arr = []
    arr.push(29)
    arr.push(87)
    arr.push(width)
    var str = this.byteToString(arr)
    return str
  }
  /**
   * @param str
   * @returns {boolean} str是否全是中文
   */
  isChinese(str) {
    return /^[\u4e00-\u9fa5]$/.test(str);
  }
  // str是否全含中文或者中文标点
  isHaveChina(str) {
    if (escape(str).indexOf("%u") < 0) {
      return 0
    } else {
      return 1
    }
  }
  /**
   * 返回字符串宽度(1个中文=2个英文字符)
   * @param str
   * @returns {number}
   */
  getStringWidth(str) {
    let width = 0;
    for (let i = 0, len = str.length; i < len; i++) {
      width += this.isHaveChina(str.charAt(i)) ? 2 : 1;
    }
    return width;
  }
  /**
   * 同一行输出str1, str2,str1居左, str2居右
   * @param {string} str1 内容1
   * @param {string} str2 内容2
   * @param {string} fillWith str1 str2之间的填充字符
   * @param {number} fontWidth 字符宽度 1/2
   *
   */
  inline(str1, str2, fillWith = ' ', fontWidth = 1) {
    const lineWidth = this.MAX_CHAR_COUNT_EACH_LINE / fontWidth;
    // 需要填充的字符数量
    let fillCount = lineWidth - (this.getStringWidth(str1) + this.getStringWidth(str2)) % lineWidth;
    let fillStr = new Array(fillCount).fill(fillWith.charAt(0)).join('');
    return str1 + fillStr + str2;
  }
  /**
   * 用字符填充一整行
   * @param {string} fillWith 填充字符
   * @param {number} fontWidth 字符宽度 1/2
   */
  fillLine(fillWith = '-', fontWidth = 1) {
    const lineWidth = this.MAX_CHAR_COUNT_EACH_LINE / fontWidth;
    return new Array(lineWidth).fill(fillWith.charAt(0)).join('');
  }
  /**
   * 文字内容居中,左右用字符填充
   * @param {string} str 文字内容
   * @param {number} fontWidth 字符宽度 1/2
   * @param {string} fillWith str1 str2之间的填充字符
   */
  fillAround(str, fillWith = '-', fontWidth = 1) {
    const lineWidth = this.MAX_CHAR_COUNT_EACH_LINE / fontWidth;
    let strWidth = this.getStringWidth(str);
    // 内容已经超过一行了,没必要填充
    if (strWidth >= lineWidth) {
      return str;
    }
    // 需要填充的字符数量
    let fillCount = lineWidth - strWidth;
    // 左侧填充的字符数量
    let leftCount = Math.round(fillCount / 2);
    // 两侧的填充字符,需要考虑左边需要填充,右边不需要填充的情况
    let fillStr = new Array(leftCount).fill(fillWith.charAt(0)).join('');
    return fillStr + str + fillStr.substr(0, fillCount - leftCount);
  }
}
export default Esc;

操作流程

  • 插件引入
// 获取打印机插件
const usbPrinterModule = uni.requireNativePlugin('singplugin-usbPrinter');
// 导入打印指令,目录结构请自行调整
import EscPosUtil from '@/common/utils/EscPosUtil.js';
// 监听USB连接断开事件
const globalEvent = uni.requireNativePlugin('globalEvent');
  • 初始化打印机
usbPrinterModule.initUSBPrinter((e) => {
    console.log('打印机初始化==>' + JSON.stringify(e));
    if (e.code === 'ok') {
      this.connectusbPrint(initFlg);
      return;
    }
    if (e.result === '请勿重复初始化') {
      this.connectusbPrint(initFlg);
      return;
    }
    if (!initFlg) this.toast('error', e.result);
  });
  • 连接打印机
// 链接打印机
   connectusbPrint(initFlg) {
      if (this.usbPrinterConnectFlg) return this.doPrint();
      // 连接设备
      usbPrinterModule.connectUSBPrinter((e) => {
        console.log('打印机连接==>' + JSON.stringify(e));
        if (e.code !== 'ok') {
           // 异常处理
        } else {
          this.usbPrinterConnectFlg = true;
          if (!initFlg) this.doPrint();
          this.bindPluginEvent();
        }
      });
    },
  • 开始打印(一个小票的打印模板)
    doPrintDeal(orderNo = '', payPrice = '', data, flg = '') {
      data = [
        { name: '杏花村清香型白酒  53度5斤装 送礼佳品  纯粮食酒', num: 1, price: 10 },
        { name: '西凤6年52度500ml', num: 12, price: 100 },
        { name: '西凤酒年份封坛10年45度单瓶(500mlX1)', num: 99, price: 999.99 }
      ];
      let Esc = new EscPosUtil();
      // 打印文字
      let strCmd = Esc.Center() + Esc.Size2(16) + Esc.boldFontOn() + this.userInfo.realName + flg + '\n';
      strCmd += Esc.boldFontOff() + Esc.Size1() + Esc.fillLine('-', 1.5) + '\n';
      strCmd += Esc.Size1() + '商品名称            总额(数量)' + '\n';
      strCmd += Esc.fillLine('-', 1.5) + '\n';
      // 商品信息
      data.forEach((item) => {
        strCmd += Esc.Left() + item.name + '\n';
        strCmd += Esc.Right() + '¥' + item.price + '(' + item.num + ')\n\n';
      });
      // 固定位置,调整格局,应该是分三份,没分最大有多少,这样来处理,比较好
      strCmd += Esc.fillLine('-', 1.5) + '\n';
      strCmd += Esc.Left() + '订单编号:' + orderNo + '\n';
      strCmd += Esc.Left() + '打印时间:' + dateDeal.dateFormat('Y-m-d H:i:s') + '\n';
      strCmd += Esc.Left() + '支付金额:¥' + payPrice + '\n';
      strCmd += Esc.fillLine('-', 1.5) + '\n';
      strCmd += Esc.Center() + '欢迎您下次光临!' + '\n';
      strCmd += Esc.feedLines(1) + '\n\n';
      // 开始打印
      usbPrinterModule.printText(
        {
          text: strCmd, //需要打印的字符串
          encoding: 'GBK' //打印字符串使用的编码格式,不填默认GBK
        },
        (e) => {
          console.log(e);
          if (e.code != 'ok') {
            this.toast('error', e.result);
          }
        }
      );
    },

踩坑指南

  • 自动初始化的时候,在onReady的时候,最好先运行一次初始化代码,这样可以提高点击打印按钮的通过率
  • 打印指令可以直接使用,上述EscPosUtil.js模块,这个地方可自行进行完善