在HbuilderX中实现微信小程序下蓝牙连接打印机完整实战案例

时间:2023-01-04 14:02:03


1.基础开发环境,所用到的 Api 以及实现的思路。

应用场景:

商家打印小票,小票包含顾客消费的商品明细信息以及末尾附上二维码,二维码供顾客扫码开票。 

在HbuilderX中实现微信小程序下蓝牙连接打印机完整实战案例

 

HbuilderX开发工具:

​HBuilderX-高效极客技巧HBuilderX是当前最快的HTML开发工具,强大的代码助手帮你快速完成开发,最全的语法库和浏览器兼容性数据让浏览器碎片化不再头痛

在HbuilderX中实现微信小程序下蓝牙连接打印机完整实战案例

https://www.dcloud.io/hbuilderx.html​​注:项目是基于VUE框架开发的。

关于蓝牙设备的api地址:

​uni-app官网

在HbuilderX中实现微信小程序下蓝牙连接打印机完整实战案例

https://uniapp.dcloud.io/api/system/bluetooth.html​

大致实现思路:

1.在页面加载时,先判断本地缓存是否有历史连接蓝牙设备。

2.页面点击【打印】按钮时,最好是提前把此次需要打印的数据项先组装好。

3.点击打印后,先判断当前页面是否有历史连接蓝牙设备的信息:

如果有:直接连接蓝牙设备,进行打印;

如果没有:则以弹框的形式,搜索附近可用的蓝牙设备;

4.页面卸载时或者页面隐藏时,需要取消蓝牙连接并关闭蓝牙模块。

注:我这里是在当前页面弹框形式展示的蓝牙列表,大家可以根据自己实际项目需求来做出一些调整。

2.接下来直接上代码,以及依赖的js和弹框组件

 弹框用的uni-popup 基础组件,如有需要,可以私信找我要。

在HbuilderX中实现微信小程序下蓝牙连接打印机完整实战案例

 页面蓝牙列表的视图view代码如下:

<template>
<view style="height: 100%;position: fixed;">
<button type="primary" style="width: 30%;" @tap="f_print()">打印</button>
<!-- 蓝牙配置弹窗 -->
<uni-popup :show="isShowSearchBle" position="middle" mode="fixed">
<view class="scrool-page">
<view class="search-part">
<button type="primary" style="" @tap="searchBlue()">搜索设备</button>
<button type="primary" style="" @tap="stopSearchBlue()">停止搜索</button>
</view>
<scroll-view v-if="deviceListLength>0" class="panel-scroll-box" scroll-y="true" style="width:320px;">
<view>
<view v-for="(item,index) in devices" :key="index" class="bluetooth-item" @click="onBlueChange(item,index)">
<view class="list-item-oneline">
<view class="list-text" style="flex: 1;">
<text>{{item.name?item.name:'未命名'}}</text>
</view>
<text>({{Math.max(0, item.rssi + 100)}}%)</text>
<text class="list-statue" style="margin-left: 30px;">{{item.status?'已连接':'未连接'}}</text>
</view>
</view>
</view>
</scroll-view>
<view v-show="deviceListLength==0" style="border-top: 1px solid #EEEEEE;margin-top: 10px;padding-top:10px;">
<image mode="aspectFit" src="/static/img/empty-icon.png"></image>
<view class="empty-text">~ 无可搜索到的设备 ~</view>
</view>
</view>
</uni-popup>
</view>
</template>

注: 当页面没有可用的蓝牙设备时,有一个背景图片,当然这个只是一个样式,不是关键的,但我还是尽量做到完善和完整度。empty-icon.png图片如下:有水印,如有需要原图,可以私信我,我会及时回复的。

在HbuilderX中实现微信小程序下蓝牙连接打印机完整实战案例

页面引入弹框组件和相关的蓝牙打印的js文件:

<script>
import * as Common from "@/common/util.js"
import {
wxAsyncPromise ,debounce,catchToast,
getDeviceCharacteristics,
sendDataToDevice,
printCommand,LAST_CONNECTED_DEVICE
} from '../bluetooth/bluetoolth.js';
import GBK from '../bluetooth/gbk.js';
import uniPopup from '@/components/uni-popup.vue'

