congming_huose-apk/pages/luru/bangding.vue

858 lines
22 KiB
Vue
Raw 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 class="pages">
<app-top-push-notice />
<view class="title">
<view class="title_li" style="display: flex;align-items: center;">
<view class="text">{{ $i18n.t('deviceList') }}</view>
<!-- <view class="btns" @click="toControl()">
控制台
</view> -->
</view>
<image src="https://api.ccttiot.com/smartmeter/img/static/uYsM9Nn95AK9c3JkUqvb" mode="widthFix"></image>
<!-- <u-icon name="grid-fill" color="#2979ff" size="28"></u-icon> -->
<text class="sm" v-if="showBluetoothStatusText && texts">{{texts}}</text>
</view>
<view class="dblist" v-for="(item, index) in devicesList" :key="index" @tap="choose(item)">
<view class="lt device-thumb-wrap">
<image class="device-thumb" :src="getDeviceImageByMacPrefix(item)" mode="aspectFill" />
</view>
<view class="cen">
<view class="device-line mac-line">
<text class="device-mac-text">{{$i18n.t('macLabel')}}{{item.localName.slice(-12)}}</text>
</view>
<view class="device-line rssi-line">
<text class="rssi-text">{{$i18n.t('bluetoothSignal')}}{{item.RSSI}}</text>
<text v-if="item.isBand" class="sn-text">{{ $i18n.t('snPrefix') }}{{item.sn}}</text>
</view>
<!-- <view class="device-line status-line">
<text class="status-text" :class="{ 'status-text--registered': item.isBand }">
{{ item.isBand ? $i18n.t('registered') : $i18n.t('notRegistered') }}
</text>
</view> -->
</view>
<view class="rt">
<!-- bindBtnState: 0 不展示 | 1 绑定 | 2 已绑定 -->
<view
v-if="item.bindBtnState === 1"
class="bind-btn"
:id="item.deviceId"
>{{ $i18n.t('bindAction') }}</view>
<view
v-if="item.bindBtnState === 2"
class="bind-btn bind-btn--bound"
:id="item.deviceId"
>{{ $i18n.t('deviceAlreadyBound') }}</view>
</view>
</view>
<!-- <view class="" style="width: 400rpx;margin: auto;" v-if="devicesList.length == 0 || devicesList == [] || devicesList == null || devicesList == undefined">
<image src="https://api.ccttiot.com/smartmeter/img/static/VhMNQ3Nf43oSKKbsPPPk" style="margin-top: 300rpx;width: 400rpx;height: 400rpx;" mode="aspectFit"></image>
</view> -->
<view class="anniu">
<view style="width: 200rpx;" @click="backPage">{{ $i18n.t('back') }}</view>
<view style="width: 450rpx;" @click="Search">{{ $i18n.t('rescan') }}</view>
</view>
<!-- 加载状态 -->
<view class="containers" v-show="statusflag">
<uni-section>
<uni-load-more :status="loadMoreStatus" />
</uni-section>
</view>
</view>
</template>
<script>
const app = getApp();
var xBlufi = require("@/common/blufi/xBlufi.js")
let _this = null;
// 设备名中出现的四位厂商前缀 → 产品图AUYK 等常在名称前部,不能仅用「后 12 位的前 4 位」)
const MAC_PREFIX_DEVICE_IMAGE = {
AUYK: 'https://api.ccttiot.com/image-1765181560094.png',
AUYG: 'https://api.ccttiot.com/203d6d03e1e65ad7410f42148b88b4e1-1758263531361.png',
AUMC: 'https://api.ccttiot.com/image-1761117712383.png',
AUHW: 'https://api.ccttiot.com/image-1765184024743.png',
AUSQ: 'https://api.ccttiot.com/image-1765184056827.png',
AUBL: 'https://api.ccttiot.com/image-1765184040649.png',
AUGW: 'https://api.ccttiot.com/ad93702b02429b8adf394569460b8b2a-1758263546164.png'
}
const KNOWN_MAC_PREFIXES = Object.keys(MAC_PREFIX_DEVICE_IMAGE)
const DEFAULT_MAC_PREFIX_IMAGE = MAC_PREFIX_DEVICE_IMAGE.AUGW
export default {
data() {
return {
titleflag: false, //提示隐藏
bgc: {
backgroundColor: "#F7FAFE",
},
devicesList: [],
devicesLists: [],
searching: false,
// 蓝牙权限未授权时不展示;授权后按真实状态赋值
showBluetoothStatusText: false,
texts: '',
btnflag: true,
tishiflag: false,
option: '',
bluthlist: [], //蓝牙数组
loadMoreStatus: 'loading',
statusflag: false,
Bluetoothmac: '',
gps: {},
mac: '',
// 首次扫描完成后为 true「重新扫描」才可用沿用原逻辑
status: false,
deviceinfo: null,
sn: '',
// 绑定弹窗
showBindModal: false,
bindFormSn: '',
bindFormMac: ''
}
},
onLoad(e) {
if (e.sn) {
this.sn = e.sn
}
xBlufi.initXBlufi(1)
},
onShow: function() {
this.bluthlist = []
this.devicesList = []
this.devicesLists = []
this.showBluetoothStatusText = false
this.texts = ''
this.$forceUpdate()
// 先检测蓝牙适配器:未授权则不展示下方状态文案;其它错误展示对应状态
uni.openBluetoothAdapter({
success: () => {
this.showBluetoothStatusText = true
this.texts = this.$i18n.t('scanningBluetooth')
xBlufi.listenDeviceMsgEvent(true, this.funListenDeviceMsgEvent)
xBlufi.notifyStartDiscoverBle({ 'isStart': true })
setTimeout(() => {
xBlufi.notifyStartDiscoverBle({ 'isStart': false })
if (this.devicesList.length == 0) {
this.tishiflag = true
this.texts = this.$i18n.t('scanCompleteNoDevice')
} else {
this.texts = this.$i18n.t('devicesFound')
}
this.status = true
this.$forceUpdate()
}, 2000)
},
fail: (err) => {
this.applyBluetoothAdapterFail(err)
this.$forceUpdate()
}
})
},
onUnload: function() {
xBlufi.listenDeviceMsgEvent(false, this.funListenDeviceMsgEvent)
xBlufi.notifyStartDiscoverBle({
'isStart': false
})
},
onHide() {
xBlufi.listenDeviceMsgEvent(false, this.funListenDeviceMsgEvent)
xBlufi.notifyStartDiscoverBle({
'isStart': false
})
},
onBeforeUnmount() {
xBlufi.listenDeviceMsgEvent(false, this.funListenDeviceMsgEvent)
xBlufi.notifyStartDiscoverBle({
'isStart': false
})
},
mounted() {
},
methods: {
/** 接口 userId 有有效值视为已绑定用户(不可再绑) */
hasBoundUserId(userId) {
if (userId === null || userId === undefined) return false
if (typeof userId === 'number') return !isNaN(userId) && userId !== 0
const s = String(userId).trim()
return s !== '' && s !== 'null' && s !== 'undefined'
},
/** 从广播名解析已知四位型号前缀(与配图表一致),用于带入录入页匹配产品 */
resolveKnownMacPrefix(raw) {
try {
if (raw === undefined || raw === null || raw === '') return ''
const upper = String(raw).toUpperCase()
let hitPrefix = null
let hitIndex = Infinity
for (let i = 0; i < KNOWN_MAC_PREFIXES.length; i++) {
const p = KNOWN_MAC_PREFIXES[i]
const idx = upper.indexOf(p)
if (idx !== -1 && idx < hitIndex) {
hitIndex = idx
hitPrefix = p
}
}
if (hitPrefix) return hitPrefix
const hexOnly = String(raw).replace(/[^0-9A-Fa-f]/g, '')
if (hexOnly.length >= 12) {
const tail = hexOnly.slice(-12)
const p2 = tail.substring(0, 4).toUpperCase()
if (MAC_PREFIX_DEVICE_IMAGE[p2]) return p2
}
const s = String(raw)
if (s.length >= 12) {
const p3 = s.slice(-12).substring(0, 4).toUpperCase()
if (MAC_PREFIX_DEVICE_IMAGE[p3]) return p3
}
return ''
} catch (e) {
return ''
}
},
getMacPrefixFromItem(item) {
const raw = item && (item.localName != null && item.localName !== '' ? item.localName : item.name)
return this.resolveKnownMacPrefix(raw)
},
// 根据设备名中的 MAC后 12 位)前四位匹配产品图
getDeviceImageByMacPrefix(item) {
try {
const raw = item && (item.localName != null && item.localName !== '' ? item.localName : item.name)
const prefix = this.resolveKnownMacPrefix(raw)
return prefix ? MAC_PREFIX_DEVICE_IMAGE[prefix] : DEFAULT_MAC_PREFIX_IMAGE
} catch (e) {
return DEFAULT_MAC_PREFIX_IMAGE
}
},
// 是否为「用户未授予蓝牙权限」(此时不展示标题下状态文案)
isBluetoothPermissionDenied(err) {
const msg = (err && err.errMsg ? String(err.errMsg) : '').toLowerCase()
return msg.includes('auth deny') || msg.includes('authorize') || msg.includes('system permission denied')
},
// 根据 openBluetoothAdapter 失败结果设置展示文案(非权限问题时展示具体状态)
applyBluetoothAdapterFail(err) {
if (this.isBluetoothPermissionDenied(err)) {
this.showBluetoothStatusText = false
this.texts = ''
return
}
this.showBluetoothStatusText = true
const errMsg = err && err.errMsg ? err.errMsg : ''
const t = this.$i18n && this.$i18n.t ? this.$i18n.t.bind(this.$i18n) : (k) => k
if (errMsg.includes('not available') || errMsg.includes('unavailable')) {
this.texts = t('bluetoothUnavailable')
} else if (errMsg.includes('open fail')) {
this.texts = t('bluetoothOpenFailed')
} else if (errMsg.includes('bluetooth service unavailable')) {
this.texts = t('bluetoothServiceUnavailable')
} else {
this.texts = (t('bluetoothInitFailed') || '') + (errMsg || '')
}
},
replay(){
uni.reLaunch({
url:'/pages/luru/index'
})
},
backPage(){
uni.navigateBack()
},
toControl(){
uni.redirectTo({
url:'/pages/luru/controlDevice'
})
},
fetchDevicesBoundStatus() {
// 提取所有设备的 MAC 地址,并通过 Set 去重
let uniqueDevices = Array.from(new Set(this.devicesList.map(item => item.localName.slice(-12))))
// 重新构建去重后的 devicesList
this.devicesList = uniqueDevices.map(mac => {
return this.devicesList.find(item => item.localName.slice(-12) === mac)
})
// 拼接成字符串
const macs = uniqueDevices
// 向批量接口请求录入状态
let array = macs
this.$http.post(`/app/device/simpleListByMacList`,array).then((res) => {
const resultList = res.data // 获取接口返回的数据数组
console.log(res,'请求')
// 遍历 devicesList 并对比 mac 地址
this.devicesList.forEach(device => {
const mac = device.localName.slice(-12)
const matchingResult = resultList.find(result => result.mac === mac)
device.boundByUser = false
if (matchingResult) {
device.sn = matchingResult.sn
device.isBand = true
const uid = matchingResult.userId !== undefined && matchingResult.userId !== null ? matchingResult.userId : matchingResult.user_id
device.boundByUser = this.hasBoundUserId(uid)
} else {
device.isBand = false
}
device.bindBtnState = device.boundByUser ? 2 : 1
})
// 对 devicesList 进行排序,将 isBand 为 false 的设备排在前面
this.devicesList.sort((a, b) => {
return (a.isBand === false ? -1 : 1) - (b.isBand === false ? -1 : 1)
})
this.$forceUpdate() // 强制更新视图
console.log(this.devicesList, 'Updated devicesList')
}).catch((error) => {
console.error('批量获取设备录入状态失败', error)
})
},
choose(e) {
if (e.bindBtnState === 0) {
return
}
if (e.bindBtnState === 2) {
uni.showToast({
title: this.$i18n.t('deviceAlreadyBound'),
icon: 'none'
})
return
}
const nm = e.name || e.localName || ''
let Bluetoothmac = nm.slice(-12)
this.bindFormMac = Bluetoothmac
this.bindFormSn = ''
const prefix = this.getMacPrefixFromItem(e)
const prefixQs = prefix ? ('&prefix=' + encodeURIComponent(prefix)) : ''
this.$http.get(`/app/device/detailByMac?mac=${Bluetoothmac}`).then(res => {
if (res.code == 200) {
if (res.data) {
uni.navigateTo({
url: '/subpackage/device/deviceaddroom?deviceId=' + e.sn
})
} else {
uni.navigateTo({
url: '/pages/luru/bind_mac?mac=' + Bluetoothmac + prefixQs
})
}
}
})
},
// 关闭绑定弹窗
closeBindModal() {
this.showBindModal = false
this.bindFormSn = ''
this.bindFormMac = ''
},
// 确认绑定
getbangd() {
if (!this.bindFormMac) {
uni.showToast({
title: this.$i18n.t('macInvalid'),
icon: 'none'
})
return
}
this.$http.put(`/bst/device/bind`, {
mac: this.bindFormMac,
sn: this.bindFormSn,
spaceId: uni.getStorageSync('kjid')
}).then(res => {
if (res.code == 200) {
uni.showToast({
title: this.$i18n.t('bindSucceeded'),
icon: 'success',
duration: 2000
})
this.showBindModal = false
setTimeout(() => {
uni.reLaunch({
url: '/pages/index/index'
})
}, 1500)
} else {
uni.showToast({
title: res.msg || this.$i18n.t('bindFailed'),
icon: 'none',
duration: 3000
})
}
})
},
funListenDeviceMsgEvent: function(options) {
switch (options.type) {
case xBlufi.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS:
if (options.result) {
this.devicesLists = options.data
console.log('搜索的设备',options);
}
break
case xBlufi.XBLUFI_TYPE.TYPE_CONNECTED:
console.log("连接回调:" + JSON.stringify(options))
if (options.result) {
uni.hideLoading()
uni.showToast({
title: this.$i18n.t('connectionSuccess'),
icon: 'none'
}); {
console.log("连接回调options.data.deviceId" + options.data.deviceId,
"连接回调options.data.name" + options.data.name);
xBlufi.notifyInitBleEsp32({
deviceId: options.data.deviceId
})
}
}
break;
case xBlufi.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS_START:
if (!options.result) {
console.log("蓝牙未开启", options);
uni.showToast({
title: this.$i18n.t('bluetoothNotEnabled'),
icon: 'none',
duration: 3000
});
if (this.showBluetoothStatusText) {
this.texts = this.$i18n.t('bluetoothNotEnabled')
}
this.tishiflag = true
} else {
this.searching = true
if (this.showBluetoothStatusText) {
this.texts = this.$i18n.t('scanningBluetooth')
}
}
break;
case xBlufi.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS_STOP:
if (options.result) {
let uniqueDevicesList = Array.from(new Set(this.devicesLists))
// 将去重后的数组重新赋值给 this.devicesList
this.devicesLists = uniqueDevicesList
let list = []
uniqueDevicesList.forEach(device => {
// 从设备名称中提取 MAC 地址(假设 MAC 地址是设备名称的后6个字符
let macFromName = device.name.substring(device.name.length - 12)
// console.log(macFromName)
// 与 this.mac 进行比较
device.bindBtnState = 0
list.push(device)
});
setTimeout(() => {
this.devicesList = list
let uniqueDevicesList = Array.from(new Set(this.devicesList))
// 将去重后的数组重新赋值给 this.devicesList
this.devicesList = uniqueDevicesList
this.fetchDevicesBoundStatus()
}, 200)
console.log('蓝牙停止搜索ok')
} else {
//蓝牙停止搜索失败
console.log('蓝牙停止搜索失败')
}
this.searching = false
break;
}
},
// 点击重新搜索
Search() {
if (!this.status) return
uni.openBluetoothAdapter({
success: () => {
this.showBluetoothStatusText = true
xBlufi.notifyStartDiscoverBle({
'isStart': true
})
this.bluthlist = []
this.devicesList = []
this.devicesLists = []
this.statusflag = true
this.loadMoreStatus = 'loading'
this.texts = this.$i18n.t('scanningBluetooth')
setTimeout(() => {
this.statusflag = false
if (this.searching) {
if (this.devicesList.length == 0) {
this.tishiflag = true
this.texts = this.$i18n.t('scanCompleteNoDevice')
} else {
this.texts = this.$i18n.t('devicesFound')
}
xBlufi.notifyStartDiscoverBle({
'isStart': false
})
} else {
xBlufi.notifyStartDiscoverBle({
'isStart': true
})
}
}, 2000)
},
fail: (err) => {
this.applyBluetoothAdapterFail(err)
this.$forceUpdate()
}
})
},
}
}
</script>
<style lang="scss">
page {
background-color: #F7FAFE !important;
}
.tabback{
width: 750rpx;
height: 130rpx;
background: #FCFCFC;
border-radius: 0rpx 0rpx 0rpx 0rpx;
position: fixed;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 28rpx;
box-sizing: border-box;
border-bottom: 1px solid #D8D8D8;
padding-top: 52rpx;
z-index: 999;
.name{
font-size: 36rpx;
color: #3D3D3D;
}
.rtjt {
font-size: 36rpx;
}
}
.containers {
width: 100%;
height: 100vh;
position: fixed;
top: 0;
padding-top: 130rpx;
box-sizing: border-box;
left: 0;
z-index: 999 !important;
/* background-color: #fff; */
z-index: 99;
}
.pages {
padding-top: 136rpx !important;
padding: 0 66rpx;
box-sizing: border-box;
padding-bottom: 200rpx;
box-sizing: border-box;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal {
width: 300px;
background: #fff;
border-radius: 8px;
overflow: hidden;
}
.modal-content {
padding: 20px;
}
.modal-title {
font-size: 18px;
margin-bottom: 10px;
text-align: center;
}
.modal-body {
font-size: 16px;
margin-bottom: 20px;
text-align: center;
}
.modal-footer {
display: flex;
justify-content: space-around;
}
.modal-footer button {
width: 80px;
height: 60rpx;
padding: 10px;
background: #8883F0;
color: #fff;
border: none;
border-radius: 4px;
}
.btn {
display: flex;
align-items: center;
justify-content: center;
background: #8883F0;
width: 80px;
height: 60rpx;
color: #fff;
border-radius: 4px;
}
.modal-footer button:first-of-type {
background: #ccc;
}
// text{
// display: block;
// }
.sm {
color: #77808D;
border-radius: 0rpx 0rpx 0rpx 0rpx;
// margin-top: 48rpx;
display: inline-block;
}
.title {
margin-bottom: 84rpx;
image {
display: inline-block;
width: 48rpx;
height: 48rpx;
vertical-align: bottom;
margin-right: 10rpx;
}
}
.title_li{
display: flex;
flex-wrap: nowrap;
align-items: center;
.text{
font-weight: 400;
font-size: 70rpx;
color: #262B37;
text-align: left;
font-style: normal;
text-transform: none;
display: block;
}
.btns{
margin-left: 30rpx;
display: flex;
align-items: center;
justify-content: center;
width: 138rpx;
height: 50rpx;
background: #F14C4C;
border-radius: 31rpx 31rpx 31rpx 31rpx;
font-weight: 500;
font-size: 24rpx;
color: #FFFFFF;
}
}
.dblist {
display: flex;
align-items: stretch;
width: 100%;
margin-top: 34rpx;
background: #FFFFFF;
box-shadow: 0rpx 8rpx 40rpx 0rpx rgba(0, 0, 0, 0.06);
padding: 24rpx 20rpx 24rpx 18rpx;
box-sizing: border-box;
border-radius: 16rpx;
border: 1rpx solid rgba(0, 0, 0, 0.04);
.lt {
flex-shrink: 0;
padding-left: 10rpx;
box-sizing: border-box;
margin-right: 20rpx;
align-self: center;
&.device-thumb-wrap {
width: 120rpx;
height: 120rpx;
border-radius: 16rpx;
overflow: hidden;
background: #f3f4f6;
}
.device-thumb {
width: 120rpx;
height: 120rpx;
display: block;
}
}
.cen {
flex: 1;
min-width: 0;
padding-left: 4rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
text-align: left;
.device-line {
width: 100%;
}
.mac-line {
.device-mac-text {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 28rpx;
font-weight: 500;
color: #1a1a1a;
line-height: 1.45;
}
}
.rssi-line {
display: flex;
flex-wrap: wrap;
align-items: baseline;
margin-top: 12rpx;
.rssi-text {
font-size: 24rpx;
color: #5c6370;
line-height: 1.4;
margin-right: 16rpx;
}
.sn-text {
font-size: 22rpx;
color: #8b9199;
}
}
/* 第三行录入状态纯文案左对齐 */
.status-line {
margin-top: 14rpx;
text-align: left;
}
.status-text {
font-size: 24rpx;
font-weight: 500;
line-height: 1.4;
color: #2563eb;
}
.status-text--registered {
color: #6b7280;
font-weight: 400;
}
}
.rt {
flex-shrink: 0;
margin-left: 16rpx;
padding-right: 4rpx;
display: flex;
align-items: center;
align-self: center;
.bind-btn {
min-width: 112rpx;
padding: 0 26rpx;
height: 64rpx;
line-height: 64rpx;
text-align: center;
font-size: 26rpx;
font-weight: 600;
color: #ffffff;
background: #0f0f0f;
border: 2rpx solid #0f0f0f;
border-radius: 32rpx;
box-sizing: border-box;
}
.bind-btn--bound {
color: #6b7280;
background: #e5e7eb;
border-color: #e5e7eb;
font-weight: 500;
}
}
}
.anniu {
padding: 0 40rpx;
width: 100%;
box-sizing: border-box;
position: fixed;
left: 0;
bottom: 60rpx;
display: flex;
align-items: center;
justify-content: space-between;
view {
background: #000;
border-radius: 52rpx 52rpx 52rpx 52rpx;
color: #fff;
height: 80rpx;
line-height: 80rpx;
text-align: center;
}
}
.mask {
width: 622rpx;
height: 710rpx;
background: #FFFFFF;
filter: blur(0px);
border-radius: 20rpx;
position: fixed;
top: 475rpx;
left: 50%;
transform: translateX(-50%);
padding-top: 38rpx;
padding-left: 60rpx;
padding-right: 60rpx;
box-sizing: border-box;
.titles {
font-size: 48rpx;
color: #262B37;
line-height: 70rpx;
text-align: center;
margin-bottom: 24rpx;
}
text {
display: block;
font-size: 32rpx;
color: #262B37;
line-height: 56rpx;
text-align: left;
}
button {
margin-top: 46rpx;
width: 266rpx;
height: 96rpx;
background: #8883F0;
border-radius: 52rpx 52rpx 52rpx 52rpx;
color: #fff;
text-align: center;
line-height: 96rpx;
}
}
</style>