uniapp蓝牙搜索连接展示蓝牙设备包含信号显示

参考界面

注意在manifest.json里在 安卓打包配置里permissions里配置蓝牙模块

javascript 复制代码
					"<uses-permission android:name=\"android.permission.BLUETOOTH\"/>",
					//蓝牙管理权限
					"<uses-permission android:name=\"android.permission.BLUETOOTH_ADMIN\"/>",
					// 蓝牙配对权限
					"<uses-permission android:name=\"android.permission.BLUETOOTH_PRIVILEGED\"/>",
					"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
					"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
					//用于使用蓝牙扫描附件其他的蓝牙设备 Android 12
					"<uses-permission android:name=\"android.permission.BLUETOOTH_SCAN\"/>",
					//用于允许当前的设备被其他的蓝牙设备所发现 Android 12
					"<uses-permission android:name=\"android.permission.BLUETOOTH_ADVERTISE\"/>",
					//用于连接之前已经配对过的蓝牙设备 Android 12
					"<uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\"/>"

以下是参考代码

html 复制代码
<template>
	<view class="content" :class="{ 'landscape': isLandscape }">
	<view class="text-area">
			<text class="title">{{title}}</text>
		</view> 

		<!-- 蓝牙状态显示 -->
		<view class="bluetooth-section">
			<view class="status-card">
				<view class="status-header">
					<text class="status-title">蓝牙连接状态</text>
					<view class="status-indicator" :class="bluetoothStatus">
						{{ getStatusText(bluetoothStatus) }}
					</view>
				</view>

				<view class="device-info" v-if="connectedDevice">
					<text class="info-label">已连接设备:</text>
					<text class="info-value">{{ connectedDevice.name }}</text>
					<text class="info-label">设备ID:</text>
					<text class="info-value">{{ connectedDevice.deviceId }}</text>
				</view>

				<view class="no-device" v-else>
					<text class="no-device-text">未连接任何设备</text>
				</view>
			</view>

			<!-- 操作按钮 -->
			<view class="action-buttons" :class="{ 'landscape-buttons': isLandscape }">
				<button class="btn" @click="initBluetooth" :disabled="loading">
					{{ loading ? '操作中...' : '初始化蓝牙' }}
				</button>

				<button class="btn" @click="startBluetoothDiscovery" :disabled="!bluetoothAvailable">
					搜索设备
				</button>

				<button class="btn btn-secondary" @click="stopBluetoothDiscovery" :disabled="!isDiscovering">
					停止搜索
				</button>
				<!-- 添加刷新按钮 -->
				<button class="btn btn-refresh" @click="refreshDevices" :disabled="loading">
					刷新设备列表
				</button>

				<!-- <button class="btn btn-danger" @click="closeBluetooth" :disabled="!bluetoothAvailable">
			  	关闭蓝牙
			  </button> -->
			</view>

			<!-- 服务信息 -->
			<view class="service-section" v-if="services.length > 0">
				<view class="service-card">
					<text class="section-title">设备服务信息</text>

					<view class="service-info">
						<text class="info-label">服务数量:</text>
						<text class="info-value">{{ services.length }} 个</text>

						<text class="info-label">当前服务:</text>
						<text class="info-value">{{ currentServiceId }}</text>

						<text class="info-label">特征值数量:</text>
						<text class="info-value">{{ characteristics.length }} 个</text>
					</view>
				</view>
			</view>

			<!-- 数据通信 -->
			<view class="communication-section" v-if="deviceConnected">
				<view class="communication-card">
					<text class="section-title">数据通信</text>

					<!-- 发送数据 -->
					<view class="send-section">
						<text class="input-label">发送数据:</text>
						<view class="input-group">
							<input class="data-input" v-model="sendData" placeholder="输入要发送的数据" />
							<button class="btn btn-send" @click="sendDataToDevice">发送</button>
						</view>
					</view>

					<!-- 接收数据 -->
					<view class="receive-section">
						<view class="receive-header">
							<text class="receive-title">接收数据 (最近50条)</text>
							<text class="receive-count">{{ receivedData.length }} 条</text>
						</view>

						<scroll-view class="receive-scroll" scroll-y>
							<view v-for="(item, index) in receivedData" :key="index" class="data-item">
								<text class="data-time">{{ item.timestamp }}</text>
								<text class="data-content">{{ item.data }}</text>
							</view>
						</scroll-view>
					</view>
				</view>
			</view>
			<!-- 设备列表 -->
			<!-- 设备列表 -->
			<view class="devices-list" v-if="devices.length > 0">
				<text class="list-title">可用设备 ({{ devices.length }})</text>
				<view class="sort-info">
					<text class="sort-text">按信号强度排序 ▼</text>
				</view>
				<scroll-view class="devices-scroll" scroll-y>
					<view v-for="device in devices" :key="device.deviceId" class="device-item"
						@click="connectToDevice(device)">
						<view class="device-main">
							<text class="device-name">{{ device.name || '未知设备' }}</text>
							<text class="device-id">{{ device.deviceId }}</text>
						</view>
						<view class="device-rssi">
							<view class="rssi-indicator" :class="getRSSILevel(device.RSSI)"></view>
							<text class="rssi-text">{{ getRSSIDisplay(device.RSSI) }}</text>
						</view>
						<view class="connect-indicator" :class="{ 'connected': device.connected }">
							{{ device.connected ? '已连接' : '点击连接' }}
						</view>
					</view>
				</scroll-view>
			</view>

			<!-- 连接状态提示 -->
			<view class="connection-status" v-if="connectionStatus">
				<text :class="['status-text', connectionStatus.type]">
					{{ connectionStatus.message }}
				</text>
			</view>
		</view>
	</view>
