更新记录

1.0.2(2026-06-25) 下载此版本

新增支持vue2

1.0.1(2026-06-25) 下载此版本

1.0.1(2026-06-24)

  • 支持低功耗蓝牙扫描、连接、发送、接收、断开
  • 单例模式,跨页面自动保持连接状态
  • 提供事件总线(on / off / once)
  • 同时导出 Vue 3 组合式 API useBluetooth

平台兼容性

uni-app(5.0)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
× × - -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - -

uni-app x(5.0)

Chrome Safari Android iOS 鸿蒙 微信小程序
- - - - - -

其他

多语言 暗黑模式 宽屏模式

q-bluetooth

基于 uni-app 官方蓝牙 API 封装的低功耗蓝牙(BLE)插件,单例模式,跨页面保持连接

特性

  • ✅ 扫描附近 BLE 设备 / 实时去重列表
  • ✅ 连接设备、自动发现服务与特征
  • ✅ 发送数据(ArrayBuffer / hex 字符串)
  • ✅ 接收数据(自动开启 notify / indicate)
  • ✅ 断开 / 关闭适配器
  • ✅ 全局单例 + 事件总线,任意页面 import 拿到的是同一份连接
  • ✅ 自带 Vue 3 组合式 API useBluetooth
  • ✅ 全平台:App、微信 / 支付宝 / 百度 / 字节 / QQ 小程序

安装

  1. 把整个 uni_modules/q-bluetooth 目录放进项目根目录的 uni_modules/ 下,无需在 main.js 引入
  2. App 端需在 manifest.json → App模块配置 → 蓝牙 勾选;小程序需在小程序后台配置蓝牙能力。

快速上手

// 任意页面
import qBluetooth from '@/uni_modules/q-bluetooth'

// 1. 初始化蓝牙适配器
await qBluetooth.init()

// 2. 监听扫描结果(增量去重列表)
qBluetooth.on('deviceFound', (devices) => {
  console.log('附近设备:', devices)
})

// 3. 开始扫描
await qBluetooth.startScan()

// 4. 连接(自动发现服务、自动开启 notify)
await qBluetooth.connect(deviceId)

// 5. 监听接收的数据
qBluetooth.on('receive', ({ value, hex }) => {
  console.log('收到:', hex)
})

// 6. 发送数据(支持 hex 字符串 / ArrayBuffer / Uint8Array)
await qBluetooth.send('A1B2C3D4')

// 7. 断开
await qBluetooth.disconnect()

跨页面使用

由于本插件采用 ES Module 单例 + 系统级蓝牙连接,因此:

  • 在 A 页面 connect() 成功后,B 页面 import 进来直接 send() 即可,无需再次 connect。
  • 通过 qBluetooth.state 可读取当前是否已连接、当前 deviceId 等信息。
  • 事件监听同样跨页面共享,记得在 onUnloadoff() 掉本页面注册的回调即可。
// B 页面
import qBluetooth from '@/uni_modules/q-bluetooth'

onShow(() => {
  if (qBluetooth.state.connected) {
    qBluetooth.send('B1B2')
  }
})

Vue2 使用方式

Vue2 项目请使用默认导出的 qBluetooth 单例 API。核心蓝牙能力、跨页面连接保持、事件监听都支持 Vue2。

<template>
  <view>
    <view>状态:{{ connected ? '已连接' : '未连接' }}</view>
    <view v-for="d in devices" :key="d.deviceId" @tap="connect(d.deviceId)">
      {{ d.name || '(未命名)' }} - 信号强度 {{ d.signalStrength }}
    </view>
  </view>
</template>

<script>
import qBluetooth from '@/uni_modules/q-bluetooth'

export default {
  data() {
    return {
      connected: false,
      devices: [],
    }
  },
  onLoad() {
    this.onDeviceFound = (devices) => {
      this.devices = devices
    }
    this.onConnectionChange = () => {
      this.connected = qBluetooth.state.connected
    }

    qBluetooth.on('deviceFound', this.onDeviceFound)
    qBluetooth.on('connectionChange', this.onConnectionChange)
  },
  onUnload() {
    qBluetooth.off('deviceFound', this.onDeviceFound)
    qBluetooth.off('connectionChange', this.onConnectionChange)
  },
  methods: {
    async scan() {
      await qBluetooth.init()
      await qBluetooth.startScan()
    },
    async connect(deviceId) {
      await qBluetooth.connect(deviceId)
      this.connected = qBluetooth.state.connected
    },
    async send() {
      await qBluetooth.send('A1B2C3D4')
    },
  },
}
</script>

Vue 3 组合式 API

<script setup>
import { useBluetooth } from '@/uni_modules/q-bluetooth'

const { state, devices, init, startScan, stopScan, connect, send, disconnect } = useBluetooth()
</script>

<template>
  <view>状态:{{ state.connected ? '已连接' : '未连接' }}</view>
  <view v-for="d in devices" :key="d.deviceId" @tap="connect(d.deviceId)">{{ d.name }}</view>
</template>

API

实例属性

属性 类型 说明
state.adapterReady boolean 蓝牙适配器是否就绪
state.scanning boolean 是否扫描中
state.connected boolean 是否已连接
state.deviceId string 当前连接的 deviceId
state.serviceId string 当前使用的 serviceId
state.writeCharId string 当前用于写入的特征 UUID
state.notifyCharId string 当前用于通知的特征 UUID
state.RSSI number | null 当前连接设备的原始 RSSI,单位 dBm
state.signalStrength number | null 当前连接设备的信号强度,范围 10 ~ 99