export default {
components: {
uniPopup
},
data() {
return {
/* 蓝牙打印相关参数-start */
isShowSearchBle:false,//是否显示蓝牙设备列表
devices: [],//搜索到的附近蓝牙设备列表//{"name":"T5810000","devId":"UUUUUUUU","rssi":-20,"status":false}
deviceName: '',//已连接或已选择的蓝牙设备名称
deviceId: '',//已连接或已选择的蓝牙设备ID
services:[],//蓝牙设备所有服务
character:[],
blueToothConnected:false,//是否已经连接
blueToothPrintData:{},//传输给蓝牙设备的数据
/* 蓝牙打印相关参数-end */
}
},
computed: {
//计算蓝牙设备列表长度
deviceListLength() {
return this.devices.length;
}
},
onShow() {
console.log(" 页面onShow");
let lastDevice = wx.getStorageSync(LAST_CONNECTED_DEVICE);
console.log("历史连接蓝牙设备:"+lastDevice);
if(Common.isExist(lastDevice)){
this.deviceId = lastDevice.split("&")[0];
this.deviceName = lastDevice.split("&")[1];
}
},
onHide() {
console.log(" 页面onHide");
//取消蓝牙连接并关闭蓝牙模块
wxAsyncPromise('closeBLEConnection', {
deviceId: this.deviceId,
}).finally((res) => {
wxAsyncPromise('closeBluetoothAdapter');
});
},
onLoad(e) {
let lastDevice = wx.getStorageSync(LAST_CONNECTED_DEVICE);
console.log("历史连接蓝牙设备:"+lastDevice);
if(Common.isExist(lastDevice)){
this.deviceId = lastDevice.split("&")[0];
this.deviceName = lastDevice.split("&")[1];
}
},
onUnload() {
//取消蓝牙连接并关闭蓝牙模块
wxAsyncPromise('closeBLEConnection', {
deviceId: this.deviceId,
}).finally((res) => {
wxAsyncPromise('closeBluetoothAdapter');
});
},
</script>

此处我只列出,当前与蓝牙打印相关的js文件:

1.

在HbuilderX中实现微信小程序下蓝牙连接打印机完整实战案例

 util.js 文件内容如下:根据自己实际需求决定是否引用

// alert提示
function alert (msg) {
alert_duration(msg,2000);
}
function alert_duration (msg, duration) {
if(!isExist(duration)){
duration = 3000;
}
uni.showToast({
title: msg,
icon: "none",
position: 'bottom',
duration: duration
});
}
//判断字符串是否存在
function isExist (str) {
if (str !== null && str !== "undefined" && str !== "" && typeof str != "undefined" && !(typeof (str) == "string" &&
str
.split(" ").join("").length == 0)) {
return true;
} else {
return false;
}
}
//转换日期格式(时间戳转换为datetime格式)
function changeDateFormat(cellval, hms) {
var dateVal = cellval + "";
if (cellval != null) {
var date = new Date(parseInt(dateVal.replace("/Date(", "").replace(")/", ""), 10));
var month = date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1;
var currentDate = date.getDate() < 10 ? "0" + date.getDate() : date.getDate();

var hours = date.getHours() < 10 ? "0" + date.getHours() : date.getHours();
var minutes = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes();
var seconds = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
if(hms){
return date.getFullYear() + "-" + month + "-" + currentDate + " " + hours + ":" + minutes + ":" + seconds;
}else{
return date.getFullYear() + "-" + month + "-" + currentDate;
}
}
}
//计算字符长度(一个汉字2个字符)
function calculateStrLengh(str){
var len=0;
if(!isExist(str)){
return 0;
}
for (var i=0; i<str.length; i++) {
var c = str.charCodeAt(i);
//单字节加1
if ((c >= 0x0001 && c <= 0x007e) || (0xff60<=c && c<=0xff9f)) {
len++;
}
else {
len+=2;
}
}
return len;
}
// 声明
module.exports = {
isExist,
alert,
alert_duration,
changeDateFormat,
calculateStrLengh
}

2.

在HbuilderX中实现微信小程序下蓝牙连接打印机完整实战案例

 分别是蓝牙设备相关的方法和中文编码js文件,截图中是我的文件在项目中的目录位置

bluetoolth.js

/*
* @Author: zyf
* @Date: 2022年2月18日12:30:21
* @LastEditors: zyf
* @LastEditTime: 2022年2月18日12:30:21
* @Description: file content
*/
/**
*
*
* @param {string} name 微信api的名称 ,如 wxAsyncPromise("getSystemInfo",options)
* @param {object} options 除了success 和 fail 的其他参数
* @returns
*/
export function wxAsyncPromise(name, options) {
return new Promise((resolve, reject) => {
wx[name]({
...(options || {}),
success: function(res) {
resolve(res);
},
fail: function(res) {
reject(res);
},
});
});
}
export function debounce(func, wait) {
let timer = null;
return function(opt) {
clearTimeout(timer);
timer = setTimeout(() => {
func && func.call(this, opt);
}, wait || 60);
};
}

//微信小程序向蓝牙打印机发送数据进行打印的坑:
//小程序api向蓝牙打印机发送数据打印,发送的任何内容都应该要转成二进制数据,而且蓝牙打印的文本编码是GBK的,发送中文需转成GBK编码再转成二进制数据发送
//发送打印机指令也要转成二进制数据发送
//蓝牙打印机一次接收的二级制数据有限制,不同的系统不同的蓝牙设备限制可能不同,微信建议一次20个字节,需做递归分包发送
//发送完要打印的内容后,一定要发送一个打印的指令才能顺利打印 (有些指令就不需要)

//一、初始化蓝牙、开始检索蓝牙设备
export function openBlue() {
return wxAsyncPromise('openBluetoothAdapter').then((res) => {
console.log('初始化蓝牙成功', res);
return wxAsyncPromise('startBluetoothDevicesDiscovery').then((res) => {
console.log('正在搜寻蓝牙设备', res);
});
});
}
//二、
/**
*
*
* @export
* @param {function} getDevices wx.getBluetoothDevices的监听回调函数
*/
export function onfindBlueDevices(getDevices) {
//监听寻找到新设备的事件
wx.onBluetoothDeviceFound(function(devices) {
//获取在蓝牙模块生效期间所有已发现的蓝牙设备
wxAsyncPromise('getBluetoothDevices').then((res) => {
getDevices && getDevices(res.devices);
});
});
}

//三、连接蓝牙设备
// wxAsyncPromise("createBLEConnection",{deviceId:""}).then(res=>{
//连接成功可选择停止搜索蓝牙
// wxAsyncPromise("stopBluetoothDevicesDiscovery").then()
// }).catch(res=>{}).finally(()=>{})

//四、连接成功后, 获取蓝牙设备的service服务
// wxAsyncPromise("getBLEDeviceServices",{deviceId:""}).then(res=>{})

//五、获取的service服务可能有多个,递归获取特征值(最后要用的是能读,能写,能监听的那个值的uuid作为特征值id)
/**
*
*
* @export
* @param {number} deviceId 蓝牙设备id
* @param {array} services wxAsyncPromise("getBLEDeviceServices",{deviceId:""}).then(res=>{})获取的res.services
* @param {function} success 成功取得有用特征值uuid的回调函数
*/
export function getDeviceCharacteristics(deviceId, services = [], success, fail) {
services = services.slice(0);
if (services.length) {
const serviceId = services.shift().uuid;
wxAsyncPromise('getBLEDeviceCharacteristics', {
deviceId,
serviceId,
})
.then((res) => {
console.log('getBLEDeviceCharacteristics', deviceId, serviceId, res);
let finished = false;
let write = false;
let notify = false;
let indicate = false;

for (var i = 0; i < res.characteristics.length; i++) {
if (!notify) {
notify = res.characteristics[i].properties.notify;
}
// if (!indicate) {
// indicate = res.characteristics[i].properties.indicate;
// }
if (!write) {
write = res.characteristics[i].properties.write;
}
if ( write) {
/* 获取蓝牙特征值uuid */
success &&
success({
serviceId,
characteristicId: res.characteristics[i].uuid,
});

finished = true;
break;
}
}

if (!finished) {
getDeviceCharacteristics(deviceId, services, success, fail);
}
})
.catch((res) => {
getDeviceCharacteristics(deviceId, services, success, fail);
});
} else {
fail && fail();
}
}

//六、启动notify 蓝牙监听功能 然后使用 wx.onBLECharacteristicValueChange用来监听蓝牙设备传递数据
/**
*
*
* @export
* @param {object} options
* {
deviceId,//蓝牙设备id
serviceId,//服务id
characteristicId,//可用特征值uuid
}
* @param {function} onChange 监听蓝牙设备传递数据回调函数
*/
export function onBLECharacteristicValueChange(options, onChange = function() {}) {
wxAsyncPromise('notifyBLECharacteristicValueChange', {
state: true,
...options,
}).then((res) => {
wx.onBLECharacteristicValueChange(onChange);
});
}

//七、发送数据(递归分包发送)
/**
*
*
* @export
* @param {object} options
* {
deviceId,
serviceId,
characteristicId,
value [ArrayBuffer],
lasterSuccess,
}
*/

export function sendDataToDevice(options) {
let byteLength = options.value.byteLength;
//这里默认一次20个字节发送
const speed = options.onceByleLength || 20;
if (byteLength > 0) {
wxAsyncPromise('writeBLECharacteristicValue', {
...options,
value: options.value.slice(0, byteLength > speed ? speed : byteLength),
})
.then((res) => {
if (byteLength > speed) {
sendDataToDevice({
...options,
value: options.value.slice(speed, byteLength),
});
} else {
options.lasterSuccess && options.lasterSuccess();
}
})
.catch((res) => {
console.log("发送数据失败"+res);
wx.hideLoading();
catchToast(res);
});
}
}
export function charToArrayBuffer(str) {
var out = new ArrayBuffer(str.length);
var uint8 = new Uint8Array(out);
var strs = str.split('');
for (var i = 0; i < strs.length; i++) {
uint8[i] = strs[i].charCodeAt();
}
return uint8;
}
export function charToArray(str) {
var arr = [];
var strs = str.split('');
for (var i = 0; i < strs.length; i++) {
arr[i] = strs[i].charCodeAt();
}
return arr;
}
//打印二维码
/**
*
*
* @export
* @param {object} options
* {
deviceId,
serviceId,
characteristicId,
value,//ArrayBuffer:二维码的数据
}
*/
export function printQR(options) {
//打印二维码的十进制指令data:
let data = [29, 107, 97, 0, 1, options.value.byteLength, 0];
sendDataToDevice({
...options,
value: new Uint8Array(data).buffer,
lasterSuccess: () => {
//指令发送成功后,发送二维码的数据
sendDataToDevice(options);
},
});
}
function grayPixle(pix) {
return pix[0] * 0.299 + pix[1] * 0.587 + pix[2] * 0.114;
}

export function overwriteImageData(data) {
let sendWidth = data.width,
sendHeight = data.height;
const threshold = data.threshold || 180;
let sendImageData = new ArrayBuffer((sendWidth * sendHeight) / 8);
sendImageData = new Uint8Array(sendImageData);
let pix = data.imageData;
const part = [];
let index = 0;
for (let i = 0; i < pix.length; i += 32) {
//横向每8个像素点组成一个字节(8位二进制数)。
for (let k = 0; k < 8; k++) {
const grayPixle1 = grayPixle(pix.slice(i + k * 4, i + k * 4 + (4 - 1)));
//阈值调整
if (grayPixle1 > threshold) {
//灰度值大于threshold位 白色 为第k位0不打印
part[k] = 0;
} else {
part[k] = 1;
}
}
let temp = 0;
for (let a = 0; a < part.length; a++) {
temp += part[a] * Math.pow(2, part.length - 1 - a);
}
//开始不明白以下算法什么意思,了解了字节才知道,一个字节是8位的二进制数,part这个数组存的0和1就是二进制的0和1,传输到打印的位图数据的一个字节是0-255之间的十进制数,以下是用权相加法转十进制数,理解了这个就用上面的for循环替代了
// const temp =
// part[0] * 128 +
// part[1] * 64 +
// part[2] * 32 +
// part[3] * 16 +
// part[4] * 8 +
// part[5] * 4 +
// part[6] * 2 +
// part[7] * 1;
sendImageData[index++] = temp;
}
return {
array: Array.from(sendImageData),
width: sendWidth / 8,
height: sendHeight,
};
}
/**
* printImage
* @param {object} opt
* {
deviceId,//蓝牙设备id
serviceId,//服务id
characteristicId,//可用特征值uuid
lasterSuccess , //最后完成的回调
}
*/
// export function printImage(opt = {}, imageInfo = {}) {
// let arr = imageInfo.array,
// width = imageInfo.width,
// height = imageInfo.height;
// const writeArray = [];
// const xl = width % 256;
// const xh = width / 256;
// const hl = height % 256;
// const hh = height / 256;
// //分行发送图片数据,用的十进制指令
// const command = [29, 118, 48, 0, xl, xh, hl, hh];
// // for (let i = 0; i < arr.length / width; i++) {
// // const subArr = arr.slice(i * width, i * width + width);
// // const tempArr = command.concat(subArr);
// // writeArray.push(new Uint8Array(tempArr));
// // }
// const tempArr = command.concat(arr);
// writeArray.push(new Uint8Array(tempArr));
// const print = (options, writeArray) => {
// if (writeArray.length) {
// sendDataToDevice({
// ...options,
// value: writeArray.shift().buffer,
// lasterSuccess: () => {
// if (writeArray.length) {
// print(options, writeArray);
// } else {
// options.lasterSuccess && options.lasterSuccess();
// }
// },
// });
// }
// };
// print(opt, writeArray);
// }
export const LAST_CONNECTED_DEVICE = 'last_connected_device';
export const printCommand = {
left: [27, 97, 0], //居左
center: [27, 97, 1], //居中
right: [27, 97, 2], //居右
clear: [27, 64], //初始化
enter: [10],
};
export function printImage(opt = {}, imageInfo = {}) {
const { printAlign = 'left' } = opt;
let arr = imageInfo.array,
width = imageInfo.width;
const writeArray = [];
const xl = width % 256;
const xh = width / 256;
//分行发送图片数据
const command = []
.concat(printCommand.clear)
.concat(printCommand[printAlign])
.concat([29, 118, 48, 0, xl, xh, 1, 0]);
for (let i = 0; i < arr.length / width; i++) {
const subArr = arr.slice(i * width, i * width + width);
const tempArr = command.concat(subArr);
writeArray.push(new Uint8Array(tempArr));
}
const len = writeArray.length;
const print = (options, writeArray) => {
if (writeArray.length) {
sendDataToDevice({
...options,
value: writeArray.shift().buffer,
lasterSuccess: () => {
options.onProgress && options.onProgress(Math.floor(((len - writeArray.length) / len) * 100));
if (writeArray.length) {
print(options, writeArray);
} else {
options.lasterSuccess && options.lasterSuccess();
}
},
});
}
};
print(opt, writeArray);
}
/**
* 蓝牙设备异常信息
*/
export const errMsg = {
10000: '未初始化蓝牙模块',
10001: '蓝牙未打开',
10002: '没有找到指定设备',
10003: '连接失败',
10004: '没有找到指定服务',
10005: '没有找到指定特征值',
10006: '当前连接已断开',
10007: '当前特征值不支持此操作',
10008: '系统上报异常',
10009: '系统版本低于 4.3 不支持BLE',
10012: '连接超时',
10013: '连接 deviceId 为空或者是格式不正确'
};
/**
* toast显示捕获的蓝牙异常
*/
export function catchToast(err) {
let coode = err.errCode ? err.errCode.toString() : '';
let msg = errMsg[coode];
if(msg==undefined||msg==null || msg==""){
msg=err.errMsg;
}
wx.showToast({
title: msg || coode,
icon: 'none',
position: 'bottom',
duration: 3000
});
}
/*****CPCL指令接口****/

gbk.js文件内容如下:

/*!
* gbk.js v0.2.4
* Homepage https://github.com/cnwhy/GBK.js
* License MIT
*/
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.GBK=t()}(this,function(){"use strict";var e,n=function(c){return{decode:function(e){for(var t="",n=0,r=e.length;n<r;n++){var o=e[n];128&o&&(o=c[(o<<8|e[++n])-33088]),t+=String.fromCharCode(o)}return t},encode:function(e){e+="";for(var t=[],n="?".charCodeAt(0),r=0;r<e.length;r++){var o=e.charCodeAt(r);if(o<128)t.push(o);else{var u=c.indexOf(o);~u?(u+=33088,t.push(255&u>>8,255&u)):t.push(n)}}return t}}},t=function(e){var t=n(e);return t.URI=function(X){var e="!'()*-._~";function t(t){t.split("").sort();var U=function(e){return~t.indexOf(e)||/[0-9a-zA-Z]/.test(e)};return{encode:function(e){return(e+"").replace(/./g,function(e){if(U(e))return e;for(var t=X.encode(e),n=0;n<t.length;n++)t[n]="%"+("0"+t[n].toString(16)).substr(-2).toUpperCase();return t.join("")})},decode:function(e){return e.replace(/(%[\dA-Za-z]{2})+/g,function(e,t,n){for(var r="",o=e.match(/.../g),u=0;u<o.length;u++){var c=o[u],d=parseInt(c.substr(1),16);if(128&d)r+=X.decode([d,parseInt(o[++u].substr(1),16)]);else{var T=String.fromCharCode(d);U(T)?r+=c:r+=T}}return r})}}}var n=t(e),r=t(e+"#$&+,/:;=?@");return{encodeURI:r.encode,decodeURI:r.decode,encodeURIComponent:n.encode,decodeURIComponent:n.decode}}(t),t},d=function(){for(var e=0,t=arguments[0],n=0,r=t.length;n<r;n++){var o=t.charCodeAt(n);if(o<38||126<o)return NaN;e+=(o-38)*Math.pow(89,r-n-1)}return e};return e=function(e){for(var t=[],n=129,r=0;n<=254;n++){0<t.length&&(t.length+=65);for(var o=64;o<=254;o++)if(127==o||161<=n&&n<=167&&o<=160||170<=n&&n<=175&&161<=o||248<=n&&161<=o)t.push(void 0);else{var u=e[r++],c=d(u);t.push(c||void 0)}}return t}(function(){return arguments[0].replace(/\x23(\d+)\x24/g,function(e,t){return Array(+t+4).join("#")}).replace(/[\x26-\x7e]\x25[\x26-\x7e]/g,function(e){for(var t=e.substr(0,1).charCodeAt(0),n=e.substr(2).charCodeAt(0),r=String.fromCharCode(t);t++<n;)r+=String.fromCharCode(t);return r}).replace(/\x23/g,"###").replace(/([\x26-\x7e]{2})\x21([\x26-\x7e\x23]+)(?:\x20|$)/g,function(e,t,n){return n.replace(/./g,function(e){return"#"!=e?t+e:e})}).match(/.../g)}("(T!HJ%LUX]e%gilotuwy{} (U!)-%/137>BDGHO%RTUW%\\_a%jl%rtw} (V!*+-0%27>C%EHJ%MP%R\\`cdfn%ptvz{} (W!()*,/3%579;=%CFGM%QWX\\^cdg%ilnprtvy%} (X!&')%.468CDHJLMOPSTWY%\\_b%dg%ilnprtuwxz%|~ (Y!'(*+-469%=?%GI%KO%RT%V[%_bdikmnptuy{}~ (Z!&')+%-/%;>@ACE%GKMNPR%TW[_%ikmo%rt%vy%{} ([!'(%+-%024%;=%BD%LO%QSTX%[]^`%ce%y{} (\\!()+%/1%7:%LN%SU%WY%cf%im%prt%xz%~ (]!&'%*-%/1%68%EG%cgloqs%uwx|%~ (^!')%-/02356;>FJKOPRSVWZ%]_`dfi%kmor%vyz (_!'+%-124%68;=@ACE%MOQRUVX%]_adegjqwx|~ (`!&)*-%/689;%=?%ADFIKLNOVX^%cehilmoq%uwyz|%~ (a!'),%/124%=?AD%HJ%PRSU%[]e%ho%qu%~ (b!()*,%.024%79;%=?A%FH%KM%WY%`c%ei%loq%tvy%| (c!'*+-.1346%8:%<>%GKLOQSUZ%\\_cghjltwy{| (d!(,/1%4679=>@D%JLMOQRTVWZ]`%ce%km%pr%tvy%} (e!+,-/0279%;>?DQW[%]_bdhqu%wy (f!&().47:;>ACEFHIKMP%SU^a%egikm%tx}~ (g!)*,.02%58<>BCGI%MPY[]`%bdeginpuv (h!(*-2%6=>A%CF%KMPRT%WZ\\%`deg%ln%qswxz{} (i!&+-%/1%469;>@AD%HJ%MP%TV%Y[\\abdh%mrsvx~ (j!&,%.0235%7:;>@%FH%PRTVXZ\\_%cf%hjkn%puw%{~ (k!').04578;=?%CFI%NQRTW%^`acdg%ilmo%ru%wz|~ (l!&(*%,.%9=%ACDHIK%OQS%U[%^`%ce%hk%ru%{~ (m!&')%79%CE%KM%PR%^`%hjkmnqxz%~ (n!&(*+./2%478<%>ACG%WYZ\\%^`%cgmnp%txy{} (o!&'%)+,.5%9;<EFHJOQS%UWX[%]`%dj%mp%tw%} (p!&'%)/2469%;=?%AC%KN%TVWZ[]`aefhk%mo%vxz%} (q!&'(,-/024%69%;=?%AC%EG%IK%NPSTV%Z\\%`b%eg%tv%| (r!&'(*+-%/1%57%<>%BE%UWZ\\^%`b%il%ps%ux%~ (s!&)%:<%?A%CE%OQ%SU%bd%ilnpqstvwy%~ (t!&')+,.%246%9=>ACDF%ILNRVXY[\\ac%fiklprsvxy (u!&(%,.013%?BDG%IK%MRVXY[%^abeg%jl%ostyz}~ (v!'(%,.013%9;%=@%CIJMOR%VXZ[]%_a%lnp%rtv~ (w!&(%+-/%24%689<=?A%CE%KNPR%VX%Z\\%`bcf%oq%tv%|~ (x!&'(*+-%5:;=%@B%SU%[^%km%svxy{%~ (y!&(),%1346%:<>@B%DF%HKMNPQSU%Y[%qs%~ (z!()%ACEFH%OQ%_bfnpqwx{}~ ({!&)+-%023569=ADEG%IKMV^%`c%fhinq%swxz (|!&'%)+%-/2%:>@ADEG%KMO%U[\\^`acefi%lnpquwy|%~ (}!&(+-%02%578:%<HIKLQRW%Y[%]_%bdgil%ruw%} (~!'(%*,/%35%7:;>?AI%MP%TVZ%\\^`be%hjlnoq%vx%} )&!&'%+-%356:<>?ABD%MO%TWX[%`b%fhj%mopr%vx{}~ )'!&)%-/%69%@BCG%QSTVX%bdghj%mo%{}~ )(!&'%9;%=?%WY%eg%mo%{} ))!'(+,2458=>@%DGHLOQ%SUVZ[_f%mp%twxz%~ )*!()-%025%:<%BE%IKLNOR%`dfhmp%rtwx{%} )+!(.137%:>%BD%HJP%SU%^a%fjkm%pr%} ),!&(%02568%:B%DFI%KMOQSVWY%[^%aehikmo%uxz%|~ )-!&'%358=%@B%DGIKLORSVX%Z\\^a%cgjq%suwxz~ ).!&(%+-%2467:%?AC%FI%MRSUVY^%`e%gijmnpqstwz{}~ )/!&()+,.9%;=>BCEIJLPQT%V^%`b%fh%loprv%|~ )0!'(*,-/%1457%:>?GJKMNPWY%[^%acdg%jlnp%ruwz{}~ )1!')*.035%79:=%DG%IL%PR%TVWY[^a%ejqruwx{%~ )2!&)%,.1%37%;=%@B%EHILO%QS%eg%nprtvwy%{}~ )3!&'%*,%/1%47%=?%BDF%XZ[]%ac%jl%txy{%~ )4!&'%+-%24%68%<>A%EHJ%Z\\%ik%su%z|~ )5!/058%:<?B%EGIK%NQRT%X^`%bfklq%suvx%~ )6!'(+4578:;=>@D%FH%KN%SW\\_%afijlmp%su%wy%{} )7!(*+-59;?GHJKNTUZ\\_b%ejm%pt%wyz|%~ )8!&(%*-78:=%?ABD%ILMO%RUXY[b%eg%nqu%wy{%} )9!'(*%-014%79;%>CEIK%MOQ%WY\\^cgijmnqsuw%{} ):!&')%+02%46%<>AC%GJKM%PRTVX%[]bce%ilmpqt%y|~ );!()%+-%/14%9;%?ABDFHIOQ%WY[%]_%ce%lnp%rt%} )<!()%.035%=@ADEG%JL%PR%UWX\\^%acfhj%lnpqs%y|~ )=!'(%*-%13%579;%SU%\\^%eg%km%xz%~ )>!'(+%.2578;%?ABEFHIKM%ORSUWZ%\\_`b%eh%jlnpqs%~ )?!&'(*%,/%146%8:<?@BDEHJLNPS%Z\\]`%bdi%lorsu%wz%~ )@!&'(*,-34;%>ABD%HJMNP%RTVY%[_%ac%egj%mor%uwy{| )A!'(%+-/024%:=>@AHIK%NPRSUWY%]a%cehik%mo%qsuvx%{}~ )B!&')%,/%35%=@%DF%JL%OQ%TV%oqu%|~ )C!&(%-/34689;?%EJLMO%QTV%XZ\\^%ceglnpqt%wyz|} )D!&)*,/0279:<%@BG%IKLPQSVY[]%`beghjkmoq%tv%y{~ )E!'+%-0258:=>@E%LNQ%Za%cgkmopr%tvwyz~ )F!()%/14%79@CEGHK%OQ%SU%[^%dg%imnrz~ )G!'),0%9DF%MOR%Z\\^a%hj%ln%pr%{}~ )H!&*%-/2357%@BD%IKMO%RTUWXZ\\%`bce%nps%uw%y|~ )I!&'(*%,.%2479;%FI%KM%TVWY%[]%`b%dfhik%{}~ )J!'(*+.01346%9;<>?A%EIJL%NPRTY%[]%befhil%uxy{}~ )K!&()+,.%02%68%?ACEG%IK%PR%TW%bd%gi%rtv%~ )L!&(%36%;=?%DFH%KMO%QS%Y\\%`bce%ln%twy%} )M!&'(*%46%<>?A%CE%GI%QSV%Z\\%ce%lnq%~ )N!&'%~ )O!&'%178:%CE%HJM%OQ%TVWZ\\]_%jl%sz{}~ )P!&'%136%9>AEG%JMNP%RU%Y[%bd%koq%wy|} )Q!&'%*,.018:<%@B%IL%NP%RTV%XZ%\\^%dh%lnrw|%~ )R!'*,2%48:=>@%CEFJ%LOR%VX%Z]_%aijl%nr%vxz|~ )S!&(%*-.2589;<?@C%FHJ%LNPVZ]^b%ehn%qt%vy{%} )T!&'-%/1249;<>ABGINTUWXZ[]%_bd%fh%kmqrt%y{}~ )U!()+,02%46;<>@EHLQ%TWY[]^`acdg%il%oqru%wy%} )V!&')*-.02359%=?EHOPSTVWYZ\\%ad%fk%mp%su%xz}~ )W!&),-/1%479:<>%@BDG%NP%SUY\\]_bcefhilp%rtvxz{} )X!&'(*,%36%=@%CFHJKM%OQ%[]^`%nq%suxy{%~ )Y!&)%/1%35689;<>@AC%FHKMPQTV%X[%^`%bd%fhjnpqs%u{ )Z!&)%24%79%@B%DFGI%MO%QU%^`%bd%gkmoqstv%|~ )[!&'%+.%024%=?%ACE%GI%KM%RU%WY[%]_ac%ik%mpqu%~ )\\!&'(*%-/%35%?ABDEG%LNP%UW%]`%jlo%z}~ )]!&'%DF%MP%VX%hj%ln%~ )^!&()+%8:%EIL%ORT%VX\\%_a%cf%hj%lnrsuvy%~ )_!&'%,246%8<@AF%IKM%Y\\^%`b%eglpr%xz|%~ )`!'(%14%8:;=@D%NP%W[%^`%mo%rtvx%~ )a!&'%+-/%359%=?%AD%GIKLN%SU%Y[%^`%ce%gj%nq%wy%{}~ )b!&'%)+-%/1%9;%DF%JM%VX%[]_%df%oq%|~ )c!&'%:<%EGIK%MP%RXZ\\^%dg%il%oq%suvxz|~ )d!()*-/%25689;%=@%BGHJ%NQSUVX%ce%psv%xz )e!&'%,1%35%8;=?%BDFG 'W!,-. &(+&.'&-~&'u'W!/1 ')>.<V')!@PBCFG 'W!@A4%;BC<= &'~&(!Kk '/!J;< '.!~| '/!>= '.u'/!K. '0`'/!94 '1t'0T'/!?Bu`\\Q1t '0!)* '/!xy2IH ';!*( &'}')!\\] '+{.;U&'q.>!&' ')Z&'t',5':!GF '9!eiha`;:ML ')e'-!XVWY 'W?'-!67%?#3$ '6!-.%@ '5!rs%~ '6!&'%, '5!^_%g## ']!67%?## '-!&'%1## .;!RST .>+.;!VW%~ .<!&'%U .>)'W!mn%~ 'X!&'%f#8$t%~ 'Y!&'%p#5$ &0!=>%MO%U#5$]%mo%u#4$ .9!89<=BC@AD%G##>?:;4#67#6$ &1!cd%hTi%~ &2!&'%)#12$*%/K0%I#10$ &.!()7 ')!=?O_ '+}',('-!\\]%_ '/!)37fz{ '0z'8!CD%ft%~ '9!&'%)-%/VW|%~ ':!&J '0P'W!>IJ#8$ &(!uU &+7&(T&).&(]&)6&(\\&)F&(a&+9&(`&)h&(g&+;&(f&*-&(n&+=&(m&+!?ACE &(!p^ &,a-Qc&)!_c -Qd&,q#1$'Z!&'%J#18$ 'W!MN%U '^`'a!@AN%PSv 'b!'*+. .93.>!(*# ',@']G#'):#0$'Yv'X!no 'Y!wx 'W2'X!pq .9!LM%UW%Z\\%ik%n -R!*+%6 'W3#10$'7!LM%~ '8!&'%>#12$ )e!HIKN%SVWabei%lnp%uw%y{%~ )f!'+%-23679%;@BCEFHIM%PS%_abdf%ik%rt%~ )g!(*%79%=?@BDFGIJL%OQ%TV%XZ[]%bdfgkn%prsv%y{}~ )h!&'(,-/3%9;%>@B%EGIK%MO%RT%cehil%or%z}~ )i!&'%)+,/13579:?%CE%HJ%\\^`eh%tvwy%} )j!'(%,.13%57%9;<>@%JLN%UY%hj%~ )k!&')%1357;=%CF%IKN%TV%Y[%bdfhj%mqstv%z|} )l!'+1369:<>%ACDFGJM%PR%UZ%ad%fh%npr%tw%{}~ )m!&()+%.0%2479<?@BFJ%NQZ[^_c%ejopr%uw{}~ )n!'(*%,047%:=>@%CEFHIMOQ%TVX%Z\\%_aeg%ilnrswyz|} )o!()+%-/346%=@%EGI%MOQ%TV%\\^%`b%iklnq%suw{|~ )p!&()+-.013%<>%FHILN%WYZ\\ (iC*r5(pM)89(gy(h[(gk)p*)o>*A;)s|*9E)ui)cO*s5*ux)R/({@(Z*)7s)B.(~d*4~)F{*42)@K)pg(_l)>Q)a|*2'*Jb(\\0(u2)4?)\\@*9t)8])5n(eJ(f+)|s(^7)mH))<)7>*Yr*ua)6M*2O(o@*t|*0J)cV)oo)E[)op);L(XR*W~)7F)z6)?3)hN);2)66*8L*xa)Dd)cf)61)76(Wo)k9(cY(a_*.d*b,))v)G`)Jk*6R*.k)HS)vH*E'*oR([d*U/*:L*4b(bm*L>(a&)p!]`bdelnrsu%wy%} )q!&'%-035%7:;=?@BCEFJLN%X[%^ac%egj%lnp%ty{}~ )r!&)+%-/%69%@C *B**t=(Yf(qR*{F({T)6!th )BK*V+++A)b})DT)um)12(c!`& *^r*4P*Wv*mT(Z=)e4(t-)1k)`B*K0(tz*:])Cj)}<)&|*/8)l*)TJ*[[*`!0t +3')Q4*cF)}()-`)v**@.*A<)Q!596 ))I)*v)nD*q<)>X),G).P*_0(s@*7;*a^*rQ*v?*_J*/W*X,)5](YH(e5(cm*_!9:< *a,)F:)-N*6j*JF+,1)3Y(`E)nu)-P)?.)\\_)Z'({u);N(^!A| )EP(T_)yA*{Q)_5)r!DE%GKNPQT%_a%ch%jm%rvwz%~ )s!&'%,/12469;%=?@BD%HJLN%Z\\%df%hk%or )mi)*e)gu*=C)<Z)7R*mh)T7(ci(b+):n*mu)~O(Wj))c*8V*5A*6\\)Wn)Sx*~=)8f(ck(hL*JC(pj(TS))K)Ow*'H*bn)/H)=:)f/*KF)D5)5i*W{)rS*Zw*eB)-M*=?*@y*z>(dx*E0)PD)1!mh )^Z*:;*8Q(Vg)SU*Bu)<z*)0)Ks)C7*;^(dK)}j(Y0(^X)UG(}G*[b):1(e&*;!AK )Eq)v7);C(|=(~@))6*TK)70)F'*V,):_)9r*G2*{`*T{*/a*nL(V;*q_*y+)@U)f))s!tvx}~ )t!&')*,.%18%=?@C%EHJNPSU%WY]_`cdf%hjkrtv%y{}~ )u!&')+-/04%8:%EK%RT%WY%_ (nv(|{)*'*p'([V*3}(d8)>Y)lB(i*(ZQ*Y,)6G*mQ)C[(ky)[T))*(f9)^m*^P)62)<Q)9[)-_)[n*bz*7\\*_A(|v)AX).|)S7*r>*Y(*JJ)<>)yh(pX)Lv)5,(fL(UE)z*)1i)[j*T>)6B*`V*~U)y\\(e`)n?)7k(c()Rg*_p),X*~:*2q+3k(Xj(}?*Xd*1T)?G)_?(]j(^~*D_)&Z({W)7'*d@)lq*ZZ)z?)2()~4(V[*/9)rl(TW*7f(`7(_m)M5(d^*[|*n^*sl)YY*rZ)J))u!`abdfgklnp%ruyz|~ )v!')+%28%;=%@BCE%GI%KO%RT%VY%[]%jl%np%tvxy{|~ )w!&'%*,%.0%6 )tu(\\&)se):o*N`(t*):B)(~){E)Ie(W[*8Y(j8(Tx)mR){])*!Qy *q>(`5(f=)^e)9.*n~(oe)@n)Ig*d[(hY)W=*.I*IY)5O*/1)mY*;=)vD*si*_/)2o)kM*T1)Ov(`T*XP)O3*3G*>{(n-(bn(Vb(Ta(_D*(G*d_*&i(YL*[t*&C){b);m)&g(\\**51)nL*(i)W6*1o)D6(zh(|V)vN)<[):r)9b)8<*ns);3*_O)}h)nt)5o(tM(fJ)P2([z)5P))n)P?(Vw*X7*Ji)-i*`f)w!78%<>%GI%MO%]_%cgil%oqt%|~ )x!&)*2457:%=?A%GJL%PSTW%Z\\%_a%c )/R*2s)7/(U&(cd*b~)9p*4J)@/)R5(X()1n)W+*TB),v*Ef)-7)82(^&*;v)G=(_s)8t*[=(ZB(~G)xH(|Z(`J)zZ)1<*a2)pp).B)-{)ov*[a)^J)om)}])s8(_f*ar(qU(X0)Z3*_{)>G)}/)e0)VG*1n(yJ)6x)++(nl*?3)}@))e),\\*`J*/U*y')9:)Y_)ut)_;(^D*uF(p5)l2(W~)l5)+-)1f(u-)Vc)Px)ue(eY*sr(_!>t )9A(eg*mF*Tg*Ys)cW)u{*G_*_~*Tq(e=)x!de%jl%wy%~ )y!&'%*,%047:<=?@BDF%HKLNOQ%VX%Z]`bdfi%oqrtvwy|} )z!&'%)+.%24578:; (TG)q/(eK*m<*xV+2S*o.({Q*S_(T!hb (^x*>m)47(ai)F>(Xy)0D(_.)Ts(^()6Y)?9*rW*UQ*`O)m|*c*)rJ)Q2)dO)eX*T_(qf)r`*XL)DA*oA*3w)+<)Wk(u_)|\\)s{*o<)Pn)?O*/O(q7(]v*qn(|W(s((f,*[g)>a*x_(my*mP)q>*`y)9?(gq(t!@` (o~*\\N)Cs*ZH*8U(`[)1p(qF*F@)&;+0<(YM*x}*Sv(w@)0O(d:)6?*a.*c/*{T)0B*2B(]d*2i(|r*{J)U-(Uy)z!<>@AC%QS%UWY]_%df%oq%uw%~ ){!&'%57%9;A%DHKMO%RT%VYZ_%adgh *X0(e.*0B)}c(WK(U<*qO)T*)h1*C6))N)lg*21)L')t3*mE*-4(_T)_h(e**_e*:q*X))dt*{B)T0(o-*9z)?[*4.)5[*r((uu(W:*S|*.T)>9*=U*uI(iZ*ye*4)(c9*Ta(e}*4>)+5)Sf*X9*9s*d.(f-)Q{(_y*.Q(oB)`C)S,*(9(tq(W8)/1)2K*(Z(Tv(|_)E7*FD)&C*ne*yU)mS)`&*`Z(^{*/^*Sz(to(_W(X=(f*(tQ)>r*4(({,)69)7,*^z)*4)R&)}:(WJ(Ya)CK){!ijmo%qt%xz%~ )|!&'%+-%79%BDEG%JNOQT%Y[]%ehikm%ortvy%{} )}!&'*%,.0135%7;=A )Uj)VM)x`*K6),T)l()6]*^o(Yx*eW)?I*5!Z| )+2*5{*Xt(a0*MY*XK(t3([\\(Vl*qk)cT*6K*Wx(|**S`*r:(uT*/[(g;(ld(kU*TI)>4)JQ*mL)po)Xz)*a)kn)D+)E])|l*3z*Xv)2F)y>)>]*Xc(^T(`4*mU*/y*3x*.L(~C)Wy)DE*&;)o}+&I*6a*0|*),):}*oQ)z^(fN(h7)O^):`)4}+04*4w)m=(a3*uT*>e)Fo*F&*qP*s1*nF(Tp(ea*.s)Fl*Z-*2K)C2)+0*1H)}!CDFH%KMOQS%XZ\\^abgikmprsuw%~ )~!&'(*+./13578:<%?A%GIJL%NQS%VY%[^%`i%kmnp%rt%{}~ *&&(pL*2u)Gq))-)>6(a`)0F+4-(X}*\\H(^8({b),P))1)Re)7[*Wz(^=*m\\(bf)SM*:M)eC(p,)Di*X-(tE*_-*=*(g@)~H(Wk)Sk(zt(vE+2X(eA*Ee*~r*UB*3~)>@*x^(n6*sd(`H)k2(`j(|?)7l*L.(UC)7:)/\\)H{(^?({O(^l*N<)~\\*{[*08)1o)^'*X/(]n)*n*`S)ix*N>(ni)tz)-6+42*qI*^R+'T*TE)oj)Fu)Eh*Z8*X5(`W*^t)Yr)HN*n_*bs(n9)E(*K~*_X(gs*&!'*%-/%246%:=>@%BD%FI%KM%OQRT%XZ%]_a%hj%lopr%wy%~ *'!&')%.124%@B%GK +&)(zy)Us)R-(V9({j*~Q*d7)3v*b5*v{(f/)VX(|0(_p(j**0=*2&)<i)8^)@:)43)0f)`3)R!^P (tU)DR*8J(sT(l|*Uu)QK*bc(uJ*2M(eT)Ue(fy(j=*<3*=2)Fk)y6(g7(X7(ee)pk*V;*qQ)Sa)V[*Xk*L<+0[*X:(l-+1o)my)-l)eL)0A(hN(V<)LG*J?)0+*^Z(go)_Z*Dm+06)U&({F),U*.<(j)(Y8)fG(f@(dP(ZI(ek(g'(U;)//)ib(su*>u*4e*G])e<*(z)XG*'!LM%UW%{~ *(!&'%*.%8:%@CDFI%KPQSV%Y[%]_`d%hjk ),d*DB(h~)6g(V&)SX)5S*9x(h)(c])fQ)Yi)l8(`,(b~*TA)KF)-n)/2)W8(o2)O4)gi)G-*_i*/T)8s)0|(hv)n5*Um)`>)VF({])*j*;g)2s+1b)v}*G@*'0)oy(_c)1v)`u)A3)*;)0&*Tr)^K)86*^s).H)0;*Eo(ms)Pz)0m)35(cX)1`)AV*X?)yu(WU)_k)RN*Sp*TV*.r*;y)@X(wu+'Z)UM)WA*UL)U7(WT)^F*<s)52)1Q*tQ*X')xI(_n(nz(q+)Cx)lu)z\\)yg)~P*(!opr%tvy{~ *)!&'%+/12458%>@%EG%NPQS%`b%vxz%} **!&'(*%,.%4 *3i*{:*`1)\\M*Sb+/q(v`*/!*J (ef)Df)HY*^{*'V*sc(e')/W)mb)Ry(d)(y2*.A)85(_S*55))9*@7)6C(^L(zs(WI)x>(`\\)18)UJ({!{y *S!^m )@+).W*r;(u`*/(*.D(kP)EC(t_(XU({m(aa*;o*xj*X>)l/*mq(Zw)z[(W2)EB*~H(y*)P5)pj(o=(|t)}N*qC)`w(^H*4-*97(uE*/E*;<)HA)Ex)v4)uS)7M)8r)~;(Yv(a+(_B*;e)KQ*g=*ZC*X1*N;*o/)~h(W1**!56%km%tv%~ *+!&'%= ([<*8P(`k*{D)WZ)Xv)VJ),7(\\s(vP(|d)UB)Rf)m**?<)GB(t|*So(c/*dE*rC*AH)I:)w+)`O*4z(V8*bP)UC*~N(v{(mQ):d*nJ)sy(Y.*5E)eM*NL*{O*/u*.x(a@)>T*dI*^!im (eH){\\):L)9])ox)yp*J5*r,)5F(al*9I)G.)DU)9/)rR)|Z)TV*.m*N](vD)5.*Bo*9l)lI(ZO(V_)mI*TO(}O))F)}_)?F*eL(V^)Tz*M2*)~*o'(VY*U3*_l*u^)A;)xR*_b)_n)Ut*+!>?%JL%RT%~ *,!&'%F (}B(as*;[(^.*:|)rB)Af++l(V@)1J*(!nc *.i(V)*R|)A_*xh*uD(r[)>g*o])-h)mm*uA)|!LK (_3)_1){^):I){<*.:)gP*w&(U2)^S(UJ*d&(d_)>L)@0*7!u~ (g9(}6)m>*v2)7B)eE)ma(}J*~C*=-(}E(g+)sw(U+)S\\*37)7<)9&))0(^C*Z!+l *o0(Yz(eB)1g)_.(a()8a+0:(w:(ZV)qw(d-*.|)<2)>&)6L)9P(ZD)cS*NC(_&*S}*.w(o**=/*mZ(^g(ex)&N*,!GH%~ *-!&'%-/%35%9;%P (YS)|P)UV*bg*~Y(iy(gA(cp(gZ),=):H)JF(_b)36(_}(q[(b@(o1)tB)qK)+M)3E*)7)5w)6Z(V6)^p*29)7a)_f*uK(oZ*:I)E{)Hv)vX*xw)yI(sr(g^(eL)W~(]p(`U({Y*Tb*43(i<(p0)0L(o4(f1){@)0)(zd)9_)6c(e@)6&({~)E;(h?){e*:T*dK)+)*Ki(t^(p8)7x*Z6*4s(o:)~2(Y2).v({t)OP(c0)}d)e.)Fy)t4)qv)@@(_((U|)pm(~k){k*?&(tt+'Y([W*-!QR%tv%~ *.!&'%46%8HNUVp~ */!-:KQ_xz|%~ *0!&'%)+.%7:; *{])2N)Xt(oG)@O)8W(n,)7V)6,)+i*qc(of)73(j'*Fc*5u*_4(i_*<m)DJ*XV*@5(x,)FB)7P*&P*q`(^<)\\)*U;):\\*NB)4G*/G)_D)Y})hf)Jc)eh)+`*bT)CU)Uf)8o(at)d'*27(XN(vY*d>)_[)V@(b:(U=*t;*on*A})vM**-(]F(ou)<o*3h*(+)T?)Hr)J-(_*(dN)H))dF+1l*&S)ed)y3)ZR)hg)D3*eG++X)wf*<E*;i(el)FJ(U*(d?(ar)ts(d<(`C([_(Xm(YY*0!>AC%FHK%OS%UWX[]%`b%eghj%qsuw%{}~ *1!&'(*+/12457%=@B%FIJMOQ%SU%XZ\\^%befh%mqr )n-)|8*(B)SS*sS(a\\)j2(h;(Yr*31*<n*_o*oq)+_)/N)VB))`)>V*5c*nX)/})_>*.]*(^(_<)G[*(a*96(Tj*^p*eJ)An(VB*Ti)<V*3'(\\X)m;)bE(|C*_N)[L(Us)</*od)+l)?;(`1*KG)_J(ad)ez)i;)D|)vz*tZ(Tz*bA+,9)Vn(_N*XA(ez)78)gl*=T*t:+3p*mv(Wq*1)(_?)^P*rk(~Y*xf(eC(Vx)Y4)hj)K*)uG*{d)lV(`2)no)U=)F&(^9+4,*3((gD)}P)|,*1!svwy%|~ *2!(*%03%58=%@DEGIPQS%UWYZa%fhjoprtvwz{}~ *3!&+%.02%468%EH%MO%Z *r|(og)7L*r=)JK(vx):a*&5)ZN*rA*9m(cq*xe)+&)^d)9k(cs*DC++5+11)uh*tv*Z=*XB+0v)[D)xU)-9*r<)5J*~>(er*n7(Y,)?g(}!jf */V*DE)R6)-o)gA)Y|*/@)&9)8,(zz)Y~(d+(U^)9X)lb)6d*^X(Wa(Ve)ST*.P*Sw*>'*HJ*0?*`s*n})~d*~k*K}*>G)Tp*~P*.v(uU+1Y)gU)t6*Dv*~!6L (kb(et(pU(U]+1D(ce*_Y*54)ry*{g*F0)Yy*@k)C:)30)Zj*3![^_begj%oqrtv{ *4!'*%,0479:<=?IK%OS%Y\\cdfgjry{} *5!&)%,.%02367@BDF%HJ%VXY[^%`bd%hj *U4*9Y*;@)q2)Qs).d*<y)f>*:s*nt)Q;)si*my(c~*:p(^^)*,*V>*X8(U5)ge)E&)G_*dJ*tM)a4)SO)qI(xT*oB)Q/*0v)@p(|.(v-(x\\*q!7F (W'*=o*?J)Mp)px*o1)f.)H'(zo)qo*dn*uL*)3*`/*U'),b*'|)yP*N?)Sz*F^({})*1)HJ)q`*/6(^a(]r(b1(wD*/F*uM*:E):.)rk*Bv)yJ)X+)+/(uf*@i)^H)Qq)7))pX)>J)1/*qL)@W*H/+/s*nG(ej(g(*U7);~*5!kl%prtx%z} *6!()*-%13%68:<%>ADEG%JSU%Y[]%`bd%iklp%tvwy{}~ *7!&'%+-%25%8=>@%BDF%L )<&*C^)L>*6n)vk);o+'l)6|)Ci*<g(s'+1v)SY)Z_(|<*rB*To)c})E\\)71)d&(V=(v:)Q-(rk)BE)*D(bG)2|)Cf)q.)rM)d,)ze)7i(h<(fT*mS*b)(w7*.M)Qv(U0(uC*2F(tn(cf)l4*s[(m_)81*5<)0U*qH)Vy(ff*{V(^Q(yR*5((u{*tW(ZZ)T:(}A)7I(XB(cN*6M*`>)YR*A*)72)*c)vA)<b)93)/'(w3*dS+/o(}D)8`*2x({4(ig*o!KN )V()1()El)eJ(_P({8*7!MN%[]%_abdg%kn%svx| *8!'(%+./135%:=@D%GINRZ\\]_%cefhjl%np%wy{%~ *9!'(%.0 )7Q)T3)cy)60*ma*.W*5>)UO(^h),A(uA(ak)/D(u@)B-*DD)mC)8;)4[*)F*T^(h8)O9+/u)Pm*B{(fY([M)SR(ic*Y[)cJ))])/G)i>)77)9J)cj)-|)X4)U:)=T)[>)pq)vS)q<)lQ(`>(e8(U@)+4)?c*2`*4Z)6^(g1(`B)_0*Jk*Yx)T6(f|*4^(q**Ky*XT*r`*cz(XK)SI(jW)p_)te)6k)_{)[t)-Q*@D)H[)uX*&Y*6z)CI+0F)8x)v3(oR)tG*t})6!<n (aI)i=)Q7*9!12%48<%?BCGHJL%NPSWZ%]abegijny|} *:!()*-.01347>DGHKPU%XZ%\\^`acdfhj%nxy{~ *;!'(+-.2%;>?CEGLPR (i]*5i)mV)pG(cH)n)(\\y(}F*S)*&)([&)ce*~a*y_)ZA)k8)x9)<4)s3)7f*xq*X+(gl*35(U?(oM*:g*=p)*b(_`)kL*T((}T(oD)TQ*xg(wa)ti+'\\*_|*(E(`p)5m*nT(VZ*<B*>k*=L*?c(p^)Cr(n?)A^)Ub*U<)Bt)]E)C])OL)FI)56(v/(TI(_7)ZE)E^)S=(^B)HL(tm)Oy)lc)&a*5;+3<)?y)w!rk *~[*Xw(~.(UK(W<(f3(tw)tA(lV)Qt*_U),@)S+)T8*Sh)9H*;!STVWYZ]_%chj%nrw *<!,-%256:%@CDF%IK%MP%SUVY[]%ch%kqwz{~ *=!&')+,.013%68:<%>AE%GI%KN%P )0I)U9)d:)6)*m!Ax (cR*_R)~9(Vs*U1(Uk+1r*m!df (`g)[B*aB)UA*U?(qB)'F)O!2[ )E6(f_*6&*0t(rq*A6),w)6e)Ld*o:)>m)0])G?)s5(UA(Y1))/*v*)<Y*1g)&@(YX){S(_z*:r(TZ)/a){I*mY(Y>(VF)y^)i<(bL*:')=&)y5(|;)S/*u=*^e)R)(Zn(d5)WO)<{)Ad)R7)1E*xW*T[)*u)iD){=)yC(T^*b7)hd)RI(mo*cx)Ss*Y2(cI*Ea)C0*\\K(}M*~;)Wj*=!QRV%Z]%_a%cehj%msu%z|%~ *>!()%-3%:<%AC%EH%OQ%Z\\%`bg%jln%rtx%z|~ *?!(*,- (e3(wQ+3w*YJ(dq(nD*y.)D(*do)Sw(^4)mn)7])dq(`R(a^*/{)^o)o*(d*+0A)K-)uJ*s]*K^*13)`_)b:(^U)E_)k:(sc)=l(mL*Sf*{K)63*.o)1y)_o(sk(V!UN ).X*vy(W6*Sa(_v(uW*.E)5p(Va)@.)5**;M*?z*;x*Gv)ad)YN({p*.l(^c):()E`*3f*;N*IL(]7({1(uO*_B(U`)Rd(^1*LQ*^Y*q}*AI)if*nj*q?)VI*:S),n)<?)Tg*>2*6@++w(r6*X[(TO(dw*?!./%24%7;>@%GIKLN%QS%_abdf%hlmoqstv%y|} *@!&'*,-/%469:<?%CE%GI%SUVX%Z (WY)>^)G+(js(Tm):s*~d)6.(y+)ig*99)VL)Ho(\\e*<J)t7)C~(`S)SG*.K(\\9(i^)a.)}o)AB)h+*:Y)D.(}v(`:)aM*JI*q9*rK)<r*Xa*7,(i})R\\)5c*/o(rj(W_(i?*XO)A<(TP(|x*Kx)G|)8_)z,)_i*9T(bb*w/(|o(h:*b;*.g(u/*XJ*Eb*8!,- *Xf);J(|b)2x)V,)wh*S{)tl)l0(g&(Ws*\\G)W;(w;*._)x/)S6({X(x8)kp*4B)2u)Wa(so)k!u{ )d7(cV*/4*@![\\%ce%hjl%npqs%vxz%~ *A!&'%),%5=%AC%FJ%TVWY%\\^%bd%fh%prt%w{|~ *B!&' )qA)pt(a**4`(n_))P(tW)eZ)?_)Xp*T;(}=*De(cP)t()7h)@?(VT*yf*Dt({U*T*(eO)sp(Xa(}S*~@(v?),L)wd(sj(T\\)F2(Ux)7W*P[*`U(b')@x(j|(lZ*`:(Vi(Xo*eE*{E)Qo))J)/u({[*N@*T9)>1)5g*{k({?(|])9@(i'(e4)nq(^I*A+)E.)H4)PC)6U*^}(e1*82)Vt)m3(US*XN({')ao*BU)B!(p *0Z*FB+1k+2e(}c)Cm*`()FD)DD)5))|C(r])+L)>k)Zp*B!(),.%46%@C%IK%MOQ%SW%\\^`%fh%np%twxz|%~ *C!&'(*%02%57%E )0.(`Q*2C);Z*JN)l&))M)FF)7^*t`(e))c[*Te)R;)rH)z-(f2)s:*xZ*8<*TX)u1)CR)_C)?Q)<B(k})/O)y9(eU(Tc)P4*3c(|X(k*(mp(W!uw *{G));)Dc*2n)/4*xb*`9({B*Sy*TY):{*8x)=2(kD*:=*d9(Y`*)w*`W*(O(\\q(r))YG({v*T-*3`*dL*?M)~R*Dq*s!(, */&+&D)uj(~-)zv*`z*sD)x0(zi)8p)|x*@))H.(hX*/j)hq*)y*n[)5\\(q}(Vj({:(uF(r,*C!FGI%KM%QS%]_%bd%suw%~ *D!&'%+-%/1%@FGI%MO%QS%UW%^ )g')8/*[@*Z]*4R*:B)6o(eM)TE*d2(tP)/3)Rw(o0);P*X*)nv)Z((o>)MD)n;)[S*U8*Tl(oI)QO*Jp)v6)9Z(eV)_/)Rk(r0);0(q3)Ha*6m(hb*)-(ZH*\\V)Vo*YF)_m)9G)V>)Yl*/'*=g)Fv*`;)V{*rl*Se(t})d>*C1*6P)m`*PR)H0(`n*Zp*nk):/*xx*[9(iu(X5)C=(l})1U)V8(`](}N(tO)WE(rC)l-)kr)0E*84*5q)7{)DF*_s)qz*ZA*X4*mt))^(d;(f0)_E*D!acdf%lw%} *E!)*%-/1%69%=@AFHIMNR%TV%Z\\%^`cghmnp%ruwx{%~ *F!'(%*,%.17%9<%?ACF%HMNP%SVX )RG),4([3(VA)+C)8K*8g*`R)G**dB)sA(ei*n6)-H)|R)GP)8V)}f*.y)n&([N)6X(tZ*`G(_k)y;){G(n:)-v*`L)U~(}e(n[(em(]y)X>(mw)9`*xs*6Q*FW*LV*`8(e{(}t*79)7@)5t*5\\+0,*r6*~!8F )IU(ur)7=(i7)8'(gr*Et)U8(vw)k<*Jz)O|(t<*UW),l)Xo)gc*x](T~)_y({C)us)8N)AJ)p/)1K({R(wd(TM*6')Rq)gE(V5)Dn*09)-d)@C)CY).c(uQ(vu*F![]_abfh%jl%oqrtuwxz%|~ *G!&)%/3468%;=%?AC%HJKO%VXZ%\\^`%ch%jmoprsuw%{}~ *H!&(%.0%4 )(n).9(d'*vx*_n)Fx(n0(i()1_(U')[3*/5*:5*=q(vH(W!Ze (}C*.F)Ch*@r(fw)lL*4G(i{+'g(X/)Ww*Tw(|g)3>(cz)nj)Ws*D~)?)*qJ*.S(f?)zR*)a*q=(k(*1P)kD)8.*FY)VD){c)5-)?C(cv):@)S1))3(f`*m[(U9)Rh*TP(}P)@)*9c)H((f!B8 )Q+)Ox(V()8S(cW*8>(WH(Y7(oA)5&*tB)AT)d.(bg(ab)0x*TS)@])ai)Du*MR)u.)t2)A`(l)(f<*n{)d?*H!5679:>@%BDF%IKLNOQ%UW%Y\\%`d%jl%oq%tv%y{%} *I!&'%/1%46%9;%@B%IKN%QS%WZ )yx)0H))T)0b*6o(t;)1\\)aT)_q)YB)RW)dE)MH*K2))u(UM(xu(j<*n=)Tl*)R*s6)5+*.`)ru*Dr(c2*tb*_')Aw)&4(Y))eY(TQ(d\\(t5(ep)98*o)(]m(Vm++P*.>(gc*W}*J2*8W*qA)V1),c(y5)A.*r_)gj)fA(du*6L*{Z*10({a*r!mF *.Y)q8(dX*ow*{n)~l*Lp)XI(UN*1c)x6*TJ)1s(gx(V?*xY))?)L~(\\M(en*Ul*>1)u9*tH(`d(h@(m8*nZ)V+({l)Vi)AC*I![\\%hj%oqrt%vx%z}~ *J!&'%),%13468%>DEHKMOQ%TV%Z^adeghjl%oq%su%y{%~ *K!&(*%/ (|m(zm)FT*3s(iq)|~)>C)I|(V/)OY*G7*uv(c^)80);@*b0)?>*4E*Tt(^e)H1(d.*I0)4=)Sr)TP*U-(}V*BP*9v*nf)O5*_,)?R)/-).N(Yj*I|)@h*67)f<)1z(^@(`+(on)-F*b(*J**df)*g)D4)/Y(vG(]i(_i*4|(bp)S'*sa)aZ(WL(Yw*1-)C{(je*nb)y8*Jt*.}(rw)5(*tD*DN(k_*x[)-t*Su*?u*d+(e6)@6)*~)lY)0=**l(xA*/!+. *^S(jG({Z)1,)?m)g>*K!13%57%;=%@B%EHILMO%RT%VX[\\_%cefkln%twz%| *L!&')+%-/%57%;=@%DH%LN%PR%UX%[]%_ac *`A)oU)qi*.t*u_*BA(zv){:*t.*Iw(b3)a_).5)@5*TF(|Y*~9))))PB)Rb*S!jl *`B*_V(dA)7&+/!v} (fD(Tq*:+)cw*E7)&i*J7)VR*sb(g|(V.*Ss)`2(gh*qX)_a)WC)pi(Tk)tI*FT(~U),3*Nz*x|*q])^i)Sm(tj)9a)q|*J+)ZS*9:(qa)bL(dB)tO*s^(hS)-J)`Y({k*T~*~V)P<(tT+0&)y_)DM)6/(XQ)mE*0r)T`*r))R.(W+)mv(^G)Fj)Z}*a_*6B(uc)i~*L!de%lnoq%~ *M!&'%03%8:%MO%QSUWXZ%t );M(},*aD){W*U.(vN(gW*7y*<+*MT*26)IX(l<*2l)cU*eI(_)(UL*xU)9N(T`(eS)C1*5v(tB)VU(TF(sx(cJ):5*nK(Y&(WE*JA*xp(t]*a6)ys(V~*4t)m5)S0({S(v\\)-A)I-*Du(ZJ)):(UF)sq(Vy*9A)6T)@\\*ap(}~(VS)-]*7?*`p*3a)mO(V:*KY)26)P~).Q(U6)pc)Y:*TG*Sk*T!n5 ),?*22*1[*.h*;J*<'(mu)Pp*w,*r*(vo(e|*nP)W5({|*{C(}>),R*s-*M!uv%~ *N!&'%:=GM%OQ%SUWY%\\^a%df%hj%ln%y{%~ *O!&')%> (d&*:_)LN(x6+0g)+**/i*<u*>!./ *;|*G0*__*Xb)pa)-U*xv)uH)@i(h1)7O(XE*7<)ml)+N*sj*ZX*F;(g:)TH)V/)R?)0X(b/)k~)*l(WS)0y).r(}))l.*b4*ms)d~)Sg)X)*cv)CG(e!(P (X3*eD*TU(d~)*k*_S)W.(U{)p[)JU*DR)2A*8&(X;(Yg*`v++0)20)W!g^ *r{(Uu(wL*t,(TT(~+)y1({>*TQ*1L)gC(pi*9R*bG(e^*a&(j[)&=).,)2/)OD)e]*46*PQ*O!?@%^`%hj%~ *P!&'%@B%H )Up(|1*KZ*xd*Uv*~7+0*)WT(^p(]h(p-*J_(dU):S*_&(n)(f\\(nB)fR)Y(*/Y*_m*:?)2f)/Z*rX)C.*.@*Y!1j *mX(~_*(T)CF)1&*qT*2N(U~*bm(bh*r^(]f*_a*As*d1)AE*w.(ze(c,)tm)D')Fp(gt)^Y({g*^g(^Y(nd(g\\)=f)Ar*X!^C *5:*>v*aL*)6(_r*CH)E/)@2)\\.*E?*[v(lX*`X)ot)^Q)b\\*W|)7S(`3)TR*X<)/@(rv)3\\)C<*X`)5j(jS)Wm)Ck*^Q*P!IJ%PS%Z\\%~ *Q!&'%R *n;*qG)qh)F8)Jw(g6(U:)_:*T,);^)?-({<){J(Zj(|F)sI)QA*PA*(H)Sl)ph)?K*_()''(}Z*D,)F;(co)54)|F)XL*/>)L<)|M)-T)Zr),])9D)VK)D;(TN))X*X&(Vk*4D)o&*s9)7E)@v)6A)Sj*Xu)a7*0Q(]k*3d(eI*aM)6[(hD*F2*_**2;*nq*@o*_j)UU)h.)?A)q_)kZ)t\\*7t*Ww*m;(Y/)Dz)m]*T.*tK)8@*oH*r+*?i)84)r()-e)e\\(Uv)/F)>o)7Y)Ou)>:*Q!ST%uw%~ *R!&'%;=%[ ))o)mk*bW*S~*9d)E))>*(f5*FZ)ss*4&*5-)RH(U8*.a*3))q4)I5)PO)6*(Zl(Xf(n|)7q)Ot*Ye(eG)@1*.R).b)8+)9o*7!lm *0V))d)&,(`P)Aj)ya)z9*X()^t)mx)Yv)Si){X(Ts)p^).k*mR*JU*.b)yE*4C*eH(W!-R *8?(fl))&)U.*T0*(U*^^*3u))b(g=)pJ({P)A,*~n)hA)F]*Ev)Co)f=*T!\\? *`H)|f)_])a>)7.)s7(kx(U(*>a)E}(aj*E[*X.*nR)}e*R!\\]%{}~ *S!&'(*%\\inx *T!+4Nm *U)(X1)T=)x.*~E)6V)cH)7g(TY)tK*^V)<]*XZ*&()GC)*s*KJ(uS)<K(sm)d{)I6*o&*`5*XX(oC*/A*Tj(_9)8Z)7C(cb)G&)i**:o*cy(i=)r')^W)UF(^E(gS*XD(vs)Yc)Vg(zl**)(Vh*(-)Yk(z`*7c)WW(zg+&t)HV*Zx({N*/=)/m*.O*b?*Xp(tu):,)yW*YZ(ca)?q)x'*T)*r1*.^*mn*-u(k{*d])C>*0R(|h*aT)@q)).(X2)Ej([1(X>(o3)+=#2$*U!CD%KM%PSUVX%_a%df%kn%tw%} *V!&'%)-%3568%:=?%EG%IK%_ (Uz(TR(]e(TV),<(cT(T[(V,(Td+'J(z|(lW(Tn)y2(U,(b}(U4(tS)cY(c})Qp(mt*4h*{l)Q3)re+2\\(T|(V3+2U(U!IV (V'*9O(zk(ie(kV(VX(d!CSY[d *uP*X](c)(eR(c!5=M *X\\(c!ur (_!u{ (`!(GMYZfx{v *?`(a!>CBQT (^N*0<(V!OW]V|u (W&(Vr(W!V.] (XF(W!0`bDf (X!@I )>3(X!<? *V!`a%~ *W!&'%f (X!A9 (W!mx (X!XVskeq`] (Y3(X^(Y5(Xv(Y!oqsNcleZh (Z!(Y| (Y|(Z!\\?^].L<UX ([U(Zs([,(Z!x~ ([!CR|~ (\\!'8T (]+(\\!kjdl (]!,0 (Vq)RD(X!:G (YW+43)RM*'}(^:))E+1g*{m(^M(t:(b!au *O((bx(_^(t?(]z(V4(]{(VG*JL*K)(VI*5?*KW)xQ(zD*I:*1G(^!w} (_!0/ *W!gh%uy *X!QRW_eh%jlnoqrx%~ *Y!&')*-%03%79:<%EG%IK%WY]^`bdg%iln%qtwy%{ (_:(^!bnq *S!]cdgqrt *T!&'/326%8:<=@CDHLMRTWZ]`cdfhkpsuvx%z} *U&*T|*U!(*%,02569:=>@A (cx(d0*q!lqp *r'*qy*r!328?JU]bhj~ *s!*4M *a!vz|x *b!2':9<@ *Y|*Z!&'*.%03%57;<>%@BDJL%RT%VY[\\^%ac%jnor%vy{~ *[!'()+%-01348:;A%EH%MO%Z\\]_`cf *b!>6UKDVJMYlhb]aqu *c!+1OKP^\\fps (`'(th(a!cmn (b&(hQ(b!8>X (e<)be)s[),1(_!ho +'G(dl),>)lW))7(o/(p!3+ (rV(s!;DP (n!ehfoujk (o?(n!w~ (o!LhNi_^KPYV (p!7. (oo(p!<Y1> (ov(p!*_bdc\\B~y (q)(p!ng *[!hjl%prsuw%y{}~ *\\!&'%*-%/1%79;%FIJLMO%UW%ik%~ *]!&' (q!<>. (pw(q!1OJuQ (r!=DYarX *{}+2^)4t*9!@DFVQoKUX^`r *:!&, *9w*:!68Q *9!_~u *:!9:/ *9!p{hqf *:!2< *9k*:!OweR}uJb@FziA *;!&/ *:t*;!)* *:!NC *;!1fpq *:v*<N*;!QIF *<W*;!sDd\\XtU,uBOH{z *]!()%~ *^!&'%. *;!}0~ *<!(&)*l4op\\fOXA8re9t7TZdvx} *=!dH; (q8*=!{n@`9 *>!&B *=!ti7Brf[\\M( *<|*>0*=!SD *>!f[dc} *?!89+:) *>!FPs *?!'=? *>!;w *?!r~ *@!(> *?n*@+*?!{pk *@8*?!RjeH *@!=;d )kc*A7*@H*A8*@w*A9*^!/0%OTjv| *_!.1%35%8;=%@C%FHIK%MPQTWZ%^`cghkqrtx} *`!')*,%.2%467?@DFKMP *A!U: *@!WT *A!XGgc]y )s0*A!Bqz *B!JN-5 *Ax*B!TB+_]Vyg *C!)LtRvc *D0),!;E (t!bg *2m(t{(u'(t~(bw(}!'*19 )5h)6!-b~ )7!4DAX )83)7`)8!J\\T )9e):U)9!)2Fl )8~)9!dB )8z)9!ft ):!QW?^ )9|):!=- );X)9!~v );!KG ):!jz );&):k);!,'d )<1);s)<C*`!QT[%]_%dh%loqruwx{}~ *a!'(%+-3%589;%@CEFH%KN%RU%[]`c%fhik%oqsuwy{}~ *b!&*+-%/138=BCEF )<!'deFgm} )=!+86]y )>)),H).])ko),N(cn(e!NZFEXosc (f!OV%XZG (e~(f![]'6 (g!-? (f!{uh (g!EF/ (f!zj (g!H{Xm_}RQ~ (h&(fv(gN(h'(g!fjw (h!+,. (g!TUO (h/(gz(h!0m9rOfEtuacy| (i`(j!/U (i!Bf:N8I5 *b!HILNOQ%SXZ%\\^%`d%fi%koprtv%y{%} *c!&'%),%.02%EG%JL%NQ%[]_%eg%oq (i!0On%p,)U (j!+r (i|(j!QY( (iz(j?(iw(j!v1 (it(k&(j!]^49 (k!1> (ji(kf(jq(k!23 (jt(k-(j!}d (k!6,9: (j!lm (k!/<+ *^~(k!eOSHkjEGnt (l!F'E (ks(l!;PGJ:BtR_Yijs (m!(Dilrv (n!1'5;@FEX ))!Wa\\Yy )*!*+ *c!rtu~ *d!'(%*,-/034:%<?AGN%PT%XZ\\^bcg%mp%ux%} *e!&'+%.1%46%ACFKM%VX%` )*!&3CJMP (~!8&9<B4DE=FHaNiXW]Omcp~ )&7(~w)&!8UVzy )'()&!wqYn )'!R8.7fUiWeEDcn| )(!:> *U~)(!Xf )-!W[fkmpy} ).!'38@G *J@)-E)d!ruy|} )e!/-:9>T[U^`cm_of *e!ab%~ *f!&'%g )eg)f!*&4 )ev)f!10(5L8?KDc`js )g!&)8 *8;(tK*yn(t!J( *{!8<%AILRUY\\_befh%j )*o)+')*z)+!,;6OKTI *XS)+!hgq~ ),'*73).!OTaZ )/5).y)/6).!lo )/!078 ).!ux )/!*KgXMA?[]<qS )0!QRTS )/t*f!hi%~ *g!&'%<>%VX%p )0!CVos )1-)0!v\\ket )1!l;1]XFZ4 )2!GM<J )1t)2!'5Rq )3!Czbu )4!,Ij ).h*sP*q!8:@BDEKMRSUWY%\\^ad%fhi (Tr)c])51)R!<[cQ )S!Q3>B: )R!op{} )S!4W~ )T))S[)T!LMF, )S`)T!5+O@C )S_)T!(DS *g!qr%vx%~ *h!&'%79%y )U5)T!|a )U!?D )T!\\Ync )U!'I*1 )To)U!KNP/ )V4)Uk)V!AC )UZ)V!67 )U!X\\x_ )Wd)V!jh )W!(X )Vb)W!*[0' )V|)W`)VN)WV)VQ)Wu)X!_?\\ )YJ)X!PwDE )W!|o )X5)Y!wI0x )Zl)Y!z=?USOoLg'Z7 )[!H,- )Z!cT8nu )[!1Z )Z!iHh )[!osr^Xb` )\\^*h!z{%~ *i!&'%TV%~ *j!&') )\\!V4FCOn{km| )]!NWOmi )^!*9 ({!7;LJ\\o )l,*~h);:(|!BL *L\\(|N*[<*Qv*_!Gdfv *`&*_!uzw *`+*_y*`!=<C^IENYmneg| *a!01/G7A: )BP*a!S\\agbj )-!4;:< (}@(gV(}!U^ ({((}!hk *1K),!gfj *9;),y+'S(}s(u!NPZvwp *j!*+%LN%~ *k!&'%.0%2 (ud(v>(u!xkq (v!F& (u|(v!K2yzQ|WLm (w'(v}(w!MO,>.[ (x)(w!epW} (x7*4p(x!l]9< (y!'T (xt(y!;= (xz(y!ILZ?OAEr (z!'&BGP (|!sz (z!ju ({*(z!acr *~!?ABDIGJKORTZ\\]`be%gijlmoq *.!9;=?BC *k!34%:<%~ *l!&'%: *.!GJXZ%\\cefjnquz{ */!),/023N7;<?B%DHILMPRSXZ]\\`b%hk%np%tv )*i)l|)(|)l;*at)g!HKh\\Y )h0)g|)h2)g!qmz )h!*)F: *x\\)h!kJH? )gt)h!Sp )i!2c.-0864 *l!;<%IK%vx%~ *m!&'%7DIKz *n!)-.4@Uvz )h!|{ )i!Id_]a )j&)iu)j!-K=/06:MWX?V )k()ji*v!|}~ )D!18C-N )E1)D!lOX} )E3)DZ)E!4*9 )D!p\\a )E|)F<)Ed)F=)E!u< )F?)E!eAf?i )F3)E!ODn )FA)EM)F!0ef )G!:;(< )Fw)G!>] )FP)G!/@ )Ft)GA)F!sq}|\\ )H6)G!iNQmE )HC)J=)H})I!GH )Hz*o!(4>DILV[aouvx%~ *p!&(%s )IL)Hq)I!3) )J&)Hd)I8)J@)Ia)J/)Ij)J!,S:OgG )I\\)KB)J!VW )KD)J!X52Hjv )K1)J!z| )K!J'7@U )Jd)KV)J\\)Ku)LL)Kc)L!45 )M))Kh)L!a[ )Md)L!mxR )M!R= )L!ZEu )M!@UTm[o )f!Je )P!:;@=KLFSTZc *^!UW[%]_`bacdfhklnqu *p!tu%~ *q!&'%6;NVbgjmor%xz%|~ *r!&-%0479@DEG%IL%PR%TVY[\\ac%gin%z} *s!&' *^!wxy *_!)+ *\\j)5!'37;>=A@H *7`)k!EJUegi )>/)@!789Sb )DW)@!L^I )^`)C5)@!f}z )A&)@~*2<)A!OD1?FQGgt| )B!>4Urs} )C'*X!236;=@EFHIGMUY *N!AD%FH%K )d!3d )c{)d!+4CDIPRTW )7r)8C)9h*s!)+.%02378:%CE%LNOQRTUWX\\_`eghmnpqs%|~ *t!&'%+/%24%689<>%ACFGIN%PT%VXY[%_acd );E)=,*2:)Q!JS]UYegfmux%z )R!(0+19 )>!0Pf )c!kptN *DV(`0*3!|yp *4@)CH*4!A5/18;k%n_FH]Q[ )CN*4!aouxq *5!89 *4v*5')CS*5=*Ue*5!aIWC]sw~ *6!F+,C2;NO?9 (q~(xw*6!ZTc )Ym*6!u| *7E)Cd*7!C4 *t!ef%moprsuw%{~ *u!&'(*%<?@BCEGHJNOQ%SU%]`b%fh%oq%uwy%{~ *v!'()+%/ *7:*6x)O!6IKUXk *y!XYZ\\`a )Pl),})P{*Nm)?2+3=)?!5^hfenptx )^![wqx )_!=B9-3jL )`!9<?XZ )a!C,8 )`n)aB)`s)a!HJh )b!*, )a!xp )b!WK^p )c!;F )^G)`A)a6)b0)5!ZY_de )w!ejps )x!,- )w})x!+3(1@8KV[kx )y+).!\\[ *v!01346%<>@BCEG%wz *w!'(%+-0%G )/n)0!63@ )/s)0!2< )1+)2!-4 )3!+wk )4!@F{ *3!]\\ )SA)TK)WF)t!+-5>LMF )?M)t!bQRn%pTqZaX^ )u!*I23 )t[)u,)t|)u!(Fwovcx} )v!&5<(W\\Lou )w/)vw)w!=NH^ +4.+2!>@A )q!1DM9GHZbYmfux )r!78.*AO *w!HI%~ *x!&'%N )r!ILftdgsx )s!-.>CK )l!7=KHEXov )m'*0!@IG *Oi*0!P\\Yaif )p!f~ *Ii*m!89:>=?@BCGJHM%OVW^]`_bcegi%moprw{%~ *n!&'(*%,/%13258%:<>?ABD *x!OP%Tknruy *y!*/%TVW[]^b%dg%mp%~ *z!&'%5 *n!CEHIM%OQSVWY]\\`acdg%il%pruw%y| *o!+,*-2357%9=?@C6E%GJMOPS%UW%Z\\_`^bce%mpr%t; )s!uz *sf)y!Me[c~ )'A)y!{z )z!=3 *z!67%:<=?%[]%~ *{!&'(+%79;HMNPSWX^ )z!XVp +1m*{z){6)oz)p!',2= )k!46 )l)+/!prx%| +0!)(-/.01589;=%@BCEHLNQ%SUZ]%chdj )m!68:DAPGWX\\hUTgfzq )n!.31/62N<GJ[KPWbUpc`d *{!aco%y{|~ *|!&'%u )nf)o.)n!mk )o2)n!x{ )o!1'50 )n~)o!?PFNH]a *1Y)|S){!>?FLN[lfrnsy *J!BGP[]` *K!'K<ANgShjv *L!F( *Km*L**Ku*L!ME?G6`Wbm *M!9N )m/*4i)p!KM )sj*2!HJLRVX[%]_^gAky| *3!*/FN *MV*x!X`c *|!vw%~ *}!&'%| *x!ilmotz{~ *y!&(),- *D!AH`bsnpo *E!C(&G8B>E.DQ_PiKULJdjO *F!+34/5 *Ek*FU*Ey*F6*E!zsl *F!EI:JLOKe}`gp\\ *G!'1 *F!vsky *G!(k *Fd*G!fgdIY5Wt<LeMBN| *H!?8b *}!}~ *~!&'%5<MSWX^_cps%y|%~ +&!&*,.%0457%;>%ACE%HJ%OQS%VX%[]_a%ikm%su%w *HV*G!qn *H!=' *Gl*H!;u<EC[cMaPZkzp~ *I!MJ5ARXps */w*0!*,- *80)|!gjupq )}))|w)}?)||)}![L>-BE498GR2lnY )~0)}!`qt )~!)6 )}v)~!-,KabsgXc@eW]fo *&3)~|*&!.?<GHLn`^qxm +&!xz%~ +'!&(%*,%68%:<%FHIK%RU%X^b%dfhjkm%~ +(!&'%: *'!(3/AJI *7!wz{}e *I{*8!ABCMOHKX[ST^dikoz *9!&/ *J!\\cf *K!]d *M1*1!,.6>?A *(,)>D*(!AMLNRblmqw|}xu *)!.? *95)B?*1!N]dtpux} *2)*)O**u*+!KS *-:*.5+1!VR *Xs*Y!+;8X *X!gm *U!RT *c!w}{| *d8+(!;<%~ +)!&'%A *d!65D=CQRFHM`Ydaevw~ *e!()*0/5 *U`+0u*Y_*Z!9S *[!N^ *Y!\\fcak}~ *Z!(, *Y!uv *Z)*Ym*Z!:EF1GI2WKbkmz|q} *[!/5.*2&>67FG?ekqdiz *\\!+0,8: *V!*4<7JF )?=*N!PXTV +)!BC%~ +*!&'%H *N!_ei *O_*R<*u>*t!-73JELSRnqt *u)+3!loq%vx +2!CPR *s!VYZko )sM*s}*g!Ww *h8*iU*j(*k/*jM*l!Jw ++!369%< )zB++!?BD%FHINOR%WY%]_abd%kn%qx%{~ +,&+*!IJ%~ ++!&'%/12478=>@CGJ%MQ^`cmr%v|} +,!,-56;<'%+.%02%478:= *u!gp|} *v!&A=5DF *~!{z +&(+0O+&!'+-2163<=B +'!][_a`ei *yo*z!;\\ *{!*) +&!PRW`\\l^jy +'!'+7; +1![\\ *-.+0!y~ +1!&)0 *k;+1!;=y%{~} +2!'.,*/4gqt~| +3!)*0 +,!>?%~ +-!&'%~ +.!&'%~ +/!&'%ntw~ +0!'+237DGI%KMPTV%Y\\efik%twxz%} +1!'(*%/2%:<>%CE%QS%UWXZ]%ac%fh%jnpqs%uwx| +2!&()+-0%35%=?BD%OQTVWY%[]_%dfh%prsu%{} +3!&(+%/1%;>%jmny%~ +4!&'%+/%14%8 .*f.+!Zv .,!oy .-!;<%>@BCGN%PRSV%X -R!XY%~ -S!&'%N")),t(e)});