</template>

<script>
export default {
	data() {
		return {
			bluetoothStatus: 'unavailable',
			bluetoothAvailable: false,
			isDiscovering: false,
			devices: [],
			connectedDevice: null,
			loading: false,
			connectionStatus: null,
			isLandscape: false, // 添加横屏状态
			windowHeight: 0,
			windowWidth: 0,
			// 新增蓝牙服务相关数据
			services: [], // 设备服务列表
			characteristics: [], // 特征值列表
			currentServiceId: '', // 当前选择的服务ID
			currentCharacteristicId: '', // 当前选择的特征值ID
			notifyEnabled: false, // 是否开启通知
			deviceConnected: false, // 设备连接状态
			receivedData: [], // 接收到的数据
			sendData: '' // 要发送的数据
		}
	},
	onLoad() {
		this.initBluetooth();
		this.checkBluetoothStatus();
		this.checkScreenOrientation(); // 检查初始屏幕方向
		this.startOrientationListener(); // 开始监听屏幕方向变化
		// 添加权限检查
		this.checkAndRequestPermissions();
	},
	onUnload() {
		// 页面卸载时关闭蓝牙
		this.closeBluetooth();
	},
	methods: {
		// 获取信号强度显示文本
		getRSSIDisplay(rssi) {
			if (rssi === null || rssi === undefined) return '未知';
			return `${rssi} dBm`;
		},

		// 获取信号强度等级
		getRSSILevel(rssi) {
			if (rssi === null || rssi === undefined) return 'unknown';

			if (rssi >= -50) return 'excellent'; // 优秀
			if (rssi >= -60) return 'good'; // 良好
			if (rssi >= -70) return 'fair'; // 一般
			if (rssi >= -80) return 'weak'; // 较弱
			return 'very-weak'; // 很弱
		},

		// 刷新设备列表
		async refreshDevices() {
			this.loading = true;
			try {
				// 清空当前列表
				this.devices = [];

				// 重新获取已配对设备
				await this.getPairedDevices();

				// 重新开始搜索
				if (this.bluetoothAvailable && !this.isDiscovering) {
					await this.startBluetoothDiscovery();
				}

				this.connectionStatus = {
					message: '设备列表已刷新',
					type: 'success'
				};
			} catch (error) {
				console.error('刷新设备列表失败:', error);
			} finally {
				this.loading = false;
			}
		},
		// 检查并请求权限
		async checkAndRequestPermissions() {
			try {
				// 检查位置权限(蓝牙扫描需要)
				const result = await uni.authorize({
					scope: 'scope.location'
				});
				console.log('位置权限授权结果:', result);

				// 对于 Android 12+,还需要蓝牙扫描权限
				// if (uni.getSystemInfoSync().platform === 'android') {
				// 	// 尝试请求蓝牙相关权限
				// 	await this.requestBluetoothPermissions();
				// }

			} catch (error) {
				console.log('权限授权失败或用户拒绝:', error);
				// 可以在这里引导用户去设置页面开启权限
				// this.showPermissionGuide();
			}
		},

		// 请求蓝牙权限
		async requestBluetoothPermissions() {
			return new Promise((resolve, reject) => {
				// 这里可以使用原生插件或自定义方式请求权限
				// 暂时使用提示方式
				uni.showModal({
					title: '蓝牙权限提示',
					content: '应用需要蓝牙权限来扫描和连接设备,请确保已授予相关权限',
					showCancel: false,
					success: resolve,
					fail: reject
				});
			});
		},
		async getConnectedDevices() {
			try {
				const res = await uni.getConnectedBluetoothDevices({
					services: [] // 空数组表示获取所有已连接设备
				});

				console.log('已连接的蓝牙设备:', res.devices);

				if (res.devices && res.devices.length > 0) {
					// 处理已连接的设备
					res.devices.forEach(device => {
						// 更新设备列表中的连接状态
						this.devices = this.devices.map(d => {
							if (d.deviceId === device.deviceId) {
								return {
									...d,
									connected: true
								};
							}
							return d;
						});

						// 设置当前连接设备
						this.connectedDevice = {
							name: device.name,
							deviceId: device.deviceId
						};
					});

					// 排序设备列表
					this.sortDevicesByRSSI();
				} else {
					console.log('没有已连接的设备');
					this.connectedDevice = null;
				}
			} catch (err) {
				console.error('获取已连接设备失败:', err);
				// 如果API不支持,尝试使用其他方法
				this.fallbackGetConnectedDevices();
			}
		},

		// 备选方法:通过设备列表筛选已连接设备
		fallbackGetConnectedDevices() {
			uni.getBluetoothDevices({
				success: (res) => {
					const connectedDevices = res.devices.filter(device => {
						// 这里可以根据实际需求判断设备是否已连接
						// 有些平台可能没有明确的连接状态标识
						return device.connected || this.isDeviceConnected(device.deviceId);
					});

					if (connectedDevices.length > 0) {
						const device = connectedDevices[0];
						this.connectedDevice = {
							name: device.name,
							deviceId: device.deviceId
						};

						// 更新设备列表连接状态
						this.devices = this.devices.map(d => {
							if (d.deviceId === device.deviceId) {
								return {
									...d,
									connected: true
								};
							}
							return d;
						});

						// 排序设备列表
						this.sortDevicesByRSSI();
					}
				}
			});
		},
		// 检查设备是否已连接
		isDeviceConnected(deviceId) {
			// 这里可以添加您的设备连接判断逻辑
			return this.connectedDevice && this.connectedDevice.deviceId === deviceId;
		},

		// 检查屏幕方向
		checkScreenOrientation() {
			const res = uni.getSystemInfoSync();
			this.windowHeight = res.windowHeight;
			this.windowWidth = res.windowWidth;
			this.isLandscape = res.windowWidth > res.windowHeight;
		},

		// 监听屏幕方向变化
		startOrientationListener() {
			uni.onWindowResize((res) => {
				this.windowHeight = res.size.windowHeight;
				this.windowWidth = res.size.windowWidth;
				this.isLandscape = res.size.windowWidth > res.size.windowHeight;
			});
		},

		// 在页面卸载时取消监听
		beforeDestroy() {
			uni.offWindowResize();
		},
		// 检查蓝牙状态
		async checkBluetoothStatus() {
			try {
				const res = await uni.getBluetoothAdapterState();
				console.log('蓝牙适配器状态:', res);
				this.bluetoothStatus = res.available ? 'enabled' : 'disabled';
				this.bluetoothAvailable = res.available;
				this.isDiscovering = res.discovering;
			} catch (error) {
				console.error('获取蓝牙状态失败:', error);
				this.bluetoothStatus = 'unavailable';
				this.bluetoothAvailable = false;
			}
		},

		// 初始化蓝牙
		async initBluetooth() {
			this.loading = true;
			this.connectionStatus = {
				message: '正在初始化蓝牙...',
				type: 'info'
			};

			try {
				// 先检查权限
				await this.checkAndRequestPermissions();

				// 打开蓝牙适配器
				await uni.openBluetoothAdapter();

				this.connectionStatus = {
					message: '蓝牙初始化成功',
					type: 'success'
				};

				this.bluetoothStatus = 'enabled';
				this.bluetoothAvailable = true;

				// 开始监听蓝牙状态变化
				this.startBluetoothListeners();

				// 先获取已连接设备,再开始搜索
				await this.getConnectedDevices();

				// 如果已经有连接设备,就不需要搜索了
				if (!this.connectedDevice) {
					setTimeout(() => {
						this.startBluetoothDiscovery();
					}, 1000);
				}

			} catch (error) {
				console.error('蓝牙初始化失败:', error);
				this.handleBluetoothError(error);

				// 如果是权限问题,提示用户
				if (error.errCode === 10013) {
					this.showPermissionGuide();
				}
			} finally {
				this.loading = false;
			}
		},

		// 开始监听蓝牙事件
		startBluetoothListeners() {
			// 蓝牙适配器状态变化
			uni.onBluetoothAdapterStateChange((res) => {
				console.log('蓝牙适配器状态变化:', res);
				this.bluetoothAvailable = res.available;
				this.isDiscovering = res.discovering;
				this.bluetoothStatus = res.available ? 'enabled' : 'disabled';
				this.getConnectedDevices();
			});

			// 发现新设备 - 增强日志
			uni.onBluetoothDeviceFound((res) => {
				// console.log('发现新设备详情:', res.devices);

				// 过滤设备,包括经典蓝牙设备
				const filteredDevices = res.devices.filter(device => {

					return device.deviceId && (device.name || device.localName);
				});

				// console.log('过滤后的设备:', filteredDevices);
				this.addOrUpdateDevices(filteredDevices);
			});

			// 蓝牙连接状态变化
			uni.onBLEConnectionStateChange((res) => {
				console.log('蓝牙连接状态变化:', res);
				this.handleConnectionStateChange(res);
			});
		},
		// 添加或更新设备列表,并按信号强度排序
		addOrUpdateDevices(newDevices) {
			newDevices.forEach(newDevice => {
				const existingIndex = this.devices.findIndex(d => d.deviceId === newDevice.deviceId);
				if (existingIndex > -1) {
					// 更新现有设备信息,保留连接状态
					this.devices[existingIndex] = {
						...this.devices[existingIndex],
						...newDevice
					};
				} else {
					// 添加新设备
					this.devices.push({
						...newDevice,
						connected: false
					});
				}
			});

			// 按信号强度排序,信号最强的排在最上面
			this.sortDevicesByRSSI();
		},

		// 按信号强度排序设备
		sortDevicesByRSSI() {
			this.devices.sort((a, b) => {
				// 处理 RSSI 值为 null 或 undefined 的情况
				const rssiA = a.RSSI !== null && a.RSSI !== undefined ? a.RSSI : -999;
				const rssiB = b.RSSI !== null && b.RSSI !== undefined ? b.RSSI : -999;

				// RSSI 值越大表示信号越强,所以降序排列
				return rssiB - rssiA;
			});
		},

		// 开始搜索设备
		async startBluetoothDiscovery() {
			if (!this.bluetoothAvailable) {
				this.connectionStatus = {
					message: '蓝牙未初始化或不可用',
					type: 'error'
				};
				return;
			}

			try {
				// 清空设备列表
				this.devices = [];

				this.connectionStatus = {
					message: '正在搜索蓝牙设备...',
					type: 'info'
				};

				// 先获取已配对设备
				await this.getPairedDevices();

				// 开始搜索新设备
				await uni.startBluetoothDevicesDiscovery({
					allowDuplicatesKey: true, // 改为 true 以获取更多设备
					interval: 1000, // 缩短间隔
					powerLevel: 'high' // 高功率扫描
				});

				this.isDiscovering = true;
				this.connectionStatus = {
					message: '正在搜索蓝牙设备...',
					type: 'info'
				};

				// 30秒后自动停止搜索(给更多时间)
				setTimeout(() => {
					this.stopBluetoothDiscovery();
				}, 30000);

			} catch (error) {
				console.error('开始搜索失败:', error);
				this.connectionStatus = {
					message: `搜索失败: ${error.errMsg || error.message}`,
					type: 'error'
				};
			}
		},
		// 获取已配对设备
		async getPairedDevices() {
			return new Promise((resolve) => {
				uni.getBluetoothDevices({
					success: (res) => {
						console.log('已配对设备:', res.devices);
						if (res.devices && res.devices.length > 0) {
							// 过滤出有效的设备
							const validDevices = res.devices.filter(device =>
								device.name && device.deviceId
							);
							this.addOrUpdateDevices(validDevices);
						}
						resolve();
					},
					fail: (err) => {
						console.error('获取已配对设备失败:', err);
						resolve();
					}
				});
			});
		},

		// 停止搜索设备
		async stopBluetoothDiscovery() {
			try {
				await uni.stopBluetoothDevicesDiscovery();
				this.isDiscovering = false;
				this.connectionStatus = {
					message: '已停止搜索设备',
					type: 'info'
				};
			} catch (error) {
				console.error('停止搜索失败:', error);
			}
		},

		// 连接到设备
		async connectToDevice(device) {
			if (device.connected) {
				this.connectionStatus = {
					message: '设备已连接',
					type: 'info'
				};
				return;
			}

			this.loading = true;
			this.connectionStatus = {
				message: `正在连接 ${device.name || '设备'}...`,
				type: 'info'
			};

			try {
				// 创建蓝牙连接
				await uni.createBLEConnection({
					deviceId: device.deviceId,
					timeout: 10000
				});

				this.connectionStatus = {
					message: '连接成功,正在发现服务...',
					type: 'info'
				};

				// 等待一段时间让连接稳定
				await new Promise(resolve => setTimeout(resolve, 500));

				// 获取蓝牙设备服务
				await this.getBLEDeviceServices(device.deviceId);

				this.deviceConnected = true;

				// 更新设备连接状态
				this.updateDeviceConnectionStatus(device.deviceId, true);

			} catch (error) {
				console.error('连接设备失败:', error);
				this.connectionStatus = {
					message: `连接失败: ${error.errMsg || error.message}`,
					type: 'error'
				};
				this.updateDeviceConnectionStatus(device.deviceId, false);
			} finally {
				this.loading = false;
			}
		},
		// 获取蓝牙设备服务
		async getBLEDeviceServices(deviceId) {
			try {
				const res = await uni.getBLEDeviceServices({
					deviceId: deviceId
				});

				console.log('获取设备服务成功:', res.services);
				this.services = res.services;

				if (res.services && res.services.length > 0) {
					// 默认选择第一个服务
					this.currentServiceId = res.services[0].uuid;
					this.connectionStatus = {
						message: `发现 ${res.services.length} 个服务,正在获取特征值...`,
						type: 'info'
					};

					// 获取该服务的特征值
					await this.getBLEDeviceCharacteristics(deviceId, this.currentServiceId);
				} else {
					this.connectionStatus = {
						message: '未发现任何服务',
						type: 'warning'
					};
				}
			} catch (error) {
				console.error('获取设备服务失败:', error);
				this.connectionStatus = {
					message: `获取服务失败: ${error.errMsg || error.message}`,
					type: 'error'
				};
			}
		},
		// 获取蓝牙设备特征值
		async getBLEDeviceCharacteristics(deviceId, serviceId) {
			try {
				const res = await uni.getBLEDeviceCharacteristics({
					deviceId: deviceId,
					serviceId: serviceId
				});

				console.log('获取特征值成功:', res.characteristics);
				this.characteristics = res.characteristics;

				if (res.characteristics && res.characteristics.length > 0) {
					this.connectionStatus = {
						message: `发现 ${res.characteristics.length} 个特征值`,
						type: 'success'
					};

					// 自动寻找可读写的特征值
					await this.setupCharacteristic(deviceId, serviceId, res.characteristics);
				} else {
					this.connectionStatus = {
						message: '未发现任何特征值',
						type: 'warning'
					};
				}
			} catch (error) {
				console.error('获取特征值失败:', error);
				this.connectionStatus = {
					message: `获取特征值失败: ${error.errMsg || error.message}`,
					type: 'error'
				};
			}
		},
		// 设置特征值(自动寻找合适的特征值并开启通知)
		async setupCharacteristic(deviceId, serviceId, characteristics) {
			// 优先寻找支持 notify 和 write 的特征值
			const suitableChar = characteristics.find(char => {
				return char.properties.notify && char.properties.write;
			});

			if (suitableChar) {
				this.currentCharacteristicId = suitableChar.uuid;

				// 开启通知
				await this.enableBLECharacteristicNotification(deviceId, serviceId, suitableChar.uuid);

				this.connectionStatus = {
					message: '已开启数据通知,可以通信了',
					type: 'success'
				};

			} else {
				// 如果没有同时支持 notify 和 write 的特征值,寻找其他可用特征值
				const notifyChar = characteristics.find(char => char.properties.notify);
				const writeChar = characteristics.find(char => char.properties.write);

				if (notifyChar) {
					await this.enableBLECharacteristicNotification(deviceId, serviceId, notifyChar.uuid);
				}

				if (writeChar) {
					this.currentCharacteristicId = writeChar.uuid;
				}

				this.connectionStatus = {
					message: '已配置可用特征值',
					type: 'info'
				};
			}
		},
		// 开启特征值通知
		async enableBLECharacteristicNotification(deviceId, serviceId, characteristicId) {
			try {
				await uni.notifyBLECharacteristicValueChange({
					deviceId: deviceId,
					serviceId: serviceId,
					characteristicId: characteristicId,
					state: true
				});

				this.notifyEnabled = true;
				console.log('开启通知成功');

				// 监听特征值变化
				uni.onBLECharacteristicValueChange((res) => {
					this.onBLECharacteristicValueChange(res);
				});

			} catch (error) {
				console.error('开启通知失败:', error);
				this.connectionStatus = {
					message: `开启通知失败: ${error.errMsg || error.message}`,
					type: 'error'
				};
			}
		},
		// 接收到蓝牙设备数据
		onBLECharacteristicValueChange(res) {
			console.log('接收到蓝牙数据:', res);

			// 将 ArrayBuffer 转换为十六进制字符串
			const hexString = this.ab2hex(res.value);
			const timestamp = new Date().toLocaleTimeString();

			this.receivedData.unshift({
				timestamp: timestamp,
				data: hexString,
				raw: res.value
			});

			// 限制接收数据条数
			if (this.receivedData.length > 50) {
				this.receivedData = this.receivedData.slice(0, 50);
			}

			console.log(`[${timestamp}] 接收数据: ${hexString}`);
		},
		// 向蓝牙设备发送数据
		async sendDataToDevice() {
			if (!this.deviceConnected || !this.currentCharacteristicId || !this.sendData) {
				this.connectionStatus = {
					message: '请先连接设备并输入要发送的数据',
					type: 'warning'
				};
				return;
			}

			try {
				// 将字符串转换为 ArrayBuffer
				const buffer = this.str2ab(this.sendData);

				await uni.writeBLECharacteristicValue({
					deviceId: this.connectedDevice.deviceId,
					serviceId: this.currentServiceId,
					characteristicId: this.currentCharacteristicId,
					value: buffer
				});

				const timestamp = new Date().toLocaleTimeString();
				this.connectionStatus = {
					message: `数据发送成功: ${this.sendData}`,
					type: 'success'
				};

				console.log(`[${timestamp}] 发送数据: ${this.sendData}`);

				// 清空发送数据
				this.sendData = '';

			} catch (error) {
				console.error('发送数据失败:', error);
				this.connectionStatus = {
					message: `发送失败: ${error.errMsg || error.message}`,
					type: 'error'
				};
			}
		},
		// ArrayBuffer 转十六进制字符串
		ab2hex(buffer) {
			const hexArr = Array.prototype.map.call(
				new Uint8Array(buffer),
				bit => ('00' + bit.toString(16)).slice(-2)
			);
			return hexArr.join(' ');
		},

		// 字符串转 ArrayBuffer
		str2ab(str) {
			const buf = new ArrayBuffer(str.length);
			const bufView = new Uint8Array(buf);
			for (let i = 0; i < str.length; i++) {
				bufView[i] = str.charCodeAt(i);
			}
			return buf;
		},

		// 更新设备连接状态
		updateDeviceConnectionStatus(deviceId, connected) {
			this.devices = this.devices.map(device => {
				if (device.deviceId === deviceId) {
					return {
						...device,
						connected
					};
				}
				return device;
			});

			if (connected) {
				const device = this.devices.find(d => d.deviceId === deviceId);
				if (device) {
					this.connectedDevice = {
						...device
					};
				}
			} else {
				this.connectedDevice = null;
			}

			// 更新连接状态后重新排序
			this.sortDevicesByRSSI();
		},
		// 断开设备连接
		async disconnectDevice() {
			if (!this.connectedDevice) return;

			try {
				// 关闭通知
				if (this.notifyEnabled) {
					await uni.notifyBLECharacteristicValueChange({
						deviceId: this.connectedDevice.deviceId,
						serviceId: this.currentServiceId,
						characteristicId: this.currentCharacteristicId,
						state: false
					});
					this.notifyEnabled = false;
				}

				// 断开连接
				await uni.closeBLEConnection({
					deviceId: this.connectedDevice.deviceId
				});

				// 重置状态
				this.updateDeviceConnectionStatus(this.connectedDevice.deviceId, false);
				this.deviceConnected = false;
				this.services = [];
				this.characteristics = [];
				this.receivedData = [];

				this.connectionStatus = {
					message: '设备已断开连接',
					type: 'info'
				};

			} catch (error) {
				console.error('断开连接失败:', error);
			}
		},
		// 关闭蓝牙
		async closeBluetooth() {
			try {
				// 停止搜索
				if (this.isDiscovering) {
					await this.stopBluetoothDiscovery();
				}

				// 断开已连接的设备
				if (this.connectedDevice) {
					await uni.closeBLEConnection({
						deviceId: this.connectedDevice.deviceId
					});
				}

				// 关闭蓝牙适配器
				await uni.closeBluetoothAdapter();

				this.bluetoothStatus = 'disabled';
				this.bluetoothAvailable = false;
				this.devices = [];
				this.connectedDevice = null;
				this.connectionStatus = {
					message: '蓝牙已关闭',
					type: 'info'
				};

			} catch (error) {
				console.error('关闭蓝牙失败:', error);
			}
		},

		// 获取状态文本
		getStatusText(status) {
			const statusMap = {
				unavailable: '不可用',
				available: '可用',
				enabled: '已开启',
				disabled: '已关闭'
			};
			return statusMap[status] || '未知';
		},
		// 处理蓝牙错误
		handleBluetoothError(error) {
			let errorMessage = '蓝牙操作失败';

			if (error.errCode === 10001) {
				errorMessage = '蓝牙适配器不可用,请检查手机蓝牙是否开启';
			} else if (error.errCode === 10002) {
				errorMessage = '没有找到指定设备';
			} else if (error.errCode === 10003) {
				errorMessage = '连接失败,请重试';
			} else if (error.errCode === 10004) {
				errorMessage = '没有找到指定服务';
			} else if (error.errCode === 10005) {
				errorMessage = '没有找到指定特征值';
			} else if (error.errCode === 10006) {
				errorMessage = '当前连接已断开';
			} else if (error.errCode === 10007) {
				errorMessage = '当前特征值不支持此操作';
			} else if (error.errCode === 10008) {
				errorMessage = '其余所有系统上报的异常';
			} else if (error.errCode === 10009) {
				errorMessage = 'Android 系统特有,系统版本低于 4.3 不支持 BLE';
			} else if (error.errCode === 10012) {
				errorMessage = '连接超时';
			} else if (error.errCode === 10013) {
				errorMessage = '未授权位置权限';
			}

			this.connectionStatus = {
				message: errorMessage,
				type: 'error'
			};
		}

	}
}
</script>

