微信小程序蓝牙对接热敏打印机

本文详细记录了一个uni-app小程序中对接热敏打印机进行小票打印的过程,包括蓝牙设备的搜索、连接、数据管理、打印模板定制及实际打印操作。项目使用了uni-app框架,涉及vue、蓝牙API、数据管理(Vuex)以及自定义打印模板。通过创建BLE连接,管理蓝牙设备状态,实现数据清理和三列打印功能,确保了打印的稳定性和准确性。
摘要由CSDN通过智能技术生成

更多文章请访问 深的个人博客

最近开发的一个小程序里需要对接热敏打印机打印小票,在此记录一下对接的过程

项目开发注意点:
1、因为项目中有多个页面的数据是需要进行打印的,为了保持蓝牙的连接状态和数据,所以连接蓝牙的操作和数据会在vuex里面进行统一管理。
2、打印模板会统一进行管理
3、打印数据前需要对上一次打印的数据进行清理 使用 printerJobs 对象里的 clear() 方法实现清理数据
4、在原有的sdk上增加三列打印的方法

技术栈

uni-app

参考资料

微信小程序蓝牙打印

项目效果图

请添加图片描述

打印效果图

请添加图片描述
请添加图片描述

项目结构

只展示主要项目结构
请添加图片描述

项目页面讲解

vuex相关文件讲解
蓝牙连接:connect.js
/**
 * 连接蓝牙
 */
function inArray(arr, key, val) {
	for (let i = 0; i < arr.length; i++) {
		if (arr[i][key] === val) {
			return i
		}
	}
	return -1
}

