1. 前言:为什么说编码转换是蓝牙打印的“拦路虎”?
大家好,我是老张,一个在移动开发领域摸爬滚打了十来年的老兵。最近几年,随着零售、物流、仓储这些行业对移动化办公的需求越来越强,我经手了不少用 uniapp 开发、需要连接蓝牙外设(比如打印机和电子秤)的项目。说实话,每次接到这种需求,我心里都会“咯噔”一下。倒不是怕连接蓝牙本身,uniapp 的蓝牙 API 已经封装得挺友好了。真正的“硬骨头”,往往藏在最后一步——给蓝牙打印机发送数据。
很多新手朋友可能会觉得,连接都成功了,发个字符串过去打印不是很简单吗?如果你真这么想,那很可能要踩个大坑。我见过太多项目卡在这里:代码在浏览器里运行得好好的,一到真机上就打印出一堆乱码,或者干脆啥也不出。核心问题就出在编码上。我们 uniapp 项目,默认的字符串编码是 UTF-8,这是现代 Web 和移动端的标准。但是,市面上很多老牌的、性价比高的蓝牙热敏打印机,它们“认”的却是 GBK 编码。这就好比一个只会说中文的人和一个只会说英文的人在对话,如果不经过翻译,根本没法沟通。
更棘手的是,这个“翻译官”(编码转换)在手机端还不太好找。浏览器里我们可以用 new TextDecoder('gbk').decode(...) 轻松搞定,但这个方法在 iOS 和 Android 的 WebView 环境里经常不被支持。网上常见的解决方案,比如引入 encoding.js 或 GBK.js,又因为依赖老式的 require 语法,在 Vue 3 或 uniapp 的某些环境下无法直接使用。所以,我们需要的是一套能在 uniapp 中稳定运行、且兼容 Vue 3 的 UTF-8 转 GBK 方案,并且转换结果必须是打印机能接受的十进制数组格式。这就是本次实战要解决的核心难题。别担心,跟着我的思路走,我会把踩过的坑和最终的解决方案,毫无保留地分享给你。
2. 蓝牙连接基础:稳扎稳打七步走
在解决编码这个“终极BOSS”之前,我们得先把连接蓝牙的“基本功”练扎实。连接蓝牙设备,尤其是低功耗蓝牙(BLE),是一个标准的流程,一步错了,后面全完。我把它总结为七个步骤,你只要按顺序来,基本不会出错。
2.1 初始化与搜索:找到你的设备
第一步永远是初始化蓝牙适配器。这相当于告诉手机系统:“喂,我要开始用蓝牙功能了”。这里有个小细节,最好在用户主动操作(比如点击按钮)时触发,因为部分平台会把它作为触发蓝牙权限弹窗的时机。
// 1. 初始化蓝牙模块
uni.openBluetoothAdapter({
success: (res) => {
console.log('蓝牙适配器初始化成功');
// 初始化成功后,立即开始搜索设备
this.startDiscovery();
},
fail: (res) => {
console.error('初始化失败,请检查手机蓝牙是否开启', res);
uni.showToast({ title: '请打开手机蓝牙', icon: 'none' });
}
});
第二步,开始搜索附近的蓝牙设备。这里有个参数 services 可以指定要搜索的设备服务UUID,如果你知道目标设备的服务UUID,填上可以加快筛选速度。不过我们通常更依赖设备名称来过滤。
// 2. 开始搜索设备
startDiscovery() {
uni.startBluetoothDevicesDiscovery({
// services: ['FFE0'], // 可选:已知的服务UUID
success: (res) => {
console.log('开始搜索设备');
// 成功启动搜索后,监听找到设备的事件
this.onDeviceFound();
},
fail: (res) => {
console.error('启动搜索失败', res);
}
});
}
第三步,监听设备发现事件。这个事件回调会多次触发,每发现一个(或一批)新设备就触发一次。这里有个关键点:deviceId 是每个蓝牙设备的唯一标识,后续所有操作都靠它,一定要存下来!
// 3. 监听发现设备事件
onDeviceFound() {
uni.onBluetoothDeviceFound((res) => {
res.devices.forEach(device => {
console.log(`发现设备: ${device.name}, ID: ${device.deviceId}`);
// 根据设备名称过滤我们需要的打印机或电子秤
if (device.name && device.name.includes('MPT-II')) { // 替换为你的设备名
console.log('找到目标打印机,停止搜索并连接');
this.targetDevice = device; // 保存设备信息
uni.stopBluetoothDevicesDiscovery(); // 找到后停止搜索,省电
this.connectDevice(device.deviceId);
}
});
});
}
2.2 连接与服务发现:深入设备内部
第四步,连接目标设备。这一步是建立手机与蓝牙设备之间的物理链路。
// 4. 连接设备
connectDevice(deviceId) {
uni.createBLEConnection({
deviceId: deviceId,
success: (res) => {
console.log('蓝牙设备连接成功');
// 连接成功后,稍微等一下再获取服务,否则容易报错
setTimeout(() => {
this.getServices(deviceId);
}, 800);
},
fail: (res) => {
console.error('连接设备失败', res);
}
});
}
为什么这里要加一个 setTimeout?这是我踩过坑的经验。蓝牙连接刚建立时,设备内部的服务列表可能还没完全准备好,立刻去获取会返回错误码(比如常见的10004)。等待600-1000毫秒再操作,成功率会高很多。
第五步,获取蓝牙设备的所有服务(Services)。一个蓝牙设备就像一栋大楼,里面有很多个房间(服务),每个房间提供不同的功能。
// 5. 获取设备服务
getServices(deviceId) {
uni.getBLEDeviceServices({
deviceId: deviceId,
success: (res) => {
console.log('获取到设备服务列表:', res.services);
// 通常我们需要遍历所有服务,寻找具备“写”或“通知”特征的那个
res.services.forEach(service => {
this.getCharacteristics(deviceId, service.uuid);
});
},
fail: (res) => {
console.error('获取服务失败', res);
}
});
}
第六步,也是最关键的一步,获取指定服务的特征值(Characteristics)。特征值才是真正干活的“开关”。比如,有的特征值负责接收数据(写),有的负责发送数据(通知或读)。
// 6. 获取服务的特征值
getCharacteristics(deviceId, serviceId) {
uni.getBLEDeviceCharacteristics({
deviceId: deviceId,
serviceId: serviceId,
success: (res) => {
console.log(`服务 ${serviceId} 的特征值:`, res.characteristics);
res.characteristics.forEach(char => {
// 判断特征值的属性
const props = char.properties;
console.log(`特征值 ${char.uuid} 属性:`, props);
// 如果是打印机,我们需要找支持「写」的特征值
if (props.write) {
console.log('找到打印特征值!', char.uuid);
this.printServiceId = serviceId;
this.printCharId = char.uuid;
this.deviceId = deviceId;
// 保存好这三个ID:deviceId, serviceId, characteristicId
}
// 如果是电子秤,我们需要找支持「通知」的特征值
if (pr


543

被折叠的 条评论
为什么被折叠?



