Sprinkler-app/page_user/lanya.vue
2026-03-26 17:48:21 +08:00

742 lines
19 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view>
<u-navbar :is-back="true" title=' ' title-color="#000" :border-bottom="false" :background="bgc"
id="navbar">
</u-navbar>
<view class="page">
<!-- 有搜索到设备 -->
<view class="you" v-if="flags">
<view class="topone">
<image src="https://api.ccttiot.com/smartmeter/img/static/ubrPcpGQEXTadkBa1gKh" mode=""></image>
{{ $t('lanya.scanHint') }}
</view>
<view class="toptwo">
{{ $t('lanya.rescanHint') }}
</view>
<view class="list">
<view class="list_item" v-for="(item,index) in sortedJiaohuaqi" :key="index" :class="{ show: item.show }">
<image :src="item.modelPicture" mode=""></image>
<view class="cen">
<view class="name" style="color: #ccc;" v-if="item.userId && item.userId != userid">
{{item.modelName == undefined ? '' : item.modelName}}
</view>
<view class="name" v-else>
{{item.modelName == undefined ? '' : item.modelName}}
</view>
<view class="devmac">
{{ $t('common.mac') }}{{item.mac == undefined ? item.name.slice(-12) : item.mac}}
</view>
<view class="devmac" style="display: flex;align-items: center;">
{{ $t('lanya.signal') }}
<!-- 最强信号:-50 及以上(-1 到 -50 -->
<image
style="width: 30rpx;height: 20rpx;"
v-if="item.ssid >= -50"
src="https://api.ccttiot.com/smartmeter/img/static/ueeDGk0mVPLUd0DHxrWj"
mode=""
></image>
<!-- 较强信号:-51 到 -60 -->
<image
style="width: 30rpx;height: 20rpx;"
v-else-if="item.ssid >= -60 && item.ssid <= -51"
src="https://api.ccttiot.com/smartmeter/img/static/uM1obZ76ittglMRKXWLq"
mode=""
></image>
<!-- 中等信号:-61 到 -70 -->
<image
style="width: 30rpx;height: 20rpx;"
v-else-if="item.ssid >= -70 && item.ssid <= -61"
src="https://api.ccttiot.com/smartmeter/img/static/ujO9AZIuUSQvHcCBKqc4"
mode=""
></image>
<!-- 较弱信号:-71 到 -80 -->
<image
style="width: 30rpx;height: 20rpx;"
v-else-if="item.ssid >= -80 && item.ssid <= -71"
src="https://api.ccttiot.com/smartmeter/img/static/uCSlbXZvho808NMCkIQP"
mode=""
></image>
<!-- 最弱信号:-81 到 -100 -->
<image
style="width: 30rpx;height: 20rpx;"
v-else-if="item.ssid >= -100 && item.ssid <= -81"
src="https://api.ccttiot.com/smartmeter/img/static/u8bj3ZNi8Zssunk69HWc"
mode=""
></image>
</view>
</view>
<!-- 绑定状态:优先显示“已添加”,未查询完显示“查询中”,其余显示“添加” -->
<view class="add" style="color: #ccc;border: 1px solid #ccc;" v-if="item.isBound === true">
{{ $t('lanya.added') }}
</view>
<view class="add" style="color: #ccc;border: 1px solid #ccc;" v-else-if="item.hasCheckBind !== true">
{{ $t('lanya.checking') }}
</view>
<view class="add" @click="btnadd(item)" v-else>
{{ $t('lanya.add') }}
</view>
</view>
</view>
</view>
<!-- 未搜索到设备 -->
<view class="wei" v-else>
<image src="https://api.ccttiot.com/smartmeter/img/static/uQ4g6A27FGtF34ebOtea" mode=""></image>
<view class="sbname">
{{ $t('lanya.searchFail') }}
</view>
<view class="sbwz">
{{ $t('lanya.searchFailHint') }}
</view>
</view>
<!-- 点击搜索 -->
<view class="btnss" @click="handleSearch">
{{ $t('lanya.rescan') }}
</view>
</view>
<!-- 自定义名称弹框 -->
<u-popup v-model="showNameDialog" mode="center" border-radius="14" width="600rpx">
<view class="custom-name-dialog">
<view class="dialog-title">{{ $t('lanya.deviceName') }}</view>
<u-input v-model="customDeviceName" :placeholder="$t('lanya.namePh')" />
<view class="dialog-btns">
<view class="btn cancel" @click="showNameDialog = false">{{ $t('common.cancel') }}</view>
<view class="btn confirm" @click="confirmAddDevice">{{ $t('common.confirm') }}</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
var xBlufi = require("@/components/blufi/xBlufi.js");
export default {
data() {
return {
bgc: {
backgroundColor: "#fff",
},
active: 1,
flag: true,
devicesList: [],
newlist:[],
deviceId: '',
name: '',
mac: '',
flags: true,
userid: '',
arr: [],
jiaohuaqi: [],
getpre: [],
showNameDialog: false,
customDeviceName: '',
currentDevice: null,
searchTimer: null,
checkTimer: null,
isSearching: false,
searchTimeout: null,
throttleTimer: null,
lastSearchTime: 0,
searchInterval: 2000, // 搜索间隔2秒
displayQueue: [], // 显示队列
processingQueue: false, // 是否正在处理队列
isBatchRequesting: false, // 是否有批量请求进行中
rssiMap: {}, // 记录每个 mac 的信号强度
}
},
computed: {
sortedJiaohuaqi() {
return this.jiaohuaqi.slice().sort((a, b) => {
if (a.ssid === undefined) return 1;
if (b.ssid === undefined) return -1;
return Math.abs(a.ssid) - Math.abs(b.ssid);
});
}
},
// 分享到好友(会话)
onShareAppMessage: function() {
return {
title: this.$t('app.name'),
path: '/pages/index/index'
}
},
// 分享到朋友圈
onShareTimeline: function() {
return {
title: this.$t('app.name'),
query: '',
path: '/pages/index/index'
}
},
onLoad() {
const vm = this
this.getmodel()
this.getinfo()
// 检查蓝牙状态
uni.getBluetoothAdapterState({
success(res) {
console.log('蓝牙适配器状态:', res)
if (!res.available) {
uni.showModal({
title: vm.$t('common.tip'),
content: vm.$t('lanya.bleOff'),
showCancel: false,
confirmText: vm.$t('common.gotIt')
})
} else {
console.log('蓝牙正常但未找到设备,设备可能断电')
}
},
fail(err) {
console.error('获取蓝牙状态失败', err)
uni.openBluetoothAdapter({
success() {
console.log('蓝牙适配器正常,但未发现设备')
},
fail(err2) {
console.error('蓝牙适配器初始化失败', err2)
console.log('错误码:', err2.errCode, '错误信息:', err2.errMsg)
const errMsg = err2.errMsg || ''
let content = ''
if (errMsg.includes('auth deny') || errMsg.includes('authorize')) {
content = vm.$t('lanya.blePerm')
} else if (errMsg.includes('not available') || errMsg.includes('unavailable')) {
content = vm.$t('lanya.bleOff')
} else if (errMsg.includes('open fail')) {
content = vm.$t('lanya.bleOpenFail')
} else if (errMsg.includes('already opened')) {
console.log('蓝牙适配器已打开,但未找到设备')
return
} else if (errMsg.includes('system permission denied')) {
content = vm.$t('lanya.bleSysDeny')
} else if (errMsg.includes('bluetooth service unavailable')) {
content = vm.$t('lanya.bleSvcBusy')
} else {
content = vm.$t('lanya.bleInitPrefix') + (err2.errMsg || vm.$t('lanya.unknownErr'))
}
uni.showModal({
title: vm.$t('common.tip'),
content: content,
showCancel: false,
confirmText: vm.$t('common.gotIt')
})
}
})
}
})
},
onShow() {
this.startSearch()
},
onHide() {
this.stopSearch()
},
onUnload() {
this.stopSearch()
},
methods: {
// 获取用户信息
getinfo() {
this.$u.get(`/system/user/profile`).then((res) => {
if (res.code == 200) {
this.userid = res.data.userId
uni.setStorageSync('user',res.data)
uni.setStorageSync('userId',res.data.userId)
} else if (res.code == 401) {
uni.showModal({
title: this.$t('common.tip'),
content: this.$t('common.promptLogin'),
success: (r) => {
if (r.confirm) {
uni.navigateTo({
url: '/pages/login/login'
})
}
}
})
}
})
},
// 点击添加按钮
btnadd(e) {
this.currentDevice = e;
this.customDeviceName = e.modelName || this.$t('lanya.unknownDevice');
this.showNameDialog = true;
},
// 确认添加设备
confirmAddDevice() {
if (!this.customDeviceName.trim()) {
uni.showToast({
title: this.$t('lanya.enterName'),
icon: 'none'
});
return;
}
let mac = this.currentDevice.name.slice(-12);
let data = {
mac: mac,
// userId: this.userid,
pre: this.currentDevice.pre,
deviceName: this.customDeviceName
}
console.log(data,'参数');
this.$u.post(`/app/device/bindDeviceByBlueTooth`, data).then((res) => {
if (res.code == 200) {
uni.showToast({
title: this.$t('common.bindSuccess'),
icon: 'none',
duration: 3000
})
this.showNameDialog = false;
setTimeout(() => {
uni.navigateBack()
}, 2000)
// let datas = {
// mac:mac
// }
// this.$u.post(`/app/device/bindDevice`, datas).then(resp =>{
// if(resp.code == 200){
// uni.showToast({
// title:'绑定成功',
// icon: 'none',
// duration: 3000
// })
// this.showNameDialog = false;
// setTimeout(() => {
// uni.navigateBack()
// }, 2000)
// }else{
// uni.showToast({
// title: resp.msg,
// icon: 'none',
// duration: 3000
// })
// }
// })
} else {
console.log(res,'报错');
uni.showToast({
title: res.msg,
icon: 'none',
duration: 3000
})
}
})
},
getmodel() {
this.$u.get(`/app/getAllModelList`).then(res => {
if (res.code == 200) {
this.getpre = res.data
}
})
},
getpipei(pre) {
// 添加默认返回值防止undefined
return this.getpre.find(item => item.pre == pre) || {
modelName: this.$t('lanya.unknownModel'),
picture: ''
};
},
// 开始搜索
startSearch() {
if (this.isSearching) return
this.isSearching = true
this.jiaohuaqi = []
this.displayQueue = []
this.processingQueue = false
this.flag = false
// 开始蓝牙搜索
xBlufi.listenDeviceMsgEvent(true, this.funListenDeviceMsgEvent)
xBlufi.notifyStartDiscoverBle({ 'isStart': true })
// 30秒后自动停止搜索
this.searchTimeout = setTimeout(() => {
this.stopSearch()
}, 30000)
},
// 停止搜索
stopSearch() {
this.isSearching = false
if (this.checkTimer) {
clearInterval(this.checkTimer)
this.checkTimer = null
}
if (this.searchTimeout) {
clearTimeout(this.searchTimeout)
this.searchTimeout = null
}
if (this.throttleTimer) {
clearTimeout(this.throttleTimer)
this.throttleTimer = null
}
xBlufi.notifyStartDiscoverBle({ 'isStart': false })
this.flag = true
},
// 处理显示队列
processDisplayQueue() {
if (this.processingQueue || this.displayQueue.length === 0) return
this.processingQueue = true
const device = this.displayQueue.shift()
// 检查设备是否已存在
if (!this.jiaohuaqi.some(item => item.name === device.name)) {
this.jiaohuaqi.push(device)
}
// 延迟处理下一个设备
setTimeout(() => {
this.processingQueue = false
this.processDisplayQueue()
}, 500) // 每个设备显示间隔500ms
},
// 添加设备到显示队列
addToDisplayQueue(device) {
this.displayQueue.push(device)
if (!this.processingQueue) {
this.processDisplayQueue()
}
},
// 更新设备列表(只展示接口返回的数据,累加去重)
updateDeviceList(existList) {
if (!Array.isArray(existList) || existList.length === 0) return
existList.forEach(item => {
// 后端返回的设备信息中 mac 为唯一标识
const mac = item.mac
if (!mac) return
// 计算信号强度:优先取本地扫描到的 rssiMap
const ssid = this.rssiMap[mac] != null ? this.rssiMap[mac] : undefined
// 设备是否已绑定
const isBound = item.userId != null
// 如果列表中已存在该设备:更新状态
const exist = this.jiaohuaqi.find(dev => dev.mac === mac)
if (exist) {
exist.userId = item.userId
exist.isBound = isBound
exist.hasCheckBind = true
exist.modelName = item.modelName || exist.modelName
exist.modelPicture = item.modelPicture || exist.modelPicture
if (ssid !== undefined) {
exist.ssid = ssid
}
} else {
// 不存在则新增(只用接口返回的数据来展示)
const pre = item.pre || ''
const matched = this.getpipei(pre)
const newDevice = {
name: mac,
mac: mac,
pre: pre,
modelName: item.modelName || matched.modelName,
modelPicture: item.modelPicture || matched.picture,
userId: item.userId,
isBound: isBound,
hasCheckBind: true,
ssid: ssid
}
this.addToDisplayQueue(newDevice)
}
})
},
// 获取附近蓝牙设备列表
funListenDeviceMsgEvent: function(options) {
switch (options.type) {
case xBlufi.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS:
if (options.result) {
const now = Date.now()
if (now - this.lastSearchTime < this.searchInterval) {
return
}
this.lastSearchTime = now
let devicesarr = []
this.devicesList = options.data
// 只记录接口需要的 pre、mac 和对应的信号强度,不直接往展示列表塞设备
options.data.forEach(item => {
const localName = item.localName || ''
const pre = localName.slice(0,5)
if(pre == 'WATER' || pre == 'SMSJ:' || pre == 'DATER' || pre == 'TATER' || pre == 'SATER'){
const mac = localName.slice(-12)
if (!mac) return
// 记录信号强度
if (item.RSSI != null) {
this.rssiMap[mac] = item.RSSI
}
// 累加去重 mac 列表,供接口使用
if (!devicesarr.some(d => d.mac === mac)) {
devicesarr.push({
pre: pre,
mac: mac
})
}
}
})
this.arr = devicesarr
// 使用防抖 + 请求中的标记,避免一次性触发很多请求
if (this.throttleTimer) {
clearTimeout(this.throttleTimer)
}
// 500ms 内多次回调只发一个请求
this.throttleTimer = setTimeout(() => {
if (!this.devicesList.length || !this.arr.length) return
// 如果上一次请求还在进行中,则本次略过,等待下一轮回调再发
if (this.isBatchRequesting) return
this.isBatchRequesting = true
const payload = this.arr.slice(0) // 拷贝一份当前 mac 列表
this.$u.post(`/app/device/batchInsert`, payload).then(res => {
if (res.code === 200 && Array.isArray(res.data)) {
this.updateDeviceList(res.data)
}
}).finally(() => {
this.isBatchRequesting = false
})
}, 500)
}
break
case xBlufi.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS_START:
if (!options.result) {
uni.showToast({
title: this.$t('lanya.bleInitFailShort'),
icon: 'none',
duration: 3000
})
this.flags = false
return
}
break
}
},
btnss() {
this.stopSearch()
this.jiaohuaqi = []
this.displayQueue = []
this.processingQueue = false
this.startSearch()
},
// 处理搜索按钮点击
handleSearch() {
this.btnss()
},
}
}
</script>
<style lang="less">
::v-deep .u-input__input{
border: 1px solid #ccc;
border-radius: 10rpx;
padding-left: 10rpx;
box-sizing: border-box;
}
::v-deep .u-title {
margin-bottom: 22rpx;
}
::v-deep .uicon-nav-back {
margin-bottom: 22rpx;
}
.page {
padding-bottom: 300rpx;
box-sizing: border-box;
}
.wei {
text-align: center;
image {
width: 380rpx;
height: 394rpx;
}
.sbname {
font-size: 40rpx;
color: #3D3D3D;
margin-top: 80rpx;
width: 100%;
text-align: center;
}
.sbwz {
font-size: 28rpx;
color: #737B80;
margin-top: 24rpx;
width: 100%;
text-align: center;
}
}
.btnss {
width: 512rpx;
height: 92rpx;
background: #48893B;
border-radius: 46rpx 46rpx 46rpx 46rpx;
border-radius: 50rpx;
text-align: center;
line-height: 92rpx;
font-weight: 600;
font-size: 40rpx;
color: #FFFFFF;
position: fixed;
left: 50%;
transform: translateX(-50%);
bottom: 106rpx;
transition: all 0.3s ease;
}
.list {
width: 100%;
border-radius: 20rpx;
margin: auto;
margin-top: 72rpx;
will-change: transform; // 优化动画性能
.list_item {
margin-top: 18rpx;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 152rpx;
background: #FFFFFF;
border-radius: 20rpx;
box-shadow: 0rpx 10rpx 64rpx 0rpx rgba(0, 0, 0, 0.08);
padding: 18rpx 30rpx;
box-sizing: border-box;
animation: slideIn 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
opacity: 0;
transform: translateX(-100%);
will-change: transform, opacity; // 优化动画性能
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
image {
width: 94rpx;
height: 94rpx;
}
.cen {
.name {
font-size: 32rpx;
color: #50565A;
}
.devmac {
font-size: 24rpx;
color: #BDBCBC;
margin-top: 6rpx;
}
}
.add {
width: 108rpx;
height: 60rpx;
background: #FFFFFF;
border: 3rpx solid #48893B;
filter: blur(0px);
border-radius: 20rpx;
text-align: center;
line-height: 60rpx;
font-size: 28rpx;
color: #48893B;
}
}
}
page {
width: 100%;
padding: 20rpx 64rpx;
box-sizing: border-box;
background-color: #fff;
}
.topone {
font-size: 36rpx;
color: #3D3D3D;
display: flex;
image {
width: 48rpx;
height: 48rpx;
}
}
.toptwo {
font-size: 28rpx;
color: #737B7F;
margin-top: 14rpx;
width: 100%;
padding-left: 48rpx;
box-sizing: border-box;
}
.custom-name-dialog {
background: #fff;
padding: 40rpx;
.dialog-title {
font-size: 32rpx;
color: #333;
text-align: center;
margin-bottom: 30rpx;
}
.dialog-btns {
display: flex;
justify-content: space-between;
margin-top: 40rpx;
.btn {
width: 240rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
border-radius: 40rpx;
font-size: 28rpx;
&.cancel {
background: #f5f5f5;
color: #666;
}
&.confirm {
background: #48893B;
color: #fff;
}
}
}
}
</style>