微信小程序蓝牙连接打印机打印单据完整Demo【蓝牙小票打印】

文章目录


下面我将提供一个完整的微信小程序通过蓝牙连接热敏打印机(支持ESC/POS指令)打印订单小票的详细实现方案。本教程假设您使用的是常见的蓝牙热敏打印机(如佳博、芯烨等品牌)。

一、准备工作

1. 硬件准备

  • 蓝牙热敏小票打印机(支持 ESC/POS 指令)
  • 确保打印机已充电/通电
  • 知道打印机的蓝牙名称(如"GBTP-XXXX")

2. 开发环境

  • 微信开发者工具
  • 已注册微信小程序账号
  • 小程序已开通蓝牙权限(在 app.json 中配置)

二、小程序配置

1. 修改app.json

json 复制代码
{
  "pages": ["pages/index/index"],
  "permission": {
    "scope.userLocation": {
      "desc": "你的位置信息将用于蓝牙设备搜索"
    }
  },
  "requiredPrivateInfos": ["getLocation", "chooseAddress", "startBluetoothDevicesDiscovery"]
}

三、完整代码实现

1. pages/index/index.wxml

xml 复制代码
<view class="container">
  <button bindtap="searchPrinter">搜索蓝牙打印机</button>
  <view wx:for="{{devices}}" wx:key="deviceId">
    <view class="device-item" bindtap="connectPrinter" data-device="{{item}}">
      {{item.name}} ({{item.deviceId}})
    </view>
  </view>
  
  <button bindtap="printTest" disabled="{{!connected}}">打印测试小票</button>
  <button bindtap="printOrder" disabled="{{!connected}}">打印订单</button>
  
  <view class="status">状态: {{status}}</view>
</view>

2. pages/index/index.wxss

css 复制代码
.container {
  padding: 20px;
}

.device-item {
  padding: 15px;
  border-bottom: 1px solid #eee;
}

button {
  margin-top: 15px;
}

.status {
  margin-top: 20px;
  color: #666;
  font-size: 14px;
}

3. pages/index/index.js

