Vue3 + UniApp 蓝牙连接与数据发送(稳定版)
本教程适用于使用 uni-app
+ Vue3 (script setup)
开发的跨平台 App(支持微信小程序、H5、Android/iOS 等)
🎯 功能目标
- ✅ 获取蓝牙权限
- ✅ 扫描周围蓝牙设备
- ✅ 连接指定蓝牙设备
- ✅ 获取服务和特征值
- ✅ 向设备发送数据包(ArrayBuffer)
- ✅ 页面 UI 展示设备列表 + 操作按钮
项目结构概览
/pages/bluetooth/
├── index.vue # 主页面(本教程重点)
└── utils/Common.ts # 公共方法(获取系统信息等)
其中的公共方法代码:
export async function getSystemInfo() {return await uni.getSystemInfo();
}
第一步:申请蓝牙权限并初始化蓝牙适配器
在 onShow()
生命周期中检查并申请蓝牙权限:
import { onShow } from "@dcloudio/uni-app";
import { ref } from "vue";let btOpenStatus = ref<boolean>(false);
let devicesList = ref<UniApp.BluetoothDeviceInfo[]>([]);onShow(() => {uni.authorize({scope: 'scope.bluetooth',success() {console.log('蓝牙权限已授权');initBluetooth();},fail() {showToast('请开启蓝牙权限!');}});
});
初始化蓝牙模块
function initBluetooth() {uni.onBluetoothAdapterStateChange(function (res) {btOpenStatus.value = res.available;if (res.available) startBluetoothScan(); // 蓝牙打开后开始扫描});uni.openBluetoothAdapter({success: () => {startBluetoothScan();},fail: (err) => {if (err.errCode == 10001) {btOpenStatus.value = false;showToast('蓝牙未打开!');}}});
}
🔍 第二步:扫描蓝牙设备
function startBluetoothScan() {uni.startBluetoothDevicesDiscovery({success: (res) => {console.log("开始扫描蓝牙设备...", res);},fail: (err) => {console.error("启动扫描失败", err);showToast("启动蓝牙扫描失败");}});uni.onBluetoothDeviceFound((res) => {res.devices.forEach((device) => {const exists = devicesList.value.some(d => d.deviceId === device.deviceId);if (!exists) devicesList.value.push(device);});});
}
🔗 第三步:连接蓝牙设备
const connectedDevice = ref({serviceOrFeature: [] as Array<{ service: any, characteristics?: any }>,devicesInfo: {} as UniApp.BluetoothDeviceInfo
});async function createBLEConnection(device: UniApp.BluetoothDeviceInfo) {uni.showToast({duration: 30000,icon: "loading",title: '蓝牙正在连接中!'});uni.createBLEConnection({deviceId: device.deviceId,success(connectionRes) {if (connectionRes.errCode === 0) {showToast('蓝牙连接成功');connectedDevice.value.devicesInfo = device;getBLEDeviceServices(device.deviceId).then(res => {if (res.code === 200) console.log('蓝牙服务初始化完成');});}},fail(connectionRes) {if (connectionRes.errCode === 10000) {showToast('请检查蓝牙是否开启!');} else if (connectionRes.errCode === 10010 || connectionRes.errCode === -1) {console.log('已经连接');}},complete() {uni.hideToast();}});
}
⚙️ 第四步:获取服务与特征值
function getBLEDeviceServices(deviceId: string): Promise<{ code: number }> {return new Promise(ok => {uni.getBLEDeviceServices({deviceId,success: (res) => {res.services.forEach(async (item) => {let characteristicsRes = await getBLEDeviceCharacteristics(deviceId, item.uuid);if (characteristicsRes.code === 200) {connectedDevice.value.serviceOrFeature.push({service: item,characteristics: characteristicsRes.data});ok({ code: 200 });}});},fail: (err) => {ok({ code: 201 });}});});
}function getBLEDeviceCharacteristics(deviceId: string, serviceId: string): Promise<{ code: number, data?: any }> {return new Promise(ok => {uni.getBLEDeviceCharacteristics({deviceId,serviceId,success: (res) => {ok({ code: 200, data: res.characteristics });},fail: () => {ok({ code: 201 });}});});
}
💬 第五步:向蓝牙设备发送数据
function getBluetoothServiceFeature(propertyName: string): { serviceUUID: string, feature: any } {let result = { serviceUUID: '', feature: {} };connectedDevice.value.serviceOrFeature.forEach(item => {let found = item.characteristics.find(f => f.properties[propertyName]);if (found) {result.serviceUUID = item.service.uuid;result.feature = found;}});return result;
}function sendMsg(msg: any, isBuffer?: boolean) {let writeFeature = getBluetoothServiceFeature('write');if (!writeFeature) {console.log('蓝牙没有对应的写服务权限!');return;}uni.writeBLECharacteristicValue({deviceId: connectedDevice.value.devicesInfo.deviceId,serviceId: writeFeature.serviceUUID,characteristicId: writeFeature.feature.uuid,value: isBuffer ? msg : stringToArrayBuffer(msg),success(res) {console.log('消息发送成功', res);},fail(res) {console.log('消息发送失败', res);}});
}function stringToArrayBuffer(str: string): ArrayBuffer {const buffer = new ArrayBuffer(str.length);const view = new Uint8Array(buffer);for (let i = 0; i < str.length; i++) {view[i] = str.charCodeAt(i);}return buffer;
}
完整代码
<template><template><scroll-view scroll-y style="height: 100vh;background: #f9f9f9;" class="device-list"><!-- 设备列表 --><view v-for="device in devicesList" :key="device.deviceId" class="device-card"><!-- 设备信息 --><view class="device-info"><text class="name">{{ device.name || '未知设备' }}</text><text class="id">ID: {{ device.deviceId }}</text></view><!-- 操作按钮 --><view class="actions"><text class="btn connect" @click.stop="createBLEConnection(device)">连接</text><text class="btn send" @click.stop="sendMsg('测试发送信息')">发送信息</text></view></view><!-- 空状态提示 --><view v-if="devicesList.length === 0" class="empty-state">正在搜索附近的蓝牙设备...</view></scroll-view></template>
</template><script setup lang="ts">import { onShow } from "@dcloudio/uni-app";import { ref , watch } from "vue";import { getSystemInfo } from "@/utils/Common";let systemInfo = ref(); let btOpenStatus = ref<boolean>();let devicesList = ref<UniApp.BluetoothDeviceInfo[]>([]); // 用于存储搜索到的设备onShow( async () => {systemInfo.value = await getSystemInfo();uni.authorize({scope: 'scope.bluetooth',success() {console.log('蓝牙权限已授权');initBluetooth();},fail() {showToast('请开启蓝牙权限!');}});});function initBluetooth() {uni.onBluetoothAdapterStateChange(function (res) {console.log(`蓝牙状态变化,用户${res.available ? '打开' : '关闭'}蓝牙!`);btOpenStatus.value = res.available;if(res.available) {startBluetoothScan();}});uni.openBluetoothAdapter({success: () => {console.log("蓝牙适配器已打开!");startBluetoothScan(); // 开始扫描设备},fail: (err) => {if (err.errCode == 10001) {btOpenStatus.value = false;showToast('蓝牙未打开!');}}});}function startBluetoothScan() {uni.startBluetoothDevicesDiscovery({success: (res) => {console.log("开始扫描蓝牙设备...",res);},fail: (err) => {console.error("启动扫描失败", err);showToast("启动蓝牙扫描失败");}});// 监听新发现的设备uni.onBluetoothDeviceFound((res) => {// 遍历发现的设备res.devices.forEach((device) => {// 去重:根据 deviceId 判断是否已存在const exists = devicesList.value.some(d => d.deviceId === device.deviceId);if (!exists) {devicesList.value.push(device);}});});}const connectedDevice = ref({serviceOrFeature: [] as Array<{ service: any, characteristics ? : any }>,devicesInfo: {} as UniApp.BluetoothDeviceInfo});/*** 连接蓝牙设备*/async function createBLEConnection(device: UniApp.BluetoothDeviceInfo) {await uni.getLocation({});if(devicesList.value.length <= 0) {showToast('正在搜索附近的蓝牙设备');return;}uni.showToast({duration: 30000,icon: "loading",title: '蓝牙正在连接中!'});console.log('选择的蓝牙设备:',device);if(device) {connectedDevice.value.devicesInfo = device;uni.createBLEConnection({deviceId: device.deviceId,async success(connectionRes) {if(connectionRes.errCode == 0) {console.log('连接成功!');showToast('蓝牙连接成功');let servicesRes = await getBLEDeviceServices(device.deviceId);if(servicesRes.code == 200) {console.log('蓝牙初始化服务完成');}}},fail(connectionRes) {if(connectionRes.errCode == 10000) {showToast('请检查蓝牙是否开启!');}else if(connectionRes.errCode == 10000) {showToast('蓝牙连接失败,可以重试!');}else if(connectionRes.errCode == 10010 || connectionRes.errCode == -1) {console.log('已经连接');}},complete() {uni.hideToast();}});}}/*** 获取蓝牙设备的服务(service)*/function getBLEDeviceServices(deviceId: string) : Promise<{code : number}> {return new Promise( ok => {uni.getBLEDeviceServices({deviceId,success: (res) => {res.services.forEach(async (item) => {let characteristicsRes = await getBLEDeviceCharacteristics(deviceId,item.uuid);if(characteristicsRes.code == 200) {connectedDevice.value.serviceOrFeature.push({service: item,characteristics: characteristicsRes.data});ok({ code : 200 });}});},fail: (err) => {console.log("获取服务失败", err);ok({ code : 201 });}});});}/*** 获取蓝牙设备的特征值(characteristic)*/async function getBLEDeviceCharacteristics(deviceId: string, serviceId: string) : Promise<{ code : number , data ? : any }> {return new Promise( ok => {uni.getBLEDeviceCharacteristics({deviceId,serviceId,success: (res) => {ok({code: 200,data: res.characteristics});},fail: () => {ok({code : 201})}});});}/*** 获取连接设备的写特征值(wirteCharacteristic)*/function getBluetoothServiceFeature(propertyName: string): { serviceUUID: string, feature: any } {let serviceFeatureInfo: { serviceUUID: string, feature: any } = { serviceUUID: '', feature: {} };connectedDevice.value.serviceOrFeature.forEach(item => {let foundFeature = item.characteristics.find((feature: any) => feature.properties[propertyName]);if (foundFeature) {serviceFeatureInfo.serviceUUID = item.service.uuid;serviceFeatureInfo.feature = foundFeature;return;}});return serviceFeatureInfo;}// 向蓝牙写数据function sendMsg(msg: any, isBuffer ? : boolean ) {console.log('发送的信息:',msg); let writeServiceFeature = getBluetoothServiceFeature('write');if (!writeServiceFeature) {console.log('蓝牙没有对应的写服务权限!');return;}uni.writeBLECharacteristicValue({deviceId: connectedDevice.value.devicesInfo.deviceId,serviceId: writeServiceFeature.serviceUUID,characteristicId: writeServiceFeature.feature.uuid, value: isBuffer ? msg : stringToArrayBuffer(msg) as any,writeType: systemInfo.value.osName == 'ios' ? 'write' : 'writeNoResponse',success(res) {console.log('消息发送成功', res);},fail(res) {console.log('消息发送失败', res);}});}function stringToArrayBuffer(str: string): ArrayBuffer {const buffer = new ArrayBuffer(str.length);const view = new Uint8Array(buffer);for (let i = 0; i < str.length; i++) {view[i] = str.charCodeAt(i);}return buffer;}function showToast(title: string) {uni.showToast({icon: 'none',title});}</script><style lang="scss" scoped>.device-card {background-color: #fff;border-radius: 8px;padding: 16px;margin-bottom: 12px;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);display: flex;flex-direction: column;gap: 10px;}.device-info {.name {font-weight: bold;font-size: 16px;color: #333;}.id {font-size: 14px;color: #888;display: block;margin-top: 4px;}}.actions {display: flex;gap: 10px;.btn {flex: 1;text-align: center;padding: 8px 0;border-radius: 4px;font-size: 14px;}.connect {color: #fff;background-color: #6659E5;}.send {color: #fff;background-color: #FC5531;}}.empty-state {text-align: center;padding: 20px;color: #999;}
</style>
🛠️ 补充建议
功能 | 实现方式 |
---|---|
显示 RSSI 信号强度 | 在设备项中显示 {{ device.RSSI }} dBm |
自动刷新设备列表 | 使用定时器每隔几秒重新扫描 |
防止重复点击连接 | 添加 connectingDeviceId 状态控制 |
发送自定义数据包 | 使用 buildBluetoothPacket() 构造特定格式数据 |
📦 最终效果预览

📌 总结
✅ 本教程实现了从蓝牙权限申请 → 设备扫描 → 连接设备 → 获取服务 → 特征值读写 → 数据发送的一整套流程。
🎯 适用于智能门锁、手环、打印机、IoT 等需要蓝牙通信的场景。
💡 如果你需要对接具体蓝牙协议(如 BLE 服务 UUID、数据格式),欢迎继续提问,我可以帮你定制!
相关文章:

Vue3 + UniApp 蓝牙连接与数据发送(稳定版)
本教程适用于使用 uni-app Vue3 (script setup) 开发的跨平台 App(支持微信小程序、H5、Android/iOS 等) 🎯 功能目标 ✅ 获取蓝牙权限✅ 扫描周围蓝牙设备✅ 连接指定蓝牙设备✅ 获取服务和特征值✅ 向设备发送数据包(ArrayBu…...

三种读写传统xls格式文件开源库libxls、xlslib、BasicExcel的比较
最近准备读写传统xls格式文件,而不是较新的xlsx,询问DeepSeek有哪些开源库,他给出了如下的简介和建议,还给出了相应链接,不过有的链接已失效。最后还不忘提醒,现在该用xlsx格式了。 以下是几个可以处理传统…...

Nature子刊同款的宏基因组免疫球蛋白测序怎么做?
免疫球蛋白A(IgA)是人体肠道黏膜分泌的主要抗体,它在塑造肠道微生物群落和维持肠道稳态中起着关键作用,有研究发现缺乏IgA的患者更容易患自身免疫性疾病和感染性疾病。 目前用于研究IgA结合的主要技术是IgA-SEQ,结合了…...

2025年牛客网秋招/社招高质量 Java 面试八股文整理
Java 面试 不论是校招还是社招都避免不了各种面试。笔试,如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的。关键在于理解企业的需求,明确自己的定位,以及掌握一定的应试技巧。 笔试部分,通常是对基础知识、…...

ADI的BF609双核DSP怎么做开发,我来说一说(五)LAN口测试
作者的话 ADI的双核DSP,第二颗是Blackfin系列的BF609,这颗DSP我用了很久,比较熟悉,且写过一些给新手的教程。 硬件准备 ADSP-BF609-CORE:ADI BF609开发板 产品链接:https://item.taobao.com/item.htm?…...

行业赋能篇-2-能源行业安全运维升级
在能源行业,尤其是风电领域,运维作业往往面临“三高”挑战——高风险环境、高异构数据量)、高合规要求。以海上风电场为例,传统运维依赖卫星电话沟通,数据记录碎片化,故障因信息传递延迟导致损失扩大。如何…...

飞云智能波段主图+多空短线决策副图指标,组合操盘技术图文解说
如上图,组合指标:主图-飞云智能波段,红线上红色K线标记,波段做多.副图指标-多空短线决策,跟踪做短线,红柱做多,绿柱短线卖出或做空。 实战操作中,我们在主图红色线支撑上红色K线出现…...

【51单片机】1. 基础点灯大师
1. 新建一个项目集一些基本操作 打开Keli软件,然后: 【Project】→【new μVision Project】→选择项目保存位置 建议文件名选一些通用的名字,如【Project】 左下角选择【Atmel】的【AT89C52】 弹出的【是否添加启动文件到文件夹下】&…...
RNN和CNN使用场景区别
RNN(循环神经网络)和 CNN(卷积神经网络)是深度学习中两种核心架构,它们的使用场景主要取决于数据结构和任务需求。以下是两者的关键区别及典型应用场景: 核心差异对比 维度RNN(循环神经网络&a…...

PC端直接打印功能(包括两张图片合并功能)
一、 效果图 二、demo代码 <template><div class"box"><divref"printContent"class"print-content"><div class"print-title">打印图片</div><imgclass"preview-image":src"merged…...

Vue前端篇——项目目录结构介绍
📘 前言 在正式开始学习 Vue 3 开发之前,了解并熟悉其项目目录结构是非常关键的第一步。一个清晰、规范的目录结构不仅有助于开发者高效地组织代码,还能显著提升项目的可读性和可维护性。 Vue 3 作为现代前端开发中广泛使用的主流框架之一&…...
基于端到端深度学习模型的语音控制人机交互系统
基于端到端深度学习模型的语音控制人机交互系统 摘要 本文设计并实现了一个基于端到端深度学习模型的人机交互系统,通过语音指令控制其他设备的程序运行,并将程序运行结果通过语音合成方式反馈给用户。系统采用Python语言开发,使用PyTorch框架实现端到端的语音识别(ASR)…...
原生js操作元素类名(classList,classList.add...)
1、classList classList属性是一个只读属性,返回元素的类名,作为一个DOMTokenList集合(用于在元素中添加,移除及切换css类) length:返回类列表中类的数量,该属性是只读的 <style> .lis { width: 200px; …...

抽象工厂模式深度解析:从原理到与应用实战
作者简介 我是摘星,一名全栈开发者,专注 Java后端开发、AI工程化 与 云计算架构 领域,擅长Python技术栈。热衷于探索前沿技术,包括大模型应用、云原生解决方案及自动化工具开发。日常深耕技术实践,乐于分享实战经验与…...

35.成功解决编写关于“江协科技”编写技巧第二期标志位积累的问题
江科大学长又发布了第二期的编写技巧! 大家可以看看:https://space.bilibili.com/383400717 最后面给了一个未完成的任务: 这里我已经把这个问题给解决了! 总代码放在资源里面,key.c放在文章最后面!同时感…...

Linux常用命令学习手册
Linux常用命令学习手册https://download.csdn.net/download/2401_87690752/90953550 《Linux常用命令学习手册》提供了一份实用的Linux操作指南,主要收录了系统管理和文件操作等基础命令。内容涵盖了目录切换、文件查看、权限设置等核心功能,适合Linux初…...

Tailwind CSS 实战:基于 Kooboo 构建 AI 对话框页面(八):异步处理逻辑详解
在现代 Web 应用中,异步处理是实现流畅交互的核心技术。本文基于前几章实现的内容Tailwind CSS 实战:基于 Kooboo 构建 AI 对话框页面(七):消息框交互功能添加-CSDN博客,深入解析 AI 对话框页面中异步逻辑的…...

Unreal从入门到精通之 UE4 vs UE5 VR性能优化实战
文章目录 前言:准备工作UE4 vs UE5 性能对比引擎核心技术方案对比UE5 优化总结项目设置可伸缩性组设置VolumetricCloud最后前言: 最近在使用UE5制作VR项目 制作完后发现,我们的场景一直很卡顿,场景优化也做到了极致,但是帧率最高也才30+ 但是我们看到一个竞品,他的帧率竟…...

COMSOL与MATLAB联合仿真人工智能的电学层析成像系统
关键词:MATLAB,电学层析成像,人工智能,图像重建,深度学习 一、引言 基于人工智能的电学层析成像系统是一种创新的检测技术,结合了电学层析成像技术与人工智能算法的优势。电学层析成像技术,简…...

配置sudo免密却不生效的问题
如图,我配置了dhcp4这个账号sudo免密,但是执行sudo的时候还是要输密码。 查看dhcp的用户组,是配置了一个wheel组,而wheel组配置的是需要密码。 我们用dhcp4用户执行sudo -l 发下他匹配了两条命令策略,一个是免密一个…...

大模型赋能:金融智能革命中的特征工程新纪元
一、AI进化论:从“判别”到“生成”的金融新战场 1.1 判别式AI的“痛点”与大模型的“破局” 想象这样一幅画面:银行风控模型像老式收音机,需要人工反复调试参数才能捕捉风险信号;而大模型则是智能调音台,能自动“听…...
Significant Location Change
一、Significant Location Change是什么 “Significant Location Change(重大位置变化)” 是苹果 iOS 系统中一项用于在应用未主动运行时,监测设备位置显著变化的功能。它主要通过基站、Wi-Fi 网络等信号来判断设备是否发生了有意义的位置移…...
springboot 微服务 根据tomcat maxthread 和 等待用户数量,达到阈值后,通知用户前面还有多少用户等待,请稍后重试
我们在java项目开发中,如何设置服务器最大负载,过了服务器承受范围之后,提示用户稍后重试,避免 服务器无法提供正常服务 如何设置服务器负载比如:最大线程数,等待数量等,请看:springtomcat 用户…...

LHA9924芯片可代替AD7190,CS5530
LHA9924是一款高性能、单芯片模数转换器(ADC)。该器件包括一个低噪声可编程增益放大器(PGA)、Δ-Σ调制器和数字滤波器。该ADC支持两种运行模式,可在功耗与分辨率之间实现最佳平衡。双通道多路复用器可以选择外部信号测量和内部ADC测试信号。具有使输入电路短路来测…...

短视频矩阵系统技术saas源头6年开发构架
在短视频内容爆发式增长的今天,短视频矩阵系统SAAS技术成为企业快速搭建视频平台的关键解决方案。本文将系统解析从技术源头到服务落地的全流程开发路径。 一、系统定义与技术基础 短视频矩阵系统是集视频上传、智能编辑、多端分享、高清播放于一体的综合性平台。其…...
枫之谷Artale端午节大当机----后端技术的巨大风险
枫之谷Artale在端午节活动造成大量玩家上线塞爆,进不去,甚至在最后时段大当机,造成数万玩家怒火。 这体现了后端技术的影响,它不像是前端技术只对少数人造成影响,只要一出事,就是大批的玩家一起面对崩溃的伺…...

前端删除评论操作(局部更新数组)
评论的删除是局部删除,把所点击的评论id号传递给后端,通知后端在数据库中删除数据,并且返回数据,但是在前端并不直接接收返回的数据,而是触发回调事件,在前端上进行删除评论,首先通过pId观察他…...
数学复习笔记 28
前言 刷数学题非常爽啊。让我感觉自己能考一百四,一百五这种错觉。我和一个朋友说,我肯定能考一百四以上,他说他觉得我最多考一百二,笑死,我是这么菜的么。下面是线代第六章的例题的一些理解。我现在觉得考研数学不需…...
Delphi 实现远程连接 Access 数据库的指南
方法一:通过局域网共享 Access 文件(简单但有限) 步骤 1:共享 Access 数据库 将 .mdb 或 .accdb 文件放在局域网内某台电脑的共享文件夹中。 右键文件夹 → 属性 → 共享 → 启用共享并设置权限(需允许网络用户读写&a…...

在线OJ项目测试
一.项目简介 1.1项目背景 历史起源:最早的OJ系统(如UVa、POJ)是为国际大学生程序设计竞赛(ICPC)等赛事开发的,用于自动评判参赛者的代码正确性和效率。 需求场景:竞赛需要公平、高效的评分系统…...