985 lines
31 KiB
Vue
985 lines
31 KiB
Vue
<template>
|
||
<view class="container">
|
||
<!-- 控制设置标题 -->
|
||
<view class="control-title">控制设置</view>
|
||
<uni-section title="请选择大棚:" :subTitle="imei" titleFontSize="18px" type="line">
|
||
<view class="uni-px-5 uni-pb-5">
|
||
<uni-data-select v-model="value" :localdata="range" @change="change"></uni-data-select>
|
||
</view>
|
||
</uni-section>
|
||
|
||
<uni-section title="实时温湿度" titleFontSize="16px" type="line" v-if="value!== 1">
|
||
<template v-slot:right >
|
||
{{ temp }}
|
||
</template>
|
||
|
||
<view>
|
||
<!-- 优化:温度卡片循环渲染 -->
|
||
<view class="uni-flex_control uni-row" >
|
||
<view
|
||
class="text uni-flex_control_one uni-view"
|
||
v-for="item in sensorCards.temp"
|
||
:key="item.key"
|
||
>
|
||
<text class="data" :style="fontStyle">
|
||
{{ liveData[item.key] }}
|
||
<p v-if="isEffectiveValue(liveData[item.key])" class="tempStyle">℃</p>
|
||
</text>
|
||
<text class="data">{{ item.label }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 优化:湿度卡片循环渲染 -->
|
||
<view class="uni-flex_control uni-row" >
|
||
<view
|
||
class="text uni-flex_control_two uni-view"
|
||
v-for="item in sensorCards.humi"
|
||
:key="item.key"
|
||
>
|
||
<text class="data" :style="fontStyle">
|
||
{{ liveData[item.key] }}
|
||
<p v-if="isEffectiveValue(liveData[item.key])" class="humiStyle"> %RH</p>
|
||
</text>
|
||
<text class="data">{{ item.label }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</uni-section>
|
||
|
||
<uni-section title="设备控制" titleFontSize="16px" type="line" v-if="value!== 1 && !['862538065276939','A','B','C'].includes(imei)">
|
||
<template v-slot:right >
|
||
{{ control }}
|
||
</template>
|
||
<!-- 优化:设备卡片循环渲染 -->
|
||
<view class="card-grid">
|
||
<view
|
||
class="control-card"
|
||
v-for="card in deviceCards"
|
||
:key="card.type"
|
||
@click="openTimeModal(card)"
|
||
>
|
||
<view class="card-text">
|
||
<text class="card-main">{{ card.name }}</text>
|
||
<view class="card-sub-wrapper">
|
||
<text class="card-sub" v-if="showStatusText">{{ show[card.type] || '未知' }}</text>
|
||
<text class="limit-time">
|
||
运行时间:{{ limitTimes[`${card.type}Limit`] && limitTimes[`${card.type}Limit`]!=='0' ? `${limitTimes[`${card.type}Limit`]} s` : '- -' }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
<view class="card-icon" :class="{ active: status[card.type] === 1 }" @click.stop="handleCardClick(1 - status[card.type], card.type)">
|
||
<!-- 加@click.stop防止冒泡触发卡片点击的弹窗事件 -->
|
||
<uni-icons
|
||
:type="status[card.type] === 1 ? 'circle' : 'circle-filled'"
|
||
size="24"
|
||
color="#fff"
|
||
/>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</uni-section>
|
||
|
||
|
||
|
||
|
||
<uni-popup ref="inputDialog" mode="center" >
|
||
<!-- 新增:修改运行时间的弹窗 -->
|
||
<view class="modal-container">
|
||
<view class="modal-title">{{ `【${selectedText} - ${currentCard.name}】运行时间` }}</view>
|
||
<view class="modal-input-wrap">
|
||
<text class="modal-label">当前时间:</text>
|
||
<text class="modal-current">{{ currentCardTime ? `${currentCardTime} 秒` : '未设置' }}</text>
|
||
</view>
|
||
<view class="modal-input-wrap" >
|
||
<text class="modal-label">修改后时间:</text>
|
||
<input
|
||
class="modal-input"
|
||
type="number"
|
||
v-model.number="newLimitTime"
|
||
/>
|
||
<!-- <uni-number-box v-model="newLimitTime" />-->
|
||
<text class="modal-unit">秒</text>
|
||
</view>
|
||
<view class="modal-btn-wrap">
|
||
<button class="modal-btn cancel" @click="close">取消</button>
|
||
<button class="modal-btn confirm" @click="confirmModifyTime">确定</button>
|
||
</view>
|
||
</view>
|
||
</uni-popup>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
// 优化:抽离魔法值常量
|
||
import UniPopupDialog from "../../uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue";
|
||
|
||
const SENSOR_MAP = {
|
||
temp1: "201", temp2: "202", temp3: "203", temp4: "204",
|
||
humi1: "101", humi2: "102", humi3: "103", humi4: "104"
|
||
};
|
||
const MQTT_TOPIC_SUFFIX = { UP: "/up", DOWN: "/down" };
|
||
|
||
import UniDatetimePicker from "../../uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue";
|
||
import UniPopup from "../../uni_modules/uni-popup/components/uni-popup/uni-popup.vue"; // 引入弹窗组件
|
||
import { findDtuDataByInfo } from "@/api/system/data";
|
||
import mqttUtil from '@/utils/mqtt';
|
||
import UniNumberBox from "../../uni_modules/uni-number-box/components/uni-number-box/uni-number-box.vue";
|
||
import {addLimit, getAgriByImei, updateLimit} from "../../api/system/assets/limit";
|
||
import {listAgri} from "../../api/system/assets/agri";
|
||
import {getNewSpecialData} from "../../api/data/specialData";
|
||
import store from "../../store";
|
||
|
||
export default {
|
||
dicts: ['sys_data_map'],
|
||
components: {
|
||
UniNumberBox,
|
||
UniPopupDialog,
|
||
UniDatetimePicker,
|
||
UniPopup // 注册弹窗组件
|
||
},
|
||
data() {
|
||
return {
|
||
temp: "",
|
||
mqttConfig: {
|
||
subscribeTopic:'/up',
|
||
},
|
||
value: 1,
|
||
selectedText: '',
|
||
// 优化:语义化变量名(替换原 hide: false)
|
||
showStatusText: false,
|
||
control: '正在加载中...',
|
||
range: [],
|
||
agriId:'',
|
||
imei:'',
|
||
publishTopic: '/down',
|
||
title:'',
|
||
message: {},
|
||
// 优化:声明响应式变量 connected
|
||
connected: false,
|
||
liveData: {
|
||
temp1: '数据加载中...',
|
||
temp2: '数据加载中...',
|
||
temp3: '数据加载中...',
|
||
temp4: '数据加载中...',
|
||
humi1: '数据加载中...',
|
||
humi2: '数据加载中...',
|
||
humi3: '数据加载中...',
|
||
humi4: '数据加载中...'
|
||
},
|
||
// 优化:温湿度卡片配置(固定顺序:温度1→2→3→4,湿度1→2→3→4)
|
||
sensorCards: {
|
||
temp: [
|
||
{ label: '温度1', key: 'temp1' },
|
||
{ label: '温度2', key: 'temp2' },
|
||
{ label: '温度3', key: 'temp3' },
|
||
{ label: '温度4', key: 'temp4' }
|
||
],
|
||
humi: [
|
||
{ label: '湿度1', key: 'humi1' },
|
||
{ label: '湿度2', key: 'humi2' },
|
||
{ label: '湿度3', key: 'humi3' },
|
||
{ label: '湿度4', key: 'humi4' }
|
||
]
|
||
},
|
||
// 优化:设备卡片配置(固定顺序)
|
||
deviceCards: [
|
||
{ type: 'jbk', name: '卷被开' },
|
||
{ type: 'jbg', name: '卷被关' },
|
||
{ type: 'jm1k', name: '卷膜1开' },
|
||
{ type: 'jm1g', name: '卷膜1关' },
|
||
{ type: 'jm2k', name: '卷膜2开' },
|
||
{ type: 'jm2g', name: '卷膜2关' },
|
||
{ type: 'jm3k', name: '卷膜3开' },
|
||
{ type: 'jm3g', name: '卷膜3关' }
|
||
],
|
||
// 卡片状态
|
||
show: {
|
||
jbk: "暂停",
|
||
jbg: "暂停",
|
||
jm1k: "暂停",
|
||
jm1g: "暂停",
|
||
jm2k: "暂停",
|
||
jm2g: "暂停",
|
||
jm3k: "暂停",
|
||
jm3g: "暂停"
|
||
},
|
||
// 新增:限位时间配置
|
||
limitTimes: {
|
||
jbkLimit: 0,
|
||
jbgLimit: 0,
|
||
jm1kLimit: 0,
|
||
jm1gLimit: 0,
|
||
jm2kLimit: 0,
|
||
jm2gLimit: 0,
|
||
jm3kLimit: 0,
|
||
jm3gLimit: 0
|
||
},
|
||
deviceType: '',
|
||
status: {
|
||
jbk: 0,
|
||
jbg: 0,
|
||
jm1k: 0,
|
||
jm1g: 0,
|
||
jm2k: 0,
|
||
jm2g: 0,
|
||
jm3k: 0,
|
||
jm3g: 0
|
||
},
|
||
fontStyle: '',
|
||
// 新增:弹窗相关变量
|
||
currentCard: {}, // 当前点击的卡片信息
|
||
currentCardTime: 0, // 当前卡片的运行时间
|
||
newLimitTime: 0, // 新的运行时间
|
||
// 新增:存储定时器(含快照信息),key=type,value={timerId, snapInfo}
|
||
timers: {}
|
||
};
|
||
},
|
||
onLoad() {
|
||
this.title="";
|
||
// 定义所有互斥的键对:[k键, g键]
|
||
const mutexPairs = [
|
||
['jbk', 'jbg'],
|
||
['jm1k', 'jm1g'],
|
||
['jm2k', 'jm2g'],
|
||
['jm3k', 'jm3g']
|
||
];
|
||
|
||
// 遍历处理每一组互斥规则(k=1则g=0,g=1则k=0)
|
||
mutexPairs.forEach(([kKey, gKey]) => {
|
||
if (this.status[kKey] === 1) this.status[gKey] = 0;
|
||
if (this.status[gKey] === 1) this.status[kKey] = 0;
|
||
});
|
||
},
|
||
onShow() {
|
||
this.getAgriList();
|
||
// 注册MQTT消息回调(接收设备消息)
|
||
mqttUtil.setOnMessageCallback(this.ackMessage);
|
||
// 更新连接状态
|
||
this.connected = mqttUtil.getMqttState().isConnected;
|
||
},
|
||
onUnload() {
|
||
// 移除MQTT消息回调(避免内存泄漏)
|
||
mqttUtil.removeOnMessageCallback();
|
||
// 清除所有定时器
|
||
this.clearAllTimers();
|
||
},
|
||
methods: {
|
||
|
||
getNewSpecialData() {
|
||
getNewSpecialData().then(response => {
|
||
if (response.code === 200 && response.data) {
|
||
this.makeSpecialData(response.data,false);
|
||
this.temp = "最后更新时间:"+response.data.time;
|
||
this.fontStyle = 'font-size:16px;'
|
||
}
|
||
})
|
||
},
|
||
// 新增:清除所有定时器(防止内存泄漏)
|
||
clearAllTimers() {
|
||
Object.keys(this.timers).forEach(type => {
|
||
if (this.timers[type]?.timerId) {
|
||
clearTimeout(this.timers[type].timerId);
|
||
}
|
||
});
|
||
this.timers = {};
|
||
},
|
||
change(e) {
|
||
this.imei = e;
|
||
if ((e === 'A' || e==='B' || e==='C')
|
||
&& store.getters && store.getters.name === 'admin' ) {
|
||
this.getNewSpecialData(e);
|
||
this.mqttConfig.subscribeTopic = `dtu/862538063921866${MQTT_TOPIC_SUFFIX.UP}`;
|
||
}
|
||
const selectedItem = this.range.find(item => item.value === e);
|
||
if (selectedItem) {
|
||
this.selectedText = selectedItem.text; // 获取展示文本
|
||
this.agriId = selectedItem.agriId;
|
||
this.title= this.selectedText;
|
||
if (e !== 'A' && e!=='B' && e!=='C') {
|
||
// 优化:使用常量拼接MQTT主题
|
||
this.publishTopic = `dtu/${this.imei}${MQTT_TOPIC_SUFFIX.DOWN}`;
|
||
this.mqttConfig.subscribeTopic = `dtu/${this.imei}${MQTT_TOPIC_SUFFIX.UP}`;
|
||
var queryParams = {
|
||
imei: this.imei
|
||
}
|
||
findDtuDataByInfo(queryParams).then(response => {
|
||
Object.keys(response.data).forEach(key => {
|
||
this.liveData[key] = response.data[key] || '已离线..';
|
||
});
|
||
this.temp = "最后更新时间:"+response.data.time;
|
||
this.fontStyle = 'font-size:16px;'
|
||
})
|
||
this.getAgriByImei();
|
||
}
|
||
} else {
|
||
this.selectedText = ''; // 无匹配项时清空
|
||
this.title='';
|
||
this.value=1;
|
||
this.agriId = '';
|
||
}
|
||
this.reset();
|
||
this.style="";
|
||
},
|
||
getAgriByImei() {
|
||
getAgriByImei(this.imei).then(response => {
|
||
if (response.code === 200) {
|
||
if (!response.data) {
|
||
return;
|
||
}
|
||
this.limitTimes = response.data;
|
||
}
|
||
})
|
||
},
|
||
getAgriList() {
|
||
listAgri().then(response => {
|
||
if (response.code === 200) {
|
||
this.range = response.rows.map(item => ({
|
||
agriId: item.id,
|
||
text: item.agriName,
|
||
value: item.imei // 提取并改名
|
||
}));
|
||
|
||
if (store.getters && store.getters.name === 'admin') {
|
||
this.range.push(
|
||
{
|
||
text:"八方南棚",
|
||
value:"A"
|
||
},
|
||
{
|
||
text:"九方春棚",
|
||
value:"B"
|
||
},
|
||
{
|
||
text:"十二方棚",
|
||
value:"C"
|
||
}
|
||
)
|
||
}
|
||
}
|
||
})
|
||
},
|
||
|
||
makeSpecialData(msgData, tag) {
|
||
const div10 = (v) => (v == null ? null : Math.round((Number(v)/10)*10)/10);
|
||
|
||
// 1. 提取B/C的tag对应键名(精简重复三元)
|
||
const B_KEYS = {
|
||
true: {temp1:'205',humi1:'105',temp2:'206',humi2:'106',temp3:'207',humi3:'107',temp4:'208',humi4:'108'},
|
||
false: {temp1:'temp5',humi1:'humi5',temp2:'temp6',humi2:'humi6',temp3:'temp7',humi3:'humi7',temp4:'temp8',humi4:'humi8'}
|
||
};
|
||
const C_KEYS = {
|
||
true: {temp1:'209',humi1:'109',temp2:'210',humi2:'110',temp3:'211',humi3:'111',temp4:'212',humi4:'112'},
|
||
false: {temp1:'temp9',humi1:'humi9',temp2:'temp10',humi2:'humi10',temp3:'temp11',humi3:'humi11',temp4:'temp12',humi4:'humi12'}
|
||
};
|
||
|
||
// 2. 简化传感器键名映射表
|
||
const IMEI_SENSOR_MAP = {
|
||
A: SENSOR_MAP,
|
||
B: B_KEYS[tag], // 直接取tag对应的键名,替代三元
|
||
C: C_KEYS[tag]
|
||
};
|
||
|
||
// 3. 确定传感器键名(精简变量名)
|
||
const isAdmin = store.getters?.name === 'admin';
|
||
const sk = (isAdmin && ['A','B','C'].includes(this.imei)) ? IMEI_SENSOR_MAP[this.imei] : IMEI_SENSOR_MAP.A;
|
||
|
||
// 4. 简化liveData的嵌套三元(核心优化)
|
||
this.liveData = Object.fromEntries(
|
||
Object.entries(sk).map(([k, skVal]) => [
|
||
k,
|
||
(tag
|
||
? div10(msgData[skVal])
|
||
: (this.imei === 'A' ? msgData[k] : msgData[skVal]))
|
||
|| "已离线..."
|
||
])
|
||
);
|
||
},
|
||
reset() {
|
||
Object.keys(this.show).forEach(key => {
|
||
this.show[key] = "暂停";
|
||
});
|
||
Object.keys(this.status).forEach(key => {
|
||
this.status[key] = 0;
|
||
});
|
||
Object.keys(this.liveData).forEach(key => {
|
||
this.liveData[key] = '数据加载中...';
|
||
});
|
||
this.limitTimes= {
|
||
jbkLimit: 0,
|
||
jbgLimit: 0,
|
||
jm1kLimit: 0,
|
||
jm1gLimit: 0,
|
||
jm2kLimit: 0,
|
||
jm2gLimit: 0,
|
||
jm3kLimit: 0,
|
||
jm3gLimit: 0
|
||
};
|
||
this.deviceType = '';
|
||
this.control = '正在加载中...';
|
||
this.message = {};
|
||
this.temp = '';
|
||
},
|
||
// 卡片点击事件(实际项目中调用接口修改状态) 功能标识
|
||
handleCardClick(status, type) {
|
||
// 校验
|
||
// 定义类型与提示文案的映射关系,减少重复代码
|
||
const tipMap = {
|
||
'jbk': {opposite: 'jbg', name: '卷被关', op: '卷被开'},
|
||
'jbg': {opposite: 'jbk', name: '卷被开', op: '卷被关'},
|
||
'jm1k': {opposite: 'jm1g', name: '卷膜1关', op: '卷膜1开'},
|
||
'jm1g': {opposite: 'jm1k', name: '卷膜1开', op: '卷膜1关'},
|
||
'jm2k': {opposite: 'jm2g', name: '卷膜2关', op: '卷膜2开'},
|
||
'jm2g': {opposite: 'jm2k', name: '卷膜2开', op: '卷膜2关'},
|
||
'jm3k': {opposite: 'jm3g', name: '卷膜3关', op: '卷膜3开'},
|
||
'jm3g': {opposite: 'jm3k', name: '卷膜3开', op: '卷膜3关'}
|
||
};
|
||
|
||
// 先判断类型是否在映射表中,避免无效case
|
||
if (!tipMap[type]) return;
|
||
|
||
const {opposite, name, op} = tipMap[type];
|
||
// 核心校验逻辑(只写一次,无需重复)
|
||
if (status === 1 && this.status[opposite] === 1) {
|
||
this.$modal.msgError(`【${this.selectedText}】${name}在运行状态,不能运行${op}操作!`);
|
||
return;
|
||
}
|
||
|
||
// ========== 修改:从全局工具类获取连接状态 ==========
|
||
this.connected = mqttUtil.getMqttState().isConnected;
|
||
if (!this.connected) {
|
||
this.$modal.msgError("设备连接异常");
|
||
return;
|
||
}
|
||
if (this.value === 1) {
|
||
this.$modal.msgError("设备控制失败!");
|
||
console.info("大棚选取失败!")
|
||
return;
|
||
}
|
||
uni.showModal({
|
||
title: '操作提示:',
|
||
content: '确定' + (status === 1 ? "运行" : "暂停") + '【' + this.selectedText + '】设备?',
|
||
cancelText: '取消',
|
||
confirmText: '确定',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// 组装消息
|
||
this.message = JSON.stringify({[type]: status})
|
||
// 控制设备
|
||
this.publishMessage();
|
||
// 设备回执
|
||
this.deviceType = type;
|
||
//todo
|
||
// this.testAuto(type);
|
||
|
||
}
|
||
}
|
||
})
|
||
},
|
||
|
||
// 测试专用
|
||
testAuto(type) {
|
||
this.$set(this.status, type, this.status[type] === 0 ? 1 : 0);
|
||
this.$set(this.show, type, this.status[type] === 0 ? "暂停" : "运行");
|
||
|
||
// ========== 核心新增:开启指令成功后启动定时器(带快照) ==========
|
||
const isStartCommand = status === 1; // 1=开启指令,0=停止指令
|
||
if (isStartCommand) {
|
||
// 1. 清除同type旧定时器(避免重复计时)
|
||
if (this.timers[type]?.timerId) {
|
||
clearTimeout(this.timers[type].timerId);
|
||
delete this.timers[type];
|
||
}
|
||
|
||
// 2. 快照:保存当前所有关键数据(值拷贝,永不改变)
|
||
const snapInfo = {
|
||
imei: this.imei, // 旧大棚imei
|
||
publishTopic: this.publishTopic, // 旧大棚Topic
|
||
delayTime: Number(this.limitTimes[`${type}Limit`] || 0) * 1000, // 旧运行时长(毫秒)
|
||
deviceType: type, // 设备类型
|
||
connected: this.connected // 连接状态
|
||
};
|
||
// 3. 仅当设置了运行时间才启动定时器
|
||
if (snapInfo.delayTime && snapInfo.delayTime > 0) {
|
||
console.info(`定时记录:大棚:${snapInfo.imei}; 指令:${this.message}`)
|
||
|
||
// 4. 启动定时器,存储timerId和快照
|
||
const timerId = setTimeout(() => {
|
||
// 计时结束:执行自动停止逻辑(使用快照数据)
|
||
this.sendAutoStopCommand(snapInfo);
|
||
// 清除当前定时器记录
|
||
delete this.timers[type];
|
||
}, snapInfo.delayTime);
|
||
|
||
// 5. 保存定时器信息
|
||
this.timers[type] = {
|
||
timerId: timerId,
|
||
snapInfo: snapInfo
|
||
};
|
||
}
|
||
}
|
||
},
|
||
publishMessage() {
|
||
if (!this.connected || !this.publishTopic || !this.message) {
|
||
uni.showToast({
|
||
title: '控制异常',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
// 调用全局MQTT工具类发布消息
|
||
const publishSuccess = mqttUtil.publishMqtt(this.publishTopic, this.message);
|
||
if (publishSuccess) {
|
||
this.addMessage(`【指令已发送】imei: ${this.publishTopic},指令: ${this.message}`);
|
||
} else {
|
||
this.addMessage(`发布失败:设备:[${this.publishTopic}]`)
|
||
}
|
||
this.message = {};
|
||
},
|
||
|
||
// 消息回调逻辑完全保留(仅依赖全局工具类转发消息)
|
||
ackMessage(topic, payload) {
|
||
// 1. 先判断是否是目标订阅主题(如dtu/xxx/up)
|
||
if (topic !== this.mqttConfig.subscribeTopic) return;
|
||
let msgData = {};
|
||
// 优化:捕获JSON解析异常
|
||
try {
|
||
msgData = JSON.parse(payload);
|
||
} catch (e) {
|
||
console.error("MQTT消息解析失败:", e, payload);
|
||
return;
|
||
}
|
||
|
||
// 3. 区分“回执”和“其他内容”
|
||
if (msgData.prop && "suc" in msgData) {
|
||
this.handleCommandAck(msgData, this.deviceType);
|
||
} else {
|
||
this.handleOtherContent(msgData,payload)
|
||
}
|
||
},
|
||
|
||
addMessage(content) {
|
||
console.info("提示消息:" + content)
|
||
},
|
||
|
||
// 处理指令回执的函数(核心修改:添加定时器+快照)
|
||
handleCommandAck(ackData, type) {
|
||
// 拿到指令字段(如jm2k)和执行状态(suc)
|
||
const commandField = Object.keys(ackData.prop)[0]; // 这里是"jm2k"
|
||
const commandValue = ackData.prop[commandField]; // 这里是0/1
|
||
const isSuccess = ackData.suc; // 这里是true
|
||
|
||
if (isSuccess) {
|
||
// 优化:使用$set确保响应式更新
|
||
this.$set(this.status, type, this.status[type] === 0 ? 1 : 0);
|
||
this.$set(this.show, type, this.status[type] === 0 ? "暂停" : "运行");
|
||
|
||
// ========== 核心新增:开启指令成功后启动定时器(带快照) ==========
|
||
const isStartCommand = commandValue === 1; // 1=开启指令,0=停止指令
|
||
if (isStartCommand) {
|
||
// 1. 清除同type旧定时器(避免重复计时)
|
||
if (this.timers[type]?.timerId) {
|
||
clearTimeout(this.timers[type].timerId);
|
||
delete this.timers[type];
|
||
}
|
||
|
||
// 2. 快照:保存当前所有关键数据(值拷贝,永不改变)
|
||
const snapInfo = {
|
||
imei: this.imei, // 旧大棚imei
|
||
publishTopic: this.publishTopic, // 旧大棚Topic
|
||
delayTime: Number(this.limitTimes[`${type}Limit`] || 0) * 1000, // 旧运行时长(毫秒)
|
||
deviceType: type, // 设备类型
|
||
connected: this.connected // 连接状态
|
||
};
|
||
|
||
// 3. 仅当设置了运行时间才启动定时器
|
||
if (snapInfo.delayTime && snapInfo.delayTime > 0) {
|
||
// 4. 启动定时器,存储timerId和快照
|
||
const timerId = setTimeout(() => {
|
||
// 计时结束:执行自动停止逻辑(使用快照数据)
|
||
this.sendAutoStopCommand(snapInfo);
|
||
// 清除当前定时器记录
|
||
delete this.timers[type];
|
||
}, snapInfo.delayTime);
|
||
|
||
// 5. 保存定时器信息
|
||
this.timers[type] = {
|
||
timerId: timerId,
|
||
snapInfo: snapInfo
|
||
};
|
||
}
|
||
}
|
||
}
|
||
|
||
this.deviceType = '';
|
||
// ========== 修改:自动停止指令不弹窗 ==========
|
||
|
||
if (commandValue === 1) this.$modal.msgSuccess("设备操作成功!");
|
||
|
||
console.log(`指令[${commandField}=${commandValue}]执行${isSuccess ? "成功" : "失败"}`);
|
||
},
|
||
|
||
// 新增:发送自动停止指令(使用快照数据,避免变量覆盖)
|
||
sendAutoStopCommand(snapInfo) {
|
||
// 1. 校验快照数据完整性
|
||
if (!snapInfo || !snapInfo.imei || !snapInfo.publishTopic || !snapInfo.deviceType) {
|
||
this.addMessage(`【自动停止失败】快照数据缺失`);
|
||
return;
|
||
}
|
||
|
||
// 2. 校验设备当前状态(若切回旧大棚,才判断状态)
|
||
let needStop = true;
|
||
if (this.imei === snapInfo.imei) {
|
||
needStop = this.status[snapInfo.deviceType] === 1;
|
||
}
|
||
|
||
if (needStop) {
|
||
// 3. 组装停止指令(使用快照中的设备类型)
|
||
const stopMessage = JSON.stringify({[snapInfo.deviceType]: 0});
|
||
console.info(`自动停:${snapInfo.publishTopic}:${stopMessage}`);
|
||
// todo 4. 使用快照中的Topic发送指令(精准发往旧大棚)
|
||
const publishSuccess = mqttUtil.publishMqtt(snapInfo.publishTopic, stopMessage);
|
||
if (publishSuccess) {
|
||
this.addMessage(`【自动停止-旧大棚${snapInfo.imei}】设备${snapInfo.deviceType},指令: ${stopMessage}`);
|
||
|
||
// 5. 若当前选中的是旧大棚,更新页面状态
|
||
if (this.imei === snapInfo.imei) {
|
||
this.$set(this.status, snapInfo.deviceType, 0);
|
||
this.$set(this.show, snapInfo.deviceType, "暂停");
|
||
|
||
}
|
||
} else {
|
||
this.addMessage(`【自动停止失败-旧大棚${snapInfo.imei}】设备${snapInfo.deviceType}`);
|
||
}
|
||
} else {
|
||
this.addMessage(`【自动停止跳过】旧大棚${snapInfo.imei}设备${snapInfo.deviceType}已非运行状态`);
|
||
}
|
||
},
|
||
|
||
handleOtherContent(msgData,payload) {
|
||
// 业务逻辑:处理传感器数据、设备状态等
|
||
// 设备状态展示
|
||
if (this.value !== 1) {
|
||
var arr = ['jbk', "jbg", "jm1k", "jm1g", "jm2k", "jm2g", "jm3k", "jm3g"]
|
||
const allKeysNumeric = Object.keys(msgData).some(key => arr.includes(key));
|
||
if (allKeysNumeric) {
|
||
this.status = {...msgData}
|
||
Object.keys(msgData).forEach(key => {
|
||
const value = msgData[key];
|
||
this.show[key] = value === 0 ? '暂停' : '运行';
|
||
});
|
||
this.control = '最后更新时间:' + this.getCurrentTime();
|
||
}
|
||
const allKeysNumeric2 = Object.keys(msgData).every(key => /^\d+$/.test(key));
|
||
if (Object.keys(msgData).length > 0 && allKeysNumeric2) {
|
||
this.temp = "最后更新时间:" + this.getCurrentTime();
|
||
this.fontStyle = 'font-size:16px;'
|
||
|
||
this.makeSpecialData(msgData, true);
|
||
}
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 获取格式化后的当前时间
|
||
* @returns {string} 格式为 YYYY-MM-DD HH:mm:ss 的当前时间
|
||
*/
|
||
getCurrentTime() {
|
||
const now = new Date();
|
||
|
||
// 获取年、月、日(补零确保两位数)
|
||
const year = now.getFullYear();
|
||
const month = String(now.getMonth() + 1).padStart(2, '0'); // 月份从 0 开始,需 +1
|
||
const day = String(now.getDate()).padStart(2, '0');
|
||
|
||
// 获取时、分、秒(补零确保两位数)
|
||
const hours = String(now.getHours()).padStart(2, '0');
|
||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||
|
||
// 拼接成标准格式
|
||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||
},
|
||
|
||
testNumber(data) {
|
||
const reg = /^-?\d+(\.\d+)?$/;
|
||
return reg.test(String(data).trim());
|
||
},
|
||
|
||
// 优化:封装温湿度单位判断函数
|
||
isEffectiveValue(value) {
|
||
return this.testNumber(value);
|
||
},
|
||
|
||
// 新增:打开修改运行时间的弹窗
|
||
openTimeModal(card) {
|
||
this.currentCard = card; // 记录当前卡片信息
|
||
this.currentCardTime = this.limitTimes[`${card.type}Limit`]; // 记录当前时间
|
||
this.newLimitTime = this.currentCardTime; // 默认填充当前时间
|
||
this.$refs.inputDialog.open()
|
||
},
|
||
close() {
|
||
this.$refs.inputDialog.close()
|
||
},
|
||
// 新增:确认修改运行时间
|
||
confirmModifyTime() {
|
||
// 校验输入:必须是1-60的数字
|
||
/*if (!this.newLimitTime || this.newLimitTime < 1 || this.newLimitTime > 60) {
|
||
uni.showToast({
|
||
title: '请输入1-60的有效数字',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}*/
|
||
// 修改limitTimes(用$set确保响应式)
|
||
if (this.newLimitTime !== this.currentCardTime) {
|
||
// 提示修改成功
|
||
uni.showModal({
|
||
title: '温馨提示:',
|
||
content: `确定修改${this.selectedText}-${this.currentCard.name}运行时间为:${this.newLimitTime} 秒?`,
|
||
cancelText: '取消',
|
||
confirmText: '确定',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
this.$set(this.limitTimes, `${this.currentCard.type}Limit`, this.newLimitTime);
|
||
if (this.limitTimes.imei) {
|
||
updateLimit(this.limitTimes).then(response => {
|
||
response.code === 200 ? this.$modal.msgSuccess("修改成功"):this.$modal.msgSuccess("修改失败")
|
||
})
|
||
return;
|
||
}
|
||
this.$set(this.limitTimes, 'imei', this.imei);
|
||
this.$set(this.limitTimes, 'agriName', this.selectedText);
|
||
this.$set(this.limitTimes, 'agriId', this.agriId);
|
||
addLimit(this.limitTimes).then(response => {
|
||
response.code === 200 ? this.$modal.msgSuccess("修改成功"):this.$modal.msgSuccess("修改失败")
|
||
this.getAgriByImei();
|
||
})
|
||
|
||
}
|
||
}
|
||
})
|
||
}
|
||
// 关闭弹窗
|
||
this.$refs.inputDialog.close()
|
||
// (可选)这里可以加接口请求,把新时间同步到后端
|
||
// this.saveLimitTimeToServer(this.currentCard.type, this.newLimitTime);
|
||
}
|
||
},
|
||
onHide() {
|
||
mqttUtil.removeOnMessageCallback();
|
||
// 隐藏时清除定时器
|
||
this.clearAllTimers();
|
||
},
|
||
beforeDestroy() {
|
||
mqttUtil.removeOnMessageCallback();
|
||
// 销毁时清除所有定时器
|
||
this.clearAllTimers();
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.container {
|
||
padding: 20rpx;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
/* 控制设置标题 */
|
||
.control-title {
|
||
font-size: 32rpx;
|
||
font-weight: 500;
|
||
text-align: center;
|
||
margin-bottom: 30rpx;
|
||
color: #333;
|
||
}
|
||
|
||
/* 2列栅格布局 */
|
||
.card-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 20rpx;
|
||
padding: 20rpx;
|
||
}
|
||
|
||
/* 卡片样式 */
|
||
.control-card {
|
||
background-color: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 30rpx 20rpx;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
box-shadow: 0 2rpx 8rpx #bfbec1;
|
||
}
|
||
|
||
/* 卡片文字区域 */
|
||
.card-text {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.card-main {
|
||
font-size: 30rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.card-sub {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
/* 保留原有样式,仅修改/新增以下部分 */
|
||
.card-sub-wrapper {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
width: 100%;
|
||
gap: 10rpx;
|
||
/* 新增:给容器加最小宽度,确保所有卡片一致 */
|
||
min-width: 0;
|
||
}
|
||
|
||
.limit-time {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
/* 可选:加固定左边距,确保和暂停的间距统一 */
|
||
margin-left: 0rpx;
|
||
}
|
||
/* 卡片图标容器 */
|
||
.card-icon {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
border-radius: 50%;
|
||
background-color: #e5e5e5;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* 激活状态(运行) */
|
||
.card-icon.active {
|
||
background-color: #007aff;
|
||
}
|
||
.uni-px-5 {
|
||
padding-left: 20rpx;
|
||
padding-right: 20rpx;
|
||
}
|
||
|
||
.uni-pb-5 {
|
||
padding-bottom: 15rpx;
|
||
}
|
||
|
||
.text {
|
||
width: 50rpx;
|
||
margin: 10rpx 10rpx 8rpx 0;
|
||
padding: 0;
|
||
height: 70rpx;
|
||
line-height: 70rpx;
|
||
text-align: center;
|
||
font-size: 26rpx;
|
||
box-shadow: 0 2rpx 8rpx #bfbec1
|
||
}
|
||
|
||
.tempStyle,.humiStyle {
|
||
display: inline-block;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.text:first-child {
|
||
margin-left: 10rpx;
|
||
}
|
||
|
||
.uni-view {
|
||
-webkit-flex: 1;
|
||
flex: 1;
|
||
height: 150rpx;
|
||
-webkit-justify-content: center;
|
||
justify-content: center;
|
||
-webkit-align-items: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.data {
|
||
font-size: 13px;
|
||
display: inline-block;
|
||
line-height: 23px;
|
||
color: #3a3a3a;
|
||
}
|
||
.data:nth-child(even) {
|
||
font-size: 12px;
|
||
margin-top: 3px;
|
||
color: #9c9c9c;
|
||
}
|
||
/deep/ .uni-section-header__slot-right {
|
||
color: green;
|
||
}
|
||
|
||
/* 新增:弹窗样式 */
|
||
.modal-container {
|
||
width: 600rpx;
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 30rpx 20rpx;
|
||
}
|
||
.modal-title {
|
||
font-size: 30rpx;
|
||
font-weight: 500;
|
||
text-align: center;
|
||
margin-bottom: 30rpx;
|
||
color: #333;
|
||
}
|
||
.modal-input-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 25rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
.modal-label {
|
||
width: 160rpx;
|
||
color: #666;
|
||
}
|
||
.modal-current {
|
||
margin-left: 14rpx;
|
||
color: #333;
|
||
}
|
||
.modal-input {
|
||
height: 60rpx;
|
||
border: 1px solid #eee;
|
||
border-radius: 8rpx;
|
||
padding: 0 15rpx;
|
||
font-size: 26rpx;
|
||
width: 80rpx;
|
||
}
|
||
.modal-unit {
|
||
margin-left: 10rpx;
|
||
color: #666;
|
||
}
|
||
.modal-btn-wrap {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
margin-top: 40rpx;
|
||
}
|
||
.modal-btn {
|
||
flex: 1;
|
||
height: 70rpx;
|
||
border-radius: 8rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
.modal-btn.cancel {
|
||
background: #f5f5f5;
|
||
color: #666;
|
||
}
|
||
.modal-btn.confirm {
|
||
background: #007aff;
|
||
color: #fff;
|
||
}
|
||
.icon {
|
||
margin-left: 15rpx
|
||
}
|
||
.uni-stat-tooltip {
|
||
width: 300rpx;
|
||
}
|
||
</style> |