367 lines
8.2 KiB
Vue
367 lines
8.2 KiB
Vue
<template>
|
||
<view class="container">
|
||
<view class="title-bar">
|
||
<text class="title">连接设备</text>
|
||
<button
|
||
class="scan-btn"
|
||
:disabled="isScanning"
|
||
@click="toggleScan"
|
||
>
|
||
{{ isScanning ? '停止扫描' : '开始扫描' }}
|
||
</button>
|
||
</view>
|
||
<view class="status-tip" v-if="isScanning">
|
||
<text>正在扫描设备...</text>
|
||
<view class="loading"></view>
|
||
</view>
|
||
<view class="status-tip" v-else-if="foundDevices.length === 0">
|
||
<text>未发现设备,请点击"开始扫描"</text>
|
||
</view>
|
||
<view class="device-list">
|
||
<view
|
||
class="device-item"
|
||
v-for="(device, index) in foundDevices"
|
||
:key="device.deviceId"
|
||
@click="connectDevice(device)"
|
||
>
|
||
<!-- 设备名称 -->
|
||
<view class="device-name">
|
||
<text>{{ device.name || '未知设备' }}</text>
|
||
<view class="is_bj" v-if="device.is_bj" >吧唧</view>
|
||
<text class="device-id">{{ device.deviceId }}</text>
|
||
|
||
</view>
|
||
|
||
<!-- 信号强度 -->
|
||
<view class="device-rssi">
|
||
<text>信号: {{ device.rssi }} dBm</text>
|
||
<view class="rssi-bar" :style="{ width: getRssiWidth(device.rssi) }"></view>
|
||
</view>
|
||
|
||
<!-- 连接状态 -->
|
||
<view class="device-status" v-if="device.connected">
|
||
<text class="connected">已连接</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
data() {
|
||
return {
|
||
foundDevices: [],
|
||
isScanning: false,
|
||
bluetoothEnabled: false,
|
||
connectedDeviceId: ''
|
||
};
|
||
},
|
||
|
||
onLoad() {
|
||
// 页面加载时初始化蓝牙
|
||
this.initBluetooth();
|
||
},
|
||
onUnload() {
|
||
// 页面卸载时清理资源
|
||
this.stopScan();
|
||
uni.closeBluetoothAdapter();
|
||
// 移除事件监听
|
||
uni.offBluetoothDeviceFound(this.onDeviceFound);
|
||
},
|
||
|
||
methods: {
|
||
// 初始化蓝牙适配器
|
||
initBluetooth() {
|
||
uni.openBluetoothAdapter({
|
||
success: () => {
|
||
this.bluetoothEnabled = true;
|
||
// uni.showToast({ title: '蓝牙初始化成功', icon: 'none' });
|
||
},
|
||
fail: (err) => {
|
||
this.bluetoothEnabled = false;
|
||
uni.showModal({
|
||
title: '蓝牙开启失败',
|
||
content: '请检查设备蓝牙是否开启',
|
||
showCancel: false
|
||
});
|
||
console.error('蓝牙初始化失败:', err);
|
||
}
|
||
});
|
||
},
|
||
|
||
// 切换扫描状态(开始/停止)
|
||
toggleScan() {
|
||
if (!this.bluetoothEnabled) {
|
||
this.initBluetooth();
|
||
return;
|
||
}
|
||
|
||
if (this.isScanning) {
|
||
this.stopScan();
|
||
} else {
|
||
this.startScan();
|
||
}
|
||
},
|
||
|
||
// 开始扫描设备
|
||
startScan() {
|
||
this.isScanning = true;
|
||
this.foundDevices = []; // 清空历史列表
|
||
|
||
// 开始扫描(空services表示扫描所有设备)
|
||
uni.startBluetoothDevicesDiscovery({
|
||
services: [],
|
||
allowDuplicatesKey: false, // 不允许重复上报
|
||
success: () => {
|
||
uni.showToast({ title: '开始扫描', icon: 'none' });
|
||
// 注册设备发现事件
|
||
uni.onBluetoothDeviceFound(this.onDeviceFound);
|
||
},
|
||
fail: (err) => {
|
||
this.isScanning = false;
|
||
uni.showToast({ title: '扫描失败', icon: 'none' });
|
||
console.error('扫描失败:', err);
|
||
}
|
||
});
|
||
|
||
// 10秒后自动停止扫描(避免耗电)
|
||
setTimeout(() => {
|
||
if (this.isScanning) this.stopScan();
|
||
}, 5000);
|
||
},
|
||
|
||
// 停止扫描
|
||
stopScan() {
|
||
if (!this.isScanning) return;
|
||
|
||
uni.stopBluetoothDevicesDiscovery({
|
||
success: () => {
|
||
this.isScanning = false;
|
||
if(this.foundDevices.length == 0){
|
||
uni.showToast({
|
||
title: `暂未扫描到任何设备`,
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
uni.showToast({
|
||
title: `扫描完成,发现${this.foundDevices.length}台设备`,
|
||
icon: 'none'
|
||
});
|
||
},
|
||
fail: (err) => {
|
||
console.error('停止扫描失败:', err);
|
||
}
|
||
});
|
||
},
|
||
|
||
// 设备发现回调(处理去重和数据格式化)
|
||
onDeviceFound(res) {
|
||
const devices = res.devices || [];
|
||
devices.forEach(device => {
|
||
var that = this
|
||
if (!device.deviceId) return;
|
||
const isExist = this.foundDevices.some(
|
||
d => d.deviceId === device.deviceId
|
||
);
|
||
if (isExist) return;
|
||
var is_bj = false;
|
||
const advData = new Uint8Array(device.advertisData);
|
||
// const companyidData = (advData[0]<<8)| advData[1];
|
||
const devicenameData = advData.slice(2, 6);
|
||
const devicename = String.fromCharCode(...devicenameData)
|
||
if(devicename == 'dzbj') is_bj = true;
|
||
this.foundDevices.push({
|
||
is_bj: is_bj,
|
||
name: device.name || '未知设备',
|
||
deviceId: device.deviceId,
|
||
rssi: device.RSSI,
|
||
connected: device.deviceId === this.connectedDeviceId
|
||
});
|
||
});
|
||
},
|
||
|
||
// 计算信号强度条宽度(-100dBm为0%,-30dBm为100%)
|
||
getRssiWidth(rssi) {
|
||
const minRssi = -100;
|
||
const maxRssi = -30;
|
||
let ratio = (rssi - minRssi) / (maxRssi - minRssi);
|
||
ratio = Math.max(0, Math.min(1, ratio)); // 限制在0-1之间
|
||
return `${ratio * 100}%`;
|
||
},
|
||
|
||
connectDevice(device) {
|
||
var that = this
|
||
this.stopScan();
|
||
uni.showLoading({ title: '连接中...' });
|
||
uni.createBLEConnection({
|
||
deviceId:device.deviceId,
|
||
success(res) {
|
||
that.foundDevices = that.foundDevices.map(d => ({
|
||
...d,
|
||
connected: d.deviceId === device.deviceId
|
||
}));
|
||
uni.hideLoading();
|
||
uni.showToast({ title: `已连接${device.name}`, icon: 'none' });
|
||
uni.navigateTo({
|
||
url:'../connect/connect?deviceId='+device.deviceId,
|
||
fail: (err) => {
|
||
uni.showToast({ title: `连接失败,请稍后重试`, icon: 'error' });
|
||
uni.closeBLEConnection({
|
||
deviceId:device.deviceId,
|
||
success() {
|
||
console('断开连接成功')
|
||
}
|
||
})
|
||
}
|
||
})
|
||
},
|
||
fail() {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: `连接失败`, icon: 'error' });
|
||
}
|
||
})
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.container {
|
||
padding: 16rpx;
|
||
background-color: #f5f5f5;
|
||
/* min-height: 100vh; */
|
||
}
|
||
|
||
/* 标题栏 */
|
||
.title-bar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 20rpx 0;
|
||
border-bottom: 1px solid #eee;
|
||
margin-bottom: 20rpx;
|
||
margin-top: 80rpx;
|
||
|
||
}
|
||
|
||
.title {
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.scan-btn {
|
||
background-color: #00c900;
|
||
color: white;
|
||
padding: 0rpx 24rpx;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
margin-right: 5%;
|
||
}
|
||
|
||
.scan-btn:disabled {
|
||
background-color: #ccc;
|
||
}
|
||
|
||
/* 状态提示 */
|
||
.status-tip {
|
||
text-align: center;
|
||
padding: 40rpx 0;
|
||
color: #666;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.loading {
|
||
width: 30rpx;
|
||
height: 30rpx;
|
||
border: 3rpx solid #007aff;
|
||
border-top-color: transparent;
|
||
border-radius: 50%;
|
||
margin: 20rpx auto;
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
/* 设备列表 */
|
||
.device-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.device-item {
|
||
background-color: white;
|
||
border-radius: 12rpx;
|
||
padding: 24rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.device-name {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.device-name text:first-child {
|
||
font-size: 32rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.is_bj{
|
||
padding: 3px 8px;
|
||
border-radius: 5px;
|
||
color: white;
|
||
background-color: rgba(30, 228, 156, 0.4);
|
||
position: relative;
|
||
right: 30px;
|
||
}
|
||
|
||
.device-id {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.device-rssi {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.device-rssi text {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.rssi-bar {
|
||
height: 8rpx;
|
||
background-color: #eee;
|
||
border-radius: 4rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.rssi-bar::after {
|
||
content: '';
|
||
display: block;
|
||
height: 100%;
|
||
background: linear-gradient(to right, #ff4d4f, #faad14, #52c41a);
|
||
}
|
||
|
||
.device-status {
|
||
margin-top: 16rpx;
|
||
text-align: right;
|
||
}
|
||
|
||
.connected {
|
||
font-size: 26rpx;
|
||
color: #07c160;
|
||
background-color: #f0fff4;
|
||
padding: 4rpx 16rpx;
|
||
border-radius: 12rpx;
|
||
}
|
||
</style> |