<style>
.content {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: flex-start;
	padding: 20rpx;
}

.logo {
	height: 120rpx;
	width: 120rpx;
	margin-top: 40rpx;
	margin-bottom: 30rpx;
}

.text-area {
	display: flex;
	justify-content: center;
	margin-bottom: 40rpx;
}

.title {
	font-size: 36rpx;
	color: #8f8f94;
}

/* 蓝牙相关样式 */
.bluetooth-section {
	width: 100%;
	max-width: 700rpx;
}

.status-card {
	background: white;
	border-radius: 16rpx;
	padding: 30rpx;
	margin-bottom: 30rpx;
	box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
	position: fixed;
	width: 90%;
	z-index: 10000;

}

.status-header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-bottom: 20rpx;

}

.status-title {
	font-size: 32rpx;
	font-weight: bold;
	color: #333;
}

.status-indicator {
	padding: 8rpx 16rpx;
	border-radius: 20rpx;
	font-size: 24rpx;
	color: white;
}

.status-indicator.enabled {
	background-color: #34C759;
}

.status-indicator.disabled {
	background-color: #FF3B30;
}

.status-indicator.unavailable {
	background-color: #FF9500;
}

.status-indicator.available {
	background-color: #007AFF;
}

.device-info {
	margin-top: 20rpx;
}

