1
0
forked from dyf/APP
Files
APP/pages/6170/deviceControl/index.vue

1790 lines
45 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>
<!-- 添加全局加载遮罩 -->
<view v-if="pageLoading" class="page-loading-mask">
</view>
<!-- 使用自定义导航栏 -->
<view v-show="!pageLoading">
<custom-navbar :title="navTitle" :showBack="true" color="#FFFFFF"
:rightIcon="isRightIconVisible ? '/static/images/common/shape.png' : ''"
@right-click="shareUp"></custom-navbar>
<view class="device-detail-container" :style="{ paddingTop: navBarHeight + 'px' }">
<!-- 设备电量信息 -->
<view class="battery-section">
<view class="battery-sectionLeft">
<image :src="deviceInfo.devicePic" class="bipImg" mode="aspectFit"></image>
</view>
<view>
<view class="battery-v1">
<image src="/static/images/common/dl.png" class="dlIMG"></image>
<view>
<view class="battery-v2"
:style="{ color: deviceInfo.batteryPercentage < 20 ? '#FF0000' : '' }">
{{ deviceInfo.batteryPercentage }}%
</view>
<view class="battery-v3">电量</view>
</view>
<view v-if="deviceInfo.chargeState == 1">
<image src="/static/images/common/chargeState.png" class="chargeStateIMG"></image>
</view>
</view>
<view class="battery-v1">
<image src="/static/images/common/nz.png" class="dlIMG" mode="aspectFit"></image>
<view>
<view class="battery-v2">{{ deviceInfo.batteryRemainingTime || '0' }}分钟</view>
<view class="battery-v3">续航时间</view>
</view>
</view>
</view>
<view>
</view>
</view>
<!-- 设备基本信息 -->
<view class="info-card">
<view class="info-row">
<text class="info-label">IMEI号</text>
<text class="info-value status-running">{{ deviceInfo.deviceImei }}</text>
</view>
<view class="info-row">
<text class="info-label">设备状态</text>
<text class="info-value status-running">{{ deviceInfo.onlineStatus === 0 ? '离线' : '在线' }}</text>
</view>
<view class="info-row">
<text class="info-label" style="display: flex; align-items: center;">定位信息</text>
<view class="info-value status-running" @click="gpsPosition(deviceInfo)">
<view class="info-value status-running">
{{ deviceInfo && deviceInfo.longitude ? Number(deviceInfo.longitude).toFixed(4) : '' }}
{{ deviceInfo && deviceInfo.latitude ? Number(deviceInfo.latitude).toFixed(4) : '' }}
</view>
<view class="info-value status-running locationGPS">
<uni-icons @click="toggleForm" type="location" size="17"
color="rgba(255, 255, 255, 0.8)" style="vertical-align: bottom;" />
{{ deviceInfo.address }}
</view>
</view>
</view>
</view>
<view class="callpolice" v-if="deviceInfo.onlineStatus == 1 && deviceInfo.alarmStatus == 1">
<view class="">设备强制报警中</view>
<view>
<uni-icons type="closeempty" size="15" color="rgba(255, 255, 255, 0.9)"
@click="handlePolice"></uni-icons>
</view>
</view>
<view class="info-row">
<text class="info-label">灯光亮度</text>
<text class="info-value status-running">{{ sliderValue }}%</text>
</view>
<!-- 灯光亮度控制 -->
<view class="control-card" @touchstart="cardTouchStart" @touchmove="cardTouchMove"
@touchend="cardTouchEnd">
<view @touchmove.prevent>
<slider :value="sliderValue" min="10" max="100" activeColor="rgb(187, 230, 0)"
backgroundColor="rgb(26, 26, 26)" :show-value="false" @changing="onSliderChanging"
@change="onSliderChangeEnd" block-color="#fff" block-size="20" />
</view>
</view>
<!-- 灯光模式选择 -->
<view class="mode-section">
<view class="mode-buttons">
<view class="mode-v1" v-if="hasPermission('1')">
<view class="mode-v2" @click="selectMode('main')">
<image src="/static/images/6170/set.png" class="setIMG"></image>
<view>
<view class="battery-v2">灯光模式</view>
<view class="mode-v3">{{ currentMainMode }}</view>
</view>
</view>
</view>
<view class="mode-v1" v-if="hasPermission('2')">
<view class="mode-v2" @click="lasermode">
<image src="/static/images/6170/jg.png" class="setIMG" mode="aspectFit"></image>
<view>
<view class="battery-v2">激光模式</view>
<view class="mode-v3">{{ currentlaserMode }}</view>
</view>
</view>
</view>
<view class="mode-v1" v-if="hasPermission('3')">
<view class="mode-v2" @click="uploadStartup">
<image src="/static/images/common/path7.png" class="setIMG" mode="aspectFit"></image>
<view>
<view class="battery-v2">开机画面</view>
<view class="mode-v3">上传</view>
</view>
</view>
</view>
</view>
</view>
<!-- 人员信息登记 -->
<view class="form-section" v-if="hasPermission('4')">
<view class="mode-buttons">
<view class="section-title">人员信息登记</view>
<view class="right-icons">
<uni-icons @click="toggleForm" :type="isFormExpanded ? 'arrowup' : 'down'" size="20"
color="rgba(255, 255, 255, 0.87" class="toggle-icon" />
</view>
</view>
<view class="form-content" v-if="isFormExpanded">
<button class="send-btn1" @click.stop="sendPersonnelInfo">发送</button>
<view class="form-row">
<text class="form-label">单位</text>
<input class="form-input" placeholder="请输入单位" v-model="personnelInfo.unitName"
:maxlength="15" />
</view>
<view class="form-row">
<text class="form-label">姓名</text>
<input class="form-input" placeholder="请输入姓名" v-model="personnelInfo.name"
:maxlength="15" />
</view>
<view class="form-row">
<text class="form-label">职位</text>
<input class="form-input" placeholder="请输入职位" v-model="personnelInfo.position"
:maxlength="15" />
</view>
<view class="form-row">
<text class="form-label">ID</text>
<input class="form-input" placeholder="请输入ID号" v-model="personnelInfo.code"
:maxlength="15" />
</view>
</view>
</view>
<!-- 人员信息登记 -->
<view class="form-section" v-if="hasPermission('5')">
<view class="mode-buttons">
<view class="section-title">发送信息</view>
<button class="send-btn" @click.stop="sendTextMessage">发送</button>
</view>
<view class="form-row">
<input class="form-input1" maxlength="20" placeholder="请输入文字" v-model="messageToSend" />
</view>
</view>
<!-- 产品信息 -->
<view v-if="hasPermission('6')">
<view class="section-title">产品信息</view>
<view class="mode-buttons">
<view class="mode_1" @click="productparams">
<image src="/static/images/common/cp.png" class="cpIMG" mode="aspectFit"></image>
<view class="">产品参数</view>
</view>
<view class="mode_1" @click="operatingInst">
<image src="/static/images/common/sm.png" class="cpIMG" mode="aspectFit"></image>
<view class="">操作说明</view>
</view>
<view class="mode_1" @click="operatingVideo">
<image src="/static/images/common/sp.png" class="cpIMG" mode="aspectFit"></image>
<view class="">操作视频</view>
</view>
</view>
</view>
</view>
<!-- 弹框 -->
<view class="agreement-mask" v-if="lightModeA" @click.stop="closePopup">
<!-- 灯光模式弹窗 -->
<view class="agreement-popup" @click.stop>
<!-- 标题 -->
<view class="popup-title"> {{ popupTitle }}</view>
<view class="popup-content">
<view v-for="(item, index) in items" :key="index">
<view class="item" :class="{ 'selected': item.selected }" @click="onItemClick(index)">
<image :src="item.image" class="setIMG"></image>
{{ item.text }}
</view>
</view>
</view>
<!-- 按钮组 -->
<view class="popup-buttons">
<button class="agree" @click="handleSumbit">确定</button>
</view>
</view>
</view>
<!-- 上传开机画面弹框 -->
<view class="agreement-mask" v-if="lightModeB" @click.stop="closePopup">
<!-- 上传画面弹窗 -->
<view class="agreement-popupB" @click.stop>
<!-- 标题 -->
<view class="popup-title">上传开机画面</view>
<view class="popup-content">
<view class="example-body">
<view class="icoContent" @click="checkImgUpload">
<image v-if="selectedImage" :src="selectedImage" mode="aspectFit" class="img"
style="width: 100%; height: 100%;"></image>
<image v-else mode="aspectFit" class="img"
src="/static/images/6155/DeviceDetail/add.png"></image>
</view>
</view>
</view>
<!-- 按钮组 -->
<view class="popup-buttons">
<button class="agree" @click="handleupload">确定</button>
</view>
</view>
</view>
<!--===================== 激光提示框================== -->
<view class="agreement-mask" v-if="lightModeC" @click.stop="closePopup">
<!-- 上传画面弹窗 -->
<view class="agreement-popupC" @click.stop>
<!-- 标题 -->
<view class="popup-title">{{ isLaserOn ? '确认关闭激光模式?' : '确认开启激光模式?' }}</view>
<view class="popup-content">
<view class="popup-Title" v-if="!isLaserOn">
<view>注意事项</view>
<view>1.禁止直视光源或反射面</view>
<view>2.避免直射人或易燃物</view>
<view>3.需佩戴相应专业防护眼镜</view>
</view>
</view>
<!-- 按钮组 -->
<view class="popup-buttons">
<button class="btn disagree" @click="handleDisagree">取消</button>
<button class="btn agreeBtn" @click.stop="handleBtn">确定</button>
</view>
</view>
</view>
<!-- 人员信息成功提示弹框 -->
<CustomPopup v-if="popupType === 'person'" :show="showPopupFlag" :message="popupMessage"
icon="/static/images/common/sendSucc.png" :confirm-text="popupConfirmText" :show-cancel="false"
@confirm="onPopupConfirm" />
<!-- 开机log上传成功的弹框提示 -->
<CustomPopup v-if="popupType === 'logo'" :show="showPopupFlag" :message="popupMessage"
icon="/static/images/common/upload.png" :confirm-text="popupConfirmText" :show-cancel="false"
@confirm="onPopupConfirm" />
<!--============= 电量低于提示弹框=========== -->
<CustomPopup v-if="popupType === 'bettery'" :show="showPopupFlag"
popupBorder="1rpx solid rgba(224, 52, 52, 0.3)" :message="popupMessage" title="设备电量低于20%"
titleColor="rgba(224, 52, 52, 1)" icon="/static/images/common/path.png" :confirm-text="popupConfirmText"
:show-cancel="true" @cancel="onPopupConfirm" @confirm="onPopupConfirm"
confirmBtnBg="rgba(224, 52, 52, 1)" confirmBtnColor="#fff" />
<!-- 解除报警 -->
<CustomPopup v-if="popupType === 'cancel'" :show="showPopupFlag"
popupBorder="1rpx solid rgba(224, 52, 52, 0.3)" :message="popupMessage"
icon="/static/images/6170/svg.png" :confirm-text="popupConfirmText" :show-cancel="false"
@confirm="onPopupConfirmPolice" confirmBtnBg="rgba(224, 52, 52, 1)" confirmBtnColor="#fff" />
</view>
</view>
</template>
<script>
import MqttClient from '@/utils/mqtt.js';
import {
generateShortId,
getdeviceSTatus
} from '@/utils/function.js';
import {
deviceDetail,
registerPersonInfo,
deviceSendMessage,
deviceShareId,
lightModeSettings, //灯光模式设置
laserModeSettings, //激光模式设置
lightBrightnessSettings, //灯光亮度设置
deviceRealTimeStatus //设备状态
} from '@/api/6170/deviceControl.js'
import {
baseURL,
getToken,
clientid
} from '@/utils/request'
import {
deviceSendAlarmMessage
} from '@/api/6170/callPolice.js'
export default {
data() {
return {
lastBrightnessTime: 0,
isCardSliding: false,
cardRect: null,
touchStartX: 0,
touchStartY: 0,
pageLoading: true,
navBarHeight: 70 + uni.getSystemInfoSync().statusBarHeight,
navTitle: "",
sliderValue: 25,
lightModeA: false,
currentMainMode: '强光',
currentlaserMode: "关闭",
lightModeB: false,
lightModeC: false, //激光提示框
items: [],
pendingMainMode: null, // 选中的索引
isFormExpanded: true, // 默认展开
deviceID: '',
itemInfo: {},
mqttClient: null,
messageToSend: '',
personnelInfo: {
unitName: '',
name: '',
position: '',
code: '',
},
deviceInfo: {},
activePermissions: [], // 存储当前设备的权限数组
isSharedDevice: false, // 标记是否来自分享
isRightIconVisible: false,
showPopupFlag: false, //是否显示弹框
popupMessage: '',
popupConfirmText: '确认',
showUploadPopup: false,
selectedImage: null, // 添加这个变量来存储选择的图片
selectedItemIndex: 0,
popupType: 'person', //弹框类型
isLaserOn: false,
isSending: false,
isProcessing: false,
isLoading: false, // 主加载状态
isPolling: false // 轮询状态
}
},
computed: {
computedDeviceId() {
return this.apiType === 'listA' ? this.deviceID : this.itemInfo.deviceId;
},
popupTitle() {
return this.modeType === 'main' ? '灯光模式' : '激光模式';
}
},
methods: {
// 点击弹框外的区域关闭
closePopup() {
this.lightModeA = false;
this.lightModeB = false;
this.lightModeC = false;
this.selectedImage = '';
this.file = null;
},
// *******定位******
gpsPosition(item) {
// 添加调试日志
uni.navigateTo({
url: '/pages/common/map/index',
events: {
ack: function(data) {}
},
success: (res) => {
res.eventChannel.emit('Map', {
data: item
});
}
})
},
// ***********进度条***********
cardTouchStart(e) {
if (!this.cardRect) return;
this.touchStartX = e.touches[0].clientX;
this.touchStartY = e.touches[0].clientY;
},
cardTouchMove(e) {
if (!this.cardRect || e.touches.length === 0) return;
const deltaX = e.touches[0].clientX - this.touchStartX;
const deltaY = e.touches[0].clientY - this.touchStartY;
// 只有当水平滑动距离大于垂直距离时,才判断为有效滑动
if (Math.abs(deltaX) > Math.abs(deltaY)) {
this.isCardSliding = true;
this.updateSliderFromTouch(e);
}
},
cardTouchEnd(e) {
if (this.isCardSliding) {
// 触摸结束时,调用滑动结束的处理函数来发送最终值
this.onSliderChangeEnd({
detail: {
value: this.sliderValue
}
});
this.isCardSliding = false;
}
this.touchStartX = 0;
this.touchStartY = 0;
},
updateSliderFromTouch(e) {
const touchX = e.touches[0].clientX;
const cardLeft = this.cardRect.left;
const cardWidth = this.cardRect.width;
let relativeX = touchX - cardLeft;
// 边界处理
if (relativeX < 0) relativeX = 0;
if (relativeX > cardWidth) relativeX = cardWidth;
const newValue = Math.round((relativeX / cardWidth) * 100);
// 调用已有的节流函数来更新值和发送命令
this.onSliderChanging({
detail: {
value: newValue
}
});
},
onSliderChanging(e) {
if (!this.cardRect) return;
let value = e.detail.value;
if (value < 10) {
value = 10;
}
this.sliderValue = value; // 实时更新UI
const now = Date.now();
// 使用节流防止指令发送过于频繁
if (now - this.lastBrightnessTime > 200) { // 200毫秒节流
this.lastBrightnessTime = now;
// 增加轻微的震动反馈,提升手感
uni.vibrateShort({
type: 'light'
});
let data = {
deviceId: this.computedDeviceId,
instructValue: this.sliderValue + '.00',
deviceImei: this.itemInfo.deviceImei,
}
lightBrightnessSettings(data).then((res) => {
if (res.code !== 200) {
// 可以在这里处理错误但滑动中不建议用toast
}
})
}
},
onSliderChangeEnd(e) {
if (this.deviceInfo.onlineStatus !== 1) {
uni.showToast({
title: '设备已离线',
icon: 'none'
});
return;
}
let value = e.detail.value;
if (value < 10) {
value = 10;
}
this.sliderValue = value;
let data = {
deviceId: this.computedDeviceId,
instructValue: this.sliderValue + '.00',
deviceImei: this.itemInfo.deviceImei,
}
lightBrightnessSettings(data).then((res) => {
if (res.code == 200) {
// 可以在此处理成功反馈
} else {
uni.showToast({
icon: 'none',
title: res.msg
})
}
})
},
selectMode(type) {
this.modeType = type;
this.lightModeA = true;
if (type === 'main') {
this.items = [{
text: '强光',
selected: this.currentMainMode === '强光', // 修正匹配条件
image: '/static/images/6170/sett.png',
instructValue: '1'
},
{
text: '弱光',
selected: this.currentMainMode === '弱光',
image: '/static/images/6170/rg.png',
instructValue: '2'
},
{
text: '爆闪',
selected: this.currentMainMode === '爆闪',
image: '/static/images/6170/bs.png',
instructValue: '3'
},
{
text: '泛光',
selected: this.currentMainMode === '泛光',
image: '/static/images/6170/settt.png',
instructValue: '4'
},
{
text: '关闭',
selected: this.currentMainMode === '关闭',
image: '/static/images/6170/close.png',
instructValue: '0'
},
];
}
},
// 人员信息登录
toggleForm() {
this.isFormExpanded = !this.isFormExpanded;
},
onItemClick(index) {
const item = this.items[index];
// 激光模式特殊处理
if (item.text === '激光') {
this.lightModeC = true;
return;
}
// 只更新临时选中状态
this.selectedItemIndex = index;
// UI高亮效果不影响实际模式
this.items = this.items.map((item, i) => ({
...item,
selected: i === index
}));
// 显示临时选中的模式名称(视觉反馈)
this.pendingMainMode = item.text;
},
// 灯光模式的确认
handleSumbit() {
if (this.deviceInfo.onlineStatus !== 1) {
uni.showToast({
title: '设备已离线',
icon: 'none'
});
return;
}
// 防重复提交
if (this.isProcessing) return;
let loadingShown = false;
uni.showLoading({
title: '处理中...',
mask: true
});
loadingShown = true;
this.isProcessing = true
if (this.selectedItemIndex === null) return;
const selectedItem = this.items[this.selectedItemIndex];
let data = {
deviceId: this.computedDeviceId,
instructValue: selectedItem.instructValue,
deviceImei: this.itemInfo.deviceImei,
typeName: this.itemInfo.typeName,
};
lightModeSettings(data).then((res) => {
if (res.code == 200) {
// 只有确认成功才更新实际模式,选中模式
this.currentMainMode = this.pendingMainMode;
this.selectedItemIndex = selectedItem;
uni.showToast({
title: res.msg,
icon: 'none'
})
uni.hideLoading();
this.lightModeA = false;
this.isProcessing = false
loadingShown = false
} else {
uni.showToast({
title: res.msg,
icon: 'none'
})
this.isProcessing = false
loadingShown = false
uni.hideLoading();
}
})
},
// 激光模式
lasermode() {
this.lightModeC = true
},
// 激光确认框提交
handleBtn() {
if (this.deviceInfo.onlineStatus !== 1) {
uni.showToast({
title: '设备已离线',
icon: 'none'
});
return;
}
// 防重复提交
if (this.isProcessing) return;
let loadingShown = false;
uni.showLoading({
title: '处理中...',
mask: true
});
loadingShown = true;
this.isProcessing = true
const instructValue = this.isLaserOn ? 0 : 1;
let data = {
deviceId: this.computedDeviceId,
instructValue: instructValue,
deviceImei: this.itemInfo.deviceImei,
typeName: this.itemInfo.typeName,
};
laserModeSettings(data).then((res) => {
if (res.code == 200) {
uni.showToast({
icon: 'none',
title: res.msg
});
// 更新状态
this.isLaserOn = !this.isLaserOn;
this.currentlaserMode = this.isLaserOn ? "开启" : "关闭";
this.lightModeC = false;
this.isProcessing = false
loadingShown = false
} else {
uni.showToast({
title: res.msg,
icon: 'none'
});
this.isProcessing = false
loadingShown = false
}
})
},
//激光取消
handleDisagree() {
this.lightModeC = false
},
// 上传开机画面
uploadStartup() {
this.lightModeB = true
},
// 上传开机画面
checkImgUpload() {
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
// 将选择的图片赋值给selectedImage
const file = res.tempFiles[0];
const fileSize = file.size || 0;
if (fileSize > 2 * 1024 * 1024) {
console.log(`文件过大: ${fileSize} 字节`); // 调试日志
uni.showToast({
title: '图片大小不能超过2MB',
icon: 'none',
duration: 3000
});
return;
}
this.selectedImage = res.tempFilePaths[0];
console.log('选择的图片:', res);
},
fail: (err) => {
uni.showToast({
title: '选择图片失败',
icon: 'none'
});
}
});
},
// 上传开机画面确认按键
handleupload() {
let loadingShown = false;
if (!this.selectedImage) {
uni.showToast({
title: '请上传一张图片',
icon: 'none'
});
return;
}
if (this.deviceInfo.onlineStatus !== 1) {
uni.showToast({
title: '设备已离线',
icon: 'none'
});
return;
}
// 显示上传加载状态
uni.showLoading({
title: '图片上传中...',
mask: true
});
loadingShown = true;
this.isPolling = true; // 标记开始轮询
uni.uploadFile({
url: baseURL + '/app/bjq/device/uploadLogo',
filePath: this.selectedImage,
name: 'file',
formData: {
deviceId: this.apiType === 'listA' ? this.deviceID : this.itemInfo.deviceId,
deviceImei: this.itemInfo.deviceImei,
typeName: this.itemInfo.typeName,
},
header: {
'Authorization': 'Bearer ' + getToken(),
'clientid': clientid(),
},
complete: async (res) => {
try {
uni.hideLoading(); // 首先关闭“
const responseData = JSON.parse(res.data);
if (responseData.code === 200) {
uni.showLoading({
title: '等待设备响应...',
mask: true
});
try {
// 获取设备状态
// 4. 获取设备状态
let deviceImei = this.itemInfo.deviceImei
//let typeName = this.itemInfo.typeName
let batchId = ''
const statusRes = await getdeviceSTatus({
functionMode: 1,
batchId,
typeName:'FunctionAccessStatusRule',
deviceImei,
interval: 800
},
deviceRealTimeStatus
);
if (statusRes.data.functionAccess === 'OK') {
uni.hideLoading();
this.selectedImage = '';
this.file = null;
this.popupType = 'logo';
this.showPopupFlag = true;
this.popupMessage = '上传成功';
this.lightModeB = false;
}
} catch (error) {
this.isPolling = false; // 标记轮询结束
uni.hideLoading();
uni.showToast({
title: error.message,
icon: 'none'
});
}
} else {
this.isPolling = false; // 标记轮询结束
uni.hideLoading();
uni.showToast({
title: responseData.msg,
icon: 'none'
});
}
} catch (e) {
this.isPolling = false; // 标记轮询结束
uni.hideLoading();
uni.showToast({
title: e.message,
icon: 'none'
});
}
}
});
},
// 分享
shareUp() {
uni.navigateTo({
url: '/pages/6170/share/index',
events: {
ack: function(data) {}
},
success: (res) => {
res.eventChannel.emit('share', {
data: this.itemInfo,
});
}
})
},
// 发送人员信息
async sendPersonnelInfo() {
if (this.isSending) return;
const requiredFields = [{
field: 'unitName',
message: '单位名称不能为空'
},
{
field: 'name',
message: '姓名不能为空'
},
{
field: 'position',
message: '职位不能为空'
},
{
field: 'code',
message: 'ID不能为空'
}
];
for (const {
field,
message
}
of requiredFields) {
if (!this.personnelInfo[field]) {
uni.showToast({
title: message,
icon: 'none'
});
return;
}
}
if (this.deviceInfo.onlineStatus !== 1) {
uni.showToast({
title: '设备已离线',
icon: 'none'
});
return;
}
this.isSending = true;
let loadingShown = false;
try {
uni.showLoading({
title: '发送中...',
mask: true
});
loadingShown = true;
// 2. 准备请求数据
const data = {
code: this.personnelInfo.code,
name: this.personnelInfo.name,
position: this.personnelInfo.position,
unitName: this.personnelInfo.unitName,
deviceId: this.computedDeviceId,
deviceImei: this.itemInfo.deviceImei
};
const registerRes = await registerPersonInfo(data);
if (registerRes.code == 200) {
this.popupType = 'person';
this.showPopupFlag = true;
this.popupMessage = '人员信息发送成功';
} else {
uni.showToast({
title: registerRes.msg,
icon: 'none'
});
}
} catch (error) {
uni.showToast({
title: error.msg,
icon: 'none'
});
} finally {
uni.hideLoading();
this.isSending = false;
}
},
// 所有弹框,确定关闭按钮
onPopupConfirm() {
this.showPopupFlag = false
},
// 强制报警()
handlePolice() {
this.popupType = 'cancel';
this.popupMessage = '确认要解除所选设备的报警状态';
this.showPopupFlag = true;
},
// 解除报警逻辑
async onPopupConfirmPolice() {
if (this.deviceInfo.onlineStatus !== 1) {
uni.showToast({
title: '设备已离线',
icon: 'none'
});
return;
}
this.isPolling = true; // 标记开始轮询
try {
uni.showLoading({
title: '解除告警中...',
mask: true
});
// 2. 准备请求数据
const batchId = generateShortId();
const data = {
deviceIds: [this.computedDeviceId],
batchId: batchId,
typeName: this.itemInfo.typeName,
deviceImeiList: [this.itemInfo.deviceImei],
instructValue: 0, // '解除报警'
};
// 3.解除告警状态
const registerRes = await deviceSendAlarmMessage(data);
if (registerRes.code !== 200) {
uni.showToast({
title: registerRes.msg,
icon: 'none'
})
return
}
// 4. 获取设备状态FunctionAccessBatchStatusRule 批量
let deviceImei = this.itemInfo.deviceImei
let typeName = this.itemInfo.typeName
const statusRes = await getdeviceSTatus({
functionMode: 2,
batchId,
typeName:'FunctionAccessBatchStatusRule',
deviceImei,
interval: 500
},
deviceRealTimeStatus
);
if (statusRes.data.functionAccess === 'OK') {
uni.showToast({
title: statusRes.msg,
icon: 'none'
});
uni.$emit('deviceStatusUpdate', {});
this.showPopupFlag = false
if (this.apiType === 'listA') {
this.fetchDeviceDetail(this.computedDeviceId)
} else {
// 分享权限详情
this.fetchSharedDeviceDetail(this.itemInfo.id)
}
}
} catch (error) {
uni.showToast({
title: error.message,
icon: 'none'
});
} finally {
this.isPolling = false; // 标记开始轮询
uni.hideLoading();
}
},
// 发送文本消息
async sendTextMessage() {
if (this.deviceInfo.onlineStatus !== 1) {
uni.showToast({
title: '设备已离线',
icon: 'none'
});
return;
}
// 防重复提交
if (this.isSending) return;
if (!this.messageToSend) {
uni.showToast({
title: '请输入要发送的文字',
icon: 'none'
});
return;
}
this.isSending = true;
let loadingShown = false;
this.isPolling = true; // 标记开始轮询
try {
uni.showLoading({
title: '正在发送...',
mask: true
});
loadingShown = true;
// 2. 准备请求数据
const batchId = generateShortId();
const data = {
sendMsg: this.messageToSend,
deviceIds: [this.computedDeviceId],
typeName: this.itemInfo.typeName,
batchId: batchId,
deviceImeiList: [this.itemInfo.deviceImei]
};
// 3.人员信息
const registerRes = await deviceSendMessage(data);
if (registerRes.code !== 200) {
uni.showToast({
title: registerRes.msg,
icon: 'none'
})
return
}
// 4. 获取设备状态
let deviceImei = this.itemInfo.deviceImei
//let typeName = this.itemInfo.typeName
const statusRes = await getdeviceSTatus({
functionMode: 2,
batchId,
typeName:'FunctionAccessBatchStatusRule',
deviceImei,
interval: 500
},
deviceRealTimeStatus
);
// 只有当状态为'OK'时才显示成功弹窗
if (statusRes.data.functionAccess === 'OK') {
this.popupType = 'person';
this.showPopupFlag = true;
this.popupMessage = '信息发送成功';
}
} catch (error) {
uni.showToast({
title: error.message,
icon: 'none'
});
} finally {
this.isPolling = false; // 标记轮询结束
uni.hideLoading();
this.isSending = false;
}
},
// 统一处理返回方法
handleDeviceData(res, isFromShared = false, isUpdate = false) {
if (res.code == 200) {
// 最后关闭加载状态
this.pageLoading = false
this.deviceInfo = res.data
if (!isUpdate) {
this.personnelInfo = {
unitName: res.data.personnelInfo.unitName || '',
name: res.data.personnelInfo.name || '',
code: res.data.personnelInfo.code || '',
position: res.data.personnelInfo.position || '',
};
this.messageToSend = res.data.sendMsg || '';
this.sliderValue = res.data.lightBrightness || '25'
let mainLightMode = parseInt(res.data.mainLightMode);
// 取主灯光模式初始值
this.currentMainMode = this.getMainLightModeLabel(mainLightMode)
console.log(this.currentMainMode, 'this.currentMainMode');
const laserMode = res.data.LaserLightMode;
this.isLaserOn = laserMode === 1;
this.currentlaserMode = this.isLaserOn ? "开启" : "关闭";
}
// 将权限字符串转换为数组 ["1", "2"]
if (isFromShared) {
this.isSharedDevice = true;
this.activePermissions = res.data.permission ? res.data.permission.split(',') : [];
} else {
this.isSharedDevice = false;
this.activePermissions = []; // 非分享设备清空权限
}
// 只有不是轮询状态时才关闭loading
if (!this.isPolling) {
this.pageLoading = false;
uni.hideLoading(); // 确保关闭
}
} else {
// 只有不是轮询状态时才关闭loading
if (!this.isPolling) {
this.pageLoading = false;
uni.hideLoading(); // 确保关闭
}
}
},
// 检查权限的方法
hasPermission(permissionCode) {
// 如果还在加载中直接返回false
if (this.pageLoading) return false
// 如果不是分享设备,默认有全部权限
if (!this.isSharedDevice) return true
return this.activePermissions.includes(permissionCode)
},
// 获取设备详情(普通详情)
async fetchDeviceDetail(id, isUpdate = false) {
try {
const res = await deviceDetail(id)
if (res.code == 200) {
this.handleDeviceData(res, false, isUpdate, true)
}
} catch (error) {
// 只有不是轮询状态时才关闭loading
if (!this.isPolling) {
this.pageLoading = false;
uni.hideLoading(); // 确保关闭
}
}
},
// 获取分享设备详情
async fetchSharedDeviceDetail(id) {
try {
const res = await deviceShareId(id)
if (res.code == 200) {
this.handleDeviceData(res, false, false, true)
}
} catch (error) {
if (!this.isPolling) {
this.pageLoading = false;
uni.hideLoading(); // 确保关闭
}
}
},
// 操作说明
operatingInst() {
let id = this.computedDeviceId
uni.navigateTo({
url: `/pages/common/operatingInstruct/index?id=${id}`
})
},
// 产品参数
productparams() {
let id = this.computedDeviceId
uni.navigateTo({
url: `/pages/common/productDes/index?id=${id}`
})
},
// 操作视频
operatingVideo() {
let id = this.computedDeviceId
uni.navigateTo({
url: `/pages/common/operationVideo/index?id=${id}`
})
},
// 发送mqtt查询设备信息
queryDeviceStatus() {
if (this.mqttClient && this.mqttClient.isConnected()) {
const topic = `B/${this.itemInfo.deviceImei}`;
const message = '{"instruct":[12,0,0,0,0]}';
this.mqttClient.publish(topic, message, {
qos: 1
});
} else {
console.log('MQTT not connected, cannot query status.');
}
},
// 灯光模式共用封装共其他地方使用
getMainLightModeLabel(mode) {
const modeMap = {
0: '关闭',
1: '强光',
2: '弱光',
3: '爆闪',
4: '泛光'
}
return modeMap[mode] || (console.log('未知的灯光模式:', mode), '未知');
}
},
onLoad(options) {
const eventChannel = this.getOpenerEventChannel();
// 监听 'detailData' 事件,获取传过来的数据
uni.showLoading({
title: '加载中...'
})
eventChannel.on('detailData', (data) => {
console.log(data, 'data');
this.itemInfo = data.data;
this.deviceID = data.data.id;
this.navTitle = data.data.deviceName;
this.apiType = data.apiType
// 根据 apiType 设置右图标的显示状态
this.isRightIconVisible = this.apiType === 'listA';
// 初始化并连接MQTT
this.mqttClient = new MqttClient();
this.mqttClient.connect(() => {
// 订阅来自设备的状态更新
const statusTopic = `A/${this.itemInfo.deviceImei}`;
this.mqttClient.subscribe(statusTopic, (payload) => {
try {
console.log(`收到来自 ${statusTopic} 的消息:`, payload);
const parsedMessage = typeof payload === 'string' ? JSON.parse(
payload) :
payload;
const deviceState = parsedMessage.state; // 直接取 state 数组
// 切换主灯光模式,亮度值设备同步
if (!deviceState) return;
// 1设备切换灯光实时返回
switch (deviceState[0]) {
case 1:
this.currentMainMode = this.getMainLightModeLabel(deviceState[
1]);
this.sliderValue = deviceState[2];
this.deviceInfo.batteryRemainingTime = deviceState[3];
break;
// 12为设备实时上报
case 12:
this.currentMainMode = this.getMainLightModeLabel(deviceState[
1]);
this.isLaserOn = deviceState[2] === 1;
this.currentlaserMode = this.isLaserOn ? "开启" : "关闭";
if (this.deviceInfo) {
this.deviceInfo.batteryPercentage = deviceState[3];
this.deviceInfo.chargeState = deviceState[4];
this.deviceInfo.batteryRemainingTime = deviceState[5];
}
setTimeout(() => {
if (this.deviceInfo.batteryPercentage <
20) {
this.popupType = 'bettery';
this.popupMessage = '请及时充电';
this.showPopupFlag = true;
}
if (this.apiType === 'listA') {
this.fetchDeviceDetail(data.data.id)
} else {
// 分享权限详情
this.fetchSharedDeviceDetail(data.data.id)
}
}, 10000);
// 上报电量,在列表里面同步
uni.$emit('deviceStatusUpdate', {
message: parsedMessage,
timestamp: new Date().getTime()
});
break;
default:
console.log('未知的 deviceState 类型:', deviceState[0]);
}
} catch (error) {
console.log('解析MQTT消息失败:', error, '原始消息:', payload);
}
});
// mqtt连接成功后上报一次设备状态
this.queryDeviceStatus();
})
if (this.apiType === 'listA') {
this.fetchDeviceDetail(data.data.id)
} else {
// 查分享权限详情
this.fetchSharedDeviceDetail(data.data.id)
}
});
eventChannel.emit('ack', {
})
},
onUnload() {
// 页面卸载时断开MQTT连接
if (this.mqttClient) {
this.mqttClient.disconnect();
}
},
}
</script>
<style scoped>
.page-loading-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #121212;
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
/* 优化权限控制区域的显示 */
.mode-section,
.form-section {
transition: opacity 0.3s ease;
}
.device-detail-container {
padding: 30rpx;
background: #121212;
min-height: 100vh;
}
.status-bar {
display: flex;
justify-content: space-between;
margin-bottom: 40rpx;
}
.time {
font-size: 36rpx;
font-weight: bold;
}
.battery {
font-size: 36rpx;
}
.battery-section {
background-color: rgb(26, 26, 26);
border-radius: 16rpx;
padding: 30rpx;
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
align-items: center;
}
.battery-info,
.duration {
display: flex;
flex-direction: column;
align-items: center;
}
.bipImg {
width: 204rpx;
height: 144rpx;
margin-top: 30rpx;
}
.dlIMG {
width: 52rpx;
height: 52rpx;
}
.chargeStateIMG {
width: 27rpx;
height: 37rpx;
margin-left: 10rpx;
}
.cpIMG {
width: 66rpx;
height: 66rpx;
}
.battery-sectionLeft {
width: 308rpx;
height: 220rpx;
background: rgba(42, 42, 42, 0.5);
border-radius: 16rpx;
text-align: center;
align-items: center;
line-height: 220rpx;
}
.percentage {
font-size: 48rpx;
font-weight: bold;
color: #007AFF;
}
.label {
font-size: 28rpx;
color: #666;
margin-top: 10rpx;
}
.battery-v1 {
display: flex;
align-items: center;
margin-bottom: 10rpx;
}
.battery-v2 {
color: rgba(255, 255, 255, 0.87);
margin-left: 18rpx;
font-size: 32rpx;
}
.battery-v3 {
color: rgba(255, 255, 255, 0.6);
margin-left: 25rpx;
font-size: 26rpx;
}
.mode-v3 {
color: rgba(255, 255, 255, 0.6);
font-size: 26rpx;
padding-top: 10rpx;
}
.info-card {
background-color: rgb(26, 26, 26);
border-radius: 16rpx;
padding: 10rpx 20rpx 5rpx 20rpx;
margin-bottom: 20rpx;
}
.info-row {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
}
.info-label {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.87);
white-space: nowrap;
}
.info-value {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.87);
}
.status-running {
color: rgba(255, 255, 255, 0.6);
text-align: end;
}
.locationGPS {
width: 88%;
text-align: end;
float: right;
line-height: 50rpx;
}
.control-card {
background-color: rgb(26, 26, 26);
border-radius: 16rpx;
margin-bottom: 20rpx;
position: relative;
}
.light {
position: absolute;
/* top:10rpx; */
color: rgba(74, 74, 74, 0.87);
top: 58rpx;
left: 108rpx
}
.section-title {
font-size: 32rpx;
margin-bottom: 20rpx;
display: block;
color: rgba(255, 255, 255, 0.87);
}
.slider-container {
display: flex;
align-items: center;
}
.slider {
flex: 1;
margin: 0 20rpx;
}
.mode-buttons {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.mode_1 {
width: 180rpx;
height: 180rpx;
border-radius: 16rpx;
background: rgb(26, 26, 26);
align-items: center;
text-align: center;
color: rgba(255, 255, 255, 0.6);
padding-top: 37rpx;
}
.mode-v1 {
background: #1A1A1A;
border-radius: 18rpx;
height: 150rpx;
margin-bottom: 20rpx;
width: 47%;
display: flex;
text-align: center;
}
.mode-v2 {
display: flex;
align-items: center;
text-align: center;
margin-left: 40rpx;
}
.callpolice {
display: flex;
justify-content: space-between;
border-radius: 16rpx;
background: rgba(224, 52, 52, 1);
padding: 15rpx;
margin-bottom: 15rpx;
color: #fff;
line-height: 35rpx;
font-size: 26rpx;
}
.example-body {
position: absolute;
left: 50%;
top: 65%;
width: 100%;
transform: translate(-20%, -100%);
}
.icoContent {
width: 320rpx;
height: 160rpx;
border-radius: 8rpx;
background: rgba(58, 58, 58, 1);
text-align: center;
line-height: 200rpx;
}
.img {
width: 62rpx;
height: 62rpx;
}
.example_title {
color: rgba(255, 255, 255, 0.87);
}
.mode-btn {
width: 48%;
height: 80rpx;
line-height: 80rpx;
background-color: #f5f5f5;
color: #333;
border-radius: 8rpx;
font-size: 28rpx;
border: none;
}
.mode-btn.active {
background-color: #007AFF;
color: #fff;
}
.form-section {
background-color: rgb(26, 26, 26);
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 30rpx;
}
.form-row {
display: flex;
align-items: center;
margin-bottom: 20rpx;
background: rgba(42, 42, 42, 1);
border-radius: 16rpx;
padding-left: 20rpx;
}
.form-label {
font-size: 32rpx;
color: rgba(255, 255, 255, 0.87);
white-space: nowrap;
}
.form-input {
height: 80rpx;
border: 1rpx solid transparent;
font-size: 32rpx;
color: rgba(255, 255, 255, 0.87);
width: 100%;
}
.form-input1 {
height: 80rpx;
border: 1rpx solid transparent;
font-size: 32rpx;
color: rgba(255, 255, 255, 0.87);
width: 98%;
}
.product-section {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
}
.product-tabs {
display: flex;
margin-top: 20rpx;
}
.tab {
padding: 15rpx 30rpx;
font-size: 28rpx;
color: #666;
border-bottom: 4rpx solid transparent;
}
.tab.active {
color: #007AFF;
border-bottom-color: #007AFF;
}
.action-buttons {
padding: 30rpx 0;
}
.send-btn {
background-color: rgb(187, 230, 0);
color: rgba(35, 35, 35, 0.87);
height: 50rpx;
line-height: 50rpx;
border-radius: 16rpx;
font-size: 26rpx;
width: 112rpx;
white-space: nowrap;
text-align: center;
position: absolute;
right: 70rpx;
}
.send-btn1 {
background-color: rgb(187, 230, 0);
color: rgba(35, 35, 35, 0.87);
height: 50rpx;
line-height: 50rpx;
border-radius: 16rpx;
font-size: 26rpx;
width: 112rpx;
white-space: nowrap;
text-align: center;
position: absolute;
right: 0rpx;
top: -70rpx
}
/* 遮罩层 */
.agreement-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
/* 弹窗主体 */
.agreement-popup {
width: 100%;
height: 50%;
background-color: rgb(42, 42, 42);
border-radius: 60rpx 60rpx 0rpx 0rpx;
padding: 40rpx;
box-sizing: border-box;
position: absolute;
bottom: 0rpx;
}
.agreement-popupB {
width: 100%;
height: 32%;
background-color: rgb(42, 42, 42);
border-radius: 60rpx 60rpx 0rpx 0rpx;
padding: 40rpx;
box-sizing: border-box;
position: absolute;
bottom: 0rpx;
}
.agreement-popupC {
width: 80%;
background-color: rgb(42, 42, 42);
border-radius: 40rpx;
padding: 40rpx;
box-sizing: border-box;
border: 1px solid rgba(255, 200, 78, 1);
}
.popup-Title {
color: rgba(255, 200, 78, 1);
}
/* 标题 */
.popup-title {
font-size: 36rpx;
text-align: center;
margin-bottom: 30rpx;
color: rgba(255, 255, 255, 0.87);
}
/* 内容文本 */
.popup-content {
font-size: 30rpx;
line-height: 1.6;
color: rgba(255, 255, 255, 0.87);
margin-bottom: 50rpx;
}
.item {
padding: 10px;
margin: 5px 0;
display: flex;
align-items: center;
cursor: pointer;
transition: background-color 0.3s;
}
.setIMG {
width: 24px;
height: 24px;
margin-right: 10px;
}
.item.selected {
background-color: rgb(58, 58, 58);
color: rgba(255, 255, 255, 0.87);
border-radius: 8rpx;
}
/* 按钮容器 */
.popup-buttons {
display: flex;
justify-content: space-between;
}
/*确定按钮 */
.agree {
background-color: rgb(187, 230, 0);
color: #232323;
border: none;
font-size: 24rpx;
height: 88rpx;
line-height: 88rpx;
font-size: 32rpx;
width: 100%;
position: absolute;
bottom: 0rpx;
left: 0rpx
}
/* 通用按钮样式 */
.btn {
flex: 1;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
font-size: 32rpx;
margin: 0 20rpx;
}
/* 不同意按钮 */
.disagree {
background-color: transparent;
color: rgba(255, 255, 255, 0.87);
border: 1px solid rgba(255, 200, 78, 0.7);
font-size: 24rpx;
color: rgba(255, 200, 78, 1);
}
/* 同意按钮 */
.agreeBtn {
background-color: rgba(255, 200, 78, 1);
color: #232323;
border: none;
font-size: 24rpx;
}
.right-icons {
/* display: flex; */
align-items: center;
}
.toggle-icon {
cursor: pointer;
font-size: 16px;
margin-top: -40rpx;
}
.icon {
font-size: 14px;
color: #fff;
}
.form-content {
transition: all 0.3s ease;
margin-top: 60rpx;
position: relative;
}
</style>