export default {
	namespaced: true,  // 开启命名空间
	state: {
		devices: [], // 搜索到的蓝牙设备
		connected: false, // 是否连接
		discoveryStarted: false, // 是否开始搜索蓝牙
		name: "", // 连接到的蓝牙设备名称
		deviceId: "", // 连接到的蓝牙设备的deviceId
		canWrite: false,
		_deviceId: "", // 连接到的蓝牙设备的deviceId
		_serviceId: "", // 连接到的蓝牙设备的seviceId
		_characteristicId: "", // 连接到的蓝牙设备的characteristicId
	},
	getters: {
		getDevices: state => state.devices,
		getName: state => state.name,
		getConnected: state => state.connected,
		getDiscoveryStarted: state => state.discoveryStarted,
		getConnectDeviceId: state => state._deviceId,
		getServiceId: state => state._serviceId,
		getCharacteristicId: state => state._characteristicId
	},
	mutations: {
		// 初始化连接
		initConnect(state) {
			state.devices = []
			if (!uni.openBluetoothAdapter) {
				uni.showModal({
					title: '提示',
					content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
				})
				return
			}
			uni.openBluetoothAdapter({
				success: (res) => {
					console.log('openBluetoothAdapter success', res)
					this.commit('connect/startBluetoothDevicesDiscovery')
				},
				fail: (res) => {
					console.log('openBluetoothAdapter fail', res)
					if (res.errCode === 10001) {
						uni.showModal({
							title: '错误',
							content: '未找到蓝牙设备, 请打开蓝牙后重试。',
							showCancel: false
						})
						uni.onBluetoothAdapterStateChange((res) => {
							console.log('onBluetoothAdapterStateChange', res)
							if (res.available) {
								// 取消监听,否则stopBluetoothDevicesDiscovery后仍会继续触发onBluetoothAdapterStateChange,
								// 导致再次调用startBluetoothDevicesDiscovery
								uni.onBluetoothAdapterStateChange(() => {});
								this.commit('connect/startBluetoothDevicesDiscovery')
							}
						})
					}
				}
			})
			uni.onBLEConnectionStateChange((res) => {
				// 该方法回调中可以用于处理连接意外断开等异常情况
				console.log('onBLEConnectionStateChange',
					`device ${res.deviceId} state has changed, connected: ${res.connected}`)
				// this.setData({
				// 	connected: res.connected
				// })
				state.connected = res.connected
				if (!res.connected) {
					uni.showModal({
						title: '错误',
						content: '蓝牙连接已断开',
						showCancel: false
					})
				}
			});

		},
		getBluetoothAdapterState(state) {
			uni.getBluetoothAdapterState({
				success: (res) => {
					console.log('getBluetoothAdapterState', res)
					if (res.discovering) {
						this.commit('connect/onBluetoothDeviceFound')
					} else if (res.available) {
						this.commit('connect/startBluetoothDevicesDiscovery')
					}
				}
			})
		},
		// 开启蓝牙搜索
		startBluetoothDevicesDiscovery(state) {
			if (state.discoveryStarted) {
				return
			}
			state.discoveryStarted = true
			uni.startBluetoothDevicesDiscovery({
				success: (res) => {
					this.commit('connect/onBluetoothDeviceFound')
				},
				fail: (res) => {
					console.log('startBluetoothDevicesDiscovery fail', res)
				}
			})
		},
		// 停止搜索蓝牙
		stopBluetoothDevicesDiscovery(state) {
			uni.stopBluetoothDevicesDiscovery({
				complete: () => {
					console.log('complete,stopBluetoothDevicesDiscovery')
					state.discoveryStarted = false
					console.log(this.discoveryStarted)
				}
			})
		},
		// 搜索蓝牙
		onBluetoothDeviceFound(state) {
			uni.onBluetoothDeviceFound((res) => {
				console.log("搜索到的蓝牙", res.devices)
				res.devices.forEach(device => {
					if (!device.name && !device.localName) {
						return
					}
					const idx = inArray(state.devices, 'deviceId', device.deviceId)
					if (idx === -1) {
						state.devices.push(device)
					}
				})

			})
		},
		// 连接蓝牙 1
		createBLEConnection(state, item) {
			console.log("连接的设备", item)
			this.commit("connect/next_createBLEConnection", {
				deviceId: item.deviceId,
				name: item.name
			})
		},
		// 连接蓝牙 2
		next_createBLEConnection(state, equipment) {
			let {
				deviceId,
				name
			} = equipment
			uni.showLoading()
			uni.createBLEConnection({
				deviceId,
				success: () => {
					console.log('createBLEConnection success');
					state.name = name
					state.connected = true
					state.deviceId = deviceId
					console.log("连接的设备", state.name)
					this.commit("connect/getBLEDeviceServices", deviceId)
					uni.setStorageSync('LAST_CONNECTED_DEVICE', name + ":" + deviceId)
				},
				complete() {
					uni.hideLoading()
				},
				fail: (res) => {
					console.log('createBLEConnection fail', res)
				}
			})
			this.commit("connect/stopBluetoothDevicesDiscovery")
		},
		// 断开蓝牙连接
		closeBLEConnection(state) {
			uni.closeBLEConnection({
				deviceId: state.deviceId
			})
			state.connected = false
			state.canWrite = false
			state.deviceId = ""
			state._deviceId= ""
			state._serviceId= ""
			state._characteristicId= ""
		},
		// 获取蓝牙服务
		getBLEDeviceServices(state, deviceId) {
			uni.getBLEDeviceServices({
				deviceId,
				success: (res) => {
					console.log('getBLEDeviceServices', res)
					for (let i = 0; i < res.services.length; i++) {
						if (res.services[i].isPrimary) {
							// this.getBLEDeviceCharacteristics(deviceId, res.services[i].uuid)
							this.commit("connect/getBLEDeviceCharacteristics", {
								deviceId,
								serviceId: res.services[i].uuid
							})
							return
						}
					}
				}
			})
		},
		getBLEDeviceCharacteristics(state, idObject) {
			let {
				deviceId,
				serviceId
			} = idObject
			uni.getBLEDeviceCharacteristics({
				deviceId,
				serviceId,
				success: (res) => {
					console.log('getBLEDeviceCharacteristics success', res.characteristics)
					// 这里会存在特征值是支持write,写入成功但是没有任何反应的情况
					// 只能一个个去试
					for (let i = 0; i < res.characteristics.length; i++) {
						const item = res.characteristics[i]
						if (item.properties.write) {
							// this.setData({
							// 	canWrite: true
							// })
							state.canWrite = true
							state._deviceId = deviceId
							state._serviceId = serviceId
							state._characteristicId = item.uuid
							break;
						}
					}
				},
				fail(res) {
					console.error('getBLEDeviceCharacteristics', res)
				}
			})
		},
		closeBluetoothAdapter(state) {
			uni.closeBluetoothAdapter()
			state.discoveryStarted = false
			console.log("关闭蓝牙")
		},
	},
	actions: {

	}
}
打印数据:print.js