.info-label {
	display: block;
	font-size: 26rpx;
	color: #666;
	margin-bottom: 8rpx;
}

.info-value {
	display: block;
	font-size: 28rpx;
	color: #333;
	font-weight: 500;
	margin-bottom: 16rpx;
	word-break: break-all;
}

.no-device {
	text-align: center;
	padding: 40rpx 0;
}

.no-device-text {
	font-size: 28rpx;
	color: #999;
}

.action-buttons {
	margin-bottom: 30rpx;
	margin-top: 300rpx;
	text-align: center;
	/* 可选,居中显示按钮 */
}

.btn {
	background-color: #007AFF;
	color: white;
	border: none;
	border-radius: 12rpx;
	padding: 24rpx;
	font-size: 28rpx;
	display: inline-block;
	margin: 10rpx;
	/* 添加间距 */
	min-width: 150rpx;
	/* 控制按钮最小宽度 */
}

.btn:disabled {
	background-color: #cccccc;
}

.btn-secondary {
	background-color: #34C759;
}

.btn-danger {
	background-color: #FF3B30;
}

.devices-list {
	background: white;
	border-radius: 16rpx;
	padding: 30rpx;
	box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}

.list-title {
	font-size: 30rpx;
	font-weight: bold;
	color: #333;
	margin-bottom: 20rpx;
	display: block;
}