方法

方法 说明
init() 打开蓝牙适配器(重复调用安全)
startScan(options?) 开始扫描;options 同 uni.startBluetoothDevicesDiscovery
stopScan() 停止扫描
getDevices() 获取当前发现的设备列表
connect(deviceId, options?) 连接设备,自动发现服务并开启 notify
disconnect() 断开当前连接
send(data, options?) 发送数据,data 支持 hex 字符串 / ArrayBuffer / Uint8Array / number[]
refreshSignal() 刷新当前连接设备的 RSSI / signalStrength;平台不支持实时 RSSI 时返回连接前最后一次扫描值
destroy() 关闭蓝牙适配器,清理所有状态与监听
on(event, handler) 监听事件
once(event, handler) 监听一次
off(event, handler?) 取消监听

事件

事件 回调参数 说明
adapterStateChange { available, discovering } 适配器状态变化
deviceFound devices[] 发现新设备(已合并去重)
connectionChange { deviceId, connected } 连接状态变化
signalChange { deviceId, RSSI, signalStrength } 当前连接设备信号强度变化
receive { deviceId, serviceId, characteristicId, value, hex } 收到通知数据
error { scene, errMsg, errCode } 任意阶段的错误

devices[]

devices[] 是扫描到的附近蓝牙设备列表,来源于 deviceFound 事件和 getDevices() 方法。插件会按 deviceId 自动去重,并合并同一设备后续扫描到的新信息。

属性 类型 说明
deviceId string 蓝牙设备 ID,连接时传给 connect(deviceId) 的值
name string 设备名称;插件会优先使用 name,没有时使用 localName
localName string 广播包中的本地名称,部分设备只有该字段
RSSI number 原始蓝牙信号强度,单位 dBm,通常是负数,例如 -40 强、-90
signalStrength number 插件根据 RSSI 换算后的信号强度,范围 10 ~ 99,数值越大信号越强
advertisData ArrayBuffer 广播数据,不同平台和设备返回内容不同
advertisServiceUUIDs string[] 广播中的服务 UUID 列表
serviceData object 广播中的服务数据

示例:

qBluetooth.on('deviceFound', (devices) => {
  devices.forEach((device) => {
    console.log(device.deviceId)
    console.log(device.name)
    console.log(device.RSSI)
    console.log(device.signalStrength)
  })
})

注意事项

  • Android 扫描需要定位权限,App 端请确保 manifest 勾选 ACCESS_FINE_LOCATION
  • 小程序需用户已开启系统蓝牙、定位。
  • 默认会自动挑选一组 notify/indicatewrite 特征;如需指定,向 connect(deviceId, { serviceId, writeCharId, notifyCharId }) 传入即可。

使用案例

import { ref } from 'vue' import { useBluetooth } from '@/uni_modules/q-bluetooth' const { state, devices, init, startScan, stopScan, connect, send, disconnect, refreshSignal, on } = useBluetooth() const hex = ref('A1B2C3D4') const logs = ref([]) const log = (s) => { logs.value.unshift(`[${new Date().toLocaleTimeString()}] ${s}`); if (logs.value.length > 30) logs.value.pop() } on('receive', ({ hex }) => log('📥 收到 ' + hex)) on('error', (e) => log('❌ ' + e.scene + ': ' + e.errMsg)) async function onScan() { try { await init() await startScan() log('开始扫描') } catch (e) { log('扫描失败:' + (e.errMsg || e.message)) } } async function onStopScan() { try { await stopScan() log('停止扫描') } catch (e) { log('停止扫描失败:' + (e.errMsg || e.message)) } } async function onConnect(d) { try { log('连接中:' + d.deviceId) await connect(d.deviceId) log('✅ 连接成功') } catch (e) { log('连接失败:' + (e.errMsg || e.message)) } } async function onSend() { try { const res = await send(hex.value) log('📤 已发送 ' + res.bytes + 'B / ' + res.chunks + '包') } catch (e) { log('发送失败:' + (e.errMsg || e.message)) } } async function onRefreshSignal() { try { const res = await refreshSignal() log('当前信号强度:' + (res.signalStrength || '--')) } catch (e) { log('刷新信号失败:' + (e.errMsg || e.message)) } } function goB() { uni.navigateTo({ url: '/pages/b/b' }) } <style> .page { padding: 20rpx; } .title { font-size: 36rpx; font-weight: bold; margin-bottom: 16rpx; } .status { font-size: 24rpx; color: #555; margin-bottom: 16rpx; } .row { display: flex; gap: 12rpx; margin-bottom: 16rpx; flex-wrap: wrap; } .input { flex: 1; border: 1rpx solid #ddd; padding: 8rpx 16rpx; border-radius: 8rpx; } .logs { background: #111; color: #0f0; font-size: 22rpx; padding: 16rpx; border-radius: 8rpx; min-height: 300rpx; margin-bottom: 16rpx; } .log { line-height: 1.5; } .device { padding: 16rpx; border-bottom: 1rpx solid #eee; } .name { font-size: 28rpx; } .sub { font-size: 22rpx; color: #888; } </style>

隐私、权限声明

1. 本插件需要申请的系统权限列表:

插件仅在调用蓝牙能力时使用蓝牙、定位(Android 扫描必需)权限

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

暂无用户评论。