打印数据:页面传递过来的要打印的数据是一个对象,为了避免数据没有及时更新的问题,所以存储这个对象用的是一个数组(printData),这个打印数据会传递到打印模板 (template.js) 中
打印类型 :每个页面都对应一个打印的模板,这个模板存储在 print/printTheTemplat/template.js 文件中,打印的时候会根据传递的打印类型打印相应的模板

/**
 * 打印的相关数据
 */
export default {
	namespaced: true,
	state: {
		printType: "", // 打印类型
		printData: [], // 打印的数据
	},
	getters: {
		// 获取打印类型
		getPrintType: state => state.printType,
		// 获取打印数据
		getPrintData: state => state.printData
	},
	mutations: {
		// 更新打印数据和打印类型
		updatePrintData(state, printObj) {
			let {
				printData,
				printType
			} = printObj
			console.log("接收到的数据", printObj)
			state.printType = printType
			state.printData = []
			state.printData.push(printData)
		}
	},
	actions: {

	}
}

print文件夹相关文件讲解
打印常量 pritConstant.js
// 打印的常量
export const REGISTRATION = "registration" // 退款单/收款单
export const CARSALES = "carSales" // 车销单
export const INVENTORY = "inventory" // 盘点详情
export const DAILY = "daily" // 销售日报
export const CUSTOMER = "customer" //客户汇总
export const COMMODITY = "commodity" // 商品汇总
export const STOCK = "stock" // 库存汇总
打印模板 template.js
// 模板
// printerJobs
// 	.print('2018年12月5日17:34')
// 	.print(printerUtil.fillLine())
// 	.setAlign('ct')
// 	.setSize(2, 2)
// 	.print('#20饿了么外卖')
// 	.setSize(1, 1)
// 	.print('切尔西Chelsea')
// 	.setSize(2, 2)
// 	.print('在线支付(已支付)')
// 	.setSize(1, 1)
// 	.print('订单号:5415221202244734')
// 	.print('下单时间:2017-07-07 18:08:08')
// 	.setAlign('lt')
// 	.print(printerUtil.fillAround('一号口袋'))
// 	.print(printerUtil.inline('意大利茄汁一面 * 1', '15.00'))
// 	.print(printerUtil.fillAround('其他'))
// 	.print('餐盒费:1')
// 	.print('[赠送康师傅冰红茶] * 1')
// 	.print(printerUtil.fillLine())
// 	.setAlign('rt')
// 	.print('原价:¥16.00')
// 	.print('总价:¥16.00')
// 	.setAlign('lt')
// 	.print(printerUtil.fillLine())
// 	.print('备注')
// 	.print("无")
// 	.print(printerUtil.fillLine())
// 	.println();
import {
	REGISTRATION,
	CARSALES,
	INVENTORY,
	DAILY,
	CUSTOMER,
	COMMODITY,
	STOCK
} from "../../utils/pritConstant.js"
const PrinterJobs = require('../sdk/printer/printerjobs.js')
const printerUtil = require('../sdk/printer/printerutil')
let printerJobs = new PrinterJobs();