.devices-scroll {
	max-height: 500rpx;
}

.device-item {
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 24rpx;
	border-bottom: 1rpx solid #f0f0f0;
	background: #fafafa;
	border-radius: 12rpx;
	margin-bottom: 16rpx;
	transition: all 0.3s ease;
}

.device-item:active {
	background: #f0f0f0;
	transform: scale(0.98);
}

.device-main {
	flex: 1;
	min-width: 0;
	/* 防止内容溢出 */
}

.device-name {
	display: block;
	font-size: 28rpx;
	color: #333;
	font-weight: 500;
	margin-bottom: 8rpx;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.device-id {
	display: block;
	font-size: 22rpx;
	color: #666;
	word-break: break-all;
	font-family: monospace;
}

.device-rssi {
	margin: 0 20rpx;
}

.rssi-text {
	font-size: 24rpx;
	color: #999;
}

.connect-indicator {
	padding: 8rpx 16rpx;
	border-radius: 12rpx;
	font-size: 22rpx;
	color: white;
	background-color: #007AFF;
}

.connect-indicator.connected {
	background-color: #34C759;
}

.connection-status {
	margin-top: 20rpx;
	padding: 20rpx;
	border-radius: 12rpx;
	text-align: center;
}

.status-text {
	font-size: 26rpx;
	font-weight: 500;
}

.status-text.success {
	color: #34C759;
}

.status-text.error {
	color: #FF3B30;
}

.status-text.warning {
	color: #FF9500;
}

.status-text.info {
	color: #007AFF;
}

/* 横屏样式 */
.landscape {
	flex-direction: row;
	flex-wrap: wrap;
}

.landscape .text-area {
	width: 100%;
	margin-bottom: 20rpx;
}

.landscape .bluetooth-section {
	display: flex;
	flex-direction: row;
	flex-wrap: wrap;
	width: 100%;
}

.landscape .status-card {
	flex: 1;
	min-width: 400rpx;
	margin-right: 20rpx;
}

.landscape .action-buttons {
	flex: 1;
	min-width: 300rpx;
	display: flex;
	flex-direction: column;
	justify-content: flex-start;
}

.landscape .action-buttons.landscape-buttons {
	flex-direction: row;
	flex-wrap: wrap;
}

.landscape .action-buttons.landscape-buttons .btn {
	flex: 1;
	min-width: 200rpx;
	margin-right: 10rpx;
	margin-bottom: 10rpx;
}

.landscape .devices-list {
	width: 100%;
	margin-top: 20rpx;
}

/* 平板大屏适配 */
@media screen and (min-width: 768px) {
	.bluetooth-section {
		max-width: 1200rpx;
	}

	.landscape .status-card,
	.landscape .action-buttons {
		min-width: 500rpx;
	}
}

.service-section,
.communication-section {
	width: 100%;
	margin-top: 30rpx;
}

.service-card,
.communication-card {
	background: white;
	border-radius: 16rpx;
	padding: 30rpx;
	margin-bottom: 30rpx;
	box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}

.section-title {
	font-size: 30rpx;
	font-weight: bold;
	color: #333;
	margin-bottom: 20rpx;
	display: block;
}

.send-section {
	margin-bottom: 30rpx;
}

.input-label {
	font-size: 26rpx;
	color: #666;
	margin-bottom: 10rpx;
	display: block;
}

.input-group {
	display: flex;
	gap: 20rpx;
}

.data-input {
	flex: 1;
	border: 1rpx solid #ddd;
	border-radius: 8rpx;
	padding: 20rpx;
	font-size: 26rpx;
}

.btn-send {
	background-color: #34C759;
	min-width: 120rpx;
}

.receive-header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-bottom: 15rpx;
}

