فهرست منبع

[Feat 0000]历史数据页面 页面定时逻辑优化

bobo04052021@163.com 3 روز پیش
والد
کامیت
c876ff8508

BIN
src/assets/images/beltFire/listAll.png


+ 377 - 378
src/views/vent/home/configurable/belt/belt-new.vue

@@ -48,316 +48,361 @@
 </template>
 
 <script setup lang="ts">
-  import { onMounted, ref, watch, nextTick, computed, onUnmounted } from 'vue';
-  import customHeader from './components/customHeader-belt.vue';
-  import { useInitConfigs, useInitPage } from '../hooks/useInit';
-  import { testBeltNew, testYjkf, testSpary } from './configurable.data';
-  import ModuleCommon from './components/ModuleCommon.vue';
-  import ModuleCommonDual from './components/ModuleCommonDual.vue';
-  import Three3D from '/@/views/vent/home/configurable/components/three3D.vue';
-  import BeltNav from './components/BeltNav.vue';
-  import { useRouter, useRoute } from 'vue-router';
-  import { getSystem, getMonitorAndAlertBelt, getWarnInfo, getDevice, getDataHome, getWarnResult, getStaffInfo } from './configurable.api';
-  import { modalAnimate, destroy } from './threejs/belt.threejs';
-  import History from './history.vue';
-  import sys from '/@/locales/lang/en/sys';
-  // 初始化配置
-  const { configs, fetchConfigs } = useInitConfigs();
-  const cfgs = computed(() => configs.value.filter((_, index) => index !== 2 && index !== 3));
-  const cfgA = computed<any>(() => configs.value[2]);
-  const cfgB = computed<any>(() => configs.value[3]);
-  const { updateEnhancedConfigs, updateData, data } = useInitPage('矿井全域皮带巷三级防灭火系统');
-  const isInitModal = ref(false);
-  const pageCache = ref({
-    fire_risk_warn: { configs: testBeltNew },
-    emergencyControl: { configs: testYjkf },
-    sprayControl: { configs: testSpary },
-  });
-  let timer = null,
-    modal = null;
-  const pageType = ref('fire_risk_warn');
-  const route = useRoute();
-  const modalMonitorData = ref({});
-  // 下拉框选项
-  /** 场景选项 */
-  const options = ref([]);
-  const optionValue = ref('');
-  async function getSysDataSource() {
-    const res = await getDataHome({ dataList: 'risk_evaluator' }).catch(() => {});
-    options.value = res.risk_evaluator || [];
-    await nextTick();
-    if (options.value.length > 0 && !optionValue.value) {
-      const firstId = options.value[0].sys_id;
-      changeSelectRow(firstId);
-    }
-  }
-  // 切换检测数据
-  function changeSelectRow(deviceID) {
-    optionValue.value = deviceID;
-    refresh();
+import { onMounted, ref, watch, nextTick, computed, onUnmounted } from 'vue';
+import customHeader from './components/customHeader-belt.vue';
+import { useInitConfigs, useInitPage } from '../hooks/useInit';
+import { testBeltNew, testYjkf, testSpary } from './configurable.data';
+import ModuleCommon from './components/ModuleCommon.vue';
+import ModuleCommonDual from './components/ModuleCommonDual.vue';
+import Three3D from '/@/views/vent/home/configurable/components/three3D.vue';
+import BeltNav from './components/BeltNav.vue';
+import { useRouter, useRoute } from 'vue-router';
+import { getSystem, getMonitorAndAlertBelt, getWarnInfo, getDevice, getDataHome, getWarnResult, getStaffInfo } from './configurable.api';
+import { modalAnimate, destroy } from './threejs/belt.threejs';
+import History from './history.vue';
+import sys from '/@/locales/lang/en/sys';
+// 初始化配置
+const { configs, fetchConfigs } = useInitConfigs();
+const cfgs = computed(() => configs.value.filter((_, index) => index !== 2 && index !== 3));
+const cfgA = computed<any>(() => configs.value[2]);
+const cfgB = computed<any>(() => configs.value[3]);
+const { updateEnhancedConfigs, updateData, data } = useInitPage('矿井全域皮带巷三级防灭火系统');
+const isInitModal = ref(false);
+const pageCache = ref({
+  fire_risk_warn: { configs: testBeltNew },
+  emergencyControl: { configs: testYjkf },
+  sprayControl: { configs: testSpary },
+});
+let timer = null,
+  modal = null;
+const pageType = ref('fire_risk_warn');
+const route = useRoute();
+const modalMonitorData = ref({});
+// 下拉框选项
+/** 场景选项 */
+const options = ref([]);
+const optionValue = ref('');
+async function getSysDataSource() {
+  const res = await getDataHome({ dataList: 'risk_evaluator' }).catch(() => {});
+  options.value = res.risk_evaluator || [];
+  await nextTick();
+  if (options.value.length > 0 && !optionValue.value) {
+    const firstId = options.value[0].sys_id;
+    changeSelectRow(firstId);
   }
-  // 预警等级映射
-  const warnTypeMap = {
-    '102': '黄色预警(较大风险)',
-    '103': '橙色预警(重大风险)',
-    '104': '红色预警(特别重大风险)',
-  };
-  // 处理接口返回数据
-  interface WarnResult {
-    warnName: string;
-    coRange?: string;
-    coTrend?: string;
-    tempRange?: string;
-    tempTrend?: string;
-    hclRange?: string;
-    warnCtrl?: string;
-  }
-  // 处理预警指标展示数据格式
-  function groupWarnByType(data: any) {
-    const result: WarnResult[] = [];
-    // 遍历 102、103、104...
-    Object.keys(data).forEach((warnKey) => {
-      const list = data[warnKey] || [];
-      const warnName = warnTypeMap[warnKey as keyof typeof warnTypeMap];
-      function getWarnCtrlByWarnName(warnName) {
-        if (warnName === '黄色预警(较大风险)') return '预警';
-        if (warnName === '橙色预警(重大风险)') return '报警启动喷淋';
-        if (warnName === '红色预警(特别重大风险)') return '启动喷淋与排烟';
-        return '';
-      }
-      // 构建当前预警对象
-      const warnObj: WarnResult = { warnName };
-      warnObj.warnCtrl = getWarnCtrlByWarnName(warnName);
-      list.forEach((item: any) => {
-        const { deviceType, fmin, fmax, trendMin, trendMax, trendCxTimeUnit } = item;
-        // CO
-        if (deviceType === 'modelsensor_co') {
-          if (fmin != null && fmax != null) {
-            warnObj.coRange = `${fmin} - ${fmax}`;
-          } else if (fmin != null && fmax == null) {
-            warnObj.coRange = `> ${fmin}`;
-          }
-          // 处理变化率
-          else if (trendMin != null && trendMax != null) {
-            if (warnName === '黄色预警(较大风险)') {
-              warnObj.coTrend = `加速上升(${trendMin} - ${trendMax})`;
-            } else if (warnName === '橙色预警(重大风险)') {
-              warnObj.coTrend = `急剧上升(${trendMin} - ${trendMax})`;
-            } else if (warnName === '红色预警(特别重大风险)') {
-              warnObj.coTrend = `极端上升(${trendMin} - ${trendMax})`;
-            }
-          } else if (trendMin != null && trendMax == null) {
-            warnObj.coTrend = `极端上升>${trendMin}`;
-          }
+}
+// 切换检测数据
+function changeSelectRow(deviceID) {
+  optionValue.value = deviceID;
+  refresh();
+}
+// 预警等级映射
+const warnTypeMap = {
+  '102': '黄色预警(较大风险)',
+  '103': '橙色预警(重大风险)',
+  '104': '红色预警(特别重大风险)',
+};
+// 处理接口返回数据
+interface WarnResult {
+  warnName: string;
+  coRange?: string;
+  coTrend?: string;
+  tempRange?: string;
+  tempTrend?: string;
+  hclRange?: string;
+  warnCtrl?: string;
+}
+// 处理预警指标展示数据格式
+function groupWarnByType(data: any) {
+  const result: WarnResult[] = [];
+  // 遍历 102、103、104...
+  Object.keys(data).forEach((warnKey) => {
+    const list = data[warnKey] || [];
+    const warnName = warnTypeMap[warnKey as keyof typeof warnTypeMap];
+    function getWarnCtrlByWarnName(warnName) {
+      if (warnName === '黄色预警(较大风险)') return '预警';
+      if (warnName === '橙色预警(重大风险)') return '报警启动喷淋';
+      if (warnName === '红色预警(特别重大风险)') return '启动喷淋与排烟';
+      return '';
+    }
+    // 构建当前预警对象
+    const warnObj: WarnResult = { warnName };
+    warnObj.warnCtrl = getWarnCtrlByWarnName(warnName);
+    list.forEach((item: any) => {
+      const { deviceType, fmin, fmax, trendMin, trendMax, trendCxTimeUnit } = item;
+      // CO
+      if (deviceType === 'modelsensor_co') {
+        if (fmin != null && fmax != null) {
+          warnObj.coRange = `${fmin} - ${fmax}`;
+        } else if (fmin != null && fmax == null) {
+          warnObj.coRange = `> ${fmin}`;
         }
-        // 温度(带单位)
-        else if (deviceType === 'modelsensor_temperature') {
-          if (fmin != null && fmax != null) {
-            warnObj.tempRange = `${fmin} - ${fmax}℃`;
-          } else if (fmin != null && fmax == null) {
-            warnObj.tempRange = `≥ ${fmin}℃(持续高温)`;
-          } else if (trendMin != null) {
-            let unit = '';
-            if (trendCxTimeUnit === 0) unit = '分钟';
-            if (trendCxTimeUnit === 1) unit = '小时';
-            warnObj.tempTrend = `>+${trendMin}℃ /${unit}`;
+        // 处理变化率
+        else if (trendMin != null && trendMax != null) {
+          if (warnName === '黄色预警(较大风险)') {
+            warnObj.coTrend = `加速上升(${trendMin} - ${trendMax})`;
+          } else if (warnName === '橙色预警(重大风险)') {
+            warnObj.coTrend = `急剧上升(${trendMin} - ${trendMax})`;
+          } else if (warnName === '红色预警(特别重大风险)') {
+            warnObj.coTrend = `极端上升(${trendMin} - ${trendMax})`;
           }
+        } else if (trendMin != null && trendMax == null) {
+          warnObj.coTrend = `极端上升>${trendMin}`;
         }
-        // HCL
-        else if (deviceType === 'modelsensor_hcl') {
-          if (fmin != null && fmax != null) {
-            warnObj.hclRange = `${fmin} - ${fmax}`;
-          } else if (fmin != null && fmax == null) {
-            warnObj.hclRange = `≥ ${fmin}`;
-          }
+      }
+      // 温度(带单位)
+      else if (deviceType === 'modelsensor_temperature') {
+        if (fmin != null && fmax != null) {
+          warnObj.tempRange = `${fmin} - ${fmax}℃`;
+        } else if (fmin != null && fmax == null) {
+          warnObj.tempRange = `≥ ${fmin}℃(持续高温)`;
+        } else if (trendMin != null) {
+          let unit = '';
+          if (trendCxTimeUnit === 0) unit = '分钟';
+          if (trendCxTimeUnit === 1) unit = '小时';
+          warnObj.tempTrend = `>+${trendMin}℃ /${unit}`;
         }
-      });
-      result.push(warnObj);
+      }
+      // HCL
+      else if (deviceType === 'modelsensor_hcl') {
+        if (fmin != null && fmax != null) {
+          warnObj.hclRange = `${fmin} - ${fmax}`;
+        } else if (fmin != null && fmax == null) {
+          warnObj.hclRange = `≥ ${fmin}`;
+        }
+      }
     });
+    result.push(warnObj);
+  });
 
-    return result;
-  }
-  // 刷新数据
-  async function refresh() {
-    // 由于模型中需要用到风门的监测数据,这里进行公共调用(后期精确调用风门)
-    const modalRes = {};
-    const systemParams = {
-      devicetype: 'sys',
-      systemID: optionValue.value,
-    };
-    const resSys = await getSystem(systemParams);
+  return result;
+}
+// 刷新数据
+async function refresh() {
+  // 由于模型中需要用到风门的监测数据,这里进行公共调用(后期精确调用风门)
+  const modalRes = {};
+  const systemParams = {
+    devicetype: 'sys',
+    systemID: optionValue.value,
+  };
+  const resSys = await getSystem(systemParams);
+  const params = {
+    sysId: optionValue.value,
+    monitorType: 2,
+  };
+  const warnInfo = await getWarnInfo(params);
+  const staffInfo = await getStaffInfo({ sysId: optionValue.value });
+  Object.assign(modalRes, resSys);
+  if (pageType.value == 'fire_risk_warn') {
+    configs.value = [...testBeltNew];
     const params = {
       sysId: optionValue.value,
-      monitorType: 2,
+      dataList: 'fire_risk_warn,warn_result,vehicle_co_correlate',
+      alarmLevel: '102,103,104',
     };
-    const warnInfo = await getWarnInfo(params);
-    const staffInfo = await getStaffInfo({ sysId: optionValue.value });
-    Object.assign(modalRes, resSys);
-    if (pageType.value == 'fire_risk_warn') {
-      configs.value = [...testBeltNew];
-      const params = {
-        sysId: optionValue.value,
-        dataList: 'fire_risk_warn,warn_result,vehicle_co_correlate',
-        alarmLevel: '102,103,104',
-      };
-      const resWarn = await getMonitorAndAlertBelt(params);
-      resWarn.warnInfo = groupWarnByType(warnInfo);
-      console.log(resWarn.warnInfo, '111111111');
-      resWarn.staffInfo = staffInfo;
-      updateData(resWarn);
-      Object.assign(modalRes, resWarn);
-    } else if (pageType.value == 'emergencyControl') {
-      updateData(resSys);
-      configs.value = [...testYjkf];
-      const alarmParams = {
-        sysId: optionValue.value,
-        alarmLevel: '104',
-      };
-      const alarmRes = await getWarnResult(alarmParams);
-      if (alarmRes.warn_result) {
-        data.value.warn_result = alarmRes.warn_result;
-      }
-      data.value.warnInfo = groupWarnByType(warnInfo);
-      data.value.staffInfo = staffInfo;
-      updateData(data.value);
-    } else if (pageType.value == 'sprayControl') {
-      updateData(resSys);
-      const params1 = {
-        sysId: optionValue.value,
-        alarmLevel: '103,104',
-      };
-      const sprayData = [];
-      const dustData = [];
-      if (data.value?.deviceInfo) {
-        // 遍历对象的所有 value
-        Object.values(data.value.deviceInfo).forEach((item) => {
-          const hasSprayAuto = item.type && item.type.toLowerCase().includes('spray');
-          if (hasSprayAuto) {
-            sprayData.push({ ...item, ...item.readData });
-          }
-        });
-        Object.values(data.value.deviceInfo).forEach((item) => {
-          const hasDustAuto = item.type && item.type.toLowerCase().includes('dustdev');
-          if (hasDustAuto) {
-            dustData.push({ ...item, ...item.readData });
-          }
-        });
-      }
-      data.value.sprayData = sprayData;
-      data.value.dustData = dustData;
-      data.value.warnInfo = groupWarnByType(warnInfo);
-      data.value.staffInfo = staffInfo;
-      const alarmRes = await getWarnResult(params1);
-      if (alarmRes.warn_result) {
-        data.value.warn_result = alarmRes.warn_result;
-      }
-      configs.value = [...testSpary];
-    } else {
-      configs.value = testBeltNew;
+    const resWarn = await getMonitorAndAlertBelt(params);
+    resWarn.warnInfo = groupWarnByType(warnInfo);
+    resWarn.staffInfo = staffInfo;
+    updateData(resWarn);
+    Object.assign(modalRes, resWarn);
+  } else if (pageType.value == 'emergencyControl') {
+    updateData(resSys);
+    configs.value = [...testYjkf];
+    const alarmParams = {
+      sysId: optionValue.value,
+      alarmLevel: '104',
+    };
+    const alarmRes = await getWarnResult(alarmParams);
+    if (alarmRes.warn_result) {
+      data.value.warn_result = alarmRes.warn_result;
+    }
+    data.value.warnInfo = groupWarnByType(warnInfo);
+    data.value.staffInfo = staffInfo;
+    updateData(data.value);
+  } else if (pageType.value == 'sprayControl') {
+    updateData(resSys);
+    const params1 = {
+      sysId: optionValue.value,
+      alarmLevel: '103,104',
+    };
+    const sprayData = [];
+    const dustData = [];
+    if (data.value?.deviceInfo) {
+      // 遍历对象的所有 value
+      Object.values(data.value.deviceInfo).forEach((item) => {
+        const hasSprayAuto = item.type && item.type.toLowerCase().includes('spray');
+        if (hasSprayAuto) {
+          sprayData.push({ ...item, ...item.readData });
+        }
+      });
+      Object.values(data.value.deviceInfo).forEach((item) => {
+        const hasDustAuto = item.type && item.type.toLowerCase().includes('dustdev');
+        if (hasDustAuto) {
+          dustData.push({ ...item, ...item.readData });
+        }
+      });
+    }
+    data.value.sprayData = sprayData;
+    data.value.dustData = dustData;
+    data.value.warnInfo = groupWarnByType(warnInfo);
+    data.value.staffInfo = staffInfo;
+    const alarmRes = await getWarnResult(params1);
+    if (alarmRes.warn_result) {
+      data.value.warn_result = alarmRes.warn_result;
     }
-    modalMonitorData.value = modalRes;
+    configs.value = [...testSpary];
+  } else {
+    configs.value = testBeltNew;
   }
+  modalMonitorData.value = modalRes;
+}
 
-  // // 定时刷新
-  function initInterval() {
-    if (timer) clearInterval(timer);
-    timer = setInterval(() => {
-      refresh();
-    }, 60000);
-  }
+// // 定时刷新
+function initInterval() {
+  if (timer) clearInterval(timer);
+  timer = setInterval(() => {
+    refresh();
+  }, 5000);
+}
 
-  async function changePage(pageTypeStr) {
-    const target = pageTypeStr || route.query.pageType || 'fire_risk_warn';
-    if (pageType.value === target) return;
-    pageType.value = target;
-    configs.value = pageCache.value[target]?.configs || testBeltNew;
-    await nextTick();
-    await refresh();
-  }
-  // watch(
-  //   // 监听动态路由参数 :type
-  //   () => route.params.type,
-  //   (newVal) => {
-  //     if (newVal) {
-  //       console.log('切换页面类型:', newVal);
-  //       refresh(); // 切换路由自动刷新
-  //     }
-  //   }
-  // );
+async function changePage(pageTypeStr) {
+  const target = pageTypeStr || route.query.pageType || 'fire_risk_warn';
+  if (pageType.value === target) return;
+  pageType.value = target;
+  configs.value = pageCache.value[target]?.configs || testBeltNew;
+  await nextTick();
+  await refresh();
+}
+// watch(
+//   // 监听动态路由参数 :type
+//   () => route.params.type,
+//   (newVal) => {
+//     if (newVal) {
+//       console.log('切换页面类型:', newVal);
+//       refresh(); // 切换路由自动刷新
+//     }
+//   }
+// );
 
-  function initModalAnimate(modal3D) {
-    modal = modal3D;
-    modal.isRender = true;
-    modalAnimate(modal, modalMonitorData);
+function initModalAnimate(modal3D) {
+  modal = modal3D;
+  modal.isRender = true;
+  modalAnimate(modal, modalMonitorData);
+}
+function clearTimer() {
+  if (timer) {
+    clearInterval(timer);
+    timer = null;
   }
-  function clearTimer() {
-    if (timer) {
-      clearInterval(timer);
-      timer = null;
+}
+watch(
+  () => route.query.pageType,
+  (newQueryType) => {
+    if (newQueryType) {
+      changePage(newQueryType as string);
     }
-  }
-  watch(
-    () => route.query.pageType,
-    (newQueryType) => {
-      if (newQueryType) {
-        changePage(newQueryType as string);
-      }
-    },
-    { immediate: true } // 初始化立刻执行
-  );
+  },
+  { immediate: true } // 初始化立刻执行
+);
 
-  watch(
-    () => modalMonitorData.value,
-    (newData, oldData) => {
-      if (newData && !Object.keys(oldData).length) {
-        isInitModal.value = true;
-      }
+watch(
+  () => modalMonitorData.value,
+  (newData, oldData) => {
+    if (newData && !Object.keys(oldData).length) {
+      isInitModal.value = true;
     }
-  );
+  }
+);
 
-  onMounted(async () => {
-    await getSysDataSource();
-    await refresh();
-    initInterval();
-  });
-  onUnmounted(() => {
-    clearTimer();
-    destroy(modal);
-    modal?.destroy();
-  });
+onMounted(async () => {
+  await getSysDataSource();
+  await refresh();
+  initInterval();
+});
+onUnmounted(() => {
+  clearTimer();
+  destroy(modal);
+  modal?.destroy();
+});
 </script>
 <style lang="less" scoped>
-  .company-home {
-    background: url('/@/assets/images/beltFire/baseMap.png') no-repeat center;
+.company-home {
+  background: url('/@/assets/images/beltFire/baseMap.png') no-repeat center;
+  width: 100%;
+  height: 100%;
+  color: @white;
+  position: relative;
+  font-family: 'Microsoft YaHei', sans-serif;
+  .top-bg {
     width: 100%;
-    height: 100%;
-    color: @white;
+    height: 56px;
+    position: absolute;
+    margin-top: 10px;
+    z-index: 1;
+  }
+  .header-container {
+    position: absolute;
+    top: 20px;
+    left: 20px;
+    z-index: 10;
+  }
+
+  .border {
+    width: 100%;
+    height: 94%;
+    background: url('/@/assets/images/beltFire/mainbj.png') no-repeat;
+    background-size: 100% 100%;
     position: relative;
-    font-family: 'Microsoft YaHei', sans-serif;
-    .top-bg {
-      width: 100%;
-      height: 56px;
-      position: absolute;
-      margin-top: 10px;
-      z-index: 1;
-    }
-    .header-container {
-      position: absolute;
-      top: 20px;
-      left: 20px;
-      z-index: 10;
-    }
+    width: 100%;
+  }
+}
 
-    .border {
-      width: 100%;
-      height: 94%;
-      background: url('/@/assets/images/beltFire/mainbj.png') no-repeat;
-      background-size: 100% 100%;
-      position: relative;
-      width: 100%;
+.center-warning-container {
+  position: absolute;
+  left: 50%;
+  transform: translateX(-50%);
+  top: 50%;
+  width: 600px;
+  height: 200px;
+  background-color: rgba(0, 0, 0, 0.7);
+  border-radius: 10px;
+  padding: 15px;
+  box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);
+  z-index: 5;
+  color: #fff;
+
+  .warning-header {
+    font-size: 18px;
+    font-weight: bold;
+    margin-bottom: 10px;
+    color: #ff6b6b;
+  }
+
+  .warning-list {
+    width: 100%;
+    height: 100%;
+    overflow-y: auto;
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+  }
+
+  .warning-item {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 8px;
+    background-color: rgba(0, 0, 0, 0.5);
+    border-radius: 5px;
+    border-left: 4px solid #ff6b6b;
+
+    .warning-time {
+      font-size: 14px;
+      color: #ccc;
     }
   }
 
+  // 中间预警结果区
   .center-warning-container {
     position: absolute;
     left: 50%;
@@ -401,113 +446,67 @@
         font-size: 14px;
         color: #ccc;
       }
-    }
-
-    // 中间预警结果区
-    .center-warning-container {
-      position: absolute;
-      left: 50%;
-      transform: translateX(-50%);
-      top: 50%;
-      width: 600px;
-      height: 200px;
-      background-color: rgba(0, 0, 0, 0.7);
-      border-radius: 10px;
-      padding: 15px;
-      box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);
-      z-index: 5;
-      color: #fff;
 
-      .warning-header {
-        font-size: 18px;
+      .warning-level {
+        font-size: 14px;
         font-weight: bold;
-        margin-bottom: 10px;
-        color: #ff6b6b;
-      }
-
-      .warning-list {
-        width: 100%;
-        height: 100%;
-        overflow-y: auto;
-        display: flex;
-        flex-direction: column;
-        gap: 8px;
-      }
-
-      .warning-item {
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
-        padding: 8px;
-        background-color: rgba(0, 0, 0, 0.5);
-        border-radius: 5px;
-        border-left: 4px solid #ff6b6b;
-
-        .warning-time {
-          font-size: 14px;
-          color: #ccc;
+        padding: 4px 8px;
+        border-radius: 4px;
+        &.level-critical {
+          background-color: #ff6b6b;
+          color: white;
         }
-
-        .warning-level {
-          font-size: 14px;
-          font-weight: bold;
-          padding: 4px 8px;
-          border-radius: 4px;
-          &.level-critical {
-            background-color: #ff6b6b;
-            color: white;
-          }
-          &.level-high {
-            background-color: #ffcc00;
-            color: black;
-          }
-          &.level-normal {
-            background-color: #66cc66;
-            color: white;
-          }
+        &.level-high {
+          background-color: #ffcc00;
+          color: black;
+        }
+        &.level-normal {
+          background-color: #66cc66;
+          color: white;
         }
+      }
 
-        .warning-action {
-          .btn-start-spray {
-            background-color: #00e1ff;
-            color: #000;
-            border: none;
-            padding: 4px 10px;
-            border-radius: 4px;
-            cursor: pointer;
-            font-size: 12px;
-            transition: all 0.3s;
-            &:hover {
-              background-color: #00c3e6;
-            }
+      .warning-action {
+        .btn-start-spray {
+          background-color: #00e1ff;
+          color: #000;
+          border: none;
+          padding: 4px 10px;
+          border-radius: 4px;
+          cursor: pointer;
+          font-size: 12px;
+          transition: all 0.3s;
+          &:hover {
+            background-color: #00c3e6;
           }
         }
       }
     }
+  }
 
-    // 巷道示意图
-    .belt-diagram {
-      position: absolute;
-      left: 50%;
-      transform: translateX(-50%);
-      bottom: 50px;
-      width: 800px;
-      height: 100px;
-      display: flex;
-      justify-content: center;
-      align-items: center;
+  // 巷道示意图
+  .belt-diagram {
+    position: absolute;
+    left: 50%;
+    transform: translateX(-50%);
+    bottom: 50px;
+    width: 800px;
+    height: 100px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
 
-      img {
-        width: 100%;
-        height: 100%;
-        object-fit: contain;
-      }
+    img {
+      width: 100%;
+      height: 100%;
+      object-fit: contain;
     }
   }
-  .modal-box {
-    width: 100%;
-    height: 100%;
-    position: absolute;
-    z-index: 1;
-  }
+}
+.modal-box {
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  z-index: 1;
+}
 </style>

+ 127 - 122
src/views/vent/home/configurable/belt/belt.vue

@@ -6,8 +6,8 @@
       <SubApp />
       <div class="box-container">
         <ModuleCommon
-          v-for="cfg in configs"
-          :key="cfg.deviceType"
+          v-for="cfg in localConfigs"
+          :key="cfg.moduleName + cfg.deviceType"
           :show-style="cfg.showStyle"
           :module-data="cfg.moduleData"
           :module-name="cfg.moduleName"
@@ -23,140 +23,145 @@
   </div>
 </template>
 <script setup lang="ts">
-  import { onMounted, onUnmounted, ref, nextTick } from 'vue';
-  import customHeader from './components/customHeader-belt.vue';
-  import { useInitConfigs, useInitPage } from '../hooks/useInit';
-  import { testBeltLaneFire } from './configurable.data';
-  import ModuleCommon from './components/ModuleCommon.vue';
-  import SubApp from '/@/components/vent/micro/createSubApp.vue';
-  import { getDataHome } from './configurable.api';
+import { onMounted, onUnmounted, ref, nextTick, watch } from 'vue';
+import customHeader from './components/customHeader-belt.vue';
+import { useInitConfigs, useInitPage } from '../hooks/useInit';
+import { testBeltLaneFire } from './configurable.data';
+import ModuleCommon from './components/ModuleCommon.vue';
+import SubApp from '/@/components/vent/micro/createSubApp.vue';
+import { getDataHome } from './configurable.api';
 
-  const { configs, devicesTypes, fetchConfigs } = useInitConfigs();
-  const { updateEnhancedConfigs, updateData, data } = useInitPage('矿井全域皮带巷三级防灭火系统');
+const { configs, devicesTypes, fetchConfigs } = useInitConfigs();
+const { updateEnhancedConfigs, updateData, data } = useInitPage('矿井全域皮带巷三级防灭火系统');
 
-  const currentSelectedId = ref<string>('');
-
-  let timer = null;
-  const showHistory = ref(false);
-  // 接收子组件上报的点击事件,更新全局选中状态
-  const handleItemClick = async (item) => {
-    const clickId = item.id;
-    if (!clickId) return;
-    currentSelectedId.value = clickId;
-    await nextTick();
-    refreshData();
-  };
-
-  // 数据筛选
-  const filterDataById = (sourceData, clickId: string | number) => {
-    if (!sourceData || !clickId) return sourceData;
-    const id = String(clickId);
-    const monitor = sourceData.monitor_alert?.find((item) => String(item.sysId) === id);
-    const spray = sourceData.spray_control?.find((item) => String(item.sysId) === id);
-    const gate = sourceData.gate_control?.find((item) => String(item.sysId) === id);
-    return {
-      ...sourceData,
-      monitor_alert: monitor ? [monitor] : [],
-      spray_control: spray ? [spray] : [],
-      gate_control: gate ? [gate] : [],
-    };
+const currentSelectedId = ref<string>('');
+let timer = null;
+const localConfigs = ref<any[]>([]);
+watch(
+  configs,
+  (newVal) => {
+    if (!newVal || newVal.length === 0) {
+      localConfigs.value = testBeltLaneFire;
+    } else {
+      localConfigs.value = newVal;
+    }
+  },
+  { deep: true, immediate: true }
+);
+// 接收子组件上报的点击事件,更新全局选中状态
+const handleItemClick = async (item) => {
+  const clickId = item.id;
+  if (!clickId) return;
+  currentSelectedId.value = clickId;
+  await nextTick();
+  refreshData();
+};
+// 数据筛选
+const filterDataById = (sourceData, clickId: string | number) => {
+  if (!sourceData || !clickId) return sourceData;
+  const id = String(clickId);
+  const monitor = sourceData.monitor_alert?.find((item) => String(item.sysId) === id);
+  const spray = sourceData.spray_control?.find((item) => String(item.sysId) === id);
+  const gate = sourceData.gate_control?.find((item) => String(item.sysId) === id);
+  return {
+    ...sourceData,
+    monitor_alert: monitor ? [monitor] : [],
+    spray_control: spray ? [spray] : [],
+    gate_control: gate ? [gate] : [],
   };
+};
 
-  function refresh() {
-    fetchConfigs('belt').then(() => {
-      if (!configs.value || configs.value.length === 0) {
-        configs.value = testBeltLaneFire;
-      }
-      refreshData();
-    });
-  }
-  function refreshData() {
-    const dataListStr = configs.value
-      .filter((e) => e.deviceType)
-      .map((e) => e.deviceType)
-      .join(',');
+function refresh() {
+  fetchConfigs('belt');
+  refreshData();
+}
 
-    getDataHome({ dataList: dataListStr }).then((res: any) => {
-      res.spray_control = [
-        {
-          systemName: '东翼胶带运输大巷',
-          sysId: '2028657172566073346',
-          sysList: [{ netstatus: '1', deviceStatus: '1', plsy: '1#区域 1.4MPa', kzms: '手动' }],
-        },
-        {
-          sysId: '2046500718274756609',
-          systemName: '1101胶带运输顺槽',
-          sysList: [{ netstatus: '1', deviceStatus: '1', plqy: '2#区域', plsy: '1.6MPa', kzms: '自动' }],
-        },
-      ];
+function refreshData() {
+  const dataListStr = localConfigs.value
+    .filter((e) => e.deviceType)
+    .map((e) => e.deviceType)
+    .join(',');
+  getDataHome({ dataList: dataListStr }).then((res: any) => {
+    res.spray_control = [
+      {
+        systemName: '东翼胶带运输大巷',
+        sysId: '2028657172566073346',
+        sysList: [{ netstatus: '1', deviceStatus: '1', plsy: '1#区域 1.4MPa', kzms: '手动' }],
+      },
+      {
+        sysId: '2046500718274756609',
+        systemName: '1101胶带运输顺槽',
+        sysList: [{ netstatus: '1', deviceStatus: '1', plqy: '2#区域', plsy: '1.6MPa', kzms: '自动' }],
+      },
+    ];
 
-      let showData = res;
-      if (currentSelectedId.value) {
-        showData = filterDataById(res, currentSelectedId.value);
-      } else {
-        const firstId = res.monitor_alert?.[0]?.sysId;
-        if (firstId) {
-          currentSelectedId.value = firstId;
-          showData = filterDataById(res, firstId);
-        }
+    let showData = res;
+    if (currentSelectedId.value) {
+      showData = filterDataById(res, currentSelectedId.value);
+    } else {
+      const firstId = res.monitor_alert?.[0]?.sysId;
+      if (firstId) {
+        currentSelectedId.value = firstId;
+        showData = filterDataById(res, firstId);
       }
-      updateData(showData);
-    });
-  }
-  // 轮询
-  function initInterval() {
-    if (timer) clearInterval(timer);
-    timer = setInterval(() => {
-      refresh();
-    }, 5000);
-  }
-  onMounted(() => {
-    refresh();
-    initInterval();
+    }
+    updateData(showData);
   });
+}
+function initInterval() {
+  if (timer) clearInterval(timer);
+  timer = setInterval(() => {
+    refresh();
+  }, 5000);
+}
 
-  onUnmounted(() => {
-    clearInterval(timer);
-    timer = null;
-  });
+onMounted(() => {
+  refresh();
+  initInterval();
+});
+
+onUnmounted(() => {
+  clearInterval(timer);
+  timer = null;
+});
 </script>
 <style lang="less" scoped>
-  .spray-wrapper {
-    width: 100%;
-    height: 100%;
-    background-image: url('/@/assets/images/beltFire/baseMap.png');
-    background-size: cover;
-  }
+.spray-wrapper {
+  width: 100%;
+  height: 100%;
+  background-image: url('/@/assets/images/beltFire/baseMap.png');
+  background-size: cover;
+}
 
-  #spray3D {
-    pointer-events: all;
-  }
+#spray3D {
+  pointer-events: all;
+}
 
-  .spray-wrapper :deep(.list-item_L .list-item__icon_L) {
-    background-image: url('/@/assets/images/home-container/configurable/minehome/list-icon-file.png');
-  }
-  .spray-wrapper :deep(.list-item_N:nth-child(1)) {
-    background-image: url('/@/assets/images/home-container/configurable/minehome/list-bg-n5.png');
-  }
-  .spray-wrapper :deep(.list-item_N:nth-child(2)) {
-    background-image: url('/@/assets/images/home-container/configurable/minehome/list-bg-n6.png');
-  }
-  .company-home {
-    background: url('/@/assets/images/beltFire/baseMap.png') no-repeat center;
+.spray-wrapper :deep(.list-item_L .list-item__icon_L) {
+  background-image: url('/@/assets/images/home-container/configurable/minehome/list-icon-file.png');
+}
+.spray-wrapper :deep(.list-item_N:nth-child(1)) {
+  background-image: url('/@/assets/images/home-container/configurable/minehome/list-bg-n5.png');
+}
+.spray-wrapper :deep(.list-item_N:nth-child(2)) {
+  background-image: url('/@/assets/images/home-container/configurable/minehome/list-bg-n6.png');
+}
+.company-home {
+  background: url('/@/assets/images/beltFire/baseMap.png') no-repeat center;
+  width: 100%;
+  height: 100%;
+  color: @white;
+  position: relative;
+  .border {
     width: 100%;
-    height: 100%;
-    color: @white;
-    position: relative;
-    .border {
-      width: 100%;
-      height: 94%;
-      background: url('/@/assets/images/beltFire/mainbj.png') no-repeat;
+    height: 94%;
+    background: url('/@/assets/images/beltFire/mainbj.png') no-repeat;
+    background-size: 100% 100%;
+    margin-top: 50px;
+    .test {
+      background: url('./test.png') no-repeat;
       background-size: 100% 100%;
-      margin-top: 50px;
-      .test {
-        background: url('./test.png') no-repeat;
-        background-size: 100% 100%;
-      }
     }
   }
-</style>
+}
+</style>

+ 59 - 13
src/views/vent/home/configurable/belt/components/detail/gateBoard.vue

@@ -16,8 +16,8 @@
               <div class="door-name"
                 ><span>{{ item.strname }}</span></div
               >
-              <a-button class="door-btn" @click="oneKeyClose(index)">一键双关</a-button>
-              <a-button class="door-btn" @click="oneKeyOpen(index)">一键双开</a-button>
+              <a-button class="door-btn" @click="playAnimation('一键双开', 'sameTimeOpen', item.deviceID, item.deviceType)">一键双开</a-button>
+              <a-button class="door-btn" @click="playAnimation('一键双关', 'sameTimeClose', item.deviceID, item.deviceType)">一键双关</a-button>
             </div>
             <div class="door-header">
               <div class="info-column" v-for="(i, idx) in config.config.items" :key="idx">
@@ -44,14 +44,23 @@
       </div>
     </div>
   </div>
+  <HandleModal
+    v-if="!globalConfig?.simulatedPassword"
+    :modal-is-show="modalIsShow"
+    :modal-title="modalTitle"
+    :modal-type="modalType"
+    @handle-ok="handleOK"
+    @handle-cancel="handleCancel"
+  />
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted, defineProps, watch } from 'vue';
+import { ref, onMounted, defineProps, watch, inject } from 'vue';
 import { getFormattedText } from '../../../hooks/helper';
-// import gateSVG from '../gateSVG.vue';
+import HandleModal from '/@/views/vent/monitorManager/gateMonitor/modal.vue';
+import { message } from 'ant-design-vue';
+import { deviceControlApi } from '/@/api/vent/index';
 import gateSVG from '../../../../../monitorManager/gateMonitor/components/gateDualSVG.vue';
-import { nextTick } from 'process';
 const props = defineProps<{
   config: {
     config: {
@@ -67,6 +76,13 @@ const props = defineProps<{
     [key: string]: any;
   };
 }>();
+const modalIsShow = ref<boolean>(false); // 是否显示模态框
+const modalTitle = ref(''); // 模态框标题显示内容,根据设备操作类型决定
+const modalType = ref(''); // 模态框内容显示类型,设备操作类型
+const paramcode = ref(''); // 模态框操作代码
+const deviceID = ref('');
+const deviceType = ref('');
+const globalConfig = inject<any>('globalConfig');
 const childRefs = ref<(InstanceType<typeof gateSVG> | null)[]>([]);
 const setChildRef = (el, index) => {
   if (el) {
@@ -83,17 +99,47 @@ function monitorAnimation(selectData, index) {
   }
 }
 
-function oneKeyOpen(index) {
-  if (childRefs.value[index]) {
-    childRefs.value[index].animate(true, true, true);
+const playAnimation = (title, flag, id, type) => {
+  modalType.value = flag + '';
+  modalTitle.value = title;
+  modalIsShow.value = true;
+  deviceID.value = id;
+  deviceType.value = type;
+  console.log(deviceID.value, '1111111111');
+};
+// 控制
+function handleOK(passWord, handlerState, value?) {
+  console.log('handleOK', passWord, handlerState, value);
+  if (!passWord && !globalConfig?.simulatedPassword) {
+    message.warning('请输入密码');
+    return;
   }
+  let data = {
+    deviceid: deviceID.value,
+    devicetype: deviceType.value,
+    paramcode: handlerState,
+    password: passWord || globalConfig?.simulatedPassword,
+    value: value ? value : null,
+  };
+  deviceControlApi(data)
+    .then((res) => {
+      if (res.success) {
+        message.success('指令已下发成功!');
+      } else {
+        message.error(res.message);
+      }
+    })
+    .finally(() => {
+      handleCancel();
+    });
 }
-function oneKeyClose(index) {
-  if (childRefs.value[index]) {
-    childRefs.value[index].animate(false, false, false);
-  }
+function handleCancel() {
+  modalIsShow.value = false;
+  modalTitle.value = '';
+  modalType.value = '';
+  deviceID.value = '';
+  deviceType.value = '';
 }
-
 watch(
   () => props.data,
   (newData) => {

+ 1 - 1
src/views/vent/home/configurable/belt/configurable.data.ts

@@ -1023,7 +1023,7 @@ export const testSpary: Config[] = [
       complex_list: [],
       preset: [
         {
-          readFrom: 'deviceInfo.gate.datalist',
+          readFrom: '',
           config: {
             title: 'name',
             contents: [

+ 22 - 13
src/views/vent/home/configurable/components/belt/CameraList.vue

@@ -19,6 +19,7 @@
 <script setup lang="ts">
 import { ref, watch, onBeforeUnmount, nextTick } from 'vue';
 import { useCamera } from '/@/hooks/system/useCameraPianation';
+import { isEqual } from 'lodash-es'; // 必须用深比较
 
 const props = defineProps({
   data: { type: Array, default: () => [] },
@@ -32,29 +33,37 @@ const defaultVideoList = ref([
   { id: 2, url: '/sparyVideo.mp4' },
   { id: 3, url: '/sparyVideo.mp4' },
 ]);
+
+// 缓存上一次的摄像头列表,用于对比是否真的变化
+const lastCameraList = ref<any[]>([]);
+
 watch(
   () => props.data,
   async (newData) => {
-    removeCamera(playerRef);
-    if (playerRef.value) playerRef.value.innerHTML = '';
-    hasCamera.value = false;
-
-    await nextTick();
-
-    // 2. 提取摄像头(内部判断)
-    const list: any[] = [];
-    if (newData && newData.length > 0) {
+    const newCameraList: any[] = [];
+    if (newData?.length) {
       newData.forEach((device: any) => {
-        if (device.cameras && device.cameras.length > 0) {
+        if (device.cameras?.length) {
           device.cameras.forEach((item) => {
-            list.push({ ...item, deviceID: device.deviceID });
+            newCameraList.push({ ...item, deviceID: device.deviceID });
           });
         }
       });
     }
-    if (list.length > 0) {
+    if (isEqual(newCameraList, lastCameraList.value)) {
+      return;
+    }
+    removeCamera(playerRef);
+    if (playerRef.value) playerRef.value.innerHTML = '';
+    hasCamera.value = false;
+    await nextTick();
+    // 更新缓存
+    lastCameraList.value = newCameraList;
+
+    //渲染新摄像头
+    if (newCameraList.length > 0) {
       hasCamera.value = true;
-      await getCamera('', playerRef, ref(true), '', list);
+      await getCamera('', playerRef, ref(true), '', newCameraList);
     } else {
       hasCamera.value = false;
     }

+ 21 - 6
src/views/vent/home/configurable/components/belt/ComplexList1Belt.vue

@@ -6,7 +6,8 @@
         <div
           style="cursor: pointer"
           @click="handleItemClick(allMineItem)"
-          :class="[`list-item__content_${type}`, getBgClass(allMineItem.value), { active: allMineItem.id === activeId }]"
+          class="list-item-all"
+          :class="[`list-item__content_${type}`, getBgClass(allMineItem.value)]"
         >
           <div class="list-item__label">全矿井</div>
           <div class="list-item__value" :class="`list-item__value_${type}`">
@@ -82,7 +83,7 @@ const firstContent = computed(() => {
   return firstItem.contents[0];
 });
 
-// 全矿井项(自动继承第一条数据的 value)
+// 全矿井项
 const allMineItem = computed(() => ({
   id: 'allMine',
   label: '全矿井',
@@ -91,10 +92,8 @@ const allMineItem = computed(() => ({
   info: firstContent.value?.info,
 }));
 
-// 是否显示全矿井(有数据才显示)
+// 是否显示全矿井
 const showAllMineItem = computed(() => !!firstContent.value);
-// ==============================================
-
 // 获取背景样式
 const getBgClass = (riskLevel: string) => {
   // 统一处理 null  0
@@ -135,7 +134,8 @@ onMounted(() => {});
 @import '/@/design/theme.less';
 
 .list {
-  padding-left: 20px;
+  padding-left: 10px;
+  padding-right: 10px;
   background-repeat: no-repeat;
   position: relative;
   display: flex;
@@ -200,6 +200,21 @@ onMounted(() => {});
   z-index: 2;
   pointer-events: none;
 }
+.list-item-all {
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+  height: 35px;
+  padding: 10px 25px;
+  font-size: 14px;
+  font-weight: bold;
+  background: url('/@/assets/images/beltFire/listAll.png');
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+  z-index: 1;
+}
 .list-item__content_A.bg-lowRisk .list-item__value {
   color: #32ddff;
 }

+ 25 - 20
src/views/vent/home/configurable/components/belt/CustomTableBelt.vue

@@ -1,16 +1,11 @@
 <template>
   <div class="table__content">
     <div class="table__content_label" :class="`table__content_label_${type}`">
-      <div class="label-t" v-for="(item, index) in columns" :key="`svvhbcth-${index}`" :style="{ flexBasis }">{{ item.name }}</div>
+      <div class="label-t" v-for="(item, index) in columns" :key="`svvhbcth-${item.prop}`" :style="{ flexBasis }">{{ item.name }}</div>
     </div>
     <div ref="scrollRef" class="table__content_list" :class="`table__content_list_${type}`">
-      <!-- 🔥 修复:空数据时依然渲染一行 -->
-      <div class="table__content_list_row" v-if="data.length === 0">
-        <div class="empty-text" :style="{ flexBasis: '100%' }"> 暂无数据 </div>
-      </div>
-
-      <div class="table__content_list_row" v-else v-for="(item, index) in data" :key="`svvhbct-${index}`">
-        <div v-for="(t, i) in columns" :key="`svvhbctr-${i}`" :style="{ flexBasis }" :class="`table__content__list_item_${type}`">
+      <div class="table__content_list_row" v-for="(item, index) in showData" :key="`svvhbct-${item.id || item.code || item.name || index}`">
+        <div v-for="(t, i) in columns" :key="`svvhbctr-${t.prop}`" :style="{ flexBasis }" :class="`table__content__list_item_${type}`">
           <slot :name="t.prop" :scope="item">
             <span>
               <span
@@ -27,23 +22,22 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted, ref } from 'vue';
+import { computed, ref, watch } from 'vue';
 import _ from 'lodash';
 import { useAutoScroll } from '/@/hooks/core/useAutoScroll';
-
 let props = withDefaults(
   defineProps<{
     type: string;
     autoScroll: boolean;
     columns: { prop: string; name: string }[];
-    data: any;
+    data: any[];
     defaultValue: string;
   }>(),
   {
     type: 'B',
     autoScroll: false,
     columns: () => [],
-    data: () => ({}),
+    data: () => [],
     defaultValue: '-',
   }
 );
@@ -52,6 +46,21 @@ const scrollRef = ref(null);
 if (props.autoScroll) {
   useAutoScroll(scrollRef);
 }
+const showData = ref<any[]>([]);
+
+watch(
+  () => props.data,
+  (newData) => {
+    const list = newData || [];
+    // 空数据时显示一行“暂无数据”
+    if (list.length === 0) {
+      showData.value = [{ _empty: true }];
+    } else {
+      showData.value = list;
+    }
+  },
+  { deep: true, immediate: true }
+);
 
 const getStatusClass = (status) => {
   switch (status) {
@@ -71,11 +80,15 @@ const flexBasis = computed(() => {
 });
 
 function get(o, p) {
+  if (o._empty) return props.defaultValue;
   const d = _.get(o, p);
   return _.isNil(d) ? props.defaultValue : d === '' ? props.defaultValue : d;
 }
 
 function formatValue(obj, path) {
+  if (obj._empty) {
+    return '暂无数据';
+  }
   const value = _.get(obj, path);
   if (_.isNil(value) || value === '') {
     return props.defaultValue;
@@ -185,13 +198,5 @@ function formatValue(obj, path) {
     background-color: gray;
     box-shadow: 0 0 6px 2px rgba(105, 105, 105, 0.6);
   }
-
-  /* 空状态样式(保持表格行高度,不塌陷) */
-  .empty-text {
-    text-align: center;
-    color: #999;
-    line-height: 50px;
-    font-size: 14px;
-  }
 }
 </style>

+ 69 - 21
src/views/vent/home/configurable/components/belt/SprayControl.vue

@@ -4,8 +4,8 @@
     <div class="sensor-list">
       <!-- 按钮 -->
       <div class="control-bar" v-if="config.deviceType != 'dust'">
-        <a-button class="control-btn" @click="handleSpary(true)">启动喷淋</a-button>
-        <a-button class="control-btn" @click="handleSpary(false)">停止喷淋</a-button>
+        <a-button class="control-btn" @click="handleSpary()">启动喷淋</a-button>
+        <a-button class="control-btn" @click="handleSpary()">停止喷淋</a-button>
         <div class="switch-wrapper">
           <span class="text1">自动</span>
           <div class="toggle-switch" :class="{ 'is-on': isOn }" @click="toggleSwitch">
@@ -22,8 +22,8 @@
           <div class="group-title" v-if="config.deviceType != 'dust'">
             <a-checkbox
               class="check-btn"
-              :checked="selectedSids.includes(beltData.sid)"
-              @change="(e) => handleCheckChange(e.target.checked, beltData.sid)"
+              :checked="selectedSids.includes(beltData.deviceID)"
+              @change="(e) => handleCheckChange(e.target.checked, beltData, beltData.deviceType)"
             >
               控制勾选
             </a-checkbox>
@@ -56,11 +56,22 @@
       </div>
     </div>
   </div>
+  <HandleModal
+    v-if="!globalConfig?.simulatedPassword"
+    :modal-is-show="modalIsShow"
+    :modal-title="modalTitle"
+    :modal-type="modalType"
+    @handle-ok="handleOK"
+    @handle-cancel="handleCancel"
+  />
 </template>
 
 <script setup lang="ts">
-import { computed, onMounted, ref } from 'vue';
+import { computed, onMounted, ref, inject } from 'vue';
 import { getFormattedText } from '../../hooks/helper';
+import HandleModal from '/@/views/vent/monitorManager/gateMonitor/modal.vue';
+import { message } from 'ant-design-vue';
+import { deviceControlApi } from '/@/api/vent/index';
 
 const props = defineProps<{
   config: Array<{
@@ -82,37 +93,74 @@ const props = defineProps<{
     [key: string]: any;
   };
 }>();
-
-// --- 存储选中的 sid ---
+const modalIsShow = ref<boolean>(false); // 是否显示模态框
+const modalTitle = ref(''); // 模态框标题显示内容,根据设备操作类型决定
+const modalType = ref(''); // 模态框内容显示类型,设备操作类型
+const paramcode = ref(''); // 模态框操作代码
+const globalConfig = inject<any>('globalConfig');
+const selectType = ref('');
+// 存储选中id
 const selectedSids = ref<string[]>([]);
 const isOn = ref(false);
-// --- 处理勾选变化 ---
-const handleCheckChange = (checked: boolean, sid: string) => {
+const openGateControlModal = (customTitle?: string) => {
+  modalIsShow.value = true;
+};
+
+// 处理勾选变化
+const handleCheckChange = (checked: boolean, data, deviceType: string) => {
   if (checked) {
-    // 勾选:如果不存在则加入数组
-    if (!selectedSids.value.includes(sid)) {
-      selectedSids.value.push(sid);
+    // 勾选
+    if (!selectedSids.value.includes(data.deviceID)) {
+      selectedSids.value.push(data.deviceID);
+      selectType.value = deviceType;
     }
   } else {
-    // 取消勾选:从数组中移除
-    const index = selectedSids.value.indexOf(sid);
+    // 取消勾选
+    const index = selectedSids.value.indexOf(data.deviceID);
     if (index > -1) {
       selectedSids.value.splice(index, 1);
+      selectType.value = '';
     }
   }
-  console.log('当前选中的 SID 列表:', selectedSids.value);
 };
 const getBgClass = (index) => {
   return index % 2 === 0 ? 'bg-1' : 'bg-2';
 };
 
-const handleSpary = (state: boolean) => {
-  if (state) {
-    console.log('启动喷淋');
-  } else {
-    console.log('停止喷淋');
-  }
+const handleSpary = () => {
+  openGateControlModal();
 };
+function handleOK(passWord, handlerState, value?) {
+  console.log('handleOK', passWord, handlerState, value);
+  if (!passWord && !globalConfig?.simulatedPassword) {
+    message.warning('请输入密码');
+    return;
+  }
+  let data = {
+    deviceid: selectedSids.value.join(','),
+    devicetype: selectType.value,
+    paramcode: handlerState,
+    password: passWord || globalConfig?.simulatedPassword,
+    value: value ? value : null,
+  };
+  deviceControlApi(data)
+    .then((res) => {
+      if (res.success) {
+        message.success('指令已下发成功!');
+      } else {
+        message.error(res.message);
+      }
+    })
+    .finally(() => {
+      handleCancel();
+    });
+}
+function handleCancel() {
+  modalIsShow.value = false;
+  modalTitle.value = '';
+  modalType.value = '';
+}
+
 function toggleSwitch() {
   isOn.value = !isOn.value;
 }

+ 15 - 4
src/views/vent/home/configurable/components/belt/WarningResultList.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="warning-result-panel">
     <div class="table-container">
-      <table v-if="!!data" class="warning-table">
+      <table class="warning-table">
         <thead class="table-header">
           <tr>
             <th v-for="col in config.columns" :key="col.prop" :style="{ width: col.width }">
@@ -11,7 +11,7 @@
         </thead>
 
         <tbody class="table-body">
-          <tr v-for="(row, index) in data[config.tableReadFrom]" :key="index" class="table-row">
+          <tr v-for="(row, index) in tableList" :key="row.warnId || row.id || index" class="table-row">
             <td
               v-for="col in config.columns"
               :key="col.prop"
@@ -51,7 +51,7 @@
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref } from 'vue';
+import { ref, watch } from 'vue';
 import ExecutePlan from './ExecutePlan.vue';
 import ExecuteAdvice from './ExecuteAdvice.vue';
 const props = defineProps<{
@@ -72,9 +72,21 @@ const props = defineProps<{
     [key: string]: any;
   };
 }>();
+
 const visible = ref(false);
 const visible1 = ref(false);
 const planData = ref([]);
+const tableList = ref<any[]>([]);
+
+watch(
+  () => props.data?.[props.config.tableReadFrom],
+  (newArr) => {
+    if (!newArr) return;
+    // 只更新内容,不重建表格
+    tableList.value = newArr;
+  },
+  { deep: true, immediate: true }
+);
 function getStatusText(status) {
   const map = {
     '102': '黄色预警',
@@ -103,7 +115,6 @@ function openModel1(row) {
   planData.value = row.alarmRecords['1'];
   visible1.value = true;
 }
-onMounted(() => {});
 </script>
 
 <style scoped lang="less">