1
0

84 Ревизии 1fcae3e961 ... 3de4afc48a

Автор SHA1 Съобщение Дата
  wangming 3de4afc48a 提交合并文件 преди 1 седмица
  wangming 330d322ba7 修改雷达坐标图片 преди 1 седмица
  wangming 454688b46f 提交选择角度,显示雷达位置 преди 1 седмица
  wangming ac4d074798 修复坐摔设置问题 преди 1 седмица
  wangming a605a8eef8 修改设置 преди 1 седмица
  wangming 4902b93673 提交新版本号 преди 1 седмица
  wangming 382be56d66 修改设备详情问题 преди 1 седмица
  wangming 5231a3459c 修改分享列表bug преди 1 седмица
  wangming 6a3054f63f 提交优化代码 преди 1 седмица
  wangming f955fd02d3 提交优化代码 преди 1 седмица
  wangming 032941c2f2 1.拆分更多设置 2.新增坐摔检测 преди 1 седмица
  wangming 89130ee207 提交绑定设备,优化设备详情 преди 3 седмици
  wangming 8c8c6d0f42 提交优化代码 преди 3 седмици
  wangming 13626cdf96 提交优化代码 преди 3 седмици
  wangming ee2d2c0283 修改加载框保存问题 преди 3 седмици
  wangming fd3b3a565a 提交优化代码 преди 3 седмици
  wangming 5f4a6717e8 提交mqtt优化代码 преди 3 седмици
  wangming ff24eae8f9 重新优化时间线 преди 3 седмици
  wangming 44b79901d1 修改目标进入离开时间 преди 3 седмици
  wangming 0eb8676d63 优化今日进出频次 преди 3 седмици
  wangming 3565367181 提交优化代码 преди 3 седмици
  wangming ec41cce3ef 优化家具后的代码 преди 3 седмици
  wangming fae4b67116 提交登录,点击不允许,退出小程序 преди 3 седмици
  wangming d77c5231a4 修改新增设备 преди 3 седмици
  wangming a9a457ee75 提交事件类型代码 преди 3 седмици
  wangming e31ec0bad5 修复图标由大变小bug преди 3 седмици
  wangming d78026e7f7 修复图标由小变大问题 преди 3 седмици
  wangming a6ff8dd3a9 设备设置 преди 3 седмици
  wangming 2c0bea88aa 提交设备详情 преди 3 седмици
  wangming fa15e250ec 新增设备列表目标进入动态刷新 преди 3 седмици
  wangming 21f60b808c 拆分分享设备,分享记录 преди 3 седмици
  wangming aeb8ed10f5 删除废弃图片,修改图片命名 преди 3 седмици
  wangming f6ac0657e4 提交新增设备修改 преди 3 седмици
  wangming07 6d5876435b 提交编辑设备代码 преди 4 седмици
  wangming 4f40b8aec3 设备设置 преди 4 седмици
  wangming 63494ea083 重构设备升级 преди 4 седмици
  wangming 11ba081a80 检测到跌倒人物目标图标由绿变黄,确认跌倒与跌倒呼救,人物目标图标变红,检测到没有跌倒事件,目标重新变绿 преди 4 седмици
  wangming 9141191c06 提交优化代码 преди 4 седмици
  wangming 96ba249809 修改新增设备逻辑 преди 4 седмици
  wangming dfcf0407dc 1.小程序新增,非主绑人不可分享,离线设备不可设置,2。修复重新进入小程序时,跌倒事件主题未订阅bug преди 4 седмици
  wangming 4974431781 提交小程序页面重新载入时,全局弹窗订阅丢失问题 преди 4 седмици
  wangming07 f06df2fd81 提交mqtt连接提示 преди 4 седмици
  wangming 20fb052129 提交mqtt加载,首页弹窗 преди 4 седмици
  wangming 1b1f98e70d 提交房间版本号 преди 4 седмици
  wangming c96bba887f 修改链接分享权限 преди 4 седмици
  wangming ec180beb2c 修改ota升级,设备详情 преди 4 седмици
  wangming 51743faba9 提交设备转移,配置家具可看到实时点位 преди 1 месец
  wangming07 10fa393938 提交设备详情 преди 1 месец
  wangming 4c473c6fbf 提交家庭用户id修改 преди 1 месец
  wangming 8de1a95a13 提交环境配置 преди 1 месец
  wangming 54b0818290 提交单个mqtt преди 1 месец
  wangming 55e780916b 提交oss上传图片代码 преди 1 месец
  wangming 79ec0d096d 提交登录逻辑 преди 1 месец
  wangming 88a82e9b58 提交分享列表 преди 1 месец
  wangming 408d25782c 提交mqtt双连接 преди 1 месец
  wangming 3039933cb5 删除告警设备 преди 1 месец
  wangming a45f78f4d8 提交设备详情与mqtt单例模式 преди 1 месец
  wangming c20ab9c813 提交单例模式 преди 1 месец
  wangming ad17467685 提交设备详情 преди 1 месец
  wangming c2cdd91978 提交全局mqtt преди 1 месец
  wangming 92fe75075c 提交mqtt优化代码 преди 1 месец
  wangming 74ea39cb2f 提交优化代码 преди 1 месец
  wangming 6d3adc9766 提交优化代码 преди 1 месец
  wangming 052465c7cc 提交客服代码 преди 1 месец
  wangming f6e366a20a 提交我的客服代码 преди 1 месец
  wangming 4c9466004f 提交设备详情和mqtt代码优化 преди 1 месец
  wangming 0dea011d39 提交ota升级版本号 преди 1 месец
  wangming d66f79653f 重新提交mqtt代码 преди 1 месец
  wangming 9cc160079f 守护计划代码 преди 1 месец
  wangming 0a7282ba0c 提交登录协议 преди 1 месец
  wangming 4015f7e697 提交设备详情 преди 1 месец
  wangming 41f9a58274 优化mqtt代码问题 преди 1 месец
  wangming 39b3f9f86d 提交全局弹窗优化代码 преди 1 месец
  wangming 56708bf003 新增版本号3.0.10 преди 1 месец
  wangming 94fda9a620 提交优化代码 преди 1 месец
  wangming07 77a95eba31 提交优化代码 преди 1 месец
  wangming 224ff0ac72 提交优化的mqtt代码 преди 1 месец
  wangming 463dbda99d 优化mqtt逻辑 преди 1 месец
  wangming 0df8a7b503 修改设备详情逻辑 преди 1 месец
  wangming aa1f8028e9 提交设备详情 преди 1 месец
  wangming 60d3ff40fe 跌倒事件与全局弹窗 преди 1 месец
  wangming ddd113118b 提交优化之后的mqtt преди 1 месец
  wangming 2a0644921b 提交优化代码 преди 1 месец
  wangming 84803fb7f7 优化mqtt преди 1 месец
променени са 51 файла, в които са добавени 3929 реда и са изтрити 1742 реда
  1. 14 3
      src/App.vue
  2. 96 10
      src/components/component/alarModel.vue
  3. 3 6
      src/components/component/tenMinutes.vue
  4. 13 10
      src/components/js_sdk/index.js
  5. 27 0
      src/main.js
  6. 30 28
      src/pages.json
  7. 180 31
      src/pages/home/home.vue
  8. 0 261
      src/pagesA/OTA/OTA.vue
  9. 1 20
      src/pagesA/abnormalInfo/abnormalInfo.vue
  10. 167 202
      src/pagesA/adDevice/adDevice.vue
  11. 450 0
      src/pagesA/adNewDevice/adNewDevice.vue
  12. 342 270
      src/pagesA/deviceDetail/deviceDetail.vue
  13. 372 6
      src/pagesA/deviceSetting/deviceSetting.vue
  14. 79 47
      src/pagesA/devices/devices.vue
  15. 80 62
      src/pagesA/discrepancy/discrepancy.vue
  16. 31 10
      src/pagesA/healthAlarm/healthAlarm.vue
  17. 1 1
      src/pagesA/homeDetail/homeDetail.vue
  18. 3 3
      src/pagesA/homeManage/homeManage.vue
  19. 57 46
      src/pagesA/linkShare/linkShare.vue
  20. 94 41
      src/pagesA/loginNew/loginNew.vue
  21. 365 0
      src/pagesA/moreSetting/moreSetting.vue
  22. 35 19
      src/pagesA/my/my.vue
  23. 241 21
      src/pagesA/playSetting/playSetting.vue
  24. 294 94
      src/pagesA/roomSetting/roomSetting.vue
  25. 3 3
      src/pagesA/shareCountList/shareCountList.vue
  26. 5 5
      src/pagesA/shareDetail/shareDetail.vue
  27. 27 13
      src/pagesA/shareList/shareList.vue
  28. 0 28
      src/pagesA/survey/survey.vue
  29. BIN
      src/static/action0.png
  30. BIN
      src/static/action1.png
  31. BIN
      src/static/action2.png
  32. BIN
      src/static/action3.png
  33. BIN
      src/static/action4.png
  34. BIN
      src/static/action5.png
  35. BIN
      src/static/action6.png
  36. BIN
      src/static/action7.png
  37. 0 0
      src/static/actionSerious.png
  38. 0 0
      src/static/actionWarn.png
  39. BIN
      src/static/down.png
  40. BIN
      src/static/home_ln.png
  41. BIN
      src/static/linkService.png
  42. BIN
      src/static/list.png
  43. BIN
      src/static/notice.png
  44. BIN
      src/static/radar.png
  45. BIN
      src/static/rander.png
  46. BIN
      src/static/share.png
  47. 548 477
      src/uni_modules/lime-echart/components/l-echart/l-echart.vue
  48. 246 0
      src/utils/globalMqtt.js
  49. 0 25
      www.json
  50. 50 0
      旋转点.js
  51. 75 0
      旋转矩形.js

+ 14 - 3
src/App.vue

@@ -6,7 +6,9 @@ import { isWmpf } from "./const";
 import { updatePushToken } from "./api/index";
 import { config } from "./data/index";
 
-// 引入voip插件
+import MqttService from "./utils/globalMqtt";
+
+// 引入voip
 const wmpfVoip = requirePlugin("wmpf-voip").default;
 
 // voip事件监听
@@ -21,7 +23,8 @@ wmpfVoip.setVoipEndPagePath({
 });
 
 export default {
-    onLaunch: function () {
+    onLaunch() {
+        this.$initMqtt();
         // 初始化云开发
         if (wx.cloud) {
             wx.cloud.init({});
@@ -41,7 +44,6 @@ export default {
                     });
                 }, 1000);
             }
-        } else {
         }
 
         // 获取系统信息
@@ -71,5 +73,14 @@ export default {
             },
         });
     },
+    onUnload() {
+        MqttService.disconnectAll();
+    },
+    onShow() {
+        console.log("onShow", "页面又进来了");
+    },
+    onHide() {
+        // MqttService.disconnectAll();
+    },
 };
 </script>

+ 96 - 10
src/components/component/alarModel.vue

@@ -1,6 +1,6 @@
 <template>
     <!-- 告警弹窗 -->
-    <view>
+    <view style="z-index: 999">
         <view class="mask" v-if="alarmModel"></view>
         <view class="alarm-modal" v-if="alarmModel">
             <view class="alarm">
@@ -18,7 +18,7 @@
                 检测到可能发生跌倒事件,请立即处理!
             </view>
             <view class="backImg">
-                <image src="../../static/actionRed.png" mode="" />
+                <image src="../../static/actionSerious.png" />
             </view>
             <view class="alarm-bottom">
                 <template v-if="voipFlag">
@@ -41,8 +41,8 @@
     </view>
 </template>
 <script>
+import MqttService from "../../utils/globalMqtt.js";
 import config from "../../data/config.js";
-import mqtt from "../../utils/mqtt";
 import {
     getSnTicket,
     authorize,
@@ -57,6 +57,7 @@ import {
     isWmpf,
 } from "../../const.js";
 import { AsyncValue } from "../../utils";
+import { onShow } from "@dcloudio/uni-app";
 const wmpfVoip = requirePlugin("wmpf-voip").default;
 const envVersion = wx.getAccountInfoSync().miniProgram.envVersion;
 const apiTypes = ["校园模式", "硬件模式", "呼叫安卓", "校园+硬件模式"];
@@ -73,7 +74,6 @@ const getEnvVersionForVoip = (function () {
         return _map[envVersion];
     };
 })();