.receive-title {
	font-size: 26rpx;
	color: #333;
	font-weight: 500;
}

.receive-count {
	font-size: 24rpx;
	color: #666;
}

.receive-scroll {
	max-height: 400rpx;
	border: 1rpx solid #f0f0f0;
	border-radius: 8rpx;
	padding: 15rpx;
}

.data-item {
	padding: 15rpx 0;
	border-bottom: 1rpx solid #f5f5f5;
}

.data-item:last-child {
	border-bottom: none;
}

.data-time {
	font-size: 22rpx;
	color: #999;
	margin-right: 15rpx;
}

.data-content {
	font-size: 24rpx;
	color: #333;
	word-break: break-all;
}

.sort-info {
	margin-bottom: 15rpx;
	text-align: center;
}

.sort-text {
	font-size: 24rpx;
	color: #007AFF;
	background: rgba(0, 122, 255, 0.1);
	padding: 8rpx 16rpx;
	border-radius: 20rpx;
}

.device-rssi {
	margin: 0 20rpx;
	display: flex;
	flex-direction: column;
	align-items: center;
	min-width: 120rpx;
}

.rssi-indicator {
	width: 60rpx;
	height: 8rpx;
	border-radius: 4rpx;
	margin-bottom: 8rpx;
	transition: all 0.3s ease;
}

