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

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

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

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

技术栈

uni-app

参考资料

微信小程序蓝牙打印

项目效果图

微信小程序蓝牙对接热敏打印机_第1张图片

打印效果图

微信小程序蓝牙对接热敏打印机_第2张图片
微信小程序蓝牙对接热敏打印机_第3张图片

项目结构

只展示主要项目结构
微信小程序蓝牙对接热敏打印机_第4张图片

项目页面讲解

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>

你可能感兴趣的:(uni-app,小程序,前端)