export default {
	// 收款单
	[REGISTRATION]: (array) => {
		console.log("打印接收的数据", array)
		let data = array[0]
		let prefix = ""
		data.order_type == 1 ? prefix = "收款" : prefix = "退款"
		printerJobs
			.setAlign('ct')
			.setSize(2, 2)
			.print(`${prefix}`)
			.lineFeed()
			.setSize(1, 1)
			.setAlign('lt')
			.print(printerUtil.inline(`${prefix}单号`, data.pay_sn))
			.print(printerUtil.inline('客户名称', data.client_name))
			.print(printerUtil.fillLine())
		if (data.order_type == 1) {
			printerJobs.print(printerUtil.inline(`${prefix}类型`, data.pay_type))
		}
		printerJobs.print(printerUtil.inline(`${prefix}金额`, data.pay_money))
			.print(printerUtil.inline('优惠金额', data.dicount_money))
			.print(printerUtil.inline(`${prefix}方式`, data.pay_method))
			.print(printerUtil.inline(`${prefix}时间`, data.pay_time))
			.print(printerUtil.fillLine())
			.print(printerUtil.inline('经办人', data.operator))
			.print(printerUtil.inline('提交人', data.userName))
			.print(printerUtil.inline('提交日期', data.created_at))
			.print(printerUtil.fillLine())
			.print('审批历程')
		data.log_list.forEach(item => {
			printerJobs.print(item.username + '' + item.contents)
		})
		printerJobs.print(printerUtil.doubleFillLine())
			.print('业代签字')
			.lineFeed(2)
			.println();

		return {
			buffer: printerJobs.buffer(),
			printerJobs
		};
	},
	// 车销单
	[CARSALES]: (array) => {
		let data = array[0]
		printerJobs
			.setAlign('ct')
			.setSize(2, 2)
			.print('车销单')
			.lineFeed()
			.setSize(1, 1)
			.setAlign('lt')
			.print(printerUtil.inline('客户名称', data.shopClient.username))
			.setBold(false)
			.print(printerUtil.inline('状态', data.status_name))
			.print(printerUtil.inline('销售单编号', data.order_sn))
			.print(printerUtil.inline('提交日期', data.created_at))
			.print(printerUtil.inline('客户经理', data.member.username))
			.print(printerUtil.doubleFillLine())
			.print(printerUtil.inline('销售清单', `${data.products_list.length}`))

		data.products_list.forEach(item => {
			printerJobs.print(item.product_name)
				.print(printerUtil.inline(`   数量:${item.num}`, `单价:${item.price}`))
				.print(`   金额:${(item.price * 1 * item.num)}`)
		})


		printerJobs.print(printerUtil.fillLine())
			.print(printerUtil.inline('销售金额', `${data.pay_money}`))
			.print(printerUtil.inline('退货金额', `${ data.refuned_money}`))
			.print(printerUtil.inline('本单应收', `${data.remain_money}`))
			.print(printerUtil.fillLine())
			.print('收款记录')
			.print(printerUtil.inline('记录数量', `${data.log_list.length}`))
			// .print(printerUtil.inline('收款核销', '230.00'))
			// .print(printerUtil.inline('欠款', '230.00'))
			.print(printerUtil.doubleFillLine())
			.print('业代签字')
			.lineFeed(2)
			.println();

		return {
			buffer: printerJobs.buffer(),
			printerJobs
		};
	},
	// 盘点详情
	[INVENTORY]: (array) => {
		let data = array[0]
		let {
			products_list,
			log_list
		} = data
		let status = ""
		data.status == 0 ? status = "待审批" : data.status == 1 ? status = "已通过" : status = "作废"
		let range = data.order_type && data.order_type == 2 ? "部分盘点" : "全部盘点"
		printerJobs
			.setAlign('ct')
			.setSize(2, 2)
			.print('盘点详情')
			.lineFeed()
			.setSize(1, 1)
			.setAlign('lt')
			.print(printerUtil.inline('盘点单号', `${data.order_sn}`))
			.print(printerUtil.inline('盘点人员', `${data.member.username}`))
			.print(printerUtil.inline('状态', `${status}`))
			.print(printerUtil.inline('盘点范围', `${range}`))
			.print(printerUtil.inline('提交日期', `${data.created}`))
			.print(printerUtil.fillLine())
			.print("商品清单")
		products_list.forEach(item => {
			printerJobs.print(`${item.product_name}`)
				.print(printerUtil.inline(`  账面:${item.num}`, `实盘:${item.diff_num}`))
		})
		if (log_list.length > 0) {
			printerJobs.print(printerUtil.fillLine()).print('审批历程')
			log_list.forEach(item => {
				printerJobs.print(`${item.username} ${item.created_at} [${item.contents}]`)
			})
		} else {
			printerJobs.print(printerUtil.fillLine()).print('审批历程(暂无)')
		}
		printerJobs.print(printerUtil.doubleFillLine())
			.print('业代签字')
			.lineFeed(2)
			.println();

		return {
			buffer: printerJobs.buffer(),
			printerJobs
		};
	},
	// 日报汇总
	[DAILY]: (array) => {
		let data = array[0]
		printerJobs
			.setAlign('ct')
			.setSize(2, 2)
			.print('销售日报')
			.lineFeed()
			.setSize(1, 1)
			.setAlign('lt')
			.setUnderline(true)
			.print(`日期:${data.date}`)
			.print(printerUtil.doubleFillLine())
			.setUnderline(false)
			.print(printerUtil.inline('销售金额', `${data.incomeTotal}`))
			.print(printerUtil.inline('其中:销售金额', `${data.salesTotal}`))
			.print(printerUtil.inline('     退货金额', `${data.returndTotal}`))
			.print(printerUtil.fillLine())
			.print(printerUtil.inline('应收金额', `${data.remainTotal}`))
			.print(printerUtil.fillLine())
			.print(printerUtil.inline('收款金额', `${data.payTotal}`))
			.print(printerUtil.inline('收款优惠', `${data.dicountTotal}`))
			.print(printerUtil.inline('支出费用', `${data.debtTotal}`))
			.print(printerUtil.fillLine())
			.print(`打印时间:${format(new Date(),"full")}`)
			.print(printerUtil.doubleFillLine())
			.print('业代签字')
			.lineFeed()
			.println();

		return {
			buffer: printerJobs.buffer(),
			printerJobs
		};
	},

	// 库存汇总
	[STOCK]: array => {
		printerJobs
			.setAlign('ct')
			.setSize(2, 2)
			.print('库存汇总')
			.lineFeed()
			.setSize(1, 1)
			.setAlign('lt')
			.setUnderline(true)
			.print('日期:2021-07-19   仓库:一号仓库')
			.print(printerUtil.doubleFillLine())
			.print(printerUtil.inline('正常品清单', '共0家'))
			.print(printerUtil.fillLine())
			.print(printerUtil.centerInline('白酒商品名称', '库存量', '金额'))
			.print(printerUtil.centerInline('白酒', '2000', '0.35'))
			.print(printerUtil.centerInline('白酒', '2000', '5000'))
			.print(printerUtil.centerInline('燕京330吉祥红佳节订单', '1000', '7000'))
			.print(printerUtil.fillLine())
			.print(printerUtil.inline('合计', '0.35'))
			.print('打印时间:2021-07-19 17:34')
			.print('业务员:张三')
			.print(printerUtil.doubleFillLine())
			.print('业代签字')
			.lineFeed()
			.lineFeed()
			.println();

		return {
			buffer: printerJobs.buffer(),
			printerJobs
		};
	}
}