javascript 复制代码
Page({
  data: {
    devices: [],         // 搜索到的设备列表
    connected: false,    // 是否已连接
    deviceId: '',        // 当前连接的设备ID
    serviceId: '',       // 蓝牙服务UUID
    characteristicId: '', // 蓝牙特征值UUID
    status: '未连接',     // 状态提示
    orderInfo: {         // 示例订单数据
      orderNo: '20231115001',
      date: '2023-11-15 14:30',
      items: [
        {name: '有机青菜', price: 12.5, quantity: 2},
        {name: '新鲜番茄', price: 8.0, quantity: 3},
        {name: '优质土豆', price: 5.5, quantity: 1}
      ],
      total: 47.5,
      address: '北京市朝阳区科技园A座1001室',
      phone: '138****1234'
    }
  },

  // 搜索蓝牙设备
  searchPrinter() {
    this.setData({status: '正在搜索蓝牙设备...'});
    
    // 初始化蓝牙模块
    wx.openBluetoothAdapter({
      success: (res) => {
        console.log('蓝牙适配器初始化成功', res);
        this.startDiscovery();
      },
      fail: (err) => {
        console.error('蓝牙适配器初始化失败', err);
        this.setData({status: '蓝牙初始化失败,请检查手机蓝牙是否开启'});
      }
    });
  },

  // 开始搜索设备
  startDiscovery() {
    wx.startBluetoothDevicesDiscovery({
      allowDuplicatesKey: false,
      success: (res) => {
        console.log('开始搜索设备', res);
        this.setData({status: '正在搜索设备...'});
        
        // 监听找到新设备事件
        wx.onBluetoothDeviceFound((res) => {
          const devices = res.devices.filter(device => 
            device.name && device.name.indexOf('GBTP') !== -1
          );
          if (devices.length > 0) {
            this.setData({
              devices: devices,
              status: `找到${devices.length}个打印机设备`
            });
            wx.stopBluetoothDevicesDiscovery(); // 找到设备后停止搜索
          }
        });
      },
      fail: (err) => {
        console.error('搜索设备失败', err);
        this.setData({status: '搜索设备失败'});
      }
    });
  },

  // 连接打印机
  connectPrinter(e) {
    const device = e.currentTarget.dataset.device;
    this.setData({status: `正在连接${device.name}...`});
    
    // 连接设备
    wx.createBLEConnection({
      deviceId: device.deviceId,
      success: (res) => {
        console.log('设备连接成功', res);
        this.setData({
          deviceId: device.deviceId,
          status: `已连接${device.name}`
        });
        this.getBLEDeviceServices(device.deviceId);
      },
      fail: (err) => {
        console.error('设备连接失败', err);
        this.setData({status: '连接失败'});
      }
    });
  },

  // 获取蓝牙服务
  getBLEDeviceServices(deviceId) {
    wx.getBLEDeviceServices({
      deviceId: deviceId,
      success: (res) => {
        console.log('获取服务成功', res.services);
        for (const service of res.services) {
          // 通常蓝牙打印机的服务UUID是FF00或FFE0
          if (service.uuid.startsWith('FFE0') || service.uuid.startsWith('FF00')) {
            this.setData({serviceId: service.uuid});
            this.getBLEDeviceCharacteristics(deviceId, service.uuid);
            break;
          }
        }
      },
      fail: (err) => {
        console.error('获取服务失败', err);
      }
    });
  },

  // 获取蓝牙特征值
  getBLEDeviceCharacteristics(deviceId, serviceId) {
    wx.getBLEDeviceCharacteristics({
      deviceId: deviceId,
      serviceId: serviceId,
      success: (res) => {
        console.log('获取特征值成功', res.characteristics);
        for (const characteristic of res.characteristics) {
          // 寻找可写的特征值
          if (characteristic.properties.write) {
            this.setData({
              characteristicId: characteristic.uuid,
              connected: true
            });
            console.log('打印机已准备好');
            break;
          }
        }
      },
      fail: (err) => {
        console.error('获取特征值失败', err);
      }
    });
  },

  // 打印测试小票
  printTest() {
    this.setData({status: '正在打印测试小票...'});
    
    // ESC/POS指令
    const buffer = new ArrayBuffer(100);
    const dataView = new Uint8Array(buffer);
    
    // 初始化打印机
    dataView[0] = 0x1B;
    dataView[1] = 0x40;
    
    // 设置居中
    dataView[2] = 0x1B;
    dataView[3] = 0x61;
    dataView[4] = 0x01;
    
    // 设置字体大小
    dataView[5] = 0x1D;
    dataView[6] = 0x21;
    dataView[7] = 0x11;
    
    // 打印文本
    const text = "测试小票\n";
    for (let i = 0; i < text.length; i++) {
      dataView[8 + i] = text.charCodeAt(i);
    }
    
    // 换行
    const lineBreak = "\n\n\n\n";
    for (let i = 0; i < lineBreak.length; i++) {
      dataView[8 + text.length + i] = lineBreak.charCodeAt(i);
    }
    
    // 切纸
    dataView[8 + text.length + lineBreak.length] = 0x1D;
    dataView[9 + text.length + lineBreak.length] = 0x56;
    dataView[10 + text.length + lineBreak.length] = 0x42;
    dataView[11 + text.length + lineBreak.length] = 0x00;
    
    // 发送数据
    this.writeBLECharacteristicValue(buffer);
  },

  // 打印订单
  printOrder() {
    this.setData({status: '正在打印订单...'});
    
    // 创建缓冲区
    const commands = [];
    
    // 初始化打印机
    commands.push(0x1B, 0x40);
    
    // 设置居中
    commands.push(0x1B, 0x61, 0x01);
    
    // 设置大字体
    commands.push(0x1D, 0x21, 0x11);
    
    // 打印标题
    this.addTextToCommand(commands, "蔬菜配送订单\n");
    
    // 恢复默认字体
    commands.push(0x1D, 0x21, 0x00);
    
    // 订单信息
    this.addTextToCommand(commands, `订单号: ${this.data.orderInfo.orderNo}\n`);
    this.addTextToCommand(commands, `日期: ${this.data.orderInfo.date}\n\n`);
    
    // 商品列表
    this.addTextToCommand(commands, "----------------------------\n");
    this.addTextToCommand(commands, "商品名称   单价  数量  小计\n");
    this.addTextToCommand(commands, "----------------------------\n");
    
    this.data.orderInfo.items.forEach(item => {
      const line = `${item.name}    ${item.price}   ${item.quantity}   ${(item.price * item.quantity).toFixed(2)}\n`;
      this.addTextToCommand(commands, line);
    });
    
    this.addTextToCommand(commands, "----------------------------\n");
    this.addTextToCommand(commands, `总计: ¥${this.data.orderInfo.total}\n\n`);
    
    // 配送信息
    this.addTextToCommand(commands, `配送地址: ${this.data.orderInfo.address}\n`);
    this.addTextToCommand(commands, `联系电话: ${this.data.orderInfo.phone}\n\n`);
    
    // 感谢语
    commands.push(0x1B, 0x61, 0x01); // 居中
    this.addTextToCommand(commands, "感谢您的惠顾!\n");
    this.addTextToCommand(commands, "期待再次为您服务\n\n\n");
    
    // 切纸
    commands.push(0x1D, 0x56, 0x42, 0x00);
    
    // 转换为ArrayBuffer
    const buffer = new ArrayBuffer(commands.length);
    const dataView = new Uint8Array(buffer);
    commands.forEach((value, index) => {
      dataView[index] = value;
    });
    
    // 发送数据
    this.writeBLECharacteristicValue(buffer);
  },

  // 辅助方法:添加文本到命令数组
  addTextToCommand(commands, text) {
    for (let i = 0; i < text.length; i++) {
      commands.push(text.charCodeAt(i));
    }
  },

  // 写入蓝牙特征值
  writeBLECharacteristicValue(buffer) {
    wx.writeBLECharacteristicValue({
      deviceId: this.data.deviceId,
      serviceId: this.data.serviceId,
      characteristicId: this.data.characteristicId,
      value: buffer,
      success: (res) => {
        console.log('写入成功', res);
        this.setData({status: '打印指令已发送'});
      },
      fail: (err) => {
        console.error('写入失败', err);
        this.setData({status: '打印失败'});
      }
    });
  }
});

