基于Native.js 实现的连接蓝牙打印机

  • 打印效果图
  • 核心代码
  • 测试代码
  • 运行设备及环境
  • PS:
  • PPS:
  • Demo


打印效果图

Java 蓝牙打印图片 h5 蓝牙打印_初始化

核心代码

/**
 * @Description: 蓝牙打印类  基于h5+ Native.js
 * @Author: EricLee
 * @Date: 2020-10-14 13:53:23
 * @Param: mod
 * @Return: $
 */

export const Bluetooth = function () {
// 全局 变量
  let main = null,
    BluetoothAdapter = null,
    UUID = null,
    uuid = null,
    BAdapter = null,
    device = null,
    bluetoothSocket = null,
    outputStream = null,
    OutputStreamWriter = null,
    writer = null

  this.status = 0  // 和设备的连接状态: 0 未连接   1 连接中 2 已连接 (可以打印) 注: ** 此状态不是 手机蓝牙和设备的配对状态 **
  // 初始化
  this.initState = function () {
    main = plus.android.runtimeMainActivity()
    BluetoothAdapter = plus.android.importClass('android.bluetooth.BluetoothAdapter')
    BAdapter = BluetoothAdapter.getDefaultAdapter()
    UUID = plus.android.importClass('java.util.UUID')
    uuid = UUID.fromString('00001101-0000-1000-8000-00805F9B34FB')
    this.status = 1
    this.queryBindDevice()
  }
  // 获取配对设备 mac地址
  this.queryBindDevice = function () {
    var lists = BAdapter.getBondedDevices()
    plus.android.importClass(lists)
    // var resultDiv = document.getElementById('bluetooth_list')
    var iterator = lists.iterator()
    plus.android.importClass(iterator)
    console.log('==> 设备列表长度', lists.size())
    if (lists.size() == 0) {
      mui.toast('连接失败!未检测到已配对成功的设备!')
      return
    }
    if (lists.size() > 1) {
      mui.toast('连接失败!检测到多个配对成功设备!')
      return
    }
    while (iterator.hasNext()) {
      var d = iterator.next()
      plus.android.importClass(d)
      console.log(d.getAddress())
      console.log(d.getName())
      this.createConnect(d.getAddress()) // 创建连接
    }
  }
  // 建立连接
  this.createConnect = function (mac) {
    if (!mac) {
      mui.toast('连接失败!未能获取设备MAC地址!')
      this.status = 0
      return
    }
    device = BAdapter.getRemoteDevice(mac) // 连接打印机
    plus.android.importClass(device)
    // 只需建立一次连接,多次调用不能正常打印 !!!
    bluetoothSocket = device.createInsecureRfcommSocketToServiceRecord(uuid)
    plus.android.importClass(bluetoothSocket)

    if (!bluetoothSocket.isConnected()) {
      console.log('断开了,需要重新连接,连接中')
      bluetoothSocket.connect()
    }
    mui.toast('打印机已准备就绪,可以打印!')
    this.status = 2
    // 注册打印类
    outputStream = bluetoothSocket.getOutputStream()
    plus.android.importClass(outputStream)
    OutputStreamWriter = plus.android.importClass('java.io.OutputStreamWriter')
    writer = new OutputStreamWriter(outputStream, 'GBK')
    plus.android.importClass(writer)
  }
  // 关闭IO 关闭连接
  // 关闭页面时必需调用该方法,要不下次不能正常连接设备 !!!
  this.closeConnect = function () {
    bluetoothSocket.close()
    outputStream.close()
    OutputStreamWriter.close()
    bluetoothSocket = null
    outputStream = null
    OutputStreamWriter = null
    device = null
    this.status = 0
  }
  // 走纸 n = 点行数
  this.feedPoint = function (n) {
    const point = n || 8
    writer.write(0x1B)
    writer.write(0x4A)
    writer.write(point) // 点行 8 点 = 1mm
    writer.flush()
  }
  // 走纸 n = 行数
  this.feedLine = function (n) {
    const line = n || 1
    writer.write(0x1B)
    writer.write(0x64)
    writer.write(line) // 行数
    writer.flush()
  }
  //  设置左边距
  this.setLeftMargin = function (n, m) {
    writer.write(0x1D)
    writer.write(0x4C)
    writer.write(n) // 行数
    writer.write(m) // 行数
    writer.flush()
  }
  // 打印空行 linNum 行数
  this.printLine = function (lineNum) {
    for (let i = 0; i < lineNum; i++) {
      writer.write('\n')
    }
    writer.flush()
  }
  // 设置打印 位置 // 0 右 1 中 2 右
  this.setPrintPosition = function (n) {
    let m = n || 1
    writer.write(0x1B)
    writer.write(0x61)
    writer.write(m) // 0 右 1 中 2 右
    writer.flush()
  }
  // 设置绝对打印位置
  this.setPrintLocation = function (light, weight) {
    writer.write(0x1B)
    writer.write(0x24)
    writer.write(light)  // 0≤ light ≤ 255
    writer.write(weight)  // 0≤ weight ≤ 2
    writer.flush()
  }
  // 打印空白(一个Tab的位置,约4个汉字)
  this.printTabSpace = function (n) {
    for (let i = 0; i < n; i++) {
      writer.write('\t')
    }
    writer.flush()
  }
  // 设置/解除字符旋转模式
  // 0解除旋转模式       1设置90°顺时针旋转模式         2设置180°顺时针旋转模式        3设置270°顺时针旋转模式
  this.setPrintRotate = function (n) {
    writer.write(0x1B)
    writer.write(0x56)
    writer.write(n)
    writer.flush()
  }
  // 打印位图  todo
  this.printBitmap = function (m, data) {
    writer.write(0x1B)
    writer.write(0x2A)
    writer.write(m)
    writer.write(data)
  }
  // 字符缩放
  this.setCharacterScale = function (n) {
    // 打印倍宽高
    if (n == 1) {
      writer.write(0x1B)
      writer.write(0x21)
      writer.write(16)
      writer.flush()

      writer.write(0x1B)
      writer.write(0x21)
      writer.write(32)
      writer.flush()
    } else {
      writer.write(0x1B)
      writer.write(0x21)
      writer.write(0)
      writer.flush()
    }
  }
  // 打印初始化 每次打印前必须调用!!!
  this.initPrinter = function () {
    writer.write(0x1B)
    writer.write(0x40)
    writer.flush()
  }
  //  打印文字 并换行
  this.printTextNewLine = function (byteStr) {
    if (!main) {
      mui.toast('设备未进行配对!')
      return
    }
    var bytes = plus.android.invoke(byteStr, 'getBytes', 'gbk')
    console.log(bytes)

    outputStream.write(bytes)
    outputStream.flush()

    // 换行
    writer.write('\n')
    writer.flush()
    console.log('print ')
  }
  // 打印字符串方法 byteStr 只能是字符串
  this.printText = function (byteStr, l, w) {
    if (!main) {
      mui.toast('设备未进行配对!')
      return
    }
    var bytes = plus.android.invoke(byteStr, 'getBytes', 'gbk')
    console.log(bytes)

    outputStream.write(bytes)
    outputStream.flush()
    console.log('print ')
    // device = null
  }
  /**
   * @Description: 二维码打印
   * @Author: EricLee
   * @Date: 2020-10-15 15:16:10
   * @Param: byteStr {String} 要打印的内容
   * @Return: void
   */
  this.printQrcode = function (byteStr) {
    if (!main) {
      mui.toast('设备未进行配对!')
      return
    }
    // init
    var moduleSize = 8
    var bytes = plus.android.invoke(byteStr, 'getBytes', 'gbk')
    var length = bytes.length

    console.log(length)
    // 缓存二维码数据
    writer.write(0x1D)// init
    writer.write('(k')// adjust height of barcode
    writer.write(length + 3) // pl
    writer.write(0) // ph
    writer.write(49) // cn
    writer.write(80) // fn
    writer.write(48) //
    writer.write(byteStr)
    // 二维码纠错等级
    writer.write(0x1D)
    writer.write('(k')
    writer.write(3)
    writer.write(0)
    writer.write(49)
    writer.write(69)
    writer.write(48)
    // 设置二维码块大小
    writer.write(0x1D)
    writer.write('(k')
    writer.write(3)
    writer.write(0)
    writer.write(49)
    writer.write(67)
    writer.write(moduleSize)
    // 打印已缓存的数据二维码
    writer.write(0x1D)
    writer.write('(k')
    writer.write(3) // pl
    writer.write(0) // ph
    writer.write(49) // cn
    writer.write(81) // fn
    writer.write(48) // m

    writer.flush()
    // 二维码打印 结束

    console.log('print Qrcode')
  }
}