// 时间处理
function format(shijianchuo, type) {
	//shijianchuo是整数,否则要parseInt转换
	var time = new Date(shijianchuo);
	var y = time.getFullYear();
	var m = time.getMonth() + 1;
	var d = time.getDate();
	var h = time.getHours();
	var mm = time.getMinutes();
	var s = time.getSeconds();

	if (type == "full") {
		return y + '-' + add0(m) + '-' + add0(d) + ' ' + add0(h) + ':' + add0(mm) + ':' +
			add0(s);
	} else {
		return y + '-' + add0(m) + '-' + add0(d);
	}
}

function add0(m) {
	return m < 10 ? '0' + m : m
}
sdk相关讲解

具体sdk可以下载 github上的代码,这里只展示在printerutil.js文件中添加的几个方法

/**
 * 一排三列
 * 同一行输出str1, str2,str3,str1居左, str2居中偏右,str3居右
 * @param {string} str1 内容1 固定宽度 占可打印宽度的3/5
 * @param {string} str2 内容2 
 * @param {string} str3 内容3
 * @param {number} fontWidth 字符宽度 1/2
 * @param {string} fillWith str1 str2之间的填充字符
 *
 */
function centerInline(str1, str2, str3, fillWith = ' ', fontWidth = 1) {
	const lineWidth = MAX_CHAR_COUNT_EACH_LINE / fontWidth;
	let newStr1 = setString(str1, 14)
	console.log("截取的字符", newStr1)
	console.log("newStr1的字符长度", getStringWidth(newStr1))
	// 需要填充的字符数量
	let fillCount = lineWidth - (getStringWidth(newStr1) + getStringWidth(str2) + getStringWidth(str3)) % lineWidth;
	let leftWidth = Math.round(lineWidth / 5 * 3);
	console.log("fillcount", fillCount)
	console.log("左侧的字符长度", getStringWidth(str1))

	let leftCount = leftWidth - getStringWidth(newStr1) % lineWidth
	let rightCount = fillCount - leftCount
	let lefFillStr = new Array(leftCount).fill(fillWith.charAt(0)).join('');
	let rightFillStr = new Array(rightCount).fill(fillWith.charAt(0)).join('');
	return newStr1 + lefFillStr + str2 + rightFillStr + str3;
}

