一、引言
随着物联网技术的飞速发展,蓝牙标签打印技术作为一种高效、便捷的打印解决方案,正逐渐在各行各业中展现出其独特的魅力。本文旨在深入探讨DIYGW uniapp低代码可视化开发工具中新增的蓝牙标签打印功能,分析其特点、应用场景、优势,并展示基于uniapp的打印核心代码实现,为开发者提供一个全面的技术参考。
二、蓝牙标签打印机的特点
无线连接
蓝牙标签打印机的最大亮点在于其无线连接方式。通过蓝牙技术,用户可以轻松地将手机、平板电脑或电脑与打印机连接,无需复杂的网络设置或线缆束缚,极大地提升了使用的便捷性和灵活性。
兼容性强
蓝牙标签打印机广泛支持多种操作系统和设备,包括安卓、iOS以及Windows等,确保了不同用户群体的需求得到满足,进一步拓宽了应用场景。
便携小巧
设计上,许多蓝牙标签打印机追求小巧轻便,便于携带。这一特性使得用户能够随时随地进行打印操作,无论是仓库、物流现场还是零售店铺,都能轻松应对。
功能多样
除了基本的文本打印外,蓝牙标签打印机还支持条形码、二维码、图片等多种打印格式,满足了物流、仓储、零售、医疗等多个行业对标签打印的多样化需求。
三、蓝牙标签打印机的应用场景
物流管理
在快递和物流行业中,蓝牙标签打印机能够快速打印出运单、标签等,极大地提高了包裹的分拣和配送效率,减少了人工错误,提升了整体运营效率。
仓储管理
仓库管理中,蓝牙标签打印机能够打印出货物标签、货架标签等,帮助工作人员快速识别和定位货物,优化库存管理流程,提升仓储效率。
零售管理
在超市、便利店等零售场所,蓝牙标签打印机能够打印出价格标签、促销标签等,不仅方便了商品管理,还提升了顾客的购物体验。
医疗管理
医疗行业中,蓝牙标签打印机在打印药品标签、患者信息标签等方面发挥着重要作用,确保了医疗过程的安全性和准确性。
四、蓝牙标签打印的优势
提高效率
无线连接和便携设计使得蓝牙标签打印机能够在任何需要的地方进行打印操作,极大地提高了工作效率,减少了等待时间。
降低成本
相比传统的有线打印方式,蓝牙标签打印机减少了线缆和设备的投入成本,同时降低了维护成本,为企业节省了大量开支。
增强灵活性
蓝牙标签打印机支持多种打印格式和内容,可以根据实际需求进行灵活调整和优化,满足不同场景下的打印需求。
五、打印核心代码实现(基于uniapp)
在DIYGW uniapp低代码可视化开发工具中,我们基于uniapp框架对蓝牙标签打印功能进行了深度集成和二次开发。以下是一个简化的打印核心代码示例,展示了如何在uniapp中实现蓝牙标签的打印功能:
import tsc from './tsc.js';
import esc from './esc.js';
// 打印程序begin---------------------------------
/**
* android 6.0以上需授权地理位置权限
*/
var checkPemission = function(that) {
var systemInfo = uni.getSystemInfoSync();
var platform = systemInfo.platform;
if (platform == "ios") {
getBluetoothDevices(that)
} else if (platform == "android") {
let system = systemInfo.system;
let system_no = system.replace('android', '');
system_no = system.replace('Android', '');
if (Number(system_no) > 5) {
uni.getSetting({
success: function(res) {
if (!res.authSetting['scope.userLocation']) {
uni.authorize({
scope: 'scope.userLocation',
complete: function(res) {
getBluetoothDevices(that)
}
})
} else {
getBluetoothDevices(that)
}
}
})
}
}
}
/**
* 获取蓝牙设备信息
*/
var getBluetoothDevices = function(that) {
console.log("start search")
uni.showLoading({
title: '搜索中',
})
that.setData({
isScanning: true
})
uni.startBluetoothDevicesDiscovery({
success: function(res) {
setTimeout(function() {
uni.getBluetoothDevices({
success: function(res) {
var devices = []
var num = 0
for (var i = 0; i < res.devices.length; ++i) {
if (res.devices[i].name != "未知设备") {
devices[num] = res.devices[i]
num++
}
}
that.setData({
devicesList: devices,
isScanning: false
})
uni.hideLoading()
uni.stopPullDownRefresh()
},
})
}, 3000)
},
})
}
/**
* 开始连接蓝牙设置
*/
var connectBluetoothSettings = function(app, that) {
let deviceId = that.deviceId;
uni.stopBluetoothDevicesDiscovery({
success: function(res) {
//console.log(res)
},
})
that.setData({
serviceId: 0,
writeCharacter: false,
readCharacter: false,
notifyCharacter: false
})
uni.showLoading({
title: '正在连接',
})
uni.createBLEConnection({
deviceId: deviceId,
success: function(res) {
app.globalData.bluetoothDeviceId = deviceId
getBLEDeviceServices(app, that);
uni.showLoading({
title: '连接好可以打印',
})
},
fail: function(e) {
uni.showModal({
title: '提示',
content: '连接失败',
})
uni.hideLoading()
},
complete: function(e) {
//console.log(e)
}
})
}
/**
* 获取蓝牙设备所有服务
*/
var getBLEDeviceServices = function(app, that) {
console.log(app.globalData.bluetoothDeviceId)
uni.getBLEDeviceServices({
deviceId: app.globalData.bluetoothDeviceId,
success: function(res) {
that.setData({
services: res.services
})
getBLEDeviceCharacteristics(app, that)
},
fail: function(e) {
console.log(e)
},
complete: function(e) {
//console.log(e)
}
})
}
/**
* 获取蓝牙设备某个服务中所有特征值
*/
var getBLEDeviceCharacteristics = function(app, that) {
var list = that.services
var num = that.serviceId
var write = that.writeCharacter
var read = that.readCharacter
var notify = that.notifyCharacter
uni.getBLEDeviceCharacteristics({
deviceId: app.globalData.bluetoothDeviceId,
serviceId: list[num].uuid,
success: function(res) {
for (var i = 0; i < res.characteristics.length; ++i) {
var properties = res.characteristics[i].properties
var item = res.characteristics[i].uuid
if (!notify) {
if (properties.notify) {
app.globalData.notifyCharaterId = item
app.globalData.notifyServiceId = list[num].uuid
notify = true
}
}
if (!write) {
if (properties.write) {
app.globalData.writeCharaterId = item
app.globalData.writeServiceId = list[num].uuid
write = true
}
}
if (!read) {
if (properties.read) {
app.globalData.readCharaterId = item
app.globalData.readServiceId = list[num].uuid
read = true
}
}
}
if (!write || !notify || !read) {
num++
that.setData({
writeCharacter: write,
readCharacter: read,
notifyCharacter: notify,
serviceId: num
})
if (num == list.length) {
uni.showModal({
title: '提示',
content: '找不到该读写的特征值',
})
} else {
getBLEDeviceCharacteristics(app, that)
}
} else {
notifyBLECharacteristicValueChange(app)
}
},
fail: function(e) {
console.log(e)
},
complete: function(e) {
//console.log("write:" + app.globalData.writeCharaterId)
//console.log("read:" + app.globalData.readCharaterId)
//console.log("notify:" + app.globalData.notifyCharaterId)
}
})
}
/**
* 启用低功耗蓝牙设备特征值变化时的 notify 功能
*/
var notifyBLECharacteristicValueChange = function(app) {
//console.log("deviceId:" + app.globalData.bluetoothDeviceId)
//console.log("serviceId:" + app.globalData.notifyServiceId)
//console.log("notifyCharaterId:" + app.globalData.notifyCharaterId)
uni.hideLoading();
uni.notifyBLECharacteristicValueChange({
deviceId: app.globalData.bluetoothDeviceId,
serviceId: app.globalData.notifyServiceId,
characteristicId: app.globalData.notifyCharaterId,
state: true,
success: function(res) {
uni.onBLECharacteristicValueChange(function(r) {
//console.log('onBLECharacteristicValueChange=', r);
})
},
fail: function(e) {
console.log('fail', e)
},
complete: function(e) {
//console.log('complete', e)
}
})
}
/**
* 标签模式
*/
var labelTest = function(app, that) {
var command = tsc.jpPrinter.createNew()
command.setSize(70, 50) //纸宽度70,高度50
command.setGap(0)
command.setCls() //需要设置这个,不然内容和上一次重复
// 10起始位置,10行距,TSS24.BF2字体,1字与字之间的间距,1字体大小,最后一个打印内容
command.setText(10, 10, "TSS24.BF2", 2, 2, 'DIY可视化蓝牙打印')
command.setText(10, 70, "TSS24.BF2", 1, 1, '联系人:邓志锋')
command.setText(10, 110, "TSS24.BF2", 1, 1, '网址:diygw.com')
command.setText(10, 150, "TSS24.BF2", 1, 1, '电话:15655555555')
//command.setText(10, 40, "TSS24.BF2", 1, 2, "蓝牙热敏标签打印测试2")
command.setPagePrint()
prepareSend(app, that, command.getData())
}
// 打印标签
var printLabel = function(app, that) {
var command = tsc.jpPrinter.createNew()
command.setSpeed(0)
command.setSize(that.form.width, that.form.height) //纸宽度,高度
command.setGap(0)
command.setCls() //需要设置这个,不然内容和上一次重复
// 10起始位置,TSS24.BF2字体,1字与字之间的间距,1字体大小,一行行增加40高度打印
// 打印位置
let y = 0;
let content = that.form.content
for (let i = 0; i < content.length; i++) {
command.setText(10, y, "TSS24.BF2", content[i].scale, content[i].scale, content[i].text)
y = y + 40
}
command.setPagePrint()
prepareSend(app, that, command.getData())
}
/**
* 准备发送数据
*/
var prepareSend = function(app, that, buff) {
//console.log('buff', buff)
var time = that.oneTimeData
var looptime = parseInt(buff.length / time);
var lastData = parseInt(buff.length % time);
that.setData({
looptime: looptime + 1,
lastData: lastData,
currentTime: 1,
})
Send(app, that, buff)
}
/**
* 查询打印机状态
*/
var queryPrinterStatus = function() {
var command = esc.jpPrinter.Query();
command.getRealtimeStatusTransmission(1);
this.setData({
returnResult: "查询成功"
})
}
/**
* 分包发送
*/
var Send = function(app, that, buff) {
var currentTime = that.currentTime;
var loopTime = that.looptime;
var lastData = that.lastData;
var onTimeData = that.oneTimeData;
var printNum = that.printNum; //打印多少份
var currentPrint = that.currentPrint;
var buf
var dataView
if (currentTime < loopTime) {
buf = new ArrayBuffer(onTimeData)
dataView = new DataView(buf)
for (var i = 0; i < onTimeData; ++i) {
dataView.setUint8(i, buff[(currentTime - 1) * onTimeData + i])
}
} else {
buf = new ArrayBuffer(lastData)
dataView = new DataView(buf)
for (var i = 0; i < lastData; ++i) {
dataView.setUint8(i, buff[(currentTime - 1) * onTimeData + i])
}
}
console.log("第" + currentTime + "次发送数据大小为:" + buf.byteLength);
console.log("deviceId:" + app.globalData.bluetoothDeviceId)
console.log("serviceId:" + app.globalData.writeServiceId)
console.log("characteristicId:" + app.globalData.writeCharaterId)
uni.writeBLECharacteristicValue({
deviceId: app.globalData.bluetoothDeviceId,
serviceId: app.globalData.writeServiceId,
characteristicId: app.globalData.writeCharaterId,
value: buf,
success: function(res) {
console.log('写入成功', res)
},
fail: function(e) {
console.error('写入失败', e)
},
complete: function() {
currentTime++
if (currentTime <= loopTime) {
that.setData({
currentTime: currentTime
})
Send(app, that, buff)
} else {
if (currentPrint == printNum) {
that.setData({
looptime: 0,
lastData: 0,
currentTime: 1,
isReceiptSend: false,
isLabelSend: false,
currentPrint: 1
})
} else {
currentPrint++
that.setData({
currentPrint: currentPrint,
currentTime: 1,
})
console.log("开始打印")
Send(app, that, buff)
}
}
//console.log('打印完成')
}
})
}
/**
* 蓝牙搜索
*/
var searchBluetooth = function(that) {
//判断蓝牙是否打开
if(uni.openBluetoothAdapter){
uni.openBluetoothAdapter({
success: function(res) {
uni.getBluetoothAdapterState({
success: function(res) {
if (res.available) {
if (res.discovering) {
uni.stopBluetoothDevicesDiscovery({
success: function(res) {
//console.log(res)
}
})
}
checkPemission(that)
} else {
uni.showModal({
title: '提示',
content: '请开启手机蓝牙后再试',
})
}
},
})
},
fail: function() {
uni.showModal({
title: '提示',
content: '蓝牙初始化失败,请打开蓝牙',
})
}
})
}else{
that.showToast('只支持APP或小程序')
}
}
// -end---------------------------------
var print = {
searchBluetooth: searchBluetooth,
connectBluetoothSettings: connectBluetoothSettings,
labelTest: labelTest,
printLabel: printLabel
}
export default print;
完整项目代码
<template>
<view class="container container329843">
<button @tap="navigateTo" data-type="searchBluetoothFunction" class="diygw-col-24 btn-clz diygw-btn-default">搜索打印机</button>
<text class="diygw-col-24 text-clz"> 如果是微信小程序,请前往微信官方公众平台设置隐私协议 </text>
<u-form-item labelWidth="auto" class="diygw-col-24" v-if="devicesList.length > 0" label="选择打印机" labelPosition="top" prop="deviceId">
<diy-checkbox class="diygw-col-24" col="12" mode="radio" valueField="deviceId" labelField="name" v-model="deviceId" :list="devicesList" @change="changeDeviceId"> </diy-checkbox>
</u-form-item>
<u-form-item labelAlign="justify" class="diygw-col-24" label="打印份数" prop="printNum">
<u-input :focus="printNumFocus" placeholder="请输入打印份数" v-model="printNum" type="number"></u-input>
</u-form-item>
<u-form :model="form" :rules="formRules" :errorType="['message', 'toast']" ref="formRef" class="flex diygw-form diygw-col-24">
<view class="flex flex-wrap diygw-col-24">
<u-form-item labelAlign="justify" class="diygw-col-12" label="标签宽度" prop="width">
<u-input :focus="formData.widthFocus" placeholder="请输入宽度" v-model="form.width" type="number"></u-input>
</u-form-item>
<u-form-item labelAlign="justify" class="diygw-col-12" label="标签高度" prop="height">
<u-input :focus="formData.heightFocus" placeholder="请输入宽度" v-model="form.height" type="number"></u-input>
</u-form-item>
</view>
<view class="flex flex-wrap diygw-col-24">
<view class="diygw-col-24" v-for="(contentItem, contentIndex) in form.content" :key="contentIndex">
<u-form class="diygw-col-24" :model="form.content[contentIndex]" :errorType="['message', 'toast']" ref="contentRef" :rules="contentItemRules">
<view class="flex flex-wrap diygw-col-24 flex1-clz">
<u-form-item labelAlign="justify" class="diygw-col-24" label="放大倍数" prop="scale">
<u-input :focus="formData.contentItemDatas[contentIndex].scaleFocus" placeholder="请输入宽度" v-model="contentItem.scale" type="number"></u-input>
</u-form-item>
<u-form-item labelAlign="justify" class="diygw-col-24" label="打印内容" prop="text">
<u-input :focus="formData.contentItemDatas[contentIndex].textFocus" placeholder="请输入宽度" v-model="contentItem.text"></u-input>
</u-form-item>
</view>
</u-form>
<view class="formcontenttools flex justify-end">
<button @tap="upContentItem" :data-index="contentIndex" class="diygw-btn flex-sub radius margin-xs">
<text class="button-icon diy-icon-fold"></text>
</button>
<button @tap="downContentItem" :data-index="contentIndex" class="diygw-btn flex-sub radius margin-xs">
<text class="button-icon diy-icon-unfold"></text>
</button>
<button @tap="addContentItem" :data-index="contentIndex" class="diygw-btn flex-sub radius margin-xs">
<text class="button-icon diy-icon-add"></text>
</button>
<button @tap="delContentItem" :data-index="contentIndex" class="diygw-btn flex-sub radius margin-xs">
<text class="button-icon diy-icon-close"></text>
</button>
</view>
</view>
</view>
</u-form>
<button @tap="navigateTo" data-type="printFunction" class="diygw-col-24 prt-clz diygw-btn-default">开始打印</button>
<view class="clearfix"></view>
</view>
</template>
<script>
import print from '@/common/print/print.js';
export default {
data() {
return {
//用户全局信息
userInfo: {},
//页面传参
globalOption: {},
//自定义全局变量
globalData: {},
devicesList: [],
services: [],
serviceId: 0,
writeCharacter: false,
readCharacter: false,
notifyCharacter: false,
isScanning: false,
looptime: 0,
currentTime: 1,
lastData: 0,
oneTimeData: 20,
returnResult: 'returnResult',
currentPrint: 1,
isReceiptSend: false,
isLabelSend: true,
deviceId: '',
printNumFocus: false,
printNum: 1,
form: {
width: 40,
height: 70,
content: []
},
formRules: {},
contentItem: {
scale: 1,
text: 'DIYGW可视化蓝牙打印'
},
contentItemData: {
scaleFocus: false,
textFocus: false
},
formData: {
widthFocus: false,
heightFocus: false,
contentItemDatas: []
},
contentItemRules: {}
};
},
onShow() {
this.setCurrentPage(this);
},
onLoad(option) {
this.setCurrentPage(this);
if (option) {
this.setData({
globalOption: this.getOption(option)
});
}
this.init();
},
onReady() {
this.$refs.formRef?.setRules(this.formRules);
this.initContentData();
},
methods: {
async init() {
await this.initResetform();
},
// 搜索打印机 自定义方法
async searchBluetoothFunction(param) {
let thiz = this;
print.searchBluetooth(this);
},
// 打印 自定义方法
async printFunction(param) {
let thiz = this;
if (!this.deviceId) {
this.navigateTo({
type: 'tip',
tip: '请选择打印机'
});
return;
}
print.printLabel(getApp(), this);
},
// 连接打印 自定义方法
async connectFunction(param) {
let thiz = this;
print.connectBluetoothSettings(getApp(), this);
},
changeDeviceId(evt) {
this.navigateTo({ type: 'connectFunction' });
},
//初始化显示子表单数据条数
initContentData() {
for (let i = 0; i < 1; i++) {
this.form.content.push(JSON.parse(JSON.stringify(this.contentItem)));
this.formData.contentItemDatas.push(JSON.parse(JSON.stringify(this.contentItemData)));
}
this.initContentValid();
},
//子表单验证
initContentValid() {
this.$nextTick(() => {
this.$refs['contentRef']?.forEach((subform) => {
subform.setRules(this.contentItemRules);
});
});
},
//上移子表单
upContentItem(evt) {
let { index } = evt.currentTarget.dataset;
if (index == 0) {
this.navigateTo({
type: 'tip',
tip: '已经是第一个'
});
return false;
}
this.form.content[index] = this.form.content.splice(index - 1, 1, this.form.content[index])[0];
this.formData.contentItemDatas[index] = this.formData.contentItemDatas.splice(index - 1, 1, this.formData.contentItemDatas[index])[0];
this.initContentValid();
},
//下移子表单
downContentItem(evt) {
let { index } = evt.currentTarget.dataset;
if (index == this.form.content.length - 1) {
this.navigateTo({
type: 'tip',
tip: '已经是最后一个'
});
return false;
}
this.form.content[index] = this.form.content.splice(index + 1, 1, this.form.content[index])[0];
this.formData.contentItemDatas[index] = this.formData.contentItemDatas.splice(index + 1, 1, this.formData.contentItemDatas[index])[0];
this.initContentValid();
},
//删除子表单
delContentItem(evt) {
if (this.form.content.length == 1) {
this.showToast('不能小于1个');
return;
}
let { index } = evt.currentTarget.dataset;
this.form.content.splice(index, 1);
this.formData.contentItemDatas.splice(index, 1);
this.initContentValid();
},
//增加子表单
addContentItem() {
this.form.content.push(JSON.parse(JSON.stringify(this.contentItem)));
this.formData.contentItemDatas.push(JSON.parse(JSON.stringify(this.contentItemData)));
this.initContentValid();
},
//验证所有的子表单
checkContentValid() {
let flag = true;
this.$refs['contentRef']?.forEach((subform) => {
subform.validate((valid) => {
if (!valid) {
flag = false;
return false;
}
});
});
return flag;
},
initResetform() {
this.initform = JSON.stringify(this.form);
//如果想给表单默认初始值,其中row为某一行数据也可能是API返回的结果集,然后给到this.form
//this.form = this.$tools.changeRowToForm(row,this.form)
},
resetForm() {
this.form = JSON.parse(this.initform);
},
async submitForm(e) {
this.$refs.formRef?.setRules(this.formRules);
this.initContentValid();
this.$nextTick(async () => {
let contentvalid = await this.checkContentValid();
let valid = await this.$refs.formRef.validate();
if (valid && contentvalid) {
//保存数据
let param = this.form;
let header = {
'Content-Type': 'application/json'
};
let url = '';
if (!url) {
this.showToast('请先配置表单提交地址', 'none');
return false;
}
let res = await this.$http.post(url, param, header, 'json');
if (res.code == 200) {
this.showToast(res.msg, 'success');
} else {
this.showModal(res.msg, '提示', false);
}
} else {
console.log('验证失败');
}
});
}
}
};
</script>
<style lang="scss" scoped>
.btn-clz {
padding-top: 20rpx;
border-bottom-left-radius: 120rpx;
color: #fff;
padding-left: 20rpx;
padding-bottom: 20rpx;
border-top-right-radius: 120rpx;
margin-right: 30rpx;
background-color: #07c160;
margin-left: 30rpx;
overflow: hidden;
width: calc(100% - 30rpx - 30rpx) !important;
border-top-left-radius: 120rpx;
margin-top: 20rpx;
border-bottom-right-radius: 120rpx;
margin-bottom: 20rpx;
text-align: center;
padding-right: 20rpx;
}
.text-clz {
border-bottom-left-radius: 120rpx;
overflow: hidden;
border-top-left-radius: 120rpx;
border-top-right-radius: 120rpx;
border-bottom-right-radius: 120rpx;
text-align: center;
}
.formcontenttools {
position: absolute;
z-index: 1;
right: 0rpx;
top: 0rpx;
}
.formcontenttools .diygw-btn {
padding: 5px;
height: auto;
flex: inherit;
border-radius: 20px;
}
.flex1-clz {
border: 2rpx solid #eee;
padding-top: 10rpx;
border-bottom-left-radius: 12rpx;
padding-left: 10rpx;
padding-bottom: 10rpx;
border-top-right-radius: 12rpx;
margin-right: 20rpx;
margin-left: 20rpx;
overflow: hidden;
width: calc(100% - 20rpx - 20rpx) !important;
border-top-left-radius: 12rpx;
margin-top: 10rpx;
border-bottom-right-radius: 12rpx;
margin-bottom: 10rpx;
padding-right: 10rpx;
}
.prt-clz {
padding-top: 20rpx;
border-bottom-left-radius: 120rpx;
color: #fff;
padding-left: 20rpx;
padding-bottom: 20rpx;
border-top-right-radius: 120rpx;
margin-right: 30rpx;
background-color: #07c160;
margin-left: 30rpx;
overflow: hidden;
width: calc(100% - 30rpx - 30rpx) !important;
border-top-left-radius: 120rpx;
margin-top: 20rpx;
border-bottom-right-radius: 120rpx;
margin-bottom: 20rpx;
text-align: center;
padding-right: 20rpx;
}
.container329843 {
}
</style>
注意:上述代码仅为示例,实际开发中需要根据具体的蓝牙打印机型号和指令集进行适配和调整。同时,由于uniapp的蓝牙API可能因平台差异而有所不同,开发者需要参考uniapp官方文档进行开发。
六、结论
https://space.bilibili.com/1811782699/video