打印相关的方法:

只要连接成功时,就直接传输数据给打印机。

methods: {
//打印
f_print(){
this.sfShowTc=false;
if (!Common.isExist(this.deviceId)) {
this.openBlueTooth();
this.isShowSearchBle=true;
return;
}else{
console.log("有历史设备信息直接连接");
this.createBLEConnectionWithDeviceId();
}
},
// 小程序在之前已有搜索过某个蓝牙设备,并成功建立连接,可直接传入之前搜索获取的 deviceId 直接尝试连接该设备
createBLEConnectionWithDeviceId(){
if (!Common.isExist(this.deviceId)) {
return
}
uni.openBluetoothAdapter({
success:(res) => {
console.log("初始化蓝牙设备模块:"+JSON.stringify(res));
if(res.errno==0){
this.connectBlue(this.deviceId,this.deviceName,null);
}else{
Common.alert_duration(res.errMsg);
}
},
fail: (res) => {
console.log("初始化蓝牙设备模块失败:"+JSON.stringify(res));
catchToast(res);
wx.onBluetoothAdapterStateChange((res) => {
console.log('onBluetoothAdapterStateChange', res)
if (res.available) {
// 取消监听
wx.onBluetoothAdapterStateChange(() => {
});
this.connectBlue(this.deviceId,this.deviceName,null);
}
})
}
});
},
//初始化蓝牙
openBlueTooth(){
if (!wx.openBluetoothAdapter) {
wx.showModal({
title: '提示',
content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
})
return
}
wx.openBluetoothAdapter({
success:(res) => {
console.log("初始化蓝牙设备模块:"+JSON.stringify(res));
if(res.errno==0){
this.searchBlueToothDiscovery();
}else{
Common.alert_duration(res.errMsg);
}
},
fail: (res) => {
console.log("初始化蓝牙设备模块失败:"+JSON.stringify(res));
catchToast(res);
wx.onBluetoothAdapterStateChange((res) => {
console.log('onBluetoothAdapterStateChange', res)
if (res.available) {
// 取消监听,否则stopBluetoothDevicesDiscovery后仍会继续触发onBluetoothAdapterStateChange,
// 导致再次调用startBluetoothDevicesDiscovery
wx.onBluetoothAdapterStateChange(() => {
});
this.searchBlueToothDiscovery();
}
})
}
});
wx.onBLEConnectionStateChange((res) => {
// 该方法回调中可以用于处理连接意外断开等异常情况
console.log('onBLEConnectionStateChange', `device ${res.deviceId} state has changed, connected: ${res.connected}`)
this.blueToothConnected = res.connected;
if (!res.connected) {
wx.showModal({
title: '提示',
content: '蓝牙连接已断开',
showCancel: false
});
}
});
},
//搜寻附近的蓝牙外围设备
searchBlueToothDiscovery(){
uni.startBluetoothDevicesDiscovery({
success:(res) => {
console.log("搜寻附近的蓝牙外围设备成功:"+JSON.stringify(res));
if(res.errno==0){
wx.onBluetoothDeviceFound(this.onFoundDevice);
}else{
catchToast(res);
}
},
fail: (res) => {
console.log("搜寻附近的蓝牙外围设备失败:"+JSON.stringify(res));
catchToast(res);
}
});
},
//监听寻找到新设备的事件
onBlueToothDeviceFound(){
uni.onBluetoothDeviceFound(function (devices) {
this.getBluetoothDevices();
})
},
//获取在蓝牙模块生效期间所有已发现的蓝牙设备。包括已经和本机处于连接状态的设备
getBluetoothDevices(){
uni.getBluetoothDevices({
success:(res) => {
console.log("获取附近的蓝牙外围设备成功:"+JSON.stringify(res));
if(res.errno==0){
this.devices=this.getNewDevicesList(res.devices);
}else{
catchToast(res);
}
},
fail: (res) => {
console.log("获取附近的蓝牙外围设备失败:"+JSON.stringify(res));
catchToast(res);
}
});
},
//获取设备列表
getNewDevicesList(devices) {
console.log("获取新的设备列表");
const ids = [];
//过滤无用蓝牙设备
const devs = devices
.filter((item) => !(item.RSSI > 0 || item.name.indexOf("T581")<0))
.map((item) => {
ids.push(item.deviceId);
return {
rssi: item.RSSI,
name: item.name,
devId: item.deviceId,
status:false
};
});
//过滤重复
const filterId = [...new Set(ids)];
const newDevices = [];
while (filterId.length) {
const id = filterId.shift();
for (let index = 0; index < devs.length; index++) {
const item = devs[index];
if (item.devId === id) {
newDevices.push(item);
break;
}
}
}
return newDevices;
},
onFoundDevice: debounce(function(devices) {
//获取搜寻的所有设备
console.log("获取搜寻的所有设备");
wxAsyncPromise('getBluetoothDevices').then((res) => {
this.devices=this.getNewDevicesList(res.devices);
});
}, 60),
onSearchDeviceFound(pro) {
pro.then((res) => {
console.log('初始化蓝牙成功');
return wxAsyncPromise('startBluetoothDevicesDiscovery').then((res) => {
console.log('正在搜寻设备');
wx.showLoading({
title: '正在搜寻设备',
mask: false,
});
});
})
.then((res) => {
//监听寻找新设备
console.log("监听寻找新设备")
wx.onBluetoothDeviceFound(this.onFoundDevice);
})
.catch((res) => {
console.log("监听寻找新设备异常:"+JSON.stringify(res));
catchToast(res);
wx.setStorageSync(LAST_CONNECTED_DEVICE,"");
this.deviceId="";
this.deviceName = "";
})
.finally(() => {
setTimeout(() => {
wx.hideToast();
}, 300);
// this.stopSearchBlue();
});
},
//重新搜索蓝牙设备
searchBlue() {
wxAsyncPromise('closeBLEConnection', {
deviceId: this.deviceId,
}).finally((res) => {
this.devices=[];
this.deviceId="";
this.deviceName="";
this.blueToothConnected=false;
wx.offBluetoothDeviceFound(this.onFoundDevice);
//停止搜寻蓝牙
wxAsyncPromise('stopBluetoothDevicesDiscovery').then(() => {
wx.getBluetoothAdapterState({
success: (res) => {
if (!res.available) {
//未打开蓝牙
} else if (!res.discovering) {
//没有在搜寻蓝牙情况
this.onSearchDeviceFound(Promise.resolve());
}
},
fail: (res) => {
if (res.errCode === 10000) {
this.openBlueTooth();
}
},
});
});
});
},
//停止搜索蓝牙设备
stopSearchBlue() {
console.log("停止搜索");
wxAsyncPromise('stopBluetoothDevicesDiscovery');
},
onBlueChange(e,index) {
const deviceId = e.devId;
// if (this.deviceId === deviceId) return;
this.deviceId="";
this.deviceName="";
this.blueToothConnected=false;
this.connectBlue(deviceId,e.name,index);
},
//连接蓝牙
connectBlue(deviceId,deviceName,index) {
wx.showLoading({
title: '正在连接'+deviceName,
mask: true,
});
wxAsyncPromise('createBLEConnection', { deviceId })
.then((res) => {
return wxAsyncPromise('getBLEDeviceServices', { deviceId });
})
.then((res) => {
console.log("获取蓝牙设备所有服务:"+JSON.stringify(res.services));
this.services = res.services;
getDeviceCharacteristics(
deviceId,
[...this.services],
(character) => {
console.log("成功获取到特征值:"+JSON.stringify(character));
this.character = character;
this.deviceName= deviceName;
this.deviceId=deviceId;
this.blueToothConnected=true;
if(index!=null){
this.devices[index].status=true;
}
wx.setStorageSync(LAST_CONNECTED_DEVICE, this.deviceId+"&"+this.deviceName);
wx.hideLoading();
wx.offBluetoothDeviceFound(this.onFoundDevice);
wxAsyncPromise('stopBluetoothDevicesDiscovery');
// 延时调用
this.print();
},
(res) => {
console.log('获取设备特征值失败');
this.blueToothConnected=false;
Common.alert_duration("获取设备特征值失败");
wx.hideLoading();
this.isShowSearchBle=true;
},
);
})
.catch((res) => {
console.log('获取服务失败');
this.blueToothConnected=false;
Common.alert_duration("获取服务失败");
wx.hideLoading();
this.isShowSearchBle=true;
});
},
print: debounce(function() {
if (!Common.isExist(this.deviceId)) {
wx.showToast({
title: '未连接任何蓝牙设备',
icon: 'none',
});
return;
}
wx.showLoading({
title: '正在打印中......',
mask: true,
});
const opt = { deviceId: this.deviceId, ...this.character };
let _queue=[printCommand.clear];
let line = GBK.encode('-------------------------------\r\n');
let spaceStr = " ";
let shName= this.blueToothPrintData.shName;
let addressPhone=this.blueToothPrintData.xfSellerAddressPhone;
_queue.push.apply(_queue, printCommand.center);
console.log("开始组装打印数据")
_queue.push.apply(_queue, printCommand.enter);
_queue.push.apply(_queue, GBK.encode(shName+"\n"));
_queue.push.apply(_queue, printCommand.enter);
_queue.push.apply(_queue, line);
_queue.push.apply(_queue, printCommand.left);
_queue.push.apply(_queue, GBK.encode("消费单号:"+this.blueToothPrintData.id+"\n"));
let currentTime1=Common.changeDateFormat(new Date().getTime(),true);
_queue.push.apply(_queue, GBK.encode("消费时间:"+currentTime1+"\r\n"));
_queue.push.apply(_queue, line);
_queue.push.apply(_queue, GBK.encode('商品名称 数量 金额'));
_queue.push.apply(_queue, printCommand.enter);
let spList = this.selectSpArr;
let sumSpNum=0;
for (var i = 0; i < spList.length; i++) {
let spmcSpaceNum = 16-Common.calculateStrLengh(spList[i].spmc);
let spItemStr=spList[i].spmc;
for (var j = 0; j < spmcSpaceNum; j++) {
spItemStr += spaceStr;
}
spItemStr += parseFloat(spList[i].spnum).toFixed(2);
let spnumSpaceNum = 6-Common.calculateStrLengh(parseFloat(spList[i].spnum).toFixed(2));
for (var k = 0; k < spnumSpaceNum; k++) {
spItemStr += spaceStr;
}
let spjeSpaceNum = 9-Common.calculateStrLengh(parseFloat(spList[i].spSumMoney).toFixed(2));
for (var l = 0; l < spjeSpaceNum; l++) {
spItemStr += spaceStr;
}
spItemStr += parseFloat(spList[i].spSumMoney).toFixed(2);
_queue.push.apply(_queue, GBK.encode(spItemStr));
_queue.push.apply(_queue, printCommand.enter);
sumSpNum = sumSpNum + spList[i].spnum;
}
_queue.push.apply(_queue, line);
_queue.push.apply(_queue, GBK.encode("消费总计:"+parseFloat(sumSpNum).toFixed(2)+"\r\n"));
_queue.push.apply(_queue, GBK.encode("消费金额:"+parseFloat(this.blueToothPrintData.spjsHj).toFixed(2)+"\r\n"));
_queue.push.apply(_queue, GBK.encode("实付金额:"+parseFloat(this.blueToothPrintData.actualAmount).toFixed(2)+"\r\n"));
_queue.push.apply(_queue, line);
_queue.push.apply(_queue, printCommand.enter);
_queue.push.apply(_queue, GBK.encode("地址:"+this.blueToothPrintData.shdz+"\n"));
_queue.push.apply(_queue, printCommand.enter);
_queue.push.apply(_queue, GBK.encode("联系电话:"+this.userInfo.username+"\n"));
_queue.push.apply(_queue, printCommand.enter);
_queue.push.apply(_queue, printCommand.center);
_queue.push.apply(_queue, GBK.encode("扫码开票\n"));
_queue.push.apply(_queue, [29, 119, 6]);//放大6倍二维码
let qrContent="https://ksp.tbzhchina.com/invoiceadd?orderId=" + this.blueToothPrintData.id + "&sfJyToken=false";
let qrCode1 = [29, 107, 97, 5, 1, new Uint8Array(GBK.encode(qrContent)).buffer.byteLength, 0];
_queue.push.apply(_queue, qrCode1);
_queue.push.apply(_queue, GBK.encode(qrContent));
_queue.push.apply(_queue, GBK.encode("感谢您的惠顾"));
_queue.push.apply(_queue, printCommand.enter);
_queue.push.apply(_queue, line);
_queue.push.apply(_queue, GBK.encode("快闪票\r\n"));
_queue.push.apply(_queue, printCommand.enter);
_queue.push.apply(_queue, GBK.encode("请在三日内开具发票,谢谢!\r\n"));
_queue.push.apply(_queue, printCommand.enter);
_queue.push.apply(_queue, printCommand.enter);
_queue.push.apply(_queue, GBK.encode(" \r\n"));
_queue.push.apply(_queue, line);
console.log("开始发送打印数据")
sendDataToDevice({
...opt,
value: new Uint8Array(_queue).buffer,
lasterSuccess: () => {
wx.hideLoading();
console.log("打印完成!")
this.orderId="";
this.spjeHj=0;
this.spseHj=0;
this.sumSpMoney="0";
this.selectSpArr = [];
this.isShowSearchBle=false;
Common.alert("打印成功!");
},
});
}, 60),
}