// 截取字符串,多余的部分用...代替  
function setString(str, len) {
	var strlen = 0;
	var s = "";
	for (var i = 0; i < str.length; i++) {
		if (str.charCodeAt(i) > 128) {
			strlen += 2;
		} else {
			strlen++;
		}
		s += str.charAt(i);
		if (strlen >= len) {
			return s + '...'
		}
	}
	return s;
}
/**
 * 用 = 字符填充一整行
 * @param {string} fillWith 填充字符
 * @param {number} fontWidth 字符宽度 1/2
 */
function doubleFillLine(fillWith = '=', fontWidth = 1) {
	const lineWidth = MAX_CHAR_COUNT_EACH_LINE / fontWidth;
	return new Array(lineWidth).fill(fillWith.charAt(0)).join('');
}
打印页面

print.vue

注意点:在调用打印或者是退出页面的时候要清理掉上一次打印的数据,这个可以通过 printerJobs 对象里的 clear() 方法实现清理数据, printerJobs 对象会在调用打印模板的时候返回,这个时候我们需要将printerJobs 对象保存在data中

<template>
	<view class="container">
		<view class="page-section">
			<view class="devices-summary my-2 flex items-center">
				<view>已发现 {{getDevices.length}} 个设备</view>
				<view class="ml-2" v-if="getDiscoveryStarted">
					<u-loading mode="flower"></u-loading>
				</view>
			</view>
			<scroll-view class="device-list" scroll-y scroll-with-animation>
				<view class="scroll_box mx-3 p-2 px-4" v-if="getDevices.length>0">
					<view v-for="(item,index) in getDevices" :key="item.deviceId" @click="connect(item)"
						class="device-item">
						<view class="item_left" style="font-size: 15px; color: #333;">{{item.name}}</view>
						<view class="item_right" style="font-size: 15px;" v-if="getConnectDeviceId == item.deviceId">已连接
						</view>
					</view>
				</view>
				<view class="scroll_box mx-3 p-2 px-4 null_box" v-else>
					暂无可连接设备
				</view>
			</scroll-view>
			<view class="py-4 px-3 w-full">
				<view class="info_box p-2 bg-white" v-if="getConnected">
					已连接设备: {{getName}}
				</view>
				<view class="info_box p-2 bg-white" v-else>
					暂未连接设备
				</view>
			</view>
			<view class="btn-area">
				<view class="btn_box px-4">
					<view class="btn_view" type="primary" @click="refreshDiscovery">重新扫描</view>
				</view>
				<view class="btn_box px-4">
					<view class="btn_view" @click="stopDiscovery" style="margin-top: 10px;">停止扫描</view>
				</view>
			</view>
		</view>
		<view class="page-section connected-area" v-if="getConnected">
			
			<view class="btn-area">
				<view class="btn_box px-4" style="margin-bottom: 10px;margin-top: 10px;">
					<view class="btn_view" type="primary" @click="writeBLECharacteristicValue">
						打印
					</view>
				</view>
				<view class="btn_box px-4">
					<view class="btn_view" @click="closeBLEConnection">断开连接</view>
				</view>


			</view>
		</view>
	</view>
</template>