-
 export default {
     comments: {
         name: "alarModel",
@@ -134,7 +134,7 @@ export default {
                 sn,
                 modelId,
             });
-            console.log(snTicket, sn, 99999999);
+            console.log(snTicket, sn, 7777555);
             var that = this;
             wx.requestDeviceVoIP({
                 sn,
@@ -222,7 +222,7 @@ export default {
                 sn,
                 modelId,
             });
-            console.log(snTicket, sn, 99999999);
+            console.log(snTicket, sn, 997777);
             var that = this;
             wx.requestDeviceVoIP({
                 sn,
@@ -462,7 +462,6 @@ export default {
                         params
                     );
                 }
-                client = mqtt.connect("wxs://cmd.radar-power.cn/mqtt/", params);
             }
             // 存储client引用以便后续操作
             this.mqttClientTwo = client;
@@ -594,13 +593,100 @@ export default {
         sendChange() {
             this.$emit("sendChange", this.targetPoints);
         },
+        hanOtherMessage(topic, message) {
+            console.log("接收到组件的消息:", JSON.parse(message.toString()));
+            console.log(JSON.parse(message.toString()), "8870");
+            // 处理点位消息
+            let userId = uni.getStorageSync("userId");
+            const noticeMatch = /^\/mps\/wx_(.+)\/notice$/;
+            const match = topic.match(noticeMatch);
+            if (!match) return;
+            this.alarmModel = true;
+            const data = JSON.parse(message.toString());
+            this.clientId = data.clientId;
+            this.devName = data.devName;
+            this.sn = data.clientId;
+            this.eventListId = data.eventListId;
+            this.getVoipAuthor(this.clientId, userId);
+            console.log(data, topic, "接收到消息222:");
+            console.log(
+                "接收到消息:",
+                this.clientId,
+                this.devName,
+                this.sn,
+                this.eventListId,
+                data
+            );
+            const now = new Date();
+            const year = now.getFullYear();
+            const month = (now.getMonth() + 1).toString().padStart(2, "0");
+            const day = now.getDate().toString().padStart(2, "0");
+            const hours = now.getHours().toString().padStart(2, "0");
+            const minutes = now.getMinutes().toString().padStart(2, "0");
+            const formattedTime = `${year}-${month}-${day} ${hours}:${minutes}`;
+            this.nowTime = formattedTime;
+            console.log("isWmpf:", isWmpf);
+            if (!isWmpf) {
+                console.log("isWmpf:", isWmpf);
+                this.getVoipDevices();
+            }
+            console.log("接收到消息:", topic, data);
+        },
     },
     mounted() {
-        this.connectMQTTwo();
+        const userId = uni.getStorageSync("userId");
+        const topic = `/mps/wx_${userId}/notice`;
+
+        // 抽取处理消息的方法
+        const handleMessage = (message, msgTopic) => {
+            console.log(`接收到 ${msgTopic} 消息:`, message);
+            const data = JSON.parse(message);
+            const noticeMatch = /^\/mps\/wx_(.+)\/notice$/;
+            if (!topic.match(noticeMatch)) return;
+            // 更新页面数据
+            this.alarmModel = true;
+            this.clientId = data.clientId;
+            this.devName = data.devName;
+            this.sn = data.clientId;
+            this.eventListId = data.eventListId;
+            // VOIP 相关逻辑
+            this.getVoipAuthor(this.clientId, userId);
+            // 格式化时间
+            const now = new Date();
+            this.nowTime = `${now.getFullYear()}-${String(
+                now.getMonth() + 1
+            ).padStart(2, "0")}-${String(now.getDate()).padStart(
+                2,
+                "0"
+            )} ${String(now.getHours()).padStart(2, "0")}:${String(
+                now.getMinutes()
+            ).padStart(2, "0")}`;
+            if (!isWmpf) {
+                this.getVoipDevices();
+            }
+        };
+
+        // 抽取订阅的方法
+        const subscribeNotice = () => {
+            this.unsubscribeFnNotice = MqttService.subscribe(
+                "DATA",
+                topic,
+                handleMessage,
+                (err) => {
+                    if (!err) console.log(`✅ 页面组件 已订阅 ${topic}`);
+                }
+            );
+        };
+        // 初始订阅
+        subscribeNotice();
+        uni.$on("mqttDataReadyOnce", subscribeNotice);
     },
 
     beforeDestroy() {
-        this.closemqtt();
+        if (this.unsubscribeFnNotice) {
+            this.unsubscribeFnNotice();
+            this.unsubscribeFnNotice = null;
+        }
     },
 };
 </script>
@@ -669,7 +755,7 @@ export default {
         height: 600rpx;
         background-color: #fff;
         border: 12rpx solid #333333;
-        background-image: url(http://jkld.radar-power.com//uploadFiles/framework/file/20250620/toilet_bg.png);
+        background-image: url(https://hflnxx.oss-cn-shanghai.aliyuncs.com/IMAGE/20250919/toilet_bg.png);
         background-repeat: no-repeat;
         background-position: center;
 

+ 3 - 6
src/components/component/tenMinutes.vue

@@ -22,7 +22,7 @@
                 检测到可能发生跌倒事件,已被{{ selectHanled.remark }}处理过!
             </view>
             <view class="backImg">
-                <image src="../../static/actionRed.png" mode="" />
+                <image src="../../static/actionSerious.png" mode="" />
             </view>
             <view class="alarm-bottom">
                 <template v-if="voipFlag">
@@ -81,7 +81,7 @@ const getEnvVersionForVoip = (function () {
 
 export default {
     comments: {
-        name: "alarModel",
+        name: "tenMinutes",
     },
     data() {
         return {
@@ -505,9 +505,6 @@ export default {
     mounted() {
         this.getFallingEventsQuery();
     },
-    onHide() {
-        uni.hideLoading();
-    },
 };
 </script>
 
@@ -575,7 +572,7 @@ export default {
         height: 600rpx;
         background-color: #fff;
         border: 12rpx solid #333333;
-        background-image: url(http://jkld.radar-power.com//uploadFiles/framework/file/20250620/toilet_bg.png);
+        background-image: url(https://hflnxx.oss-cn-shanghai.aliyuncs.com/IMAGE/20250919/toilet_bg.png);
         background-repeat: no-repeat;
         background-position: center;
 

+ 13 - 10
src/components/js_sdk/index.js

@@ -5,21 +5,24 @@ http.setConfig(config => {
     if (__wxConfig.envVersion == 'develop') {
         let selectedService = uni.getStorageSync("sercviceChoice")
         if (!selectedService || selectedService == 'aloneServe') {
-            // config.baseUrl = "https://api.radar-power.asia:4443/portal-service-server/";
+            config.baseUrl = "https://api.radar-power.asia:4443/portal-service-server/";
         }
-        config.baseUrl = "https://radar-power.cn/portal-service-server/";
+        // config.baseUrl = "https://radar-power.cn/portal-service-server/";
     }
     if (__wxConfig.envVersion == 'trial') {
-        let selectedService = uni.getStorageSync("sercviceChoice")
-        if (!selectedService || selectedService == 'aloneServe') {
-            config.baseUrl = "https://radar-power.cn/portal-service-server/";
-        }
+        // let selectedService = uni.getStorageSync("sercviceChoice")
+        // if (!selectedService || selectedService == 'aloneServe') {
+        //     config.baseUrl = "https://radar-power.cn/portal-service-server/";
+        // }
+        config.baseUrl = "https://radar-power.cn/portal-service-server/";
+        config.baseUrl = "https://api.radar-power.asia:4443/portal-service-server/";
     }
     if (__wxConfig.envVersion == 'release') {
-        let selectedService = uni.getStorageSync("sercviceChoice")
-        if (!selectedService || selectedService == 'aloneServe') {
-            config.baseUrl = "https://radar-power.cn/portal-service-server/";
-        }
+        // let selectedService = uni.getStorageSync("sercviceChoice")
+        // if (!selectedService || selectedService == 'aloneServe') {
+        //     config.baseUrl = "https://radar-power.cn/portal-service-server/";
+        // }
+        config.baseUrl = "https://radar-power.cn/portal-service-server/";
     }
     config.header = {
         // a: 1,

+ 27 - 0
src/main.js

@@ -1,6 +1,7 @@
 import Vue from 'vue'
 import App from './App'
 import './uni.promisify.adaptor'
+import MqttService from "./utils/globalMqtt";
 
 import {
     http
@@ -14,6 +15,32 @@ Vue.component('tenMinutes', tenMinutes);
 
 Vue.prototype.$http = http;
 
+// 全局方法:初始化 MQTT(登录成功时调用)
+Vue.prototype.$initMqtt = function () {
+    const userId = uni.getStorageSync("userId");
+    if (!userId) {
+        console.log("MQTT 初始化失败:缺少 userId");
+        return;
+    }
+    // 连接 DATA
+    // 如果已经连接过 DATA,直接返回
+    if (MqttService.dataConnected) {
+        uni.$emit('mqttData-ready', MqttService.dataClient);
+        uni.$emit('mqttDataReady', MqttService.dataClient);
+        return Promise.resolve(MqttService.dataClient);
+    }
+    return MqttService.connectData(userId)
+        .then(client => {
+            uni.$emit('mqttDataReadyOnce', this.dataClient);
+            uni.$emit('mqttDataReady', this.dataClient);
+            console.log("DATA MQTT 初始化完成", client);
+        })
+        .catch(err => {
+            console.error("DATA MQTT 初始化失败", err);
+        });
+}
+
+
 // 挂载处理时间的方法
 function timestampToTime(value, type = 0) {
     var time = new Date(value);

+ 30 - 28
src/pages.json

@@ -4,7 +4,7 @@
             "path": "pages/home/home",
             "style": {
                 "navigationStyle": "custom",
-                "enablePullDownRefresh": true
+                "enablePullDownRefresh": false
             }
         }
     ],
@@ -15,6 +15,7 @@
                 {
                     "path": "deviceDetail/deviceDetail",
                     "style": {
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "设备详情",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -24,7 +25,8 @@
                 {
                     "path": "adDevice/adDevice",
                     "style": {
-                        "navigationBarTitleText": "添加设备",
+                        "enablePullDownRefresh": false,
+                        "navigationBarTitleText": "参数配置",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
                     }
@@ -42,6 +44,7 @@
                 {
                     "path": "roomSetting/roomSetting",
                     "style": {
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "房间布局",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -59,7 +62,7 @@
                 {
                     "path": "deviceSetting/deviceSetting",
                     "style": {
-                        "enablePullDownRefresh": true,
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "设置",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -69,7 +72,7 @@
                 {
                     "path": "my/my",
                     "style": {
-                        "enablePullDownRefresh": true,
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "我的",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -77,20 +80,10 @@
                     "usingComponents": {}
                 },
                 {
-                    "path": "OTA/OTA",
-                    "style": {
-                        "enablePullDownRefresh": true,
-                        "navigationBarTitleText": "OTA升级",
-                        "navigationBarBackgroundColor": "#faede2",
-                        "navigationBarTextStyle": "black"
-                    },
-                    "usingComponents": {}
-                },
-                {
                     "path": "shareList/shareList",
                     "style": {
                         "enablePullDownRefresh": true,
-                        "navigationBarTitleText": "分享记录",
+                        "navigationBarTitleText": "",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
                     },
@@ -99,7 +92,7 @@
                 {
                     "path": "linkShare/linkShare",
                     "style": {
-                        "enablePullDownRefresh": true,
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "链接分享",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -109,7 +102,7 @@
                 {
                     "path": "serChange/serChange",
                     "style": {
-                        "enablePullDownRefresh": true,
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "网络配置",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -118,7 +111,7 @@
                 {
                     "path": "sharePages/sharePages",
                     "style": {
-                        "enablePullDownRefresh": true,
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "分享确认",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -145,7 +138,7 @@
                 {
                     "path": "discrepancy/discrepancy",
                     "style": {
-                        "enablePullDownRefresh": true,
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "出入详情",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
@@ -155,7 +148,7 @@
                     "path": "abnormalInfo/abnormalInfo",
                     "style": {
                         "enablePullDownRefresh": true,
-                        "navigationBarTitleText": "异常信息",
+                        "navigationBarTitleText": "统计信息",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
                     }
@@ -173,7 +166,7 @@
                     "path": "shareCountList/shareCountList",
                     "style": {
                         "enablePullDownRefresh": true,
-                        "navigationBarTitleText": "分享账户",
+                        "navigationBarTitleText": "分享记录",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
                     }
@@ -190,26 +183,35 @@
                 {
                     "path": "healthAlarm/healthAlarm",
                     "style": {
-                        "enablePullDownRefresh": true,
+                        "enablePullDownRefresh": false,
                         "navigationBarTitleText": "守护计划",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
                     }
                 },
                 {
-                    "path": "survey/survey",
+                    "path": "playSetting/playSetting",
                     "style": {
-                        "enablePullDownRefresh": true,
-                        "navigationBarTitleText": "问卷调查",
+                        "enablePullDownRefresh": false,
+                        "navigationBarTitleText": "计划配置",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
                     }
                 },
                 {
-                    "path": "playSetting/playSetting",
+                    "path": "adNewDevice/adNewDevice",
                     "style": {
-                        "enablePullDownRefresh": true,
-                        "navigationBarTitleText": "计划配置",
+                        "enablePullDownRefresh": false,
+                        "navigationBarTitleText": "绑定设备",
+                        "navigationBarBackgroundColor": "#faede2",
+                        "navigationBarTextStyle": "black"
+                    }
+                },
+                {
+                    "path": "moreSetting/moreSetting",
+                    "style": {
+                        "enablePullDownRefresh": false,
+                        "navigationBarTitleText": "更多设置",
                         "navigationBarBackgroundColor": "#faede2",
                         "navigationBarTextStyle": "black"
                     }

+ 180 - 31
src/pages/home/home.vue

@@ -9,7 +9,7 @@
         <view class="leineng">
             <view>
                 <image
-                    src="http://jkld.radar-power.com//uploadFiles/framework/file/20250620/home_ln.png"
+                    src="https://hflnxx.oss-cn-shanghai.aliyuncs.com/IMAGE/20250919/home_ln.png"
                     mode=""
                 />
             </view>
@@ -35,7 +35,7 @@
                     >
                 </view>
                 <view class="rightItem" @click="goAbnormalInfo()">
-                    <view class="rightTitle">异常信息</view>
+                    <view class="rightTitle">统计信息</view>
                     <view class="rightContent" v-if="warnNum == 0"
                         ><text>0</text>条</view
                     >
@@ -48,7 +48,10 @@
         </view>
 
         <view class="content">
-            <view class="card" @click="gotoPath('/pagesA/adDevice/adDevice')">
+            <view
+                class="card"
+                @click="gotoPath('/pagesA/adNewDevice/adNewDevice')"
+            >
                 <view class="contentTitle">
                     <text>添加设备</text>
                     <image src="../../static/rightArrow.png" />
@@ -73,15 +76,32 @@
             </view>
         </view>
 
-        <view class="bottom" @click="gotoSurvey('/pagesA/survey/survey')">
+        <view class="bottom">
             <image src="../../static/linkService.png" mode="" />
-            <!-- <button>联系客服</button> -->
             <button open-type="contact">联系客服</button>
         </view>
-        <view class="bot_version"> v3.0.9 </view>
+
+        <!-- <movable-area class="movable-area" direction="all">
+            <movable-view
+                class="movable-button"
+                :x="pos.x"
+                :y="pos.y"
+                direction="all"
+                @change="onChange"
+                @touchend="onTouchEnd"
+                v-if="showService"
+            >
+                <image src="../../static/linkService.png" class="icon" />
+                <span class="pulse"></span>
+                <span class="pulse"></span>
+                <button open-type="contact" class="contact-btn"></button>
+            </movable-view>
+        </movable-area> -->
+
+        <view class="bot_version"> v3.1.0</view>
         <view
             class="shareInfo"
-            @click="goDeviceShare()"
+            @click="goDeviceShare('sure')"
             v-if="shareNum > 0"
             :style="{ top: warnNum > 0 ? '280rpx' : '210rpx' }"
         >
@@ -138,7 +158,7 @@
             <image src="../../static/arrFour.png" class="warmImage"></image>
         </view>
         <tenMinutes v-if="isIniTenMinutes" />
-        <alarModel v-if="isInitAlarm" />
+        <alarModel v-if="showModle" />
     </view>
 </template>
 <script>
@@ -157,8 +177,20 @@ export default {
             warmDetail: [],
             alarmRetentionLists: [],
             warmTypeList: [],
-            isInitAlarm: false,
             isIniTenMinutes: false,
+            showModle: false,
+            // 修改我的客服点位
+            pos: { x: 0, y: 0 },
+            selectX: 0,
+            selectY: 0,
+            screenWidth: 0,
+            screenHeight: 0,
+            btnWidth: 80,
+            btnHeight: 80,
+            margin: 0, // 边距
+            windowWidth: 0,
+            dragTimer: null,
+            showService: false,
         };
     },
     computed: {},
@@ -227,16 +259,12 @@ export default {
                 url: "/pagesA/loginNew/loginNew",
             });
         },
-        goDeviceShare() {
-            uni.setStorageSync("shareStatus", "authorized");
+        goDeviceShare(shareType) {
             uni.navigateTo({
-                url: "/pagesA/shareList/shareList",
+                url: "/pagesA/shareList/shareList?shareType=" + shareType,
             });
         },
         getperSonInfo() {
-            uni.showLoading({
-                title: "查询中...",
-            });
             this.$http
                 .get("wap/home/homeInfo", {
                     userId: uni.getStorageSync("userId"),
@@ -249,7 +277,6 @@ export default {
                         this.warnNum = warnNum;
                         this.devDevice = res.data.data.deviceList;
                     }
-                    uni.hideLoading();
                 });
         },
         getShareNum() {
@@ -265,9 +292,6 @@ export default {
 
         // 异常信息处理
         getWarnList() {
-            uni.showLoading({
-                title: "查询中...",
-            });
             this.$http
                 .post(
                     "wap/stats/alarmEventsQuery",
@@ -288,7 +312,6 @@ export default {
                     if (res.data.data) {
                         this.alarmRetentionLists = res.data.data.rows;
                     }
-                    uni.hideLoading();
                 });
         },
         getwarmType() {
@@ -304,21 +327,62 @@ export default {
                     ?.eventDesc || "未知"
             );
         },
-        gotoSurvey(path) {
-            uni.navigateTo({
-                url: path,
+        onChange(e) {
+            this.selectX = e.detail.x;
+            this.selectY = e.detail.y;
+        },
+        onTouchEnd() {
+            uni.getSystemInfo({
+                success: (res) => {
+                    this.windowWidth = Number(res.windowWidth / 2);
+                },
             });
+            // console.log(this.selectX, this.selectY, "777777");
+            // if (this.selectX >= this.windowWidth) {
+            //     this.selectX = 0;
+            // } else {
+            //     this.selectX = 0;
+            // }
+            this.selectX = 0;
+            this.pos.x = this.selectX;
+            this.pos.y = this.selectY;
+            uni.setStorageSync("posLocationX", this.pos.x);
+            uni.setStorageSync("posLocationY", this.pos.y);
         },
     },
-    onLoad() {},
+    onLoad() {
+        // const sys = uni.getSystemInfoSync();
+        // this.windowWidth = sys.windowWidth;
+        // this.windowHeight = sys.windowHeight;
+
+        // // 初始化位置:右下角
+        // this.pos.x = this.windowWidth - this.btnWidth - this.margin;
+        // this.pos.y = this.windowHeight - this.btnHeight - this.margin;
+
+        // // 如果之前存储过位置,用存储覆盖
+        // const savedX = uni.getStorageSync("posLocationX");
+        // const savedY = uni.getStorageSync("posLocationY");
+        // if (savedX !== "" && savedX !== undefined) this.pos.x = savedX;
+        // if (savedY !== "" && savedY !== undefined) this.pos.y = savedY;
+
+        if (
+            !uni.getStorageSync("posLocationX") ||
+            !uni.getStorageSync("posLocationY")
+        ) {
+            this.pos = { x: 0, y: 0 };
+        }
+        this.pos.x = uni.getStorageSync("posLocationX");
+        this.pos.y = uni.getStorageSync("posLocationY");
+    },
     onShow() {
         if (uni.getStorageSync("userId")) {
             this.getperSonInfo();
             this.getShareNum();
             this.getwarmType();
             this.getWarnList();
-            this.isInitAlarm = true;
+            this.showModle = true;
             this.isIniTenMinutes = true;
+            this.showService = true;
         }
         // 配置服务器
         let selectedService = uni.getStorageSync("sercviceChoice");
@@ -327,13 +391,18 @@ export default {
         }
     },
     onHide() {
-        this.isInitAlarm = false;
-        this.isIniTenMinutes = false;
-    },
-    onUnload() {
-        this.isInitAlarm = false;
+        this.showModle = false;
         this.isIniTenMinutes = false;
     },
+    // onLaunch() {
+    //     uni.$on("mqttDataReadyOnce", (client) => {
+    //         this.showModle = true;
+    //         console.log("showModle", this.showModle);
+    //     });
+    // },
+    // onUnload() {
+    //     this.showModle = false;
+    // },
 
     onShareAppMessage() {
         return {
@@ -395,7 +464,7 @@ export default {
 }
 
 .right {
-    z-index: 99;
+    z-index: 2;
     padding-right: 100rpx;
 }
 
@@ -410,6 +479,9 @@ export default {
 }
 
 .right .rightItem .rightContent {
+    display: flex;
+    justify-content: center;
+    align-items: baseline;
     margin-top: 17rpx;
     font-size: 24rpx;
     font-weight: bold;
@@ -621,7 +693,84 @@ export default {
     line-height: 40rpx;
     white-space: nowrap;
 }
+.movable-area {
+    position: fixed;
+    width: 100%;
+    height: 80%;
+}
+
+.movable-button {
+    display: flex;
+    justify-content: center;
+    z-index: 999;
+    align-items: center;
+    width: 80rpx;
+    height: 80rpx;
+}
+
+.movable-button .icon {
+    pointer-events: none;
+    width: 80rpx;
+    height: 80rpx;
+}
+
+.movable-button .contact-btn {
+    position: absolute;
+    width: 80rpx;
+    height: 80rpx;
+    background: none;
+    border: none;
+    padding: 0;
+    top: 0;
+    left: 0;
+    z-index: 10;
+}
+
+/* 去掉伪元素 after 样式 */
+.movable-button .contact-btn::after,
+.movable-button .contact-btn::before,
+wx-button::after,
+wx-button::before {
+    content: none !important;
+    border: none !important;
+    background: transparent !important;
+    box-shadow: none !important;
+}
 
+.pulse {
+    position: absolute;
+    pointer-events: none;
+    top: 50%;
+    left: 50%;
+    width: 80rpx;
+    height: 80rpx;
+    border: 2px solid #ffd700; /* 黄色边框 */
+    border-radius: 50%;
+    transform: translate(-50%, -50%) scale(1);
+    opacity: 0.6;
+    animation: pulseAnim 2s ease-out infinite;
+    z-index: 4;
+}
+
+/* 第二个 pulse 动画延迟,产生连续效果 */
+.pulse:nth-child(2) {
+    animation-delay: 0.75s;
+}
+
+@keyframes pulseAnim {
+    0% {
+        transform: translate(-50%, -50%) scale(1);
+        opacity: 0.6;
+    }
+    70% {
+        transform: translate(-50%, -50%) scale(2);
+        opacity: 0;
+    }
+    100% {
+        transform: translate(-50%, -50%) scale(2);
+        opacity: 0;
+    }
+}
 /* 无限无缝滚动 */
 @keyframes scrollUp {
     0% {

+ 0 - 261
src/pagesA/OTA/OTA.vue

@@ -1,261 +0,0 @@
-<template>
-    <view class="box">
-        <view
-            class="OTA-info"
-            v-if="devsClientId.length > 0 && otaList.length > 0"
-        >
-            <view class="meauList">
-                <view class="title">OTA信息</view>
-                <view class="menu-item">
-                    <view class="meanLeft"> 设备序列号 </view>
-                    <view class="meanRight">
-                        <picker
-                            @change="bindPickerChange"
-                            :value="index"
-                            :range="devsClientId"
-                        >
-                            <view class="uni-input">{{
-                                devsClientId[index]
-                            }}</view>
-                        </picker>
-                    </view>
-                </view>
-                <view class="menu-item" bind:tap="gotoPath">
-                    <view class="meanLeft">
-                        <text>设备名称</text>
-                    </view>
-                    <view class="meanRight">
-                        {{ devs[index].devName }}
-                    </view>
-                </view>
-                <view class="menu-item" bind:tap="logout">
-                    <view class="meanLeft">
-                        <text>雷达型号</text>
-                    </view>
-                    <view class="meanRight">
-                        {{ devs[index].devType }}
-                    </view>
-                </view>
-                <view class="menu-item" bind:tap="logout">
-                    <view class="meanLeft">
-                        <text>当前版本</text>
-                    </view>
-                    <view class="meanRight">
-                        {{ devs[index].software }}
-                    </view>
-                </view>
-            </view>
-
-            <view class="meauList">
-                <view class="title">OTA版本</view>
-                <view class="menu-item">
-                    <view class="meanLeft">OTA版本</view>
-                    <view class="meanRight">
-                        <picker
-                            @change="bindPickerChangeTwo"
-                            :value="otaIndex"
-                            :range="otaListName"
-                        >
-                            <view class="uni-input">{{ otaListName[0] }}</view>
-                        </picker>
-                    </view>
-                </view>
-            </view>
-        </view>
-
-        <view
-            class="updateOta"
-            @click="freshOTA()"
-            v-if="devsClientId.length > 0 && otaList.length > 0"
-            >更新OTA</view
-        >
-    </view>
-</template>
-
-<script>
-export default {
-    data() {
-        return {
-            ota: "",
-            devs: [],
-            devsClientId: [],
-            index: 0,
-            selectClientId: "",
-            // ota部分
-            otaList: [],
-            otaListName: [],
-            otaIndex: 0,
-        };
-    },
-    methods: {
-        queryList() {
-            this.$http
-                .post("wap/device/deviceList", {
-                    userId: uni.getStorageSync("userId"),
-                    keyword: "",
-                    status: "",
-                })
-                .then((res) => {
-                    if (res.data.code == 200) {
-                        let devs = res.data.data;
-                        for (let i = 0; i < devs.length; i++) {
-                            if (
-                                devs[i].installPosition == "Toilet" &&
-                                devs[i].stayTimes?.length > 0
-                            ) {
-                                devs[i].wcTimes = devs[i].stayTimes.length;
-                            } else {
-                                devs[i].wcTimes = 0;
-                            }
-                            const signalTime = devs[i].signal_time;
-                            const currentTime = Date.now();
-                        }
-                        this.devs = devs;
-                        this.devsClientId = this.devs.map(
-                            (ele) => ele.clientId
-                        );
-                    } else {
-                        uni.showModal({
-                            content: res.data.message,
-                            showCancel: false,
-                        });
-                    }
-                });
-        },
-        freshOTA() {
-            if (this.devs[this.index].online == 0) {
-                uni.showModal({
-                    content: "设备离线,无法升级",
-                    complete: (res) => {
-                        if (res.confirm) {
-                        } else {
-                        }
-                    },
-                });
-                return;
-            }
-            let clientIds = [];
-            clientIds.push(this.devs[this.index].clientId);
-            let ossUrl = this.otaList[this.otaIndex].ossUrl;
-            this.$http
-                .post(
-                    "pub/OTA/update",
-                    { clientIds: clientIds, ossUrl: ossUrl },
-                    {
-                        header: {
-                            "Content-Type": "application/json;charset=UTF-8",
-                        },
-                    }
-                )
-                .then((res) => {
-                    if (res.data.code == 200) {
-                        uni.showToast({
-                            title: "OTA升级成功",
-                            icon: "success",
-                            duration: 1500,
-                        });
-                    } else {
-                        uni.showToast({
-                            title: res.data.message,
-                            icon: "none",
-                            duration: 1500,
-                        });
-                    }
-                });
-        },
-        bindPickerChange(e) {
-            this.index = e.detail.value;
-        },
-        // OTA部分
-        bindPickerChangeTwo(e) {
-            this.otaIndex = e.detail.value;
-        },
-        queryOtaList() {
-            this.$http.get("pub/OTA/query", {}).then((res) => {
-                this.otaList = res.data.data;
-                this.otaListName = this.otaList.map((ele) => ele.fileName);
-            });
-        },
-    },
-    onLoad() {},
-    onShow() {
-        this.queryList();
-        this.queryOtaList();
-    },
-};
-</script>
-
-<style lang="less" scoped>
-.box {
-    padding: 10rpx 18rpx;
-    width: 100vw;
-    height: 100vh;
-    background: linear-gradient(180deg, #faede2 0%, #f4f4f4 100%);
-    box-sizing: border-box;
-    .meauList {
-        .title {
-            padding-top: 20rpx;
-            margin-left: 25rpx;
-            font-weight: 500;
-            color: #784c41;
-            font-size: 32rpx;
-        }
-        width: 710rpx;
-        margin: 18rpx auto 0 auto;
-        background: #fff;
-        border-radius: 37rpx;
-        box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
-        padding: 0;
-        box-sizing: border-box;
-
-        .menu-item {
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-            margin: 0 32rpx;
-            padding: 32rpx 0;
-            border-bottom: 1rpx solid #f0f0f0;
-
-            &:last-child {
-                border-bottom: none;
-            }
-
-            .meanLeft {
-                display: flex;
-                align-items: center;
-                justify-content: center;
-
-                image {
-                    width: 38rpx;
-                    height: 38rpx;
-                }
-
-                text {
-                    margin-left: 10rpx;
-                    color: #111111;
-                    font-size: 32rpx;
-                }
-            }
-
-            .meanRight image {
-                width: 16rpx;
-                height: 26rpx;
-            }
-        }
-    }
-
-    .updateOta {
-        width: 710rpx;
-        margin: 48rpx auto 0 auto;
-        height: 88rpx;
-        background: #fff;
-        color: #b48a7c;
-        font-size: 32rpx;
-        border-radius: 30rpx;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
-    }
-}
-</style>

+ 1 - 20
src/pagesA/abnormalInfo/abnormalInfo.vue

@@ -55,26 +55,7 @@
                         <text>事件类型</text>
                     </view>
                     <view class="meanRight">
-                        <text
-                            v-if="item.eventType == 0"
-                            style="color: #e2a746"
-                            >{{ getEventTypeName(item.eventType) }}</text
-                        >
-                        <text
-                            v-if="item.eventType == 1"
-                            style="color: #f4726e"
-                            >{{ getEventTypeName(item.eventType) }}</text
-                        >
-                        <text
-                            v-if="item.eventType == 2"
-                            style="color: #919399"
-                            >{{ getEventTypeName(item.eventType) }}</text
-                        >
-                        <text
-                            v-if="item.eventType == 3"
-                            style="color: #cf6f29"
-                            >{{ getEventTypeName(item.eventType) }}</text
-                        >
+                        <text>{{ getEventTypeName(item.eventType) }}</text>
                     </view>
                 </view>
                 <!-- <view class="menu-item">

+ 167 - 202
src/pagesA/adDevice/adDevice.vue

@@ -1,22 +1,6 @@
 <template>
     <view>
         <view class="home-warp" v-if="nextTip == 'one'">
-            <view class="header">
-                <view class="item">
-                    <image src="../../static/activePod.png" alt=""> </image>
-                    <text>1</text>
-                </view>
-                <view class="line"></view>
-                <view class="item"
-                    ><image src="../../static/inactivePod.png" alt=""></image>
-                    <text>2</text>
-                </view>
-                <view class="line"></view>
-                <view class="item"
-                    ><image src="../../static/inactivePod.png" alt=""></image>
-                    <text>3</text>
-                </view>
-            </view>
             <view class="bodyContent">
                 <view
                     class="deviceInfo"
@@ -25,15 +9,15 @@
                     :class="{ 'fade-in': showDeviceInfo }"
                 >
                     <image src="../../static/ln_small.png" alt=""></image>
-                    <text>{{ clientId }}</text>
+                    <view class="deviceText">{{ clientId }}</view>
                 </view>
                 <view class="wifiScan" @click="scanCode" v-if="!clientId">
                     <image src="../../static/wifiScan.png" alt=""></image>
+                    <view class="wifiText" v-if="!clientId"
+                        >点击扫一扫识别设备</view
+                    >
                 </view>
-                <view class="wifiText" v-if="!clientId"
-                    >点击扫一扫识别设备</view
-                >
-                <view class="wifItem" style="margin-top: 90rpx">
+                <!-- <view class="wifItem" style="margin-top: 90rpx">
                     <image src="../../static/wifiIcon.png" alt=""></image>
                     <text>无线网账户</text>
                     <input
@@ -54,7 +38,7 @@
                         style="width: 60%"
                     />
                 </view>
-                <view class="btn" @click="startDisNet">开始配网</view>
+                <view class="btn" @click="startDisNet">开始配网</view> -->
             </view>
             <view class="clientInfo">
                 <view class="wifItem">
@@ -80,6 +64,24 @@
                         @click="showPicker"
                     ></image>
                 </view>
+                <view class="wifItem" style="margin-top: 30rpx">
+                    <text>安装方式</text>
+                    <picker
+                        @change="bindPickerChangeThree"
+                        :value="mountPlainIndex"
+                        :range="mountPlainArray"
+                        style="margin-left: auto"
+                    >
+                        <view class="uni-input">{{
+                            mountPlainArray[mountPlainIndex]
+                        }}</view>
+                    </picker>
+                    <image
+                        src="../../static/rightArrow.png"
+                        alt=""
+                        @click="showPicker"
+                    ></image>
+                </view>
             </view>
             <view class="bottom">
                 <view class="nextTip" @click="goNextTip">下一步</view>
@@ -87,30 +89,8 @@
         </view>
         <view class="home-warpTwo" v-if="nextTip == 'two'">
             <view class="header_two">
-                <view class="header">
-                    <view class="item">
-                        <image src="../../static/activePod.png" alt=""> </image>
-                        <text>1</text>
-                    </view>
-                    <view class="line"></view>
-                    <view class="item"
-                        ><image src="../../static/activePod.png" alt=""></image>
-                        <text>2</text>
-                    </view>
-                    <view class="line"></view>
-                    <view class="item"
-                        ><image
-                            src="../../static/inactivePod.png"
-                            alt=""
-                        ></image>
-                        <text>3</text>
-                    </view>
-                </view>
-                <view class="airCantInfo" v-if="environmentJudge">
-                    <text>暂未配置环境数据...</text>
-                </view>
                 <view class="airbody">
-                    <view class="airTitle">环境配置</view>
+                    <view class="airTitle">参数配置</view>
                     <view class="wifItem" style="margin-top: 30rpx">
                         <text>X轴范围(cm)</text>
                         <view class="inputBox">
@@ -170,7 +150,7 @@
                         </view>
                     </view>
                     <view class="wifItem" style="margin-top: 30rpx">
-                        <text>正北向夹角(°)</text>
+                        <text>指示灯方向</text>
                         <view class="inputBox">
                             <picker
                                 @change="bindPickerChangeTwo"
@@ -182,9 +162,72 @@
                                     angleArray[northAngleIndex]
                                 }}</view>
                             </picker>
+                            <!-- <image
+                                src="../../static/rightArrow.png"
+                                alt=""
+                                @click="showPicker"
+                            ></image> -->
                         </view>
                     </view>
-                    <view class="wifItem" style="margin-top: 30rpx">
+                    <!-- <view class="wifItem" style="margin-top: 30rpx">
+                        <text>坐摔检测</text>
+                        <view class="inputBox">
+                            <switch
+                                :checked="fallSettingEnabled == 1"
+                                @change="onChange"
+                                :active-value="1"
+                                :inactive-value="0"
+                                size="24px"
+                                active-color="#07c160"
+                                inactive-color="#eeeff1"
+                                style="transform: scale(0.8); margin-left: auto"
+                            />
+                        </view>
+                    </view>
+                    <template v-if="fallSettingEnabled == 1">
+                        <view class="wifItem" style="margin-top: 30rpx">
+                            <text>坐摔最低高度(m)</text>
+                            <view class="inputBox">
+                                <input
+                                    type="number"
+                                    v-model="lowZMax"
+                                    placeholder="请输入坐摔最低高度"
+                                />
+                            </view>
+                        </view>
+                        <view class="wifItem" style="margin-top: 30rpx">
+                            <text>坐摔门限</text>
+                            <view class="inputBox">
+                                <input
+                                    type="number"
+                                    v-model="humanPredThreshold"
+                                    placeholder="请输入坐摔门限"
+                                />
+                            </view>
+                        </view>
+                        <view class="wifItem" style="margin-top: 30rpx">
+                            <text>最小连续坐摔判断</text>
+                            <view class="inputBox">
+                                <input
+                                    type="number"
+                                    v-model="minEventsForDetection"
+                                    placeholder="请输入最小连续坐摔判断"
+                                />
+                            </view>
+                        </view>
+                        <view class="wifItem" style="margin-top: 30rpx">
+                            <text>最小连续坐摔次数</text>
+                            <view class="inputBox">
+                                <input
+                                    type="number"
+                                    v-model="minHumanEventsForDetection"
+                                    placeholder="请输入最小连续坐摔次数"
+                                />
+                            </view>
+                        </view>
+                    </template> -->
+
+                    <!-- <view class="wifItem" style="margin-top: 30rpx">
                         <text>安装方式</text>
                         <view class="inputBox">
                             <picker
@@ -198,7 +241,7 @@
                                 }}</view>
                             </picker>
                         </view>
-                    </view>
+                    </view> -->
                     <!-- <view class="wifItem" style="margin-top: 30rpx">
                         <text>指示灯开关</text>
                         <view class="inputBox">
@@ -217,14 +260,14 @@
                 </view>
                 <view class="bottomTwo">
                     <view class="previousTip" @click="goPrevious">上一步</view>
-                    <view class="nextTip" @click="bindDevice">下一步</view>
+                    <view class="nextTip" @click="bindDevice">完成</view>
                 </view>
             </view>
         </view>
     </view>
 </template>
 <script>
-const airkiss = requirePlugin("airkiss");
+// const airkiss = requirePlugin("airkiss");
 export default {
     name: "my",
     data() {
@@ -257,6 +300,14 @@ export default {
             yyEnd: "",
             zzStart: "",
             zzEnd: "",
+            // 坐摔参数
+            fallSettingEnabled: 0,
+            lowZMax: 0.5,
+            humanPredThreshold: 0.78,
+            minEventsForDetection: 3,
+            minHumanEventsForDetection: 2,
+            lowHighSnrRatio: false,
+            lowMidSnrRatio: false,
             // statusLight: 1,
             showDeviceInfo: false,
             devInfo: "",
@@ -310,13 +361,6 @@ export default {
                 this.mountPlain = "Ceiling";
             }
         },
-        // onChange({ detail }) {
-        //     this.statusLight = detail;
-        //     if(detail){
-
-        //     }
-        //     console.log(this.statusLight, 9999);
-        // },
         goNextTip() {
             if (!this.devName.length > 10) {
                 uni.showModal({
@@ -332,13 +376,13 @@ export default {
                 });
                 return;
             }
-            if (!this.wifiPassword) {
-                uni.showModal({
-                    content: "请先开始配网!",
-                    showCancel: 1500,
-                });
-                return;
-            }
+            // if (!this.wifiPassword) {
+            //     uni.showModal({
+            //         content: "请先开始配网!",
+            //         showCancel: 1500,
+            //     });
+            //     return;
+            // }
             if (!this.devName) {
                 uni.showModal({
                     content: "请输入设备名称!",
@@ -397,9 +441,9 @@ export default {
                 userId: uni.getStorageSync("userId"),
                 devName: this.devName,
                 height: parseInt(this.height),
-                wifiName: this.wifiName,
-                wifiPassword: this.wifiPassword,
-                northAngle: this.northAngle,
+                // wifiName: this.wifiName,
+                // wifiPassword: this.wifiPassword,
+                northAngle: this.northAngle == "" ? "0" : this.northAngle,
                 mountPlain:
                     this.mountPlain == "" ? "mountPlain" : this.mountPlain,
                 installPosition: this.installPosition,
@@ -409,9 +453,15 @@ export default {
                 yyEnd: parseInt(this.yyEnd),
                 zzStart: parseInt(this.zzStart),
                 zzEnd: parseInt(this.zzEnd),
+                // fallSettingEnabled: this.fallSettingEnabled,
+                // lowZMax: this.lowZMax,
+                // humanPredThreshold: this.humanPredThreshold,
+                // minEventsForDetection: this.minEventsForDetection,
+                // minHumanEventsForDetection: this.minHumanEventsForDetection,
+                // lowHighSnrRatio: false,
+                // lowMidSnrRatio: false,
                 // statusLight: this.statusLight,
             };
-
             if (this.editFlag) {
                 this.$http
                     .post("wap/device/updateDevice", deviceBandingParams, {
@@ -422,44 +472,17 @@ export default {
                     .then((res) => {
                         if (res.data.code == 200) {
                             uni.showToast({
-                                title: "绑定成功",
-                                icon: "success",
-                                duration: 1500,
-                            });
-                            uni.setStorageSync("devId", res.data.data.devId);
-                            uni.reLaunch({
-                                url:
-                                    "/pagesA/roomSetting/roomSetting?devId=" +
-                                    res.data.data.devId,
-                            });
-                        } else {
-                            uni.showToast({
-                                title: res.data.message,
-                                icon: "none",
-                                duration: 1500,
-                            });
-                        }
-                    });
-            } else {
-                this.$http
-                    .post("wap/device/deviceBinding", deviceBandingParams, {
-                        header: {
-                            "Content-Type": "application/json;charset=UTF-8",
-                        },
-                    })
-                    .then((res) => {
-                        if (res.data.code == 200) {
-                            uni.showToast({
-                                title: "绑定成功",
+                                title: "设置成功",
                                 icon: "success",
                                 duration: 1500,
                             });
                             uni.setStorageSync("devId", res.data.data.devId);
-                            uni.reLaunch({
-                                url:
-                                    "/pagesA/roomSetting/roomSetting?devId=" +
-                                    res.data.data.devId,
-                            });
+
+                            setTimeout(() => {
+                                uni.navigateBack({
+                                    delta: 1,
+                                });
+                            }, 1500);
                         } else {
                             uni.showToast({
                                 title: res.data.message,
@@ -515,8 +538,7 @@ export default {
                 return;
             }
             uni.showLoading({
-                title: "配网中",
-                mask: true,
+                title: "配网中...",
             });
             airkiss.startAirkiss(this.wifiName, this.wifiPassword, (res) => {
                 uni.hideLoading();
@@ -590,11 +612,20 @@ export default {
             this.yyEnd = devInfo.yyEnd;
             this.zzStart = devInfo.zzStart;
             this.zzEnd = devInfo.zzEnd;
+
+            // this.fallSettingEnabled = devInfo.fallSettingEnabled;
+            // this.lowZMax = devInfo.lowZMax;
+            // this.humanPredThreshold = devInfo.humanPredThreshold;
+            // this.minEventsForDetection = devInfo.minEventsForDetection;
+            // this.minHumanEventsForDetection =
+            //     devInfo.minHumanEventsForDetection;
+            // this.lowHighSnrRatio = devInfo.lowHighSnrRatio;
+            // this.lowMidSnrRatio = devInfo.lowMidSnrRatio;
             // this.statusLight = devInfo.statusLight;
         },
     },
     onLoad(options) {
-        this.refreshWifi();
+        // this.refreshWifi();
         if (JSON.stringify(options) != "{}") {
             this.devInfo = JSON.parse(options.devInfo);
             this.setDevInfo(this.devInfo);
@@ -602,11 +633,10 @@ export default {
         } else {
             console.log("没有参数");
         }
-        // this.$refs.alarModel.connectMQTTwo();
     },
 
     onUnload(options) {
-        airkiss.stopAirkiss();
+        // airkiss.stopAirkiss();
     },
     onShow() {},
 };
@@ -615,51 +645,24 @@ export default {
 .home-warp {
     position: relative;
     height: 100vh;
+    padding-top: 30rpx;
     background: linear-gradient(180deg, #faede2 0%, #f4f4f4 100%);
-    .header {
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-        padding: 0rpx 120rpx;
-        padding-top: 30rpx;
-        .item {
-            position: relative;
-            text-align: center;
-            width: 90rpx;
-            height: 90rpx;
-            image {
-                width: 85rpx;
-                height: 90rpx;
-            }
-            text {
-                position: absolute;
-                top: 50%;
-                left: 50%;
-                transform: translate(-50%, -50%);
-                font-weight: 700;
-                color: #ffffff;
-                font-size: 38rpx;
-                text-align: center;
-            }
-        }
-        .line {
-            width: 56rpx;
-            height: 4rpx;
-            background-color: #e4c5b9;
-        }
-    }
 
     .bodyContent {
         width: 700rpx;
-        height: 740rpx;
-        margin: 20rpx auto 0 auto;
+        height: 600rpx;
+        position: relative;
+        margin: 0 auto;
         background: #ffffff;
         border-radius: 38rpx;
         box-sizing: border-box;
-        padding: 0 30rpx;
         .deviceInfo {
             display: flex;
             flex-direction: column;
+            position: absolute;
+            top: 24%;
+            left: 36%;
+            transform: translate(-50%, -50%);
             padding: 0 auto;
             padding-top: 40rpx;
             width: 200rpx;
@@ -671,11 +674,15 @@ export default {
                 width: 200rpx;
                 height: 200rpx;
             }
-            text {
+            .wifiText {
                 margin-top: 20rpx;
                 text-align: center;
                 font-weight: 500;
             }
+            .deviceText {
+                text-align: center;
+                // margin-left: -10px;
+            }
         }
 
         .deviceInfo.fade-in {
@@ -685,20 +692,24 @@ export default {
 
         .wifiScan {
             margin: 0 auto;
-            padding-top: 75rpx;
             width: 188rpx;
             height: 188rpx;
+            position: absolute;
+            top: 30%;
+            left: 36%;
             image {
                 width: 188rpx;
                 height: 188rpx;
             }
+            .wifiText {
+                width: 208rpx;
+                margin: 20rpx auto;
+                color: #95a4b3;
+                font-size: 24rpx;
+                text-align: center;
+            }
         }
-        .wifiText {
-            margin-top: 20rpx;
-            color: #95a4b3;
-            font-size: 24rpx;
-            text-align: center;
-        }
+
         .wifItem {
             width: 640rpx;
             height: 40rpx;
@@ -747,7 +758,7 @@ export default {
 
     .clientInfo {
         width: 700rpx;
-        height: 140px;
+        height: 180px;
         margin: 20rpx auto 0 auto;
         background: #ffffff;
         border-radius: 38rpx;
@@ -808,63 +819,17 @@ export default {
     height: 100vh;
     background: #f4f4f4;
     .header_two {
+        padding-top: 30rpx;
         width: 750rpx;
-        height: 700rpx;
+        height: 560rpx;
         border-bottom-left-radius: 35rpx;
         border-bottom-right-radius: 35rpx;
         background: linear-gradient(180deg, #faede2 0%, #ffffff 100%);
-        .header {
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-            padding: 0rpx 120rpx;
-            padding-top: 30rpx;
-            .item {
-                position: relative;
-                text-align: center;
-                width: 90rpx;
-                height: 90rpx;
-                image {
-                    width: 85rpx;
-                    height: 90rpx;
-                }
-                text {
-                    position: absolute;
-                    top: 50%;
-                    left: 50%;
-                    transform: translate(-50%, -50%);
-                    font-weight: 700;
-                    color: #ffffff;
-                    font-size: 38rpx;
-                    text-align: center;
-                }
-            }
-            .line {
-                width: 56rpx;
-                height: 4rpx;
-                background-color: #e4c5b9;
-            }
-        }
-        .airCantInfo {
-            margin: 40rpx auto 0 auto;
-            opacity: 20%;
-            width: 400rpx;
-            height: 400rpx;
-            border: 18rpx solid;
-            border-color: #333333;
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            text {
-                color: #999999;
-                font-size: 38rpx;
-                text-align: center;
-            }
-        }
+
         .airbody {
-            margin: 140rpx auto 0 auto;
+            margin: 0 auto;
             width: 700rpx;
-            height: 540rpx;
+            max-height: 1000rpx;
             background: #ffffff;
             border-radius: 38rpx;
             padding: 38rpx 38rpx;

+ 450 - 0
src/pagesA/adNewDevice/adNewDevice.vue

@@ -0,0 +1,450 @@
+<template>
+    <view>
+        <view class="home-warp" v-if="nextTip == 'one'">
+            <view class="bodyContent">
+                <view
+                    class="deviceInfo"
+                    v-if="clientId"
+                    @click="scanCode"
+                    :class="{ 'fade-in': showDeviceInfo }"
+                >
+                    <image src="../../static/ln_small.png" alt=""></image>
+                    <view class="deviceText">{{ clientId }}</view>
+                </view>
+                <view class="wifiScan" @click="scanCode" v-if="!clientId">
+                    <image src="../../static/wifiScan.png" alt=""></image>
+                    <view class="wifiText" v-if="!clientId"
+                        >点击扫一扫识别设备</view
+                    >
+                </view>
+            </view>
+            <view class="clientInfo">
+                <view class="wifItem">
+                    <text>设备名称</text>
+                    <input v-model="devName" placeholder="请输入设备名称" />
+                </view>
+                <view class="wifItem" style="margin-top: 30rpx">
+                    <text>安装位置</text>
+                    <picker
+                        @change="bindPickerChange"
+                        :value="installPositionIndex"
+                        :range="selectArray"
+                        style="margin-left: auto"
+                    >
+                        <view class="uni-input">{{
+                            selectArray[installPositionIndex]
+                        }}</view>
+                    </picker>
+
+                    <image
+                        src="../../static/rightArrow.png"
+                        alt=""
+                        @click="showPicker"
+                    ></image>
+                </view>
+                <view class="wifItem" style="margin-top: 30rpx">
+                    <text>安装方式</text>
+                    <picker
+                        @change="bindPickerChangeThree"
+                        :value="mountPlainIndex"
+                        :range="mountPlainArray"
+                        style="margin-left: auto"
+                    >
+                        <view class="uni-input">{{
+                            mountPlainArray[mountPlainIndex]
+                        }}</view>
+                    </picker>
+                    <image
+                        src="../../static/rightArrow.png"
+                        alt=""
+                        @click="showPicker"
+                    ></image>
+                </view>
+            </view>
+            <view class="bottom">
+                <view class="nextTip" @click="goNextTip">完成</view>
+            </view>
+        </view>
+    </view>
+</template>
+<script>
+export default {
+    data() {
+        return {
+            editFlag: false,
+            selectArray: ["卫生间", "客厅", "餐厅", "卧室"],
+            angleArray: ["0", "90", "180", "270"],
+            mountPlainArray: ["墙装", "顶装"],
+            installPositionIndex: 0,
+            northAngleIndex: 0,
+            mountPlainIndex: 0,
+            pickModel: false,
+            environmentJudge: true,
+            nextTip: "one",
+            // 设备信息参数
+            clientId: "",
+            userId: uni.getStorageSync("userId"),
+            devName: "",
+            width: "",
+            length: "",
+            height: "",
+            wifiName: "",
+            wifiPassword: "",
+            northAngle: "0",
+            mountPlain: "Wall",
+            installPosition: "Toilet",
+            xxStart: "",
+            xxEnd: "",
+            yyStart: "",
+            yyEnd: "",
+            zzStart: "",
+            zzEnd: "",
+            // statusLight: 1,
+            showDeviceInfo: false,
+            devInfo: "",
+        };
+    },
+    watch: {
+        clientId(newVal) {
+            if (newVal) {
+                this.$nextTick(() => {
+                    this.showDeviceInfo = true;
+                });
+            } else {
+                this.showDeviceInfo = false;
+            }
+        },
+    },
+    computed: {},
+    methods: {
+        scanCode() {
+            wx.scanCode({
+                success: (res) => {
+                    this.clientId = res.result;
+                },
+            });
+        },
+        showPicker() {
+            this.pickModel = true;
+        },
+        bindPickerChange(e) {
+            this.installPositionIndex = e.detail.value;
+            if (this.installPositionIndex == "0") {
+                this.installPosition = "Toilet";
+            } else if (this.installPositionIndex == "1") {
+                this.installPosition = "LivingRoom";
+            } else if (this.installPositionIndex == "2") {
+                this.installPosition = "Restaurant";
+            } else if (this.installPositionIndex == "3") {
+                this.installPosition = "Bedroom";
+            }
+        },
+        bindPickerChangeTwo(e) {
+            this.northAngleIndex = e.detail.value;
+            this.northAngle = this.angleArray[e.detail.value];
+        },
+        bindPickerChangeThree(e) {
+            this.mountPlainIndex = e.detail.value;
+            this.mountPlain = this.mountPlainArray[e.detail.value];
+            if (this.mountPlainArray[e.detail.value] == "墙装") {
+                this.mountPlain = "Wall";
+            } else {
+                this.mountPlain = "Ceiling";
+            }
+        },
+        goNextTip() {
+            if (!this.devName.length > 10) {
+                uni.showModal({
+                    content: "设备名称过长!",
+                    showCancel: 1500,
+                });
+                return;
+            }
+            if (!this.clientId) {
+                uni.showModal({
+                    content: "请先开始扫码!",
+                    showCancel: 1500,
+                });
+                return;
+            }
+            if (!this.devName) {
+                uni.showModal({
+                    content: "请输入设备名称!",
+                    showCancel: 1500,
+                });
+                return;
+            }
+            this.bindDevice();
+        },
+        bindDevice() {
+            let deviceBandingParams = {
+                clientId: this.clientId,
+                userId: uni.getStorageSync("userId"),
+                devName: this.devName,
+                mountPlain:
+                    this.mountPlain == "" ? "mountPlain" : this.mountPlain,
+                installPosition: this.installPosition,
+            };
+            this.$http
+                .post("wap/device/deviceBinding", deviceBandingParams, {
+                    header: {
+                        "Content-Type": "application/json;charset=UTF-8",
+                    },
+                })
+                .then((res) => {
+                    if (res.data.code == 200) {
+                        uni.showToast({
+                            title: "绑定成功",
+                            icon: "success",
+                            duration: 1500,
+                        });
+                        uni.reLaunch({
+                            url: "/pages/home/home",
+                        });
+                    } else {
+                        uni.showToast({
+                            title: res.data.message,
+                            icon: "none",
+                            duration: 1500,
+                        });
+                    }
+                });
+        },
+        setDevInfo(devInfo) {
+            this.clientId = devInfo.clientId;
+            this.devName = devInfo.devName;
+            this.height = devInfo.height;
+            this.wifiName = devInfo.wifiName;
+            this.wifiPassword = devInfo.wifiPassword;
+            this.northAngle = devInfo.northAngle;
+            if (this.northAngle == 0) {
+                this.northAngleIndex = 0;
+            } else if (this.northAngle == 90) {
+                this.northAngleIndex = 1;
+            } else if (this.northAngle == 180) {
+                this.northAngleIndex = 2;
+            } else if (this.northAngle == 270) {
+                this.northAngleIndex = 3;
+            }
+            this.mountPlain = devInfo.mountPlain;
+            if (this.northAngle == "Wall") {
+                this.mountPlainIndex = 0;
+            } else if (this.northAngle == 90) {
+                this.mountPlainIndex = 1;
+            }
+            this.installPosition = devInfo.installPosition;
+
+            if (this.installPosition == "Toilet") {
+                this.installPositionIndex = 0;
+            } else if (this.installPosition == "LivingRoom") {
+                this.installPositionIndex = 1;
+            } else if (this.installPosition == "Restaurant") {
+                this.installPositionIndex = 2;
+            } else if (this.installPosition == "Bedroom") {
+                this.installPositionIndex = 3;
+            }
+            this.xxStart = devInfo.xxStart;
+            this.xxEnd = devInfo.xxEnd;
+            this.yyStart = devInfo.yyStart;
+            this.yyEnd = devInfo.yyEnd;
+            this.zzStart = devInfo.zzStart;
+            this.zzEnd = devInfo.zzEnd;
+            // this.statusLight = devInfo.statusLight;
+        },
+    },
+    onLoad(options) {
+        if (JSON.stringify(options) != "{}") {
+            this.devInfo = JSON.parse(options.devInfo);
+            this.setDevInfo(this.devInfo);
+            this.editFlag = true;
+        } else {
+            console.log("没有参数");
+        }
+    },
+
+    onUnload(options) {},
+    onShow() {},
+};
+</script>
+<style lang="less" scoped>
+.home-warp {
+    position: relative;
+    height: 100vh;
+    background: linear-gradient(180deg, #faede2 0%, #f4f4f4 100%);
+    .bodyContent {
+        width: 700rpx;
+        height: 600rpx;
+        position: relative;
+        margin: 0 auto;
+        background: #ffffff;
+        border-radius: 38rpx;
+        box-sizing: border-box;
+        padding: 0 30rpx;
+        padding-top: 30rpx;
+        .deviceInfo {
+            display: flex;
+            flex-direction: column;
+            position: absolute;
+            top: 24%;
+            left: 36%;
+            transform: translate(-50%, -50%);
+            padding: 0 auto;
+            padding-top: 40rpx;
+            width: 200rpx;
+            margin: 0 auto;
+            transition: all 0.8s ease;
+            opacity: 0;
+            transform: translateY(20rpx);
+            image {
+                width: 200rpx;
+                height: 200rpx;
+            }
+            .wifiText {
+                margin-top: 20rpx;
+                text-align: center;
+                font-weight: 500;
+            }
+            .deviceText {
+                text-align: center;
+                // margin-left: -10px;
+            }
+        }
+
+        .deviceInfo.fade-in {
+            opacity: 1;
+            transform: translateY(0);
+        }
+
+        .wifiScan {
+            margin: 0 auto;
+            width: 250rpx;
+            height: auto;
+            position: absolute;
+            top: 30%;
+            left: 50%;
+            transform: translateX(-50%);
+            display: flex;
+            flex-direction: column;
+            align-items: center; // 横向居中
+
+            image {
+                width: 188rpx;
+                height: 188rpx;
+            }
+
+            .wifiText {
+                margin-top: 20rpx;
+                color: #95a4b3;
+                font-size: 24rpx;
+                text-align: center;
+                width: auto; // 自适应宽度
+                max-width: 400rpx; // 限制最大宽度,防止太宽撑开
+                word-break: break-word; // 超出一行自动换行
+            }
+        }
+
+        .wifItem {
+            width: 640rpx;
+            height: 40rpx;
+            display: flex;
+            align-content: center;
+            border-bottom: 2rpx solid #ebeff5;
+            padding-bottom: 38rpx;
+            image {
+                width: 40rpx;
+                height: 40rpx;
+            }
+            text {
+                margin-left: 20rpx;
+                color: #111111;
+                font-size: 32rpx;
+            }
+            input {
+                margin-left: auto;
+                text-align: right;
+            }
+
+            .refresh {
+                width: 80rpx;
+                height: 50rpx;
+                margin-left: 10rpx;
+                border-radius: 10rpx;
+                background-color: #f3e2dd;
+                color: #151515;
+                text-align: center;
+            }
+        }
+
+        .btn {
+            margin: 0 auto;
+            margin-top: 38rpx;
+            width: 240rpx;
+            height: 75rpx;
+            background: #f3e2dd;
+            border-radius: 60rpx;
+            text-align: center;
+            line-height: 75rpx;
+            color: #111111;
+            font-size: 32rpx;
+        }
+    }
+
+    .clientInfo {
+        width: 700rpx;
+        height: 180px;
+        margin: 20rpx auto 0 auto;
+        background: #ffffff;
+        border-radius: 38rpx;
+        box-sizing: border-box;
+        padding: 30rpx 30rpx;
+        .wifItem {
+            width: 640rpx;
+            height: 40rpx;
+            display: flex;
+            border-bottom: 2rpx solid #ebeff5;
+            padding-bottom: 38rpx;
+            text {
+                margin-left: 20rpx;
+                color: #111111;
+                font-size: 32rpx;
+            }
+            input {
+                margin-left: auto;
+                // width: 70%;
+                text-align: right;
+            }
+            image {
+                width: 30rpx;
+                height: 30rpx;
+                margin-top: 10rpx;
+            }
+        }
+    }
+    .bottom {
+        position: fixed;
+        bottom: 0;
+        left: 0;
+        width: 750rpx;
+        height: 170rpx;
+        background: #ffffff;
+        box-shadow: 0rpx 1.17rpx 0rpx #eaeaea inset;
+
+        .nextTip {
+            width: 675rpx;
+            height: 95rpx;
+            background: linear-gradient(97.91deg, #a27867 0%, #74483d 100%);
+            border-radius: 28rpx;
+            box-shadow: 0rpx 4.69rpx 18.75rpx rgba(72, 41, 29, 0.15),
+                0rpx 9.38rpx 9.38rpx rgba(154, 132, 89, 0.2),
+                0rpx -4.69rpx 28.13rpx 4.69rpx #a16647 inset;
+
+            font-weight: 500;
+            color: #ffffff;
+            font-size: 32rpx;
+            text-align: center;
+            margin: 30rpx auto;
+            line-height: 95rpx;
+        }
+    }
+}
+</style>

+ 342 - 270
src/pagesA/deviceDetail/deviceDetail.vue

@@ -10,46 +10,56 @@
                     width: `${length / 200}px`,
                     height: `${width / 200}px`,
                     position: 'relative',
-                    overflow: 'hidden',
+                    transform: `scale(${zoomTimes})`,
+                    border: `${9 / zoomTimes}rpx solid #333333`,
                 }"
                 :class="[
                     width < 25500 && length < 25000 ? 'tranStyle' : 'center',
                 ]"
             >
-                <view
-                    v-for="(item, index) in modules"
-                    :key="index"
-                    class="moduleContent"
-                >
+                <template v-if="modules.length > 0">
                     <view
-                        :class="item.type"
-                        :style="{
-                            width: `${item.width / 2}px`,
-                            height: `${item.length / 2}px`,
-                            top: `${item.top / 2}px`,
-                            left: `${item.left / 2}px`,
-                            transform: `rotate(${item.rotate}deg)`,
-                            'transform-origin': 'center center',
-                        }"
+                        v-for="(item, index) in modules"
+                        :key="index"
+                        class="moduleContent"
                     >
-                        <image
-                            class="module-img"
-                            :src="`../../static/furnitures/${item.type}.png`"
-                            mode=""
-                        />
+                        <view
+                            :class="item.type"
+                            :style="{
+                                width: `${item.width / 2}px`,
+                                height: `${item.length / 2}px`,
+                                top: `${item.top / 2}px`,
+                                left: `${item.left / 2}px`,
+                                transform: `rotate(${item.rotate}deg)`,
+                                'transform-origin': 'center center',
+                                zIndex: 4444,
+                            }"
+                        >
+                            <image
+                                style="
+                                    width: 100%;
+                                    height: 100%;
+                                    display: block;
+                                "
+                                :src="`../../static/furnitures/${item.type}.png`"
+                                mode=""
+                            />
+                        </view>
                     </view>
-                </view>
-                <template v-if="devType == 'LNB'">
+                </template>
+                <template>
                     <view v-for="item in targetPoints" :key="item.id">
                         <image
                             class="action-icon-M"
                             :style="{
                                 position: 'absolute',
                                 transform: `translate3d(${
-                                    item.displayX / 2 + 100
+                                    item.displayX / 2
                                 }px, ${
-                                    -item.displayY / 2 + 100
-                                }px, 0) translate(-50%, -50%)`,
+                                    -item.displayY / 2
+                                }px, 0) translate(-50%, -50%) scale(${
+                                    1.5 / zoomTimes
+                                })`,
                                 zIndex: 9999,
                                 transition: 'transform 1s linear',
                                 willChange: 'transform',
@@ -59,33 +69,18 @@
                         />
                     </view>
                 </template>
-                <template v-else>
-                    <image
-                        v-if="actionName"
-                        class="action-icon-G"
-                        :style="{
-                            top: `${top / 200}px`,
-                            left: `${left / 200}px`,
-                        }"
-                        :src="`../../images/furnitures/${actionName}.png`"
-                        mode=""
-                    />
-                </template>
             </view>
 
-            <!-- <image
+            <image
                 class="redar-pic"
                 src="../../static/rander.png"
                 mode=""
                 :style="{
-                    transform:
-                        'translate(' +
-                        -xOffset / 100 +
-                        'rpx,' +
-                        -yOffset / 100 +
-                        'rpx)',
+                    transform: `translate(${-xOffset}rpx, ${-yOffset}rpx) rotate(${
+                        devInfo.northAngle
+                    }deg)`,
                 }"
-            /> -->
+            />
         </view>
 
         <view class="switchBox">
@@ -113,7 +108,7 @@
                 class="title"
                 v-if="
                     devInfo.installPosition == 'Bedroom' ||
-                    (breathRate === 0 && breathRate !== '')
+                    (breathRate !== 0 && breathRate !== '')
                 "
             >
                 <view
@@ -149,50 +144,35 @@
                 </view>
             </view>
 
-            <view class="title" v-if="devInfo.installPosition == 'Toilet'">
-                <view class="title-text" style="color: #22dea7"
-                    >今日卫生间使用频次</view
-                >
-                <view class="title-btn" style="color: #22dea7"
-                    >{{
-                        freQuenceList.length == "" ? "0" : freQuenceList.length
-                    }}次</view
-                >
-            </view>
-            <view class="title" v-if="devInfo.installPosition != 'Toilet'">
+            <view class="title">
                 <view class="title-text" style="color: #22dea7"
                     >今日进出频次</view
                 >
-                <view class="title-btn" style="color: #22dea7"
-                    >{{
-                        freQuenceList.length == "" ? "0" : freQuenceList.length
-                    }}次</view
-                >
+                <view class="title-btn" style="color: #22dea7">
+                    {{ freQuenceList.length || 0 }}次
+                </view>
             </view>
-            <view
-                class="stayDetail"
-                v-if="freQuenceList.length > 0 && freQuenceList.length == 1"
-            >
-                <view v-for="item in freQuenceList" :key="item.id">
-                    <view class="stayDetail-item">
-                        <view class="stayDetail-text"
-                            >{{
-                                item.info.start_time.slice(11, 16)
-                            }}目标进入</view
-                        >
-                        <view class="stayDetail-btn"
-                            >{{
-                                item.info.end_time.slice(11, 16)
-                            }}目标离开</view
-                        >
+
+            <view class="stayDetail" v-if="freQuenceList.length > 0">
+                <!-- 只有一条时直接展示 -->
+                <view v-if="freQuenceList.length === 1">
+                    <view
+                        v-for="item in freQuenceList"
+                        :key="item.id"
+                        class="stayDetail-item"
+                    >
+                        <view class="stayDetail-text">
+                            {{ formatTime(item.enterTime) }}目标进入
+                        </view>
+                        <view class="stayDetail-btn">
+                            {{ formatTime(item.leaveTime) }}目标离开
+                        </view>
                     </view>
                 </view>
-            </view>
-            <view
-                class="stayDetail"
-                v-if="freQuenceList.length > 0 && freQuenceList.length > 1"
-            >
+
+                <!-- 多条时用 swiper 展示 -->
                 <swiper
+                    v-else
                     class="auto-scroll-swiper"
                     :indicator-dots="false"
                     :autoplay="false"
@@ -208,44 +188,33 @@
                         :key="item.id"
                         class="stayDetail-item"
                     >
-                        <view class="stayDetail-text"
-                            >{{
-                                item.info.start_time.slice(11, 16)
-                            }}目标进入</view
-                        >
-                        <view class="stayDetail-btn"
-                            >{{
-                                item.info.end_time.slice(11, 16)
-                            }}目标离开</view
-                        >
+                        <view class="stayDetail-text">
+                            {{ formatTime(item.enterTime) }}目标进入
+                        </view>
+                        <view class="stayDetail-btn">
+                            {{ formatTime(item.leaveTime) }}目标离开
+                        </view>
                     </swiper-item>
                 </swiper>
             </view>
-            <!-- <view class="title" v-if="devInfo.installPosition == 'Toilet'">
-                <view class="title-text" style="color: #ff976a"
-                    >昨日卫生间使用频次</view
+        </view>
+        <view class="box" v-if="!breathShow">
+            <view class="handle-btn">
+                <view class="btn1" @click="shareDevice" v-if="shareJudge"
+                    >分享</view
                 >
-                <view class="title-btn" style="color: #ff976a"
-                    >{{
-                        freQuenceList.length == "" ? "0" : freQuenceList.length
-                    }}次</view
+                <view class="noShareFlag" @click="noShareFlag" v-else
+                    >分享</view
                 >
-            </view>
-            <view class="title" v-else>
-                <view class="title-text" style="color: #ff976a"
-                    >昨日进出频次</view
+                <view
+                    class="btn1"
+                    @click="gotoSetting"
+                    v-if="devInfo.online == 1"
+                    >设置</view
                 >
-                <view class="title-btn" style="color: #ff976a"
-                    >{{
-                        freQuenceList.length == "" ? "0" : freQuenceList.length
-                    }}次</view
+                <view class="noShareFlag" @click="gotoSetting" v-else
+                    >设置</view
                 >
-            </view> -->
-        </view>
-        <view class="box" v-if="!breathShow">
-            <view class="handle-btn">
-                <view class="btn1" @click="shareDevice">分享</view>
-                <view class="btn1" @click="gotoSetting">设置</view>
                 <view class="btn2" @click="healthAlarm">守护计划</view>
             </view>
         </view>
@@ -360,29 +329,17 @@
                 @click="breathShow = false"
             ></l-echart>
         </view>
-
-        <!-- <alarModel
-            v-if="isInitAlarm"
-            :clientIdProp="clientIdProp"
-            @sendChange="receptionChange"
-            @sendHealth="receptHealth"
-        /> -->
-
-        <!-- <alarModel
-            v-if="isInitAlarm"
-            @sendChange="receptionChange"
-            @sendHealth="receptHealth"
-        /> -->
+        <alarModel v-if="showModle" ref="alarmModel" />
     </view>
 </template>
 <script>
 import * as echarts from "../../uni_modules/lime-echart/static/echarts.min";
-
-import mqtt from "../../utils/mqtt";
+import MqttService from "../../utils/globalMqtt.js";
 
 export default {
     data() {
         return {
+            clientId: "",
             width: 0, //检测区域宽度
             length: 0, //检测区域长度
             xOffset: 0,
@@ -393,10 +350,7 @@ export default {
             endDate: "",
             softWare: "",
             statusLight: 0,
-            currentDate: new Date().getTime(),
-            lnbAction: "action8",
-            wsj: false,
-            todayWcTimes: "",
+            lnbAction: "",
             stayDetail: "",
             todayDate: "",
             dev_id: "",
@@ -404,7 +358,6 @@ export default {
             devName: "",
             devType: "",
             localPhone: uni.getStorageSync("phone"),
-            startArr: [],
             freQuenceList: [],
             // mqtt相关
             currentIndex: 0,
@@ -415,17 +368,14 @@ export default {
             shareModel: false,
             alarmModel: false,
             sharedPhone: "",
-            messageFlag: true,
-            serviceNumberFlag: true,
-            voipFlag: true,
-            isInitAlarm: "",
-
+            messageFlag: false,
+            serviceNumberFlag: false,
+            voipFlag: false,
             // mqtt模块
             targetPoints: {},
             inactivityTimer: null,
             left: 0,
             top: 0,
-            clientIdProp: null,
             breathRate: "",
             breathShow: false,
             breathRpmList: [],
@@ -479,10 +429,14 @@ export default {
                 animation: true,
                 animationDuration: 100,
             },
-            index: 0,
-            loopTimer: null,
-            mqttClienTwoFlag: false,
-            mqttClientOne: false,
+            showModle: false,
+            zoomTimes: 2,
+            // 设备分享权限判断
+            shareJudge: false,
+            unsubscribeFn: null,
+            fallingEventChange: null,
+            lnbActionJudean: false,
+            setIntervalVal: null,
         };
     },
     computed: {},
@@ -506,11 +460,13 @@ export default {
                             Math.abs(
                                 this.devInfo.xxEnd - this.devInfo.xxStart
                             ) * 100;
-                        this.xOffset =
-                            (this.devInfo.xxStart + this.devInfo.xxEnd) * 50;
-                        this.yOffset =
-                            -(this.devInfo.yyStart + this.devInfo.yyEnd) * 50;
                         this.statusLight = this.devInfo.statusLight;
+                        this.calculate(this.width, this.length);
+                        // 设备分享权限判断
+                        this.shareJudge =
+                            this.devInfo.userId == uni.getStorageSync("userId")
+                                ? true
+                                : false;
                     }
                 })
 
@@ -550,21 +506,16 @@ export default {
                 });
         },
 
-        parseDeviceItem(devItemStr) {
-            try {
-                const devItem = JSON.parse(devItemStr);
-                if (!devItem || !devItem.devId || !devItem.clientId) {
-                    throw new Error("设备信息不完整");
-                }
-                return devItem;
-            } catch (e) {
-                throw new Error("设备信息解析失败: " + e.message);
-            }
-        },
         // 分享功能模块
         shareDevice() {
             this.choiceVisible = true;
         },
+        noShareFlag() {
+            uni.showToast({
+                title: "您没有分享权限",
+                icon: "none",
+            });
+        },
         onShareConfirm() {
             if (!this.sharedPhone) {
                 uni.showModal({
@@ -654,56 +605,36 @@ export default {
                     JSON.stringify(this.devInfo),
             });
         },
-        getFrequency(clientId) {
+        getFrequency(devId) {
+            let params = {
+                devId: devId,
+                createTimeStart: this.$time(new Date()),
+                createTimeEnd: this.$time(new Date()),
+                pageNo: 1,
+                pageSize: 999,
+            };
+
             this.$http
-                .post(`wap/stats/alarmEventsQuery`, {
-                    clientId: clientId,
-                    createTimeStart: this.$time(new Date()),
-                    createTimeEnd: this.$time(new Date()),
-                    eventType: 1,
+                .post(`wap/stats/inOutEventQuery`, params, {
+                    header: {
+                        "Content-Type": "application/json;charset=UTF-8",
+                    },
                 })
                 .then((res) => {
                     if (res.data.code == 200) {
                         if (res.data.data.rows.length > 0) {
-                            this.freQuenceList = this.parseJsonToObjects(
-                                res.data.data.rows
-                            );
+                            this.freQuenceList = res.data.data.rows;
                         } else {
                             this.freQuenceList = [];
                         }
                     } else {
-                        wx.showToast({
+                        uni.showToast({
                             title: res.data.message,
                             icon: "none",
                         });
                     }
                 });
         },
-        parseJsonToObjects(jsonArray) {
-            return jsonArray.map((item) => {
-                // 解析info字段的JSON字符串
-                let infoData = {};
-                try {
-                    infoData = JSON.parse(item.info);
-                } catch (e) {
-                    console.error("Failed to parse info JSON:", e);
-                }
-
-                return {
-                    id: item.id,
-                    clientId: item.clientId,
-                    tenantId: item.tenantId,
-                    devName: item.devName,
-                    uuid: item.uuid,
-                    planUuid: item.planUuid,
-                    eventType: item.eventType,
-                    info: infoData,
-                    isHandle: item.isHandle,
-                    createTime: item.createTime,
-                    remark: item.remark,
-                };
-            });
-        },
         // 健康闹钟方法
         healthAlarm() {
             uni.navigateTo({
@@ -723,7 +654,7 @@ export default {
 
         autoSwipe() {
             if (this.freQuenceList && this.freQuenceList.length > 0) {
-                setInterval(() => {
+                this.setIntervalVal = setInterval(() => {
                     let nextIndex = this.currentIndex + 1;
                     if (nextIndex >= this.freQuenceList.length) {
                         nextIndex = 0; // 循环到第一个项目
@@ -739,12 +670,6 @@ export default {
                     JSON.stringify(this.freQuenceList),
             });
         },
-        getCurrentDate() {
-            const now = new Date();
-            this.currentDate = `${now.getFullYear()}-${
-                now.getMonth() + 1
-            }月${now.getDate().toString().padStart(2, "0")}日`;
-        },
         receptionChange(val) {
             this.targetPoints = val;
         },
@@ -754,7 +679,6 @@ export default {
         },
 
         // echarts图表模块
-
         getOption(list) {
             // 固定 X 轴 [0 ~ 60]
             const xData = Array.from({ length: 61 }, (_, i) => i);
@@ -868,7 +792,6 @@ export default {
                             });
                         },
                         fail: (err) => {
-                            console.error("保存失败:", err);
                             uni.showToast({
                                 title: "保存失败",
                                 icon: "none",
@@ -877,7 +800,6 @@ export default {
                     });
                 },
                 fail: (err) => {
-                    console.error("导出失败:", err);
                     uni.showToast({
                         title: "导出失败",
                         icon: "none",
@@ -935,10 +857,6 @@ export default {
                         params
                     );
                 }
-                clientTwo = mqtt.connect(
-                    "wxs://data.radar-power.cn/mqtt/",
-                    params
-                );
             }
             console.log("主题开始订阅5555");
             // 存储client引用以便后续操作
@@ -989,33 +907,26 @@ export default {
             clearTimeout(this.inactivityTimer);
             this.inactivityTimer = setTimeout(() => {
                 this.targetPoints = {};
+                console.log("没有点位,消除点位数据");
             }, 1500);
-
-            console.log(topic, 99999);
-            console.log(JSON.parse(message.toString()), "99999999");
-
             // 验证topic格式
             const match = topic.match(/^\/dev\/(.+)\/tracker_targets$/);
             if (!match || match[1] !== clientId) return;
 
             try {
                 const data = JSON.parse(message.toString());
+                console.log(data.tracker_targets, "点位消息");
                 if (data.health) {
                     if (
                         data.health.breath_rpm ||
                         data.health.breath_rpm === 0
                     ) {
                         this.receptHealth(Math.floor(data.health.breath_rpm));
-                        // this.$emit(
-                        //     "sendHealth",
-                        //     Math.floor(data.health.breath_rpm)
-                        // );
                     } else {
-                        // this.$emit("sendHealth", 0);
                     }
                 }
                 this.processTrackerData(data.tracker_targets);
-                console.log(data.tracker_targets, "MQTT消息解析成功22222");
+                // console.log(data.tracker_targets, "MQTT消息解析成功22222");
             } catch (e) {
                 console.error("MQTT消息解析失败", e);
             }
@@ -1069,7 +980,6 @@ export default {
                         (item) => item !== null && item !== undefined
                     );
                 }
-                console.log("当前目标点11111", this.targetPoints);
             }
         },
         createNewTargetPoint(x, y, z, id) {
@@ -1078,8 +988,8 @@ export default {
                 y,
                 z,
                 id,
-                displayX: x,
-                displayY: y,
+                displayX: x - Number(this.devInfo.xxStart),
+                displayY: y - Number(this.devInfo.yyEnd),
                 lastX: x,
                 lastY: y,
             };
@@ -1097,53 +1007,186 @@ export default {
                     z,
                     lastX: x,
                     lastY: y,
-                    displayX: x,
-                    displayY: y,
+                    displayX: x - Number(this.devInfo.xxStart),
+                    displayY: y - Number(this.devInfo.yyEnd),
                 };
             }
 
             return existingPoint;
         },
-
-        closemqtTwo() {
-            this.mqttClienTwoFlag = false;
-            if (this.mqttClientOne) {
-                this.mqttClientOne.end();
-                this.mqttClientOne = null;
-                console.log("MQTT连接已断开");
+        // 计算缩放比例
+        calculate(width, length) {
+            let zoomTime = width > length ? width : length;
+            let practical = zoomTime / 100;
+            this.zoomTimes = 710 / practical;
+            // this.normalizeZoomTimes(this.zoomTimes);
+            this.zoomTimes = this.zoomTimes - 0.2;
+            this.zoomTimes = Number(this.zoomTimes.toFixed(2));
+            this.xOffset = this.devInfo.xxStart + this.devInfo.xxEnd;
+            this.yOffset = -(this.devInfo.yyStart + this.devInfo.yyEnd);
+            console.log(this.xOffset, this.yOffset, this.zoomTimes, "偏移量");
+            this.lnbActionJudean = true;
+        },
+        normalizeZoomTimes(num) {
+            const intPart = Math.floor(num);
+            const decimalPart = num - intPart;
+            let result;
+            if (decimalPart >= 0.1 && decimalPart < 0.5) {
+                result = intPart;
+            } else if (decimalPart >= 0.5 && decimalPart < 0.9) {
+                result = intPart + 0.5;
+            } else {
+                result = num;
             }
+            return Math.round(result * 100) / 100;
+        },
+
+        formatTime(time) {
+            return time ? time.slice(11, 19) : "暂无";
+        },
+        initSubscriptions() {
+            const topicList = [
+                {
+                    topic: `/dev/${this.clientId}/tracker_targets`,
+                    key: "unsubscribeFn",
+                    callback: (message, msgTopic) => {
+                        const dataMatch = msgTopic.match(
+                            /^\/dev\/(.+)\/tracker_targets$/
+                        );
+                        const cmdMatch = msgTopic.match(
+                            /^\/mps\/wx_(.+)\/notice$/
+                        );
+                        if (dataMatch && dataMatch[1] === this.clientId) {
+                            this.handleMessage(
+                                msgTopic,
+                                message,
+                                this.clientId
+                            );
+                        } else if (cmdMatch) {
+                            this.$refs.alarmModel.hanOtherMessage(
+                                msgTopic,
+                                message
+                            );
+                        }
+                    },
+                },
+                {
+                    topic: `/dev/${this.clientId}/falling_event_change`,
+                    key: "fallingEventChange",
+                    callback: (message, msgTopic) => {
+                        const dataMatch = msgTopic.match(
+                            /^\/dev\/(.+)\/falling_event_change$/
+                        );
+                        if (dataMatch && dataMatch[1] === this.clientId) {
+                            const dataMessage = JSON.parse(message.toString());
+
+                            console.log(dataMessage, 888888);
+                            if (dataMessage.falling == 1) {
+                                this.falling = dataMessage.falling;
+                                this.lnbAction = "actionWarn";
+                            } else if (
+                                dataMessage.falling == 2 ||
+                                dataMessage.falling == 3
+                            ) {
+                                this.falling = dataMessage.falling;
+                                this.lnbAction = "actionSerious";
+                            } else {
+                                this.lnbAction = "action8";
+                            }
+                        }
+                    },
+                },
+            ];
+
+            topicList.forEach((item) => {
+                // 避免重复订阅
+                if (this[item.key]) return;
+
+                const subscribeFunc = () => {
+                    const unsubscribe = MqttService.subscribe(
+                        "DATA",
+                        item.topic,
+                        item.callback
+                    );
+                    if (unsubscribe) {
+                        this[item.key] = unsubscribe;
+                        console.log(`✅ 已成功订阅主题: ${item.topic}`);
+                    }
+                };
+
+                if (MqttService.dataConnected) {
+                    subscribeFunc();
+                } else {
+                    // MQTT 未连接,等待重连成功再订阅
+                    const handler = () => {
+                        subscribeFunc();
+                        uni.$off("mqttData-ready", handler);
+                    };
+                    uni.$on("mqttData-ready", handler);
+                }
+            });
         },
-    },
-    onShow() {
-        this.clientIdProp = uni.getStorageSync("clientIDetail");
-        this.isInitAlarm = true;
-        this.todayDate = this.$time(new Date(), 2);
     },
     onLoad(options) {
-        const devItem = this.parseDeviceItem(options.devItem);
+        this.lnbAction = "action8";
+        const devItem = JSON.parse(options.devItem);
         const { devId, clientId } = devItem;
-        this.getFrequency(clientId);
-        this.getdevInfo(devId);
-        this.getdevRoomInfo(devId);
-        this.autoPlayinterval = setTimeout(() => {
-            this.autoSwipe();
-        }, 3000);
-        this.getCurrentDate();
+        // 缓存到页面实例
+        this.clientId = clientId;
+        uni.setStorageSync("currentDevItem", devItem);
+        this.getFrequency(devId);
+        this.dev_id = devId;
+        this.todayDate = this.$time(new Date(), 2);
+        // 清理旧定时器
+        clearTimeout(this.autoPlayinterval);
+        this.autoPlayinterval = setTimeout(() => this.autoSwipe(), 3000);
+
+        if (MqttService.dataConnected && MqttService.dataClient) {
+            // 已连接,直接订阅
+            this.initSubscriptions();
+        }
     },
-    onReady() {
-        this.connectMqtt();
+
+    onShow() {
+        this.showModle = true;
+        this.lnbAction = "action8";
+        // MQTT 初始化
+        const userId = uni.getStorageSync("userId");
+        if (!userId) return console.error("MQTT 初始化失败:缺少 userId");
+        if (!MqttService.dataConnected) {
+            // 未连接,先 connect
+            MqttService.connectData(userId)
+                .then((client) => {
+                    // 页面订阅逻辑,等 connect 成功再执行
+                    const handler = () => {
+                        MqttService.resubscribeAll("DATA");
+                        uni.$off("mqttData-ready", handler);
+                    };
+                    uni.$on("mqttData-ready", handler);
+                })
+                .catch((err) => console.error("DATA MQTT 初始化失败", err));
+        }
+        this.getdevInfo(this.dev_id);
+        this.getdevRoomInfo(this.dev_id);
     },
+
     onUnload() {
-        this.isInitAlarm = false;
-        this.inactivityTimer = null;
+        // 清理定时器
+        clearTimeout(this.autoPlayinterval);
+        // 清理自动滑动定时器
+        clearInterval(this.setIntervalVal);
+        this.setIntervalVal = null;
         this.autoPlayinterval = null;
-        this.closemqtTwo();
+        // 取消订阅
+        ["unsubscribeFn", "fallingEventChange"].forEach((key) => {
+            if (this[key]) {
+                this[key]();
+                this[key] = null;
+            }
+        });
     },
     onHide() {
-        this.inactivityTimer = null;
-        this.autoPlayinterval = null;
-        this.isInitAlarm = false;
-        this.closemqtTwo();
+        this.showModle = false;
     },
 };
 </script>
@@ -1182,12 +1225,19 @@ export default {
 
         .center {
             position: absolute;
-            background-color: #fff;
-            border: 14rpx solid #333333;
-            background-image: url("http://jkld.radar-power.com//uploadFiles/framework/file/20250620/toilet_bg.png");
+            overflow: hidden;
+            border: none;
             background-repeat: no-repeat;
             background-position: center;
-            transform: scale(1.3);
+            &::before {
+                content: "";
+                position: absolute;
+                inset: 0;
+                background: url("https://hflnxx.oss-cn-shanghai.aliyuncs.com/IMAGE/20250919/toilet_bg.png")
+                    no-repeat center;
+                background-size: 100% 100%;
+                z-index: 0;
+            }
 
             .moduleContent {
                 position: relative;
@@ -1203,18 +1253,26 @@ export default {
                 }
             }
         }
-
         .tranStyle {
             position: absolute;
+            overflow: hidden;
             background-color: #fff;
-            border: 9rpx solid #333333;
-            background-image: url("http://jkld.radar-power.com//uploadFiles/framework/file/20250620/toilet_bg.png");
+            border: none;
+            // outline: 10rpx solid #333333;
+            // background-image: url("https://hflnxx.oss-cn-shanghai.aliyuncs.com/IMAGE/20250919/toilet_bg.png");
             background-repeat: no-repeat;
             background-position: center;
-            transform: scale(2.2);
-
+            &::before {
+                content: "";
+                position: absolute;
+                inset: 0;
+                background: url("https://hflnxx.oss-cn-shanghai.aliyuncs.com/IMAGE/20250919/toilet_bg.png")
+                    no-repeat center;
+                background-size: 100% 100%;
+                z-index: 0; // 在最底层
+            }
             .moduleContent {
-                overflow: hidden;
+                // overflow: hidden;
 
                 // position: relative;
                 view {
@@ -1242,10 +1300,10 @@ export default {
         }
         .redar-pic {
             position: absolute;
-            left: 50%;
-            top: 50%;
-            width: 40rpx;
-            height: 40rpx;
+            // left: 50%;
+            // top: 50%;
+            width: 60rpx;
+            height: 60rpx;
             transform: translate(-50%, -50%); /* 先居中 */
         }
     }
@@ -1270,7 +1328,7 @@ export default {
     .notice-info {
         width: 710rpx;
         max-height: 300rpx;
-        overflow: hidden;
+        // overflow: hidden;
         padding: 30rpx 37rpx 10rpx 37rpx;
         background: #ffffff;
         border-radius: 37rpx;
@@ -1314,7 +1372,7 @@ export default {
 
         .stayDetail {
             height: 80rpx;
-            overflow: hidden;
+            // overflow: hidden;
             margin-top: 10rpx;
             margin-bottom: 10rpx;
             padding-left: 20rpx;
@@ -1373,6 +1431,20 @@ export default {
                 font-size: 32rpx;
                 text-align: center;
             }
+            .noShareFlag {
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                width: 155rpx;
+                height: 94rpx;
+                border-radius: 28rpx;
+                font-weight: 500;
+                font-size: 32rpx;
+                text-align: center;
+                background: #f0f0f0;
+                color: #999999;
+                opacity: 0.6;
+            }
 
             .btn2 {
                 display: flex;
@@ -1527,7 +1599,7 @@ export default {
             width: 80%;
             background: #fff;
             border-radius: 16rpx;
-            overflow: hidden;
+            // overflow: hidden;
             animation: fadeIn 0.3s;
 
             .modal-header {
@@ -1602,7 +1674,7 @@ export default {
             position: fixed;
             top: 10rpx;
             right: 10rpx;
-            z-index: 999;
+            z-index: 333;
         }
     }
 }

+ 372 - 6
src/pagesA/deviceSetting/deviceSetting.vue

@@ -17,13 +17,36 @@
                 <text>安装位置</text>
                 <text>{{ position }}</text>
             </view>
+            <view class="wifItem">
+                <text>硬件版本</text>
+                <text>{{ devInfo.hardware }}</text>
+            </view>
+
+            <view class="menu-item">
+                <view class="meanLeft">设备升级</view>
+                <view class="meanRight" v-if="otaList.length > 0">
+                    <picker
+                        @change="bindPickerChangeTwo"
+                        :value="otaIndex"
+                        :range="otaListName"
+                    >
+                        <view class="uni-input">{{ otaListName[0] }}</view>
+                    </picker>
+                    <image
+                        src="../../static/rightArrow.png"
+                        alt=""
+                        @click="showPicker"
+                    ></image>
+                </view>
+                <view class="meanRight" v-else> 已是最新版本,无需升级 </view>
+            </view>
         </view>
         <view class="clientInfo" style="margin-top: 40rpx">
             <view class="wifItem" @click="goShareCount">
-                <text>分享账户</text>
+                <text>分享记录</text>
                 <image
                     src="../../static/rightArrow.png"
-                    style="width: 40rpx; height: 40rpx"
+                    style="width: 30rpx; height: 30rpx"
                 ></image>
             </view>
             <!-- <view class="wifItem">
@@ -45,8 +68,16 @@
                 ></image>
             </view>
 
+            <view class="wifItem" @click="showRemoveDevice">
+                <text>设备转移</text>
+                <image
+                    src="../../static/rightArrow.png"
+                    style="width: 30rpx; height: 30rpx"
+                ></image>
+            </view>
+
             <view class="wifItem" @click="unbindDevice">
-                <text>解绑设备</text>
+                <text>设备解绑</text>
                 <image
                     src="../../static/rightArrow.png"
                     style="width: 30rpx; height: 30rpx"
@@ -54,11 +85,45 @@
             </view>
         </view>
         <view class="box">
-            <view class="handle-btn">
+            <view class="handle-btn" v-if="settingFlag">
                 <view class="btn1" @click="goMoreSetting">更多设置</view>
             </view>
+            <view class="handle-noflage" v-else>
+                <view class="btn1" @click="goNoSetting">更多设置</view>
+            </view>
         </view>
         <tenMinutes v-if="isIniTenMinutes" />
+        <view class="modal-mask" v-if="removeDeviceModle">
+            <view class="modal-container">
+                <view class="modal-header">
+                    <text class="title">设备转移</text>
+                    <image
+                        src="../../static/closePng.png"
+                        @click="removeDeviceModle = false"
+                        class="close-btn"
+                    >
+                    </image>
+                </view>
+
+                <view class="modal-content">
+                    <input
+                        class="input"
+                        placeholder="请输入要转移的手机号"
+                        type="number"
+                        v-model="sharedPhone"
+                    />
+                </view>
+
+                <view class="modal-buttons">
+                    <button
+                        class="btn phone-btn"
+                        @click="handlePhoneAnalysis()"
+                    >
+                        <text>确认转移</text>
+                    </button>
+                </view>
+            </view>
+        </view>
     </view>
 </template>
 <script>
@@ -69,6 +134,13 @@ export default {
             devName: "",
             position: "",
             isIniTenMinutes: "",
+            sharedPhone: "",
+            removeDeviceModle: false,
+            // ota部分
+            otaList: [],
+            otaListName: [],
+            otaIndex: 0,
+            settingFlag: false,
         };
     },
     methods: {
@@ -82,7 +154,6 @@ export default {
             } else if (val == "Bedroom") {
                 this.position = " 卧室";
             }
-            console.log(this.position, 9999);
             return this.installPosition;
         },
         unbindDevice() {
@@ -121,12 +192,23 @@ export default {
             });
         },
         goMoreSetting() {
+            // uni.navigateTo({
+            //     url:
+            //         "/pagesA/adDevice/adDevice?devInfo=" +
+            //         JSON.stringify(this.devInfo),
+            // });
             uni.navigateTo({
                 url:
-                    "/pagesA/adDevice/adDevice?devInfo=" +
+                    "/pagesA/moreSetting/moreSetting?devInfo=" +
                     JSON.stringify(this.devInfo),
             });
         },
+        goNoSetting() {
+            uni.showToast({
+                title: "您没有设置权限",
+                icon: "none",
+            });
+        },
         goFailDetail() {
             uni.navigateTo({
                 url:
@@ -142,6 +224,129 @@ export default {
                     JSON.stringify(this.devInfo),
             });
         },
+        showRemoveDevice() {
+            this.removeDeviceModle = true;
+        },
+        handlePhoneAnalysis() {
+            if (!this.sharedPhone) {
+                uni.showToast({
+                    title: "请输入手机号",
+                    icon: "none",
+                    duration: 1500,
+                });
+                return;
+            }
+
+            let reg_tel =
+                /^(13[0-9]|14[01456879]|15[0-3,5-9]|16[2567]|17[0-8]|18[0-9]|19[0-3,5-9])\d{8}$/;
+            if (!reg_tel.test(this.sharedPhone)) {
+                uni.showModal({
+                    content: "请填写正确手机号!",
+                    showCancel: false,
+                });
+                return;
+            }
+            this.$http
+                .post(
+                    "wap/device/transfer",
+                    {
+                        userId: uni.getStorageSync("userId"),
+                        devId: this.devInfo.devId,
+                        phone: this.sharedPhone,
+                    },
+                    {
+                        header: {
+                            "Content-Type": "application/json;charset=UTF-8",
+                            token: uni.getStorageSync("tokenValue") || "",
+                        },
+                    }
+                )
+                .then((res) => {
+                    if (res.data.code == 200) {
+                        uni.showToast({
+                            title: "转移成功",
+                            icon: "success",
+                            duration: 1500,
+                        });
+                        this.removeDeviceModle = false;
+                        this.sharedPhone = "";
+                    } else {
+                        uni.showToast({
+                            title: "转移失败",
+                            icon: "none",
+                            duration: 1500,
+                        });
+                        this.removeDeviceModle = false;
+                        this.sharedPhone = "";
+                    }
+                });
+        },
+        queryOtaList() {
+            this.$http.get("wap/device/OTA/query", {}).then((res) => {
+                this.otaListName = res.data.data;
+                // this.otaList = this.filterFilesByVersion(
+                //     res.data.data,
+                //     this.devInfo.hardware
+                // );
+                // this.otaListName = this.otaList.map((ele) => ele.fileName);
+            });
+        },
+        bindPickerChangeTwo(e) {
+            this.otaIndex = e.detail.value;
+            this.freshOTA();
+        },
+
+        freshOTA() {
+            let clientIds = [];
+            clientIds.push(this.devInfo.clientId);
+            let ossUrl = this.otaList[this.otaIndex].ossUrl;
+            this.$http
+                .post(
+                    "wap/device/OTA/update",
+                    { clientIds: clientIds, ossUrl: ossUrl },
+                    {
+                        header: {
+                            "Content-Type": "application/json;charset=UTF-8",
+                        },
+                    }
+                )
+                .then((res) => {
+                    if (res.data.code == 200) {
+                        uni.showToast({
+                            title: "设备升级成功",
+                            icon: "success",
+                            duration: 1500,
+                        });
+                    } else {
+                        uni.showToast({
+                            title: res.data.message,
+                            icon: "none",
+                            duration: 1500,
+                        });
+                    }
+                });
+        },
+
+        parseVersion(fileName) {
+            const match = fileName.match(/V(\d+\.\d+\.\d+)/);
+            return match ? match[1].split(".").map(Number) : [0, 0, 0];
+        },
+        compareVersion(v1, v2) {
+            for (let i = 0; i < Math.max(v1.length, v2.length); i++) {
+                const num1 = v1[i] || 0;
+                const num2 = v2[i] || 0;
+                if (num1 > num2) return 1;
+                if (num1 < num2) return -1;
+            }
+            return 0;
+        },
+        filterFilesByVersion(files, minVersionStr) {
+            const minVersion = minVersionStr.split(".").map(Number);
+            return files.filter((file) => {
+                const version = this.parseVersion(file.fileName);
+                return this.compareVersion(version, minVersion) > 0;
+            });
+        },
     },
     onLoad(options) {
         this.isIniTenMinutes = true;
@@ -150,6 +355,9 @@ export default {
     onShow() {
         this.isIniTenMinutes = true;
         this.getPosition(this.devInfo.installPosition);
+        this.queryOtaList();
+        let userId = uni.getStorageSync("userId");
+        this.settingFlag = this.devInfo.userId == userId ? true : false;
     },
     onUnload() {
         this.isIniTenMinutes = false;
@@ -190,6 +398,47 @@ export default {
                 text-align: right;
             }
         }
+
+        .menu-item {
+            width: 640rpx;
+            display: flex;
+            align-content: center;
+            justify-content: space-between;
+            border-bottom: 2rpx solid #ebeff5;
+            padding-top: 30rpx;
+            padding-bottom: 20rpx;
+
+            &:last-child {
+                border-bottom: none;
+            }
+
+            .meanLeft {
+                display: flex;
+                align-items: center;
+                justify-content: center;
+
+                image {
+                    width: 38rpx;
+                    height: 38rpx;
+                }
+
+                text {
+                    margin-left: 10rpx;
+                    color: #111111;
+                    font-size: 32rpx;
+                }
+            }
+            .meanRight {
+                display: flex;
+                align-items: center;
+            }
+
+            .meanRight image {
+                margin-left: 4rpx;
+                width: 16rpx;
+                height: 26rpx;
+            }
+        }
     }
     .box {
         position: fixed;
@@ -221,6 +470,123 @@ export default {
                 text-align: center;
             }
         }
+        .handle-noflage {
+            margin-top: 40rpx;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+
+            .btn1 {
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                width: 700rpx;
+                height: 94rpx;
+                border-radius: 28rpx;
+                font-weight: 500;
+                background: #f0f0f0;
+                color: #999999;
+                font-size: 32rpx;
+                text-align: center;
+            }
+        }
+    }
+    .modal-mask {
+        position: fixed;
+        top: 0;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        background: rgba(0, 0, 0, 0.5);
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        z-index: 333;
+
+        .modal-container {
+            width: 80%;
+            background: #fff;
+            border-radius: 16rpx;
+            // overflow: hidden;
+            animation: fadeIn 0.3s;
+
+            .modal-header {
+                padding: 30rpx;
+                text-align: center;
+                border-bottom: 1rpx solid #f5f5f5;
+                position: relative;
+                .title {
+                    font-size: 36rpx;
+                    display: block;
+                    margin-bottom: 10rpx;
+                }
+
+                .close-btn {
+                    position: absolute;
+                    width: 40rpx;
+                    height: 40rpx;
+                    top: 20rpx;
+                    right: 20rpx;
+                }
+            }
+
+            .modal-content {
+                padding: 20rpx 20rpx;
+
+                .input {
+                    text-align: left;
+                    flex: 1;
+                    border: none;
+                    background: transparent;
+                    font-size: 28rpx;
+                    color: #333;
+                }
+            }
+
+            .modal-buttons {
+                display: flex;
+                flex-direction: column;
+                padding: 20rpx;
+
+                .btn {
+                    flex: 1;
+                    height: 90rpx;
+                    margin: 15rpx 0;
+                    border-radius: 45rpx;
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+                    font-size: 32rpx;
+                    border: none;
+                    background: none;
+                    position: relative;
+                }
+
+                .btn-icon {
+                    width: 40rpx;
+                    height: 40rpx;
+                    margin-right: 15rpx;
+                }
+
+                .phone-btn {
+                    background: linear-gradient(
+                        105.95deg,
+                        #ba978a 0%,
+                        #a27867 100%
+                    );
+                    color: white;
+                }
+
+                .link-btn {
+                    background: linear-gradient(
+                        105.95deg,
+                        #a27867 0%,
+                        #74483d 100%
+                    );
+                    color: white;
+                }
+            }
+        }
     }
 }
 </style>

+ 79 - 47
src/pagesA/devices/devices.vue

@@ -32,8 +32,10 @@
                 <view v-if="item.online == 1">
                     <view class="online" v-if="item.existFlag == 1">
                         <view class="cardInfo">当前有人</view>
-                        <view class="wcTimes" v-if="item.wcTimes"
-                            >今日卫生间使用频率{{ item.wcTimes }}次</view
+                        <view class="wcTimes" v-if="item.presenceChangeTime"
+                            >进入时间{{
+                                forMateTime(item.presenceChangeTime)
+                            }}</view
                         >
                     </view>
                     <view class="cardTwo" v-else>
@@ -69,19 +71,24 @@
             </view>
         </view>
 
-        <alarModel v-if="isInitAlarm" />
+        <alarModel v-if="showModle" />
     </view>
 </template>
 
 <script>
+import MqttService from "../../utils/globalMqtt.js";
+
 export default {
     data() {
         return {
             keyWord: "",
-            option: ["全部", "离线", "在线", "报警"],
+            option: ["全部", "离线", "在线"],
             selectValue: 0,
             devs: [],
             isInitAlarm: false,
+            showModle: false,
+            devClientidList: [],
+            unsubscribeFns: [],
         };
     },
     methods: {
@@ -110,7 +117,6 @@ export default {
             const [hours, minutes] = timePart.split(":");
             return `${parseInt(month)}月${parseInt(day)}日${hours}:${minutes}`;
         },
-
         queryList() {
             uni.showLoading({
                 title: "查询中...",
@@ -123,8 +129,6 @@ export default {
                 status = "0";
             } else if (this.selectValue == 2) {
                 status = "1";
-            } else if (this.selectValue == 3) {
-                status = "2";
             }
             this.$http
                 .post("wap/device/deviceList", {
@@ -133,7 +137,6 @@ export default {
                     status: status,
                 })
                 .then((res) => {
-                    uni.hideLoading();
                     if (res.data.code == 200) {
                         let devs = res.data.data;
                         for (let i = 0; i < devs.length; i++) {
@@ -145,66 +148,95 @@ export default {
                             } else {
                                 devs[i].wcTimes = 0;
                             }
-                            // if (devs[i].lastTargetTime) {
-                            //     const signalTimeStr = devs[
-                            //         i
-                            //     ].lastTargetTime.replace(/-/g, "/");
-                            //     const signalTime = new Date(
-                            //         signalTimeStr
-                            //     ).getTime();
-
-                            //     if (isNaN(signalTime)) {
-                            //         devs[i].judge = false;
-                            //     } else {
-                            //         const currentTime = Date.now();
-                            //         const diff = Math.abs(
-                            //             currentTime - signalTime
-                            //         );
-                            //         devs[i].judge = diff <= 5000; // 10秒内为 true
-                            //     }
-                            // }
                         }
                         this.devs = devs;
+                        this.devClientidList = devs.map(
+                            (item) => item.clientId
+                        );
+                        this.subscribePresenceChange(this.devClientidList);
                     }
                     uni.hideLoading();
                 });
         },
+        subscribePresenceChange(clientIdList) {
+            // 遍历订阅
+            if (clientIdList.length == 0) return;
+
+            clientIdList.forEach((clientId) => {
+                const topic = `/dev/${clientId}/presence_change`;
+                const unsubscribe = MqttService.subscribe(
+                    "DATA",
+                    topic,
+                    (message, msgTopic) => {
+                        const dataMatch = msgTopic.match(
+                            /^\/dev\/(.+)\/presence_change$/
+                        );
+                        if (dataMatch) {
+                            // console.log("presence_change", message, msgTopic);
+                            const messageObj = JSON.parse(message);
+                            const id = dataMatch[1];
+                            const format = (date) => {
+                                const Y = date.getFullYear();
+                                const M = String(date.getMonth() + 1).padStart(
+                                    2,
+                                    "0"
+                                );
+                                const D = String(date.getDate()).padStart(
+                                    2,
+                                    "0"
+                                );
+                                const h = String(date.getHours()).padStart(
+                                    2,
+                                    "0"
+                                );
+                                const m = String(date.getMinutes()).padStart(
+                                    2,
+                                    "0"
+                                );
+                                const s = String(date.getSeconds()).padStart(
+                                    2,
+                                    "0"
+                                );
+                                return `${Y}-${M}-${D} ${h}:${m}:${s}`;
+                            };
+
+                            this.devs.forEach((item) => {
+                                if (item.clientId == id) {
+                                    item.existFlag = messageObj.presence;
+                                    item.presenceChangeTime = format(
+                                        new Date(messageObj.timestamp * 1000)
+                                    );
+                                }
+                            });
+                        }
+                    }
+                );
+                this.unsubscribeFns.push(unsubscribe); // 保存取消订阅的方法
+            });
+        },
+
         formatDevName(name) {
             return typeof name === "string" && name.length > 6
                 ? name.slice(0, 6) + "..."
                 : name;
         },
-        onPullDownRefresh() {
-            uni.showLoading({
-                title: "加载中...",
-                icon: "none",
-                duration: 1500,
-            });
-            this.queryList();
-            setTimeout(() => {
-                uni.hideLoading();
-                uni.stopPullDownRefresh();
-            }, 2000);
-        },
     },
     onLoad() {},
     onShow() {
-        this.isInitAlarm = true;
         this.queryList();
+        this.showModle = true;
     },
     onHide() {
-        this.isInitAlarm = false;
-    },
-    onUnload() {
-        this.isInitAlarm = false;
+        this.showModle = false;
+        if (this.unsubscribeFns) {
+            this.unsubscribeFns.forEach((fn) => fn && fn());
+            this.unsubscribeFns = [];
+        }
     },
-
+    onUnload() {},
     onPullDownRefresh() {
-        uni.showNavigationBarLoading();
         this.queryList();
         setTimeout(() => {
-            uni.hideLoading();
-            uni.hideNavigationBarLoading();
             uni.stopPullDownRefresh();
         }, 2000);
     },

+ 80 - 62
src/pagesA/discrepancy/discrepancy.vue

@@ -6,35 +6,31 @@
                 :key="index"
                 class="timeline-item"
             >
-                <!-- 时间节点 -->
-                <view class="timeline-node">
-                    <view class="node-circle"></view>
-                    <view
-                        class="node-line"
-                        v-if="index !== freQuenceList.length - 1"
-                    ></view>
-                </view>
-
-                <!-- 内容区域 -->
                 <view class="timeline-content">
-                    <view class="timeline-time"
-                        >{{ item.info.start_time.slice(11, 16) }}目标进入</view
-                    >
-                    <view
-                        style="
-                            flex: 1;
-                            width: 2rpx;
-                            height: 40rpx;
-                            background-color: #7e5648;
-                        "
-                    ></view>
+                    <!-- 左侧:目标离开 -->
+                    <view class="timeline-time timeline-time-left">
+                        <text class="time-text">{{
+                            formatTime(item.enterTime)
+                        }}</text>
+                        <text class="event-text">目标进入</text>
+                    </view>
+
+                    <!-- 中间:时间轴 -->
+                    <view class="timeline-line">
+                        <view class="node-circle"></view>
+                        <view
+                            class="node-line"
+                            v-if="index !== freQuenceList.length - 1"
+                        ></view>
+                    </view>
 
-                    <view class="timeline-time"
-                        >{{ item.info.end_time.slice(11, 16) }}目标离开</view
-                    >
-                    <!-- <view class="timeline-desc" v-if="item.title">{{
-                        item.title
-                    }}</view> -->
+                    <!-- 右侧:目标进入 -->
+                    <view class="timeline-time timeline-time-right">
+                        <text class="time-text">{{
+                            formatTime(item.leaveTime)
+                        }}</text>
+                        <text class="event-text">目标离开</text>
+                    </view>
                 </view>
             </view>
         </view>
@@ -48,6 +44,11 @@ export default {
             freQuenceList: "",
         };
     },
+    methods: {
+        formatTime(time) {
+            return time ? time.slice(11, 19) : "暂无";
+        },
+    },
     onLoad(options) {
         this.freQuenceList = JSON.parse(options.freQuenceList);
     },
@@ -56,68 +57,85 @@ export default {
 
 <style scoped>
 .timeline-container {
-    padding: 20rpx 30rpx;
-    height: 100vh;
-    background: linear-gradient(180deg, #faede2 0%, #f4f4f4 100%);
+    padding: 30rpx;
+    background: linear-gradient(180deg, #f6f6f6 0%, #ffffff 100%);
     box-sizing: border-box;
 }
 
 .timeBox {
-    overflow: scroll;
-    height: 98vh;
+    overflow-y: auto;
+    height: 100vh;
 }
 
 .timeline-item {
     display: flex;
+    justify-content: center;
     padding-bottom: 40rpx;
     position: relative;
 }
 
-.timeline-node {
+.timeline-content {
     display: flex;
-    flex-direction: column;
     align-items: center;
-    width: 40rpx;
-    margin-right: 20rpx;
-    margin-top: 50rpx;
+    justify-content: center;
+    width: 100%;
+    position: relative;
 }
 
-.node-circle {
-    width: 20rpx;
-    height: 20rpx;
-    border-radius: 50%;
-    background-color: #7f5447;
-    z-index: 1;
+.timeline-time {
+    font-size: 24rpx;
+    color: #99715f;
+    display: flex;
+    flex-direction: column;
+    align-items: flex-end;
 }
 
-.node-line {
-    width: 2rpx;
-    height: 100rpx;
-    background-color: #7e5648;
-    /* margin-top: 40rpx; */
+.timeline-time-left {
+    margin-right: 30rpx;
+    align-items: flex-end;
+    text-align: right;
 }
 
-.timeline-content {
-    flex: 1;
-    padding-bottom: 20rpx;
+.timeline-time-right {
+    margin-left: 30rpx;
+    align-items: flex-start;
+    text-align: left;
 }
 
-.timeline-time {
+.time-text {
+    font-size: 30rpx;
+    color: #99715f;
+    font-weight: bold;
+    margin-bottom: 6rpx;
+}
+
+.event-text {
     font-size: 24rpx;
     color: #999;
-    margin-bottom: 8rpx;
 }
 
-.timeline-title {
-    font-size: 32rpx;
-    color: #333;
-    font-weight: bold;
-    margin-bottom: 8rpx;
+.timeline-line {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    width: 4rpx;
+    min-height: 120rpx;
+    background-color: #7e5648;
+    position: relative;
 }
 
-.timeline-desc {
-    font-size: 28rpx;
-    color: #666;
-    line-height: 1.5;
+.node-circle {
+    width: 16rpx;
+    height: 16rpx;
+    border-radius: 50%;
+    background-color: #7e5648;
+    border: 4rpx solid #fff;
+    z-index: 2;
+    margin: 10rpx 0;
+}
+.node-line {
+    flex: 1;
+    width: 4rpx;
+    background-color: #7e5648;
 }
 </style>

+ 31 - 10
src/pagesA/healthAlarm/healthAlarm.vue

@@ -31,18 +31,21 @@
                 </view>
             </view>
         </view>
-        <view class="bottomTwo">
+        <view class="bottomTwo" v-if="this.planList.length > 0">
             <picker
                 mode="selector"
                 :range="planListName"
                 @change="bindPickerChange"
                 :value="selectedIndex"
             >
-                <text>新增守护计划 </text>
+                <text>新增守护计划</text>
             </picker>
         </view>
+        <view class="bottomTwo" v-else @click="addNoPlan">
+            <text>新增守护计划</text>
+        </view>
 
-        <alarModel v-if="isInitAlarm" />
+        <alarModel v-if="showModle" />
     </view>
 </template>
 <script>
@@ -56,7 +59,7 @@ export default {
             // 已经新增的守护计划
             selectedPlan: [],
             changFlage: false,
-            isInitAlarm: false,
+            showModle: false,
         };
     },
     computed: {},
@@ -223,7 +226,27 @@ export default {
             uni.navigateTo({
                 url:
                     "/pagesA/playSetting/playSetting?planInfo=" +
-                    JSON.stringify(item),
+                    JSON.stringify(item) +
+                    "&devInfo=" +
+                    JSON.stringify(this.devInfo),
+            });
+        },
+        addNoPlan() {
+            uni.showModal({
+                title: "提示",
+                content: "暂无守护计划模版",
+                success: (res) => {
+                    if (res.confirm) {
+                        uni.navigateBack({
+                            delta: 1,
+                        });
+                    }
+                    if (res.cancel) {
+                        uni.navigateBack({
+                            delta: 1,
+                        });
+                    }
+                },
             });
         },
     },
@@ -231,16 +254,14 @@ export default {
         this.devInfo = JSON.parse(options.devInfo);
     },
     onShow() {
+        this.showModle = true;
         this.getPlanList();
         this.getSelectPlan();
-        this.isInitAlarm = true;
     },
     onHide() {
-        this.isInitAlarm = false;
-    },
-    onUnload() {
-        this.isInitAlarm = false;
+        this.showModle = false;
     },
+    onUnload() {},
 };
 </script>
 <style lang="less" scoped>

+ 1 - 1
src/pagesA/homeDetail/homeDetail.vue

@@ -86,7 +86,7 @@ export default {
         getGroupDetail() {
             this.$http
                 .get(`wap/group/queryGroupDeviceInfoById`, {
-                    group_id: this.groupInfo.groupId,
+                    groupId: this.groupInfo.groupId,
                 })
                 .then((res) => {
                     if (res.data.data) {

+ 3 - 3
src/pagesA/homeManage/homeManage.vue

@@ -147,10 +147,10 @@ export default {
 
         getgroupList() {
             uni.showLoading({
-                title: "加载中",
+                title: "加载中...",
             });
             this.$http
-                .get(`wap/group/groupList`, { user_id: this.userId })
+                .get(`wap/group/groupList`, { userId: this.userId })
                 .then((res) => {
                     uni.hideLoading();
                     if (res.data.data) {
@@ -170,7 +170,7 @@ export default {
                             .post(
                                 "wap/group/deleteGroup",
                                 {
-                                    group_id: item.groupId,
+                                    groupId: item.groupId,
                                 },
                                 {
                                     header: {

+ 57 - 46
src/pagesA/linkShare/linkShare.vue

@@ -37,6 +37,7 @@
                 <view class="funItem" @click="smChange()">
                     <label class="simple-radio"
                         ><radio
+                            :disabled="generateFlag == true"
                             :value="messageFlag"
                             color="#7c5345"
                             :checked="messageFlag == true"
@@ -48,6 +49,7 @@
                 <view class="funItem">
                     <label class="simple-radio" @click="snChange()"
                         ><radio
+                            :disabled="generateFlag == true"
                             :value="serviceNumberFlag"
                             color="#7c5345"
                             :checked="serviceNumberFlag == true"
@@ -59,6 +61,7 @@
                 <view class="funItem">
                     <label class="simple-radio" @click="vfChange()"
                         ><radio
+                            :disabled="generateFlag == true"
                             :value="voipFlag"
                             color="#7c5345"
                             :checked="voipFlag == true"
@@ -69,10 +72,10 @@
                 >
             </view>
         </view>
-        <button class="btn1" open-type="share" v-if="shareFlag">
-            确认分享
+        <button class="btn1" @click="shareUserLink()" v-if="!generateFlag">
+            生成分享链接
         </button>
-        <button class="btn1" v-else @click="unShare()">暂不可分享</button>
+        <button class="btn1" open-type="share" v-else>确认分享</button>
     </view>
 </template>
 <script>
@@ -81,88 +84,96 @@ export default {
         return {
             devInfo: "",
             phone: uni.getStorageSync("phone"),
-            messageFlag: true,
-            serviceNumberFlag: true,
-            voipFlag: true,
+            messageFlag: false,
+            serviceNumberFlag: false,
+            voipFlag: false,
             shareFlag: false,
             shareId: "",
+            generateFlag: false,
         };
     },
     methods: {
+        // 切换短信分享
         smChange() {
             this.messageFlag = !this.messageFlag;
-            this.shareUserLink("权限修改成功");
         },
+
+        // 切换服务号分享
         snChange() {
             this.serviceNumberFlag = !this.serviceNumberFlag;
-            this.shareUserLink("权限修改成功");
         },
 
+        // 切换语音分享
         vfChange() {
             this.voipFlag = !this.voipFlag;
-            this.shareUserLink("权限修改成功");
         },
         onShareAppMessage() {
             return {
                 title: "好友向您分享了一台设备",
                 path:
                     "/pagesA/sharePages/sharePages?phone=" +
-                    this.phone +
+                    uni.getStorageSync("phone") +
                     "&devInfo=" +
                     JSON.stringify(this.devInfo) +
                     "&shareId=" +
                     this.shareId,
             };
         },
-        shareUserLink(itemTitle) {
-            let shareParam = {
-                devId: this.devInfo.devId,
-                sharerUserId: uni.getStorageSync("userId"),
-                sharerPhone: uni.getStorageSync("phone"),
-                sharedUserId: "",
-                sharedPhone: "",
-                messageFlag: this.messageFlag == true ? 0 : 1,
-                serviceNumberFlag: this.serviceNumberFlag == true ? 0 : 1,
-                voipFlag: this.voipFlag == true ? 0 : 1,
-            };
-            this.$http
-                .post("wap/share/shareByUrl", shareParam, {
-                    header: {
-                        "Content-Type": "application/json;charset=UTF-8",
-                    },
-                })
-                .then((res) => {
-                    if (res.data.code == 200) {
-                        uni.showToast({
-                            title: itemTitle,
-                            icon: "none",
-                            duration: 1500,
-                        });
-                        this.shareFlag = true;
-                        this.shareId = res.data.data.shareId;
-                    } else {
-                        this.shareFlag = false;
+
+        // 调用后端接口生成分享链接
+        shareUserLink() {
+            return new Promise((resolve, reject) => {
+                const shareParam = {
+                    devId: this.devInfo.devId,
+                    sharerUserId: uni.getStorageSync("userId"),
+                    sharerPhone: uni.getStorageSync("phone"),
+                    sharedUserId: "",
+                    sharedPhone: "",
+                    messageFlag: this.messageFlag ? 0 : 1,
+                    serviceNumberFlag: this.serviceNumberFlag ? 0 : 1,
+                    voipFlag: this.voipFlag ? 0 : 1,
+                };
+
+                this.$http
+                    .post("wap/share/shareByUrl", shareParam, {
+                        header: {
+                            "Content-Type": "application/json;charset=UTF-8",
+                        },
+                    })
+                    .then((res) => {
+                        const { code, data, message } = res.data;
+                        if (code == 200 && data.shareId) {
+                            this.shareId = data.shareId;
+                            this.generateFlag = true;
+                        } else {
+                            uni.showToast({
+                                title: message || "生成分享链接失败",
+                                icon: "none",
+                            });
+                            this.generateFlag = false;
+                        }
+                    })
+                    .catch((err) => {
                         uni.showToast({
-                            title: res.data.message,
+                            title: "网络异常,请稍后再试",
                             icon: "none",
-                            duration: 1500,
                         });
-                    }
-                });
+                        reject();
+                    });
+            });
         },
         unShare() {
             uni.showToast({
                 title: "当前用户暂不可分享",
                 icon: "none",
-                duration: 1500,
             });
         },
     },
     onLoad(options) {
         this.devInfo = JSON.parse(options.devInfo);
-        setTimeout(() => {
-            this.shareUserLink("分享链接创建成功");
-        }, 500);
+        // setTimeout(() => {
+        //     this.shareUserLink();
+        // }, 500);
     },
 };
 </script>

+ 94 - 41
src/pagesA/loginNew/loginNew.vue

@@ -39,7 +39,6 @@
 </template>
 <script>
 export default {
-    name: "my",
     data() {
         return {
             checked: false,
@@ -54,8 +53,8 @@ export default {
         dkwd(val) {
             let typeval = val;
             if (typeval == "yhfwxy") {
-                wx.downloadFile({
-                    url: "https://hflnxx.oss-cn-shanghai.aliyuncs.com/USERPROTOCOL/20250714/9767deca.pdf?x-oss-date=20250714T031639Z&x-oss-expires=300&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5t9ZjmmHVs7nseZx3xWT%2F20250714%2Fcn-shanghai%2Foss%2Faliyun_v4_request&x-oss-signature=798fdd115bdeee0b6d9031250ef75fcf5db2b8efc938988aabeb7fc4b8231a51",
+                uni.downloadFile({
+                    url: "https://hflnxx.oss-cn-shanghai.aliyuncs.com/USERPROTOCOL/20250918/yhfwxy.pdf",
                     success: function (res) {
                         const filePath = res.tempFilePath;
                         wx.openDocument({
@@ -66,8 +65,8 @@ export default {
                 });
             }
             if (typeval == "yszc") {
-                wx.downloadFile({
-                    url: "https://hflnxx.oss-cn-shanghai.aliyuncs.com/pdf/20250714/623d3ac4.pdf?x-oss-date=20250714T031639Z&x-oss-expires=299&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5t9ZjmmHVs7nseZx3xWT%2F20250714%2Fcn-shanghai%2Foss%2Faliyun_v4_request&x-oss-signature=cc138ac382eae75d3e181c9e8757a5965305324a9e54b34f7bf1e6463ea2a6a1",
+                uni.downloadFile({
+                    url: "https://hflnxx.oss-cn-shanghai.aliyuncs.com/USERPROTOCOL/20250918/yszc.pdf",
                     success: function (res) {
                         const filePath = res.tempFilePath;
                         wx.openDocument({
@@ -79,44 +78,98 @@ export default {
             }
         },
         onGetPhoneNumber(e) {
-            let code = "";
-            // console.log(e.detail.code, 9999);
-            if (e.detail) {
-                code = e.detail.code;
-                this.checked = true;
-            } else {
-                code = e.code;
-                this.checked = true;
-            }
-            this.$http
-                .post(
-                    "wap/user/wx/loginByPhone",
-                    JSON.stringify({
-                        openId: uni.getStorageSync("openid"),
-                        unionId: uni.getStorageSync("unionid"),
-                        code: code,
-                    }),
-                    {
-                        header: {
-                            "Content-Type": "application/json;charset=UTF-8",
-                        },
-                    }
-                )
-                .then((res) => {
-                    if (res.data.data) {
-                        uni.setStorageSync("phone", res.data.data.phone);
-                        uni.setStorageSync(
-                            "tokenValue",
-                            res.data.data.tokenValue
-                        );
-                        uni.setStorageSync("userId", res.data.data.userId);
-                        uni.reLaunch({
-                            url: "/pages/home/home",
-                        });
-                    }
+            if (e.detail.errMsg == "getPhoneNumber:fail user deny") {
+                uni.exitMiniProgram({
+                    success(res) {},
+                    fail(err) {},
                 });
+                return;
+            }
+
+            let userId = uni.getStorageSync("userId");
+            if (!userId) {
+                let code = "";
+                // console.log(e.detail.code, 9999);
+                if (e.detail) {
+                    code = e.detail.code;
+                    this.checked = true;
+                } else {
+                    code = e.code;
+                    this.checked = true;
+                }
+                this.$http
+                    .post(
+                        "wap/user/wx/loginByPhone",
+                        JSON.stringify({
+                            openId: uni.getStorageSync("openid"),
+                            unionId: uni.getStorageSync("unionid"),
+                            code: code,
+                        }),
+                        {
+                            header: {
+                                "Content-Type":
+                                    "application/json;charset=UTF-8",
+                            },
+                        }
+                    )
+                    .then((res) => {
+                        if (res.data.data) {
+                            uni.setStorageSync("phone", res.data.data.phone);
+                            uni.setStorageSync(
+                                "tokenValue",
+                                res.data.data.tokenValue
+                            );
+                            uni.setStorageSync("userId", res.data.data.userId);
 
-            this.checked = false;
+                            this.$initMqtt();
+                            console.log("✅ 登录成功后 MQTT 开始登录连接");
+
+                            uni.reLaunch({
+                                url: "/pages/home/home",
+                            });
+                        }
+                    });
+                this.checked = false;
+            } else {
+                let code = "";
+                // console.log(e.detail.code, 9999);
+                if (e.detail) {
+                    code = e.detail.code;
+                    this.checked = true;
+                } else {
+                    code = e.code;
+                    this.checked = true;
+                }
+                this.$http
+                    .post(
+                        "wap/user/wx/loginByPhone",
+                        JSON.stringify({
+                            openId: uni.getStorageSync("openid"),
+                            unionId: uni.getStorageSync("unionid"),
+                            code: code,
+                        }),
+                        {
+                            header: {
+                                "Content-Type":
+                                    "application/json;charset=UTF-8",
+                            },
+                        }
+                    )
+                    .then((res) => {
+                        if (res.data.data) {
+                            uni.setStorageSync("phone", res.data.data.phone);
+                            uni.setStorageSync(
+                                "tokenValue",
+                                res.data.data.tokenValue
+                            );
+                            uni.setStorageSync("userId", res.data.data.userId);
+                            uni.reLaunch({
+                                url: "/pages/home/home",
+                            });
+                        }
+                    });
+                this.checked = false;
+            }
         },
         noLogin() {
             uni.clearStorageSync();

+ 365 - 0
src/pagesA/moreSetting/moreSetting.vue

@@ -0,0 +1,365 @@
+<template>
+    <view class="home-warp">
+        <view class="clientInfo">
+            <view class="wifItem" @click="goDeviceSetting()">
+                <text>设备参数</text>
+                <image
+                    src="../../static/rightArrow.png"
+                    style="width: 30rpx; height: 30rpx"
+                ></image>
+            </view>
+            <view class="wifItem" @click="roomSetting">
+                <text>房间配置</text>
+                <image
+                    src="../../static/rightArrow.png"
+                    style="width: 30rpx; height: 30rpx"
+                ></image>
+            </view>
+            <view class="wifItem">
+                <text>坐摔检测</text>
+                <view class="inputBox">
+                    <switch
+                        :checked="fallSettingEnabled == 1"
+                        @change="onFallSettingChange"
+                        :active-value="1"
+                        :inactive-value="0"
+                        size="24px"
+                        active-color="#07c160"
+                        inactive-color="#eeeff1"
+                        style="transform: scale(0.8); margin-left: auto"
+                    />
+                </view>
+            </view>
+        </view>
+
+        <view class="modal-mask" v-if="showFallSettingModle">
+            <view class="modal-container">
+                <view class="modal-header">
+                    <text class="title">坐摔参数配置</text>
+                </view>
+                <view class="modal-body">
+                    <view class="wifItem" style="margin-top: 30rpx">
+                        <text>坐摔最低高度</text>
+                        <view class="inputBox">
+                            <input
+                                v-model="lowZMax"
+                                placeholder="请输入坐摔最低高度"
+                            />
+                        </view>
+                    </view>
+                    <view class="wifItem" style="margin-top: 30rpx">
+                        <text>坐摔门限</text>
+                        <view class="inputBox">
+                            <input
+                                v-model="humanPredThreshold"
+                                placeholder="请输入坐摔门限"
+                            />
+                        </view>
+                    </view>
+                    <view class="wifItem" style="margin-top: 30rpx">
+                        <text>坐摔判断阈值</text>
+                        <view class="inputBox">
+                            <input
+                                type="number"
+                                v-model="minEventsForDetection"
+                                placeholder="请输入最坐摔判断阈值"
+                            />
+                        </view>
+                    </view>
+                    <view class="wifItem" style="margin-top: 30rpx">
+                        <text>坐摔状态阈值</text>
+                        <view class="inputBox">
+                            <input
+                                type="number"
+                                v-model="minHumanEventsForDetection"
+                                placeholder="请输入坐摔状态阈值"
+                            />
+                        </view>
+                    </view>
+                </view>
+                <view class="modal-buttons">
+                    <button class="btn cancle-btn" @click="cancleFall">
+                        <text>取消</text>
+                    </button>
+                    <button class="btn phone-btn" @click="saveFallSetting">
+                        <text>保存</text>
+                    </button>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+<script>
+import { save } from "../../utils/mqtt";
+
+export default {
+    name: "my",
+    data() {
+        return {
+            devInfo: "",
+            // 坐摔参数
+            preFallSettingEnabled: 0,
+            fallSettingEnabled: 0,
+            lowZMax: 0.5,
+            humanPredThreshold: 0.78,
+            minEventsForDetection: 3,
+            minHumanEventsForDetection: 2,
+            lowHighSnrRatio: false,
+            lowMidSnrRatio: false,
+            // 坐摔检测弹窗
+            showFallSettingModle: false,
+        };
+    },
+    computed: {},
+
+    watch: {
+        fallSettingEnabled(newVal, oldVal) {
+            this.preFallSettingEnabled = oldVal;
+
+            if (newVal === 1) {
+                // 显示弹窗(由 onFallSettingChange 控制即可)
+                // this.showFallSettingModle = true;
+            } else {
+                this.showFallSettingModle = false;
+                this.updateFallSetting(false); // 关闭时自动保存
+            }
+        },
+    },
+
+    methods: {
+        goDeviceSetting() {
+            uni.navigateTo({
+                url: `/pagesA/adDevice/adDevice?devInfo=${JSON.stringify(
+                    this.devInfo
+                )}`,
+            });
+        },
+
+        roomSetting() {
+            const { devId, clientId } = this.devInfo;
+            uni.navigateTo({
+                url: `/pagesA/roomSetting/roomSetting?devId=${devId}&clientId=${clientId}`,
+            });
+        },
+        cancleFall() {
+            this.fallSettingEnabled = this.preFallSettingEnabled;
+            this.showFallSettingModle = false;
+        },
+        // ====================== 初始化参数 ======================
+        setFallSetting(devInfo) {
+            console.log("初始化坐摔参数:", devInfo);
+            Object.assign(this, {
+                preFallSettingEnabled: devInfo.preFallSettingEnabled,
+                fallSettingEnabled: devInfo.fallSettingEnabled,
+                lowZMax: devInfo.lowZMax,
+                humanPredThreshold: devInfo.humanPredThreshold,
+                minEventsForDetection: devInfo.minEventsForDetection,
+                minHumanEventsForDetection: devInfo.minHumanEventsForDetection,
+                showFallSettingModle: false,
+            });
+        },
+
+        // ====================== 监听切换 ======================
+        onFallSettingChange({ detail }) {
+            this.fallSettingEnabled = detail.value ? 1 : 0;
+            this.showFallSettingModle = this.fallSettingEnabled === 1;
+        },
+
+        cancelFall() {
+            this.fallSettingEnabled = this.preFallSettingEnabled;
+            this.showFallSettingModle = false;
+        },
+
+        // ====================== 通用更新方法 ======================
+        async updateFallSetting(showToast = true) {
+            // 整理更新参数
+            const payload = {
+                ...this.devInfo,
+                preFallSettingEnabled: this.preFallSettingEnabled,
+                fallSettingEnabled: this.fallSettingEnabled,
+                lowZMax: this.lowZMax,
+                humanPredThreshold: this.humanPredThreshold,
+                minEventsForDetection: this.minEventsForDetection,
+                minHumanEventsForDetection: this.minHumanEventsForDetection,
+            };
+
+            try {
+                const res = await this.$http.post(
+                    "wap/device/updateDevice",
+                    payload,
+                    {
+                        header: {
+                            "Content-Type": "application/json;charset=UTF-8",
+                        },
+                    }
+                );
+
+                if (res.data.code === 200) {
+                    if (showToast) {
+                        uni.showToast({
+                            title: "设置成功",
+                            icon: "success",
+                            duration: 1500,
+                        });
+                    }
+                } else {
+                    uni.showToast({
+                        title: res.data.message || "设置失败",
+                        icon: "none",
+                        duration: 1500,
+                    });
+                }
+            } catch (err) {
+                uni.showToast({
+                    title: "网络异常,请稍后再试",
+                    icon: "none",
+                    duration: 1500,
+                });
+            }
+        },
+
+        // ====================== 保存/关闭 ======================
+        async saveFallSetting() {
+            await this.updateFallSetting();
+            this.showFallSettingModle = false;
+        },
+    },
+
+    onLoad(options) {
+        if (JSON.stringify(options) != "{}") {
+            this.devInfo = JSON.parse(options.devInfo);
+        }
+        this.setFallSetting(this.devInfo);
+        // console.log("devInfo11111111", this.devInfo);
+    },
+
+    onUnload(options) {},
+    onShow() {},
+};
+</script>
+<style lang="less" scoped>
+.home-warp {
+    position: relative;
+    height: 100vh;
+    background: linear-gradient(180deg, #faede2 0%, #f4f4f4 100%);
+    .clientInfo {
+        width: 700rpx;
+        height: 180px;
+        margin: 0 auto;
+        background: #ffffff;
+        border-radius: 38rpx;
+        box-sizing: border-box;
+        padding: 30rpx 30rpx;
+        .wifItem {
+            width: 640rpx;
+            display: flex;
+            align-content: center;
+            justify-content: space-between;
+            border-bottom: 2rpx solid #ebeff5;
+            padding-top: 30rpx;
+            padding-bottom: 20rpx;
+            image {
+                width: 40rpx;
+                height: 40rpx;
+            }
+            input {
+                margin-left: auto;
+                text-align: right;
+            }
+        }
+    }
+
+    .modal-mask {
+        position: fixed;
+        top: 0;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        background: rgba(0, 0, 0, 0.5);
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        z-index: 333;
+
+        .modal-container {
+            width: 80%;
+            background: #fff;
+            border-radius: 16rpx;
+            // overflow: hidden;
+            animation: fadeIn 0.3s;
+
+            .modal-header {
+                padding: 30rpx;
+                text-align: center;
+                border-bottom: 1rpx solid #f5f5f5;
+
+                .title {
+                    font-size: 36rpx;
+                    display: block;
+                    margin-bottom: 10rpx;
+                }
+            }
+            .modal-body {
+                margin: 0 auto 0 auto;
+                box-sizing: border-box;
+                padding: 0 30rpx 20rpx 30rpx;
+                .wifItem {
+                    width: 550rpx;
+                    display: flex;
+                    align-content: center;
+                    justify-content: space-between;
+                    border-bottom: 2rpx solid #ebeff5;
+                    padding-top: 30rpx;
+                    padding-bottom: 20rpx;
+                    image {
+                        width: 40rpx;
+                        height: 40rpx;
+                    }
+                    input {
+                        margin-left: auto;
+                        text-align: right;
+                    }
+                }
+            }
+
+            .modal-buttons {
+                display: flex;
+                padding: 20rpx;
+
+                .btn {
+                    flex: 1;
+                    height: 90rpx;
+                    margin: 15rpx 0;
+                    border-radius: 45rpx;
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+                    font-size: 32rpx;
+                    border: none;
+                    background: none;
+                    position: relative;
+                }
+                .cancle-btn {
+                    width: 40rpx;
+                    background: linear-gradient(
+                        105.95deg,
+                        #ba978a 0%,
+                        #a27867 100%
+                    );
+                    color: white;
+                }
+
+                .phone-btn {
+                    margin-left: 10rpx;
+                    background: linear-gradient(
+                        105.95deg,
+                        #ba978a 0%,
+                        #a27867 100%
+                    );
+                    color: white;
+                }
+            }
+        }
+    }
+}
+</style>

+ 35 - 19
src/pagesA/my/my.vue

@@ -9,7 +9,7 @@
         </view>
 
         <view class="meauList">
-            <view class="menu-item" @click="updateOTA()">
+            <!-- <view class="menu-item" @click="updateOTA()">
                 <view class="meanLeft">
                     <image src="../../static/update.png" mode="" />
                     <text>OTA升级</text>
@@ -17,8 +17,8 @@
                 <view class="meanRight">
                     <image src="../../static/arrTwo.png" mode="" />
                 </view>
-            </view>
-            <view class="menu-item" @click="gotoShare()">
+            </view> -->
+            <view class="menu-item" @click="gotoShare('sure')">
                 <view class="meanLeft">
                     <image src="../../static/share.png" mode="" />
                     <text>分享确认</text>
@@ -27,10 +27,21 @@
                     <image src="../../static/arrTwo.png" mode="" />
                 </view>
             </view>
-            <view class="menu-item" @click="serverchange()">
+
+            <view class="menu-item" @click="gotoShare('list')">
+                <view class="meanLeft">
+                    <image src="../../static/list.png" mode="" />
+                    <text>分享记录</text>
+                </view>
+                <view class="meanRight">
+                    <image src="../../static/arrTwo.png" mode="" />
+                </view>
+            </view>
+
+            <view class="menu-item" @click="goAbnormalInfo()">
                 <view class="meanLeft">
-                    <image src="../../static/setting.png" mode="" />
-                    <text>网络配置</text>
+                    <image src="../../static/list.png" mode="" />
+                    <text>统计信息</text>
                 </view>
                 <view class="meanRight">
                     <image src="../../static/arrTwo.png" mode="" />
@@ -49,16 +60,20 @@
 
         <view class="logoutBtn" @click="goLogout">退出登录</view>
 
-        <tenMinutes v-if="isIniTenMinutes" />
+        <!-- <tenMinutes v-if="isIniTenMinutes" /> -->
+        <alarModel v-if="showModle" />
     </view>
 </template>
 
 <script>
+import MqttService from "../../utils/globalMqtt";
+
 export default {
     data() {
         return {
-            headerImg: "../../static/headerInfo.png",
+            headerImg: "../../static/headerInfo.jpg",
             phone: uni.getStorageSync("phone"),
+            showModle: false,
         };
     },
     methods: {
@@ -67,10 +82,9 @@ export default {
                 url: "/pagesA/OTA/OTA",
             });
         },
-        gotoShare() {
-            uni.setStorageSync("shareStatus", "all");
+        gotoShare(shareType) {
             uni.navigateTo({
-                url: "/pagesA/shareList/shareList",
+                url: "/pagesA/shareList/shareList?shareType=" + shareType,
             });
         },
         goLogout() {
@@ -79,6 +93,7 @@ export default {
                 content: "是否确认退出登录?",
                 success: function (res) {
                     if (res.confirm) {
+                        MqttService.disconnectAll();
                         uni.clearStorageSync();
                         uni.reLaunch({
                             url: "/pagesA/loginNew/loginNew",
@@ -132,20 +147,21 @@ export default {
                 url: "/pagesA/serChange/serChange",
             });
         },
+        goAbnormalInfo() {
+            uni.navigateTo({
+                url: "/pagesA/abnormalInfo/abnormalInfo",
+            });
+        },
     },
     onShow() {
         this.isIniTenMinutes = true;
+        this.showModle = true;
     },
-    onLoad() {
-        this.isIniTenMinutes = true;
-    },
+    onLoad() {},
     onHide() {
-        this.isIniTenMinutes = false;
-    },
-
-    onUnload() {
-        this.isIniTenMinutes = false;
+        this.showModle = false;
     },
+    onUnload() {},
 };
 </script>
 

+ 241 - 21
src/pagesA/playSetting/playSetting.vue

@@ -135,11 +135,91 @@
                 style="transform: scale(0.8)"
             />
         </view>
-
         <!-- 保存按钮 -->
         <view class="save-button" @click="saveTemplate">
             <text class="save-button-text">保存</text>
         </view>
+        <!-- 区域选择 -->
+        <view v-if="areaModle" class="areaModleClass">
+            <view
+                class="airCantInfo"
+                :style="{
+                    width: `${length / 150}px`,
+                    height: `${width / 150}px`,
+                }"
+            >
+                <view
+                    v-for="(item, index) in modules"
+                    :key="index"
+                    class="module-content"
+                >
+                    <view
+                        :class="item.type"
+                        :style="{
+                            width: `${item.width / 2}px`,
+                            height: `${item.length / 2}px`,
+                            top: `${item.top / 2}px`,
+                            left: `${item.left / 2}px`,
+                            transform: `rotate(${item.rotate}deg)`,
+                            'transform-origin': 'center center',
+                        }"
+                    >
+                        <image
+                            style="width: 100%; height: 100%; display: block"
+                            :src="`../../static/furnitures/${item.type}.png`"
+                            mode=""
+                        />
+                    </view>
+                </view>
+                <view
+                    class="areaModleArea"
+                    :style="{
+                        width: `${areaModleWidth / 2}px`,
+                        height: `${areaModLength / 2}px`,
+                        top: `${areaModLeTop / 2}px`,
+                        left: `${areaModLeLeft / 2}px`,
+                        transform: `rotate(${item.rotate}deg)`,
+                        'transform-origin': 'center center',
+                    }"
+                    @touchmove.prevent="onTouchMove($event)"
+                    @touchstart="onTouchStart($event)"
+                    @touchend="onTouchEnd($event)"
+                ></view>
+            </view>
+
+            <view style="display: flex; margin: 20rpx auto">
+                <view style="display: flex">
+                    <text>长:</text>
+                    <input
+                        type="number"
+                        placeholder="请输入区域长"
+                        v-model="areaModLength"
+                    />
+                </view>
+                <view style="display: flex">
+                    <text>宽:</text>
+                    <input
+                        type="number"
+                        placeholder="请输入区域宽"
+                        v-model="areaModleWidth"
+                    />
+                </view>
+            </view>
+            <view
+                style="
+                    margin: 20rpx auto;
+                    width: 200rpx;
+                    height: 100rpx;
+                    text-align: center;
+                    line-height: 100rpx;
+                    background: #f3e2dd;
+                    color: #000;
+                    border-radius: 20rpx;
+                "
+                @click="saveRegion()"
+                >确定区域</view
+            >
+        </view>
     </view>
 </template>
 
@@ -165,9 +245,66 @@ export default {
             mergeTime: 30,
             judgeFlage: "",
             remark: "",
+            areaModle: false,
+            devInfo: "",
+            modules: [],
+            length: 0,
+            width: 0,
+            xOffset: 0,
+            yOffset: 0,
+            areaModleWidth: 100,
+            areaModLength: 100,
+            areaModLeTop: 0,
+            areaModLeLeft: 0,
+            areaModLeX: 0,
+            areaModLeY: 0,
+            initTop: 0,
+            initLeft: 0,
+            region: [],
+            judgeFlagFlag: false,
+            selectedPlan: {},
         };
     },
     methods: {
+        saveRegion() {
+            this.region[0] = this.areaModLeX;
+            this.region[1] = this.areaModLeY;
+            this.region[2] = this.areaModleWidth;
+            this.region[3] = this.areaModLength;
+            this.areaModle = false;
+            this.selectedPlan.region = JSON.stringify(this.region);
+            console.log(this.selectedPlan, 9999);
+            this.$http
+                .post(
+                    "wap/alarm/plan/save",
+                    { ...this.selectedPlan },
+                    {
+                        header: {
+                            "Content-Type": "application/json",
+                            token: uni.getStorageSync("tokenValue") || "",
+                        },
+                    }
+                )
+                .then((res) => {
+                    if (res.data.code == 200) {
+                        uni.showToast({
+                            title: "修改成功",
+                            icon: "success",
+                        });
+                        setTimeout(() => {
+                            uni.navigateBack({
+                                delta: 1,
+                            });
+                        }, 1000);
+                    } else {
+                        uni.showToast({
+                            title: res.data.message,
+                            icon: "none",
+                            duration: 1500,
+                        });
+                    }
+                });
+        },
         saveTemplate() {
             // 1 2 3 9
             // 允许多个时间
@@ -175,7 +312,7 @@ export default {
             // 只有一个时间
             // 4 6 8
             // 不配生效时间
-
+            // 1 2 6 9  需要配置区域
             if (this.judgeFlage == "noneFlag") {
                 this.timeRange = [
                     {
@@ -211,6 +348,7 @@ export default {
                 mergeTime: this.mergeTime,
                 linkagePushWechatService: this.linkagePushWechatService,
                 enable: this.enable,
+                region: JSON.stringify(this.region),
                 alarmTimePlan: {
                     id: this.planInfo.alarmTimePlan.id,
                     startDate: this.startDate,
@@ -220,6 +358,19 @@ export default {
                     weekdays: this.planInfo.alarmTimePlan.weekdays,
                 },
             };
+            if (this.region.length > 0) {
+                planInfo.region = JSON.stringify(this.region);
+            }
+            if (
+                this.planInfo.eventVal == 1 ||
+                this.planInfo.eventVal == 2 ||
+                this.planInfo.eventVal == 6 ||
+                this.planInfo.eventVal == 9
+            ) {
+                this.selectedPlan = planInfo;
+                this.areaModle = true;
+                return;
+            }
             if (
                 this.planInfo.eventVal == 5 ||
                 this.planInfo.eventVal == 4 ||
@@ -229,7 +380,6 @@ export default {
             }
 
             console.log(planInfo, 9999);
-
             this.$http
                 .post(
                     "wap/alarm/plan/save",
@@ -259,23 +409,23 @@ export default {
                             duration: 1500,
                         });
                     }
-                    if (
-                        this.planInfo.eventVal == 1 ||
-                        this.planInfo.eventVal == 2 ||
-                        this.planInfo.eventVal == 3 ||
-                        this.planInfo.eventVal == 9
-                    ) {
-                        uni.showModal({
-                            title: "提示",
-                            content: "该计划需要到web端配置区域",
-                            success: (res) => {
-                                if (res.confirm) {
-                                }
-                                if (res.cancel) {
-                                }
-                            },
-                        });
-                    }
+                    // if (
+                    //     this.planInfo.eventVal == 1 ||
+                    //     this.planInfo.eventVal == 2 ||
+                    //     this.planInfo.eventVal == 3 ||
+                    //     this.planInfo.eventVal == 9
+                    // ) {
+                    //     uni.showModal({
+                    //         title: "提示",
+                    //         content: "该计划需要到web端配置区域",
+                    //         success: (res) => {
+                    //             if (res.confirm) {
+                    //             }
+                    //             if (res.cancel) {
+                    //             }
+                    //         },
+                    //     });
+                    // }
                 });
         },
         handlePlanInfo() {
@@ -322,11 +472,55 @@ export default {
             let newValue = e.detail.value == true ? 1 : 0;
             this.enable = newValue.toString();
         },
+        getdevRoomInfo(devId) {
+            this.$http
+                .get(`wap/room/readRoom`, {
+                    devId: devId,
+                })
+                .then((res) => {
+                    if (res.data.data) {
+                        this.modules = res.data.data.furnitures;
+                    }
+                });
+        },
+
+        onTouchMove(event) {
+            const currentX = event.touches[0].clientX;
+            const currentY = event.touches[0].clientY;
+            const deltaX = currentX - this.startX;
+            const deltaY = currentY - this.startY;
+            this.areaModLeLeft = this.initLeft + deltaX;
+            this.areaModLeTop = this.initTop + deltaY;
+            this.areaModLeX = Math.round(
+                (this.devInfo?.xxStart || 0) +
+                    event.currentTarget.offsetLeft * 2
+            );
+            this.areaModLeY = Math.round(
+                (this.devInfo?.yyEnd || 0) - event.currentTarget.offsetTop * 2
+            );
+        },
+        onTouchStart(event) {
+            this.startX = event.touches[0].clientX;
+            this.startY = event.touches[0].clientY;
+            this.initTop = this.areaModLeTop;
+            this.initLeft = this.areaModLeLeft;
+        },
+        onTouchEnd(index, event) {},
     },
     onLoad(options) {
         this.planInfo = JSON.parse(options.planInfo);
+        this.devInfo = JSON.parse(options.devInfo);
 
-        console.log(this.planInfo, 9999999);
+        this.width = Math.abs(this.devInfo.yyEnd - this.devInfo.yyStart) * 100;
+        this.length = Math.abs(this.devInfo.xxEnd - this.devInfo.xxStart) * 100;
+
+        this.xOffset = (this.devInfo.xxStart + this.devInfo.xxEnd) * 50;
+        this.yOffset = -(this.devInfo.yyStart + this.devInfo.yyEnd) * 50;
+
+        console.log(this.xOffset, this.yOffset, 9999);
+        console.log(this.devInfo, 9999999);
+
+        this.getdevRoomInfo(this.devInfo.devId);
         this.startDate =
             this.planInfo?.alarmTimePlan.startDate?.slice(0, 10) || "";
         this.stopDate =
@@ -471,4 +665,30 @@ export default {
     font-size: 18px;
     /* font-weight: bold; */
 }
+.areaModleClass {
+    z-index: 111;
+    position: absolute;
+    width: 650rpx;
+    padding: 20rpx 0;
+    /* height: 650rpx; */
+    top: -8%;
+    left: -37%;
+    background: #fff;
+    transform: translate(50%, 50%);
+    border-radius: 12px;
+}
+.airCantInfo {
+    background-image: url("https://hflnxx.oss-cn-shanghai.aliyuncs.com/IMAGE/20250919/toilet_bg.png");
+    margin: 0 auto 0 auto;
+    border: 18rpx solid #1f1f1f;
+    position: relative;
+}
+.airCantInfo .module-content > view {
+    position: absolute;
+}
+.airCantInfo .areaModleArea {
+    border: 2rpx solid #000;
+    background: #f8efe4;
+    position: absolute;
+}
 </style>

+ 294 - 94
src/pagesA/roomSetting/roomSetting.vue

@@ -1,68 +1,82 @@
 <template>
     <view class="home-warpTwo">
         <view class="header_two">
-            <view class="header">
-                <view class="item">
-                    <image src="../../static/activePod.png" alt=""> </image>
-                    <text>1</text>
-                </view>
-                <view class="line"></view>
-                <view class="item"
-                    ><image src="../../static/activePod.png" alt=""></image>
-                    <text>2</text>
-                </view>
-                <view class="line"></view>
-                <view class="item"
-                    ><image src="../../static/activePod.png" alt=""></image>
-                    <text>3</text>
-                </view>
-            </view>
-            <view
-                class="airCantInfo"
-                :style="{
-                    width: `${length / 150}px`,
-                    height: `${width / 150}px`,
-                }"
-            >
+            <view class="radar-box">
                 <view
-                    v-for="(item, index) in modules"
-                    :key="index"
-                    class="module-content"
+                    :style="{
+                        width: `${length / 200}px`,
+                        height: `${width / 200}px`,
+                        position: 'relative',
+                    }"
+                    :class="[
+                        width < 25500 && length < 25000
+                            ? 'tranStyle'
+                            : 'center',
+                    ]"
                 >
                     <view
-                        :class="item.type"
-                        :style="{
-                            width: `${item.width / 2}px`,
-                            height: `${item.length / 2}px`,
-                            top: `${item.top / 2}px`,
-                            left: `${item.left / 2}px`,
-                            transform: `rotate(${item.rotate}deg)`,
-                            'transform-origin': 'center center',
-                        }"
-                        @touchmove.prevent="onTouchMove(index, $event)"
-                        @touchstart="onTouchStart(index, $event)"
-                        @touchend="onTouchEnd(index, $event)"
+                        v-for="(item, index) in modules"
+                        :key="index"
+                        class="moduleContent"
                     >
-                        <image
-                            style="width: 100%; height: 100%; display: block"
-                            :src="`../../static/furnitures/${item.type}.png`"
-                            mode=""
-                        />
+                        <view
+                            :class="item.type"
+                            :style="{
+                                width: `${item.width / 2}px`,
+                                height: `${item.length / 2}px`,
+                                top: `${item.top / 2}px`,
+                                left: `${item.left / 2}px`,
+                                transform: `rotate(${item.rotate}deg)`,
+                                'transform-origin': 'center center',
+                            }"
+                            @touchmove.prevent="onTouchMove(index, $event)"
+                            @touchstart="onTouchStart(index, $event)"
+                            @touchend="onTouchEnd(index, $event)"
+                        >
+                            <image
+                                class="module-img"
+                                :src="`../../static/furnitures/${item.type}.png`"
+                                mode=""
+                            />
+                        </view>
                     </view>
-                    <image
-                        class="redar-pic"
-                        src="../../static/rander.png"
-                        mode=""
-                        :style="{
-                            transform:
-                                'translate(' +
-                                -xOffset / 80 +
-                                'rpx,' +
-                                -yOffset / 80 +
-                                'rpx)',
-                        }"
-                    />
+                    <template>
+                        <view v-for="item in targetPoints" :key="item.id">
+                            <image
+                                class="action-icon-M"
+                                :style="{
+                                    position: 'absolute',
+                                    transform: `translate3d(${
+                                        item.displayX / 2
+                                    }px, ${
+                                        -item.displayY / 2
+                                    }px, 0) translate(-50%, -50%) scale(${
+                                        1.5 / 2
+                                    })`,
+                                    zIndex: 9999,
+                                    transition: 'transform 1s linear',
+                                    willChange: 'transform',
+                                }"
+                                :src="`../../static/${lnbAction}.png`"
+                                mode=""
+                            />
+                        </view>
+                    </template>
                 </view>
+
+                <!-- <image
+                class="redar-pic"
+                src="../../static/rander.png"
+                mode=""
+                :style="{
+                    transform:
+                        'translate(' +
+                        -xOffset / 100 +
+                        'rpx,' +
+                        -yOffset / 100 +
+                        'rpx)',
+                }"
+            /> -->
             </view>
             <view class="airbody">
                 <view class="header_top">
@@ -246,6 +260,7 @@
     </view>
 </template>
 <script>
+import MqttService from "../../utils/globalMqtt.js";
 export default {
     name: "my",
     data() {
@@ -391,6 +406,14 @@ export default {
                     length: 40,
                 },
             ],
+            lnbAction: "action8",
+            // mqtt模块
+            targetPoints: {},
+            inactivityTimer: null,
+            left: 0,
+            top: 0,
+            clientIdProp: null,
+            clientId: null,
         };
     },
     computed: {},
@@ -640,12 +663,12 @@ export default {
                     if (res.data.code == 200) {
                         uni.showToast({
                             title: "保存成功",
-                            icon: "none",
+                            icon: "success",
                             duration: 1500,
                         });
                         setTimeout(() => {
-                            uni.reLaunch({
-                                url: "/pages/home/home",
+                            uni.navigateBack({
+                                delta: 1,
                             });
                         }, 1500);
                     } else {
@@ -711,13 +734,155 @@ export default {
                     }
                 });
         },
+
+        handleMessage(topic, message, clientId) {
+            // 清除不活动定时器
+            clearTimeout(this.inactivityTimer);
+            this.inactivityTimer = setTimeout(() => {
+                this.targetPoints = {};
+                console.log("长时间没有点位消除");
+            }, 1500);
+            // 验证topic格式
+            const match = topic.match(/^\/dev\/(.+)\/tracker_targets$/);
+            if (!match || match[1] !== clientId) return;
+
+            try {
+                const data = JSON.parse(message.toString());
+                // if (data.health) {
+                //     if (
+                //         data.health.breath_rpm ||
+                //         data.health.breath_rpm === 0
+                //     ) {
+                //         this.receptHealth(Math.floor(data.health.breath_rpm));
+                //     } else {
+                //     }
+                // }
+                // console.log(data.tracker_targets, "MQTT消息解析成功22222");
+                this.processTrackerData(data.tracker_targets);
+            } catch (e) {
+                console.error("MQTT消息解析失败", e);
+            }
+        },
+
+        processTrackerData(arr) {
+            if (Array.isArray(arr) && arr.length > 0 && Array.isArray(arr[0])) {
+                this.targetPoints = {};
+                const currentIds = new Set();
+                const newTargetPoints = {};
+                // 处理每个追踪目标
+                arr.forEach((item) => {
+                    if (!Array.isArray(item) || item.length < 4) return;
+
+                    const [x, y, z, id] = item;
+                    currentIds.add(id.toString());
+
+                    // 处理新点或更新现有点
+                    if (!this.targetPoints[id]) {
+                        newTargetPoints[id] = this.createNewTargetPoint(
+                            x,
+                            y,
+                            z,
+                            id
+                        );
+                    } else {
+                        newTargetPoints[id] = this.updateExistingTargetPoint(
+                            this.targetPoints[id],
+                            x,
+                            y,
+                            z,
+                            2
+                        );
+                    }
+                });
+
+                // 移除不存在的点
+                Object.keys(this.targetPoints).forEach((id) => {
+                    if (!currentIds.has(id)) {
+                        delete this.targetPoints[id];
+                    }
+                });
+
+                // 更新目标点
+                this.targetPoints = {
+                    ...this.targetPoints,
+                    ...newTargetPoints,
+                };
+                if (Array.isArray(this.targetPoints)) {
+                    this.targetPoints = this.targetPoints.filter(
+                        (item) => item !== null && item !== undefined
+                    );
+                }
+            }
+        },
+        createNewTargetPoint(x, y, z, id) {
+            return {
+                x,
+                y,
+                z,
+                id,
+                displayX: x - Number(this.xxStart),
+                displayY: y - Number(this.yyEnd),
+                lastX: x,
+                lastY: y,
+            };
+        },
+        updateExistingTargetPoint(existingPoint, x, y, z, THRESHOLD) {
+            const dx = x - existingPoint.lastX;
+            const dy = y - existingPoint.lastY;
+            const distance = Math.sqrt(dx * dx + dy * dy);
+
+            if (distance > THRESHOLD) {
+                return {
+                    ...existingPoint,
+                    x,
+                    y,
+                    z,
+                    lastX: x,
+                    lastY: y,
+                    displayX: x - Number(this.xxStart),
+                    displayY: y - Number(this.yyEnd),
+                };
+            }
+
+            return existingPoint;
+        },
     },
     onLoad(options) {
         this.devId = options.devId;
+        this.clientId = options.clientId;
+
+        console.log(options, "options111111");
         this.getdevInfo(this.devId);
         this.getRoomInfo(this.devId);
     },
-    onShow() {},
+    onShow() {
+        const topic = `/dev/${this.clientId}/tracker_targets`;
+        // 使用 MqttService.subscribe 管理订阅
+        this.unsubscribeFn = MqttService.subscribe(
+            "DATA",
+            topic,
+            (message, msgTopic) => {
+                // console.log(`接收到 ${msgTopic} 消息:`, message);
+                // 处理消息
+                const dataMatch = msgTopic.match(
+                    /^\/dev\/(.+)\/tracker_targets$/
+                );
+                if (dataMatch && dataMatch[1] === this.clientId) {
+                    this.handleMessage(msgTopic, message, this.clientId);
+                }
+            }
+        );
+        if (this.unsubscribeFn) {
+            console.log(`✅ 已成功订阅主题: ${topic}`);
+        }
+    },
+
+    onHide() {
+        if (this.unsubscribeFn) {
+            this.unsubscribeFn();
+            this.unsubscribeFn = null;
+        }
+    },
 };
 </script>
 <style lang="less" scoped>
@@ -730,45 +895,79 @@ export default {
         border-bottom-left-radius: 35rpx;
         border-bottom-right-radius: 35rpx;
         background: linear-gradient(180deg, #faede2 0%, #ffffff 100%);
-        .header {
+        .radar-box {
+            margin: 0 auto;
+            position: relative;
             display: flex;
             align-items: center;
-            justify-content: space-between;
-            padding: 0rpx 120rpx;
-            // padding-top: 30rpx;
-            .item {
-                position: relative;
-                text-align: center;
-                width: 70rpx;
-                height: 75rpx;
-                image {
-                    width: 45rpx;
-                    height: 50rpx;
-                }
-                text {
-                    position: absolute;
-                    top: 33%;
-                    left: 50%;
-                    transform: translate(-50%, -50%);
-                    font-weight: 700;
-                    color: #ffffff;
-                    font-size: 30rpx;
-                    text-align: center;
+            justify-content: center;
+            width: 710rpx;
+            height: 710rpx;
+            background: #ffffff;
+            border-radius: 37.5rpx;
+            box-sizing: border-box;
+
+            .center {
+                position: absolute;
+                overflow: hidden;
+                background-color: #fff;
+                border: 8rpx solid #333333;
+                background-image: url("https://hflnxx.oss-cn-shanghai.aliyuncs.com/IMAGE/20250919/toilet_bg.png");
+                background-repeat: no-repeat;
+                background-position: center;
+                transform: scale(1.3);
+
+                .moduleContent {
+                    position: relative;
+
+                    view {
+                        position: absolute;
+                    }
+
+                    .module-img {
+                        width: 100%;
+                        height: 100%;
+                        display: block;
+                    }
                 }
             }
-            .line {
-                width: 56rpx;
-                height: 4rpx;
-                margin-bottom: 10rpx;
-                background-color: #e4c5b9;
+
+            .tranStyle {
+                position: absolute;
+                overflow: hidden;
+                background-color: #fff;
+                border: 9rpx solid #333333;
+                background-image: url("https://hflnxx.oss-cn-shanghai.aliyuncs.com/IMAGE/20250919/toilet_bg.png");
+                background-repeat: no-repeat;
+                background-position: center;
+                transform: scale(2.3);
+
+                .moduleContent {
+                    // overflow: hidden;
+
+                    // position: relative;
+                    view {
+                        position: absolute;
+                    }
+
+                    .module-img {
+                        width: 100%;
+                        height: 100%;
+                        display: block;
+                    }
+                }
             }
-        }
-        .airCantInfo {
-            margin: 30rpx auto 0 auto;
-            border: 18rpx solid #1f1f1f;
-            position: relative;
-            .module-content > view {
+
+            .action-icon-G {
                 position: absolute;
+                width: 100rpx;
+                height: 100rpx;
+            }
+
+            .action-icon-M {
+                // position: absolute;
+                width: 50rpx;
+                height: 50rpx;
             }
             .redar-pic {
                 position: absolute;
@@ -779,8 +978,9 @@ export default {
                 transform: translate(-50%, -50%); /* 先居中 */
             }
         }
+
         .airbody {
-            margin: 80rpx auto 0 auto;
+            margin: 20rpx auto 0 auto;
             width: 700rpx;
             background: #ffffff;
             border-radius: 38rpx;

+ 3 - 3
src/pagesA/shareCountList/shareCountList.vue

@@ -26,14 +26,14 @@
                         <text>{{ item.sharerPhone }}</text></view
                     >
                 </view>
-                <view class="menu-item">
+                <!-- <view class="menu-item">
                     <view class="meanLeft">
                         <text>被分享人手机号</text>
                     </view>
                     <view class="meanRight">
                         <text>{{ item.sharedPhone }}</text></view
                     >
-                </view>
+                </view> -->
                 <view class="menu-item">
                     <view class="meanLeft">
                         <text>分享时间</text>
@@ -98,7 +98,7 @@ export default {
                     }
                 )
                 .then((res) => {
-                    uni.hideLoading();
+                    uni.hideToast();
                     if (res.data.data) {
                         this.shareList = res.data.data;
                     }

+ 5 - 5
src/pagesA/shareDetail/shareDetail.vue

@@ -25,14 +25,14 @@
                     <text>{{ shareDetail.sharerPhone }}</text></view
                 >
             </view>
-            <view class="menu-item">
+            <!-- <view class="menu-item">
                 <view class="meanLeft">
                     <text>被分享人手机号</text>
                 </view>
                 <view class="meanRight">
                     <text>{{ shareDetail.sharedPhone }}</text></view
                 >
-            </view>
+            </view> -->
             <view class="menu-item">
                 <view class="meanLeft">
                     <text>分享时间</text>
@@ -101,9 +101,9 @@ export default {
         return {
             shareDetail: "",
             shareList: [],
-            messageFlag: true,
-            serviceNumberFlag: true,
-            voipFlag: true,
+            messageFlag: false,
+            serviceNumberFlag: false,
+            voipFlag: false,
         };
     },
     methods: {

+ 27 - 13
src/pagesA/shareList/shareList.vue

@@ -60,6 +60,10 @@
                 </view>
             </view>
         </template>
+        <view v-else class="noData">
+            <text v-if="status === 0">暂无分享设备</text>
+            <text v-else>暂无分享记录</text>
+        </view>
     </view>
 </template>
 
@@ -74,29 +78,19 @@ export default {
     methods: {
         getShareList() {
             uni.showLoading({
-                title: "加载中",
+                title: "加载中...",
             });
             this.$http
                 .post("wap/share/queryDevShare", {
                     userId: uni.getStorageSync("userId"),
-                    state: null,
+                    state: this.status,
                 })
                 .then((res) => {
                     uni.hideLoading();
                     if (res.data.data) {
                         this.shareList = res.data.data;
-                        if (this.shareList && this.status != null) {
-                            const hasStatusZero = this.shareList.some(
-                                (item) => item.state === 0
-                            );
-                            if (!hasStatusZero) {
-                                this.state = null;
-                                this.getShareList();
-                            }
-                        }
                     } else {
-                        this.state = null;
-                        this.getShareList();
+                        this.shareList = [];
                     }
                 });
         },
@@ -169,6 +163,19 @@ export default {
             uni.stopPullDownRefresh();
         }, 1000);
     },
+    onLoad(options) {
+        const shareType = options.shareType;
+        if (shareType) {
+            uni.setNavigationBarTitle({
+                title: shareType === "list" ? "分享记录" : "分享设备",
+            });
+        }
+        if (shareType === "list") {
+            this.status = null;
+        } else if (shareType === "sure") {
+            this.status = 0;
+        }
+    },
 };
 </script>
 
@@ -253,5 +260,12 @@ export default {
             }
         }
     }
+
+    .noData {
+        text-align: center;
+        padding: 20rpx;
+        color: #999;
+        font-size: 28rpx;
+    }
 }
 </style>

+ 0 - 28
src/pagesA/survey/survey.vue

@@ -1,28 +0,0 @@
-<template>
-    <view class="container">
-        <!-- 使用绝对路径引用本地文件 -->
-        <web-view
-            src="https://radar-power.cn/device/detail?devId=1945297099374182401&clientId=806599CDE77C"
-        />
-    </view>
-</template>
-
-<script>
-export default {
-    data() {
-        return {
-            // 使用绝对路径引用 static 文件夹中的文件
-            surveyUrl: "",
-        };
-    },
-    onShow() {
-        console.log("surveyUrl:", this.surveyUrl); // 确保路径正确
-    },
-};
-</script>
-
-<style>
-.container {
-    height: 100%;
-}
-</style>

BIN
src/static/action0.png


BIN
src/static/action1.png


BIN
src/static/action2.png


BIN
src/static/action3.png


BIN
src/static/action4.png


BIN
src/static/action5.png


BIN
src/static/action6.png


BIN
src/static/action7.png


+ 0 - 0
src/static/actionRed.png → src/static/actionSerious.png


+ 0 - 0
src/static/actionYellow.png → src/static/actionWarn.png


BIN
src/static/down.png


BIN
src/static/home_ln.png


BIN
src/static/linkService.png


BIN
src/static/list.png


BIN
src/static/notice.png


BIN
src/static/radar.png


BIN
src/static/rander.png


BIN
src/static/share.png


+ 548 - 477
src/uni_modules/lime-echart/components/l-echart/l-echart.vue

@@ -1,65 +1,85 @@
 <template>
-	<view class="lime-echart" :style="[customStyle]" v-if="canvasId" ref="limeEchart" :aria-label="ariaLabel">
-		<!-- #ifndef APP-NVUE -->
-		<canvas
-			class="lime-echart__canvas"
-			v-if="use2dCanvas"
-			type="2d"
-			:id="canvasId"
-			:style="canvasStyle"
-			:disable-scroll="isDisableScroll"
-			@touchstart="touchStart"
-			@touchmove="touchMove"
-			@touchend="touchEnd"
-		/>
-		<canvas
-			class="lime-echart__canvas"
-			v-else
-			:width="nodeWidth"
-			:height="nodeHeight"
-			:style="canvasStyle"
-			:canvas-id="canvasId"
-			:id="canvasId"
-			:disable-scroll="isDisableScroll"
-			@touchstart="touchStart"
-			@touchmove="touchMove"
-			@touchend="touchEnd"
-		/>
-		<view class="lime-echart__mask"
-			v-if="isPC"
-			@mousedown="touchStart"
-			@mousemove="touchMove"
-			@mouseup="touchEnd"
-			@touchstart="touchStart"
-			@touchmove="touchMove"
-			@touchend="touchEnd">
-		</view>
-		<canvas v-if="isOffscreenCanvas" :style="offscreenStyle" :canvas-id="offscreenCanvasId"></canvas>
-		<!-- #endif -->
-		<!-- #ifdef APP-NVUE -->
-		<web-view
-			class="lime-echart__canvas"
-			:id="canvasId"
-			:style="canvasStyle"
-			:webview-styles="webviewStyles"
-			ref="webview"
-			src="/uni_modules/lime-echart/static/uvue.html?v=1"
-			@pagefinish="finished = true"
-			@onPostMessage="onMessage"
-		></web-view>
-		<!-- #endif -->
-	</view>
+    <view
+        class="lime-echart"
+        :style="[customStyle]"
+        v-if="canvasId"
+        ref="limeEchart"
+        :aria-label="ariaLabel"
+    >
+        <!-- #ifndef APP-NVUE -->
+        <canvas
+            class="lime-echart__canvas"
+            v-if="use2dCanvas"
+            type="2d"
+            :id="canvasId"
+            :style="canvasStyle"
+            :disable-scroll="isDisableScroll"
+            @touchstart="touchStart"
+            @touchmove="touchMove"
+            @touchend="touchEnd"
+        />
+        <canvas
+            class="lime-echart__canvas"
+            v-else
+            :width="nodeWidth"
+            :height="nodeHeight"
+            :style="canvasStyle"
+            :canvas-id="canvasId"
+            :id="canvasId"
+            :disable-scroll="isDisableScroll"
+            @touchstart="touchStart"
+            @touchmove="touchMove"
+            @touchend="touchEnd"
+        />
+        <view
+            class="lime-echart__mask"
+            v-if="isPC"
+            @mousedown="touchStart"
+            @mousemove="touchMove"
+            @mouseup="touchEnd"
+            @touchstart="touchStart"
+            @touchmove="touchMove"
+            @touchend="touchEnd"
+        >
+        </view>
+        <canvas
+            v-if="isOffscreenCanvas"
+            :style="offscreenStyle"
+            :canvas-id="offscreenCanvasId"
+        ></canvas>
+        <!-- #endif -->
+        <!-- #ifdef APP-NVUE -->
+        <web-view
+            class="lime-echart__canvas"
+            :id="canvasId"
+            :style="canvasStyle"
+            :webview-styles="webviewStyles"
+            ref="webview"
+            src="/uni_modules/lime-echart/static/uvue.html?v=1"
+            @pagefinish="finished = true"
+            @onPostMessage="onMessage"
+        ></web-view>
+        <!-- #endif -->
+    </view>
 </template>
 
 <script>
 // @ts-nocheck
 // #ifndef APP-NVUE
-import {Canvas, setCanvasCreator, dispatch} from './canvas';
-import {wrapTouch, convertTouchesToArray, devicePixelRatio ,sleep, canIUseCanvas2d, getRect, getDeviceInfo} from './utils';
+import { Canvas, setCanvasCreator, dispatch } from "./canvas";
+import {
+    wrapTouch,
+    convertTouchesToArray,
+    devicePixelRatio,
+    sleep,
+    canIUseCanvas2d,
+    getRect,
+    getDeviceInfo,
+} from "./utils";
 // #endif
 // #ifdef APP-NVUE
-import { base64ToPath, sleep } from './utils';
-import {Echarts} from './nvue'
+import { base64ToPath, sleep } from "./utils";
+import { Echarts } from "./nvue";
 // #endif
 
 /**
@@ -79,437 +99,488 @@ import {Echarts} from './nvue'
  * @event {Function} finished 加载完成触发
  */
 export default {
-	name: 'lime-echart',
-	props: {
-		// #ifdef MP-WEIXIN || MP-TOUTIAO
-		type: {
-			type: String,
-			default: '2d'
-		},
-		// #endif
-		// #ifdef APP-NVUE
-		webviewStyles: Object,
-		// hybrid: Boolean,
-		// #endif
-		customStyle: String,
-		isDisableScroll: Boolean,
-		isClickable: {
-			type: Boolean,
-			default: true
-		},
-		enableHover: Boolean,
-		beforeDelay: {
-			type: Number,
-			default: 30
-		},
-		landscape: Boolean
-	},
-	data() {
-		return {
-			// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
-			use2dCanvas: true,
-			// #endif
-			// #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
-			use2dCanvas: false,
-			// #endif
-			ariaLabel: '图表',
-			width: null,
-			height: null,
-			nodeWidth: null,
-			nodeHeight: null,
-			// canvasNode: null,
-			config: {},
-			inited: false,
-			finished: false,
-			file: '',
-			platform: '',
-			isPC: false,
-			isDown: false,
-			isOffscreenCanvas: false,
-			offscreenWidth: 0,
-			offscreenHeight: 0,
-		};
-	},
-	computed: {
-		rootStyle() {
-			if(this.landscape) {
-				return `transform: translate(-50%,-50%) rotate(90deg); top:50%; left:50%;`
-			}
-		},
-		canvasId() {
-			return `lime-echart${this._ && this._.uid || this._uid}`
-		},
-		offscreenCanvasId() {
-			return `${this.canvasId}_offscreen`
-		},
-		offscreenStyle() {
-			return `width:${this.offscreenWidth}px;height: ${this.offscreenHeight}px; position: fixed; left: 99999px; background: red`
-		},
-		canvasStyle() {
-			return this.rootStyle + (this.width && this.height ? ('width:' + this.width + 'px;height:' + this.height + 'px') : '')
-		}
-	},
-	// #ifndef VUE3
-	beforeDestroy() {
-		this.clear()
-		this.dispose()
-		// #ifdef H5
-		if(this.isPC) {
-			document.removeEventListener('mousewheel', this.mousewheel)
-		}
-		// #endif
-	},
-	// #endif
-	// #ifdef VUE3
-	beforeUnmount() {
-		this.clear()
-		this.dispose()
-		// #ifdef H5
-		if(this.isPC) {
-			document.removeEventListener('mousewheel', this.mousewheel)
-		}
-		// #endif
-	},
-	// #endif
-	created() {
-		// #ifdef H5
-		if(!('ontouchstart' in window)) {
-			this.isPC = true
-			document.addEventListener('mousewheel', this.mousewheel)
-		}
-		// #endif
-		// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
-		const { platform } = getDeviceInfo();
-		this.isPC = /windows/i.test(platform)
-		// #endif
-		this.use2dCanvas = this.type === '2d' && canIUseCanvas2d()
-	},
-	mounted() {
-		this.$nextTick(() => {
-			this.$emit('finished')
-		})
-	},
-	methods: {
-		// #ifdef APP-NVUE
-		onMessage(e) {
-			const detail = e?.detail?.data[0] || null;
-			const data = detail?.data
-			const key = detail?.event
-			const options = data?.options
-			const event = data?.event
-			const file = detail?.file
-			if (key == 'log' && data) {
-				console.log(data)
-			}
-			if(event) {
-				this.chart.dispatchAction(event.replace(/"/g,''), options)
-			}
-			if(file) {
-				thie.file = file
-			}
-		},
-		// #endif
-		setChart(callback) {
-			if(!this.chart) {
-				console.warn(`组件还未初始化,请先使用 init`)
-				return
-			}
-			if(typeof callback === 'function' && this.chart) {
-				callback(this.chart);
-			}
-			// #ifdef APP-NVUE
-			if(typeof callback === 'function') {
-				this.$refs.webview.evalJs(`setChart(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.chart.options)})`);
-			}
-			// #endif
-		},
-		setOption() {
-			if (!this.chart || !this.chart.setOption) {
-				console.warn(`组件还未初始化,请先使用 init`)
-				return
-			}
-			this.chart.setOption(...arguments);
-		},
-		showLoading() {
-			if(this.chart) {
-				this.chart.showLoading(...arguments)
-			}
-		},
-		hideLoading() {
-			if(this.chart) {
-				this.chart.hideLoading()
-			}
-		},
-		clear() {
-			if(this.chart && !this.chart.isDisposed()) {
-				this.chart.clear()
-			}
-		},
-		dispose() {
-			if(this.chart && !this.chart.isDisposed()) {
-				this.chart.dispose()
-			}
-		},
-		resize(size) {
-			if(size && size.width && size.height) {
-				this.height = size.height
-				this.width = size.width
-				if(this.chart) {this.chart.resize(size)}
-			} else {
-				this.$nextTick(() => {
-					getRect('.lime-echart', this).then(res =>{
-						if (res) {
-							let { width, height } = res;
-							this.width = width = width || 300;
-							this.height = height = height || 300;
-							this.chart.resize({width, height})
-						}
-					})
-				})
-			}
-			
-		},
-		canvasToTempFilePath(args = {}) {
-			// #ifndef APP-NVUE
-			const { use2dCanvas, canvasId } = this;
-			return new Promise((resolve, reject) => {
-				const copyArgs = Object.assign({
-					canvasId,
-					success: resolve,
-					fail: reject
-				}, args);
-				if (use2dCanvas) {
-					delete copyArgs.canvasId;
-					copyArgs.canvas = this.canvasNode;
-				}
-				uni.canvasToTempFilePath(copyArgs, this);
-			});
-			// #endif
-			// #ifdef APP-NVUE
-			this.file = ''
-			this.$refs.webview.evalJs(`canvasToTempFilePath()`);
-			return new Promise((resolve, reject) => {
-				this.$watch('file', async (file) => {
-					if(file) {
-						const tempFilePath = await base64ToPath(file)
-						resolve(args.success({tempFilePath}))
-					} else {
-						reject(args.fail({error: ``}))
-					}
-				})
-			})
-			// #endif
-		},
-		async init(echarts, ...args) {
-			// #ifndef APP-NVUE
-			if(args && args.length == 0 && !echarts) {
-				console.error('缺少参数:init(echarts, theme?:string, opts?: object, callback?: function)')
-				return
-			}
-			// #endif
-			let theme=null,opts={},callback;
-			// Array.from(arguments)
-			args.forEach(item => {
-				if(typeof item === 'function') {
-					callback = item
-				}
-				if(['string'].includes(typeof item)) {
-					theme = item
-				}
-				if(typeof item === 'object') {
-					opts = item
-				}
-			})
-			if(this.beforeDelay) {
-				await sleep(this.beforeDelay)
-			}
-			let config = await this.getContext();
-			// #ifndef APP-NVUE
-			setCanvasCreator(echarts, config)
-			try {
-				this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts || {}))
-				
-				callback?.(this.chart)
-				return this.chart
-			} catch(e) {
-				console.error("【lime-echarts】:", e)
-				return null
-			}
-			// #endif
-			// #ifdef APP-NVUE
-			this.chart = new Echarts(this.$refs.webview)
-			this.$refs.webview.evalJs(`init(null, null, ${JSON.stringify(opts)}, ${theme})`)
-			callback?.(this.chart)
-			return this.chart
-			// #endif
-		},
-		getContext() {
-			// #ifdef APP-NVUE
-			if(this.finished) {
-				return Promise.resolve(this.finished)
-			}
-			return new Promise(resolve => {
-				this.$watch('finished', (val) => {
-					if(val) {
-						resolve(this.finished)
-					}
-				})
-			})
-			// #endif
-			// #ifndef APP-NVUE
-			return getRect(`#${this.canvasId}`, this, this.use2dCanvas).then(res => {
-				if(res) {
-					let dpr = devicePixelRatio
-					let {width, height, node} = res
-					let canvas;
-					this.width = width = width || 300;
-					this.height = height = height || 300;
-					if(node) {
-						const ctx = node.getContext('2d');
-						canvas = new Canvas(ctx, this, true, node);
-						this.canvasNode = node
-					} else {
-						// #ifdef MP-TOUTIAO
-						dpr = !this.isPC ? devicePixelRatio : 1// 1.25
-						// #endif
-						// #ifndef MP-ALIPAY || MP-TOUTIAO
-						dpr = this.isPC ? devicePixelRatio : 1
-						// #endif
-						// #ifdef MP-ALIPAY || MP-LARK
-						dpr = devicePixelRatio
-						// #endif
-						// #ifdef WEB
-						dpr = 1
-						// #endif
-						this.rect = res
-						this.nodeWidth = width * dpr;
-						this.nodeHeight = height * dpr;
-						const ctx = uni.createCanvasContext(this.canvasId, this);
-						canvas =  new Canvas(ctx, this, false);
-					}
-					
-					return { canvas, width, height, devicePixelRatio: dpr, node };
-				} else {
-					return {}
-				}
-			})
-			// #endif
-		},
-		// #ifndef APP-NVUE
-		getRelative(e, touches) {
-			let { clientX, clientY } = e
-			if(!(clientX && clientY) && touches && touches[0]) {
-				clientX = touches[0].clientX
-				clientY = touches[0].clientY
-			}
-			return {x: clientX - this.rect.left, y: clientY - this.rect.top, wheelDelta: e.wheelDelta || 0}
-		},
-		getTouch(e, touches) {
-			const {x} = touches && touches[0] || {}
-			const touch = x ? touches[0] : this.getRelative(e, touches);
-			if(this.landscape) {
-				[touch.x, touch.y] = [touch.y, this.height - touch.x]
-			}
-			return touch;
-		},
-		touchStart(e) {
-			this.isDown = true
-			const next = () => {
-				const touches = convertTouchesToArray(e.touches)
-				if(this.chart) {
-					const touch = this.getTouch(e, touches)
-					this.startX = touch.x
-					this.startY = touch.y
-					this.startT = new Date()
-					const handler = this.chart.getZr().handler;
-					dispatch.call(handler, 'mousedown', touch)
-					dispatch.call(handler, 'mousemove', touch)
-					handler.processGesture(wrapTouch(e), 'start');
-					clearTimeout(this.endTimer);
-				}
-				
-			}
-			if(this.isPC) {
-				getRect(`#${this.canvasId}`, {context: this}).then(res => {
-					this.rect = res
-					next()
-				})
-				return
-			}
-			next()
-		},
-		touchMove(e) {
-			if(this.isPC && this.enableHover && !this.isDown) {this.isDown = true}
-			const touches = convertTouchesToArray(e.touches)
-			if (this.chart && this.isDown) {
-				const handler = this.chart.getZr().handler;
-				dispatch.call(handler, 'mousemove', this.getTouch(e, touches))
-				handler.processGesture(wrapTouch(e), 'change');
-			}
-			
-		},
-		touchEnd(e) {
-			this.isDown = false
-			if (this.chart) {
-				const touches = convertTouchesToArray(e.changedTouches)
-				const {x} = touches && touches[0] || {}
-				const touch = (x ? touches[0] : this.getRelative(e, touches)) || {};
-				if(this.landscape) {
-					[touch.x, touch.y] = [touch.y,  this.height - touch.x]
-				}
-				const handler = this.chart.getZr().handler;
-				const isClick = Math.abs(touch.x - this.startX) < 10 && new Date() - this.startT < 200;
-				dispatch.call(handler, 'mouseup', touch)
-				handler.processGesture(wrapTouch(e), 'end');
-				if(isClick) {
-					dispatch.call(handler, 'click', touch)
-				} else {
-					this.endTimer = setTimeout(() => {
-						dispatch.call(handler, 'mousemove', {x: 999999999,y: 999999999});
-						dispatch.call(handler, 'mouseup', {x: 999999999,y: 999999999});
-					},50)
-				}
-			}
-		},
-		// #endif
-		// #ifdef H5
-		mousewheel(e){
-			if(this.chart) {
-				dispatch.call(this.chart.getZr().handler, 'mousewheel', this.getTouch(e))
-			}
-		}
-		// #endif
-	}
+    name: "lime-echart",
+    props: {
+        // #ifdef MP-WEIXIN || MP-TOUTIAO
+        type: {
+            type: String,
+            default: "2d",
+        },
+        // #endif
+        // #ifdef APP-NVUE
+        webviewStyles: Object,
+        // hybrid: Boolean,
+        // #endif
+        customStyle: String,
+        isDisableScroll: Boolean,
+        isClickable: {
+            type: Boolean,
+            default: true,
+        },
+        enableHover: Boolean,
+        beforeDelay: {
+            type: Number,
+            default: 30,
+        },
+        landscape: Boolean,
+    },
+    data() {
+        return {
+            // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
+            use2dCanvas: true,
+            // #endif
+            // #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
+            use2dCanvas: false,
+            // #endif
+            ariaLabel: "图表",
+            width: null,
+            height: null,
+            nodeWidth: null,
+            nodeHeight: null,
+            // canvasNode: null,
+            config: {},
+            inited: false,
+            finished: false,
+            file: "",
+            platform: "",
+            isPC: false,
+            isDown: false,
+            isOffscreenCanvas: false,
+            offscreenWidth: 0,
+            offscreenHeight: 0,
+        };
+    },
+    computed: {
+        rootStyle() {
+            if (this.landscape) {
+                return `transform: translate(-50%,-50%) rotate(90deg); top:50%; left:50%;`;
+            }
+        },
+        canvasId() {
+            return `lime-echart${(this._ && this._.uid) || this._uid}`;
+        },
+        offscreenCanvasId() {
+            return `${this.canvasId}_offscreen`;
+        },
+        offscreenStyle() {
+            return `width:${this.offscreenWidth}px;height: ${this.offscreenHeight}px; position: fixed; left: 99999px; background: red`;
+        },
+        canvasStyle() {
+            return (
+                this.rootStyle +
+                (this.width && this.height
+                    ? "width:" + this.width + "px;height:" + this.height + "px"
+                    : "")
+            );
+        },
+    },
+    // #ifndef VUE3
+    beforeDestroy() {
+        this.clear();
+        this.dispose();
+        // #ifdef H5
+        if (this.isPC) {
+            document.removeEventListener("mousewheel", this.mousewheel);
+        }
+        // #endif
+    },
+    // #endif
+    // #ifdef VUE3
+    beforeUnmount() {
+        this.clear();
+        this.dispose();
+        // #ifdef H5
+        if (this.isPC) {
+            document.removeEventListener("mousewheel", this.mousewheel);
+        }
+        // #endif
+    },
+    // #endif
+    created() {
+        // #ifdef H5
+        if (!("ontouchstart" in window)) {
+            this.isPC = true;
+            document.addEventListener("mousewheel", this.mousewheel);
+        }
+        // #endif
+        // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
+        const { platform } = getDeviceInfo();
+        this.isPC = /windows/i.test(platform);
+        // #endif
+        this.use2dCanvas = this.type === "2d" && canIUseCanvas2d();
+    },
+    mounted() {
+        this.$nextTick(() => {
+            this.$emit("finished");
+        });
+    },
+    methods: {
+        // #ifdef APP-NVUE
+        onMessage(e) {
+            const detail = e?.detail?.data[0] || null;
+            const data = detail?.data;
+            const key = detail?.event;
+            const options = data?.options;
+            const event = data?.event;
+            const file = detail?.file;
+            if (key == "log" && data) {
+                console.log(data);
+            }
+            if (event) {
+                this.chart.dispatchAction(event.replace(/"/g, ""), options);
+            }
+            if (file) {
+                thie.file = file;
+            }
+        },
+        // #endif
+        setChart(callback) {
+            if (!this.chart) {
+                console.warn(`组件还未初始化,请先使用 init`);
+                return;
+            }
+            if (typeof callback === "function" && this.chart) {
+                callback(this.chart);
+            }
+            // #ifdef APP-NVUE
+            if (typeof callback === "function") {
+                this.$refs.webview.evalJs(
+                    `setChart(${JSON.stringify(
+                        callback.toString()
+                    )}, ${JSON.stringify(this.chart.options)})`
+                );
+            }
+            // #endif
+        },
+        setOption() {
+            if (!this.chart || !this.chart.setOption) {
+                console.warn(`组件还未初始化,请先使用 init`);
+                return;
+            }
+            this.chart.setOption(...arguments);
+        },
+        showLoading() {
+            if (this.chart) {
+                this.chart.showLoading(...arguments);
+            }
+        },
+        hideLoading() {
+            if (this.chart) {
+                this.chart.hideLoading();
+            }
+        },
+        clear() {
+            if (this.chart && !this.chart.isDisposed()) {
+                this.chart.clear();
+            }
+        },
+        dispose() {
+            if (this.chart && !this.chart.isDisposed()) {
+                this.chart.dispose();
+            }
+        },
+        resize(size) {
+            if (size && size.width && size.height) {
+                this.height = size.height;
+                this.width = size.width;
+                if (this.chart) {
+                    this.chart.resize(size);
+                }
+            } else {
+                this.$nextTick(() => {
+                    getRect(".lime-echart", this).then((res) => {
+                        if (res) {
+                            let { width, height } = res;
+                            this.width = width = width || 300;
+                            this.height = height = height || 300;
+                            this.chart.resize({ width, height });
+                        }
+                    });
+                });
+            }
+        },
+        canvasToTempFilePath(args = {}) {
+            // #ifndef APP-NVUE
+            const { use2dCanvas, canvasId } = this;
+            return new Promise((resolve, reject) => {
+                const copyArgs = Object.assign(
+                    {
+                        canvasId,
+                        success: resolve,
+                        fail: reject,
+                    },
+                    args
+                );
+                if (use2dCanvas) {
+                    delete copyArgs.canvasId;
+                    copyArgs.canvas = this.canvasNode;
+                }
+                uni.canvasToTempFilePath(copyArgs, this);
+            });
+            // #endif
+            // #ifdef APP-NVUE
+            this.file = "";
+            this.$refs.webview.evalJs(`canvasToTempFilePath()`);
+            return new Promise((resolve, reject) => {
+                this.$watch("file", async (file) => {
+                    if (file) {
+                        const tempFilePath = await base64ToPath(file);
+                        resolve(args.success({ tempFilePath }));
+                    } else {
+                        reject(args.fail({ error: `` }));
+                    }
+                });
+            });
+            // #endif
+        },
+        async init(echarts, ...args) {
+            // #ifndef APP-NVUE
+            if (args && args.length == 0 && !echarts) {
+                console.error(
+                    "缺少参数:init(echarts, theme?:string, opts?: object, callback?: function)"
+                );
+                return;
+            }
+            // #endif
+            let theme = null,
+                opts = {},
+                callback;
+            // Array.from(arguments)
+            args.forEach((item) => {
+                if (typeof item === "function") {
+                    callback = item;
+                }
+                if (["string"].includes(typeof item)) {
+                    theme = item;
+                }
+                if (typeof item === "object") {
+                    opts = item;
+                }
+            });
+            if (this.beforeDelay) {
+                await sleep(this.beforeDelay);
+            }
+            let config = await this.getContext();
+            // #ifndef APP-NVUE
+            setCanvasCreator(echarts, config);
+            try {
+                this.chart = echarts.init(
+                    config.canvas,
+                    theme,
+                    Object.assign({}, config, opts || {})
+                );
+
+                callback?.(this.chart);
+                return this.chart;
+            } catch (e) {
+                console.error("【lime-echarts】:", e);
+                return null;
+            }
+            // #endif
+            // #ifdef APP-NVUE
+            this.chart = new Echarts(this.$refs.webview);
+            this.$refs.webview.evalJs(
+                `init(null, null, ${JSON.stringify(opts)}, ${theme})`
+            );
+            callback?.(this.chart);
+            return this.chart;
+            // #endif
+        },
+        getContext() {
+            // #ifdef APP-NVUE
+            if (this.finished) {
+                return Promise.resolve(this.finished);
+            }
+            return new Promise((resolve) => {
+                this.$watch("finished", (val) => {
+                    if (val) {
+                        resolve(this.finished);
+                    }
+                });
+            });
+            // #endif
+            // #ifndef APP-NVUE
+            return getRect(`#${this.canvasId}`, this, this.use2dCanvas).then(
+                (res) => {
+                    if (res) {
+                        let dpr = devicePixelRatio;
+                        let { width, height, node } = res;
+                        let canvas;
+                        this.width = width = width || 300;
+                        this.height = height = height || 300;
+                        if (node) {
+                            const ctx = node.getContext("2d");
+                            canvas = new Canvas(ctx, this, true, node);
+                            this.canvasNode = node;
+                        } else {
+                            // #ifdef MP-TOUTIAO
+                            dpr = !this.isPC ? devicePixelRatio : 1; // 1.25
+                            // #endif
+                            // #ifndef MP-ALIPAY || MP-TOUTIAO
+                            dpr = this.isPC ? devicePixelRatio : 1;
+                            // #endif
+                            // #ifdef MP-ALIPAY || MP-LARK
+                            dpr = devicePixelRatio;
+                            // #endif
+                            // #ifdef WEB
+                            dpr = 1;
+                            // #endif
+                            this.rect = res;
+                            this.nodeWidth = width * dpr;
+                            this.nodeHeight = height * dpr;
+                            const ctx = uni.createCanvasContext(
+                                this.canvasId,
+                                this
+                            );
+                            canvas = new Canvas(ctx, this, false);
+                        }
+
+                        return {
+                            canvas,
+                            width,
+                            height,
+                            devicePixelRatio: dpr,
+                            node,
+                        };
+                    } else {
+                        return {};
+                    }
+                }
+            );
+            // #endif
+        },
+        // #ifndef APP-NVUE
+        getRelative(e, touches) {
+            let { clientX, clientY } = e;
+            if (!(clientX && clientY) && touches && touches[0]) {
+                clientX = touches[0].clientX;
+                clientY = touches[0].clientY;
+            }
+            return {
+                x: clientX - this.rect.left,
+                y: clientY - this.rect.top,
+                wheelDelta: e.wheelDelta || 0,
+            };
+        },
+        getTouch(e, touches) {
+            const { x } = (touches && touches[0]) || {};
+            const touch = x ? touches[0] : this.getRelative(e, touches);
+            if (this.landscape) {
+                [touch.x, touch.y] = [touch.y, this.height - touch.x];
+            }
+            return touch;
+        },
+        touchStart(e) {
+            this.isDown = true;
+            const next = () => {
+                const touches = convertTouchesToArray(e.touches);
+                if (this.chart) {
+                    const touch = this.getTouch(e, touches);
+                    this.startX = touch.x;
+                    this.startY = touch.y;
+                    this.startT = new Date();
+                    const handler = this.chart.getZr().handler;
+                    dispatch.call(handler, "mousedown", touch);
+                    dispatch.call(handler, "mousemove", touch);
+                    handler.processGesture(wrapTouch(e), "start");
+                    clearTimeout(this.endTimer);
+                }
+            };
+            if (this.isPC) {
+                getRect(`#${this.canvasId}`, { context: this }).then((res) => {
+                    this.rect = res;
+                    next();
+                });
+                return;
+            }
+            next();
+        },
+        touchMove(e) {
+            if (this.isPC && this.enableHover && !this.isDown) {
+                this.isDown = true;
+            }
+            const touches = convertTouchesToArray(e.touches);
+            if (this.chart && this.isDown) {
+                const handler = this.chart.getZr().handler;
+                dispatch.call(handler, "mousemove", this.getTouch(e, touches));
+                handler.processGesture(wrapTouch(e), "change");
+            }
+        },
+        touchEnd(e) {
+            this.isDown = false;
+            if (this.chart) {
+                const touches = convertTouchesToArray(e.changedTouches);
+                const { x } = (touches && touches[0]) || {};
+                const touch =
+                    (x ? touches[0] : this.getRelative(e, touches)) || {};
+                if (this.landscape) {
+                    [touch.x, touch.y] = [touch.y, this.height - touch.x];
+                }
+                const handler = this.chart.getZr().handler;
+                const isClick =
+                    Math.abs(touch.x - this.startX) < 10 &&
+                    new Date() - this.startT < 200;
+                dispatch.call(handler, "mouseup", touch);
+                handler.processGesture(wrapTouch(e), "end");
+                if (isClick) {
+                    dispatch.call(handler, "click", touch);
+                } else {
+                    this.endTimer = setTimeout(() => {
+                        dispatch.call(handler, "mousemove", {
+                            x: 999999999,
+                            y: 999999999,
+                        });
+                        dispatch.call(handler, "mouseup", {
+                            x: 999999999,
+                            y: 999999999,
+                        });
+                    }, 50);
+                }
+            }
+        },
+        // #endif
+        // #ifdef H5
+        mousewheel(e) {
+            if (this.chart) {
+                dispatch.call(
+                    this.chart.getZr().handler,
+                    "mousewheel",
+                    this.getTouch(e)
+                );
+            }
+        },
+        // #endif
+    },
 };
 </script>
-<style>	
+<style>
 .lime-echart {
-	position: relative;
-	/* #ifndef APP-NVUE */
-	width: 100%;
-	height: 100%;
-	/* #endif */
-	/* #ifdef APP-NVUE */
-	flex: 1;
-	/* #endif */
+    position: relative;
+    /* #ifndef APP-NVUE */
+    width: 100%;
+    height: 100%;
+    /* #endif */
+    /* #ifdef APP-NVUE */
+    flex: 1;
+    /* #endif */
 }
 .lime-echart__canvas {
-	/* #ifndef APP-NVUE */
-	width: 100%;
-	height: 100%;
-	/* #endif */
-	/* #ifdef APP-NVUE */
-	flex: 1;
-	/* #endif */
+    /* #ifndef APP-NVUE */
+    width: 100%;
+    height: 100%;
+    /* #endif */
+    /* #ifdef APP-NVUE */
+    flex: 1;
+    /* #endif */
 }
 /* #ifndef APP-NVUE */
 .lime-echart__mask {
-	position: absolute;
-	width: 100%;
-	height: 100%;
-	left: 0;
-	top: 0;
-	z-index: 1;
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    left: 0;
+    top: 0;
+    z-index: 1;
 }
 /* #endif */
 </style>

+ 246 - 0
src/utils/globalMqtt.js

@@ -0,0 +1,246 @@
+import mqtt from './mqtt.js';
+
+class MqttService {
+    constructor() {
+        if (!MqttService.instance) {
+            this.cmdClient = null;
+            this.dataClient = null;
+            this.cmdConnected = false;
+            this.dataConnected = false;
+            this.connections = new Map(); // 内部管理多连接
+            MqttService.instance = this;
+        }
+        return MqttService.instance;
+    }
+
+    // ---------------- 连接 CMD ----------------
+    connectCmd(userId) {
+        if (this.cmdConnected && this.cmdClient) {
+            console.log("CMD MQTT 已连接,复用现有实例");
+            return Promise.resolve(this.cmdClient);
+        }
+
+        const brokerName = 'CMD';
+        const url = 'wxs://cmd.radar-power.cn/mqtt/';
+        const clientId = `xcx_mqtt_cmd1_${userId}_${Math.random().toString(16).substring(2, 8)}`;
+
+        return this.connectToBroker(brokerName, { url, clientId, username: 'lnradar', password: 'lnradar' })
+            .then(client => {
+                this.cmdClient = client;
+                this.cmdConnected = true;
+
+                // 默认订阅 CMD 主题
+                const topic = `/mps/wx_${userId}/notice`;
+                this.subscribe('CMD', topic, (msg) => console.log('CMD 消息:', msg));
+
+                uni.$emit('mqtt-ready', this.cmdClient);
+                return client;
+            });
+    }
+
+    // ---------------- 连接 DATA ----------------
+    connectData(userId) {
+
+        if (this.dataConnected && this.dataClient) {
+            console.log("DATA MQTT 已连接,复用现有实例");
+            return Promise.resolve(this.dataClient);
+        }
+        uni.showToast({ title: "平台连接中...", icon: "loading", duration: 1000 });
+        const brokerName = 'DATA';
+        let url = ""
+        // url = "wxs://data.radar-power.cn/mqtt/";
+        if (__wxConfig.envVersion == 'develop') {
+            url = "wxs://data.radar-power.asia:8084/mqtt/";
+        }
+        if (__wxConfig.envVersion == 'trial') {
+            url = "wxs://data.radar-power.asia:8084/mqtt/";
+        }
+
+        if (__wxConfig.envVersion == 'release') {
+            url = "wxs://data.radar-power.cn/mqtt/";
+        }
+
+        const clientId = `xcx_mqtt_data1_${userId}_${Date.now()}`;
+        return this.connectToBroker(brokerName, { url, clientId, username: 'lnradar', password: 'lnradar' })
+            .then(client => {
+                this.dataClient = client;
+                this.dataConnected = true;
+                uni.$emit('mqttData-ready', this.dataClient);
+                return client;
+            });
+    }
+
+    // ---------------- 内部通用连接方法 ----------------
+    connectToBroker(brokerName, config) {
+        return new Promise((resolve, reject) => {
+            if (this.connections.has(brokerName)) {
+                const existingConn = this.connections.get(brokerName);
+                if (existingConn.connected) {
+                    resolve(existingConn.client);
+                    return;
+                }
+            }
+
+            const client = mqtt.connect(config.url, {
+                clientId: config.clientId,
+                username: config.username,
+                password: config.password,
+                reconnectPeriod: config.reconnectPeriod || 500,
+                wsOptions: {
+                    WebSocket: url => wx.connectSocket({
+                        url,
+                        header: { 'content-type': 'application/json' },
+                        protocols: ['mqtt'],
+                    })
+                },
+            });
+
+            const connection = {
+                client,
+                connected: false,
+                subscriptions: new Map(), // topic => [callbacks]
+            };
+            this.connections.set(brokerName, connection);
+
+            // ---------------- 连接成功 ----------------
+            client.on('connect', () => {
+                console.log(`${brokerName} MQTT 连接成功`);
+                connection.connected = true;
+
+                // 重连时恢复所有订阅
+                connection.subscriptions.forEach((callbacks, topic) => {
+                    connection.client.subscribe(topic, (err) => {
+                        if (err) console.error(`重连订阅失败: ${topic}`, err);
+                        else console.log(`✅ 重连订阅成功: ${topic}`);
+                    });
+                });
+
+                resolve(client);
+            });
+
+            // ---------------- 连接错误 ----------------
+            client.on('error', (err) => {
+                uni.showModal({
+                    title: '提示',
+                    content: '连接平台失败,请重新登录',
+                    showCancel: false,
+                    success: (res) => {
+                        if (res.confirm) {
+                            uni.clearStorageSync();
+                            uni.reLaunch({ url: "/pagesA/loginNew/loginNew" });
+                        }
+                    }
+                });
+                console.error(`${brokerName} MQTT 连接错误:`, err);
+                connection.connected = false;
+                reject(err);
+            });
+
+            // ---------------- 连接断开 ----------------
+            client.on('close', () => {
+                uni.showToast({ title: `平台已断开`, icon: "none", duration: 1500 });
+                console.log(`${brokerName} MQTT 断开`);
+                connection.connected = false;
+                if (brokerName === 'DATA') this.dataConnected = false;
+                if (brokerName === 'CMD') this.cmdConnected = false;
+            });
+
+            // ---------------- 接收消息 ----------------
+            client.on('message', (topic, message) => {
+                const callbacks = connection.subscriptions.get(topic) || [];
+                callbacks.forEach(cb => {
+                    try { cb(message.toString(), topic, brokerName); }
+                    catch (err) { console.error('Message callback error:', err); }
+                });
+            });
+
+            // ---------------- 连接超时 ----------------
+            setTimeout(() => {
+                if (!connection.connected) reject(new Error(`${brokerName} 连接超时`));
+            }, 10000);
+        });
+    }
+
+    // ---------------- 发布消息 ----------------
+    publish(clientType, topic, message) {
+        const client = clientType === 'CMD' ? this.cmdClient : this.dataClient;
+        if (client) client.publish(topic, message);
+        else console.warn(`${clientType} MQTT 未连接,无法 publish`);
+    }
+
+    // ---------------- 订阅 ----------------
+    subscribe(clientType, topic, callback, onSubscribeDone) {
+        const connection = clientType === 'CMD' ? this.connections.get('CMD') : this.connections.get('DATA');
+        if (!connection || !connection.connected) {
+            console.warn(`${clientType} MQTT 未连接,无法 subscribe`);
+            if (onSubscribeDone) onSubscribeDone(new Error('未连接'));
+            return;
+        }
+
+        // 保存回调
+        if (!connection.subscriptions.has(topic)) connection.subscriptions.set(topic, []);
+        const callbacks = connection.subscriptions.get(topic);
+        if (!callbacks.includes(callback)) callbacks.push(callback);
+
+        // 执行订阅
+        connection.client.subscribe(topic, (err) => {
+            if (err) console.error(`Subscribe error on ${clientType}:`, err);
+            if (onSubscribeDone) onSubscribeDone(err);
+        });
+
+        // 返回取消订阅函数
+        return () => this.unsubscribe(clientType, topic, callback);
+    }
+
+    // ---------------- 取消订阅 ----------------
+    unsubscribe(clientType, topic, callback) {
+        const connection = clientType === 'CMD' ? this.connections.get('CMD') : this.connections.get('DATA');
+        if (!connection) return;
+
+        const callbacks = connection.subscriptions.get(topic);
+        if (callbacks) {
+            const index = callbacks.indexOf(callback);
+            if (index > -1) callbacks.splice(index, 1);
+            if (callbacks.length === 0) {
+                connection.subscriptions.delete(topic);
+                connection.client.unsubscribe(topic);
+            }
+        }
+    }
+
+    // ---------------- 手动恢复订阅 ----------------
+    resubscribeAll(brokerName) {
+        const connection = this.connections.get(brokerName);
+        if (!connection || !connection.connected) return;
+
+        connection.subscriptions.forEach((callbacks, topic) => {
+            connection.client.subscribe(topic, (err) => {
+                if (err) console.error(`手动恢复订阅失败: ${topic}`, err);
+                else console.log(`✅ 手动恢复订阅成功: ${topic}`);
+            });
+        });
+    }
+
+    // ---------------- 断开连接 ----------------
+    disconnectAll() {
+        if (this.cmdClient) { this.cmdClient.end(true); this.cmdClient = null; this.cmdConnected = false; }
+        if (this.dataClient) { this.dataClient.end(true); this.dataClient = null; this.dataConnected = false; }
+        this.connections.forEach(conn => conn.client.end(true));
+        this.connections.clear();
+        console.log('所有 MQTT 连接已关闭');
+    }
+
+    disconnectData() {
+        if (this.dataClient) {
+            this.dataClient.end(true);
+            this.dataClient = null;
+            this.dataConnected = false;
+            this.connections.delete("DATA");
+            console.log("DATA MQTT 连接已关闭");
+        }
+    }
+}
+
+// 单例导出
+const instance = new MqttService();
+export default instance;

+ 0 - 25
www.json

@@ -1,25 +0,0 @@
-{
-    "id": 2107846690,
-    "uuid": "73c41f7c-4740-4f6e-81a3-69ea90e9610d",
-    "name": "wm测试告警计划",
-    "clientId": "94A9900B0B51",
-    "enable": null,
-    "region": null,
-    "eventVal": null,
-    "alarmTimePlanId": 908238896,
-    "thresholdTime": null,
-    "mergeTime": null,
-    "param": null,
-    "linkagePushWechatService": null,
-    "createTime": "2025-09-10 10:18:20",
-    "updateTime": "2025-09-10 10:18:20",
-    "remark": null,
-    "alarmTimePlan": {
-        "id": 908238896,
-        "startDate": "2025-09-10 10:18:18",
-        "stopDate": "2026-09-10 10:18:18",
-        "timeRange": "[{\"start_time\":\"01:01\",\"end_time\":\"04:03\"},{\"start_time\":\"01:01\",\"end_time\":\"04:03\"},{\"start_time\":\"01:01\",\"end_time\":\"04:03\"},{\"start_time\":\"04:03\",\"end_time\":\"20:03\"}]",
-        "monthDays": "[]",
-        "weekdays": "[1,2,3,4,5,6,7]"
-    }
-}

+ 50 - 0
旋转点.js

@@ -0,0 +1,50 @@
+/**
+ * point: [x, y]
+ * radar: [radarX, radarY]
+ * angle: 0, 90, 180, 270 顺时针旋转角度
+ * 返回旋转后的点 [x2, y2]
+ */
+function rotatePoint(point, radar, angle) {
+    const [x, y] = point;
+    const [cx, cy] = radar;
+    const dx = x - cx;
+    const dy = y - cy;
+    let nx, ny;
+
+    switch (angle) {
+        case 0:
+            nx = dx;
+            ny = dy;
+            break;
+        case 90:
+            nx = dy;
+            ny = -dx;
+            break;
+        case 180:
+            nx = -dx;
+            ny = -dy;
+            break;
+        case 270:
+            nx = -dy;
+            ny = dx;
+            break;
+        default:
+            throw new Error("angle must be 0, 90, 180, 270");
+    }
+
+    return [cx + nx, cy + ny];
+}
+
+// 测试
+const point = [3, 2];
+const radar = [2, 1];
+
+[0, 90, 180, 270].forEach(angle => {
+    const newPoint = rotatePoint(point, radar, angle);
+    console.log(`angle=${angle}:`, newPoint);
+});
+// 预期输出
+// angle=0: [3, 2]
+// angle=90: [2, 0]
+// angle=180: [1, 0]
+// angle=270: [2, 2]

+ 75 - 0
旋转矩形.js

@@ -0,0 +1,75 @@
+/**
+ * rect: [left, top, w, h]
+ * radar: [radarX, radarY]
+ * angle: 0, 90, 180, 270
+ * 返回旋转后的矩形 [left, top, w, h]
+ */
+function rotateRect(rect, radar, angle) {
+    const [left, top, w, h] = rect;
+    const [cx, cy] = radar;
+
+    // 矩形四个角
+    const corners = [
+        [left, top],
+        [left + w, top],
+        [left + w, top + h],
+        [left, top + h]
+    ];
+
+    const newCorners = corners.map(([x, y]) => {
+        const dx = x - cx;
+        const dy = y - cy;
+        let nx, ny;
+        switch (angle) {
+            case 0:
+                nx = dx;
+                ny = dy;
+                break;
+            case 90:
+                nx = dy;
+                ny = -dx;
+                break;
+            case 180:
+                nx = -dx;
+                ny = -dy;
+                break;
+            case 270:
+                nx = -dy;
+                ny = dx;
+                break;
+            default:
+                throw new Error("angle must be 0, 90, 180, 270");
+        }
+        return [cx + nx, cy + ny];
+    });
+
+    const xs = newCorners.map(p => p[0]);
+    const ys = newCorners.map(p => p[1]);
+
+    const newLeft = Math.min(...xs);
+    const newTop = Math.min(...ys);
+    const newW = Math.max(...xs) - newLeft;
+    const newH = Math.max(...ys) - newTop;
+
+    return [newLeft, newTop, newW, newH];
+}
+
+// 测试
+const rect = [0, 0, 2, 1];
+const radar = [2, 1];
+
+[0, 90, 180, 270].forEach(angle => {
+    const newRect = rotateRect(rect, radar, angle);
+    console.log(`angle=${angle}:`, newRect);
+});
+
+// 预期输出
+// angle=0: [0, 0, 2, 1]
+// angle=90: [2, -1, 1, 2]
+// angle=180: [2, 1, 2, 1]
+// angle=270: [1, 1, 1, 2]
+
+
+
+
+