注意点:getNewDevicesList 方法中,此处我们用的设备名称是 T581 开头的,所以我用名称过滤无用的设备信息,第二步根据设备ID过滤重复设备。

 最后页面样式部分如下:

<style>
/*蓝牙配置弹窗-start*/
.scrool-page {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
.panel-scroll-box {
height: 85%;
max-height: 350px;
flex: 1;
position: relative;
margin-top: 10px;
border-top: 1px solid #EEEEEE;
}
.bluetooth-item{
border-bottom: 1px solid #EEEEEE;
}
.bluetooth-item:last-child{
border-bottom: 0px solid #EEEEEE;
}
.empty-icon{
margin-top: 20px;
margin-bottom: 26upx;
}
.empty-text{
width: 100%;
line-height: 50upx;
font-size: 30upx;
text-align: center;
color: #999999;
}
/* 列表--一行的样式 */
.list-item-oneline {
width: 100%;
background-color: #FFFFFF;
margin-bottom: 10px;
height: 50px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0 15px;
box-sizing: border-box;
}

.list-item-oneline .list-text {
font-size: 16px;
height: 20px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
flex: 1;
}

.list-item-oneline .list-statue {
font-size: 12px;
border: 1px solid #0faeff;
padding: 1px 4px;
border-radius: 3px;
color: #0faeff;
margin-left: 6px;
}
/*蓝牙配置弹窗-end*/
</style>

附带页面弹框效果图如下,第一个为没有可用设备时,第二个是有可用设备时:

在HbuilderX中实现微信小程序下蓝牙连接打印机完整实战案例

在HbuilderX中实现微信小程序下蓝牙连接打印机完整实战案例

如有问题,还请各位大佬多多指教,我是一名做后端 Java开发的程序猿,前端难免会有疏漏的部分。另外,有吉他或者民谣爱好者,可以关注我的微信公众号@民谣嗑学家,可以微信搜索:民谣嗑学家,帮我点个免费的关注,我是一名爱生活,爱代码,爱交朋友的热血青年,希望我们能够共同进步,共同成长。

目录

​​1.基础开发环境,所用到的 Api 以及实现的思路。​​

​​应用场景:​​

​​HbuilderX开发工具:​​

​​关于蓝牙设备的api地址:​​

​​大致实现思路:​​

​​1.在页面加载时,先判断本地缓存是否有历史连接蓝牙设备。​​

​​2.页面点击【打印】按钮时,最好是提前把此次需要打印的数据项先组装好。​​

​​3.点击打印后,先判断当前页面是否有历史连接蓝牙设备的信息:​​

​​4.页面卸载时或者页面隐藏时,需要取消蓝牙连接并关闭蓝牙模块。​​

​​2.接下来直接上代码,以及依赖的js和弹框组件​​

​​ 弹框用的uni-popup 基础组件,如有需要,可以私信找我要。​​

​​ 页面蓝牙列表的视图view代码如下:​​

​​页面引入弹框组件和相关的蓝牙打印的js文件:​​

​​此处我只列出,当前与蓝牙打印相关的js文件:​​

​​1.​​

​​2.​​

​​打印相关的方法:​​