
设置页面:
javascript
<template>
<view class="pageBg">
<u-navbar leftIconColor="#fff" :leftIconSize="28" title="打印设置" bgColor="#3c9cff" :placeholder="true"
@leftClick="$navigateBack"></u-navbar>
<view class="h10"></view>
<!-- 蓝牙未开启提示 -->
<u-alert v-if="!isBlue" type="warning" :show-icon="true" title="未开启蓝牙"
description="无法连接蓝牙打印机,请先开启蓝牙!"></u-alert>
<view class="p10">已连接打印机</view>
<view>
<!-- <view v-if="connected" class="list">{{ connectedDeviceName }}</view> -->
<view v-if="connected || connectedDeviceName" class="list">{{ connectedDeviceName }}</view>
<view v-else class="list center cloc">暂无</view>
</view>
<view class="p10">搜索到的蓝牙打印机</view>
<view>
<view v-for="d in devices" :key="d.deviceId" class="list">
<view @click="connectDevice(d.deviceId)" class="flexBetween">
<view>{{ d.name || '未知设备' }}</view>
<view style="color:#3c9cff;">添加</view>
</view>
</view>
<view v-if="devices.length === 0" class="list center cloc">暂无</view>
</view>
<view class="h100"></view>
<!-- <view class="float-button p10 grid2">
<u-button type="primary" text="重新搜索蓝牙打印机" @click="initAndScan()" color="#3c9cff" />
<u-button type="primary" text="一键打印" @click="oneClickPrint()" color="#3c9cff" />
</view> -->
<view class="float-button p10 ">
<u-button type="primary" text="重新搜索蓝牙打印机" @click="initAndScan()" color="#3c9cff" />
</view>
</view>
</template>
<script>
import {
initBluetooth,
discoverDevices,
connect,
print,
onPrinterStatus,
isBluetoothReady
} from "@/utils/printer.js"
export default {
data() {
return {
devices: [],
connected: false,
connectedDeviceId: null,
connectedDeviceName: "",
reconnecting: false,
isBlue: true,
scanning: false,
}
},
// async onLoad() {
// await this.checkBluetooth()
// if (this.isBlue) {
// await this.autoReconnect()
// await this.initAndScan()
// }
// },
async onLoad() {
await this.checkBluetooth()
// ✅ 获取上次打印机信息
const lastPrinter = uni.getStorageSync("lastPrinter")
console.log('lastPrinter:',lastPrinter)
if (lastPrinter && lastPrinter.name) {
this.connectedDeviceName = lastPrinter.name
}
if (this.isBlue) {
await this.autoReconnect() // 尝试重连
await this.initAndScan() // 扫描设备
}
},
methods: {
/** 检查蓝牙是否可用 */
async checkBluetooth() {
this.isBlue = await isBluetoothReady()
},
/** 初始化蓝牙并扫描设备 */
async initAndScan() {
if (!this.isBlue) {
uni.showToast({
title: "请先开启蓝牙",
icon: "none"
})
return
}
if (this.scanning) return
this.scanning = true
try {
await initBluetooth()
uni.showLoading({
title: '加载中',
mask: true,
})
// 扫描设备
this.devices = await discoverDevices(3000) // 扫描3秒
// ✅ 获取上次打印机
const lastPrinter = uni.getStorageSync("lastPrinter")
if (lastPrinter) {
// 如果扫描列表里没有上次打印机,就加到最前面
if (!this.devices.find(d => d.deviceId === lastPrinter.deviceId)) {
this.devices.unshift({
deviceId: lastPrinter.deviceId,
name: lastPrinter.name || '未知设备'
})
}
}
uni.hideLoading()
console.log('this.devices:', this.devices)
if (this.devices.length === 0) {
uni.showToast({
title: "未找到设备",
icon: "none"
})
}
} catch (err) {
console.error("搜索设备失败", err)
uni.showToast({
title: "搜索设备失败",
icon: "none"
})
} finally {
this.scanning = false
}
},
/** 手动连接设备 */
async connectDevice(deviceId) {
try {
await connect(deviceId)
this.connected = true
this.connectedDeviceId = deviceId
const dev = this.devices.find(d => d.deviceId === deviceId)
this.connectedDeviceName = dev ? dev.name : deviceId
uni.showToast({ title: "连接成功" })
// ✅ 保存上次打印机(带名字)
uni.setStorageSync("lastPrinter", {
deviceId,
name: this.connectedDeviceName
})
this.listenPrinterStatus()
this.listenDisconnect()
} catch (err) {
console.error("连接失败", err)
uni.showToast({ title: "连接失败", icon: "none" })
}
},
/** 打印机状态监听 */
listenPrinterStatus() {
// uni.offBLECharacteristicValueChange()
onPrinterStatus(data => {
if (data[0] & 0x04) uni.showToast({
title: "缺纸",
icon: "none"
})
if (data[0] & 0x08) uni.showToast({
title: "开盖",
icon: "none"
})
if (data[0] & 0x10) uni.showToast({
title: "过热",
icon: "none"
})
})
},
/** 监听断开并自动重连 */
listenDisconnect() {
// uni.offBLEConnectionStateChange()
uni.onBLEConnectionStateChange(res => {
if (!res.connected && res.deviceId === this.connectedDeviceId && !this.reconnecting) {
console.warn("打印机断开,开始自动重连")
this.connected = false
this.reconnecting = true
this.autoReconnect()
}
})
},
/** 自动重连上次打印机 */
async autoReconnect() {
try {
const lastPrinter = uni.getStorageSync("lastPrinter")
if (!lastPrinter || !lastPrinter.deviceId) {
this.reconnecting = false
return
}
uni.showLoading({ title:'加载中', mask:true })
// ✅ 优先直接用 deviceId 连接
try {
await connect(lastPrinter.deviceId)
this.connected = true
this.connectedDeviceId = lastPrinter.deviceId
this.connectedDeviceName = lastPrinter.name || lastPrinter.deviceId
uni.hideLoading()
uni.showToast({ title: "打印机已重连" })
this.listenPrinterStatus()
this.listenDisconnect()
return
} catch (err) {
console.warn("直接重连失败,尝试扫描", err)
}
// ❌ 兜底:扫描设备再匹配
const devices = await discoverDevices(3000)
uni.hideLoading()
const dev = devices.find(d => d.deviceId === lastPrinter.deviceId)
if (!dev) {
console.warn("未找到上次打印机")
this.reconnecting = false
return
}
await connect(dev.deviceId)
this.connected = true
this.connectedDeviceId = dev.deviceId
this.connectedDeviceName = dev.name
uni.showToast({ title: "打印机已重连" })
// ✅ 更新存储,保证 name 最新
uni.setStorageSync("lastPrinter", {
deviceId: dev.deviceId,
name: dev.name
})
this.listenPrinterStatus()
this.listenDisconnect()
} catch (err) {
console.error("自动重连失败", err)
} finally {
this.reconnecting = false
uni.hideLoading()
}
},
/** 一键打印 */
async oneClickPrint() {
try {
if (!this.connected) {
await this.autoReconnect()
if (!this.connected) {
uni.showToast({
title: "未连接打印机",
icon: "none"
})
return
}
}
await print("Hello K329", "https://uniapp.dcloud.io")
uni.showToast({
title: "打印完成"
})
} catch (err) {
console.error("打印失败", err)
uni.showToast({
title: "打印失败",
icon: "none"
})
}
},
},
onUnload() {
// 页面卸载时解绑事件
// uni.offBLECharacteristicValueChange()
// uni.offBLEConnectionStateChange()
}
}
</script>
<style lang="scss" scoped>
.list {
border-bottom: 2rpx solid #efefef;
background: white;
padding: 30rpx;
font-size: 32rpx;
}
.list:active {
background: #efefef;
}
.grid2 {
display: flex;
align-items: center;
}
.grid2>* {
flex: 1;
}
.grid2>*:first-child {
margin-right: 20rpx;
}
</style>
printer.js
javascript
let deviceId = null
let writeServiceId = null
let writeCharacteristicId = null
let notifyServiceId = null
let notifyCharacteristicId = null
/**
* 初始化蓝牙
*/
export function initBluetooth() {
return new Promise((resolve, reject) => {
uni.openBluetoothAdapter({
success: resolve,
fail: reject
})
})
}
/**
* 搜索蓝牙设备
*/
export function discoverDevices(scanTime = 3000) {
return new Promise((resolve, reject) => {
const devices = []
uni.startBluetoothDevicesDiscovery({
allowDuplicatesKey: true, // 可以重复发现
success: () => {
console.log("开始扫描蓝牙设备...")
uni.onBluetoothDeviceFound((res) => {
res.devices.forEach((d) => {
if (d.name && !devices.find(x => x.deviceId === d.deviceId)) {
devices.push(d)
}
})
})
// 扫描指定时间后返回结果
setTimeout(() => {
uni.stopBluetoothDevicesDiscovery({
success: () => {
console.log("扫描结束,设备列表:", devices)
resolve(devices)
},
fail: (err) => {
console.error("停止扫描失败", err)
resolve(devices) // 即使失败也返回已收集的
}
})
}, scanTime)
},
fail: reject
})
})
}
/**
* 连接设备并自动发现写入/状态通道
*/
export function connect(deviceId_) {
return new Promise((resolve, reject) => {
uni.createBLEConnection({
deviceId: deviceId_,
success: () => {
deviceId = deviceId_
// ✅ 存储上次使用的打印机
// uni.setStorageSync("lastPrinter", deviceId_)
console.log("已保存 lastPrinter:", deviceId_)
uni.getBLEDeviceServices({
deviceId,
success: (res) => {
let foundWrite = false
let foundNotify = false
res.services.forEach(service => {
uni.getBLEDeviceCharacteristics({
deviceId,
serviceId: service.uuid,
success: (res2) => {
res2.characteristics.forEach(
c => {
// 找写入通道
if ((c.properties
.write || c
.properties
.writeWithoutResponse
) && !
foundWrite) {
writeServiceId =
service.uuid
writeCharacteristicId
= c.uuid
foundWrite =
true
console.log(
"找到写入通道:",
service
.uuid, c
.uuid)
}
// 找状态通道
if ((c.properties
.notify || c
.properties
.read) && !
foundNotify) {
notifyServiceId
= service
.uuid
notifyCharacteristicId
= c.uuid
foundNotify =
true
console.log(
"找到状态通道:",
service
.uuid, c
.uuid)
// 开启状态通知
uni.notifyBLECharacteristicValueChange({
deviceId,
serviceId: service
.uuid,
characteristicId: c
.uuid,
state: true,
success: () =>
console
.log(
"已开启状态通知"
),
fail: (
err
) =>
console
.error(
"开启状态通知失败:",
err
)
})
}
})
if (foundWrite) resolve()
},
fail: reject
})
})
},
fail: reject
})
},
fail: reject
})
})
}
/**
* 打印文本和二维码
*/
export function print(text, qrcode) {
if (!deviceId || !writeServiceId || !writeCharacteristicId) {
return Promise.reject("请先连接打印机")
}
let cmd = `
SIZE 40 mm,30 mm
GAP 2 mm,0 mm
CLS
TEXT 50,50,"3",0,1,1,"${text}"
`
if (qrcode) {
cmd += `QRCODE 50,150,L,5,A,0,"${qrcode}"\n`
}
cmd += `PRINT 1\n`
const buffer = str2ab(cmd)
return new Promise((resolve, reject) => {
uni.writeBLECharacteristicValue({
deviceId,
serviceId: writeServiceId,
characteristicId: writeCharacteristicId,
value: buffer,
success: resolve,
fail: reject
})
})
}
/**
* 监听打印机状态
*/
export function onPrinterStatus(callback) {
uni.onBLECharacteristicValueChange((res) => {
const data = new Uint8Array(res.value)
console.log("打印机状态数据:", data)
callback(data)
})
}
/**
* 字符串转 ArrayBuffer
*/
function str2ab(str) {
let buf = new ArrayBuffer(str.length)
let bufView = new Uint8Array(buf)
for (let i = 0; i < str.length; i++) {
bufView[i] = str.charCodeAt(i)
}
return buf
}
export async function isBluetoothReady() {
return new Promise((resolve) => {
uni.openBluetoothAdapter({
success: () => {
// 延迟 500ms 再获取状态,保证 iOS 能拿到真实值
setTimeout(() => {
uni.getBluetoothAdapterState({
success(res) {
resolve(res.available)
},
fail() {
resolve(false)
}
})
}, 500)
},
fail() {
resolve(false)
}
})
})
}
export async function oneClickPrint2(text = "Hello K329", qrcode = "https://uniapp.dcloud.io") {
try {
// 1. 蓝牙是否开启
const ready = await isBluetoothReady()
if (!ready) throw new Error("蓝牙未开启")
// 2. 初始化蓝牙适配器
await initBluetooth()
// 3. 获取上次打印机
const lastPrinter = uni.getStorageSync("lastPrinter")
// console.log("lastPrinter:", lastPrinter, "打印机上次")
if (!lastPrinter) throw new Error("未找到已连接打印机")
// 4. 直接尝试连接
await connect(lastPrinter)
// 5. 打印
await print(text, qrcode)
return true
} catch (err) {
console.error("一键打印失败", err)
throw err
}
}
/** 一键打印 */
export async function oneClickPrint(text = "Hello K329", qrcode = "https://uniapp.dcloud.io") {
try {
// 1. 蓝牙是否开启
const ready = await isBluetoothReady()
if (!ready) {
uni.showToast({ title: "蓝牙未开启", icon: "none" })
return
}
// 2. 初始化蓝牙
await initBluetooth()
// 3. 检查是否已连接
if (!this.connected) {
const lastPrinter = uni.getStorageSync("lastPrinter")
if (!lastPrinter || !lastPrinter.deviceId) {
uni.showToast({ title: "未连接打印机", icon: "none" })
return
}
// 尝试直接连接上次打印机
try {
await connect(lastPrinter.deviceId)
this.connected = true
this.connectedDeviceId = lastPrinter.deviceId
this.connectedDeviceName = lastPrinter.name || lastPrinter.deviceId
this.listenPrinterStatus()
this.listenDisconnect()
uni.showToast({ title: "打印机已重连" })
} catch (err) {
console.warn("自动连接上次打印机失败,需要手动连接", err)
uni.showToast({ title: "打印机未连接", icon: "none" })
return
}
}
// 4. 打印
await print(text, qrcode)
uni.showToast({ title: "打印完成" })
} catch (err) {
console.error("一键打印失败", err)
uni.showToast({ title: "打印失败", icon: "none" })
}
}
一键打印:
javascript
<view class="p10">
<u-button type="primary" class="lockoutClass" text="打印" :color="bgColor" @click="printNow()" />
</view>
javascript
async printNow() {
try {
uni.showLoading({
title:'打印中',
mask:true,
})
await this.$printer.oneClickPrint("测试文本", "https://example.com/qrcode")
uni.hideLoading()
uni.showToast({
title: "打印完成"
})
} catch (err) {
console.log('err:',err)
uni.showToast({
title: "打印失败,请检查蓝牙或者打印机!",
icon: "none"
})
}
},