diff --git a/App.vue b/App.vue index c979bc4..506d94f 100644 --- a/App.vue +++ b/App.vue @@ -2,6 +2,7 @@ import config from './config' import { getToken } from '@/utils/auth' import mqttUtil from '@/utils/mqtt' +import {startMqttOnlinePing, stopMqttOnlinePing} from "./utils/mqtt"; export default { globalData: { @@ -55,6 +56,9 @@ export default { console.info("token存在,mqtt重新连接中。。") this.reconnectMqtt() } + + // 防止 mqtt client 还没连上:可以等 connected 后再 start(见下面提示) + startMqttOnlinePing( 20000); } }, onHide() { @@ -65,6 +69,7 @@ export default { // ========== 新增:切后台时同步到localStorage ========== uni.setStorageSync('mqtt_subscribe_list', this.globalData.mqtt.subscribeList) mqttUtil.disconnectMqtt() + stopMqttOnlinePing(); } }, methods: { diff --git a/pages/control/index.vue b/pages/control/index.vue index 8abc272..c3975bf 100644 --- a/pages/control/index.vue +++ b/pages/control/index.vue @@ -230,8 +230,6 @@ export default { currentCard: {}, // 当前点击的卡片信息 currentCardTime: 0, // 当前卡片的运行时间 newLimitTime: 0, // 新的运行时间 - // 新增:存储定时器(含快照信息),key=type,value={timerId, snapInfo} - timers: {} }; }, onLoad() { @@ -260,8 +258,6 @@ export default { onUnload() { // 移除MQTT消息回调(避免内存泄漏) mqttUtil.removeOnMessageCallback(); - // 清除所有定时器 - this.clearAllTimers(); }, methods: { @@ -274,15 +270,6 @@ export default { } }) }, - // 新增:清除所有定时器(防止内存泄漏) - 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') @@ -480,43 +467,6 @@ export default { 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) { @@ -573,89 +523,15 @@ export default { // 优化:使用$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("设备操作成功!"); + 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) { // 业务逻辑:处理传感器数据、设备状态等 // 设备状态展示 @@ -768,13 +644,9 @@ export default { }, onHide() { mqttUtil.removeOnMessageCallback(); - // 隐藏时清除定时器 - this.clearAllTimers(); }, beforeDestroy() { mqttUtil.removeOnMessageCallback(); - // 销毁时清除所有定时器 - this.clearAllTimers(); }, }; diff --git a/utils/mqtt.js b/utils/mqtt.js index 5b89e5b..5aced59 100644 --- a/utils/mqtt.js +++ b/utils/mqtt.js @@ -277,6 +277,39 @@ export function getMqttState() { } } + + +// utils/mqttOnline.js +let timer = null; + +export function startMqttOnlinePing(intervalMs = 20000) { + if (!mqttState.client || !mqttState.options.clientId) return; + if (timer) return; + + const topic = `frontend/${mqttState.options.clientId}/online`; + + const ping = () => { + try { + if (!mqttState.client.connected) return; + const payload = JSON.stringify({ ts: Date.now() }); + // qos=0 足够;retain 不要 + mqttState.client.publish(topic, payload, { qos: 0, retain: false }); + } catch (e) {} + }; + + ping(); + // 每 20000ms(20秒)执行一次 ping + timer = setInterval(ping, intervalMs); +} + +export function stopMqttOnlinePing() { + if (timer) { + clearInterval(timer); + timer = null; + } +} + + // 导出所有方法(全局调用) export default { initMqttConfig,