<script>
	const LAST_CONNECTED_DEVICE = 'last_connected_device'
	const PrinterJobs = require('./sdk/printer/printerjobs')
	const printerUtil = require('./sdk/printer/printerutil')

	import printObject from "./printTheTemplate/template.js"
	import {
		mapGetters
	} from "vuex"

	function inArray(arr, key, val) {
		for (let i = 0; i < arr.length; i++) {
			if (arr[i][key] === val) {
				return i
			}
		}
		return -1
	}

	// ArrayBuffer转16进度字符串示例
	function ab2hex(buffer) {
		const hexArr = Array.prototype.map.call(
			new Uint8Array(buffer),
			function(bit) {
				return ('00' + bit.toString(16)).slice(-2)
			}
		)
		return hexArr.join(',')
	}

	function str2ab(str) {
		// Convert str to ArrayBuff and write to printer
		let buffer = new ArrayBuffer(str.length)
		let dataView = new DataView(buffer)
		for (let i = 0; i < str.length; i++) {
			dataView.setUint8(i, str.charAt(i).charCodeAt(0))
		}
		return buffer;
	}
	export default {
		data() {
			return {
				devices: [],
				connected: false,
				discoveryStarted: false,
				chs: [],
				name: "",
				deviceId: "",
				canWrite: false,
				lastDevice: "",
				printerJobs: null
			}
		},
		computed: {
			...mapGetters("print", ['getPrintType', 'getPrintData']),
			...mapGetters("connect", ['getDevices', 'getDiscoveryStarted', 'getName', 'getConnected', 'getConnectDeviceId',
				'getServiceId',
				'getCharacteristicId'
			])
		},
		onShow() {
			console.log(this.getDiscoveryStarted, this.getConnected)
			if (!this.getDiscoveryStarted && !this.getConnected) {
				this.$store.commit("connect/initConnect")
			}
			// const lastDevice = uni.getStorageSync(LAST_CONNECTED_DEVICE);
			// this.lastDevice = lastDevice
			// if (this.lastDevice) {
			// 	this.createBLEConnectionWithDeviceId()
			// } else {
			// 	this.openBluetoothAdapter()
			// }
		},
		onUnload() {
			// 清理上一次打印的数据
			this.printerJobs && this.printerJobs.clear()
		},
		methods: {
			connect(item) {
				// createBLEConnection(item)
				this.$store.commit("connect/createBLEConnection", item)
			},
			refreshDiscovery() {
				this.$store.commit("connect/startBluetoothDevicesDiscovery")
			},
			stopDiscovery() {
				this.$store.commit("connect/stopBluetoothDevicesDiscovery")
			},
			closeBLEConnection(){
				this.$store.commit("connect/closeBLEConnection")
			},
			writeBLECharacteristicValue() {
				if (this.printerJobs) {
					// 清理上一次打印的数据
					this.printerJobs.clear()
				}
				let {
					buffer,
					printerJobs
				} = printObject[this.getPrintType](this.getPrintData)
				this.printerJobs = printerJobs
				// console.log("buffer", buffer)
				// console.log('ArrayBuffer', 'length: ' + buffer.byteLength, ' hex: ' + ab2hex(buffer));
				// 1.并行调用多次会存在写失败的可能性
				// 2.建议每次写入不超过20字节
				// 分包处理,延时调用
				const maxChunk = 20;
				const delay = 20;
				for (let i = 0, j = 0, length = buffer.byteLength; i < length; i += maxChunk, j++) {
					let subPackage = buffer.slice(i, i + maxChunk <= length ? (i + maxChunk) : length);
					setTimeout(this._writeBLECharacteristicValue, j * delay, subPackage);
				}
			},
			_writeBLECharacteristicValue(buffer) {
				uni.writeBLECharacteristicValue({
					deviceId: this.getConnectDeviceId,
					serviceId: this.getServiceId,
					characteristicId: this.getCharacteristicId,
					value: buffer,
					success(res) {
						console.log('writeBLECharacteristicValue success', res)
					},
					fail(res) {
						console.log('writeBLECharacteristicValue fail', res)
					}
				})
			},
			closeBluetoothAdapter() {
				uni.closeBluetoothAdapter()
				this.discoveryStarted = false
			},
			createBLEConnectionWithDeviceId(e) {
				// 小程序在之前已有搜索过某个蓝牙设备,并成功建立连接,可直接传入之前搜索获取的 deviceId 直接尝试连接该设备
				const device = this.lastDevice
				if (!device) {
					return
				}
				const index = device.indexOf(':');
				const name = device.substring(0, index);
				const deviceId = device.substring(index + 1, device.length);
				console.log('createBLEConnectionWithDeviceId', name + ':' + deviceId)
				uni.openBluetoothAdapter({
					success: (res) => {
						console.log('openBluetoothAdapter success', res)
						this._createBLEConnection(deviceId, name)
					},
					fail: (res) => {
						console.log('openBluetoothAdapter fail', res)
						if (res.errCode === 10001) {
							uni.showModal({
								title: '错误',
								content: '未找到蓝牙设备, 请打开蓝牙后重试。',
								showCancel: false
							})
							uni.onBluetoothAdapterStateChange((res) => {
								console.log('onBluetoothAdapterStateChange', res)
								if (res.available) {
									// 取消监听
									uni.onBluetoothAdapterStateChange(() => {});
									this._createBLEConnection(deviceId, name)
								}
							})
						}
					}
				})
			}
		}
	}