四、ESC/POS指令说明

  1. 打印机初始化:0x1B 0x40

  2. 设置对齐方式

    • 左对齐:0x1B 0x61 0x00
    • 居中:0x1B 0x61 0x01
    • 右对齐:0x1B 0x61 0x02
  3. 设置字体大小

    • 0x1D 0x21 0x00 - 正常大小
    • 0x1D 0x21 0x11 - 双倍宽高
  4. 换行0x0A

  5. 切纸0x1D 0x56 0x42 0x00

五、测试流程

  1. 打开微信开发者工具,导入本项目
  2. 点击"搜索蓝牙打印机"按钮
  3. 在设备列表中找到您的打印机并点击连接
  4. 连接成功后,点击"打印测试小票"测试基本功能
  5. 点击"打印订单"打印完整的订单信息

六、常见问题解决

  1. 找不到设备:

    • 确保打印机蓝牙已开启并可被发现
    • 检查打印机是否支持 BLE (蓝牙4.0及以上)
    • 修改代码中的设备名称过滤条件(如GBTP)
  2. 连接失败:

    • 确保打印机未被其他设备连接
    • 尝试重启打印机蓝牙
  3. 打印乱码:

    • 检查 ESC/POS 指令是否正确
    • 确保打印机支持接收的指令集
  4. 权限问题:

    • 确保小程序已获取蓝牙相关权限
    • 在手机上授权小程序使用蓝牙

七、进一步优化建议

  1. 添加打印机断开重连机制
  2. 实现打印任务队列,防止并发打印冲突
  3. 根据打印机型号调整指令集
  4. 添加打印状态回调,提供更好的用户反馈
  5. 实现打印内容模板化,便于维护

这个 Demo 提供了完整的蓝牙连接和打印功能实现,您可以根据实际需求进行调整和扩展。实际开发中,建议参考您使用的打印机型号的特定指令集文档进行微调。

相关推荐
努力成为包租婆2 小时前
微信小程序 van-dropdown-menu
微信·微信小程序·小程序
thinkQuadratic4 小时前
微信小程序动态设置高度,添加动画等常用操作
微信小程序·小程序
中科三方5 小时前
APP和小程序需要注册域名吗?(国科云)
小程序·apache
幽络源小助理7 小时前
微信小程序文章管理系统开发实现
java·微信小程序·springboot
10年前端老司机8 小时前
微信小程序模板语法和事件
前端·javascript·微信小程序
上趣工作室9 小时前
微信小程序开发1------微信小程序中的消息提示框总结
微信小程序·小程序
韩仔搭建12 小时前
美乐迪电玩大厅加载机制与 RoomList 配置结构分析
游戏·小程序·开源·lua
WKK_14 小时前
uniapp自定义封装tabbar
前端·javascript·小程序·uni-app
老李不敲代码2 天前
榕壹云预约咨询系统:基于ThinkPHP+MySQL+UniApp打造的灵活预约小程序解决方案
mysql·微信小程序·小程序·uni-app·php