Files
APP/pages/common/addBLE/addEquip.vue
2025-11-14 17:28:20 +08:00

957 lines
19 KiB
Vue

<template>
<view class="content">
<view class="topAnimate">
<view class="animate center">
<view class="animateContent">
<view class="circle"></view>
<view class="circle"></view>
<view class="circle"></view>
</view>
<view class="imgContent center">
<view class="img center">
<image src="/static/images/BLEAdd/bluetooth.png" class="titleIco" mode="aspectFit">
</image>
</view>
</view>
</view>
</view>
<view class="mainContent">
<view class="p100">
<view class="lblTitle">配对设备</view>
<view class="list" style="margin-bottom: 30rpx;">
<view class="item " v-for="item, index in PairEquip" v-show="PairEquip.length>0">
<view class="leftImg ">
<image src="/static/images/common/bluetooth.png" class="titleIco filterNone"
mode="heightFix">
</image>
</view>
<view class="centertxt ">
<view class="name" v-text="item.name"></view>
<view class="id">
<text>信号:{{110+item.RSSI>100?100:110+item.RSSI}}%</text>
</view>
</view>
<view class="rightIco center">
<image src="/static/images/BLEAdd/linked.png" class="img" mode="aspectFit">
</image>
</view>
</view>
<view v-show="PairEquip.length==0" class="item center">
<view class="noLink">无已配对设备</view>
</view>
</view>
<view class="lblTitle">
<text>搜索设备</text>
<view @click="refreshBleList()">刷新</view>
</view>
<view class="list searchList">
<view class="item" v-on:click="Link(item,index)" v-for="item, index in EquipMents"
v-show="!item['linkStatu']">
<view class="before" v-if="item.isTarget"></view>
<view class="leftImg ">
<image src="/static/images/BLEAdd/bluetooth.png" class="titleIco" mode="heightFix">
</image>
</view>
<view class="centertxt ">
<view class="name">
<text>{{item.name?item.name:'Unnamed'}}</text>
</view>
<view class="id">
<text>信号:{{110+item.RSSI>100?100:110+item.RSSI}}%</text>
</view>
</view>
<view class="rightIco center">
<image :src="isItemLink(item,index)" class="img" mode="aspectFit">
</image>
</view>
</view>
</view>
</view>
</view>
<BottomSlideMenuPlus :config="Status.BottomMenu">
<view class="openBlue">
<view class="txt">
当前手机蓝牙关闭,是否打开以扫描附近的蓝牙设备?
</view>
<view class="btns">
<view class="btn cancel" @click="Status.BottomMenu.show=false">
<view>取消</view>
</view>
<view class="ok btn" @click="gotoSetting">
<view>开启</view>
</view>
</view>
</view>
</BottomSlideMenuPlus>
<global-loading ref="loading" />
</view>
</template>
<script>
import bleTool from '@/utils/BleHelper.js';
import request from '@/utils/request.js';
import {
showLoading,
hideLoading,
updateLoading
} from '@/utils/loading.js'
const pagePath = "pages/common/addBLE/addEquip";
var ble = null;
var these = null;
var eventChannel = null;
export default {
data() {
return {
Status: {
isPageHidden: false,
intval: null,
time: null,
BottomMenu: {
show: false,
showHeader: false,
menuItems: [],
activeIndex: -1,
bgColor: '#2a2a2a',
itemBgColor: '#3a3a3a',
textColor: '#ffffffde',
textAlign: 'flex-start',
title: '主灯模式',
showDivider: false,
dividerColor: '#00000000',
dividerThickness: '0rpx',
dividerMargin: '10rpx',
itemHeight: '80rpx',
type: '',
showBtn: false,
btnBgColor: "#bbe600",
btnText: "确定",
btnTextColor: "#232323de",
showMask: true,
maskBgColor: '#00000066',
showClose: false
}
},
PairEquip: [], //已配对设备
EquipMents: [], //搜索出来的设备
device: null,
item: {
deviceId: ''
}
}
},
computed: {
},
onHide: function() {
this.Status.isPageHidden = true;
ble.StopSearch();
},
onUnload() {
ble.StopSearch();
ble.removeAllCallback(pagePath);
},
onLoad(option) {
let search = option.search;
these = this;
eventChannel = this.getOpenerEventChannel();
const systemInfo = uni.getSystemInfoSync();
if (systemInfo.uniPlatform == 'web') {
this.EquipMents = [{
"RSSI": -55,
"advertisData": "",
"advertisServiceUUIDs": [
"0000FFE0-0000-1000-8000-00805F9B34FB"
],
"deviceId": "EBDA4E6F-3A28-FF65-A845-AE8CC7B78375",
"name": "HBY670-BF74EA",
"linkStatu": false
},
{
"RSSI": -61,
"advertisData": "",
"advertisServiceUUIDs": [
"0000FFE0-0000-1000-8000-00805F9B34FB"
],
"deviceId": "469FB381-B47E-1E40-8073-EF50B5704AAB",
"name": "EF4651",
"linkStatu": false,
"isTarget": true
},
{
"RSSI": -69,
"advertisData": "",
"advertisServiceUUIDs": [
"0000FFE0-0000-1000-8000-00805F9B34FB"
],
"deviceId": "4F0DAC91-4391-CB07-905E-72D7F03EFCD3",
"name": "4877-BF743D",
"linkStatu": false
}
];
these.PairEquip = [this.EquipMents[0]];
return;
}
let StartSubsrib = () => {
these.EquipMents = [];
ble = bleTool.getBleTool();
//蓝牙不可用的回调
ble.addStateBreakCallback(res => {
if (these.Status.isPageHidden) {
return;
}
console.log("处理蓝牙不可用");
hideLoading(these);
these.PairEquip = [];
these.EquipMents = [];
uni.showToast({
icon: 'fail',
title: '蓝牙已不可用'
});
these.showOpenSetting();
}, pagePath);
//蓝牙再次可用的回调
ble.addStateRecoveryCallback(res => {
if (these.Status.isPageHidden) {
return;
}
uni.showToast({
icon: 'success',
title: '蓝牙恢复可用'
});
these.Status.BottomMenu.show = false;
these.EquipMents = [];
these.refreshBleList();
}, pagePath);
//蓝牙断开连接的回调
ble.addDisposeCallback(res => {
if (these.Status.isPageHidden) {
return;
}
console.log("处理蓝牙断开连接");
hideLoading(these);
these.PairEquip.find(function(v, ind) {
these.PairEquip.splice(ind, 1);
return v.deviceId == res.deviceId;
});
}, pagePath);
//蓝牙连接已恢复的回调
ble.addRecoveryCallback(res => {
if (these.Status.isPageHidden) {
return;
}
// hideLoading(these);
these.EquipMents.find(function(v, ind) {
if (v.deviceId == res.deviceId) {
these.PairEquip.push(v);
return true;
}
return false;
});
if (these.device) {
clearInterval(this.Status.intval);
showLoading(these, {
text: '蓝牙连接已恢复,正在验证设备'
});
setTimeout(() => {
these.DeviceVerdict(res.deviceId);
}, 0);
} else {
hideLoading(these);
}
}, pagePath);
//搜索到新设备的回调
ble.addDeviceFound((arr) => {
if (these.Status.isPageHidden) {
return;
}
arr = arr.devices;
for (var i = 0; i < arr.length; i++) {
arr[i].linkStatu = false;
if (!arr[i].name) {
continue;
}
let f = these.EquipMents.find((v, index) => {
if (v.deviceId == arr[i].deviceId) {
these.$set(these.EquipMents[index], 'RSSI', arr[i].RSSI);
return true;
}
return false;
});
if (!f) {
console.log("发现新设备,", arr[i]);
if (these.device && these.device.bluetoothName) {
if (these.device.bluetoothName === arr[i].name || arr[i].name.indexOf(these
.device.bluetoothName) > -1 || these.device.bluetoothName.indexOf(arr[
i].name) > -1) {
arr[i].isTarget = true;
}
}
arr[i].name = arr[i].name.replace('JQZM-', '');
these.EquipMents.push(arr[i]);
}
}
console.log("EquipMents=", these.EquipMents)
}, pagePath);
//收到设备的消息回调
ble.addReceiveCallback((receivData, f, path, arr) => {
if (these.Status.isPageHidden) {
return;
}
if (f.macAddress && these.device) {
clearInterval(this.Status.intval);
this.Status.intval = null;
this.Status.time = null;
showLoading(these, {
text: '正在验证设备'
});
setTimeout(() => {
these.DeviceVerdict(f.deviceId);
}, 0);
}
}, pagePath);
}
if (search) {
StartSubsrib();
}
eventChannel.on('detailData', function(rec) {
console.log("接收到父页面的参数:", rec);
these.device = rec.data;
StartSubsrib();
these.refreshBleList();
});
},
onShow: function() {
this.Status.isPageHidden = false;
this.refreshBleList();
},
methods: {
refreshBleList() {
if (!ble) {
return;
}
let promis = [];
for (let index = 0; index < this.PairEquip.length; index++) {
let item = this.PairEquip[index];
promis.push(ble.disconnectDevice(item.deviceId));
}
Promise.allSettled(promis).finally(() => {
ble.StopSearch().finally(res => {
console.log("停止搜索成功");
these.EquipMents = [];
these.PairEquip = [];
ble.StartSearch().then(result => {
console.log("开始搜索成功");
}).catch(err => {
console.error("开始搜索失败,err=", err);
if (err.code === 10001) {
these.showOpenSetting();
} else {
uni.showModal({
title: '提示',
content: '出现异常:' + err.msg
});
}
});
}).catch(ex => {
console.error("ex=", ex);
});
});
},
isItemLink: function(item, index) {
let src = '/static/images/BLEAdd/noLink.png';
if (this.PairEquip && this.PairEquip.length) {
if (this.PairEquip.length > 0) {
let f = this.PairEquip.find(function(v) {
return v.deviceId == item.deviceId;
});
if (f) {
src = '/static/images/BLEAdd/linked.png';
}
}
}
return src;
},
showOpenSetting: function() {
this.Status.BottomMenu.show = true;
},
gotoSetting: function() {
this.Status.BottomMenu.show = false;
ble.showBlueSetting(false);
},
DeviceVerdict(deviceId) { //判断是否是目标设备
console.log("deviceid=", deviceId);
console.log("these.device=", these.device)
if (these.device) { //从设备详情过来的,回设备详情去
let f = ble.data.LinkedList.find(v => {
if (v.deviceId == deviceId) {
v.device = these.device;
return true;
}
return false;
});
let removeLink = () => {
ble.subScribe(deviceId, false); //取消订阅消息
ble.DropDevice(deviceId, null); //从缓存中删除该设备并断开连接
these.device.deviceMac = "";
let index = these.PairEquip.findIndex(function(v) {
return v.deviceId == deviceId;
});
if (index > -1) {
this.PairEquip.splice(index, 1);
}
}
console.log("f=", f);
if (f.macAddress) {
if (f.macAddress != these.device.deviceMac) {
removeLink();
updateLoading(these, {
text: "设备Mac地址错误,请重选设备连接"
});
setTimeout(() => {
hideLoading(these);
}, 1000)
return;
} else {
}
hideLoading(these);
eventChannel.emit('BindOver', these.device);
ble.updateCache();
uni.navigateBack();
return true;
} else {
this.Status.time = 30;
showLoading(these, {
text: "等待设备上报Mac地址," + these.Status.time + 's'
});
console.log("11111111", this.Status.time);
clearInterval(this.Status.intval);
this.Status.intval = null;
this.Status.intval = setInterval(() => {
this.Status.time = this.Status.time - 1;
if (this.Status.time < 0) {
console.log("停止倒计时", this.Status.time);
clearInterval(this.Status.intval)
this.Status.intval = null;
this.Status.time = null;
f = ble.data.LinkedList.find(v => {
if (v.deviceId == deviceId) {
v.device = these.device;
return true;
}
return false;
});
if (!f.macAddress) {
removeLink();
updateLoading(these, {
text: "出现错误,未收到设备Mac地址"
});
setTimeout(() => {
hideLoading(these)
}, 1500);
}
return;
}
updateLoading(these, {
text: "等待设备上报Mac地址," + these.Status.time + 's'
});
}, 1000);
return undefined;
}
}
return false;
},
Link: function(item) {
this.item.deviceId = item.deviceId;
showLoading(this, {
text: "正在连接:第1次"
});
let index = 1;
let total = 5;
let linkCallback = (res) => {
let c = these.PairEquip.find(function(v) {
return v.deviceId == item.deviceId;
});
if (!c) {
these.PairEquip.push(item);
}
console.log("连接成功", these.device);
if (!these.device) {
console.log("跳转到绑定")
hideLoading(these);
uni.navigateTo({
url: "/pages/common/addBLE/LinkBle",
events: {
},
success(res) {
res.eventChannel.emit('LinkItem', item);
}
});
return;
}
console.log("验证设备")
these.DeviceVerdict(item.deviceId);
}
let execLink = () => {
return new Promise((resolve, reject) => {
if (index > total) {
reject({
msg: "连接超时"
});
return;
}
ble.LinkBlue(item.deviceId).then((res) => {
console.log("连接成功");
ble.StopSearch();
resolve(res);
}).catch((ex) => {
if (index == total) {
console.log("连接了N次都没连上");
reject(ex);
return;
}
index++;
updateLoading(this, {
text: ex.msg + ",正在重试第" + index + "次"
})
execLink().then(resolve).catch(reject);
})
});
}
execLink().then((res) => {
console.log("res=", res);
linkCallback(res);
}).catch(ex => {
console.log("ex=", ex)
uni.showModal({
content: "连接失败:" + ex.msg
});
hideLoading(these);
});
}
}
}
</script>
<style>
.noLink {
text-align: center;
width: 100%;
font-size: 28rpx;
}
.content {
background-color: #121212;
color: #ffffffde;
box-sizing: border-box;
overflow: hidden;
width: 100%;
min-height: 100vh;
height: auto;
}
.p100 {
width: 100%;
height: 100%;
}
.fleft {
float: left;
}
.fright {
float: right;
}
.clear {
clear: both;
}
.center {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
justify-content: center;
align-items: center;
}
.topAnimate {
position: absolute;
left: 0rpx;
width: 100%;
height: 400rpx;
top: 20px;
/* 距离视口顶部 20px 时固定 */
z-index: 100;
/* 确保元素显示在最上层 */
}
.animate {
width: 100%;
height: 400rpx;
position: relative;
}
.animate .animateContent {
position: relative;
z-index: 0;
}
.animate .imgContent {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
}
.animate .imgContent .img {
height: 100rpx;
width: 100rpx;
background: #bbe600;
border-radius: 50%;
}
.animate .titleIco {
width: 36rpx;
height: 36rpx;
}
.circle {
position: absolute;
border-radius: 50%;
background-color: #bbe60033;
animation: expand 4s infinite ease-out;
display: inline-block;
transform: translate(-50%, -50%);
border: 1rpx solid #bbe6003d;
animation: expand 5s infinite ease-in-out;
}
.circle:nth-child(2) {
animation-delay: 1s;
}
.circle:nth-child(3) {
animation-delay: 2s;
}
@keyframes expand {
0% {
width: 0;
height: 0;
opacity: 0.8;
}
80% {
width: 18.75rem;
height: 18.75rem;
opacity: 0.2;
}
100% {
width: 0.5rem;
height: 0.5rem;
opacity: 0.5;
}
}
.mainContent {
padding: 30rpx;
box-sizing: border-box;
width: 100%;
height: calc(100% - 300rpx);
overflow-y: scroll;
position: absolute;
top: 300rpx;
left: 0rpx;
}
.mainContent .p100 {}
.mainContent .lblTitle {
color: #ffffffde;
font-family: "PingFang SC";
font-size: 28rpx;
font-weight: 700;
text-align: left;
width: 100%;
height: 28rpx;
line-height: 28rpx;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
}
.list {
min-height: 120rpx;
}
.searchList {
width: 100%;
height: calc(100% - 186rpx);
overflow-y: scroll;
}
.list .item {
width: 100%;
min-height: 120rpx;
height: auto;
box-sizing: border-box;
padding: 20rpx;
border-radius: 8px;
background: #1a1a1a;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
margin-top: 20rpx;
position: relative;
overflow: hidden;
}
.list .item .leftImg {
width: 60rpx;
height: 60rpx;
}
.list .item .centertxt {
width: calc(100% - 100rpx);
height: 100%;
padding-left: 10rpx;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: space-evenly;
align-items: flex-start;
align-content: flex-start;
}
.list .item .before {
position: absolute;
bottom: 0px;
right: 0px;
content: "";
width: 0;
height: 0;
border-right: 30rpx solid #bbe600d4;
border-top: 30rpx solid transparent;
animation-delay: 1s;
animation: fade 1.5s infinite ease-in-out;
}
@keyframes fade {
0% {
opacity: 0.3;
}
50% {
opacity: 1;
}
100% {
opacity: 0.3;
}
}
.list .item .rightIco {
width: 40rpx;
height: 100%;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
justify-content: space-between;
align-items: center;
}
.list .item .leftImg .titleIco {
width: 100%;
height: 100%;
filter: invert(100%);
}
.list .item .name {
color: #ffffffde;
font-family: "PingFang SC";
font-size: 26rpx;
font-weight: 400;
line-height: 36rpx;
text-align: left;
}
.list .item .id {
color: #ffffff99;
font-family: "PingFang SC";
font-size: 26rpx;
font-weight: 400;
line-height: 36rpx;
text-align: left;
}
.list .item .rightIco .img {
width: 40rpx;
height: 40rpx;
}
.openBlue {
padding: 30rpx;
width: 100%;
box-sizing: border-box;
height: auto;
}
.openBlue .txt {
color: rgba(255, 255, 255, 0.87);
font-family: "PingFang SC";
font-size: 28rpx;
font-weight: 400;
letter-spacing: 0.14rpx;
text-align: center;
}
.openBlue .btns {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
align-items: center;
justify-content: space-evenly;
margin-top: 50rpx;
width: 100%;
height: auto;
}
.openBlue .btn {
border-radius: 91rpx;
box-sizing: border-box;
width: 25%;
height: 60rpx;
text-align: center;
font-family: "PingFang SC";
font-size: 28rpx;
letter-spacing: 12rpx;
display: flex !important;
align-items: center;
letter-spacing: 0.375rem;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
justify-content: center;
}
.openBlue .cancel {
border: 1px solid rgba(255, 255, 255, 1);
color: rgba(255, 255, 255, 1);
}
.openBlue .ok {
background-color: #BBE600;
color: #232323;
}
.filterNone {
filter: none !important;
-webkit-filter: none !important;
}
</style>