.rssi-indicator.excellent {
	width: 80rpx;
	background: linear-gradient(90deg, #34C759, #34C759);
	box-shadow: 0 0 8rpx rgba(52, 199, 89, 0.5);
}

.rssi-indicator.good {
	width: 70rpx;
	background: linear-gradient(90deg, #34C759 70%, #FFD60A 100%);
}

.rssi-indicator.fair {
	width: 60rpx;
	background: linear-gradient(90deg, #FFD60A 50%, #FF9500 100%);
}

.rssi-indicator.weak {
	width: 50rpx;
	background: linear-gradient(90deg, #FF9500 30%, #FF3B30 100%);
}

.rssi-indicator.very-weak {
	width: 40rpx;
	background: #FF3B30;
}

.rssi-indicator.unknown {
	width: 40rpx;
	background: #8E8E93;
}

.rssi-text {
	font-size: 22rpx;
	color: #666;
	text-align: center;
}

.btn-refresh {
	background-color: #FF9500;
}
</style>

需要注意的是如果要实现整正的与蓝牙设备连接通信,手机需要开启定位服务和蓝牙服务,同时app需要申请位置授权,主要针对安卓12版本,下一篇博文我将进入具体实战demo中

相关推荐
mldong5 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排5 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60616 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角7 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!7 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰7 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
Cx330❀8 小时前
【Linux网络】网络层协议 IP :从网络层原理到 Linux 内核源码
linux·运维·服务器·网络·tcp/ip·ai·ai编程
mayaairi9 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen9 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
FPC工厂——皇榜科技10 小时前
机器人灵巧手线路板:从“握得住”到“摸得准”:一只机械手的柔性神经密码
网络·人工智能·科技·机器人·pcb工艺