如何搭建一个自己上传视频的网站,apache 做网站,百度 移动网站优化,网站销售系统前言 这是个蓝牙打印图片的功能#xff0c;业务是打印界面固定的demo范围#xff0c;这里通过html2canvas插件生成的图片base64#xff0c;然后图片base64绘制到canvas中去后#xff0c;获取canvas中的像素信息#xff0c;然后对像素信息进行一个灰度值处理#xff0c;灰…前言 这是个蓝牙打印图片的功能业务是打印界面固定的demo范围这里通过html2canvas插件生成的图片base64然后图片base64绘制到canvas中去后获取canvas中的像素信息然后对像素信息进行一个灰度值处理灰度值处理后再进行黑白值的处理然后再根据蓝牙机需要通过图片的宽高比例进行一个二维数组的生成等等一番数据处理后将数据转换为buffer格式 因为蓝牙一次只能发20字节所以采用递归方式发送。
采用贴纸打印的话需要使用黑标指令发送固定数据给蓝牙一次打印机会蜂鸣目的是打印内容完后自动切换下一个纸准备不会仍旧在当前纸打印。
这里用的是南方鸿志科技的58mm热敏打印机
总结打印的view视图范围中有文字有图片其中图片是base64格式是正常运行的,如果是本地图片会发现html2canvas在app端无法处理。 搜索连接蓝牙界面蓝牙打印界面引用组件界面 搜索连接蓝牙界面
templateview classcontentbutton typedefault clickbluetoothInit搜寻蓝牙设备/buttonbutton clickgoPrint跳转打印界面/buttonuni-search-bar :focustrue v-modelsearchValue inputinputcancelcancel clearclear/uni-search-barview classlistview classitem v-for(item,index) in bluetoothList :keyindextext classname{{item.name}}----{{item.deviceId}}/textview classbtns clickconnect(item)点击连接/view/view/view/view
/templatescriptexport default {data() {return {bluetoothList: [], //蓝牙列表searchValue:}},onshow() {},created() {},methods: {input(e){console.log(e);let bluetoothListuni.getStorageSync(bluetoothList);this.bluetoothListbluetoothList.filter(element{return element.name.includes(e)})console.log(this.bluetoothList);},cancel(){this.bluetoothListuni.getStorageSync(bluetoothList);},clear(){this.bluetoothListuni.getStorageSync(bluetoothList);},goPrint(){uni.navigateTo({url:/pages/Print/Print})},//点击连接设备connect(device) {uni.showModal({title: device.name,content: 确定连接此设备,success: (res {if (res.confirm) {uni.setStorageSync(DeviceID, device.deviceId) //把已经连接的蓝牙设备信息放入缓存this.DeviceID device.deviceIdlet DeviceID device.deviceId //这里是拿到的uuidthis.StopBluetoothDevicesDiscovery() //当找到匹配的蓝牙后就关掉蓝牙搜寻,因为蓝牙搜寻很耗性能console.log(匹配到的蓝牙this.DeviceID, this.DeviceID)this.CreateBLEConnection(DeviceID) //创建蓝牙连接,连接低功耗蓝牙设备 uni.showLoading({title: 正在尝试连接蓝牙...}); console.log(用户点击确定);} else if (res.cancel) {console.log(用户点击取消);}})});},//蓝牙初始化bluetoothInit() {this.searchValue;this.bluetoothList [];uni.openBluetoothAdapter({success: (res) {console.log(第一步初始化蓝牙成功: res.errMsg);// 初始化完毕开始搜索this.StartBluetoothDeviceDiscovery()},fail: (res) {console.log(初始化蓝牙失败: JSON.stringify(res));if (res.errCode 10001) {uni.showToast({title: 蓝牙未打开,duration: 2000,})} else {uni.showToast({title: res.errMsg,duration: 2000,})}}});},/*** 第二步 在页面显示的时候判断是都已经初始化完成蓝牙适配器若成功则开始查找设备*/StartBluetoothDeviceDiscovery() {uni.startBluetoothDevicesDiscovery({// services: [0000FFE0],success: res {console.log(第二步 开始搜寻附近的蓝牙外围设备startBluetoothDevicesDiscovery success, res)this.OnBluetoothDeviceFound();},fail: res {uni.showToast({icon: none,title: 查找设备失败,duration: 3000})}});},/*** 第三步 发现外围设备*/OnBluetoothDeviceFound() {console.log(监听寻找新设备);uni.showLoading({title: 搜寻设备中...});uni.onBluetoothDeviceFound(res {console.log(第三步 监听寻找到新设备的事件:, JSON.stringify(res))console.log(第三步 监听寻找到新设备列表:, res.devices)let bluetoothList [...res.devices, ...this.bluetoothList];this.bluetoothListbluetoothList;uni.setStorageSync(bluetoothList, bluetoothList);console.log(bluetoothList);// res.devices.forEach(device { //这一步就是去筛选找到的蓝牙中,有没有你匹配的名称// console.log(这一步就是去筛选找到的蓝牙中,有没有你匹配的名称:, JSON.stringify(device))// if (device.name Qsprinter) { //匹配蓝牙名称// uni.setStorageSync(DeviceID, device.deviceId) //把已经连接的蓝牙设备信息放入缓存// this.DeviceID device.deviceId// let DeviceID device.deviceId //这里是拿到的uuid// this.StopBluetoothDevicesDiscovery() //当找到匹配的蓝牙后就关掉蓝牙搜寻,因为蓝牙搜寻很耗性能// console.log(匹配到的蓝牙this.DeviceID, this.DeviceID)// this.CreateBLEConnection(DeviceID) //创建蓝牙连接,连接低功耗蓝牙设备// }// })setTimeout(() {this.StopBluetoothDevicesDiscovery()}, 10000)});},/*** 第四步 停止搜索蓝牙设备*/StopBluetoothDevicesDiscovery() {uni.stopBluetoothDevicesDiscovery({success: res {console.log(第四步 找到匹配的蓝牙后就关掉蓝牙搜寻:, JSON.stringify(res))},fail: res {console.log(第四步 停止搜索蓝牙设备失败错误码 res.errCode);},complete() {uni.hideLoading();}});},// 第五步 创建蓝牙连接,连接低功耗蓝牙设备CreateBLEConnection(DeviceID, index) {let doc thisuni.createBLEConnection({ //创建蓝牙连接,连接低功耗蓝牙设备deviceId: DeviceID, //传入刚刚获取的uuidsuccess(res) {console.log(第五步 创建蓝牙连接成功:, JSON.stringify(res))doc.GetBLEDeviceServices(DeviceID) //获取蓝牙设备所有服务(service)。},fail(res) {console.log(res)}})},//第六步 获取蓝牙设备所有服务(service)。GetBLEDeviceServices(DeviceID, index) {let doc thissetTimeout(function() { //这里为什么要用setTimeout呢,等等下面会解释uni.getBLEDeviceServices({ //获取蓝牙设备所有服务deviceId: DeviceID,success(res) { //为什么要用延时,因为不用延时就拿不到所有的服务,在上一步,连接低功耗蓝牙//设备的时候,需要一个600-1000毫秒的时间后,再去获取设备所有服务,不给延时就会一直返回错误码10004console.log(第六步 获取蓝牙设备所有服务:, JSON.stringify(res))uni.setStorageSync(ServiceUUID, res.services[2].uuid) //把已经连接的蓝牙设备信息放入缓存uni.setStorageSync(ServiceUUIDNew, res.services[2].uuid) //把已经连接的蓝牙设备信息放入缓存let ServiceUUIDNew res.services[2].uuidthis.ServiceUUID res.services[2].uuidconsole.log(this.ServiceUUID:, this.ServiceUUID);doc.GetBLEDeviceCharacteristics(DeviceID) //获取蓝牙设备某个服务中所有特征值},fail(res) {console.log(JSON.stringify(res))}})}, 1000)},// 第七步 获取蓝牙特征值GetBLEDeviceCharacteristics(DeviceID) {console.log(第七步 获取蓝牙特征值DeviceID:, DeviceID, serviceId:, uni.getStorageSync(ServiceUUIDNew));setTimeout(() {let that this;uni.getBLEDeviceCharacteristics({ //获取蓝牙设备某个服务中所有特征值deviceId: DeviceID,serviceId: uni.getStorageSync(ServiceUUIDNew), //这个serviceId可以在上一步获取中拿到,也可以在//蓝牙文档中(硬件的蓝牙文档)拿到,我这里是通过文档直接赋值上去的,一般有两个,一个是收的uuid,一个是发的uuid,我们这边是发success(res) {console.log(第七步 获取蓝牙设备某个服务中所有特征值成功:, JSON.stringify(res))uni.showToast({title: 设备蓝牙已连接,duration: 2000});// uni.hideLoading();// #ifdef APP-IOSuni.setStorageSync(CharacteristicId, res.characteristics[0].uuid) //把某个服务中所有特征值信息放入缓存that.characteristicId res.characteristics[0].uuidconsole.log(res);// #endif// #ifdef APP-ANDROIDuni.setStorageSync(CharacteristicId, res.characteristics[1].uuid) //把某个服务中所有特征值信息放入缓存that.characteristicId res.characteristics[1].uuid// #endif// that.WriteBLECharacteristicValue()},fail(res) {console.log(获取蓝牙设备某个服务中所有特征值失败:, JSON.stringify(res))}})}, 2000)},}}
/scriptstyle.container {display: flex;flex-direction: column;align-items: center;}.row {display: flex;}.cell {width: 10px;height: 10px;margin-top: 2rpx;color: #000;/* 这里可以根据灰度值设置背景色 */}.list {margin-top: 10rpx;}.list .item {width: 97%;height: 100rpx;margin-top: 10rpx;display: flex;justify-content: space-between;align-items: center;border-bottom: 1rpx solid #ccc;}.btns {white-space: nowrap;}
/style蓝牙打印界面
templateviewPrintCode refPrintCode :imgUrlimgUrl codeTypecode :infoinfo/PrintCodebutton typedefault clickbtn选中图片打印/buttonbutton typedefault clickbtn2黑标打印/button/view
/templatescriptexport default {data() {return {imgUrl:data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkAQAAAABYmaj5AAAAtElEQVR4nJWTwYkDQRADa43/tRk4/7CcgSYC3cOfgwMfml/DCKmlFoA2FODB7/dtwlYCtR1wD64bXxGue8A9Ad7e5950PgFenHhPOGyNcdzvAgTgbHxtW2vbZtEZkJC68aVRsbLwJZiIyeILDSmCdcshmBA3Xz5Ak7jhwLSUZNTZFsm4H0IsMOXQtqaxzPtRVMYcqqTWue/AOde/P/9Mx5v6vta9wC59r5/znvC8el7Rl9AN59eFd5eY5PAAAAAElFTkSuQmCC,resultArray:[],info:{name:xxxxxxxxxxxxxxxxxx,model:1111111111112123123132312,amount:12312312313212312123,start_use_date:2024-2-3}}},methods: {btn(){this.$refs.PrintCode.open()},btn2(){let value[31, 27, 31, 128, 4, 5, 6, 68];uni.writeBLECharacteristicValue({deviceId: uni.getStorageSync(DeviceID),// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取serviceId: uni.getStorageSync(ServiceUUIDNew),// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取characteristicId: uni.getStorageSync(CharacteristicId),// 这里的value是ArrayBuffer类型value: value,success: function(res) {console.log(res);//写入成功后继续递归调用发送剩下的数据// that.sendMsg(newData)},fail: function(err) {console.log(err)},complete: function() {}})},},onShow() {uni.onBLEConnectionStateChange(function (res) {// 该方法回调中可以用于处理连接意外断开等异常情况console.log(device ${res.deviceId} state has changed, connected: ${res.connected})})}}
/script
引用组件界面
templateviewuni-popup refpopup typecenterview classBigBox v-ifcodeTypebarimage :srcimgUrl mode classbarImg/image/viewview classBigBox2 v-else idpagePosterview classlfview class名称{{setLength(info.name)}}/viewview class型号{{setLength(info.model)}}/viewview class数量{{setLength(info.amount)}}/viewview class投用时间{{setLength(info.start_use_date)}}/view/viewview classrgimage :srcimgUrl mode/image/view/viewview classbtnBoxview classprint v-ifcodeTypebar clickprintBtn打印/viewview classprint v-else clickcanvasImage.generateImage打印/viewview classclose clickclose取消/view/viewcanvas canvas-idmyCanvas idmyCanvas :style{width:imgWidth,height:imgHeight}/canvas/uni-popup/view
/templatescriptexport default {name: PrintCode,props: {imgUrl: {default: },codeType: {default: bar},info: {type: Object}},data() {return {imgWidth: ,imgHeight: };},mounted() {uni.openBluetoothAdapter({success: (res) {console.log(第一步初始化蓝牙成功: res.errMsg);uni.showToast({title: 蓝牙已初始化,duration: 1000});},fail: (res) {console.log(初始化蓝牙失败: JSON.stringify(res));if (res.errCode 10001) {uni.showToast({title: 蓝牙未打开,duration: 2000,})} else {uni.showToast({title: res.errMsg,duration: 2000,})}}});},methods: {//canvas无法显示省略号给文字添加省略号setLength(e) {console.log(e);let text e;if (e?.length 8) {text e.substring(0, 8) ...;}return text},open() {this.$refs.popup.open(center)},close() {this.$refs.popup.close()},//二维码打印receiveSendData(val) {const ctx uni.createCanvasContext(myCanvas, this);// 获取图片信息成功后绘制到 Canvas 上ctx.drawImage(val, 0, 0, 350, 176);// 获取绘制完成的图片数据ctx.draw(false, () {this.imgWidth 350 px;this.imgHeight 176 px;// 将 Canvas 中的图片数据转换为灰度图像uni.canvasGetImageData({canvasId: myCanvas,x: 0,y: 0,width: 350,height: 176,success: (res) {console.log(res);const imageData res.data;console.log(imageData);this.chuli(imageData)},fail: (error) {console.error(获取图片数据失败, error);}});});},//条码打印printBtn() {// const dcRichAlert uni.requireNativePlugin(Yunjinginc-Print)// dcRichAlert.printBitmap1(0,this.imgUrl,true)},//处理像素为打印机发送数据chuli(imageData) {let imgWidth 350;let imgHeight 176;// 将彩色图片转换为灰度图片for (let i 0; i imageData.length; i 4) {const r imageData[i];const g imageData[i 1];const b imageData[i 2];// 根据灰度公式将 RGB 值转换为灰度值const grayscale 0.299 * r 0.587 * g 0.114 *b;// 将灰度值赋给 RGBimageData[i] grayscale; // RedimageData[i 1] grayscale; // GreenimageData[i 2] grayscale; // Blue}console.log(imageData);let blackWhiteData this.convertToBlackWhite(imageData); // 转换成黑白像素点数据console.log(blackWhiteData);this.grayscaleArray [];// 将一维数组转换为二维数组for (let i 0; i imgWidth; i) {// 每一行的起始索引let startIndex i * imgWidth;// 每一行的灰度值数据let row blackWhiteData.slice(startIndex,startIndex imgWidth);// 将当前行添加到二维数组中this.grayscaleArray.push(row);}let originalArray this.grayscaleArray;console.log(this.grayscaleArray);let resultArray this.complementArr(this.grayscaleArray, imgWidth, imgHeight);//求二维数组进行24行处理时除数与余数const quotient resultArray.length / 24; // 求次数let list []// 对前24*n行进行处理for (let i 0; i quotient; i) {const startIndex i * 24;const endIndex (i 1) * 24;const rowsToProcess originalArray.slice(startIndex, endIndex);console.log(rowsToProcess);console.log(startIndex, endIndex);list.push(this.processRows(rowsToProcess))}console.log(list);// 二维转一维let list2 list.flat()console.log(list2);this.resultArray list2;// 创建一个 Uint8Array 视图对象将数组数据复制到该视图中const typedArray new Uint8Array(this.resultArray);// 获取 typedArray 的 buffer 属性即得到对应的 ArrayBufferconst buffer typedArray.buffer;this.sendMsg(buffer)},//发送数据给蓝牙sendMsg(buffer) {var newData buffer.slice(20)var writeBuffer buffer.slice(0, 20)console.log(writeBuffer.length);if (writeBuffer.byteLength 20) {console.log(Invalid data length. Data will not be sent.);//纸打印完了之后进行切刀标签纸)uni.writeBLECharacteristicValue({deviceId: uni.getStorageSync(DeviceID),// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取serviceId: uni.getStorageSync(ServiceUUIDNew),// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取characteristicId: uni.getStorageSync(CharacteristicId),// 这里的value是ArrayBuffer类型value: [27, 35, 35, 67, 84, 71, 72, 48],success: function(res) {console.log(res);},})return; // 数据无效不发送}let that this;console.log(uni.getStorageSync(DeviceID));uni.writeBLECharacteristicValue({deviceId: uni.getStorageSync(DeviceID),// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取serviceId: uni.getStorageSync(ServiceUUIDNew),// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取characteristicId: uni.getStorageSync(CharacteristicId),// 这里的value是ArrayBuffer类型value: writeBuffer,writeType: write,success: function(res) {//写入成功后继续递归调用发送剩下的数据that.sendMsg(newData)},fail: function(err) {console.log(err)},complete: function() {}})},//补充数组complementArr(originalArray, width, height) {let originalArrayData originalArraylet replenish 24 - height % 24for (let i 0; i replenish; i) {let arr []for (let j 0; j width; j) {arr.push(0)}originalArrayData.push(arr)}return originalArrayData},//处理数据processRows(rows) {const columnValues [];for (let col 0; col rows[0].length; col) {const values [];for (let row 0; row rows.length; row) {values.push(rows[row][col]);}// 将每列的值拆分为三份const partitionSize Math.ceil(values.length / 3);const partitions [];for (let i 0; i values.length; i partitionSize) {partitions.push(values.slice(i, i partitionSize));}// 将每份的值拼接成一个字符串并转换为10进制数for (let i 0; i partitions.length; i) {const binaryString partitions[i].join();const decimalValue parseInt(binaryString, 2);columnValues.push(decimalValue);}}//计算 n1,n2const n2 Math.floor((this.grayscaleArray[0]).length / 256) 1 ? 0 : Math.floor((this.grayscaleArray[0]).length / 256); // 求除数const n1 this.grayscaleArray[0].length % 256; // 求余数console.log(n2, n1);columnValues.unshift(27, 42, 33, n1, n2)columnValues.push(10)console.log(columnValues);return columnValues},// 根据阈值将灰度值转换为黑白像素值convertToBlackWhite(grayscaleData) {let blackWhiteData [];for (let i 0; i grayscaleData.length; i 4) {let pixel grayscaleData[i];let bwPixel pixel 128 ? 0 : 1;blackWhiteData.push(bwPixel);}return blackWhiteData;},}}
/script
script langrenderjs modulecanvasImageimport html2canvas from html2canvasexport default {data() {return {}},methods: {// 生成图片需要调用的方法generateImage(e, ownerVm) {setTimeout(() {const dom document.getElementById(pagePoster) // 需要生成图片内容的 dom 节点console.log(dom.clientWidth, dom.clientHeight);html2canvas(dom, {width: dom.clientWidth, //dom 原始宽度height: dom.clientHeight,scrollY: 0, // html2canvas默认绘制视图内的页面需要把scrollYscrollX设置为0scrollX: 0,useCORS: true //支持跨域// , // 设置生成图片的像素比例默认是1如果生成的图片模糊的话可以开启该配置项}).then((canvas) {// 创建新的 Canvasvar newCanvas document.createElement(canvas);var newContext newCanvas.getContext(2d);// 设置新 Canvas 的宽度和高度var width 350; // 设置新 Canvas 的宽度var height 176; // 设置新 Canvas 的高度newCanvas.width width;newCanvas.height height;console.log(newCanvas.height);// 将 HTML2Canvas 生成的内容绘制到新 Canvas 中newContext.drawImage(canvas, 0, 0, width, height);// 将新 Canvas 转换为 base64 图像var base64 newCanvas.toDataURL(image/png);console.log(base64);// 发送数据到 逻辑层ownerVm.callMethod(receiveSendData, base64)}).catch(err {})}, 300)},}}
/scriptstyle scoped langscss.BigBox {width: 630rpx;height: auto;background: #FFFFFF;border-radius: 20rpx 20rpx 20rpx 20rpx;display: flex;justify-content: center;align-items: center;}.barImg {width: 566rpx;height: 300rpx;}.BigBox2 {width: 630rpx;height: auto;background: #FFFFFF;border-radius: 20rpx 20rpx 20rpx 20rpx;display: flex;justify-content: space-between;padding: 32rpx 25rpx;box-sizing: border-box;margin: 0 auto;.lf {width: 362rpx;view {width: 100%;white-space: nowrap;/* 禁止换行 */overflow: hidden;/* 溢出内容隐藏 */text-overflow: ellipsis;/* 显示省略号 */font-family: PingFang SC, PingFang SC;font-weight: 500;font-size: 40rpx;color: #000000;margin-top: 15rpx;font-weight: 700;}view:first-child {margin-top: 0;}}.rg {display: flex;justify-content: center;align-items: center;image {width: 204rpx;height: 204rpx;}}}.qrImg {}.print,.close {width: 48%;height: 88rpx;line-height: 88rpx;text-align: center;border-radius: 10rpx 10rpx 10rpx 10rpx;border: 2rpx solid #FFFFFF;font-family: PingFang SC, PingFang SC;font-weight: 500;font-size: 30rpx;color: #FFFFFF;margin-top: 160rpx;}.btnBox {width: 630rpx;display: flex;justify-content: space-between;margin: 0 auto;}#myCanvas {width: 350px;height: 176px;opacity: 0;}
/style