测试代码

<template>
  <div>
    <div>
      <Button @click="_initBluetooth">{{ statusList[status] }}</Button>
    </div>
    <div>
<!--      <Button @click="_printText('DC:0D:30:9B:AC:99')">打印</Button>-->
      <Button @click="_printTest(msg)">打印</Button>
      <br/>
      <Button @click="_printQrcode(code)">打印二维码</Button>
    </div>
    <div>
      <br/>
      <Input v-model="line" placeholder="走纸行数" />
      <Button @click="feed(line)">走纸</Button>
    </div>
    <div>
      <br/>
      <Input v-model="marginNum" placeholder="定位" />
      <Button @click="_setPrintPosition(marginNum)">定位</Button>
    </div>
    <div>
      <br/>
      <Button @click="_setCharacterScale(1)">放大</Button>
      <Button @click="_setCharacterScale(0)">缩小</Button>
    </div>
    <div>
      <br/>
      <Input v-model="light" placeholder="light" />
      <Input v-model="weight" placeholder="weight" />
      <Button @click="_setPrintLocation(light,weight)">绝对位置</Button>
    </div>
    <div>
      <br/>
      <Input v-model="rotateNum" placeholder="旋转" />
      <Button @click="_setPrintRotate(rotateNum)">旋转</Button>
    </div>
    <div>
      <br/>
      <Button @click="_closeConnect()">关闭连接</Button>
    </div>
  </div>