</script>

<style lang="scss" scoped>
	.page-section {
		display: flex;
		flex-direction: column;
		align-items: center;
		width: 100%;
		box-sizing: border-box;
		border-bottom: 2rpx solid #EEE;
		// padding: 30rpx 0;
	}

	.devices-summary {
		padding: 10rpx;
		font-size: 30rpx;
	}

	.device-list {
		height: 400rpx;

		.scroll_box {
			background: #FAFAFA;
			height: 400rpx;

			.device-item {
				// border-bottom: 1rpx solid #EEE;
				padding: 20rpx;
				color: #666;
				background-color: white;
				margin: 10upx 0upx;
				display: flex;
				justify-content: space-between;
				align-items: center;

				.item_left {
					width: 80%;
					overflow: hidden;
					text-overflow: ellipsis;
					white-space: nowrap;
					padding-right: 20upx;
				}

				.item_right {
					color: #CCCCCC;
				}
			}
		}

		.null_box {
			display: flex;
			justify-content: center;
			align-items: center;
			font-size: 28upx;
			color: #CCCCCC;
		}
	}



	.device-item-hover {
		background-color: rgba(0, 0, 0, .1);
	}

	.btn-area {
		box-sizing: border-box;
		width: 100%;
		// padding: 0 30rpx;
	}

	.connected-area {
		font-size: 22rpx;
	}

	.connected-info {}

	.input-area {
		background: #fff;
		margin-top: 10rpx;
		width: 100%;
	}

	.input {
		font-size: 28rpx;
		height: 2.58823529em;
		min-height: 2.58823529em;
		line-height: 2.58823529em;
		padding: 10rpx;
	}

	.btn_box {
		width: 100%;
		display: flex;
		justify-content: center;
		align-items: center;

		.btn_view {
			width: 100%;
			height: 80upx;
			background-color: $colorred;
			color: white;
			display: flex;
			justify-content: center;
			align-items: center;
			border-radius: 10upx;
			font-size: 30upx;
		}
	}

	.info_box {
		width: 100%;
		text-align: center;
		font-size: 28upx;
	}
</style>

  • 5
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
微信小程序蓝牙连接打印机功能可以实现与TSC规范的打印机进行连接和打印操作。下面是一个简单的示例代码。 首先,在小程序的配置文件中声明蓝牙权限,确保可以使用蓝牙功能: "permissions": { "bluetooth": true, "openBluetoothAdapter": true, "writeCharacteristicValue": true } 接下来,我们需要编写相关的代码来连接和操作打印机。首先需要初始化蓝牙适配器: wx.openBluetoothAdapter({ success: function(res) { console.log("蓝牙适配器初始化成功"); // 在成功回调中,开始搜索附近的蓝牙设备 wx.startBluetoothDevicesDiscovery({ allowDuplicatesKey: false, success: function(res) { console.log("开始搜索蓝牙设备"); // 在成功回调中,获取附近的蓝牙设备列表 wx.getBluetoothDevices({ success: function(res) { console.log("获取蓝牙设备列表成功"); // 在成功回调中,筛选并连接TSC规范的打印机 var devices = res.devices; for (var i = 0; i < devices.length; i++) { if (devices[i].name == "TSC") { var deviceId = devices[i].deviceId; // 连接选定的打印机 wx.createBLEConnection({ deviceId: deviceId, success: function(res) { console.log("连接打印机成功"); // 在成功回调中,进行打印操作 wx.writeBLECharacteristicValue({ deviceId: deviceId, serviceId: "0000FFF0-0000-1000-8000-00805F9B34FB", characteristicId: "0000FFF1-0000-1000-8000-00805F9B34FB", value: "打印内容" }); } }); } } } }); } }); } }); 以上是一个简单的微信小程序连接和打印TSC规范打印机的示例代码。需要注意的是,具体的打印内容和打印机的服务和特征值等需要根据实际情况进行修改和调整。希望对你有所帮助!
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值