</template>

<script>
  import {Bluetooth} from '../lib/bluetooth'
  export default {
    name: 'printTest',
    data () {
      return {
        msg: '样品内容\n' + '101013Q73898\n' + '2020-10-10 09:33:33\n' + '张三三\n',
        code: '191013Q7398',
        mac: '',
        line: 8,
        light: 0,
        weight: 0,
        rotateNum: 0,
        marginNum: 1,
        initFlag: false,
        bluetoothPrinter: null,
        text: '配对',
        status: 0,
        statusList: [
          '待连接',
          '连接中',
          '已连接'
        ]
      }
    },
    mounted () {
      this._initBluetooth()
    },
    destroyed () {
      this._closeConnect()
    },
    watch: {
      status () {
        console.log('status==>', this.status)
        if (this.status == 2) {
          this._loading(false)
        }
      }
    },
    methods: {
      _loading (flag) {
        if (flag) {
          this.$vux.loading.show({
            text: 'Loading'
          })
          setTimeout(() => {
            this.$vux.loading.hide()
          }, 5000)
        } else {
          this.$vux.loading.hide()
        }
      },
      // 初始化并配对设备
      _initBluetooth () {
        if (!this.bluetoothPrinter) {
          this.bluetoothPrinter = new Bluetooth()
          this._loading(true)
          console.log(this.bluetoothPrinter.status)
        }
        this.bluetoothPrinter.initState()
        this.status = this.bluetoothPrinter.status
      },
      _printTest (msg) {
        this.bluetoothPrinter.initPrinter()
        this.bluetoothPrinter.setPrintPosition(1) // 居中 打印
        this.bluetoothPrinter.printQrcode(msg)
        this.bluetoothPrinter.feedPoint(20)
        this.bluetoothPrinter.printTextNewLine('样品111')
        this.bluetoothPrinter.printTextNewLine('101013Q73898')
        this.bluetoothPrinter.printTextNewLine('2020-10-10 09:33:33')
        this.bluetoothPrinter.printTextNewLine('张三三')
        this.bluetoothPrinter.printLine(3)

      },
      _printQrcode (msg) {
        this.bluetoothPrinter.initPrinter()
        this.bluetoothPrinter.printTabSpace(10)
        // this.bluetoothPrinter.setPrintPosition(2)
        this.bluetoothPrinter.printQrcode(msg)
      },
      feed (n) {
        this.bluetoothPrinter.feedPoint(n)
      },
      _closeConnect () {
        if (this.bluetoothPrinter) {
          this.bluetoothPrinter.closeConnect()
        }
      },
      _setPrintLocation (l, w) {
        this.bluetoothPrinter.setPrintLocation(l, w)
      },
      _setPrintRotate (l) {
        this.bluetoothPrinter.setPrintRotate(l)
      },
      _setCharacterScale (l) {
        this.bluetoothPrinter.setCharacterScale(l)
      },
      _setPrintPosition (l) {
        this.bluetoothPrinter.setPrintPosition(l)
      }
    }
  }
</script>

<style scoped>

</style>

运行设备及环境

IDE:Hbuilder X 2.8.13
测试机型:红米 note4 android6.0
测试打印机:科密PB8001
打印指令类型:ESC/POS ESC/POS指令参考文档

PS:

本文未提供连接蓝牙设备的方法,如有需要请点传送门 —» H5+连接蓝牙打印机

PPS:

只初始化一次即可持续打印实现
可以把 new Bluetooth() 的实例的初始化放到store中进行管理,在需要的组件注入实例。代码如下:

// store
const printer = {
  namespaced: true,
  state: {
    bluetoothPrinter: null
  },
  mutations: {
    setBluetoothPrinter (state, payload) {
      state.bluetoothPrinter = payload
    }
  }
}


// component
export default {
  data () {
    return {
      pagination: {
        page: 0,
        rows: 15
      },
      bottomList: [
        {name: '添加', color: '#0D55A7'},
        {name: '提交', color: '#11C827'},
        {name: '标签打印', color: '#F7AC0C'}
      ],
      records: [],
      recordsList: [],
      printData: [],
      refresh: false, // 上拉刷新
      loading: false, // 下拉加载
      finished: false, // 是否获取到了所有数据
      submitStatus: false,
      status: 0
    }
  },
  computed: {
    ...mapState('printer', {
      bluetoothPrinter: 'bluetoothPrinter'
    })
  },
  watch: {
    status () {
      console.log('status==>', this.status)
      if (this.status == 2) {
        this._loading(false)
      }
    }
  },
  mounted () {
    this._initBluetooth()
  },
  destroyed () {
    console.log('destroyed!')
    // this._closeConnect()
  },
  methods: {
    _add () {
      this.$store.commit('steel/removeInsSteelItem')
      this.$jump('typeA-insScrapSteelContractEdit', {sampleCode: '添加/编辑基本信息'})
    },
    // 全选
    _allCheck () {
      if (this.records.length === this.recordsList.length) {
        this.records = []
        return
      }
      this.records = this.recordsList.map(item => item.id)
    },
    // 提交
    async _submitById () {
      if (!this.records.length) {
        this.$toast('至少选择一个')
        return
      }
      if (this.submitStatus) return
      this.submitStatus = true
      this.$vux.loading.show({
        text: '正在提交中...'
      })
      const result = await waitInsScrapSteelSubmit(this.records)
      if (result) {
        this._resultChange('提交成功!')
        this.$vux.loading.hide()
      } else {
        this._resultChange('网络问题,请重新提交!')
        this.$vux.loading.hide()
      }
    },
    _resultChange (msg) {
      this.$toast(msg)
      this._onRefresh()
    },
    // 上拉刷新
    async _onRefresh () {
      this.pagination.page = 0
      this.recordsList = []
      this.printData = []
      await this._onLoad()
      this.refresh = false
    },
    // 下拉加载
    async _onLoad () {
      this.pagination.page++
      await this._waitInsScrapSteel()
      this.loading = false
      return true
    },
    _loading (flag) {
      if (flag) {
        this.$vux.loading.show({
          text: '设备配对中'
        })
        setTimeout(() => {
          this.$vux.loading.hide()
        }, 5000)
      } else {
        this.$vux.loading.hide()
      }
    },
    // 初始化并配对设备
    _initBluetooth () {
      if (!this.bluetoothPrinter) {
        const obj = new Bluetooth()
        this._loading(true)
        this.$store.commit('printer/setBluetoothPrinter', obj)
        console.log(this.bluetoothPrinter.status)
        this.bluetoothPrinter.initState()
        this.status = this.bluetoothPrinter.status
      }
    },
    _closeConnect () {
      if (this.bluetoothPrinter) {
        this.bluetoothPrinter.closeConnect()
      }
    },
    // 待打印数据
    _selectData () {
      if (!this.records.length) {
        this.$toast('至少选择一个')
        return false
      } else {
        const {records, recordsList} = this
        this.printData = []
        let data = []
        for (let i = 0; i < records.length; i++) {
          for (let j = 0; j < recordsList.length; j++) {
            if (records[i] === recordsList[j].id) {
              data.push(recordsList[j])
            }
          }
        }
        this.printData = [...data]
      }
    },
    // 打印方法
    _print (data) {
      this.bluetoothPrinter.initPrinter()
      this.bluetoothPrinter.setPrintPosition(1) // 居中 打印
      this.bluetoothPrinter.printQrcode(data.sampleCode) // 二维码 样品编号
      this.bluetoothPrinter.feedPoint(20)
      this.bluetoothPrinter.printTextNewLine(data.name) // 物料名称
      this.bluetoothPrinter.printTextNewLine(data.sampleCode) // 二维码 样品编号
      this.bluetoothPrinter.printTextNewLine(this.$getTime(data.obtainSampleTime, true)) // 取样时间
      this.bluetoothPrinter.printTextNewLine(data.obtainSampler) // 取样人
      this.bluetoothPrinter.feedLine(4)
    },
    // 打印标签
    _printLabel () {
      this._selectData()
      const {printData} = this
      const len = printData.length
      for (let i = 0; i < len; i++) {
        this._print(printData[i])
      }
    },
    _dataBack (msg) {
      switch (msg) {
        case '添加':
          this._add()
          break
        case '提交':
          this._submitById()
          break
        case '标签打印':
          this._printLabel()
          break
      }
    }
  }
}

Demo

github地址:H5-bluetooth