ソースを参照

Merge branch 'master' of http://39.97.59.228:8013/hrx/mky-vent-base

bobo04052021@163.com 6 日 前
親
コミット
886baef52a
57 ファイル変更、4854 行追加、5090 行削除
  1. 52 0
      public/svg-models/tun-face-2d/model.json
  2. 587 0
      public/svg-models/tun-face-2d/model.svg
  3. BIN
      src/assets/images/vent/home/selfIgnition/1-7.png
  4. BIN
      src/assets/images/vent/home/selfIgnition/2-1.png
  5. BIN
      src/assets/images/vent/home/selfIgnition/2-2.png
  6. BIN
      src/assets/images/vent/home/selfIgnition/2-3.png
  7. BIN
      src/assets/images/vent/home/selfIgnition/2-4.png
  8. BIN
      src/assets/images/vent/home/selfIgnition/2-5.png
  9. BIN
      src/assets/images/vent/home/selfIgnition/2-6.png
  10. BIN
      src/assets/images/vent/home/selfIgnition/2-7.png
  11. BIN
      src/assets/images/vent/home/selfIgnition/3-1.png
  12. 0 184
      src/devtest/svg2d-demo/index.vue
  13. 0 3
      src/devtest/svg2d-demo/main.ts
  14. 0 280
      src/devtest/svg2d-demo/models.ts
  15. 61 72
      src/views/vent/home/selfIgnition/components/FiberTemperatureChart.vue
  16. 5 11
      src/views/vent/home/selfIgnition/components/ManualView.vue
  17. 799 180
      src/views/vent/home/selfIgnition/components/MonitorView.vue
  18. 23 61
      src/views/vent/home/selfIgnition/components/OverviewView.vue
  19. 132 22
      src/views/vent/home/selfIgnition/components/PanelSelect.vue
  20. 2 3
      src/views/vent/home/selfIgnition/data.ts
  21. 155 0
      src/views/vent/home/selfIgnition/goafOverview.adapter.ts
  22. 104 0
      src/views/vent/home/selfIgnition/goafOverview.api.ts
  23. 98 34
      src/views/vent/home/selfIgnition/index.vue
  24. 43 0
      src/views/vent/home/selfIgnition/monitorDevice.api.ts
  25. 31 7
      src/views/vent/home/selfIgnition/safetyEvaluation.api.ts
  26. 0 80
      src/views/vent/monitorManager/alarmMonitor/common/PipeWarn.vue
  27. 0 898
      src/views/vent/monitorManager/alarmMonitor/common/closeWall-111.vue
  28. 11 13
      src/views/vent/monitorManager/alarmMonitor/common/closeWall.vue
  29. 0 236
      src/views/vent/monitorManager/alarmMonitor/common/echartLine1.vue
  30. 0 260
      src/views/vent/monitorManager/alarmMonitor/common/echartLine2.vue
  31. 111 93
      src/views/vent/monitorManager/alarmMonitor/common/external-gx-echart.vue
  32. 477 400
      src/views/vent/monitorManager/alarmMonitor/common/external-mb.vue
  33. 52 81
      src/views/vent/monitorManager/alarmMonitor/common/external-sgjc.vue
  34. 70 86
      src/views/vent/monitorManager/alarmMonitor/common/fireWork.vue
  35. 38 65
      src/views/vent/monitorManager/alarmMonitor/common/gasCcb.vue
  36. 141 192
      src/views/vent/monitorManager/alarmMonitor/common/internal-fire-bot.vue
  37. 22 59
      src/views/vent/monitorManager/alarmMonitor/common/internal-fire-bot1.vue
  38. 88 73
      src/views/vent/monitorManager/alarmMonitor/common/internal-fire-center.vue
  39. 24 22
      src/views/vent/monitorManager/alarmMonitor/common/internal-fire-top.vue
  40. 19 21
      src/views/vent/monitorManager/alarmMonitor/common/mainWell.vue
  41. 0 252
      src/views/vent/monitorManager/alarmMonitor/common/measurePoint copy.vue
  42. 14 19
      src/views/vent/monitorManager/alarmMonitor/common/top-area.vue
  43. 159 259
      src/views/vent/monitorManager/alarmMonitor/warn/dustWarn.vue
  44. 119 165
      src/views/vent/monitorManager/alarmMonitor/warn/fireWarn.vue
  45. 376 502
      src/views/vent/monitorManager/alarmMonitor/warn/gasWarn.vue
  46. 174 210
      src/views/vent/monitorManager/alarmMonitor/warn/ventilateWarn.vue
  47. 15 6
      src/views/vent/monitorManager/dedustMonitor/components/DedustHome.vue
  48. 15 6
      src/views/vent/monitorManager/dedustMonitor/components/DedustHomeBet.vue
  49. 101 0
      src/views/vent/monitorManager/dedustMonitor/components/dedustSVG.vue
  50. 20 0
      src/views/vent/monitorManager/dedustMonitor/dedust.data.ts
  51. 23 2
      src/views/vent/monitorManager/dedustMonitor/index.vue
  52. 33 29
      src/views/vent/monitorManager/deviceMonitor/components/device/device.data.ts
  53. 26 17
      src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.vue
  54. 103 0
      src/views/vent/monitorManager/tunFaceMonitor/components/tunFaceSVG.vue
  55. 24 8
      src/views/vent/monitorManager/tunFaceMonitor/index.vue
  56. 19 1
      src/views/vent/monitorManager/tunFaceMonitor/tunFace.data.ts
  57. 488 178
      src/views/vent/testChart/FiberTempChart.vue

+ 52 - 0
public/svg-models/tun-face-2d/model.json

@@ -0,0 +1,52 @@
+{
+  "viewBox": "0 0 1920 1080",
+  "css": "[data-anim-id] { transition: transform 3s; }",
+  "animations": [],
+  "elements": {
+    "auto_anim_0": {
+      "key": "auto_anim_0",
+      "frames": [
+        "matrix( 1, 0, 0, 1, 1157.9,431.1)",
+        "matrix( 1, 0, 0, 1, 1148.85,431.1)",
+        "matrix( 1, 0, 0, 1, 1139.75,431.1)",
+        "matrix( 1, 0, 0, 1, 1130.7,431.1)",
+        "matrix( 1, 0, 0, 1, 1121.6,431.1)",
+        "matrix( 1, 0, 0, 1, 1112.55,431.1)",
+        "matrix( 1, 0, 0, 1, 1103.45,431.1)",
+        "matrix( 1, 0, 0, 1, 1094.4,431.1)",
+        "matrix( 1, 0, 0, 1, 1085.3,431.1)",
+        "matrix( 1, 0, 0, 1, 1076.25,431.1)"
+      ]
+    },
+    "auto_anim_1": {
+      "key": "auto_anim_1",
+      "frames": [
+        "matrix( 1, 0, 0, 1, 1019.2,502.15)",
+        "matrix( 1, 0, 0, 1, 1029.8179,502.15)",
+        "matrix( 1, 0, 0, 1, 1040.4822,502.15)",
+        "matrix( 1, 0, 0, 1, 1051.1,502.15)",
+        "matrix( 1, 0, 0, 1, 1061.7179,502.15)",
+        "matrix( 1, 0, 0, 1, 1072.3821,502.15)",
+        "matrix( 1, 0, 0, 1, 1083,502.15)",
+        "matrix( 1, 0, 0, 1, 1093.6178,502.15)",
+        "matrix( 1, 0, 0, 1, 1104.2821,502.15)",
+        "matrix( 1, 0, 0, 1, 1114.9,502.15)"
+      ]
+    },
+    "auto_anim_2": {
+      "key": "auto_anim_2",
+      "frames": [
+        "matrix( 1, 0, 0, 1, 1398.35,474.95)",
+        "matrix( 1, 0, 0, 1, 1405.5,480.2)",
+        "matrix( 1, 0, 0, 1, 1412.65,485.45)",
+        "matrix( 1, 0, 0, 1, 1419.75,490.7)",
+        "matrix( 1, 0, 0, 1, 1426.9,495.95)",
+        "matrix( 1, 0, 0, 1, 1434.05,501.15)",
+        "matrix( 1, 0, 0, 1, 1441.2,506.4)",
+        "matrix( 1, 0, 0, 1, 1448.3,511.65)",
+        "matrix( 1, 0, 0, 1, 1455.45,516.9)",
+        "matrix( 1, 0, 0, 1, 1462.6,522.15)"
+      ]
+    }
+  }
+}

ファイルの差分が大きいため隠しています
+ 587 - 0
public/svg-models/tun-face-2d/model.svg


BIN
src/assets/images/vent/home/selfIgnition/1-7.png


BIN
src/assets/images/vent/home/selfIgnition/2-1.png


BIN
src/assets/images/vent/home/selfIgnition/2-2.png


BIN
src/assets/images/vent/home/selfIgnition/2-3.png


BIN
src/assets/images/vent/home/selfIgnition/2-4.png


BIN
src/assets/images/vent/home/selfIgnition/2-5.png


BIN
src/assets/images/vent/home/selfIgnition/2-6.png


BIN
src/assets/images/vent/home/selfIgnition/2-7.png


BIN
src/assets/images/vent/home/selfIgnition/3-1.png


+ 0 - 184
src/devtest/svg2d-demo/index.vue

@@ -1,184 +0,0 @@
-<template>
-  <div class="demo">
-    <h2>二维 SVG 模型手动测试({{ MODELS.length }} 个模型)</h2>
-    <div class="bar">
-      <select v-model="current">
-        <option v-for="m in MODELS" :key="m.id" :value="m.id">{{ m.label }}({{ m.id }})</option>
-      </select>
-      <button @click="reload">重置模型</button>
-      <span class="status">{{ status }}</span>
-    </div>
-
-    <div class="states">
-      <button v-for="(s, i) in currentModel?.states || []" :key="i" :class="{ active: lastState === i }" @click="trigger(i)">
-        {{ s.label }}
-      </button>
-    </div>
-
-    <div class="stage">
-      <div :id="'host-' + current" class="svg-host">
-        <component :is="currentComp" :key="'m-' + current + '-' + mountKey" ref="drvRef" :identify="'demo'" />
-      </div>
-    </div>
-
-    <pre class="readout">{{ readout }}</pre>
-  </div>
-</template>
-
-<script setup lang="ts">
-  import { computed, defineAsyncComponent, nextTick, onMounted, ref } from 'vue';
-  import { MODELS } from './models';
-
-  defineOptions({ name: 'Svg2dDemoPage' });
-
-  const current = ref(MODELS[0].id);
-  const mountKey = ref(0);
-  const status = ref('就绪');
-  const readout = ref('');
-  const lastState = ref(-1);
-  const drvRef = ref<any>(null);
-
-  const currentModel = computed(() => MODELS.find((m) => m.id === current.value));
-  const currentComp = computed(() => {
-    const m = currentModel.value;
-    return m ? defineAsyncComponent(m.comp) : null;
-  });
-
-  const hostEl = () => document.getElementById('host-' + current.value);
-  const svgOf = () => hostEl()?.querySelector('svg') as SVGSVGElement | undefined;
-
-  async function waitForSvg(timeout = 15000) {
-    const t0 = performance.now();
-    while (performance.now() - t0 < timeout) {
-      if (svgOf()) return true;
-      await new Promise((r) => setTimeout(r, 30));
-    }
-    return false;
-  }
-
-  function info() {
-    const svg = svgOf();
-    if (!svg) return '模型未加载';
-    const anims = svg.getAnimations ? svg.getAnimations() : [];
-    const playing = anims.filter((a) => a.playState === 'running').length;
-    const loopEls = svg.querySelectorAll('[class*="_Loop"], [data-zj-anim]');
-    const cls = [...svg.querySelectorAll('[class]')]
-      .map((el) => el.getAttribute('class') || '')
-      .filter((c) => /block|hidden|animate-play|scan/.test(c))
-      .slice(0, 6);
-    return [
-      `模型:${currentModel.value?.label}(${current.value})`,
-      `元素:data-anim-id ${svg.querySelectorAll('[data-anim-id]').length} 个,svg 子节点 ${svg.querySelectorAll('*').length} 个`,
-      `循环元素:${loopEls.length} 个;运行中动画:${playing}/${anims.length}`,
-      cls.length ? `状态类:${cls.join(' | ')}` : '状态类:无',
-    ].join('\n');
-  }
-
-  async function trigger(i: number) {
-    const model = currentModel.value;
-    if (!model) return;
-    const s = model.states[i];
-    lastState.value = i;
-    status.value = `已触发:${s.label}`;
-    try {
-      drvRef.value?.animate?.(...s.args);
-    } catch (e: any) {
-      status.value = `执行出错:${e?.message || e}`;
-    }
-    await new Promise((r) => setTimeout(r, 120));
-    readout.value = info();
-  }
-
-  async function reload() {
-    lastState.value = -1;
-    status.value = '重新加载…';
-    mountKey.value++;
-    await nextTick();
-    const ok = await waitForSvg();
-    status.value = ok ? '就绪' : '加载失败';
-    readout.value = info();
-  }
-
-  async function onModelChange() {
-    lastState.value = -1;
-    status.value = '加载中…';
-    await nextTick();
-    const ok = await waitForSvg();
-    status.value = ok ? '就绪' : '加载失败';
-    readout.value = info();
-  }
-
-  onMounted(onModelChange);
-</script>
-
-<style scoped>
-  .demo {
-    padding: 14px;
-    background: #0f1420;
-    color: #d7e0ee;
-    min-height: 100vh;
-    font-size: 13px;
-  }
-  h2 {
-    font-size: 15px;
-    margin: 0 0 8px;
-  }
-  .bar {
-    display: flex;
-    gap: 8px;
-    align-items: center;
-    margin-bottom: 8px;
-  }
-  select,
-  button {
-    padding: 4px 10px;
-    border-radius: 4px;
-    border: 1px solid #3d4b63;
-    background: #1b2434;
-    color: #cfd9e6;
-    font-size: 12px;
-  }
-  button {
-    cursor: pointer;
-  }
-  button.active {
-    background: #2d4b7a;
-    border-color: #4f7cc4;
-  }
-  .status {
-    color: #7fd18b;
-    font-size: 12px;
-  }
-  .states {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 8px;
-    margin-bottom: 10px;
-  }
-  .stage {
-    width: 960px;
-    height: 540px;
-    background: #0b101a;
-    border: 1px solid #24324a;
-    overflow: hidden;
-  }
-  .svg-host {
-    width: 100%;
-    height: 100%;
-  }
-  .svg-host :deep(svg) {
-    width: 100% !important;
-    height: 100% !important;
-    margin: 0 !important;
-    display: block;
-  }
-  .readout {
-    background: #111a28;
-    padding: 8px;
-    font-size: 12px;
-    line-height: 1.6;
-    color: #9fd0a8;
-    white-space: pre-wrap;
-    min-height: 70px;
-  }
-</style>

+ 0 - 3
src/devtest/svg2d-demo/main.ts

@@ -1,3 +0,0 @@
-import { createApp } from 'vue';
-import Page from './index.vue';
-createApp(Page).mount('#app');

+ 0 - 280
src/devtest/svg2d-demo/models.ts

@@ -1,280 +0,0 @@
-/* 全部已迁移模型 + 每个模型的手动动画状态按钮 */
-export interface DemoState {
-  label: string;
-  args: any[];
-}
-export interface DemoModel {
-  id: string;
-  label: string;
-  comp: () => Promise<any>;
-  states: DemoState[];
-}
-
-const gateStates: DemoState[] = [
-  { label: '全开', args: [true, true, true] },
-  { label: '全关', args: [false, false, false] },
-  { label: '前门开', args: [true, false, false] },
-  { label: '后门开', args: [false, false, true] },
-];
-const boolStates: DemoState[] = [
-  { label: '全开', args: [true, true, true] },
-  { label: '全关', args: [false, false, false] },
-];
-const progressStates = (field = 'frontArea', max = 90): DemoState[] =>
-  [0, 25, 50, 75, 100].map((p) => ({ label: `${field} ${p}%`, args: [{ [field]: (max * p) / 100 }, max] }));
-const degreeStates = (max = 180): DemoState[] => [0, 45, 90, 135, 180].map((d) => ({ label: `开度 ${d}`, args: [{ OpenDegree: d }, max] }));
-const fanStates = (v: any = '1'): DemoState[] => [
-  { label: '1号风机开', args: [{ Fan1StartStatus: v }] },
-  { label: '2号风机开', args: [{ Fan2StartStatus: v }] },
-  { label: '双机开', args: [{ Fan1StartStatus: v, Fan2StartStatus: v }] },
-  { label: '全停', args: [{}] },
-];
-const mainFanStates: DemoState[] = [
-  { label: '1号主扇开', args: [{ Fan1StartStatus: 1 }] },
-  { label: '2号主扇开', args: [{ Fan2StartStatus: 1 }] },
-  { label: '双机开', args: [{ Fan1StartStatus: 1, Fan2StartStatus: 1 }] },
-  { label: '全停', args: [{}] },
-];
-const gasStates: DemoState[] = [
-  { label: '泵1开', args: [{ pumps: { 1: true } }] },
-  { label: '泵2开', args: [{ pumps: { 2: true } }] },
-  { label: '双泵开', args: [{ pumps: { 1: true, 2: true } }] },
-  { label: '全停', args: [{ pumps: {} }] },
-  { label: '不传数据(保持)', args: [] },
-];
-const noopStates: DemoState[] = [{ label: '触发(无动画)', args: [] }];
-const scanStates: DemoState[] = [
-  { label: '向下扫描', args: ['down'] },
-  { label: '向上扫描', args: ['up'] },
-  { label: '停止', args: [''] },
-];
-const junyaStates: DemoState[] = [
-  { label: '门窗开', args: [{ windowOpen: true, airflowOpen: false }] },
-  { label: '门窗关', args: [{ windowOpen: false, airflowOpen: false }] },
-  { label: '风流开', args: [{ windowOpen: false, airflowOpen: true }] },
-  { label: '门窗+风流开', args: [{ windowOpen: true, airflowOpen: true }] },
-];
-const zhijiangStates: DemoState[] = [
-  { label: '开车', args: [{ running: true }] },
-  { label: '停车', args: [{ running: false }] },
-];
-
-export const MODELS: DemoModel[] = [
-  {
-    id: 'gas-pump-2d',
-    label: '瓦斯抽采泵站',
-    comp: () => import('/@/views/vent/monitorManager/gasPumpMonitor/components/gasPumpSVG.vue'),
-    states: gasStates,
-  },
-  {
-    id: 'gate-dual-2d',
-    label: '双扇风门',
-    comp: () => import('/@/views/vent/monitorManager/gateMonitor/components/gateDualSVG.vue'),
-    states: gateStates,
-  },
-  { id: 'gate-2d', label: '风门', comp: () => import('/@/views/vent/monitorManager/gateMonitor/components/gateSVG.vue'), states: gateStates },
-  {
-    id: 'gate-triple-2d',
-    label: '风门三道',
-    comp: () => import('/@/views/vent/monitorManager/gateMonitor/components/gateTripleSVG.vue'),
-    states: gateStates,
-  },
-  {
-    id: 'gate-xr-2d',
-    label: '风门(斜巷)',
-    comp: () => import('/@/views/vent/monitorManager/gateMonitor/components/gateXrSVG.vue'),
-    states: gateStates,
-  },
-  {
-    id: 'gate-liang-lie-2d',
-    label: '风门两列',
-    comp: () => import('/@/views/vent/monitorManager/gateMonitor/components/gateLiangLieSVG.vue'),
-    states: gateStates,
-  },
-  {
-    id: 'gate-san-dao-shen-suo-2d',
-    label: '风门三道伸缩',
-    comp: () => import('/@/views/vent/monitorManager/gateMonitor/components/gateSanDaoShenSuoSVG.vue'),
-    states: gateStates,
-  },
-  {
-    id: 'gate-ye-ya-2d',
-    label: '液压风门',
-    comp: () => import('/@/views/vent/monitorManager/gateMonitor/components/GateYeYaSVG.vue'),
-    states: gateStates,
-  },
-  {
-    id: 'gate-shrink-2d',
-    label: '风门收缩',
-    comp: () => import('/@/views/vent/monitorManager/gateMonitor/components/GateShrinkSVG.vue'),
-    states: gateStates,
-  },
-  {
-    id: 'gate-dual-aio-2d',
-    label: '风门(一体化)',
-    comp: () => import('/@/views/vent/monitorManager/gateMonitor/components/gateDualAIO.vue'),
-    states: gateStates,
-  },
-  {
-    id: 'gate-ssl-2d',
-    label: '风门(ssl)',
-    comp: () => import('/@/views/vent/monitorManager/airDoor/components/gateSVG.ssl.vue'),
-    states: gateStates,
-  },
-  {
-    id: 'gate-dual-ssl-2d',
-    label: '双扇风门(ssl)',
-    comp: () => import('/@/views/vent/monitorManager/airDoor/components/gateDualSVG.ssl.vue'),
-    states: gateStates,
-  },
-  {
-    id: 'fire-door-2d',
-    label: '防火门',
-    comp: () => import('/@/views/vent/monitorManager/fireDoorMonitor/components/fireDoorSVG.vue'),
-    states: boolStates,
-  },
-  {
-    id: 'pai-dai-hang-2d',
-    label: '皮带密闭门',
-    comp: () => import('/@/views/vent/monitorManager/fireDoorMonitor/components/pidaihangSVG.vue'),
-    states: boolStates,
-  },
-  {
-    id: 'window-small-2d',
-    label: '小风窗',
-    comp: () => import('/@/views/vent/monitorManager/windowMonitor/components/SmallWindowSVG.vue'),
-    states: progressStates(),
-  },
-  {
-    id: 'window-2d',
-    label: '风窗',
-    comp: () => import('/@/views/vent/monitorManager/windowMonitor/components/windowSVG.vue'),
-    states: progressStates(),
-  },
-  {
-    id: 'window-da-liu-ta-2d',
-    label: '大风窗(塔)',
-    comp: () => import('/@/views/vent/monitorManager/windowMonitor/components/windowDaLiuTaSVG.vue'),
-    states: progressStates(),
-  },
-  {
-    id: 'window-dai-xiao-men-2d',
-    label: '带小门风窗',
-    comp: () => import('/@/views/vent/monitorManager/windowMonitor/components/windowDaiXiaoMenSVG.vue'),
-    states: progressStates(),
-  },
-  {
-    id: 'window-door-2d',
-    label: '风窗门',
-    comp: () => import('/@/views/vent/monitorManager/windowMonitor/components/windowDoorSVG.vue'),
-    states: progressStates(),
-  },
-  {
-    id: 'window-door-blt-2d',
-    label: '风窗门(blt)',
-    comp: () => import('/@/views/vent/monitorManager/windowMonitor/components/windowDoorBltSVG.vue'),
-    states: noopStates,
-  },
-  {
-    id: 'window-dual-2d',
-    label: '双扇风窗',
-    comp: () => import('/@/views/vent/monitorManager/windowMonitor/components/windowDualSVG.vue'),
-    states: degreeStates(90),
-  },
-  {
-    id: 'window-dual-adjust-2d',
-    label: '双扇可调风窗',
-    comp: () => import('/@/views/vent/monitorManager/windowMonitor/components/windowDualAdjustSVG.vue'),
-    states: progressStates(),
-  },
-  {
-    id: 'window-dual-zd-2d',
-    label: '双扇自动风窗',
-    comp: () => import('/@/views/vent/monitorManager/windowMonitor/components/windowDualZDSVG.vue'),
-    states: degreeStates(),
-  },
-  {
-    id: 'window-liang-lie-bd-2d',
-    label: '两列(bd)风窗',
-    comp: () => import('/@/views/vent/monitorManager/windowMonitor/components/windowLiangLieBDSVG.vue'),
-    states: progressStates(),
-  },
-  {
-    id: 'window-triple-2d',
-    label: '三扇风窗',
-    comp: () => import('/@/views/vent/monitorManager/windowMonitor/components/windowTripleSVG.vue'),
-    states: progressStates(),
-  },
-  {
-    id: 'window-yi-lie-2d',
-    label: '一列风窗',
-    comp: () => import('/@/views/vent/monitorManager/windowMonitor/components/windowYiLieSVG.vue'),
-    states: progressStates(),
-  },
-  {
-    id: 'window-zd-2d',
-    label: '自动风窗',
-    comp: () => import('/@/views/vent/monitorManager/windowMonitor/components/windowZDSVG.vue'),
-    states: degreeStates(),
-  },
-  {
-    id: 'fan-local-2d',
-    label: '局部风机',
-    comp: () => import('/@/views/vent/monitorManager/fanLocalMonitor/components/fanLocalSVG.vue'),
-    states: fanStates(),
-  },
-  {
-    id: 'fan-tunnel-2d',
-    label: '风筒风机',
-    comp: () => import('/@/views/vent/monitorManager/fanLocalMonitor/components/fanTunnelSVG.vue'),
-    states: fanStates(),
-  },
-  {
-    id: 'fan-tunnel-dual-2d',
-    label: '双风筒风机',
-    comp: () => import('/@/views/vent/monitorManager/fanLocalMonitor/components/fanTunnelDualSVG.vue'),
-    states: fanStates(),
-  },
-  {
-    id: 'main-fan-2d',
-    label: '主扇',
-    comp: () => import('/@/views/vent/monitorManager/mainFanMonitor/components/mainFanSVG.vue'),
-    states: mainFanStates,
-  },
-  {
-    id: 'main-fan-sz-2d',
-    label: '主扇(sz)',
-    comp: () => import('/@/views/vent/monitorManager/mainFanMonitor/components/mainFanSZSVG.vue'),
-    states: mainFanStates,
-  },
-  {
-    id: 'main-fan-xj-2d',
-    label: '主扇(xj)',
-    comp: () => import('/@/views/vent/monitorManager/mainFanMonitor/components/mainFanXJSVG.vue'),
-    states: mainFanStates,
-  },
-  {
-    id: 'windrect-fixed-2d',
-    label: '风向(固定)',
-    comp: () => import('/@/views/vent/monitorManager/windrectMonitor/components/fixedSVG.vue'),
-    states: noopStates,
-  },
-  {
-    id: 'windrect-scan-2d',
-    label: '风向(扫描)',
-    comp: () => import('/@/views/vent/monitorManager/windrectMonitor/components/scanSVG.vue'),
-    states: scanStates,
-  },
-  {
-    id: 'junya-2d',
-    label: '均压',
-    comp: () => import('/@/views/vent/monitorManager/balancePressMonitor/components/junyaSVG.vue'),
-    states: junyaStates,
-  },
-  {
-    id: 'zhijiang-2d',
-    label: '制浆',
-    comp: () => import('/@/views/vent/monitorManager/groutMonitor/components/zhijiangSVG.vue'),
-    states: zhijiangStates,
-  },
-];

+ 61 - 72
src/views/vent/home/selfIgnition/components/FiberTemperatureChart.vue

@@ -17,9 +17,14 @@
   let resizeObserver: ResizeObserver | undefined;
 
   function buildOption(data: FiberChartData) {
-    const peakIndex = data.peakIndex;
-    const peakPercent = ((peakIndex + 0.5) / data.categories.length) * 100;
-    const labelIndexes = [0, 3, 6, 8, 11];
+    const labelStep = Math.max(1, Math.ceil(data.categories.length / 6));
+    const dense = data.solid.length > 100;
+    const values = [...data.solid, ...data.dashed].filter((value) => Number.isFinite(Number(value))).map(Number);
+    const minValue = values.length ? Math.min(...values) : 0;
+    const maxValue = values.length ? Math.max(...values) : 1000;
+    const padding = Math.max(1, (maxValue - minValue) * 0.12);
+    const chartMin = Math.max(0, Math.floor(minValue - padding));
+    const chartMax = Math.ceil(maxValue + padding);
 
     return {
       animationDuration: 800,
@@ -41,8 +46,36 @@
         left: 40,
         right: 8,
         top: 22,
-        bottom: 22,
+        bottom: 38,
       },
+      dataZoom: [
+        {
+          type: 'inside',
+          xAxisIndex: 0,
+          filterMode: 'none',
+        },
+        {
+          type: 'slider',
+          xAxisIndex: 0,
+          filterMode: 'none',
+          height: 12,
+          bottom: 2,
+          borderColor: 'transparent',
+          backgroundColor: 'rgba(9, 48, 80, 0.55)',
+          fillerColor: 'rgba(61, 246, 255, 0.16)',
+          handleStyle: { color: '#3df6ff', borderColor: '#3df6ff' },
+          moveHandleStyle: { color: '#3df6ff' },
+          textStyle: { color: '#8fc7e8', fontSize: 9 },
+          dataBackground: {
+            lineStyle: { color: 'rgba(61, 246, 255, 0.35)' },
+            areaStyle: { color: 'rgba(61, 246, 255, 0.08)' },
+          },
+          selectedDataBackground: {
+            lineStyle: { color: 'rgba(255, 179, 71, 0.65)' },
+            areaStyle: { color: 'rgba(255, 122, 26, 0.12)' },
+          },
+        },
+      ],
       xAxis: {
         type: 'category',
         boundaryGap: false,
@@ -56,14 +89,14 @@
           fontSize: 9,
           margin: 8,
           interval: 0,
-          formatter: (value: string, index: number) => (labelIndexes.includes(index) ? value : ''),
+          formatter: (value: string, index: number) => (index === 0 || index === data.categories.length - 1 || index % labelStep === 0 ? value : ''),
         },
       },
       yAxis: {
         type: 'value',
-        min: 0,
-        max: 1000,
-        interval: 200,
+        min: chartMin,
+        max: chartMax,
+        splitNumber: 5,
         axisLine: { show: false },
         axisTick: { show: false },
         axisLabel: {
@@ -92,9 +125,9 @@
           name: '光纤温度',
           type: 'line',
           data: data.solid,
-          smooth: 0.35,
-          symbol: 'circle',
-          symbolSize: 4,
+          smooth: dense ? 0 : 0.35,
+          symbol: 'none',
+          symbolSize: 0,
           itemStyle: {
             color: '#ffb347',
             borderColor: '#fff',
@@ -102,68 +135,16 @@
           },
           lineStyle: {
             color: '#ff7a1a',
-            width: 2,
-          },
-          areaStyle: {
-            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-              { offset: 0, color: 'rgba(255, 122, 26, 0.32)' },
-              { offset: 1, color: 'rgba(255, 122, 26, 0)' },
-            ]),
-          },
-          markLine: {
-            silent: true,
-            symbol: 'none',
-            label: { show: false },
-            lineStyle: {
-              color: 'rgba(255, 77, 79, 0.85)',
-              type: 'dashed',
-              width: 1,
-            },
-            data: [{ xAxis: data.categories[peakIndex] }],
-          },
-          markPoint: {
-            symbol: 'circle',
-            symbolSize: 8,
-            itemStyle: {
-              color: '#ffb347',
-              borderColor: '#fff',
-              borderWidth: 2,
-              shadowBlur: 12,
-              shadowColor: 'rgba(255, 122, 26, 0.9)',
-            },
-            label: { show: false },
-            data: [{ coord: [data.categories[peakIndex], data.solid[peakIndex]] }],
-          },
-        },
-      ],
-      graphic: [
-        {
-          type: 'text',
-          left: `${peakPercent - 8}%`,
-          top: '26%',
-          style: {
-            text: '预警确认',
-            fill: '#ffd15c',
-            fontSize: 10,
-            backgroundColor: 'rgba(255, 157, 28, 0.24)',
-            borderColor: '#ff9d1c',
-            borderWidth: 1,
-            padding: [2, 4],
-          },
-        },
-        {
-          type: 'text',
-          left: `${peakPercent - 7}%`,
-          top: '38%',
-          style: {
-            text: '立即报警',
-            fill: '#fff',
-            fontSize: 10,
-            backgroundColor: 'rgba(255, 77, 79, 0.5)',
-            borderColor: '#ff4d4f',
-            borderWidth: 1,
-            padding: [2, 4],
+            width: dense ? 1 : 2,
           },
+          areaStyle: dense
+            ? undefined
+            : {
+                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                  { offset: 0, color: 'rgba(255, 122, 26, 0.32)' },
+                  { offset: 1, color: 'rgba(255, 122, 26, 0)' },
+                ]),
+              },
         },
       ],
     };
@@ -182,6 +163,14 @@
     chart.resize();
   }
 
+  watch(
+    () => props.data,
+    (data) => {
+      chart?.setOption(buildOption(data), true);
+    },
+    { deep: true }
+  );
+
   watch(
     () => props.active,
     () => {

+ 5 - 11
src/views/vent/home/selfIgnition/components/ManualView.vue

@@ -215,12 +215,9 @@
       .filter((item) => item.code && item.options.length);
   }
 
-  function initDefaults() {
-    items.value.forEach((item) => {
-      const field = FIELD_BY_CODE[item.code];
-      if (field && !formValues[field]) {
-        formValues[field] = item.options[0]?.name || '';
-      }
+  function clearFormValues() {
+    Object.keys(formValues).forEach((key) => {
+      formValues[key] = '';
     });
   }
 
@@ -238,7 +235,6 @@
       createMessage.warning('评价指标接口异常,已使用内置指标字典');
     } finally {
       loadingOptions.value = false;
-      initDefaults();
     }
   }
 
@@ -248,8 +244,7 @@
       ...entity,
       createTime: entity.createTime || formatDateTime(new Date()),
     };
-    items.value.forEach((item) => {
-      const field = FIELD_BY_CODE[item.code];
+    Object.values(FIELD_BY_CODE).forEach((field) => {
       const value = entity[field];
       if (field && value) {
         formValues[field] = value;
@@ -259,6 +254,7 @@
 
   async function loadLatest() {
     evaluation.value = null;
+    clearFormValues();
     if (!props.manageSystemId) {
       return;
     }
@@ -268,8 +264,6 @@
       const entity = evalType.value === 2 ? data?.miningEval : data?.beforeEval;
       if (entity) {
         applyEvaluation(entity);
-      } else {
-        evaluation.value = null;
       }
     } catch {
       evaluation.value = null;

ファイルの差分が大きいため隠しています
+ 799 - 180
src/views/vent/home/selfIgnition/components/MonitorView.vue


+ 23 - 61
src/views/vent/home/selfIgnition/components/OverviewView.vue

@@ -3,7 +3,7 @@
     <!-- 左:矿井采空区概览 -->
     <ModuleCommon
       class="overview-module"
-      :show-style="panelShowStyle(20, 114, 393, 456)"
+      :show-style="panelShowStyle(20, 114, 393, 576)"
       :module-data="EMPTY_MODULE_DATA"
       module-name="矿井采空区概览"
       device-type="ignition_goaf_overview"
@@ -24,7 +24,7 @@
     <!-- 左下:安装设备情况 -->
     <ModuleCommon
       class="install-module"
-      :show-style="panelShowStyle(20, 602, 393, 456)"
+      :show-style="panelShowStyle(20, 722, 393, 336)"
       :module-data="EMPTY_MODULE_DATA"
       module-name="安装设备情况"
       device-type="ignition_device_install"
@@ -72,7 +72,6 @@
             <span class="col-seam">所属煤层</span>
             <span class="col-level">预警等级</span>
             <span class="col-safety">安全评价等级</span>
-            <span class="col-status">状态</span>
           </div>
           <div class="table-body">
             <div v-for="row in warnings" :key="row.id" class="table-row">
@@ -80,16 +79,12 @@
               <span class="col-name">{{ row.name }}</span>
               <span class="col-seam">{{ row.seam }}</span>
               <span class="col-level level-cell">
-                <img class="level-icon warning-icon" :src="warningLevelIcon" alt="" />
-                <span>{{ row.level }}</span>
+                <img v-if="row.level && row.level !== '未预警'" class="level-icon warning-icon" :src="warningLevelIcon" alt="" />
+                <span :title="row.level">{{ row.level }}</span>
               </span>
               <span class="col-safety level-cell">
-                <img class="level-icon safety-icon" :src="safetyLevelIcon" alt="" />
-                <span>{{ row.level }}</span>
-              </span>
-              <span class="col-status status-cell">
-                <i class="status-dot" :class="statusDotClass(row.status)"></i>
-                <span>{{ row.status }}</span>
+                <img v-if="showSafetyIcon(row.safetyLevel || row.level)" class="level-icon safety-icon" :src="safetyLevelIcon" alt="" />
+                <span :title="row.safetyLevel || row.level">{{ row.safetyLevel || row.level }}</span>
               </span>
             </div>
             <div v-for="n in emptyRowCount" :key="`empty-${n}`" class="table-row table-row-empty"></div>
@@ -108,8 +103,6 @@
   import bundleMonitorIcon from '@/assets/images/vent/home/selfIgnition/install-bundle-monitor.svg';
   import fiberTemperatureIcon from '@/assets/images/vent/home/selfIgnition/install-fiber-temperature.svg';
   import sealedMonitorIcon from '@/assets/images/vent/home/selfIgnition/install-sealed-monitor.svg';
-  import windSpeedIcon from '@/assets/images/vent/home/selfIgnition/install-wind-speed.svg';
-  import windVolumeIcon from '@/assets/images/vent/home/selfIgnition/install-wind-volume.svg';
   import warningLevelIcon from '@/assets/images/vent/home/selfIgnition/level-warning.svg';
   import safetyLevelIcon from '@/assets/images/vent/home/selfIgnition/level-safety.svg';
 
@@ -123,19 +116,14 @@
     束管监测: bundleMonitorIcon,
     光纤测温: fiberTemperatureIcon,
     密闭监测: sealedMonitorIcon,
-    风速传感器: windSpeedIcon,
-    风量传感器: windVolumeIcon,
   };
 
   function deviceIcon(name: string) {
     return DEVICE_ICONS[name] || '';
   }
 
-  function statusDotClass(status: string) {
-    if (status === '回采中') return 'is-running';
-    if (status === '回撤中') return 'is-retreating';
-    if (status === '停产') return 'is-stopped';
-    return 'is-closed';
+  function showSafetyIcon(level: string | undefined) {
+    return !!level && level !== '安全' && level !== '未评价';
   }
 
   // 表体固定 5 行视口,数据不足时用空行补足;超过 5 行时在表体内部滚动
@@ -157,6 +145,7 @@
   .overview-panel {
     display: flex;
     flex-direction: column;
+    justify-content: space-evenly;
     gap: 24px;
     height: 100%;
     padding: 16px 10px;
@@ -238,8 +227,9 @@
   .install-panel {
     display: flex;
     flex-direction: column;
+    justify-content: space-evenly;
     height: 100%;
-    padding: 12px 0 8px;
+    padding: 8px 0;
     box-sizing: border-box;
     overflow-x: hidden;
     overflow-y: auto;
@@ -419,36 +409,37 @@
   }
 
   .col-index {
-    width: 8%;
+    width: 9%;
   }
 
   .col-name {
-    width: 21%;
+    width: 24%;
   }
 
   .col-seam {
-    width: 17%;
+    width: 18%;
   }
 
   .col-level {
-    width: 18%;
+    width: 24%;
   }
 
   .col-safety {
-    width: 18%;
+    width: 25%;
   }
 
-  .col-status {
-    width: 18%;
-  }
-
-  .level-cell,
-  .status-cell {
+  .level-cell {
     display: flex;
     align-items: center;
     justify-content: center;
     gap: 7px;
     min-width: 0;
+
+    > span {
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
   }
 
   .level-icon {
@@ -466,35 +457,6 @@
     height: 15px;
   }
 
-  .status-cell {
-    gap: 8px;
-  }
-
-  .status-dot {
-    flex: none;
-    width: 8px;
-    height: 8px;
-    border-radius: 50%;
-    background: var(--dot-color);
-    box-shadow: 0 0 8px var(--dot-color);
-
-    &.is-running {
-      --dot-color: #3df6a3;
-    }
-
-    &.is-retreating {
-      --dot-color: #3fa9ff;
-    }
-
-    &.is-stopped {
-      --dot-color: #a6a6a6;
-    }
-
-    &.is-closed {
-      --dot-color: #71808f;
-    }
-  }
-
   /* 表格面板较宽,使用短边框图拉伸,保证顶部/底部装饰点居中 */
   :deep(.table-module .box1-top) {
     --image-box1-top: url('@/assets/images/vent/border/box1-top.png');

+ 132 - 22
src/views/vent/home/selfIgnition/components/PanelSelect.vue

@@ -7,33 +7,34 @@
     tabindex="0"
     :aria-expanded="open"
     @click.stop="toggle"
-    @keydown.enter.prevent="toggle"
-    @keydown.space.prevent="toggle"
-    @keydown.esc="open = false"
+    @keydown="handleKeydown"
   >
     <span class="ignition-select-value" :title="selectedLabel">{{ selectedLabel }}</span>
     <i class="ignition-select-arrow"></i>
     <Transition name="ignition-drop">
-      <div v-if="open" class="ignition-select-dropdown">
+      <div v-if="open" ref="dropdownRef" class="ignition-select-dropdown" @click.stop>
+        <input v-if="options.length > 5" ref="searchRef" v-model="query" class="ignition-select-search" type="text" placeholder="搜索" @click.stop />
         <div
-          v-for="option in options"
+          v-for="(option, index) in filteredOptions"
           :key="option.value"
           class="ignition-select-option"
           role="option"
           :title="option.label"
           :aria-selected="option.value === modelValue"
-          :class="{ active: option.value === modelValue }"
+          :class="{ active: option.value === modelValue, highlighted: index === highlightIndex }"
+          @mouseenter="highlightIndex = index"
           @click.stop="choose(option)"
         >
           {{ option.label }}
         </div>
+        <div v-if="!filteredOptions.length" class="ignition-select-empty">无匹配项</div>
       </div>
     </Transition>
   </div>
 </template>
 
 <script lang="ts" setup>
-  import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
+  import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
   import type { ManualOption } from '../data';
 
   const props = defineProps<{
@@ -46,13 +47,27 @@
   }>();
 
   const rootRef = ref<HTMLElement>();
+  const dropdownRef = ref<HTMLElement>();
+  const searchRef = ref<HTMLInputElement>();
   const open = ref(false);
   const dropUp = ref(false);
+  const query = ref('');
+  const highlightIndex = ref(-1);
 
   const selectedLabel = computed(() => {
     return props.options.find((option) => option.value === props.modelValue)?.label ?? '';
   });
 
+  const filteredOptions = computed(() => {
+    const keyword = query.value.trim().toLowerCase();
+    if (!keyword) return props.options;
+    return props.options.filter((option) => option.label.toLowerCase().includes(keyword));
+  });
+
+  watch(query, () => {
+    highlightIndex.value = filteredOptions.value.length ? 0 : -1;
+  });
+
   function findScrollParent(element: HTMLElement | undefined): HTMLElement | null {
     let node = element?.parentElement;
     while (node) {
@@ -65,30 +80,91 @@
     return null;
   }
 
+  function updateDropDirection() {
+    const rect = rootRef.value?.getBoundingClientRect();
+    if (!rect) return;
+    const scrollParent = findScrollParent(rootRef.value);
+    const bounds = scrollParent ? scrollParent.getBoundingClientRect() : { top: 0, bottom: window.innerHeight };
+    const spaceBelow = bounds.bottom - rect.bottom;
+    const spaceAbove = rect.top - bounds.top;
+    dropUp.value = spaceBelow < 220 && spaceAbove > spaceBelow;
+  }
+
+  function openDropdown() {
+    open.value = true;
+    query.value = '';
+    highlightIndex.value = Math.max(
+      0,
+      props.options.findIndex((option) => option.value === props.modelValue)
+    );
+    nextTick(() => {
+      updateDropDirection();
+      if (props.options.length > 5) {
+        searchRef.value?.focus();
+      }
+    });
+  }
+
+  function closeDropdown() {
+    open.value = false;
+    query.value = '';
+    highlightIndex.value = -1;
+  }
+
   function toggle() {
-    open.value = !open.value;
-    if (open.value) {
-      nextTick(() => {
-        const rect = rootRef.value?.getBoundingClientRect();
-        if (!rect) return;
-        const scrollParent = findScrollParent(rootRef.value);
-        const bounds = scrollParent ? scrollParent.getBoundingClientRect() : { top: 0, bottom: window.innerHeight };
-        const spaceBelow = bounds.bottom - rect.bottom;
-        const spaceAbove = rect.top - bounds.top;
-        dropUp.value = spaceBelow < 220 && spaceAbove > spaceBelow;
-      });
-    }
+    if (open.value) closeDropdown();
+    else openDropdown();
   }
 
   function choose(option: ManualOption) {
     emit('update:modelValue', option.value);
-    open.value = false;
+    closeDropdown();
+  }
+
+  function moveHighlight(step: number) {
+    const list = filteredOptions.value;
+    if (!list.length) {
+      highlightIndex.value = -1;
+      return;
+    }
+    highlightIndex.value = (highlightIndex.value + step + list.length) % list.length;
+    nextTick(() => {
+      const target = dropdownRef.value?.querySelector('.ignition-select-option.highlighted');
+      target?.scrollIntoView({ block: 'nearest' });
+    });
+  }
+
+  function handleKeydown(event: KeyboardEvent) {
+    if (event.key === 'Escape') {
+      closeDropdown();
+      return;
+    }
+
+    if (!open.value) {
+      if (event.key === 'Enter' || event.key === 'ArrowDown') {
+        event.preventDefault();
+        openDropdown();
+      }
+      return;
+    }
+
+    if (event.key === 'ArrowDown') {
+      event.preventDefault();
+      moveHighlight(1);
+    } else if (event.key === 'ArrowUp') {
+      event.preventDefault();
+      moveHighlight(-1);
+    } else if (event.key === 'Enter') {
+      event.preventDefault();
+      const option = filteredOptions.value[highlightIndex.value];
+      if (option) choose(option);
+    }
   }
 
   function handleDocumentClick(event: MouseEvent) {
     if (!open.value) return;
     if (rootRef.value && !rootRef.value.contains(event.target as Node)) {
-      open.value = false;
+      closeDropdown();
     }
   }
 
@@ -179,6 +255,35 @@
     bottom: calc(100% + 4px);
   }
 
+  .ignition-select-search {
+    width: 100%;
+    height: 26px;
+    margin-bottom: 4px;
+    padding: 0 8px;
+    box-sizing: border-box;
+    border: 1px solid rgba(61, 246, 255, 0.35);
+    border-radius: 3px;
+    outline: none;
+    background: rgba(5, 28, 48, 0.85);
+    color: #fff;
+    font-size: 12px;
+
+    &::placeholder {
+      color: rgba(169, 220, 244, 0.65);
+    }
+
+    &:focus {
+      border-color: rgba(61, 246, 255, 0.8);
+    }
+  }
+
+  .ignition-select-empty {
+    padding: 8px;
+    color: #8fc7e8;
+    font-size: 12px;
+    text-align: center;
+  }
+
   .ignition-select-option {
     min-height: 30px;
     padding: 5px 8px;
@@ -189,7 +294,8 @@
     word-break: break-all;
     cursor: pointer;
 
-    &:hover {
+    &:hover,
+    &.highlighted {
       color: #fff;
       background: rgba(61, 246, 255, 0.16);
     }
@@ -198,6 +304,10 @@
       color: #3df6ff;
       background: rgba(61, 246, 255, 0.1);
     }
+
+    &.active.highlighted {
+      background: rgba(61, 246, 255, 0.22);
+    }
   }
 
   .ignition-drop-enter-active,

+ 2 - 3
src/views/vent/home/selfIgnition/data.ts

@@ -29,7 +29,8 @@ export interface WarningRow {
   name: string;
   seam: string;
   level: string;
-  status: string;
+  safetyLevel?: string;
+  status?: string;
 }
 
 export interface AlertItem {
@@ -110,8 +111,6 @@ export const INSTALLED_DEVICES: DeviceInstallItem[] = [
   { name: '束管监测', installed: 15, running: 13 },
   { name: '光纤测温', installed: 15, running: 13 },
   { name: '密闭监测', installed: 15, running: 13 },
-  { name: '风速传感器', installed: 15, running: 13 },
-  { name: '风量传感器', installed: 15, running: 13 },
 ];
 
 export const OVERALL_WARNING: OverallWarning = {

+ 155 - 0
src/views/vent/home/selfIgnition/goafOverview.adapter.ts

@@ -0,0 +1,155 @@
+import type { DeviceInstallItem, GoafOverviewItem, WarningRow } from './data';
+import type {
+  DeviceKindStatistic,
+  DeviceStatistics,
+  DeviceTypeStatistic,
+  GoafCountOverview,
+  GoafOverviewSummary,
+  SafetyEvaluationItem,
+  SensorAlarmItem,
+} from './goafOverview.api';
+
+export interface OverviewSummaryViewData {
+  overview: GoafOverviewItem[];
+  devices: DeviceInstallItem[];
+  warnings: WarningRow[];
+}
+
+interface DeviceBucket {
+  name: string;
+  typeKeywords: string[];
+  kindKeywords: string[];
+}
+
+const DEVICE_BUCKETS: DeviceBucket[] = [
+  { name: '束管监测', typeKeywords: ['束管', 'bundletube'], kindKeywords: ['束管', 'bundletube'] },
+  { name: '光纤测温', typeKeywords: ['光纤', 'fiber'], kindKeywords: ['光纤', 'fiber'] },
+  { name: '密闭监测', typeKeywords: ['密闭', 'sealed'], kindKeywords: ['密闭', 'sealed'] },
+];
+
+const ALARM_LEVEL_TEXT: Record<number, string> = {
+  104: '高风险',
+  103: '中等风险',
+  102: '低风险',
+  101: '一般风险',
+};
+
+function toNumber(value: unknown, fallback = 0) {
+  const result = Number(value);
+  return Number.isFinite(result) ? result : fallback;
+}
+
+function includesAny(text: string, keywords: string[]) {
+  const source = text.toLowerCase();
+  return keywords.some((keyword) => source.includes(keyword.toLowerCase()));
+}
+
+function buildOverview(counts: GoafCountOverview | undefined, fallback: GoafOverviewItem[]): GoafOverviewItem[] {
+  if (!counts) return fallback;
+
+  const fallbackMap = new Map(fallback.map((item) => [item.label, item.value]));
+  const pick = (value: number | undefined, label: string) =>
+    value === null || value === undefined ? (fallbackMap.get(label) ?? 0) : toNumber(value);
+
+  return [
+    { label: '矿井采空区总数', value: pick(counts.totalCount, '矿井采空区总数') },
+    { label: '已封闭采空区数', value: pick(counts.closedCount, '已封闭采空区数') },
+    { label: '回撤工作面采空区数', value: pick(counts.withdrawCount, '回撤工作面采空区数') },
+    { label: '回采工作面采空区数', value: pick(counts.miningCount, '回采工作面采空区数') },
+    { label: '停产工作面采空区数', value: pick(counts.stoppedCount, '停产工作面采空区数') },
+  ];
+}
+
+function sumTypeStatistics(statistics: DeviceTypeStatistic[], keywords: string[]) {
+  return statistics.reduce((total, item) => {
+    const text = `${item.deviceTypeName || ''} ${item.deviceType || ''}`;
+    return includesAny(text, keywords) ? total + toNumber(item.count) : total;
+  }, 0);
+}
+
+function sumKindStatistics(statistics: DeviceKindStatistic[], keywords: string[]) {
+  return statistics.reduce((total, item) => {
+    const text = `${item.deviceKindName || ''} ${item.deviceKind || ''}`;
+    return includesAny(text, keywords) ? total + toNumber(item.count) : total;
+  }, 0);
+}
+
+function buildDevices(statistics: DeviceStatistics | undefined, fallback: DeviceInstallItem[]): DeviceInstallItem[] {
+  if (!statistics) return fallback;
+
+  const typeStatistics = statistics.typeStatistics || [];
+  const kindStatistics = statistics.kindStatistics || [];
+  if (!typeStatistics.length && !kindStatistics.length) return fallback;
+
+  return DEVICE_BUCKETS.map((bucket) => {
+    const typeCount = sumTypeStatistics(typeStatistics, bucket.typeKeywords);
+    const count = typeCount || sumKindStatistics(kindStatistics, bucket.kindKeywords);
+    return {
+      name: bucket.name,
+      installed: count,
+      // 汇总接口只提供设备接入数量,运行数量暂按接入数量展示
+      running: count,
+    };
+  });
+}
+
+function normalizeAlarmLevel(alarm: SensorAlarmItem | null) {
+  if (!alarm) return '未预警';
+
+  const name = alarm.alarmLevelName?.trim();
+  if (name) return name;
+
+  const code = Number(alarm.alarmLevel);
+  return ALARM_LEVEL_TEXT[code] || '未预警';
+}
+
+function pickAlarm(sceneId: string, alarms: SensorAlarmItem[]) {
+  const matched = alarms.filter((alarm) => String(alarm.sysId ?? '') === sceneId && alarm.isTriggered === true);
+  if (!matched.length) return null;
+
+  return matched.reduce((current, alarm) => (toNumber(alarm.alarmLevel) > toNumber(current.alarmLevel) ? alarm : current));
+}
+
+function normalizeRiskLevel(level: string | undefined) {
+  if (!level) return '未评价';
+  return level === '中风险' ? '中等风险' : level;
+}
+
+function pickSafetyLevel(sceneId: string, evaluations: SafetyEvaluationItem[]) {
+  const matched = evaluations
+    .filter((item) => String(item.manageSystemId ?? item.sysId ?? '') === sceneId)
+    .sort((a, b) => {
+      const timeCompare = (b.updateTime || b.latestEvalTime || '').localeCompare(a.updateTime || a.latestEvalTime || '');
+      return timeCompare || toNumber(b.evalType) - toNumber(a.evalType);
+    });
+  return normalizeRiskLevel(matched[0]?.currentRiskLevel);
+}
+
+function buildWarnings(summary: GoafOverviewSummary): WarningRow[] {
+  const scenes = summary.goafCountOverview?.goafList || [];
+  const alarms = summary.goafAlarmSummary?.sensorAlarmList || [];
+  const evaluations = summary.goafAlarmSummary?.safetyEvaluationList || [];
+
+  return scenes.map((scene, index) => {
+    const sceneId = String(scene.id ?? '');
+    const alarm = pickAlarm(sceneId, alarms);
+    return {
+      id: index + 1,
+      name: scene.systemName || scene.name || '--',
+      seam: scene.coalSeam || scene.properties?.coalSeam || '--',
+      level: normalizeAlarmLevel(alarm),
+      safetyLevel: pickSafetyLevel(sceneId, evaluations),
+    };
+  });
+}
+
+/** 将综合汇总接口转换为系统概览页面的展示数据 */
+export function mapGoafSummaryData(summary: GoafOverviewSummary | undefined, fallback: OverviewSummaryViewData): OverviewSummaryViewData {
+  if (!summary) return fallback;
+
+  return {
+    overview: buildOverview(summary.goafCountOverview, fallback.overview),
+    devices: buildDevices(summary.deviceStatistics, fallback.devices),
+    warnings: buildWarnings(summary),
+  };
+}

+ 104 - 0
src/views/vent/home/selfIgnition/goafOverview.api.ts

@@ -0,0 +1,104 @@
+import { defHttp } from '/@/utils/http/axios';
+
+enum Api {
+  summary = '/safety/goafOverview/summary',
+}
+
+export interface GoafSummaryScene {
+  id?: string | number;
+  systemName?: string;
+  name?: string;
+  strType?: string;
+  strTypeName?: string;
+  coalSeam?: string;
+  status?: number;
+  sysOrgCode?: string;
+  createTime?: string;
+  properties?: Record<string, any>;
+}
+
+export interface GoafCountOverview {
+  totalCount?: number;
+  closedCount?: number;
+  withdrawCount?: number;
+  miningCount?: number;
+  stoppedCount?: number;
+  runningCount?: number;
+  goafList?: GoafSummaryScene[];
+}
+
+export interface DeviceKindStatistic {
+  deviceKind?: string;
+  deviceKindName?: string;
+  count?: number;
+}
+
+export interface DeviceTypeStatistic {
+  deviceType?: string;
+  deviceTypeName?: string;
+  deviceKind?: string;
+  count?: number;
+}
+
+export interface DeviceStatistics {
+  totalDeviceCount?: number;
+  kindStatistics?: DeviceKindStatistic[];
+  typeStatistics?: DeviceTypeStatistic[];
+}
+
+export interface TriggerDevice {
+  managesysAlarmId?: string;
+  deviceId?: string | number;
+  deviceName?: string;
+  devicePos?: string;
+  deviceType?: string;
+  monitorId?: string | number;
+  monitorName?: string;
+  fmin?: number;
+  fmax?: number;
+  currentValue?: string | number | Record<string, any>[];
+  isExceeded?: boolean;
+}
+
+export interface SensorAlarmItem {
+  sysId?: string | number;
+  sysName?: string;
+  alarmId?: string | number;
+  alarmName?: string;
+  systemType?: string;
+  alarmLevel?: number;
+  alarmLevelName?: string;
+  measureRemark?: string;
+  handSug?: string;
+  isTriggered?: boolean;
+  autoContent?: string;
+  alarmTime?: string;
+  triggerDevices?: TriggerDevice[];
+}
+
+export interface SafetyEvaluationItem {
+  evalId?: string | number;
+  manageSystemId?: string | number;
+  sysId?: string | number;
+  workfaceName?: string;
+  evalType?: number;
+  evalTypeName?: string;
+  latestTotalScore?: number;
+  currentRiskLevel?: string;
+  updateTime?: string;
+  latestEvalTime?: string;
+}
+
+export interface GoafAlarmSummary {
+  sensorAlarmList?: SensorAlarmItem[];
+  safetyEvaluationList?: SafetyEvaluationItem[];
+}
+
+export interface GoafOverviewSummary {
+  goafCountOverview?: GoafCountOverview;
+  deviceStatistics?: DeviceStatistics;
+  goafAlarmSummary?: GoafAlarmSummary;
+}
+
+/** 矿井采空区综合数据汇总:概览数量、设备统计与分级预警 */
+export const getGoafOverviewSummary = (params?: Record<string, any>) => defHttp.get({ url: Api.summary, params });

+ 98 - 34
src/views/vent/home/selfIgnition/index.vue

@@ -16,7 +16,6 @@
 
       <!-- 设备监测/人工录入共用的场景选择 -->
       <div v-if="activeTab > 0" class="scene-toolbar">
-        <span class="scene-toolbar-label">场景选择:</span>
         <PanelSelect v-model="manageSystemId" :options="sceneOptions" />
       </div>
 
@@ -47,6 +46,8 @@
   import PanelSelect from './components/PanelSelect.vue';
   import { createSelfIgnitionData } from './data';
   import { getSafetySceneList } from './safetyEvaluation.api';
+  import { getGoafOverviewSummary } from './goafOverview.api';
+  import { mapGoafSummaryData } from './goafOverview.adapter';
   import { FALLBACK_SCENE } from './evaluation.config';
   import type { SafetyScene } from './evaluation.config';
 
@@ -63,6 +64,7 @@
 
   let resizeObserver: ResizeObserver | undefined;
   let dataInterval: number | undefined;
+  let summaryLoading = false;
 
   const isEmbed = computed(() => !!route.query.embed);
 
@@ -100,28 +102,69 @@
     scaleY.value = fitScale / outerScaleY;
   }
 
-  /** 数据接口就绪后,在这里替换成接口请求 */
-  function refresh() {
-    data.value = createSelfIgnitionData();
+  /** 概览数据来自采空区综合汇总接口;接口异常时保留现有兜底数据 */
+  async function refresh() {
+    if (summaryLoading) return;
+    summaryLoading = true;
+    try {
+      const res: any = await getGoafOverviewSummary();
+      const summary = res?.result ?? res;
+      if (!summary || typeof summary !== 'object') return;
+      const mapped = mapGoafSummaryData(summary, createSelfIgnitionData());
+      data.value = { ...data.value, ...mapped };
+    } catch {
+      // 保持上一次成功数据,避免接口短暂异常导致页面回退为模拟数据
+    } finally {
+      summaryLoading = false;
+    }
+  }
+
+  function extractSceneRows(res: any): any[] {
+    const payload = res?.result ?? res;
+    if (Array.isArray(payload)) return payload;
+    if (Array.isArray(payload?.records)) return payload.records;
+    if (Array.isArray(payload?.list)) return payload.list;
+    if (Array.isArray(payload?.sysInfo)) return payload.sysInfo;
+    if (payload?.sysInfo) return [payload.sysInfo];
+    if (Array.isArray(payload?.data)) return payload.data;
+    return [];
+  }
+
+  function toSafetyScene(record: any): SafetyScene {
+    return {
+      id: String(record.id ?? record.deviceID ?? record.sysId ?? ''),
+      systemname: record.systemname || record.systemName || record.name || record.sysname || record.strname || record.strinstallpos || '未命名场景',
+    };
+  }
+
+  async function loadScenesFromSummary(): Promise<SafetyScene[]> {
+    try {
+      const res: any = await getGoafOverviewSummary();
+      const summary = res?.result ?? res;
+      const goafList = summary?.goafCountOverview?.goafList || [];
+      return goafList.map(toSafetyScene).filter((scene) => !!scene.id);
+    } catch {
+      return [];
+    }
   }
 
   async function loadScenes() {
+    let list: SafetyScene[] = [];
     try {
-      const res: any = await getSafetySceneList();
-      let records = res?.records || res?.result?.records || [];
-      // 测试环境没有采空区场景时,回退到全部场景
-      if (!records.length) {
-        const allRes: any = await getSafetySceneList({ pagetype: 'normal' });
-        records = allRes?.records || allRes?.result?.records || [];
-      }
-      const list: SafetyScene[] = records.map((record: any) => ({
-        id: String(record.id ?? record.deviceID ?? ''),
-        systemname: record.systemname || record.name || record.sysname || '未命名场景',
-      }));
-      scenes.value = list.length ? list : [{ ...FALLBACK_SCENE }];
+      const res: any = await getSafetySceneList({ pagetype: 'normal' });
+      list = extractSceneRows(res)
+        .map(toSafetyScene)
+        .filter((scene) => !!scene.id);
     } catch {
-      scenes.value = [{ ...FALLBACK_SCENE }];
+      list = [];
     }
+
+    // /monitor/system 未返回列表时,使用综合汇总中的采空区场景兜底
+    if (!list.length) {
+      list = await loadScenesFromSummary();
+    }
+
+    scenes.value = list.length ? list : [{ ...FALLBACK_SCENE }];
     const queryScene = (route.query.systemID || route.query.manageSystemId || route.query.sceneId) as string;
     if (queryScene && !scenes.value.some((scene) => scene.id === queryScene)) {
       scenes.value.unshift({ id: queryScene, systemname: '当前场景' });
@@ -131,11 +174,16 @@
 
   function initDataInterval() {
     clearInterval(dataInterval);
-    dataInterval = window.setInterval(refresh, 60000);
+    dataInterval = window.setInterval(() => {
+      if (!document.hidden && activeTab.value === 0) {
+        void refresh();
+      }
+    }, 60000);
   }
 
   onMounted(() => {
     void loadScenes();
+    void refresh();
     initDataInterval();
     updateScale();
     if (typeof ResizeObserver !== 'undefined' && rootRef.value) {
@@ -247,28 +295,44 @@
   .scene-toolbar {
     position: absolute;
     left: 20px;
-    top: 64px;
+    top: 76px;
     z-index: 6;
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    width: 353px;
-    height: 68px;
+    width: 357px;
+    height: 52px;
+  }
+
+  :deep(.scene-toolbar .ignition-select) {
+    width: 100%;
+    height: 52px;
+    padding: 0 48px 0 86px;
     box-sizing: border-box;
+    border: none;
+    border-radius: 0;
+    background: url('@/assets/images/vent/home/selfIgnition/1-7.png') no-repeat center / 100% 100%;
+    font-size: 17px;
+    letter-spacing: 0.5px;
+
+    &:hover,
+    &.open {
+      border-color: transparent;
+      box-shadow: none;
+    }
   }
 
-  .scene-toolbar-label {
-    flex: none;
-    color: #d9f7ff;
-    font-size: 15px;
-    white-space: nowrap;
+  :deep(.scene-toolbar .ignition-select-arrow) {
+    right: 30px;
+    width: 12px;
+    height: 12px;
+    margin-top: -7px;
+    border: none;
+    border-right: 2px solid #eaffff;
+    border-bottom: 2px solid #eaffff;
+    transform: rotate(45deg);
   }
 
-  :deep(.scene-toolbar .ignition-select) {
-    flex: 1;
-    min-width: 0;
-    width: auto;
-    height: 32px;
+  :deep(.scene-toolbar .ignition-select.open .ignition-select-arrow) {
+    margin-top: -3px;
+    transform: rotate(225deg);
   }
 
   :deep(.box1-top .title) {

+ 43 - 0
src/views/vent/home/selfIgnition/monitorDevice.api.ts

@@ -0,0 +1,43 @@
+import { defHttp } from '/@/utils/http/axios';
+
+enum Api {
+  deviceList = '/monitor/device',
+}
+
+export interface MonitorDeviceReadData {
+  o2val?: string | number;
+  coval?: string | number;
+  co2val?: string | number;
+  gasval?: string | number;
+  ch2val?: string | number;
+  chval?: string | number;
+  temperature?: string | number;
+  tTime?: string;
+  isRun?: string | number;
+  [key: string]: any;
+}
+
+export interface MonitorDevice {
+  deviceID?: string | number;
+  strname?: string;
+  strinstallpos?: string;
+  deviceType?: string;
+  typeName?: string;
+  readTime?: string;
+  readData?: MonitorDeviceReadData;
+  [key: string]: any;
+}
+
+export interface MonitorDeviceGroup {
+  type?: string;
+  typeName?: string;
+  datalist?: MonitorDevice[];
+  [key: string]: any;
+}
+
+export interface MonitorDeviceResult {
+  msgTxt?: MonitorDeviceGroup[];
+}
+
+/** 按设备类型获取实时监测设备数据 */
+export const getMonitorDeviceList = (params: Record<string, any>) => defHttp.post({ url: Api.deviceList, params });

+ 31 - 7
src/views/vent/home/selfIgnition/safetyEvaluation.api.ts

@@ -10,7 +10,7 @@ enum Api {
   update = '/safety/safetyEvaluation/updateSafetyEvaluation',
   remove = '/safety/safetyEvaluation/deleteSafetyEvaluation',
   getByScene = '/safety/safetyEvaluation/getByScene',
-  sceneList = '/safety/ventanalyManageSystem/list',
+  sceneList = '/monitor/system',
 }
 
 /** 22 项评价指标、权重和选项字典 */
@@ -19,8 +19,19 @@ export const getSafetyItemOptions = () => defHttp.get({ url: Api.itemOptions });
 /** 仅计算,不落库 */
 export const calculateSafetyEvaluation = (params: Record<string, any>) => defHttp.post({ url: Api.calculate, params });
 
+const sceneEvaluationCache = new Map<string, { time: number; promise: Promise<any> }>();
+
+function invalidateSceneEvaluation(sceneId?: string) {
+  if (sceneId) sceneEvaluationCache.delete(String(sceneId));
+  else sceneEvaluationCache.clear();
+}
+
 /** 计算并入库 */
-export const saveSafetyEvaluation = (params: Record<string, any>) => defHttp.post({ url: Api.calculateAndSave, params });
+export const saveSafetyEvaluation = (params: Record<string, any>) =>
+  defHttp.post({ url: Api.calculateAndSave, params }).then((res) => {
+    invalidateSceneEvaluation(params?.manageSystemId);
+    return res;
+  });
 
 /** 用 Excel 示例数据计算并入库,evalType: 1=采前 2=采中 */
 export const calculateSampleSafetyEvaluation = (evalType: number) =>
@@ -38,9 +49,22 @@ export const updateSafetyEvaluation = (params: Record<string, any>) => defHttp.p
 /** 按 id 删除评价记录 */
 export const deleteSafetyEvaluation = (id: string) => defHttp.get({ url: Api.remove, params: { id } });
 
-/** 按场景查询最新采前/采中评价 */
-export const getSafetyEvaluationByScene = (sceneId: string) => defHttp.get({ url: Api.getByScene, params: { sceneId } });
+/** 按场景查询最新采前/采中评价;短时间内的重复请求复用同一个结果 */
+export const getSafetyEvaluationByScene = (sceneId: string) => {
+  const key = String(sceneId);
+  const now = Date.now();
+  const cached = sceneEvaluationCache.get(key);
+  if (cached && now - cached.time < 2000) return cached.promise;
+
+  const promise = defHttp.get({ url: Api.getByScene, params: { sceneId } });
+  sceneEvaluationCache.set(key, { time: now, promise });
+  promise.catch(() => {
+    if (sceneEvaluationCache.get(key)?.promise === promise) {
+      sceneEvaluationCache.delete(key);
+    }
+  });
+  return promise;
+};
 
-/** 采空区场景列表,用于评价对象绑定;不传参数时默认查采空区场景 */
-export const getSafetySceneList = (params?: Record<string, any>) =>
-  defHttp.get({ url: Api.sceneList, params: params ?? { strtype: 'sys_surface_caimei', pagetype: 'normal' } });
+/** 采空区场景列表,用于评价对象绑定 */
+export const getSafetySceneList = (params?: Record<string, any>) => defHttp.post({ url: Api.sceneList, params: params ?? {} });

+ 0 - 80
src/views/vent/monitorManager/alarmMonitor/common/PipeWarn.vue

@@ -1,80 +0,0 @@
-<template>
-  <div class="warnTarget">
-    <div class="top-section">
-      <a-table :columns="columns" :data-source="tableData" bordered :pagination="false">
-        <template #bodyCell="{ column, text }">
-          <template v-if="column.dataIndex === 'name'">
-            <a href="javascript:;">{{ text }}</a>
-          </template>
-        </template>
-      </a-table>
-    </div>
-    <div class="bottom-section"></div>
-  </div>
-</template>
-
-<script setup lang="ts">
-import { ref, reactive } from 'vue';
-
-let columns = reactive([
-  {
-    title: '序号',
-    width: 60,
-    align: 'center',
-  },
-  {
-    title: '一级指标',
-    dataIndex: 'one',
-    align: 'center',
-  },
-  {
-    title: '二级指标',
-    dataIndex: 'two',
-    align: 'center',
-  },
-  {
-    title: '三级指标',
-    align: 'center',
-    dataIndex: 'three',
-  },
-  {
-    title: '低风险',
-    dataIndex: 'warnLow',
-    align: 'center',
-  },
-  {
-    title: '一般风险',
-    dataIndex: 'warnCen',
-    align: 'center',
-  },
-  {
-    title: '较高风险',
-    dataIndex: 'warnJg',
-    align: 'center',
-  },
-  {
-    title: '高风险',
-    dataIndex: 'warnHign',
-    align: 'center',
-  },
-]);
-</script>
-
-<style lang="less" scoped>
-.warnTarget {
-  width: 100%;
-  height: 100%;
-  padding: 20px;
-  box-sizing: border-box;
-  display: flex;
-  flex-direction: column;
-}
-.top-section,
-.bottom-section {
-  flex: 1;
-  min-height: 0;
-}
-.bottom-section {
-  overflow: hidden;
-}
-</style>

+ 0 - 898
src/views/vent/monitorManager/alarmMonitor/common/closeWall-111.vue

@@ -1,898 +0,0 @@
-<template>
-  <div class="closeWall">
-    <div class="title">
-      <div class="box-container">
-        <div class="contents">
-          <div class="text">
-            <div class="text-label">位置: </div>
-            <!-- <div class="text-value">{{ topContent.position }}</div> -->
-            <div class="text-value">
-              <a-select v-model:value="selectData" style="width: 360px" @change="changeSelect">
-                <a-select-option v-for="file in selectList" :key="file.label" :value="file.value">{{ file.label }}</a-select-option>
-              </a-select>
-            </div>
-          </div>
-          <div class="text">
-            <span class="text-label">时间 : </span>
-            <span class="text-value">{{ topContent.time }}</span>
-          </div>
-        </div>
-        <div class="contents">
-          <img src="@/assets/images/fire/pie.png" alt="" />
-          <span class="text">{{ topContent.temperature }}</span>
-          <span class="dw">°C</span>
-        </div>
-      </div>
-      <div class="box-container">
-        <div class="text1">
-          <span>预警等级 : </span>
-          <span
-            :class="{
-              value1: topContent.warnLevel == '绿色预警',
-              value2: topContent.warnLevel == '黄色预警',
-              value3: topContent.warnLevel == '红色预警',
-              value4: topContent.warnLevel == '橙色预警',
-            }"
-            >{{ topContent.warnLevel || '-' }}</span
-          >
-        </div>
-        <div class="text1">
-          <span>煤自燃阶段 : </span>
-          <span>{{ topContent.smokeJd || '-' }}</span>
-        </div>
-      </div>
-      <div class="pl-10px pr-10px">
-        <div class="mb-10px">
-          <AButton class="w-full" type="primary" @click="controlDevice('dianwei', 'zhi')">应急启动</AButton>
-        </div>
-        <div>
-          <AButton class="w-full" type="primary" @click="controlDevice('dianwei', 'zhi')">应急关闭</AButton>
-        </div>
-      </div>
-    </div>
-    <div class="content">
-      <div class="title-b">采空区密闭参数</div>
-      <div class="card-btn">
-        <div
-          :class="activeIndex == index ? 'box-container1' : 'box-container'"
-          v-for="(item, index) in mbList"
-          :key="index"
-          @click="btnClick(item, index)"
-        >
-          <div class="box-label">
-            <span> {{ item.label }}</span>
-            <span>{{ item.dw }}</span>
-          </div>
-          <div class="box-item box-item1">
-            <span class="text-t">{{ `${item.label1}:` }}</span>
-            <span class="text-v">{{ item.nd }}</span>
-          </div>
-          <div class="box-item box-item2">
-            <span class="text-t">{{ `${item.label2}:` }}</span>
-            <span class="text-v">{{ item.time1 }}</span>
-          </div>
-          <div class="box-item box-item3">
-            <span class="text-t">{{ `${item.label3}:` }}</span>
-            <span class="text-v">{{ item.address }}</span>
-          </div>
-        </div>
-      </div>
-      <div class="echart-box">
-        <div class="left-echartbox">
-          <div class="title-f">
-            <div class="title-text">
-              {{ `${type}趋势` }}&nbsp; <span style="color: red">{{ netStatus != 1 ? '(设备未连接)' : '' }}</span>
-            </div>
-
-            <!-- <BaseTab
-              style="width: 180px; color: var(--vent-font-color)"
-              :tabs="[
-                { name: '实时监测', id: 'default' },
-                { name: '预测曲线', id: 'predict' },
-              ]"
-              v-model:id="shownChart"
-            /> -->
-          </div>
-          <div class="echarts-box">
-            <PredictionCurve v-if="shownChart === 'default'" :chart="curveConfig" />
-            <echartLine1
-              v-if="shownChart === 'predict'"
-              :echartDataSg="echartDataSg1"
-              :lengedDataName="echartDataSg1.lengedDataName"
-              :maxY="maxY"
-              :minY="minY"
-            />
-          </div>
-        </div>
-        <div class="right-echartbox">
-          <warnZb
-            :widthV="widthV"
-            :heightV="heightV"
-            :coordDw="coordDw"
-            :widthCanvas="widthCanvas"
-            :heightCanvas="heightCanvas"
-            :warnLevel="topContent.warnLevel"
-          />
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-  import { ref, reactive, watch, inject, h } from 'vue';
-  import echartLine1 from './echartLine1.vue';
-  import warnZb from './warnZb.vue';
-  import PredictionCurve from './predictionCurve.vue';
-  import { Modal, Input } from 'ant-design-vue';
-  import { getMaxY, getMinY } from '../common.data';
-  // import { deviceControlApi } from '/@/api/vent/index';
-
-  let props = defineProps({
-    listData: Object,
-  });
-
-  const globalConfig = inject('globalConfig');
-  const shownChart = ref('default');
-  let maxY = ref<any>(0);
-  let minY = ref<any>(0);
-  let selectSj = ref<any[]>([]);
-  let selectData = ref('');
-  let selectList = reactive<any[]>([]);
-  let widthV = ref('75%');
-  let heightV = ref('80%');
-  let coordDw = ref<any[]>([31, 62, 110]);
-  let widthCanvas = ref(562);
-  let heightCanvas = ref(316);
-  //设备连接状态
-  let netStatus = ref(0);
-  //密闭-顶部区域数据
-  let topContent = reactive({
-    temperature: 0,
-    position: '',
-    time: '',
-    warnLevel: '',
-    smokeJd: '',
-  });
-  //密闭参数列表
-  let mbList = reactive([
-    {
-      label: 'O₂',
-      dw: '(%)',
-      label1: '浓度',
-      label2: '时间',
-      label3: '位置',
-      nd: '0',
-      time1: '',
-      address: '',
-    },
-    {
-      label: 'CO',
-      dw: '(ppm)',
-      label1: '浓度',
-      label2: '时间',
-      label3: '位置',
-      nd: '0',
-      time1: '',
-      address: '',
-    },
-    {
-      label: 'CO₂',
-      dw: '(%)',
-      label1: '浓度',
-      label2: '时间',
-      label3: '位置',
-      nd: '0',
-      time1: '',
-      address: '',
-    },
-    {
-      label: 'CH₄',
-      dw: '(%)',
-      label1: '浓度',
-      label2: '时间',
-      label3: '位置',
-      nd: '0',
-      time1: '',
-      address: '',
-    },
-    {
-      label: 'C₂H₂',
-      dw: '(ppm)',
-      label1: '浓度',
-      label2: '时间',
-      label3: '位置',
-      nd: '0',
-      time1: '',
-      address: '',
-    },
-    {
-      label: 'C₂H₄',
-      dw: '(ppm)',
-      label1: '浓度',
-      label2: '时间',
-      label3: '位置',
-      nd: '0',
-      time1: '',
-      address: '',
-    },
-  ]);
-
-  //当前密闭参数激活选项
-  let activeIndex = ref(0);
-  //当前激活密闭参数类型
-  let type = ref('O₂');
-
-  let echartDataSg1 = reactive({
-    xData: [],
-    yData: [],
-    lengedData: 'O₂',
-    lengedDataName: '(%)',
-  });
-  let echartDataSgList = reactive<any[]>([]);
-
-  function getmax() {
-    getMaxY(echartDataSg1.yData);
-    getMinY(echartDataSg1.yData);
-    // maxY.value = echartDataSg1.yData.reduce((acr, cur) => {
-    //   return acr > cur ? acr : cur;
-    // });
-    // maxY.value =
-    //   maxY.value.toString().indexOf('.') == -1 ? maxY.value.toString() : maxY.value.toString().substring(0, maxY.value.toString().indexOf('.'));
-    // if (maxY.value.length < 2 && Number(maxY.value) < 1) {
-    //   maxY.value = 1;
-    // } else if (maxY.value.length < 2 && Number(maxY.value) >= 1) {
-    //   maxY.value = 10;
-    // } else if (maxY.value.length < 3) {
-    //   maxY.value = (Number(maxY.value[0]) + 1) * 10;
-    // } else if (maxY.value.length < 4) {
-    //   maxY.value = (Number(maxY.value[0]) + 1) * 100;
-    // } else if (maxY.value.length < 5) {
-    //   maxY.value = (Number(maxY.value[0]) + 1) * 1000;
-    // } else if (maxY.value.length < 6) {
-    //   maxY.value = (Number(maxY.value[0]) + 1) * 10000;
-    // }
-  }
-  //密闭参数选项切换
-  function btnClick(item, ind) {
-    // debugger;
-    activeIndex.value = ind;
-    type.value = item.label;
-    echartDataSg1.xData.length = 0;
-    echartDataSg1.yData.length = 0;
-    echartDataSg1.lengedData = type.value;
-    echartDataSg1.lengedDataName = item.dw;
-
-    const data = selectSj.value.filter((v) => v.strinstallpos == selectData.value)[0];
-    switch (type.value) {
-      case 'O₂':
-        echartDataSgList.forEach((el) => {
-          echartDataSg1.xData.push(el.time);
-          echartDataSg1.yData.push(el.o2val);
-        });
-
-        curveConfig.value = {
-          id: 'o2',
-          label: 'O₂',
-          time: echartDataSg1.xData,
-          data: [
-            // 氧气预测曲线
-            data.avgParam?.avg_o2_value || 0,
-            data.avgParam?.max_o2_value || 0,
-            data.avgParam?.min_o2_value || 0,
-            // data.readData?.o2val,
-          ],
-          monitorData: echartDataSg1.yData,
-        };
-        getmax();
-        break;
-      case 'C₂H₄':
-        echartDataSgList.forEach((el) => {
-          echartDataSg1.xData.push(el.time);
-          echartDataSg1.yData.push(el.ch2val);
-        });
-        curveConfig.value = {
-          id: 'ch2',
-          label: 'C₂H₄',
-          time: echartDataSg1.xData,
-          data: [
-            // 预测曲线
-            data.avgParam?.avg_c2h4_value || 0,
-            data.avgParam?.max_c2h4_value || 0,
-            data.avgParam?.min_c2h4_value || 0,
-            // data.readData?.ch2val,
-          ],
-          monitorData: echartDataSg1.yData,
-        };
-        getmax();
-        break;
-      case 'CO':
-        echartDataSgList.forEach((el) => {
-          echartDataSg1.xData.push(el.time);
-          echartDataSg1.yData.push(el.coval);
-        });
-        curveConfig.value = {
-          id: 'co',
-          label: 'CO',
-          time: echartDataSg1.xData,
-          data: [
-            // 预测曲线
-            data.avgParam?.avg_co_value || 0,
-            data.avgParam?.max_co_value || 0,
-            data.avgParam?.min_co_value || 0,
-            // data.readData?.coval,
-          ],
-          monitorData: echartDataSg1.yData,
-        };
-        getmax();
-        break;
-      case 'CH₄':
-        echartDataSgList.forEach((el) => {
-          echartDataSg1.xData.push(el.time);
-          echartDataSg1.yData.push(el.gasval);
-        });
-        curveConfig.value = {
-          id: 'ch',
-          label: 'CH₄',
-          time: echartDataSg1.xData,
-          data: [
-            // 预测曲线
-            data.avgParam?.avg_ch4_value || 0,
-            data.avgParam?.max_ch4_value || 0,
-            data.avgParam?.min_ch4_value || 0,
-            // data.readData?.chval,
-          ],
-          monitorData: echartDataSg1.yData,
-        };
-        getmax();
-        break;
-      case 'CO₂':
-        echartDataSgList.forEach((el) => {
-          echartDataSg1.xData.push(el.time);
-          echartDataSg1.yData.push(el.co2val);
-        });
-        curveConfig.value = {
-          id: 'co2',
-          label: 'CO₂',
-          time: echartDataSg1.xData,
-          data: [
-            // 预测曲线
-            data.avgParam?.avg_co2_value || 0,
-            data.avgParam?.max_co2_value || 0,
-            data.avgParam?.min_co2_value || 0,
-            // data.readData?.co2val,
-          ],
-          monitorData: echartDataSg1.yData,
-        };
-        getmax();
-        break;
-      case 'C₂H₂':
-        echartDataSgList.forEach((el) => {
-          echartDataSg1.xData.push(el.time);
-          echartDataSg1.yData.push(el.chval);
-        });
-        curveConfig.value = {
-          id: 'gas',
-          label: 'C₂H₂',
-          time: echartDataSg1.xData,
-          data: [
-            // 预测曲线
-            data.avgParam?.avg_c2h2_value || 0,
-            data.avgParam?.max_c2h2_value || 0,
-            data.avgParam?.min_c2h2_value || 0,
-            // data.readData?.gasval,
-          ],
-          monitorData: echartDataSg1.yData,
-        };
-        getmax();
-        break;
-    }
-  }
-
-  function changeSelect(val) {
-    selectData.value = val;
-    let data = selectSj.value.filter((v) => v.strinstallpos == selectData.value)[0];
-    topContent.time = data.readTime || '--';
-    topContent.warnLevel = data.syswarnLevel_str;
-    topContent.smokeJd = data.syswarnLevel_des;
-
-    mbList[0].nd = data.readData.o2val || '--';
-    mbList[1].nd = data.readData.coval || '--';
-    mbList[2].nd = data.readData.co2val || '--';
-    mbList[3].nd = data.readData.gasval || '--';
-    mbList[4].nd = data.readData.chval || '--';
-    mbList[5].nd = data.readData.ch2val || '--';
-    mbList.forEach((el) => {
-      el.time1 = data ? data.readTime.substring(0, data.readTime.lastIndexOf(':')) : '--';
-      el.address = data ? data.strinstallpos : '--';
-    });
-    netStatus.value = data['netStatus'];
-  }
-
-  // 设备控制
-  function controlDevice(code, value?) {
-    const passWord = ref('');
-    Modal.confirm({
-      title: '是否确认执行该操作?',
-      maskClosable: true,
-      content: () => {
-        return h(Input, {
-          placeholder: '请输入密码',
-          type: 'password',
-          modelValue: passWord.value,
-          'onUpdate:value'(val) {
-            passWord.value = val;
-          },
-        });
-      },
-      onOk() {
-        console.log('OK', passWord);
-      },
-    });
-    // Promise.all(
-    //   [].map(({ deviceID, deviceType }) => {
-    //     return deviceControlApi({
-    //       deviceid: deviceID,
-    //       devicetype: deviceType,
-    //       paramcode: code,
-    //       value,
-    //       // password: passWord || globalConfig?.simulatedPassword,
-    //     });
-    //   })
-    // ).then((res) => {
-    //   // 模拟时开启
-    //   if (res.success) {
-    //     if (globalConfig.History_Type == 'remote') {
-    //       message.success('指令已下发至生产管控平台成功!');
-    //     } else {
-    //       message.success('指令已下发成功!');
-    //     }
-    //   } else {
-    //     message.error(res.message);
-    //   }
-    // });
-  }
-
-  /** 预测曲线的配置,数据分别为平均、最大、最小、当前值 */
-  const curveConfig = ref<any>({ id: '0', label: '', time: new Date(), data: [0, 0, 0, 0] });
-
-  watch(
-    () => props.listData,
-    (val: any) => {
-      echartDataSg1.xData.length = 0;
-      echartDataSg1.yData.length = 0;
-      echartDataSgList.length = 0;
-      selectList.length = 0;
-      if (JSON.stringify(val) === '{}') return;
-      if (val.bundletube.length != 0) {
-        selectSj.value = val.bundletube;
-        selectSj.value.forEach((el) => {
-          selectList.push({ label: el.strinstallpos, value: el.strinstallpos });
-        });
-        selectData.value = selectData.value ? selectData.value : selectList[0].value;
-        let dataVal = selectData.value ? selectSj.value.filter((v) => v.strinstallpos == selectData.value)[0] : selectSj.value[0];
-        topContent.temperature = val.temperature[0] ? val.temperature[0].readData.temperature : '--';
-        topContent.time = dataVal.readTime || '--';
-        topContent.warnLevel = dataVal.syswarnLevel_str;
-        topContent.smokeJd = dataVal.syswarnLevel_des;
-
-        mbList[0].nd = dataVal.readData.o2val || '--';
-        mbList[1].nd = dataVal.readData.coval || '--';
-        mbList[2].nd = dataVal.readData.co2val || '--';
-        mbList[3].nd = dataVal.readData.gasval || '--';
-        mbList[4].nd = dataVal.readData.chval || '--';
-        mbList[5].nd = dataVal.readData.ch2val || '--';
-        mbList.forEach((el) => {
-          el.time1 = dataVal ? dataVal.readTime.substring(0, dataVal.readTime.lastIndexOf(':')) : '--';
-          el.address = dataVal ? dataVal.strinstallpos : '--';
-        });
-        netStatus.value = dataVal['netStatus'];
-        echartDataSgList = dataVal.history;
-        btnClick(
-          {
-            label: type.value,
-            dw: echartDataSg1.lengedDataName,
-          },
-          activeIndex.value
-        );
-        // dataVal.history.forEach((v) => {
-        //   echartDataSg1.xData.push(v.time);
-        //   if (echartDataSg1.lengedData == 'O₂') {
-        //     echartDataSg1.yData.push(v.o2val);
-        //   } else if (echartDataSg1.lengedData == 'C₂H₄') {
-        //     echartDataSg1.yData.push(v.ch2val);
-        //   } else if (echartDataSg1.lengedData == 'CO') {
-        //     echartDataSg1.yData.push(v.coval);
-        //   } else if (echartDataSg1.lengedData == 'CH₄') {
-        //     echartDataSg1.yData.push(v.chval);
-        //   } else if (echartDataSg1.lengedData == 'CO₂') {
-        //     echartDataSg1.yData.push(v.co2val);
-        //   } else if (echartDataSg1.lengedData == 'C₂H₂') {
-        //     echartDataSg1.yData.push(v.gasval);
-        //   }
-        //   echartDataSgList.push(v);
-        // });
-        getmax();
-      } else {
-        topContent.temperature = 0;
-        // topContent.position = '--';
-        topContent.time = '--';
-        topContent.warnLevel = '--';
-        topContent.smokeJd = '--';
-        mbList[0].nd = '--';
-        mbList[1].nd = '--';
-        mbList[2].nd = '--';
-        mbList[3].nd = '--';
-        mbList[4].nd = '--';
-        mbList[5].nd = '--';
-        mbList.forEach((el) => {
-          el.time1 = '--';
-          el.address = '--';
-        });
-      }
-    },
-    { immediate: true, deep: true }
-  );
-</script>
-
-<style lang="less" scoped>
-  @import '/@/design/theme.less';
-
-  @{theme-deepblue} {
-    .closeWall {
-      --image-bj1: url('/@/assets/images/themify/deepblue/fire/bj1.png');
-      --image-pj: url('/@/assets/images/themify/deepblue/fire/pj.svg');
-      --image-1: url('/@/assets/images/themify/deepblue/fire/1.png');
-      --image-contetn: url('/@/assets/images/themify/deepblue/fire/contetn.png');
-      --image-2: url('/@/assets/images/themify/deepblue/fire/2.png');
-    }
-  }
-
-  .closeWall {
-    --image-bj1: url('/@/assets/images/fire/bj1.png');
-    --image-pj: url('/@/assets/images/fire/pj.svg');
-    --image-1: url('/@/assets/images/fire/1.png');
-    --image-contetn: url('/@/assets/images/fire/contetn.png');
-    --image-2: url('/@/assets/images/fire/2.png');
-    --border-image-2: linear-gradient(to bottom, transparent, #024688, transparent);
-    width: 100%;
-    height: 100%;
-    padding: 20px;
-    box-sizing: border-box;
-
-    .title {
-      width: 100%;
-      height: 17%;
-      margin-bottom: 20px;
-      display: flex;
-      justify-content: space-between;
-      background: var(--image-bj1) no-repeat center;
-      background-size: 100% 100%;
-      align-items: center;
-
-      .box-container {
-        display: flex;
-
-        &:nth-child(1) {
-          justify-content: space-between;
-          align-items: center;
-          padding: 0px 40px;
-          flex: 2;
-          height: 100%;
-          border-right: 2px solid;
-          border-image: var(--border-image-2) 1 1 1;
-        }
-
-        &:nth-child(2) {
-          flex-direction: column;
-          flex: 1;
-          justify-content: space-around;
-          align-items: center;
-          height: 73%;
-          border-right: 2px solid;
-          border-image: var(--border-image-2) 1 1 1;
-        }
-
-        .contents {
-          height: 73%;
-
-          &:nth-child(1) {
-            width: 50%;
-            display: flex;
-            flex-direction: column;
-            justify-content: space-around;
-
-            .text {
-              font-size: 14px;
-              display: flex;
-              align-items: center;
-
-              .text-label {
-                color: #b3b8cc;
-                font-weight: bold;
-              }
-
-              .text-value {
-                font-family: 'douyuFont';
-                color: var(--vent-table-action-link);
-                margin-left: 10px;
-              }
-            }
-          }
-
-          &:nth-child(2) {
-            width: 50%;
-            display: flex;
-            justify-content: center;
-            align-items: center;
-
-            img {
-              position: relative;
-              width: 90px;
-              height: 100%;
-              background: var(--image-pj) no-repeat center;
-              background-size: 90px 50%;
-            }
-
-            .text {
-              font-family: 'douyuFont';
-              font-size: 28px;
-              margin: 0px 15px;
-              color: var(--vent-table-action-link);
-            }
-
-            .dw {
-              font-size: 14px;
-              color: #b3b8cc;
-            }
-          }
-        }
-
-        .text1 {
-          width: 90%;
-          height: 30px;
-          line-height: 30px;
-          font-size: 14px;
-          color: #b3b8cc;
-          font-weight: bold;
-
-          span {
-            display: inline-block;
-
-            &:nth-child(1) {
-              width: 160px;
-              text-align: right;
-            }
-
-            &:nth-child(2) {
-              font-family: 'douyuFont';
-              color: var(--vent-table-action-link);
-              margin-left: 10px;
-            }
-          }
-
-          .value1 {
-            color: rgb(145, 230, 9) !important;
-          }
-
-          .value2 {
-            // color: rgb(0, 242, 255) !important;
-            color: #ffff35 !important;
-          }
-
-          .value3 {
-            // color: #ffff35 !important;
-            color: #ff0000 !important;
-          }
-
-          .value4 {
-            color: orange !important;
-          }
-
-          .value5 {
-            color: #ff6f00 !important;
-          }
-
-          .value6 {
-            color: #ff0000 !important;
-          }
-        }
-      }
-    }
-
-    .content {
-      width: 100%;
-      height: calc(83% - 20px);
-      padding: 10px;
-      background: var(--image-bj1) no-repeat center;
-      background-size: 100% 100%;
-      box-sizing: border-box;
-
-      .title-b {
-        height: 30px;
-        line-height: 30px;
-        font-family: 'douyuFont';
-        font-size: 14px;
-        color: #fff;
-        // color: var(--vent-table-action-link);
-      }
-
-      .card-btn {
-        height: 28%;
-        margin-bottom: 10px;
-        display: flex;
-        justify-content: space-between;
-
-        .box-container {
-          position: relative;
-          width: 16%;
-          height: 100%;
-          background: var(--image-1) no-repeat center;
-          background-size: 100% 100%;
-          cursor: pointer;
-
-          .box-label {
-            position: absolute;
-            left: 50%;
-            top: 2px;
-            transform: translate(-50%);
-            color: #fff;
-          }
-
-          .box-item {
-            position: absolute;
-            left: 50%;
-            transform: translate(-50%, 0);
-            width: 89%;
-            height: 16%;
-            padding: 0px 10px;
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-            background: var(--image-contetn) no-repeat center;
-            background-size: 100% 100%;
-
-            .text-t {
-              width: 17%;
-              color: #fff;
-              font-size: 12px;
-            }
-
-            .text-v {
-              width: 83%;
-              font-family: 'douyuFont';
-              font-size: 10px;
-              color: var(--vent-table-action-link);
-              display: flex;
-              justify-content: flex-end;
-            }
-          }
-
-          .box-item1 {
-            top: 24%;
-          }
-
-          .box-item2 {
-            top: 50%;
-          }
-
-          .box-item3 {
-            top: 75%;
-          }
-        }
-
-        .box-container1 {
-          position: relative;
-          width: 16%;
-          height: 100%;
-          background: var(--image-2) no-repeat center;
-          background-size: 100% 100%;
-          cursor: pointer;
-
-          .box-label {
-            position: absolute;
-            left: 50%;
-            top: 2px;
-            transform: translate(-50%);
-            color: #fff;
-          }
-
-          .box-item {
-            position: absolute;
-            left: 50%;
-            transform: translate(-50%, 0);
-            width: 89%;
-            height: 16%;
-            padding: 0px 10px;
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-            background: var(--image-contetn) no-repeat center;
-            background-size: 100% 100%;
-
-            .text-t {
-              width: 17%;
-              color: #fff;
-              font-size: 12px;
-            }
-
-            .text-v {
-              width: 83%;
-              font-family: 'douyuFont';
-              font-size: 10px;
-              color: var(--vent-table-action-link);
-              display: flex;
-              justify-content: flex-end;
-            }
-          }
-
-          .box-item1 {
-            top: 19%;
-          }
-
-          .box-item2 {
-            top: 41%;
-          }
-
-          .box-item3 {
-            top: 63%;
-          }
-        }
-      }
-
-      .echart-box {
-        display: flex;
-        height: calc(72% - 41px);
-
-        .left-echartbox {
-          width: calc(50% - 6px);
-          margin-right: 6px;
-          border: 1px solid #114aac;
-          border-radius: 5px;
-
-          .title-f {
-            height: 40px;
-            padding: 0px 10px;
-            box-sizing: border-box;
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-
-            .title-text {
-              font-family: 'douyuFont';
-              font-size: 14px;
-              color: #fff;
-              // color: var(--vent-table-action-link);
-            }
-          }
-
-          .echarts-box {
-            height: calc(100% - 40px);
-            padding: 0px 10px;
-            box-sizing: border-box;
-          }
-        }
-
-        .right-echartbox {
-          width: calc(50% - 6px);
-          margin-left: 6px;
-          border: 1px solid #114aac;
-          border-radius: 5px;
-        }
-      }
-    }
-  }
-
-  :deep(.zxm-select:not(.zxm-select-customize-input) .zxm-select-selector) {
-    border: 1px solid #3ad8ff77 !important;
-    background-color: #ffffff00 !important;
-  }
-
-  :deep(.zxm-select-selection-item) {
-    color: #fff !important;
-  }
-
-  :deep(.zxm-select-arrow) {
-    color: #fff;
-  }
-</style>

+ 11 - 13
src/views/vent/monitorManager/alarmMonitor/common/closeWall.vue

@@ -16,21 +16,19 @@
 </template>
 
 <script lang="ts" setup>
-  import { ref, reactive, watch, inject } from 'vue';
+  import { ref, watch } from 'vue';
   import internalFireBot1 from './internal-fire-bot1.vue';
   import internalFireTop1 from './internal-fire-top1.vue';
 
-  let props = defineProps({
+  const props = defineProps({
     listData: Object,
   });
 
-  const globalConfig = inject('globalConfig');
-
-  let internalFireTopData = ref<any[]>([]);
-  let temperatureData = ref(0);
-  let internalFireBotData = ref<any[]>([]);
-  let selectData = ref('');
-  let warnLevel = ref('');
+  const internalFireTopData = ref<any[]>([]);
+  const temperatureData = ref(0);
+  const internalFireBotData = ref<any[]>([]);
+  const selectData = ref('');
+  const warnLevel = ref('');
 
   function changeSelect(data) {
     selectData.value = data;
@@ -43,10 +41,10 @@
   watch(
     () => props.listData,
     (val: any) => {
-      if (JSON.stringify(val) === '{}') return;
-      internalFireTopData.value = val.bundletube;
-      temperatureData.value = val.temperature[0] ? val.temperature[0].readData.temperature : '--';
-      internalFireBotData.value = val.bundletube;
+      if (!val || JSON.stringify(val) === '{}') return;
+      internalFireTopData.value = val.bundletube || [];
+      temperatureData.value = val.temperature?.[0]?.readData?.temperature ?? '--';
+      internalFireBotData.value = val.bundletube || [];
     },
     { immediate: true, deep: true }
   );

+ 0 - 236
src/views/vent/monitorManager/alarmMonitor/common/echartLine1.vue

@@ -1,236 +0,0 @@
-<template>
-  <div class="echartLine">
-    <div class="line" ref="line"></div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-  import * as echarts from 'echarts';
-  import { ref, nextTick, reactive, watch } from 'vue';
-  let props = defineProps({
-    echartDataSg: {
-      type: Object,
-    },
-    lengedDataName: {
-      type: String,
-    },
-    maxY: {
-      type: Number,
-    },
-    minY: {
-      type: Number,
-    },
-  });
-
-  //获取dom元素节点
-  let line = ref<any>();
-  let echartDataSgs = reactive({});
-  watch(
-    () => props.echartDataSg,
-    (data) => {
-      echartDataSgs = Object.assign({}, data);
-      getOption();
-    },
-    { immediate: true, deep: true }
-  );
-
-  function getOption() {
-    nextTick(() => {
-      const myChart = echarts.init(line.value);
-      let option = {
-        grid: {
-          top: '8%',
-          left: '5%',
-          bottom: '14%',
-          right: '5%',
-          //   containLabel: true,
-        },
-        tooltip: {
-          trigger: 'axis',
-          backgroundColor: 'rgba(0, 0, 0, .6)',
-          textStyle: {
-            color: '#fff',
-            fontSize: 12,
-          },
-        },
-        legend: {
-          align: 'left',
-          right: '50%',
-          top: '0%',
-          type: 'plain',
-          textStyle: {
-            color: '#7ec7ff',
-            fontSize: 14,
-          },
-          // icon:'rect',
-          itemGap: 25,
-          itemWidth: 18,
-          icon: 'path://M0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z',
-          data: [
-            {
-              name: echartDataSgs.lengedData ? echartDataSgs.lengedData : '',
-            },
-          ],
-        },
-        xAxis: [
-          {
-            type: 'category',
-            boundaryGap: false,
-            axisLabel: {
-              // formatter: '{value}',
-              fontSize: 14,
-              margin: 10,
-              textStyle: {
-                color: '#b3b8cc',
-              },
-              // interval: 0,
-              formatter: function (params) {
-                let newParamsName = ref(''); // 最终拼接成的字符串
-                let paramsNameNumber = ref(params.length); // 实际标签的个数
-                let provideNumber = ref(10); // 每行能显示的字的个数
-                let rowNumber = Math.ceil(paramsNameNumber.value / provideNumber.value); // 换行的话,需要显示几行,向上取整
-                /**
-                 * 判断标签的个数是否大于规定的个数, 如果大于,则进行换行处理 如果不大于,即等于或小于,就返回原标签
-                 */
-                // 条件等同于rowNumber>1
-                if (paramsNameNumber.value > provideNumber.value) {
-                  /** 循环每一行,p表示行 */
-                  for (var p = 0; p < rowNumber; p++) {
-                    var tempStr = ''; // 表示每一次截取的字符串
-                    var start = p * provideNumber.value; // 开始截取的位置
-                    var end = start + provideNumber.value; // 结束截取的位置
-                    // 此处特殊处理最后一行的索引值
-                    if (p == rowNumber - 1) {
-                      // 最后一次不换行
-                      tempStr = params.substring(start, paramsNameNumber.value);
-                    } else {
-                      // 每一次拼接字符串并换行
-                      tempStr = params.substring(start, end) + '\n';
-                    }
-                    newParamsName.value += tempStr; // 最终拼成的字符串
-                  }
-                } else {
-                  // 将旧标签的值赋给新标签
-                  newParamsName.value = params;
-                }
-                //将最终的字符串返回
-                return newParamsName.value;
-              },
-            },
-            axisLine: {
-              lineStyle: {
-                color: '#244a94',
-              },
-            },
-            splitLine: {
-              show: true,
-              lineStyle: {
-                color: '#0d2973',
-                type: 'dashed',
-              },
-            },
-            axisTick: {
-              show: false,
-            },
-            data: echartDataSgs.xData,
-          },
-        ],
-        yAxis: [
-          {
-            boundaryGap: false,
-            name: props.lengedDataName ? props.lengedDataName : '',
-            type: 'value',
-            max: props.maxY,
-            min: props.minY,
-            axisLabel: {
-              textStyle: {
-                color: '#b3b8cc',
-              },
-            },
-            nameTextStyle: {
-              color: '#fff',
-              fontSize: 12,
-              lineHeight: 5,
-            },
-            splitLine: {
-              lineStyle: {
-                color: '#0d2973',
-                type: 'dashed',
-              },
-            },
-            axisLine: {
-              show: true,
-              lineStyle: {
-                color: '#244a94',
-              },
-            },
-            axisTick: {
-              show: false,
-            },
-          },
-        ],
-        series: [
-          {
-            name: echartDataSgs.lengedData ? echartDataSgs.lengedData : '',
-            type: 'line',
-            smooth: false,
-            showSymbol: false,
-            zlevel: 3,
-            itemStyle: {
-              color: '#02f2de',
-              borderColor: '#a3c8d8',
-            },
-            lineStyle: {
-              normal: {
-                width: 3,
-                color: '#02f2de',
-              },
-            },
-            areaStyle: {
-              normal: {
-                color: new echarts.graphic.LinearGradient(
-                  0,
-                  0,
-                  0,
-                  1,
-                  [
-                    {
-                      offset: 0,
-                      color: 'rgba(2, 242, 222,0.8)',
-                    },
-                    {
-                      offset: 0.5,
-                      color: 'rgba(2, 242, 222,0.4)',
-                    },
-                    {
-                      offset: 0.9,
-                      color: 'rgba(2, 242, 222,0)',
-                    },
-                  ],
-                  false
-                ),
-              },
-            },
-            data: echartDataSgs.yData,
-          },
-        ],
-      };
-      myChart.setOption(option);
-      window.onresize = function () {
-        myChart.resize();
-      };
-    });
-  }
-</script>
-
-<style scoped lang="less">
-  .echartLine {
-    width: 100%;
-    height: 100%;
-
-    .line {
-      width: 100%;
-      height: 100%;
-    }
-  }
-</style>

+ 0 - 260
src/views/vent/monitorManager/alarmMonitor/common/echartLine2.vue

@@ -1,260 +0,0 @@
-<template>
-  <div class="echart-work">
-    <div ref="work" class="work-box"></div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-  import * as echarts from 'echarts';
-  import { ref, nextTick, reactive, watch } from 'vue';
-
-  let props = defineProps({
-    echartDataWd: {
-      type: Object,
-    },
-  });
-
-  //获取dom元素节点
-  let work = ref<any>();
-  let echartDataWds = reactive({});
-
-  watch(
-    () => props.echartDataWd,
-    (data) => {
-      echartDataWds = Object.assign({}, data);
-      getOption();
-    },
-    { immediate: true, deep: true }
-  );
-
-  function getOption() {
-    nextTick(() => {
-      const myChart = echarts.init(work.value);
-      let option = {
-        tooltip: {
-          trigger: 'axis',
-          backgroundColor: 'rgba(0, 0, 0, .6)',
-          textStyle: {
-            color: '#fff',
-            fontSize: 12,
-          },
-        },
-
-        legend: {
-          align: 'left',
-          right: '50%',
-          top: '0%',
-          type: 'plain',
-          textStyle: {
-            color: '#7ec7ff',
-            fontSize: 14,
-          },
-          // icon:'rect',
-          itemGap: 25,
-          itemWidth: 18,
-          icon: 'path://M0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z',
-          data: [
-            {
-              name: echartDataWds.avgData ? echartDataWds.avgData.lengedData : '',
-            },
-            {
-              name: echartDataWds.maxData ? echartDataWds.maxData.lengedData : '',
-            },
-            {
-              name: echartDataWds.minData ? echartDataWds.minData.lengedData : '',
-            },
-            {
-              name: echartDataWds.curData ? echartDataWds.curData.lengedData : '',
-            },
-          ],
-        },
-
-        grid: {
-          top: '20%',
-          left: '4%',
-          right: '4%',
-          bottom: '20%',
-          // containLabel: true
-        },
-
-        xAxis: [
-          {
-            type: 'category',
-            boundaryGap: false,
-            axisLine: {
-              //坐标轴轴线相关设置。数学上的x轴
-              show: true,
-              lineStyle: {
-                color: '#244a94',
-              },
-            },
-            axisLabel: {
-              //坐标轴刻度标签的相关设置
-              textStyle: {
-                color: '#b3b8cc',
-                padding: 5,
-                fontSize: 14,
-              },
-              formatter: function (data) {
-                return data;
-              },
-            },
-            splitLine: {
-              show: true,
-              lineStyle: {
-                color: '#0d2973',
-                type: 'dashed',
-              },
-            },
-            axisTick: {
-              show: false,
-            },
-            data: echartDataWds.xData,
-          },
-        ],
-
-        yAxis: [
-          {
-            name: '(℃)',
-            nameTextStyle: {
-              color: '#7ec7ff',
-              fontSize: 14,
-              padding: 0,
-            },
-            min: 0,
-            max: 100,
-            splitLine: {
-              show: true,
-              lineStyle: {
-                color: '#0d2973',
-                type: 'dashed',
-              },
-            },
-            axisLine: {
-              show: true,
-              lineStyle: {
-                color: '#244a94',
-              },
-            },
-            axisLabel: {
-              show: true,
-              textStyle: {
-                color: '#b3b8cc',
-                padding: 5,
-              },
-              formatter: function (value) {
-                if (value === 0) {
-                  return value;
-                }
-                return value;
-              },
-            },
-            axisTick: {
-              show: false,
-            },
-          },
-        ],
-        series: [
-          {
-            name: echartDataWds.maxData ? echartDataWds.maxData.lengedData : '',
-            type: 'line',
-            yAxisIndex: 0,
-            smooth: true,
-            showSymbol: false,
-            lineStyle: {
-              normal: {
-                width: 2,
-                color: '#4653fd', // 线条颜色
-              },
-              borderColor: 'rgba(0,0,0,.4)',
-            },
-            itemStyle: {
-              color: '#4653fd',
-              borderColor: '#646ace',
-              borderWidth: 0,
-            },
-            data: echartDataWds.maxData ? echartDataWds.maxData.data : [],
-          },
-          {
-            name: echartDataWds.minData ? echartDataWds.minData.lengedData : '',
-            type: 'line',
-            smooth: true,
-            yAxisIndex: 0,
-            showSymbol: false,
-
-            lineStyle: {
-              normal: {
-                width: 2,
-                color: '#46fda8', // 线条颜色
-              },
-              borderColor: 'rgba(0,0,0,.4)',
-            },
-            itemStyle: {
-              color: '#46fda8',
-              borderColor: '#646ace',
-              borderWidth: 0,
-            },
-            data: echartDataWds.minData ? echartDataWds.minData.data : [],
-          },
-          {
-            name: echartDataWds.avgData ? echartDataWds.avgData.lengedData : '',
-            type: 'line',
-            smooth: true,
-            yAxisIndex: 0,
-            showSymbol: false,
-
-            lineStyle: {
-              normal: {
-                width: 2,
-                color: '#1eb0fc', // 线条颜色
-              },
-              borderColor: 'rgba(0,0,0,.4)',
-            },
-            itemStyle: {
-              color: '#1eb0fc',
-              borderColor: '#646ace',
-              borderWidth: 0,
-            },
-            data: echartDataWds.avgData ? echartDataWds.avgData.data : [],
-          },
-          {
-            name: echartDataWds.curData ? echartDataWds.curData.lengedData : '',
-            type: 'line',
-            smooth: true,
-            yAxisIndex: 0,
-            showSymbol: false,
-            lineStyle: {
-              normal: {
-                width: 2,
-                color: '#1eb0fc', // 线条颜色
-              },
-              borderColor: 'rgba(0,0,0,.4)',
-            },
-            itemStyle: {
-              color: '#1eb0fc',
-              borderColor: '#646ace',
-              borderWidth: 0,
-            },
-            data: echartDataWds.curData ? echartDataWds.curData.data : [],
-          },
-        ],
-      };
-      myChart.setOption(option);
-      window.onresize = function () {
-        myChart.resize();
-      };
-    });
-  }
-</script>
-
-<style scoped lang="less">
-  .echart-work {
-    width: 100%;
-    height: 100%;
-
-    .work-box {
-      width: 100%;
-      height: 100%;
-    }
-  }
-</style>

+ 111 - 93
src/views/vent/monitorManager/alarmMonitor/common/external-gx-echart.vue

@@ -1,119 +1,137 @@
 <template>
-    <div class="external-gx-echart">
-        <div class="footer-title">
-            <div class="echart-label">光纤测温系统实时温度监测</div>
-        </div>
-        <div class="echart-content">
-            <echartLine2 :echartDataWd="echartDataWd" />
-        </div>
+  <div class="external-gx-echart">
+    <div class="footer-title">
+      <div class="echart-label">光纤测温系统实时温度监测</div>
     </div>
+    <div class="echart-content">
+      <FiberTempChart
+        :x-axis-data="xAxisData"
+        :series-data="seriesData"
+        :max-value="maxValue"
+        :min-value="minValue"
+        title=""
+        height="100%"
+        compact
+        :mark-symbol-size="36"
+        data-zoom-mode="index"
+        :data-zoom-start="0"
+        :data-zoom-end="20"
+      />
+    </div>
+  </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, watch } from 'vue'
-import echartLine2 from './echartLine2.vue';
-
-let props = defineProps({
-    echartData: {
-        type: Array,
-        default: () => {
-            return []
-        }
-    }
-})
+import { ref, watch } from 'vue';
+import FiberTempChart from '/@/views/vent/testChart/FiberTempChart.vue';
 
-//外因火灾温度监测-图表数据
-let echartDataWd = reactive({
-    xData: [],
-    curData: {
-        lengedData: '实时温度',
-        data: [],
-    },
-    maxData: {
-        lengedData: '最大温度',
-        data: [],
-    },
-    minData: {
-        lengedData: '最小温度',
-        data: [],
-    },
-    avgData: {
-        lengedData: '平均温度',
-        data: [],
+const props = defineProps({
+  echartData: {
+    type: Array,
+    default: () => {
+      return [];
     },
+  },
 });
 
-watch(() => props.echartData, (newV, oldV) => {
-    echartDataWd.xData.length = 0;
-    echartDataWd.avgData.data.length = 0;
-    echartDataWd.maxData.data.length = 0;
-    echartDataWd.minData.data.length = 0;
-    echartDataWd.curData.data.length = 0;
-    if (newV.length != 0) {
-        const fiber: any = newV[0];
-        const arr: any[] = JSON.parse(fiber.readData.fibreTemperature);
+const xAxisData = ref<any[]>([]);
+const seriesData = ref<number[]>([]);
+const maxValue = ref(60);
+const minValue = ref(20);
+let lastSignature = '';
+
+watch(
+  () => props.echartData,
+  (newV) => {
+    const nextX: any[] = [];
+    const nextSeries: number[] = [];
+    let nextMax = 60;
+    let nextMin = 20;
+
+    if (newV && newV.length !== 0) {
+      const fiber: any = newV[0];
+      const readData = fiber?.readData || {};
+      // 上限/下限取对应字段最大温度 fmax、最小温度 fmin
+      const fmax = Number(readData.fmax);
+      const fmin = Number(readData.fmin);
+      if (!Number.isNaN(fmax)) nextMax = fmax;
+      if (!Number.isNaN(fmin)) nextMin = fmin;
+
+      // 曲线取值逻辑保持与原先一致:fibreTemperature 的 pos / value
+      try {
+        const arr: any[] = JSON.parse(readData.fibreTemperature || '[]');
         arr.forEach((el) => {
-        echartDataWd.xData.push(el.pos);
-        echartDataWd.curData.data.push(el.value);
-        echartDataWd.maxData.data.push(fiber.avgParam?.max_temperature_value || 0);
-        echartDataWd.minData.data.push(fiber.avgParam?.min_temperature_value || 0);
-        echartDataWd.avgData.data.push(fiber.avgParam?.avg_temperature_value || 0);
-      });
+          nextX.push(el.pos);
+          nextSeries.push(Number(el.value));
+        });
+      } catch (e) {
+        console.warn('fibreTemperature parse error', e);
+      }
     }
-}, { immediate: true })
 
+    const signature = JSON.stringify({ x: nextX, s: nextSeries, max: nextMax, min: nextMin });
+    if (signature === lastSignature) return;
+    lastSignature = signature;
+
+    xAxisData.value = nextX;
+    seriesData.value = nextSeries;
+    maxValue.value = nextMax;
+    minValue.value = nextMin;
+  },
+  { immediate: true, deep: true }
+);
 </script>
 
 <style lang="less" scoped>
 @import '/@/design/theme.less';
 
 @{theme-deepblue} {
-    .external-gx-echart {
-        --image-bj1: url('/@/assets/images/themify/deepblue/fire/bj1.png');
-        --image-max: url('/@/assets/images/themify/deepblue/fire/max.svg');
-        --image-min: url('/@/assets/images/themify/deepblue/fire/min.svg');
-        --image-pj: url('/@/assets/images/themify/deepblue/fire/pj.svg');
-        --image-1: url('/@/assets/images/themify/deepblue/fire/1.png');
-        --image-contetn: url('/@/assets/images/themify/deepblue/fire/contetn.png');
-        --image-2: url('/@/assets/images/themify/deepblue/fire/2.png');
-    }
+  .external-gx-echart {
+    --image-bj1: url('/@/assets/images/themify/deepblue/fire/bj1.png');
+    --image-max: url('/@/assets/images/themify/deepblue/fire/max.svg');
+    --image-min: url('/@/assets/images/themify/deepblue/fire/min.svg');
+    --image-pj: url('/@/assets/images/themify/deepblue/fire/pj.svg');
+    --image-1: url('/@/assets/images/themify/deepblue/fire/1.png');
+    --image-contetn: url('/@/assets/images/themify/deepblue/fire/contetn.png');
+    --image-2: url('/@/assets/images/themify/deepblue/fire/2.png');
+  }
 }
 
 .external-gx-echart {
-    --image-zwnr: url('/@/assets/images/fire/wy-zwnr.png');
-    --image-z: url('/@/assets/images/fire/wy-z.png');
-    --image-zt: url('/@/assets/images/fire/wy-zt.png');
-    --image-zt1: url('/@/assets/images/fire/wy-zt2.png');
-    --image-time: url('/@/assets/images/fire/wy-time.png');
-    --image-time1: url('/@/assets/images/fire/wy-time2.png');
-    --image-title: url('/@/assets/images/fire/wy-title.png');
-    --image-bj2: url('/@/assets/images/fire/wy-bj.png');
-    --image-max: url('/@/assets/images/fire/max.svg');
-    --image-min: url('/@/assets/images/fire/min.svg');
-    --image-pj: url('/@/assets/images/fire/pj.svg');
-    --image-1: url('/@/assets/images/fire/1.png');
-    --image-contetn: url('/@/assets/images/fire/contetn.png');
-    --image-2: url('/@/assets/images/fire/2.png');
-    --border-image-2: linear-gradient(to bottom, transparent, #024688, transparent);
-    position: relative;
-    width: 100%;
-    height: 100%;
+  --image-zwnr: url('/@/assets/images/fire/wy-zwnr.png');
+  --image-z: url('/@/assets/images/fire/wy-z.png');
+  --image-zt: url('/@/assets/images/fire/wy-zt.png');
+  --image-zt1: url('/@/assets/images/fire/wy-zt2.png');
+  --image-time: url('/@/assets/images/fire/wy-time.png');
+  --image-time1: url('/@/assets/images/fire/wy-time2.png');
+  --image-title: url('/@/assets/images/fire/wy-title.png');
+  --image-bj2: url('/@/assets/images/fire/wy-bj.png');
+  --image-max: url('/@/assets/images/fire/max.svg');
+  --image-min: url('/@/assets/images/fire/min.svg');
+  --image-pj: url('/@/assets/images/fire/pj.svg');
+  --image-1: url('/@/assets/images/fire/1.png');
+  --image-contetn: url('/@/assets/images/fire/contetn.png');
+  --image-2: url('/@/assets/images/fire/2.png');
+  --border-image-2: linear-gradient(to bottom, transparent, #024688, transparent);
+  position: relative;
+  width: 100%;
+  height: 100%;
 
-    .footer-title {
-        height: 30px;
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
+  .footer-title {
+    height: 30px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
 
-        .echart-label {
-            font-family: 'douyuFont';
-            font-size: 14px;
-            color: #fff;
-        }
+    .echart-label {
+      font-family: 'douyuFont';
+      font-size: 14px;
+      color: #fff;
     }
+  }
 
-    .echart-content {
-        height: calc(100% - 30px);
-    }
+  .echart-content {
+    height: calc(100% - 30px);
+  }
 }
-</style>
+</style>

+ 477 - 400
src/views/vent/monitorManager/alarmMonitor/common/external-mb.vue

@@ -1,464 +1,541 @@
 <template>
-    <div class="external-mb">
-        <div class="title-b">采空区密闭参数</div>
-        <div class="card-btn">
-            <div :class="activeIndex == 0 ? 'content-box1' : 'content-box'" @click="btnClick({ label: 'O₂', dw: '(%)' }, 0)">
-                <div class="btn-label">
-                    <span>O₂</span>
-                    <span>(%)</span>
-                </div>
-                <div class="box-item box-item1">
-                    <span class="text-t">浓度:</span>
-                    <span class="text-v">{{ mbData.o2 }}</span>
-                </div>
-                <div class="box-item box-item2">
-                    <span class="text-t">时间:</span>
-                    <span class="text-v">{{ mbData.o2time }}</span>
-                </div>
-                <div class="box-item box-item3">
-                    <span class="text-t">位置:</span>
-                    <span class="text-v">{{ mbData.o2address }}</span>
-                </div>
-            </div>
-            <div :class="activeIndex == 1 ? 'content-box1' : 'content-box'" @click="btnClick({ label: 'CO', dw: '(ppm)' }, 1)">
-                <div class="btn-label">
-                    <span>CO</span>
-                    <span>(ppm)</span>
-                </div>
-                <div class="box-item box-item1">
-                    <span class="text-t">浓度:</span>
-                    <span class="text-v">{{ mbData.co }}</span>
-                </div>
-                <div class="box-item box-item2">
-                    <span class="text-t">时间:</span>
-                    <span class="text-v">{{ mbData.cotime }}</span>
-                </div>
-                <div class="box-item box-item3">
-                    <span class="text-t">位置:</span>
-                    <span class="text-v">{{ mbData.coaddress }}</span>
-                </div>
-            </div>
-            <div :class="activeIndex == 2 ? 'content-box1' : 'content-box'" @click="btnClick({ label: 'CO₂', dw: '(%)' }, 2)">
-                <div class="btn-label">
-                    <span>CO₂</span>
-                    <span>(%)</span>
-                </div>
-                <div class="box-item box-item1">
-                    <span class="text-t">浓度:</span>
-                    <span class="text-v">{{ mbData.co2 }}</span>
-                </div>
-                <div class="box-item box-item2">
-                    <span class="text-t">时间:</span>
-                    <span class="text-v">{{ mbData.co2time }}</span>
-                </div>
-                <div class="box-item box-item3">
-                    <span class="text-t">位置:</span>
-                    <span class="text-v">{{ mbData.co2address }}</span>
-                </div>
-            </div>
-            <div :class="activeIndex == 3 ? 'content-box1' : 'content-box'" @click="btnClick({ label: 'CH₄', dw: '(%)' }, 3)">
-                <div class="btn-label">
-                    <span>CH₄</span>
-                    <span>(%)</span>
-                </div>
-                <div class="box-item box-item1">
-                    <span class="text-t">浓度:</span>
-                    <span class="text-v">{{ mbData.ch4 }}</span>
-                </div>
-                <div class="box-item box-item2">
-                    <span class="text-t">时间:</span>
-                    <span class="text-v">{{ mbData.ch4time }}</span>
-                </div>
-                <div class="box-item box-item3">
-                    <span class="text-t">位置:</span>
-                    <span class="text-v">{{ mbData.ch4address }}</span>
-                </div>
-            </div>
-            <div :class="activeIndex == 4 ? 'content-box1' : 'content-box'" @click="btnClick({ label: 'C₂H₂', dw: '(ppm)' }, 4)">
-                <div class="btn-label">
-                    <span>C₂H₂</span>
-                    <span>(ppm)</span>
-                </div>
-                <div class="box-item box-item1">
-                    <span class="text-t">浓度:</span>
-                    <span class="text-v">{{ mbData.c2h2 }}</span>
-                </div>
-                <div class="box-item box-item2">
-                    <span class="text-t">时间:</span>
-                    <span class="text-v">{{ mbData.c2h2time }}</span>
-                </div>
-                <div class="box-item box-item3">
-                    <span class="text-t">位置:</span>
-                    <span class="text-v">{{ mbData.c2h2address }}</span>
-                </div>
-            </div>
-            <div :class="activeIndex == 5 ? 'content-box1' : 'content-box'" @click="btnClick({ label: 'C₂H₄', dw: '(ppm)' }, 5)">
-                <div class="btn-label">
-                    <span>C₂H₄</span>
-                    <span>(ppm)</span>
-                </div>
-                <div class="box-item box-item1">
-                    <span class="text-t">浓度:</span>
-                    <span class="text-v">{{ mbData.c2h4 }}</span>
-                </div>
-                <div class="box-item box-item2">
-                    <span class="text-t">时间:</span>
-                    <span class="text-v">{{ mbData.c2h4time }}</span>
-                </div>
-                <div class="box-item box-item3">
-                    <span class="text-t">位置:</span>
-                    <span class="text-v">{{ mbData.c2h4address }}</span>
-                </div>
-            </div>
+  <div class="external-mb">
+    <div class="title-b">采空区密闭参数</div>
+    <div class="card-btn">
+      <div :class="activeIndex == 0 ? 'content-box1' : 'content-box'" @click="btnClick({ label: 'O₂', dw: '(%)' }, 0)">
+        <div class="btn-label">
+          <span>O₂</span>
+          <span>(%)</span>
         </div>
-        <div class="echart-box">
-            <div class="title-f">
-                <div class="title-text">
-                    {{ `${type}趋势` }}&nbsp; <span style="color: red">{{ netStatus != 1 ? '(设备未连接)' : '' }}</span>
-                </div>
-            </div>
-            <div class="echarts-box">
-                <echartLine1 :echartDataSg="echartDataSg1" :lengedDataName="echartDataSg1.lengedDataName" />
-            </div>
+        <div class="box-item box-item1">
+          <span class="text-t">浓度:</span>
+          <span class="text-v">{{ mbData.o2 }}</span>
         </div>
+        <div class="box-item box-item2">
+          <span class="text-t">时间:</span>
+          <span class="text-v">{{ mbData.o2time }}</span>
+        </div>
+        <div class="box-item box-item3">
+          <span class="text-t">位置:</span>
+          <span class="text-v">{{ mbData.o2address }}</span>
+        </div>
+      </div>
+      <div :class="activeIndex == 1 ? 'content-box1' : 'content-box'" @click="btnClick({ label: 'CO', dw: '(ppm)' }, 1)">
+        <div class="btn-label">
+          <span>CO</span>
+          <span>(ppm)</span>
+        </div>
+        <div class="box-item box-item1">
+          <span class="text-t">浓度:</span>
+          <span class="text-v">{{ mbData.co }}</span>
+        </div>
+        <div class="box-item box-item2">
+          <span class="text-t">时间:</span>
+          <span class="text-v">{{ mbData.cotime }}</span>
+        </div>
+        <div class="box-item box-item3">
+          <span class="text-t">位置:</span>
+          <span class="text-v">{{ mbData.coaddress }}</span>
+        </div>
+      </div>
+      <div :class="activeIndex == 2 ? 'content-box1' : 'content-box'" @click="btnClick({ label: 'CO₂', dw: '(%)' }, 2)">
+        <div class="btn-label">
+          <span>CO₂</span>
+          <span>(%)</span>
+        </div>
+        <div class="box-item box-item1">
+          <span class="text-t">浓度:</span>
+          <span class="text-v">{{ mbData.co2 }}</span>
+        </div>
+        <div class="box-item box-item2">
+          <span class="text-t">时间:</span>
+          <span class="text-v">{{ mbData.co2time }}</span>
+        </div>
+        <div class="box-item box-item3">
+          <span class="text-t">位置:</span>
+          <span class="text-v">{{ mbData.co2address }}</span>
+        </div>
+      </div>
+      <div :class="activeIndex == 3 ? 'content-box1' : 'content-box'" @click="btnClick({ label: 'CH₄', dw: '(%)' }, 3)">
+        <div class="btn-label">
+          <span>CH₄</span>
+          <span>(%)</span>
+        </div>
+        <div class="box-item box-item1">
+          <span class="text-t">浓度:</span>
+          <span class="text-v">{{ mbData.ch4 }}</span>
+        </div>
+        <div class="box-item box-item2">
+          <span class="text-t">时间:</span>
+          <span class="text-v">{{ mbData.ch4time }}</span>
+        </div>
+        <div class="box-item box-item3">
+          <span class="text-t">位置:</span>
+          <span class="text-v">{{ mbData.ch4address }}</span>
+        </div>
+      </div>
+      <div :class="activeIndex == 4 ? 'content-box1' : 'content-box'" @click="btnClick({ label: 'C₂H₂', dw: '(ppm)' }, 4)">
+        <div class="btn-label">
+          <span>C₂H₂</span>
+          <span>(ppm)</span>
+        </div>
+        <div class="box-item box-item1">
+          <span class="text-t">浓度:</span>
+          <span class="text-v">{{ mbData.c2h2 }}</span>
+        </div>
+        <div class="box-item box-item2">
+          <span class="text-t">时间:</span>
+          <span class="text-v">{{ mbData.c2h2time }}</span>
+        </div>
+        <div class="box-item box-item3">
+          <span class="text-t">位置:</span>
+          <span class="text-v">{{ mbData.c2h2address }}</span>
+        </div>
+      </div>
+      <div :class="activeIndex == 5 ? 'content-box1' : 'content-box'" @click="btnClick({ label: 'C₂H₄', dw: '(ppm)' }, 5)">
+        <div class="btn-label">
+          <span>C₂H₄</span>
+          <span>(ppm)</span>
+        </div>
+        <div class="box-item box-item1">
+          <span class="text-t">浓度:</span>
+          <span class="text-v">{{ mbData.c2h4 }}</span>
+        </div>
+        <div class="box-item box-item2">
+          <span class="text-t">时间:</span>
+          <span class="text-v">{{ mbData.c2h4time }}</span>
+        </div>
+        <div class="box-item box-item3">
+          <span class="text-t">位置:</span>
+          <span class="text-v">{{ mbData.c2h4address }}</span>
+        </div>
+      </div>
+    </div>
+    <div class="echart-box">
+      <div class="title-f">
+        <div class="title-text">
+          {{ `${type}趋势` }}&nbsp; <span style="color: red">{{ netStatus != 1 ? '(设备未连接)' : '' }}</span>
+        </div>
+      </div>
+      <div class="echarts-box">
+        <FiberTempChart
+          :x-axis-data="chartXData"
+          :series-data="chartYData"
+          :max-value="chartMaxValue"
+          :min-value="chartMinValue"
+          title=""
+          :y-axis-name="chartYAxisName"
+          :value-label="chartValueLabel"
+          :value-unit="chartValueUnit"
+          height="100%"
+          compact
+          :mark-symbol-size="32"
+          data-zoom-mode="index"
+          :data-zoom-start="0"
+          :data-zoom-end="10"
+        />
+      </div>
     </div>
+  </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, watch } from 'vue'
-import echartLine1 from './echartLine1.vue';
-
-let props = defineProps({
-    externalTSgData: {
-        type: Array,
-        default: () => {
-            return []
-        }
-    },
-    selectData:{
-        type:String,
-        default:'',
+import { ref, reactive, watch } from 'vue';
+import FiberTempChart from '/@/views/vent/testChart/FiberTempChart.vue';
+
+const props = defineProps({
+  externalTSgData: {
+    type: Array,
+    default: () => {
+      return [];
     },
-    warnLevel:{
-        type:String,
-        default:''
-    }
-})
-
-//束管监测当前密闭参数激活选项
-let activeIndex = ref(0);
-let bundletube = ref<any[]>([])
-//束管监测密闭参数列表
-let mbData = reactive({
-    o2: '',
-    o2time: '',
-    o2address: '',
-    co: '',
-    cotime: '',
-    coaddress: '',
-    co2: '',
-    co2time: '',
-    co2address: '',
-    ch4: '',
-    ch4time: '',
-    ch4address: '',
-    c2h2: '',
-    c2h2time: '',
-    c2h2address: '',
-    c2h4: '',
-    c2h4time: '',
-    c2h4address: ''
-})
-//束管监测当前激活密闭参数类型
-let type = ref('O₂');
-let echartDataSg1 = reactive({
-  xData: [],
-  yData: [],
-  lengedData: 'O₂',
-  lengedDataName: '(%)',
+  },
+  selectData: {
+    type: String,
+    default: '',
+  },
+});
+
+const activeIndex = ref(0);
+const bundletube = ref<any[]>([]);
+const mbData = reactive({
+  o2: '',
+  o2time: '',
+  o2address: '',
+  co: '',
+  cotime: '',
+  coaddress: '',
+  co2: '',
+  co2time: '',
+  co2address: '',
+  ch4: '',
+  ch4time: '',
+  ch4address: '',
+  c2h2: '',
+  c2h2time: '',
+  c2h2address: '',
+  c2h4: '',
+  c2h4time: '',
+  c2h4address: '',
 });
-let echartDataSgList = ref<any[]>([]);
-    let netStatus = ref(0);
 
-//束管监测密闭参数选项切换
+const type = ref('O₂');
+const currentUnit = ref('(%)');
+const echartDataSgList = ref<any[]>([]);
+const netStatus = ref(0);
+
+const chartXData = ref<any[]>([]);
+const chartYData = ref<number[]>([]);
+const chartMaxValue = ref(0);
+const chartMinValue = ref(0);
+const chartYAxisName = ref('浓度(%)');
+const chartValueLabel = ref('浓度');
+const chartValueUnit = ref('%');
+let lastChartSignature = '';
+
+function parseUnit(dw: string) {
+  const text = String(dw || '').replace(/[()()]/g, '').trim();
+  return text || '';
+}
+
+function getSelectedDevice() {
+  if (!bundletube.value.length) return null;
+  if (props.selectData) {
+    return bundletube.value.find((v) => v.strinstallpos == props.selectData) || bundletube.value[0];
+  }
+  return bundletube.value[0];
+}
+
+function fillCardData(dataVal: any) {
+  if (!dataVal?.readData) return;
+  const times = dataVal.readTime ? dataVal.readTime.substring(0, dataVal.readTime.lastIndexOf(':')) || '--' : '--';
+  const address = dataVal.strinstallpos || '--';
+  mbData.o2 = dataVal.readData.o2val || '--';
+  mbData.o2time = times;
+  mbData.o2address = address;
+  mbData.co = dataVal.readData.coval || '--';
+  mbData.cotime = times;
+  mbData.coaddress = address;
+  mbData.co2 = dataVal.readData.co2val || '--';
+  mbData.co2time = times;
+  mbData.co2address = address;
+  mbData.ch4 = dataVal.readData.gasval || '--';
+  mbData.ch4time = times;
+  mbData.ch4address = address;
+  mbData.c2h2 = dataVal.readData.chval || '--';
+  mbData.c2h2time = times;
+  mbData.c2h2address = address;
+  mbData.c2h4 = dataVal.readData.ch2val || '--';
+  mbData.c2h4time = times;
+  mbData.c2h4address = address;
+}
+
+function applyChartData(nextX: any[], nextY: number[], nextMax: number, nextMin: number, title: string, dw: string) {
+  const unit = parseUnit(dw);
+  const signature = JSON.stringify({
+    x: nextX,
+    y: nextY,
+    max: nextMax,
+    min: nextMin,
+    title,
+    dw,
+  });
+  if (signature === lastChartSignature) return;
+  lastChartSignature = signature;
+
+  chartXData.value = nextX;
+  chartYData.value = nextY;
+  chartMaxValue.value = nextMax;
+  chartMinValue.value = nextMin;
+  chartYAxisName.value = unit ? `浓度(${unit})` : '浓度';
+  chartValueLabel.value = '浓度';
+  chartValueUnit.value = unit;
+}
+
+// 束管监测密闭参数选项切换(取值逻辑与原先保持一致)
 function btnClick(item, ind) {
   activeIndex.value = ind;
   type.value = item.label;
-  echartDataSg1.xData.length = 0;
-  echartDataSg1.yData.length = 0;
-  echartDataSg1.lengedData = type.value;
-  echartDataSg1.lengedDataName = item.dw;
+  currentUnit.value = item.dw;
+
+  const dataVal = getSelectedDevice();
+  const nextX: any[] = [];
+  const nextY: number[] = [];
+  let nextMax = 0;
+  let nextMin = 0;
+  const avg = dataVal?.avgParam || {};
 
   switch (type.value) {
     case 'O₂':
       echartDataSgList.value.forEach((el) => {
-        echartDataSg1.xData.push(el.time);
-        echartDataSg1.yData.push(el.o2val);
+        nextX.push(el.time);
+        nextY.push(Number(el.o2val));
       });
+      nextMax = Number(avg.max_o2_value || 0);
+      nextMin = Number(avg.min_o2_value || 0);
       break;
     case 'C₂H₄':
       echartDataSgList.value.forEach((el) => {
-        echartDataSg1.xData.push(el.time);
-        echartDataSg1.yData.push(el.ch2val);
+        nextX.push(el.time);
+        nextY.push(Number(el.ch2val));
       });
+      nextMax = Number(avg.max_c2h4_value || 0);
+      nextMin = Number(avg.min_c2h4_value || 0);
       break;
     case 'CO':
       echartDataSgList.value.forEach((el) => {
-        echartDataSg1.xData.push(el.time);
-        echartDataSg1.yData.push(el.coval);
+        nextX.push(el.time);
+        nextY.push(Number(el.coval));
       });
+      nextMax = Number(avg.max_co_value || 0);
+      nextMin = Number(avg.min_co_value || 0);
       break;
     case 'CH₄':
       echartDataSgList.value.forEach((el) => {
-        echartDataSg1.xData.push(el.time);
-        echartDataSg1.yData.push(el.gasval);
+        nextX.push(el.time);
+        nextY.push(Number(el.gasval));
       });
+      nextMax = Number(avg.max_ch4_value || 0);
+      nextMin = Number(avg.min_ch4_value || 0);
       break;
     case 'CO₂':
       echartDataSgList.value.forEach((el) => {
-        echartDataSg1.xData.push(el.time);
-        echartDataSg1.yData.push(el.co2val);
+        nextX.push(el.time);
+        nextY.push(Number(el.co2val));
       });
+      nextMax = Number(avg.max_co2_value || 0);
+      nextMin = Number(avg.min_co2_value || 0);
       break;
     case 'C₂H₂':
       echartDataSgList.value.forEach((el) => {
-        echartDataSg1.xData.push(el.time);
-        echartDataSg1.yData.push(el.chval);
+        nextX.push(el.time);
+        nextY.push(Number(el.chval));
       });
+      nextMax = Number(avg.max_c2h2_value || 0);
+      nextMin = Number(avg.min_c2h2_value || 0);
       break;
   }
+
+  applyChartData(nextX, nextY, nextMax, nextMin, type.value, item.dw);
 }
 
-watch(() => props.externalTSgData, (newV, oldV) => {
-    echartDataSg1.xData.length = 0;
-    echartDataSg1.yData.length = 0;
-    if (newV.length != 0 && props.selectData) {
-        bundletube.value = newV
-        let dataVal =bundletube.value.filter((v) => v.strinstallpos == props.selectData)[0] 
-        let times = dataVal.readTime.substring(0, dataVal.readTime.lastIndexOf(':')) || '--';
-        let address = dataVal.strinstallpos || '--';
-        mbData.o2 = dataVal.readData.o2val || '--';
-        mbData.o2time = times
-        mbData.o2address = address
-        mbData.co = dataVal.readData.coval || '--';
-        mbData.cotime = times
-        mbData.coaddress = address;
-        mbData.co2 = dataVal.readData.co2val || '--';
-        mbData.co2time = times
-        mbData.co2address = address;
-        mbData.ch4 = dataVal.readData.gasval || '--';
-        mbData.ch4time =times
-        mbData.ch4address = address;
-        mbData.c2h2 = dataVal.readData.chval || '--';
-        mbData.c2h2time = times
-        mbData.c2h2address = address;
-        mbData.c2h4 = dataVal.readData.ch2val || '--';
-        mbData.c2h4time = times
-        mbData.c2h4address = address;
-
-        echartDataSgList.value = dataVal.history;
-        netStatus.value = dataVal['netStatus'];
-        btnClick(
-            {
-                label: type.value,
-                dw: echartDataSg1.lengedDataName,
-            },
-            activeIndex.value
-        );
-    }
-}, { immediate: true })
+function refreshBySelect() {
+  if (!props.externalTSgData || props.externalTSgData.length === 0) return;
+  bundletube.value = props.externalTSgData as any[];
+  const dataVal = getSelectedDevice();
+  if (!dataVal) return;
+
+  fillCardData(dataVal);
+  echartDataSgList.value = dataVal.history || [];
+  netStatus.value = dataVal['netStatus'];
+  btnClick(
+    {
+      label: type.value,
+      dw: currentUnit.value,
+    },
+    activeIndex.value
+  );
+}
+
+watch(
+  () => [props.externalTSgData, props.selectData],
+  () => {
+    refreshBySelect();
+  },
+  { immediate: true, deep: true }
+);
 </script>
 
 <style lang="less" scoped>
 @import '/@/design/theme.less';
 
 @{theme-deepblue} {
-    .external-mb {
-        --image-bj1: url('/@/assets/images/themify/deepblue/fire/bj1.png');
-        --image-max: url('/@/assets/images/themify/deepblue/fire/max.svg');
-        --image-min: url('/@/assets/images/themify/deepblue/fire/min.svg');
-        --image-pj: url('/@/assets/images/themify/deepblue/fire/pj.svg');
-        --image-1: url('/@/assets/images/themify/deepblue/fire/1.png');
-        --image-contetn: url('/@/assets/images/themify/deepblue/fire/contetn.png');
-        --image-2: url('/@/assets/images/themify/deepblue/fire/2.png');
-    }
+  .external-mb {
+    --image-bj1: url('/@/assets/images/themify/deepblue/fire/bj1.png');
+    --image-max: url('/@/assets/images/themify/deepblue/fire/max.svg');
+    --image-min: url('/@/assets/images/themify/deepblue/fire/min.svg');
+    --image-pj: url('/@/assets/images/themify/deepblue/fire/pj.svg');
+    --image-1: url('/@/assets/images/themify/deepblue/fire/1.png');
+    --image-contetn: url('/@/assets/images/themify/deepblue/fire/contetn.png');
+    --image-2: url('/@/assets/images/themify/deepblue/fire/2.png');
+  }
 }
 
 .external-mb {
-    --image-zwnr: url('/@/assets/images/fire/wy-zwnr.png');
-    --image-z: url('/@/assets/images/fire/wy-z.png');
-    --image-zt: url('/@/assets/images/fire/wy-zt.png');
-    --image-zt1: url('/@/assets/images/fire/wy-zt2.png');
-    --image-time: url('/@/assets/images/fire/wy-time.png');
-    --image-time1: url('/@/assets/images/fire/wy-time2.png');
-    --image-title: url('/@/assets/images/fire/wy-title.png');
-    --image-bj2: url('/@/assets/images/fire/wy-bj.png');
-    --image-max: url('/@/assets/images/fire/max.svg');
-    --image-min: url('/@/assets/images/fire/min.svg');
-    --image-pj: url('/@/assets/images/fire/pj.svg');
-    --image-1: url('/@/assets/images/fire/1.png');
-    --image-contetn: url('/@/assets/images/fire/contetn.png');
-    --image-2: url('/@/assets/images/fire/2.png');
-    --border-image-2: linear-gradient(to bottom, transparent, #024688, transparent);
-    position: relative;
-    width: 100%;
-    height: 100%;
-
-    .title-b {
-        height: 30px;
-        line-height: 30px;
-        font-family: 'douyuFont';
-        font-size: 14px;
-        color: #fff;
-    }
+  --image-zwnr: url('/@/assets/images/fire/wy-zwnr.png');
+  --image-z: url('/@/assets/images/fire/wy-z.png');
+  --image-zt: url('/@/assets/images/fire/wy-zt.png');
+  --image-zt1: url('/@/assets/images/fire/wy-zt2.png');
+  --image-time: url('/@/assets/images/fire/wy-time.png');
+  --image-time1: url('/@/assets/images/fire/wy-time2.png');
+  --image-title: url('/@/assets/images/fire/wy-title.png');
+  --image-bj2: url('/@/assets/images/fire/wy-bj.png');
+  --image-max: url('/@/assets/images/fire/max.svg');
+  --image-min: url('/@/assets/images/fire/min.svg');
+  --image-pj: url('/@/assets/images/fire/pj.svg');
+  --image-1: url('/@/assets/images/fire/1.png');
+  --image-contetn: url('/@/assets/images/fire/contetn.png');
+  --image-2: url('/@/assets/images/fire/2.png');
+  --border-image-2: linear-gradient(to bottom, transparent, #024688, transparent);
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  .title-b {
+    height: 30px;
+    line-height: 30px;
+    font-family: 'douyuFont';
+    font-size: 14px;
+    color: #fff;
+  }
 
-    .card-btn {
-        height: 28%;
-        margin-bottom: 10px;
+  .card-btn {
+    height: 28%;
+    margin-bottom: 10px;
+    display: flex;
+    justify-content: space-between;
+
+    .content-box {
+      position: relative;
+      width: 16%;
+      height: 100%;
+      background: var(--image-1) no-repeat center;
+      background-size: 100% 100%;
+      cursor: pointer;
+
+      .btn-label {
+        position: absolute;
+        left: 50%;
+        top: 0;
+        transform: translate(-50%);
+        font-size: 16px;
+        color: #fff;
+      }
+
+      .box-item {
+        position: absolute;
+        left: 50%;
+        transform: translate(-50%, 0);
+        width: 89%;
+        height: 16%;
+        padding: 0px 10px;
         display: flex;
         justify-content: space-between;
-
-        .content-box {
-            position: relative;
-            width: 16%;
-            height: 100%;
-            background: var(--image-1) no-repeat center;
-            background-size: 100% 100%;
-            cursor: pointer;
-
-            .btn-label {
-                position: absolute;
-                left: 50%;
-                top: 0;
-                transform: translate(-50%);
-                font-size: 16px;
-                color: #fff;
-            }
-
-            .box-item {
-                position: absolute;
-                left: 50%;
-                transform: translate(-50%, 0);
-                width: 89%;
-                height: 16%;
-                padding: 0px 10px;
-                display: flex;
-                justify-content: space-between;
-                align-items: center;
-                background: var(--image-contetn) no-repeat center;
-                background-size: 100% 100%;
-
-                .text-t {
-                    width: 17%;
-                    color: #fff;
-                    font-size: 12px;
-                }
-
-                .text-v {
-                    width: 83%;
-                    font-family: 'douyuFont';
-                    font-size: 10px;
-                    color: var(--vent-table-action-link);
-                    display: flex;
-                    justify-content: flex-end;
-                }
-            }
-
-            .box-item1 {
-                top: 24%;
-            }
-
-            .box-item2 {
-                top: 50%;
-            }
-
-            .box-item3 {
-                top: 75%;
-            }
+        align-items: center;
+        background: var(--image-contetn) no-repeat center;
+        background-size: 100% 100%;
+
+        .text-t {
+          width: 17%;
+          color: #fff;
+          font-size: 12px;
         }
 
-        .content-box1 {
-            position: relative;
-            width: 16%;
-            height: 100%;
-            background: var(--image-2) no-repeat center;
-            background-size: 100% 100%;
-            cursor: pointer;
-
-            .btn-label {
-                position: absolute;
-                left: 50%;
-                top: 0;
-                transform: translate(-50%);
-                font-size: 16px;
-                color: #fff;
-            }
-
-            .box-item {
-                position: absolute;
-                left: 50%;
-                transform: translate(-50%, 0);
-                width: 89%;
-                height: 16%;
-                padding: 0px 10px;
-                display: flex;
-                justify-content: space-between;
-                align-items: center;
-                background: var(--image-contetn) no-repeat center;
-                background-size: 100% 100%;
-
-                .text-t {
-                    width: 17%;
-                    color: #fff;
-                    font-size: 12px;
-                }
-
-                .text-v {
-                    width: 83%;
-                    font-family: 'douyuFont';
-                    font-size: 10px;
-                    color: var(--vent-table-action-link);
-                    display: flex;
-                    justify-content: flex-end;
-                }
-            }
-
-            .box-item1 {
-                top: 19%;
-            }
-
-            .box-item2 {
-                top: 41%;
-            }
-
-            .box-item3 {
-                top: 63%;
-            }
+        .text-v {
+          width: 83%;
+          font-family: 'douyuFont';
+          font-size: 10px;
+          color: var(--vent-table-action-link);
+          display: flex;
+          justify-content: flex-end;
         }
+      }
+
+      .box-item1 {
+        top: 24%;
+      }
+
+      .box-item2 {
+        top: 50%;
+      }
+
+      .box-item3 {
+        top: 75%;
+      }
     }
 
-    .echart-box {
-        height: calc(72% - 41px);
-        border: 1px solid #114aac;
-
-        .title-f {
-            height: 40px;
-            padding: 0px 10px;
-            box-sizing: border-box;
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-
-            .title-text {
-                font-family: 'douyuFont';
-                font-size: 14px;
-                color: #fff;
-            }
+    .content-box1 {
+      position: relative;
+      width: 16%;
+      height: 100%;
+      background: var(--image-2) no-repeat center;
+      background-size: 100% 100%;
+      cursor: pointer;
+
+      .btn-label {
+        position: absolute;
+        left: 50%;
+        top: 0;
+        transform: translate(-50%);
+        font-size: 16px;
+        color: #fff;
+      }
+
+      .box-item {
+        position: absolute;
+        left: 50%;
+        transform: translate(-50%, 0);
+        width: 89%;
+        height: 16%;
+        padding: 0px 10px;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        background: var(--image-contetn) no-repeat center;
+        background-size: 100% 100%;
+
+        .text-t {
+          width: 17%;
+          color: #fff;
+          font-size: 12px;
         }
 
-        .echarts-box {
-            height: calc(100% - 40px);
+        .text-v {
+          width: 83%;
+          font-family: 'douyuFont';
+          font-size: 10px;
+          color: var(--vent-table-action-link);
+          display: flex;
+          justify-content: flex-end;
         }
+      }
+
+      .box-item1 {
+        top: 19%;
+      }
+
+      .box-item2 {
+        top: 41%;
+      }
+
+      .box-item3 {
+        top: 63%;
+      }
+    }
+  }
+
+  .echart-box {
+    height: calc(72% - 41px);
+    border: 1px solid #114aac;
+
+    .title-f {
+      height: 40px;
+      padding: 0px 10px;
+      box-sizing: border-box;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+
+      .title-text {
+        font-family: 'douyuFont';
+        font-size: 14px;
+        color: #fff;
+      }
+    }
+
+    .echarts-box {
+      height: calc(100% - 40px);
     }
+  }
 }
-</style>
+</style>

+ 52 - 81
src/views/vent/monitorManager/alarmMonitor/common/external-sgjc.vue

@@ -39,10 +39,10 @@
         </div>
         <div class="pl-10px pr-10px">
             <div class="mb-10px">
-                <AButton class="w-full" type="primary" @click="controlDevice('dianwei', 'zhi')">应急启动</AButton>
+                <AButton class="w-full" type="primary" @click="controlDevice()">应急启动</AButton>
             </div>
             <div>
-                <AButton class="w-full" type="primary" @click="controlDevice('dianwei', 'zhi')">应急关闭</AButton>
+                <AButton class="w-full" type="primary" @click="controlDevice()">应急关闭</AButton>
             </div>
         </div>
     </div>
@@ -80,91 +80,62 @@ let topContent = reactive({
 let $emit=defineEmits(['changeSelect','changeWarnLevel'])
 
 function changeSelect(val) {
-    selectData.value = val;
-    let data = bundletube.value.filter((v) => v.strinstallpos == selectData.value)[0];
-    topContent.time = data.readTime || '--';
-    topContent.warnLevel = data.syswarnLevel_str;
-    topContent.smokeJd = data.syswarnLevel_des;
-
-    // mbList[0].nd = data.readData.o2val || '--';
-    // mbList[1].nd = data.readData.coval || '--';
-    // mbList[2].nd = data.readData.co2val || '--';
-    // mbList[3].nd = data.readData.gasval || '--';
-    // mbList[4].nd = data.readData.chval || '--';
-    // mbList[5].nd = data.readData.ch2val || '--';
-    // mbList.forEach((el) => {
-    //     el.time1 = data ? data.readTime.substring(0, data.readTime.lastIndexOf(':')) : '--';
-    //     el.address = data ? data.strinstallpos : '--';
-    // });
-    // netStatus.value = data['netStatus'];
+  selectData.value = val;
+  const data = bundletube.value.find((v) => v.strinstallpos == selectData.value);
+  if (!data) return;
+  topContent.time = data.readTime || '--';
+  topContent.warnLevel = data.syswarnLevel_str;
+  topContent.smokeJd = data.syswarnLevel_des;
+  $emit('changeSelect', selectData.value);
+  $emit('changeWarnLevel', topContent.warnLevel);
 }
-// 设备控制
-function controlDevice(code, value?) {
-    const passWord = ref('');
-    Modal.confirm({
-        title: '是否确认执行该操作?',
-        maskClosable: true,
-        content: () => {
-            return h(Input, {
-                placeholder: '请输入密码',
-                type: 'password',
-                modelValue: passWord.value,
-                'onUpdate:value'(val) {
-                    passWord.value = val;
-                },
-            });
-        },
-        onOk() {
-            console.log('OK', passWord);
+
+function controlDevice() {
+  const passWord = ref('');
+  Modal.confirm({
+    title: '是否确认执行该操作?',
+    maskClosable: true,
+    content: () => {
+      return h(Input, {
+        placeholder: '请输入密码',
+        type: 'password',
+        modelValue: passWord.value,
+        'onUpdate:value'(val) {
+          passWord.value = val;
         },
-    });
-    // Promise.all(
-    //   [].map(({ deviceID, deviceType }) => {
-    //     return deviceControlApi({
-    //       deviceid: deviceID,
-    //       devicetype: deviceType,
-    //       paramcode: code,
-    //       value,
-    //       // password: passWord || globalConfig?.simulatedPassword,
-    //     });
-    //   })
-    // ).then((res) => {
-    //   // 模拟时开启
-    //   if (res.success) {
-    //     if (globalConfig.History_Type == 'remote') {
-    //       message.success('指令已下发至生产管控平台成功!');
-    //     } else {
-    //       message.success('指令已下发成功!');
-    //     }
-    //   } else {
-    //     message.error(res.message);
-    //   }
-    // });
+      });
+    },
+    onOk() {},
+  });
 }
 
-watch(() => props.externalTSgData, (newV, oldV) => {
-    bundletube.value = newV
-    if (bundletube.value.length != 0) {
-        selectList.value = bundletube.value.map((el) => {
-            return { label: el.strinstallpos, value: el.strinstallpos }
-        });
-        selectData.value = selectData.value ? selectData.value : selectList.value[0].value;
-
-        let dataVal = selectData.value ? bundletube.value.filter((v) => v.strinstallpos == selectData.value)[0] : bundletube.value[0];
-        topContent.temperature = props.temperatureData;
-        topContent.time = dataVal.readTime || '--';
-        topContent.warnLevel = dataVal.syswarnLevel_str;
-        topContent.smokeJd = dataVal.syswarnLevel_des;
-        $emit('changeSelect', selectData.value)
-        $emit('changeWarnLevel',topContent.warnLevel)
+watch(
+  () => props.externalTSgData,
+  (newV) => {
+    bundletube.value = (newV as any[]) || [];
+    if (bundletube.value.length !== 0) {
+      selectList.value = bundletube.value.map((el) => ({
+        label: el.strinstallpos,
+        value: el.strinstallpos,
+      }));
+      selectData.value = selectData.value || selectList.value[0].value;
+      const dataVal =
+        bundletube.value.find((v) => v.strinstallpos == selectData.value) || bundletube.value[0];
+      topContent.temperature = props.temperatureData;
+      topContent.time = dataVal.readTime || '--';
+      topContent.warnLevel = dataVal.syswarnLevel_str;
+      topContent.smokeJd = dataVal.syswarnLevel_des;
+      $emit('changeSelect', selectData.value);
+      $emit('changeWarnLevel', topContent.warnLevel);
     } else {
-        topContent.temperature = '0';
-        // topContent.position = '--';
-        topContent.time = '--';
-        topContent.warnLevel = '--';
-        topContent.smokeJd = '--';
+      topContent.temperature = '0';
+      topContent.time = '--';
+      topContent.warnLevel = '--';
+      topContent.smokeJd = '--';
     }
-}, { immediate: true })
+  },
+  { immediate: true }
+)
 
 </script>
 

+ 70 - 86
src/views/vent/monitorManager/alarmMonitor/common/fireWork.vue

@@ -1,114 +1,98 @@
 <template>
   <div class="fireWork">
-    <!-- 顶部区域 -->
     <div class="work-nav">
-      <internalFireTop :internalFireTopData="internalFireTopData"></internalFireTop>
+      <internalFireTop :internalFireTopData="internalFireTopData" />
     </div>
-    <!-- 中间区域 -->
     <div class="center-echart">
-      <internalFireCenter :internalFireCenterData="internalFireCenterData"></internalFireCenter>
+      <internalFireCenter :internalFireCenterData="internalFireCenterData" />
     </div>
-    <!-- 底部区域 -->
     <div class="bot-content">
-      <internalFireBot :internalFireBotData="internalFireBotData"></internalFireBot>
+      <internalFireBot :internalFireBotData="internalFireBotData" />
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-  import { ref, reactive, watch } from 'vue';
-  import internalFireTop from './internal-fire-top.vue';
-  import internalFireCenter from './internal-fire-center.vue';
-  import internalFireBot from './internal-fire-bot.vue';
-  // import BaseTab from '../../../gas/components/tab/baseTab.vue';
+import { ref, watch } from 'vue';
+import internalFireTop from './internal-fire-top.vue';
+import internalFireCenter from './internal-fire-center.vue';
+import internalFireBot from './internal-fire-bot.vue';
 
-  let props = defineProps({
-    listData: Object,
-  });
-  let internalFireTopData = ref<any[]>([]);
-  let internalFireCenterData = ref<any[]>([]);
-  let internalFireBotData = ref<any[]>([]);
+const props = defineProps({
+  listData: Object,
+});
 
-  watch(
-    () => props.listData,
-    (val: any, val1) => {
-      if (JSON.stringify(val) === '{}') return;
-      internalFireTopData.value = val.fiber;
-      internalFireCenterData.value = val.fiber;
-      internalFireBotData.value = val.bundletube;
-    },
-    { deep: true }
-  );
+const internalFireTopData = ref<any[]>([]);
+const internalFireCenterData = ref<any[]>([]);
+const internalFireBotData = ref<any[]>([]);
+
+watch(
+  () => props.listData,
+  (val: any) => {
+    if (!val || JSON.stringify(val) === '{}') return;
+    internalFireTopData.value = val.fiber || [];
+    internalFireCenterData.value = val.fiber || [];
+    internalFireBotData.value = val.bundletube || [];
+  },
+  { deep: true, immediate: true }
+);
 </script>
 
 <style lang="less" scoped>
-  @import '/@/design/theme.less';
-
-  @{theme-deepblue} {
-    .fireWork {
-      --image-bj1: url('/@/assets/images/themify/deepblue/fire/bj1.png');
-      --image-max: url('/@/assets/images/themify/deepblue/fire/max.svg');
-      --image-min: url('/@/assets/images/themify/deepblue/fire/min.svg');
-      --image-pj: url('/@/assets/images/themify/deepblue/fire/pj.svg');
-      --image-bj1: url('/@/assets/images/themify/deepblue/fire/bj1.png');
-      --image-14174: url('/@/assets/images/themify/deepblue/fire/14174.png');
-      --image-contetn: url('/@/assets/images/themify/deepblue/fire/contetn.png');
-    }
-  }
+@import '/@/design/theme.less';
 
+@{theme-deepblue} {
   .fireWork {
-    --image-bj1: url('/@/assets/images/fire/bj1.png');
-    --image-max: url('/@/assets/images/fire/max.svg');
-    --image-min: url('/@/assets/images/fire/min.svg');
-    --image-pj: url('/@/assets/images/fire/pj.svg');
-    --image-bj1: url('/@/assets/images/fire/bj1.png');
-    --image-14174: url('/@/assets/images/fire/14174.png');
-    --image-contetn: url('/@/assets/images/fire/contetn.png');
-    --border-image-2: linear-gradient(to bottom, transparent, #024688, transparent);
-    width: 100%;
-    height: 100%;
-    padding: 20px;
-    box-sizing: border-box;
-
-    .work-nav {
-      height: 15%;
-      width: 100%;
-      margin-bottom: 20px;
-      background: var(--image-bj1) no-repeat center;
-      background-size: 100% 100%;
-    }
+    --image-bj1: url('/@/assets/images/themify/deepblue/fire/bj1.png');
+  }
+}
 
-    .center-echart {
-      width: 100%;
-      height: 32%;
-      padding: 10px;
-      margin-bottom: 20px;
-      box-sizing: border-box;
-      background: var(--image-bj1) no-repeat center;
-      background-size: 100% 100%;
-    }
+.fireWork {
+  --image-bj1: url('/@/assets/images/fire/bj1.png');
+  width: 100%;
+  height: 100%;
+  padding: 20px;
+  box-sizing: border-box;
 
-    .bot-content {
-      position: relative;
-      width: 100%;
-      height: calc(53% - 40px);
-      padding: 10px 10px 0px 10px;
-      box-sizing: border-box;
-      background: var(--image-bj1) no-repeat center;
-      background-size: 100% 100%;
-    }
+  .work-nav {
+    height: 15%;
+    width: 100%;
+    margin-bottom: 20px;
+    background: var(--image-bj1) no-repeat center;
+    background-size: 100% 100%;
   }
 
-  :deep(.zxm-select:not(.zxm-select-customize-input) .zxm-select-selector) {
-    border: 1px solid #3ad8ff77 !important;
-    background-color: #ffffff00 !important;
+  .center-echart {
+    width: 100%;
+    height: 32%;
+    padding: 10px;
+    margin-bottom: 20px;
+    box-sizing: border-box;
+    background: var(--image-bj1) no-repeat center;
+    background-size: 100% 100%;
   }
 
-  :deep(.zxm-select-selection-item) {
-    color: #fff !important;
+  .bot-content {
+    position: relative;
+    width: 100%;
+    height: calc(53% - 40px);
+    padding: 10px 10px 0;
+    box-sizing: border-box;
+    background: var(--image-bj1) no-repeat center;
+    background-size: 100% 100%;
   }
+}
 
-  :deep(.zxm-select-arrow) {
-    color: #fff;
-  }
+:deep(.zxm-select:not(.zxm-select-customize-input) .zxm-select-selector) {
+  border: 1px solid #3ad8ff77 !important;
+  background-color: #ffffff00 !important;
+}
+
+:deep(.zxm-select-selection-item) {
+  color: #fff !important;
+}
+
+:deep(.zxm-select-arrow) {
+  color: #fff;
+}
 </style>

+ 38 - 65
src/views/vent/monitorManager/alarmMonitor/common/gasCcb.vue

@@ -75,75 +75,56 @@
 </template>
 
 <script setup lang="ts">
-  import { onMounted, ref, watch, reactive } from 'vue';
+  import { ref, watch, reactive } from 'vue';
   import { getTableHeaderColumns } from '/@/hooks/web/useWebColumns';
 
-  let props = defineProps({
+  const props = defineProps({
     gasData: {
       type: Object,
-      default: () => {
-        return {};
-      },
+      default: () => ({}),
     },
   });
-  let typeData = ref<any[]>(['pump_under']);
-  let pumpUnder = reactive({});
-  let pumpOver = reactive({});
+
+  const typeData = ref<any[]>(['pump_under']);
+  const pumpUnder = reactive<any>({ label: '', data: [] });
+  const pumpOver = reactive<any>({ label: '', data: [] });
+
+  function buildPumpList(data: any[], deviceType: string, columnKey: string) {
+    const labelData = getTableHeaderColumns(columnKey);
+    const param = labelData.map((el: any) => ({
+      name: el.title,
+      val: el.dataIndex,
+    }));
+    const filterData = data.find((v) => v.deviceType == deviceType);
+    if (!filterData) return { label: '', data: [] };
+    return {
+      label: filterData.strinstallpos,
+      data: param.map((item) => ({
+        name: item.name,
+        val: filterData.readData[item.val] && filterData.readData[item.val] != '0' ? filterData.readData[item.val] : '-',
+      })),
+    };
+  }
 
   watch(
     () => props.gasData,
-    (newV, oldV) => {
-      console.log(newV, 'ccc---');
-      let data = newV.pump;
-
-      if (data && data.length != 0) {
-        typeData.value = data.map((v) => v.deviceType);
-
-        let list = {
-          label: '',
-          data: [],
-        };
-        if (typeData.value.includes('pump_under')) {
-          let labelData = getTableHeaderColumns('pump_under_monitor');
-          let param = labelData.map((el: any) => {
-            return {
-              name: el.title,
-              val: el.dataIndex,
-            };
-          });
-          let filterData = data.filter((v) => v.deviceType == 'pump_under')[0];
-          list.label = filterData.strinstallpos;
-          list.data = param.map((item) => {
-            return {
-              name: item.name,
-              val: filterData.readData[item.val] && filterData.readData[item.val] != '0' ? filterData.readData[item.val] : '-',
-            };
-          });
-          pumpUnder = Object.assign({}, list);
-        } else if (typeData.value.includes('pump_under_31')) {
-          let labelData = getTableHeaderColumns('pump_under_31_monitor');
-          let param = labelData.map((el: any) => {
-            return {
-              name: el.title,
-              val: el.dataIndex,
-            };
-          });
-          let filterData = data.filter((v) => v.deviceType == 'pump_under_31')[0];
-          list.label = filterData.strinstallpos;
-          list.data = param.map((item) => {
-            return {
-              name: item.name,
-              val: filterData.readData[item.val] && filterData.readData[item.val] != '0' ? filterData.readData[item.val] : '-',
-            };
-          });
-          pumpOver = Object.assign({}, list);
-        }
+    (newV: any) => {
+      const data = newV?.pump;
+      if (!data || data.length === 0) return;
+
+      typeData.value = data.map((v) => v.deviceType);
+      if (typeData.value.includes('pump_under')) {
+        const list = buildPumpList(data, 'pump_under', 'pump_under_monitor');
+        Object.keys(pumpUnder).forEach((key) => delete pumpUnder[key]);
+        Object.assign(pumpUnder, list);
+      } else if (typeData.value.includes('pump_under_31')) {
+        const list = buildPumpList(data, 'pump_under_31', 'pump_under_31_monitor');
+        Object.keys(pumpOver).forEach((key) => delete pumpOver[key]);
+        Object.assign(pumpOver, list);
       }
     },
-    { immediate: true }
+    { immediate: true, deep: true }
   );
-
-  onMounted(() => {});
 </script>
 
 <style lang="less" scoped>
@@ -151,19 +132,11 @@
 
   @{theme-deepblue} {
     .gas-ccb {
-      --image-no-choice: url('/@/assets/images/themify/deepblue/fire/no-choice.png');
-      --image-choice: url('/@/assets/images/themify/deepblue/fire/choice.png');
-      --image-border: url('/@/assets/images/themify/deepblue/fire/border.png');
-      --image-bj1: url('/@/assets/images/themify/deepblue/fire/bj1.png');
       --image-top-area: url('/@/assets/images/themify/deepblue/fire/gas-top.png');
     }
   }
 
   .gas-ccb {
-    --image-no-choice: url('/@/assets/images/fire/no-choice.png');
-    --image-choice: url('/@/assets/images/fire/choice.png');
-    --image-border: url('/@/assets/images/fire/border.png');
-    --image-bj1: url('/@/assets/images/fire/bj1.png');
     --image-top-area: url('/@/assets/images/fire/gas-top.png');
 
     width: 100%;

+ 141 - 192
src/views/vent/monitorManager/alarmMonitor/common/internal-fire-bot.vue

@@ -102,33 +102,45 @@
       </div>
     </div>
     <div class="echart-box">
-      <PredictionCurve v-if="shownChart === 'default'" :chart="curveConfig" />
-      <echartLine1 v-if="shownChart === 'predict'" :echartDataSg="echartDataSg" :maxY="maxY1"
-        :lengedDataName="echartDataSg.lengedDataName" />
+      <FiberTempChart
+        :x-axis-data="chartXData"
+        :series-data="chartYData"
+        :max-value="chartMaxValue"
+        :min-value="chartMinValue"
+        :title="chartTitle"
+        :y-axis-name="chartYAxisName"
+        :value-label="chartValueLabel"
+        :value-unit="chartValueUnit"
+        height="100%"
+        compact
+        :mark-symbol-size="32"
+        data-zoom-mode="index"
+        :data-zoom-start="0"
+        :data-zoom-end="10"
+      />
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, watch } from 'vue'
-import echartLine1 from './echartLine1.vue';
-import PredictionCurve from './predictionCurve.vue';
+import { ref, reactive, watch } from 'vue';
+import FiberTempChart from '/@/views/vent/testChart/FiberTempChart.vue';
 
-let props = defineProps({
+const props = defineProps({
   internalFireBotData: {
     type: Array,
     default: () => {
-      return []
-    }
-  }
-})
-const shownChart = ref('default');
-let selectData = ref('');//下拉选项数据
-let selectList = ref<any[]>([]);//下拉选项列表
+      return [];
+    },
+  },
+});
 
-let bundletubeData = ref<any[]>([]);
+const selectData = ref('');
+const selectList = ref<any[]>([]);
+const bundletubeData = ref<any[]>([]);
+const echartDataSgList = ref<any[]>([]);
 
-let bunData = reactive({
+const bunData = reactive({
   o2: '--',
   o2time: '--',
   c2h4: '--',
@@ -141,31 +153,32 @@ let bunData = reactive({
   co2time: '--',
   c2h2: '--',
   c2h2time: '--',
-})
-
-//束管图表数据
-let echartDataSgList = ref<any[]>([]);
-//束管监测-图表数据
-let echartDataSg = reactive({
-  xData: [],
-  lengedDataName: '(%)',
-  yData: [],
-  lengedData: 'O₂',
 });
 
-let maxY1 = ref<any>(0);
+const currentGas = ref('O₂');
+const currentUnit = ref('(%)');
 
-/** 预测曲线的配置,数据分别为平均、最大、最小、当前值 */
-const curveConfig = ref<any>({ id: '0', label: '', time: new Date(), data: [0, 0, 0, 0] });
+const chartXData = ref<any[]>([]);
+const chartYData = ref<number[]>([]);
+const chartMaxValue = ref(0);
+const chartMinValue = ref(0);
+const chartTitle = ref('O₂');
+const chartYAxisName = ref('浓度(%)');
+const chartValueLabel = ref('浓度');
+const chartValueUnit = ref('%');
+let lastChartSignature = '';
 
-//下拉选项切换
-function changeSelect(val) {
-  selectData.value = val;
-  let data = bundletubeData.value.filter((v) => v.strinstallpos == selectData.value)[0];
-  let time = data.readTime.substring(0, data.readTime.lastIndexOf(':'));
-  bunData.o2 = data.readData.o2val
+function parseUnit(dw: string) {
+  const text = String(dw || '').replace(/[()()]/g, '').trim();
+  return text || '';
+}
+
+function fillCardData(data: any) {
+  if (!data?.readData) return;
+  const time = data.readTime ? data.readTime.substring(0, data.readTime.lastIndexOf(':')) : '--';
+  bunData.o2 = data.readData.o2val;
   bunData.o2time = time;
-  bunData.c2h4 = data.readData.ch2val
+  bunData.c2h4 = data.readData.ch2val;
   bunData.c2h4time = time;
   bunData.co = data.readData.coval;
   bunData.cotime = time;
@@ -177,193 +190,131 @@ function changeSelect(val) {
   bunData.c2h2time = time;
 }
 
-//束管实时数据选项点击
+function getSelectedDevice() {
+  return bundletubeData.value.find((v) => v.strinstallpos == selectData.value) || bundletubeData.value[0];
+}
+
+function applyChartData(nextX: any[], nextY: number[], nextMax: number, nextMin: number, title: string, dw: string) {
+  const unit = parseUnit(dw);
+  const signature = JSON.stringify({
+    x: nextX,
+    y: nextY,
+    max: nextMax,
+    min: nextMin,
+    title,
+    dw,
+  });
+  if (signature === lastChartSignature) return;
+  lastChartSignature = signature;
+
+  chartXData.value = nextX;
+  chartYData.value = nextY;
+  chartMaxValue.value = nextMax;
+  chartMinValue.value = nextMin;
+  chartTitle.value = title;
+  chartYAxisName.value = unit ? `浓度(${unit})` : '浓度';
+  chartValueLabel.value = '浓度';
+  chartValueUnit.value = unit;
+}
+
+// 下拉选项切换
+function changeSelect(val) {
+  selectData.value = val;
+  const data = getSelectedDevice();
+  if (!data) return;
+  fillCardData(data);
+  echartDataSgList.value = data.history || [];
+  getSgClick(currentGas.value, currentUnit.value);
+}
+
+// 束管实时数据选项点击(取值逻辑与原先保持一致)
 function getSgClick(title, dw) {
-  echartDataSg.xData.length = 0;
-  echartDataSg.yData.length = 0;
-  echartDataSg.lengedData = title;
-  echartDataSg.lengedDataName = dw;
+  currentGas.value = title;
+  currentUnit.value = dw;
+
+  const data = getSelectedDevice();
+  if (!data) return;
+
+  const nextX: any[] = [];
+  const nextY: number[] = [];
+  let nextMax = 0;
+  let nextMin = 0;
+  const avg = data.avgParam || {};
 
-  const data = bundletubeData.value.filter((v) => v.strinstallpos == selectData.value)[0];
   switch (title) {
     case 'O₂':
       echartDataSgList.value.forEach((el) => {
-        echartDataSg.xData.push(el.time);
-        echartDataSg.yData.push(el.o2val);
+        nextX.push(el.time);
+        nextY.push(Number(el.o2val));
       });
-
-      curveConfig.value = {
-        id: 'o2',
-        label: 'O₂',
-        time: echartDataSg.xData,
-        data: [
-          // 氧气预测曲线
-          data.avgParam?.avg_o2_value || 0,
-          data.avgParam?.max_o2_value || 0,
-          data.avgParam?.min_o2_value || 0,
-          // data.readData?.o2val,
-        ],
-        monitorData: echartDataSg.yData,
-      };
-      getmaxY();
+      nextMax = Number(avg.max_o2_value || 0);
+      nextMin = Number(avg.min_o2_value || 0);
       break;
     case 'C₂H₄':
       echartDataSgList.value.forEach((el) => {
-        echartDataSg.xData.push(el.time);
-        echartDataSg.yData.push(el.ch2val);
+        nextX.push(el.time);
+        nextY.push(Number(el.ch2val));
       });
-      curveConfig.value = {
-        id: 'ch2',
-        label: 'C₂H₄',
-        time: echartDataSg.xData,
-        data: [
-          // 预测曲线
-          data.avgParam?.avg_c2h4_value || 0,
-          data.avgParam?.max_c2h4_value || 0,
-          data.avgParam?.min_c2h4_value || 0,
-          // data.readData?.ch2val,
-        ],
-        monitorData: echartDataSg.yData,
-      };
-      getmaxY();
+      nextMax = Number(avg.max_c2h4_value || 0);
+      nextMin = Number(avg.min_c2h4_value || 0);
       break;
     case 'CO':
       echartDataSgList.value.forEach((el) => {
-        echartDataSg.xData.push(el.time);
-        echartDataSg.yData.push(el.coval);
+        nextX.push(el.time);
+        nextY.push(Number(el.coval));
       });
-      curveConfig.value = {
-        id: 'co',
-        label: 'CO',
-        time: echartDataSg.xData,
-        data: [
-          // 预测曲线
-          data.avgParam?.avg_co_value || 0,
-          data.avgParam?.max_co_value || 0,
-          data.avgParam?.min_co_value || 0,
-          // data.readData?.coval,
-        ],
-        monitorData: echartDataSg.yData,
-      };
-      getmaxY();
+      nextMax = Number(avg.max_co_value || 0);
+      nextMin = Number(avg.min_co_value || 0);
       break;
     case 'CH₄':
       echartDataSgList.value.forEach((el) => {
-        echartDataSg.xData.push(el.time);
-        echartDataSg.yData.push(el.gasval);
+        nextX.push(el.time);
+        nextY.push(Number(el.gasval));
       });
-      curveConfig.value = {
-        id: 'ch4',
-        label: 'CH₄',
-        time: echartDataSg.xData,
-        data: [
-          // 预测曲线
-          data.avgParam?.avg_ch4_value || 0,
-          data.avgParam?.max_ch4_value || 0,
-          data.avgParam?.min_ch4_value || 0,
-          // data.readData?.chval,
-        ],
-        monitorData: echartDataSg.yData,
-      };
-      getmaxY();
+      nextMax = Number(avg.max_ch4_value || 0);
+      nextMin = Number(avg.min_ch4_value || 0);
       break;
     case 'CO₂':
       echartDataSgList.value.forEach((el) => {
-        echartDataSg.xData.push(el.time);
-        echartDataSg.yData.push(el.co2val);
+        nextX.push(el.time);
+        nextY.push(Number(el.co2val));
       });
-      curveConfig.value = {
-        id: 'co2',
-        label: 'CO₂',
-        time: echartDataSg.xData,
-        data: [
-          // 预测曲线
-          data.avgParam?.avg_co2_value || 0,
-          data.avgParam?.max_co2_value || 0,
-          data.avgParam?.min_co2_value || 0,
-          // data.readData?.co2val,
-        ],
-        monitorData: echartDataSg.yData,
-      };
-      getmaxY();
+      nextMax = Number(avg.max_co2_value || 0);
+      nextMin = Number(avg.min_co2_value || 0);
       break;
     case 'C₂H₂':
       echartDataSgList.value.forEach((el) => {
-        echartDataSg.xData.push(el.time);
-        echartDataSg.yData.push(el.chval);
+        nextX.push(el.time);
+        nextY.push(Number(el.chval));
       });
-      curveConfig.value = {
-        id: 'gas',
-        label: 'C₂H₂',
-        time: echartDataSg.xData,
-        data: [
-          // 预测曲线
-          data.avgParam?.avg_c2h2_value || 0,
-          data.avgParam?.max_c2h2_value || 0,
-          data.avgParam?.min_c2h2_value || 0,
-          // data.readData?.gasval,
-        ],
-        monitorData: echartDataSg.yData,
-      };
-      getmaxY();
+      nextMax = Number(avg.max_c2h2_value || 0);
+      nextMin = Number(avg.min_c2h2_value || 0);
       break;
   }
-}
 
-function getmaxY() {
-  maxY1.value = echartDataSg.yData.reduce((acr, cur) => {
-    return acr > cur ? acr : cur;
-  });
-  maxY1.value =
-    maxY1.value.toString().indexOf('.') == -1 ? maxY1.value.toString() : maxY1.value.toString().substring(0, maxY1.value.toString().indexOf('.'));
-  if (maxY1.value.length < 2 && Number(maxY1.value) < 1) {
-    maxY1.value = 1;
-  } else if (maxY1.value.length < 2 && Number(maxY1.value) >= 1) {
-    maxY1.value = 10;
-  } else if (maxY1.value.length < 3) {
-    maxY1.value = (Number(maxY1.value[0]) + 1) * 10;
-  } else if (maxY1.value.length < 4) {
-    maxY1.value = (Number(maxY1.value[0]) + 1) * 100;
-  } else if (maxY1.value.length < 5) {
-    maxY1.value = (Number(maxY1.value[0]) + 1) * 1000;
-  } else if (maxY1.value.length < 6) {
-    maxY1.value = (Number(maxY1.value[0]) + 1) * 10000;
-  }
+  applyChartData(nextX, nextY, nextMax, nextMin, title, dw);
 }
 
-watch(() => props.internalFireBotData, (newV, oldV) => {
-  echartDataSg.xData.length = 0;
-  echartDataSg.yData.length = 0;
-  if (newV.length != 0) {
-    bundletubeData.value = newV;
-    //下拉选项列表
+watch(
+  () => props.internalFireBotData,
+  (newV) => {
+    if (!newV || newV.length === 0) return;
+
+    bundletubeData.value = newV as any[];
     selectList.value = bundletubeData.value.map((el) => {
       return { label: el.strinstallpos, value: el.strinstallpos };
     });
-    //默认选中
-    selectData.value = selectData.value ? selectData.value : selectList.value[0].value;
-    let choiceData = selectData.value ? bundletubeData.value.filter((v) => v.strinstallpos == selectData.value)[0] : bundletubeData.value[0];
-
-    let time = choiceData.readTime.substring(0, choiceData.readTime.lastIndexOf(':'));
-    bunData.o2 = choiceData.readData.o2val
-    bunData.o2time = time;
-    bunData.c2h4 = choiceData.readData.ch2val
-    bunData.c2h4time = time;
-    bunData.co = choiceData.readData.coval;
-    bunData.cotime = time;
-    bunData.ch4 = choiceData.readData.gasval;
-    bunData.ch4time = time;
-    bunData.co2 = choiceData.readData.co2val;
-    bunData.co2time = time;
-    bunData.c2h2 = choiceData.readData.chval;
-    bunData.c2h2time = time;
-
-    echartDataSgList.value = choiceData.history;
-
-    getSgClick(echartDataSg.lengedData, echartDataSg.lengedDataName);
-    getmaxY();
-  }
-}, { immediate: true })
+    selectData.value = selectData.value ? selectData.value : selectList.value[0]?.value;
+
+    const choiceData = getSelectedDevice();
+    if (!choiceData) return;
+
+    fillCardData(choiceData);
+    echartDataSgList.value = choiceData.history || [];
+    getSgClick(currentGas.value, currentUnit.value);
+  },
+  { immediate: true, deep: true }
+);
 </script>
 
 <style lang="less" scoped>
@@ -402,8 +353,6 @@ watch(() => props.internalFireBotData, (newV, oldV) => {
     margin-bottom: 10px;
 
     .text {
-      // height: 30px;
-      // line-height: 30px;
       font-family: 'douyuFont';
       font-size: 14px;
       color: #fff;
@@ -488,4 +437,4 @@ watch(() => props.internalFireBotData, (newV, oldV) => {
     height: calc(100% - 50px);
   }
 }
-</style>
+</style>

+ 22 - 59
src/views/vent/monitorManager/alarmMonitor/common/internal-fire-bot1.vue

@@ -125,9 +125,7 @@
                     </div>
                 </div>
                 <div class="echarts-box">
-                    <PredictionCurve v-if="shownChart === 'default'" :chart="curveConfig" />
-                    <echartLine1 v-if="shownChart === 'predict'" :echartDataSg="echartDataSg1"
-                        :lengedDataName="echartDataSg1.lengedDataName" :maxY="maxY" :minY="minY" />
+                    <PredictionCurve :chart="curveConfig" />
                 </div>
             </div>
             <div class="right-echartbox">
@@ -139,9 +137,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, watch } from 'vue'
-import { getMaxY, getMinY } from '../common.data'
-import echartLine1 from './echartLine1.vue';
+import { ref, reactive, watch } from 'vue';
 import warnZb from './warnZb.vue';
 import PredictionCurve from './predictionCurve.vue';
 
@@ -194,19 +190,15 @@ let echartDataSg1 = reactive({
     lengedData: 'O₂',
     lengedDataName: '(%)',
 });
-let echartDataSgList = ref<any[]>([]);
-//设备连接状态
-let netStatus = ref(0);
-const shownChart = ref('default');
+const echartDataSgList = ref<any[]>([]);
+const netStatus = ref(0);
 /** 预测曲线的配置,数据分别为平均、最大、最小、当前值 */
 const curveConfig = ref<any>({ id: '0', label: '', time: new Date(), data: [0, 0, 0, 0] });
-let maxY = ref<any>(0);
-let minY = ref<any>(0)
-    let widthV = ref('75%');
-  let heightV = ref('80%');
-  let coordDw = ref<any[]>([31, 62, 110]);
-  let widthCanvas = ref(562);
-  let heightCanvas = ref(316);
+const widthV = ref('75%');
+const heightV = ref('80%');
+const coordDw = ref<any[]>([31, 62, 110]);
+const widthCanvas = ref(562);
+const heightCanvas = ref(316);
 
 //密闭参数选项切换
 function btnClick(item, ind) {
@@ -237,7 +229,6 @@ function btnClick(item, ind) {
                 ],
                 monitorData: echartDataSg1.yData,
             };
-            getmax();
             break;
         case 'C₂H₄':
             echartDataSgList.value.forEach((el) => {
@@ -257,7 +248,6 @@ function btnClick(item, ind) {
                 ],
                 monitorData: echartDataSg1.yData,
             };
-            getmax();
             break;
         case 'CO':
             echartDataSgList.value.forEach((el) => {
@@ -277,7 +267,6 @@ function btnClick(item, ind) {
                 ],
                 monitorData: echartDataSg1.yData,
             };
-            getmax();
             break;
         case 'CH₄':
             echartDataSgList.value.forEach((el) => {
@@ -297,7 +286,6 @@ function btnClick(item, ind) {
                 ],
                 monitorData: echartDataSg1.yData,
             };
-            getmax();
             break;
         case 'CO₂':
             echartDataSgList.value.forEach((el) => {
@@ -317,7 +305,6 @@ function btnClick(item, ind) {
                 ],
                 monitorData: echartDataSg1.yData,
             };
-            getmax();
             break;
         case 'C₂H₂':
             echartDataSgList.value.forEach((el) => {
@@ -337,59 +324,36 @@ function btnClick(item, ind) {
                 ],
                 monitorData: echartDataSg1.yData,
             };
-            getmax();
             break;
     }
 }
-function getmax() {
-    getMaxY(echartDataSg1.yData)
-    getMinY(echartDataSg1.yData)
-    // maxY.value = echartDataSg1.yData.reduce((acr, cur) => {
-    //   return acr > cur ? acr : cur;
-    // });
-    // maxY.value =
-    //   maxY.value.toString().indexOf('.') == -1 ? maxY.value.toString() : maxY.value.toString().substring(0, maxY.value.toString().indexOf('.'));
-    // if (maxY.value.length < 2 && Number(maxY.value) < 1) {
-    //   maxY.value = 1;
-    // } else if (maxY.value.length < 2 && Number(maxY.value) >= 1) {
-    //   maxY.value = 10;
-    // } else if (maxY.value.length < 3) {
-    //   maxY.value = (Number(maxY.value[0]) + 1) * 10;
-    // } else if (maxY.value.length < 4) {
-    //   maxY.value = (Number(maxY.value[0]) + 1) * 100;
-    // } else if (maxY.value.length < 5) {
-    //   maxY.value = (Number(maxY.value[0]) + 1) * 1000;
-    // } else if (maxY.value.length < 6) {
-    //   maxY.value = (Number(maxY.value[0]) + 1) * 10000;
-    // }
-}
 
-watch(() => props.internalFireBotData, (newV, oldV) => {
+watch(() => props.internalFireBotData, (newV) => {
     echartDataSg1.xData.length = 0;
     echartDataSg1.yData.length = 0;
     if (newV.length != 0 && props.selectData) {
-        bundletube.value = newV
-     
-        let dataVal =bundletube.value.filter((v) => v.strinstallpos == props.selectData)[0] 
-        let times = dataVal.readTime.substring(0, dataVal.readTime.lastIndexOf(':')) || '--';
-        let address = dataVal.strinstallpos || '--';
+        bundletube.value = newV as any[];
+        const dataVal = bundletube.value.find((v) => v.strinstallpos == props.selectData);
+        if (!dataVal) return;
+        const times = dataVal.readTime.substring(0, dataVal.readTime.lastIndexOf(':')) || '--';
+        const address = dataVal.strinstallpos || '--';
         bundleD.o2 = dataVal.readData.o2val || '--';
-        bundleD.o2time = times
-        bundleD.o2address = address
+        bundleD.o2time = times;
+        bundleD.o2address = address;
         bundleD.co = dataVal.readData.coval || '--';
-        bundleD.cotime = times
+        bundleD.cotime = times;
         bundleD.coaddress = address;
         bundleD.co2 = dataVal.readData.co2val || '--';
-        bundleD.co2time = times
+        bundleD.co2time = times;
         bundleD.co2address = address;
         bundleD.ch4 = dataVal.readData.gasval || '--';
-        bundleD.ch4time =times
+        bundleD.ch4time = times;
         bundleD.ch4address = address;
         bundleD.c2h2 = dataVal.readData.chval || '--';
-        bundleD.c2h2time = times
+        bundleD.c2h2time = times;
         bundleD.c2h2address = address;
         bundleD.c2h4 = dataVal.readData.ch2val || '--';
-        bundleD.c2h4time = times
+        bundleD.c2h4time = times;
         bundleD.c2h4address = address;
 
         echartDataSgList.value = dataVal.history;
@@ -401,7 +365,6 @@ watch(() => props.internalFireBotData, (newV, oldV) => {
             },
             activeIndex.value
         );
-        getmax();
     }
 }, { immediate: true })
 

+ 88 - 73
src/views/vent/monitorManager/alarmMonitor/common/internal-fire-center.vue

@@ -1,93 +1,108 @@
 <template>
-    <div class="internal-fire-center">
-        <div class="nav-title">
-            <div class="title">光纤测温系统温度实时监测</div>
-        </div>
-        <div class="echart-box">
-            <echartLine :echartDataGq="echartDataGq" :maxY="maxY" :echartDw="echartDw" />
-        </div>
+  <div class="internal-fire-center">
+    <div class="nav-title">
+      <div class="title">光纤测温系统温度实时监测</div>
     </div>
+    <div class="echart-box">
+      <FiberTempChart
+        :x-axis-data="xAxisData"
+        :series-data="seriesData"
+        :max-value="maxValue"
+        :min-value="minValue"
+        title=""
+        height="100%"
+        compact
+        :mark-symbol-size="36"
+        data-zoom-mode="index"
+        :data-zoom-start="0"
+        :data-zoom-end="20"
+      />
+    </div>
+  </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive,watch } from 'vue'
-import echartLine from './echartLine.vue';
-
-let props=defineProps({
-    internalFireCenterData:{
-        type:Array,
-        default:()=>{
-            return []
-        }
-    }
-})
+import { ref, watch } from 'vue';
+import FiberTempChart from '/@/views/vent/testChart/FiberTempChart.vue';
 
-let maxY = ref(100);
-let echartDw = ref('(­°C)');
-//光钎测温-图表数据
-let echartDataGq = reactive<any>({
-  curData: {
-    lengedData: '实时温度',
-    data: [],
-  },
-  maxData: {
-    lengedData: '最大温度',
-    data: [],
+const props = defineProps({
+  internalFireCenterData: {
+    type: Array,
+    default: () => {
+      return [];
+    },
   },
-  minData: {
-    lengedData: '最小温度',
-    data: [],
-  },
-  avgData: {
-    lengedData: '平均温度',
-    data: [],
-  },
-  xData: [],
 });
 
-watch(()=>props.internalFireCenterData,(newV,oldV)=>{
-    echartDataGq.xData.length = 0;
-    echartDataGq.curData.data.length = 0;
-    echartDataGq.maxData.data.length = 0;
-    echartDataGq.minData.data.length = 0;
-    echartDataGq.avgData.data.length = 0;
-    if(newV.length!=0){
-        const fiber: any = newV[0];
-        const arr: any[] = JSON.parse(fiber.readData.fibreTemperature);
+const xAxisData = ref<any[]>([]);
+const seriesData = ref<number[]>([]);
+const maxValue = ref(60);
+const minValue = ref(20);
+let lastSignature = '';
+
+watch(
+  () => props.internalFireCenterData,
+  (newV) => {
+    const nextX: any[] = [];
+    const nextSeries: number[] = [];
+    let nextMax = 60;
+    let nextMin = 20;
+
+    if (newV && newV.length !== 0) {
+      const fiber: any = newV[0];
+      const readData = fiber?.readData || {};
+      const fmax = Number(readData.fmax);
+      const fmin = Number(readData.fmin);
+      if (!Number.isNaN(fmax)) nextMax = fmax;
+      if (!Number.isNaN(fmin)) nextMin = fmin;
+
+      try {
+        const arr: any[] = JSON.parse(readData.fibreTemperature || '[]');
         arr.forEach((el) => {
-        echartDataGq.xData.push(el.pos);
-        echartDataGq.curData.data.push(el.value);
-        echartDataGq.maxData.data.push(fiber.avgParam?.max_temperature_value || 0);
-        echartDataGq.minData.data.push(fiber.avgParam?.min_temperature_value || 0);
-        echartDataGq.avgData.data.push(fiber.avgParam?.avg_temperature_value || 0);
-      });
+          nextX.push(el.pos);
+          nextSeries.push(Number(el.value));
+        });
+      } catch (e) {
+        console.warn('fibreTemperature parse error', e);
+      }
     }
-},{immediate:true})
+
+    // 定时器重复推送相同数据时不触发图表更新,避免闪烁
+    const signature = JSON.stringify({ x: nextX, s: nextSeries, max: nextMax, min: nextMin });
+    if (signature === lastSignature) return;
+    lastSignature = signature;
+
+    xAxisData.value = nextX;
+    seriesData.value = nextSeries;
+    maxValue.value = nextMax;
+    minValue.value = nextMin;
+  },
+  { immediate: true, deep: true }
+);
 </script>
 
 <style lang="less" scoped>
 .internal-fire-center {
-    position: relative;
-    width: 100%;
-    height: 100%;
+  position: relative;
+  width: 100%;
+  height: 100%;
 
-    .nav-title {
-        height: 30px;
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
+  .nav-title {
+    height: 30px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
 
-        .title {
-            font-family: 'douyuFont';
-            font-size: 14px;
-            color: #fff;
-            // color: var(--vent-table-action-link);
-        }
+    .title {
+      font-family: 'douyuFont';
+      font-size: 14px;
+      color: #fff;
     }
+  }
 
-    .echart-box {
-        width: 100%;
-        height: calc(100% - 30px);
-    }
+  .echart-box {
+    width: 100%;
+    height: calc(100% - 30px);
+  }
 }
-</style>
+</style>

+ 24 - 22
src/views/vent/monitorManager/alarmMonitor/common/internal-fire-top.vue

@@ -24,33 +24,35 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, watch } from 'vue'
+import { watch } from 'vue';
 import { topList } from '../common.data';
 import imgUrl from '/@/assets/images/fire/pie.png';
 
-let props = defineProps({
-    internalFireTopData: {
-        type: Array,
-        default: () => {
-            return []
-        }
-    }
-})
-
-watch(() => props.internalFireTopData, (newV, oldV) => {
-    if (newV.length != 0) {
-        const fiber: any = newV[0];
-        topList[0].value = fiber.readData.fmax;
-        topList[1].value = fiber.readData.fmin;
-        topList[2].value = fiber.readData.favg;
-        topList[3].text = fiber.warnFlag ? '报警' : '正常';
+const props = defineProps({
+  internalFireTopData: {
+    type: Array,
+    default: () => [],
+  },
+});
+
+watch(
+  () => props.internalFireTopData,
+  (newV) => {
+    if (newV && newV.length !== 0) {
+      const fiber: any = newV[0];
+      topList[0].value = fiber.readData?.fmax ?? '--';
+      topList[1].value = fiber.readData?.fmin ?? '--';
+      topList[2].value = fiber.readData?.favg ?? '--';
+      topList[3].text = fiber.warnFlag ? '报警' : '正常';
     } else {
-        topList[0].value = '--';
-        topList[1].value = '--';
-        topList[2].value = '--';
-        topList[3].text = '正常';
+      topList[0].value = '--';
+      topList[1].value = '--';
+      topList[2].value = '--';
+      topList[3].text = '正常';
     }
-}, { immediate: true })
+  },
+  { immediate: true }
+);
 </script>
 
 <style lang="less" scoped>

+ 19 - 21
src/views/vent/monitorManager/alarmMonitor/common/mainWell.vue

@@ -10,7 +10,6 @@
         :externalTSgData="externalTSgData"
         :temperatureData="temperatureData"
         @changeSelect="changeSelect"
-        @changeWarnLevel="changeWarnLevel"
       ></externalSgjc>
     </div>
 
@@ -30,13 +29,13 @@
 
     <!-- 束管监测  -->
     <div class="content-s" v-if="bundletubeData.bundletube.length != 0 && strType != 'sys_coalseam'">
-      <externalMb :externalTSgData="externalTSgData" :selectData="selectData" :warnLevel="warnLevel"></externalMb>
+      <externalMb :externalTSgData="externalTSgData" :selectData="selectData"></externalMb>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-  import { ref, computed, reactive, watch, h } from 'vue';
+  import { ref, reactive, watch } from 'vue';
   import externalGxcw from './external-gqcw.vue';
   import externalSgjc from './external-sgjc.vue';
   import externalThreeC from './external-threec.vue';
@@ -44,34 +43,33 @@
   import externalGxEchart from './external-gx-echart.vue';
   import externalMb from './external-mb.vue';
 
-  let props = defineProps({
+  const props = defineProps({
     listData: Object,
     strType: String,
   });
-  let bundletubeData = reactive({});
-  let externalTData = ref<any[]>([]); //top-光钎
-  let externalTSgData = ref<any[]>([]); //top-束管
-  let temperatureData = ref(''); //top-束管-温度
-  let selectData = ref('');
-  let warnLevel = ref('');
+
+  const bundletubeData = reactive<any>({
+    bundletube: [],
+    fiber: [],
+    temperature: [],
+  });
+  const externalTData = ref<any[]>([]);
+  const externalTSgData = ref<any[]>([]);
+  const temperatureData = ref('');
+  const selectData = ref('');
 
   function changeSelect(data) {
     selectData.value = data;
   }
-  function changeWarnLevel(data) {
-    warnLevel.value = data;
-  }
 
   watch(
     () => props.listData,
-    (val) => {
-      console.log(val, 'val---------------');
-      if (JSON.stringify(val) === '{}') return;
-      // props.listData = val;
-      bundletubeData = Object.assign({}, val);
-      externalTData.value = val.fiber;
-      externalTSgData.value = val.bundletube;
-      temperatureData.value = val.temperature[0] ? val.temperature[0].readData.temperature : '--';
+    (val: any) => {
+      if (!val || JSON.stringify(val) === '{}') return;
+      Object.assign(bundletubeData, val);
+      externalTData.value = val.fiber || [];
+      externalTSgData.value = val.bundletube || [];
+      temperatureData.value = val.temperature?.[0]?.readData?.temperature ?? '--';
     },
     { immediate: true, deep: true }
   );

+ 0 - 252
src/views/vent/monitorManager/alarmMonitor/common/measurePoint copy.vue

@@ -1,252 +0,0 @@
-<template>
-  <div class="content">
-    <div v-if="shown === 'default'" class="content-item">
-      <div class="card-content" v-for="(item, index) in cards" :key="`vmac${index}`">
-        <div class="item-l">
-          <div class="label-l">{{ item.label }}</div>
-          <div class="value-l">{{ item.value }}</div>
-        </div>
-        <div class="item-r">
-          <div class="content-r" v-for="el in item.listR" :key="el.id">
-            <span>{{ `${el.label} : ` }}</span>
-            <span
-              :class="{
-                'status-f': el.label.includes('状态') && el.value == 1,
-                'status-l': el.label.includes('状态') && el.value == 0,
-              }"
-            >
-              {{ el.label.includes('状态') ? (el.value == 1 ? '异常' : el.value == 0 ? '正常' : el.value) : el.value }}
-            </span>
-          </div>
-        </div>
-      </div>
-    </div>
-    <div v-if="shown === 'chart'" class="chart-item">
-      <div v-for="(item, index) in charts" :key="`acmt${index}`" class="chart-content">
-        <div class="text-center">
-          {{ item.label }}
-        </div>
-        <PredictionCurve :style="{ width: chartWidth || '474px' }" style="height: 300px; margin: 15px" :chart="item" />
-      </div>
-    </div>
-  </div>
-</template>
-<script setup lang="ts">
-  import { ref } from 'vue';
-  import BaseTab from '../../../gas/components/tab/baseTab.vue';
-  import PredictionCurve from './predictionCurve.vue';
-
-  // well i know this is trash but the time is reaching
-  defineProps<{
-    cards: { label: string; value: any; listR: { id: number; label: string; dw: string; value: any }[] }[];
-    charts: {
-      label: string;
-      time: Date;
-      data: [number, number, number, number];
-    }[];
-    title: string;
-    timeout?: number;
-    chartWidth?: string;
-  }>();
-
-  const shown = ref('default');
-  // const chartsConfig = ref<
-  //   {
-  //     label: string;
-  //     /** 下一项数据更新后应该替换配置中的哪项数据的标志 */
-  //     indexMark: number;
-  //     x: string[];
-  //     y1: number[];
-  //     y2: number[];
-  //     y3: number[];
-  //     y4: number[];
-  //   }[]
-  // >([]);
-
-  // watch(
-  //   () => props.charts,
-  //   () => {
-  //     const arr = new Array(20).fill(0);
-  //     props.charts.forEach((el, i) => {
-  //       if (chartsConfig.value[i]) {
-  //         // 由于上面这些数据都是 20 项组成的,当指针移动到 20 时说明上次更新了最后一项
-  //         // 那么应该按先进后出的队列模式更新数据了
-  //         const val = chartsConfig.value[i];
-  //         if (val.indexMark === 20) {
-  //           val.x.shift();
-  //           val.y1.shift();
-  //           val.y2.shift();
-  //           val.y3.shift();
-  //           val.y4.shift();
-  //           val.indexMark = 19;
-  //         }
-  //         val.x[val.indexMark] = moment(el.time).format('HH:mm:ss');
-  //         val.y1[val.indexMark] = el.data[0];
-  //         val.y2[val.indexMark] = el.data[1];
-  //         val.y3[val.indexMark] = el.data[2];
-  //         val.y4[val.indexMark] = el.data[3];
-  //         // 指针向后移动1
-  //         val.indexMark += 1;
-  //       } else {
-  //         // 更新配置
-  //         // 初始化配置数据,按照一项数据,生成一个由 20 项数据组成的数组,该数组由此项数据衍生
-  //         const startFrom = moment(el.time);
-  //         chartsConfig.value[i] = {
-  //           label: el.label,
-  //           indexMark: 1,
-  //           x: arr.map(() => {
-  //             const str = startFrom.format('HH:mm:ss');
-  //             startFrom.add(props.timeout || 3000);
-  //             return str;
-  //           }),
-  //           y1: arr.map(() => {
-  //             return el.data[0];
-  //           }),
-  //           y2: arr.map(() => {
-  //             return el.data[1];
-  //           }),
-  //           y3: arr.map(() => {
-  //             return el.data[2];
-  //           }),
-  //           y4: arr.map(() => {
-  //             return el.data[3];
-  //           }),
-  //         };
-  //       }
-  //     });
-  //   },
-  //   { immediate: true, deep: true }
-  // );
-</script>
-<style lang="less">
-  @import '/@/design/theme.less';
-
-  @{theme-deepblue} {
-    .content {
-      --image-bot-area: url('/@/assets/images/themify/deepblue/fire/bot-area.png');
-      --image-bot-area1: url('/@/assets/images/themify/deepblue/fire/bot-area1.png');
-    }
-  }
-  .content {
-    --image-bot-area: url('/@/assets/images/fire/bot-area.png');
-    --image-bot-area1: url('/@/assets/images/fire/bot-area1.png');
-    height: 100%;
-    color: var(--vent-font-color);
-
-    .title {
-      height: 30px;
-      margin-bottom: 10px;
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-
-      .text {
-        font-family: 'douyuFont';
-        font-size: 14px;
-      }
-    }
-
-    .content-item {
-      display: flex;
-      // justify-content: flex-start;
-      align-items: flex-start;
-      flex-wrap: wrap;
-      height: calc(100% - 50px);
-      overflow-y: auto;
-
-      .card-content {
-        position: relative;
-        width: 30%;
-        height: 128px;
-        margin: 0px 15px 15px 15px;
-        background: var(--image-bot-area) no-repeat center;
-        background-size: 100% 100%;
-
-        .item-l {
-          position: absolute;
-          left: 32px;
-          top: 50%;
-          transform: translate(0, -50%);
-          width: 89px;
-          height: 98px;
-          background: var(--image-bot-area1) no-repeat center;
-
-          .label-l {
-            position: absolute;
-            left: 50%;
-            top: 7px;
-            color: var(--vent-font-color);
-            font-size: 14px;
-            transform: translate(-50%, 0);
-          }
-
-          .value-l {
-            position: absolute;
-            left: 50%;
-            top: 50px;
-            transform: translate(-50%, 0);
-            font-family: 'douyuFont';
-            font-size: 14px;
-            color: var(--vent-table-action-link);
-          }
-        }
-
-        .item-r {
-          position: absolute;
-          left: 132px;
-          top: 50%;
-          transform: translate(0, -50%);
-          height: 128px;
-          padding: 5px 0px;
-          display: flex;
-          flex-direction: column;
-          justify-content: space-around;
-          box-sizing: border-box;
-
-          .content-r {
-            display: flex;
-
-            span {
-              font-size: 14px;
-              color: var(--vent-font-color);
-
-              &:first-child {
-                display: inline-block;
-                width: 68px;
-              }
-
-              &:last-child {
-                display: inline-block;
-                width: calc(100% - 68px);
-              }
-            }
-
-            .status-f {
-              color: #ff0000;
-            }
-
-            .status-l {
-              color: var(--vent-table-action-link);
-            }
-          }
-        }
-      }
-    }
-
-    .chart-item {
-      display: flex;
-      justify-content: flex-start;
-      align-items: flex-start;
-      flex-wrap: wrap;
-      height: calc(100% - 50px);
-      overflow-y: auto;
-
-      .chart-content {
-        // border: 1px solid var(--vent-base-border);
-        box-shadow: inset 0px 0px 10px 1px var(--vent-modal-box-shadow);
-        padding: 10px 0;
-        margin: 0 5px 5px 5px;
-      }
-    }
-  }
-</style>

+ 14 - 19
src/views/vent/monitorManager/alarmMonitor/common/top-area.vue

@@ -53,22 +53,19 @@
   import { ventTunColumns } from '../alarm.data';
   import MonitorTable from '../../comment/MonitorTable.vue';
   import echartLine from '../common/echartLine.vue';
-  let props = defineProps({
+
+  const props = defineProps({
     activeIndex: {
       type: Number,
       default: 0,
     },
     statusData: {
       type: Array,
-      default: () => {
-        return [];
-      },
+      default: () => [],
     },
     echartData: {
       type: Object,
-      default: () => {
-        return {};
-      },
+      default: () => ({}),
     },
     maxY: {
       type: Number,
@@ -80,31 +77,29 @@
     },
     ventTunDataSource: {
       type: Array,
-      default: () => {
-        return [];
-      },
+      default: () => [],
     },
   });
 
-  let statusD = ref<any[]>([]);
-  let echartD = reactive({});
-  let echartDw = ref('(m³/min)');
+  const statusD = ref<any[]>([]);
+  const echartD = reactive<any>({});
+  const echartDw = ref('(m³/min)');
 
   watch(
     () => props.statusData,
-    (newV, oldV) => {
-      statusD.value = newV;
+    (newV) => {
+      statusD.value = (newV as any[]) || [];
     },
     { immediate: true }
   );
 
   watch(
     () => props.echartData,
-    (newE, oldE) => {
-      console.log(newE, '999');
-      echartD = Object.assign({}, newE);
+    (newE) => {
+      Object.keys(echartD).forEach((key) => delete echartD[key]);
+      Object.assign(echartD, newE || {});
     },
-    { immediate: true }
+    { immediate: true, deep: true }
   );
 </script>
 

+ 159 - 259
src/views/vent/monitorManager/alarmMonitor/warn/dustWarn.vue

@@ -13,7 +13,7 @@
       >
       <div class="alarm-menu">
         <div class="card-btn">
-          <div :class="activeIndex1 == ind ? 'btn1' : 'btn'" v-for="(item, ind) in menuList" :key="ind" @click="cardClick(ind, item)">
+          <div :class="activeIndex1 == ind ? 'btn1' : 'btn'" v-for="(item, ind) in menuList" :key="ind" @click="cardClick(ind)">
             <div class="text">{{ item.name }}</div>
             <div class="warn">{{ item.warn }}</div>
           </div>
@@ -54,30 +54,23 @@
 
 <script setup lang="ts">
   import { ref, reactive, onMounted, onUnmounted } from 'vue';
-  import { sysTypeWarnList, sysWarn, getDevice } from '../common.api';
+  import { sysTypeWarnList, getDevice } from '../common.api';
   import echartLine from '../common/echartLine.vue';
   import { useSystemSelect } from '/@/hooks/vent/useSystemSelect';
   import { useRouter } from 'vue-router';
   import CustomHeader from '/@/components/vent/customHeader.vue';
   import { usePermission } from '/@/hooks/web/usePermission';
-  import { useGlobSetting } from '/@/hooks/setting';
   import MeasurePoint from '../common/measurePoint.vue';
   import { getMaxY, getMinY } from '../common.data';
 
-  const glob = useGlobSetting();
   const { hasPermission } = usePermission();
-  const { options, optionValue, getSelectRow, getSysDataSource } = useSystemSelect('sys_surface_caimei'); // 参数为场景类型(设备类型管理中可以查询到)
-  //左侧数据列表
-  let menuList = ref<any[]>([]);
-  //当前左侧激活菜单的索引
-  let activeIndex1 = ref(0);
-  //顶部区域激活选项
-  let activeIndex = ref(0);
-  //顶部区域数据
-  let topAreaList = reactive<any[]>([]);
-  let choiceData = reactive<any[]>([]);
-  //粉尘图表数据
-  let echartDataFc = reactive<any>({
+  const { options, optionValue, getSelectRow } = useSystemSelect('sys_surface_caimei');
+  const menuList = ref<any[]>([]);
+  const activeIndex1 = ref(0);
+  const activeIndex = ref(0);
+  const topAreaList = reactive<any[]>([]);
+  let choiceData: any[] = [];
+  const echartDataFc = reactive<any>({
     maxData: {
       lengedData: '实时值(mg/m³)',
       data: [],
@@ -92,10 +85,10 @@
     },
     xData: [],
   });
-  let maxY = ref<any>(0);
-  let minY = ref<any>(0);
-  let echartDw = ref('(mg/m³)');
-  let gridV = reactive({
+  const maxY = ref<any>(0);
+  const minY = ref<any>(0);
+  const echartDw = ref('(mg/m³)');
+  const gridV = reactive({
     top: '12%',
     left: '2%',
     bottom: '5%',
@@ -104,259 +97,193 @@
   });
   const cardListTf = ref<any[]>([]);
   const chartListTf = ref<any[]>([]);
-  let router = useRouter();
-  let echartNow = ref<any[]>([]);
-  let echartYc = reactive<any[]>([]);
+  const router = useRouter();
+  const echartNow = ref<any[]>([]);
+  const echartYc = reactive<any[]>([]);
   let flag = ref(true);
 
-  // https获取监测数据
   let timer: null | NodeJS.Timeout = null;
-  function getMonitor(flag?) {
+
+  function clearPollTimer() {
+    if (timer) {
+      clearTimeout(timer);
+      timer = null;
+    }
+  }
+
+  function clearChartSeries() {
+    echartDataFc.maxData.data.length = 0;
+    echartDataFc.minData.data.length = 0;
+    echartDataFc.aveValue.data.length = 0;
+    echartDataFc.xData.length = 0;
+  }
+
+  function parseExpectInfo(index: number) {
+    const raw = choiceData[index]?.readData?.expectInfo;
+    if (!raw) return null;
+    try {
+      return typeof raw === 'string' ? JSON.parse(raw) : raw;
+    } catch (e) {
+      return null;
+    }
+  }
+
+  function fillChartByIndex(index: number, resetNowList = false) {
+    const expectInfo = parseExpectInfo(index);
+    if (!expectInfo) return;
+
+    if (resetNowList || flag.value) {
+      echartNow.value = expectInfo['list'] || [];
+      flag.value = false;
+    }
+
+    echartYc.push({
+      time: expectInfo['nowTime'],
+      value: expectInfo['nowVal'],
+    });
+
+    const setData = [...echartNow.value, ...echartYc].sort(
+      (a, b) => Date.parse(String(a.time)) - Date.parse(String(b.time))
+    );
+
+    clearChartSeries();
+    const aveVal = expectInfo['aveVal'];
+    const warnVal = expectInfo['fmin'] ? expectInfo['fmin'] : 0;
+    setData.forEach((el) => {
+      if (el.value && el.value != '0') {
+        echartDataFc.xData.push(el.time);
+        echartDataFc.maxData.data.push(el.value);
+        echartDataFc.minData.data.push(aveVal);
+        echartDataFc.aveValue.data.push(warnVal);
+      }
+    });
+
+    const echartD = [echartDataFc.maxData.data, echartDataFc.minData.data];
+    maxY.value = getMaxY(echartD);
+    minY.value = getMinY(echartD);
+  }
+
+  function getMonitor(immediate?) {
+    clearPollTimer();
     timer = setTimeout(
       () => {
         getMenuList();
         getWindDeviceList();
-        if (timer) {
-          timer = null;
-        }
+        timer = null;
         getMonitor(false);
       },
-      flag ? 0 : 1000
+      immediate ? 0 : 1000
     );
   }
-  //返回首页
+
   function getBack() {
     router.push('/monitorChannel/monitor-alarm-home');
   }
-  //菜单选项切换
-  function cardClick(ind, item) {
-    clearTimeout(timer);
+
+  function cardClick(ind) {
+    clearPollTimer();
     activeIndex1.value = ind;
     getMonitor(true);
   }
-  //顶部区域选项切换
+
   function topAreaClick(index) {
     activeIndex.value = index;
-    echartDataFc.maxData.data.length = 0;
-    echartDataFc.minData.data.length = 0;
-    echartDataFc.aveValue.data.length = 0;
-    echartDataFc.xData.length = 0;
+    clearChartSeries();
     echartYc.length = 0;
     flag.value = true;
-    if (flag.value) {
-      echartNow.value = JSON.parse(choiceData[index].readData.expectInfo)['list'];
-      flag.value = false;
-    }
-    echartYc.push({
-      time: JSON.parse(choiceData[index].readData.expectInfo)['nowTime'],
-      value: JSON.parse(choiceData[index].readData.expectInfo)['nowVal'],
-    });
-    let setData = [...echartNow.value, ...echartYc].sort((a, b) => Date.parse(new Date(a.time)) - Date.parse(new Date(b.time)));
-    setData.forEach((el) => {
-      if (el.value && el.value != '0') {
-        echartDataFc.xData.push(el.time);
-        echartDataFc.maxData.data.push(el.value);
-        echartDataFc.minData.data.push(JSON.parse(choiceData[index].readData.expectInfo)['aveVal']);
-        echartDataFc.aveValue.data.push(
-          JSON.parse(choiceData[activeIndex.value].readData.expectInfo)['fmin']
-            ? JSON.parse(choiceData[activeIndex.value].readData.expectInfo)['fmin']
-            : 0
-        );
-      }
-    });
+    fillChartByIndex(index, true);
   }
-  //获取左侧菜单列表
+
   async function getMenuList() {
-    let res = await sysTypeWarnList({ type: 'dust' });
-    if (res.length != 0) {
-      const menuListTemp: any[] = [];
-      res.forEach((el) => {
-        menuListTemp.push({
-          name: el.systemname,
-          warn: el.warnDes,
-          deviceID: el.id,
-          strtype: el.strtype,
-          detail: el.detail,
-        });
-      });
-      menuList.value = menuListTemp;
-      getDetailList(menuList.value[activeIndex1.value].detail);
-    }
+    const res = await sysTypeWarnList({ type: 'dust' });
+    if (!res || res.length === 0) return;
+
+    menuList.value = res.map((el) => ({
+      name: el.systemname,
+      warn: el.warnDes,
+      deviceID: el.id,
+      strtype: el.strtype,
+      detail: el.detail,
+    }));
+    getDetailList(menuList.value[activeIndex1.value]?.detail);
   }
-  //获取右侧详情列表数据
+
   function getDetailList(param) {
     topAreaList.length = 0;
-    if (JSON.stringify(param) != '{}') {
-      param.dust.forEach((el) => {
-        topAreaList.push({
-          title: el.strinstallpos,
-          content: [
-            { ids: 0, label: '温度(°C)', value: el.readData.temperature || '--' },
-            { ids: 1, label: '粉尘浓度(mg/m³)', value: el.readData.dustval || '--' },
-            { ids: 2, label: '喷雾水压(mPa)', value: el.readData.waterPressure || '--' },
-            { ids: 3, label: '喷雾状态', value: el.readData.atomizingState || '--' },
-          ],
-        });
+    if (!param || JSON.stringify(param) === '{}' || !param.dust) return;
+
+    param.dust.forEach((el) => {
+      topAreaList.push({
+        title: el.strinstallpos,
+        content: [
+          { ids: 0, label: '温度(°C)', value: el.readData.temperature || '--' },
+          { ids: 1, label: '粉尘浓度(mg/m³)', value: el.readData.dustval || '--' },
+          { ids: 2, label: '喷雾水压(mPa)', value: el.readData.waterPressure || '--' },
+          { ids: 3, label: '喷雾状态', value: el.readData.atomizingState || '--' },
+        ],
       });
-      choiceData = param.dust;
+    });
+    choiceData = param.dust;
 
-      if (choiceData[activeIndex.value]) {
-        if (flag.value) {
-          echartNow.value = JSON.parse(choiceData[activeIndex.value].readData.expectInfo)['list'];
-          flag.value = false;
-        }
-        echartYc.push({
-          time: JSON.parse(choiceData[activeIndex.value].readData.expectInfo)['nowTime'],
-          value: JSON.parse(choiceData[activeIndex.value].readData.expectInfo)['nowVal'],
-        });
-        let setData = [...echartNow.value, ...echartYc].sort((a, b) => Date.parse(new Date(a.time)) - Date.parse(new Date(b.time)));
-        echartDataFc.maxData.data.length = 0;
-        echartDataFc.minData.data.length = 0;
-        echartDataFc.aveValue.data.length = 0;
-        echartDataFc.xData.length = 0;
-        setData.forEach((el) => {
-          if (el.value && el.value != '0') {
-            echartDataFc.xData.push(el.time);
-            echartDataFc.maxData.data.push(el.value);
-            echartDataFc.minData.data.push(JSON.parse(choiceData[activeIndex.value].readData.expectInfo)['aveVal']);
-            echartDataFc.aveValue.data.push(
-              JSON.parse(choiceData[activeIndex.value].readData.expectInfo)['fmin']
-                ? JSON.parse(choiceData[activeIndex.value].readData.expectInfo)['fmin']
-                : 0
-            );
-          }
-        });
-        let echartD = [echartDataFc.maxData.data, echartDataFc.minData.data];
-        maxY.value = getMaxY(echartD);
-        minY.value = getMinY(echartD);
-        // maxY.value = echartDataFc.maxData.data.reduce((acr, cur) => {
-        //   return acr > cur ? acr : cur;
-        // });
-        // maxY.value =
-        //   maxY.value.toString().indexOf('.') == -1 ? maxY.value.toString() : maxY.value.toString().substring(0, maxY.value.toString().indexOf('.'));
-        // if (maxY.value.length < 2 && Number(maxY.value) < 1) {
-        //   maxY.value = 1;
-        // } else if (maxY.value.length < 2 && Number(maxY.value) >= 1) {
-        //   maxY.value = 10;
-        // } else if (maxY.value.length < 3) {
-        //   maxY.value = (Number(maxY.value[0]) + 1) * 10;
-        // } else if (maxY.value.length < 4) {
-        //   maxY.value = (Number(maxY.value[0]) + 1) * 100;
-        // } else if (maxY.value.length < 5) {
-        //   maxY.value = (Number(maxY.value[0]) + 1) * 1000;
-        // } else if (maxY.value.length < 6) {
-        //   maxY.value = (Number(maxY.value[0]) + 1) * 10000;
-        // }
-      } else {
-        activeIndex.value = 0;
-        if (flag.value) {
-          echartNow.value = JSON.parse(choiceData[activeIndex.value].readData.expectInfo)['list'];
-          flag.value = false;
-        }
-        echartYc.push({
-          time: JSON.parse(choiceData[activeIndex.value].readData.expectInfo)['nowTime'],
-          value: JSON.parse(choiceData[activeIndex.value].readData.expectInfo)['nowVal'],
-        });
-        let setData = [...echartNow.value, ...echartYc].sort((a, b) => Date.parse(new Date(a.time)) - Date.parse(new Date(b.time)));
-        echartDataFc.maxData.data.length = 0;
-        echartDataFc.minData.data.length = 0;
-        echartDataFc.aveValue.data.length = 0;
-        echartDataFc.xData.length = 0;
-        setData.forEach((el) => {
-          if (el.value && el.value != '0') {
-            echartDataFc.xData.push(el.time);
-            echartDataFc.maxData.data.push(el.value);
-            echartDataFc.minData.data.push(JSON.parse(choiceData[activeIndex.value].readData.expectInfo)['aveVal']);
-            echartDataFc.aveValue.data.push(
-              JSON.parse(choiceData[activeIndex.value].readData.expectInfo)['fmin']
-                ? JSON.parse(choiceData[activeIndex.value].readData.expectInfo)['fmin']
-                : 0
-            );
-          }
-        });
-        let echartD = [echartDataFc.maxData.data, echartDataFc.minData.data];
-        maxY.value = getMaxY(echartD);
-        minY.value = getMinY(echartD);
-        // maxY.value = echartDataFc.maxData.data.reduce((acr, cur) => {
-        //   return acr > cur ? acr : cur;
-        // });
-        // maxY.value =
-        //   maxY.value.toString().indexOf('.') == -1 ? maxY.value.toString() : maxY.value.toString().substring(0, maxY.value.toString().indexOf('.'));
-        // if (maxY.value.length < 2) {
-        //   maxY.value = 10;
-        // } else if (maxY.value.length < 3) {
-        //   maxY.value = (Number(maxY.value[0]) + 1) * 10;
-        // } else if (maxY.value.length < 4) {
-        //   maxY.value = (Number(maxY.value[0]) + 1) * 100;
-        // } else if (maxY.value.length < 5) {
-        //   maxY.value = (Number(maxY.value[0]) + 1) * 1000;
-        // } else if (maxY.value.length < 6) {
-        //   maxY.value = (Number(maxY.value[0]) + 1) * 10000;
-        // }
-      }
+    if (!choiceData[activeIndex.value]) {
+      activeIndex.value = 0;
     }
+    if (!choiceData[activeIndex.value]) return;
+    fillChartByIndex(activeIndex.value);
   }
 
-  //获取粉尘监控测点信息
   async function getWindDeviceList() {
-    // cardListTf.length = 0;
     const cardListTfTemp: any[] = [];
     const chartListTfTemp: any[] = [];
-    let res = await getDevice({ devicetype: 'dusting', pagetype: 'normal' });
-    if (res && res.msgTxt[0]) {
-      let list = res.msgTxt[0].datalist || [];
-      if (list.length > 0) {
-        list.forEach((el: any) => {
-          const readData = el.readData;
-          el = Object.assign(el, readData);
-          cardListTfTemp.push({
-            label: '通信状态',
-            value: el.netStatus == '0' ? '断开' : '连接',
-            listR: [
-              { id: 0, label: '安装位置', dw: '', value: el.strinstallpos || '-' },
-              { id: 1, label: '粉尘浓度', dw: '(mg/m³)', value: el.dustval || '-' },
-              {
-                id: 2,
-                label: '巷道湿度',
-                dw: el.humidity && Number(el.humidity) < 1 ? '(RH)' : el.humidity && Number(el.humidity) > 1 ? '(%RH)' : '',
-                value: el.humidity || '-',
-              },
-              { id: 4, label: '巷道温度', dw: el.humidity ? '(℃)' : '', value: el.temperature || '-' },
-              {
-                id: 3,
-                label: '是否报警',
-                dw: '',
-                value: el.warnFlag == '0' ? '正常' : el.warnFlag == 1 ? '报警' : el.warnFlag == 2 ? '断开' : '未监测',
-              },
-            ],
-          });
-
-          // 初始化预测曲线配置,分别为x轴时间、平均、最大、最小、实时
-          const avgParam = el.avgParam || {
-            avg_dusting_value: 0,
-            max_dusting_value: 0,
-            min_dusting_value: 0,
-          };
-          chartListTfTemp.push({
-            label: el.strinstallpos,
-            time: new Date(),
-            data: [avgParam.avg_dusting_value, avgParam.max_dusting_value, avgParam.min_dusting_value, el.readData.dustval],
-          });
-        });
-      }
-    }
+    const res = await getDevice({ devicetype: 'dusting', pagetype: 'normal' });
+    const list = res?.msgTxt?.[0]?.datalist || [];
+    list.forEach((el: any) => {
+      const readData = el.readData || {};
+      Object.assign(el, readData);
+      cardListTfTemp.push({
+        label: '通信状态',
+        value: el.netStatus == '0' ? '断开' : '连接',
+        listR: [
+          { id: 0, label: '安装位置', dw: '', value: el.strinstallpos || '-' },
+          { id: 1, label: '粉尘浓度', dw: '(mg/m³)', value: el.dustval || '-' },
+          {
+            id: 2,
+            label: '巷道湿度',
+            dw: el.humidity && Number(el.humidity) < 1 ? '(RH)' : el.humidity && Number(el.humidity) > 1 ? '(%RH)' : '',
+            value: el.humidity || '-',
+          },
+          { id: 4, label: '巷道温度', dw: el.humidity ? '(℃)' : '', value: el.temperature || '-' },
+          {
+            id: 3,
+            label: '是否报警',
+            dw: '',
+            value: el.warnFlag == '0' ? '正常' : el.warnFlag == 1 ? '报警' : el.warnFlag == 2 ? '断开' : '未监测',
+          },
+        ],
+      });
+
+      const avgParam = el.avgParam || {};
+      chartListTfTemp.push({
+        label: el.strinstallpos,
+        time: new Date(),
+        data: [
+          avgParam.avg_dusting_value || 0,
+          avgParam.max_dusting_value || 0,
+          avgParam.min_dusting_value || 0,
+          readData.dustval,
+        ],
+      });
+    });
     cardListTf.value = cardListTfTemp;
     chartListTf.value = chartListTfTemp;
   }
+
   onMounted(() => {
-    getMenuList();
     getMonitor(true);
   });
+
   onUnmounted(() => {
-    if (timer) {
-      clearTimeout(timer);
-      timer = undefined;
-    }
+    clearPollTimer();
   });
 </script>
 
@@ -371,7 +298,6 @@
       --image-dust-choice: url('/@/assets/images/themify/deepblue/fire/dust-choice.png');
       --image-dust-content: url('/@/assets/images/themify/deepblue/fire/dust-content.png');
       --image-dust-choice1: url('/@/assets/images/themify/deepblue/fire/dust-choice1.png');
-      --image-dust-content: url('/@/assets/images/themify/deepblue/fire/dust-content.png');
       --image-bj1: url('/@/assets/images/themify/deepblue/fire/bj1.png');
     }
   }
@@ -383,7 +309,6 @@
     --image-dust-choice: url('/@/assets/images/fire/dust-choice.png');
     --image-dust-content: url('/@/assets/images/fire/dust-content.png');
     --image-dust-choice1: url('/@/assets/images/fire/dust-choice1.png');
-    --image-dust-content: url('/@/assets/images/fire/dust-content.png');
     --image-bj1: url('/@/assets/images/fire/bj1.png');
     width: 100%;
     height: 100%;
@@ -490,7 +415,6 @@
           height: 100%;
           display: flex;
           flex-direction: column;
-          // justify-content: space-around;
           align-items: flex-start;
           overflow-y: auto;
           overflow-x: hidden;
@@ -627,30 +551,6 @@
             height: calc(100% - 40px);
           }
         }
-
-        .content-right1 {
-          width: 100%;
-          height: 100%;
-
-          .title-t {
-            height: 30px;
-            margin-bottom: 10px;
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-
-            .text-t {
-              font-family: 'douyuFont';
-              font-size: 14px;
-              color: #fff;
-            }
-          }
-
-          .echart-boxd {
-            width: 100%;
-            height: calc(100% - 40px);
-          }
-        }
       }
     }
 

+ 119 - 165
src/views/vent/monitorManager/alarmMonitor/warn/fireWarn.vue

@@ -27,219 +27,176 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted, onUnmounted, defineAsyncComponent } from 'vue';
+import { ref, reactive, onMounted, onUnmounted } from 'vue';
 import { typeMenuList, componentName } from '../common.data';
-import { sysTypeWarnList, sysWarn } from '../common.api';
+import { sysTypeWarnList } from '../common.api';
 import { useSystemSelect } from '/@/hooks/vent/useSystemSelect';
 import { useGlobSetting } from '/@/hooks/setting';
 import { useRouter } from 'vue-router';
 import CustomHeader from '/@/components/vent/customHeader.vue';
 import { usePermission } from '/@/hooks/web/usePermission';
 
-let loading = ref(false); //加载状态
+const loading = ref(false);
 const { hasPermission } = usePermission();
-const { options, optionValue, getSelectRow, getSysDataSource } = useSystemSelect('sys_surface_caimei'); // 参数为场景类型(设备类型管理中可以查询到)
-//当前加载组件
-let currentLoad = ref('');
-//内外因火灾激活索引
-let activeIndex = ref(0);
-//当前激活菜单的索引
-let activeIndex1 = ref(0);
-let menuList = ref<any[]>([]); //左侧菜单列表
-let menuList1 = reactive({
-  external: [],
-  internal: [],
-  info: [],
+const { options, optionValue, getSelectRow } = useSystemSelect('sys_surface_caimei');
+const currentLoad = ref('');
+const activeIndex = ref(0);
+const activeIndex1 = ref(0);
+const menuList = ref<any[]>([]);
+const menuList1 = reactive({
+  external: [] as any[],
+  internal: [] as any[],
+  info: [] as any[],
 });
-//详情数据
-let listData = reactive({
+const listData = reactive({
   common: {},
-  bundletube: [],
-  fiber: [],
-  fire: [],
-  co: [],
-  smoke: [],
-  spray: [],
-  temperature: [],
-}); //详情数据
-let strType = ref(''); //火灾外因-区别工作面和煤层
-let router = useRouter();
-let tableLists = ref<any[]>([]);
-
-// https获取监测数据
+  bundletube: [] as any[],
+  fiber: [] as any[],
+  fire: [] as any[],
+  co: [] as any[],
+  smoke: [] as any[],
+  spray: [] as any[],
+  temperature: [] as any[],
+});
+const strType = ref('');
+const router = useRouter();
+const tableLists = ref<any[]>([]);
+
 let timer: null | NodeJS.Timeout = null;
+
+function clearPollTimer() {
+  if (timer) {
+    clearTimeout(timer);
+    timer = null;
+  }
+}
+
 function getMonitor(flag?) {
   timer = setTimeout(
     async () => {
       await getMenuList();
-      if (timer) {
-        timer = null;
-      }
+      timer = null;
       getMonitor(false);
     },
     flag ? 0 : 1500
   );
 }
 
-//返回首页
 function getBack() {
   router.push('/monitorChannel/monitor-alarm-home');
 }
 
-//获取左侧菜单列表
 async function getMenuList() {
-  let res = await sysTypeWarnList({ type: 'fire' });
-  if (res.length != 0) {
-     menuList1.external = res.external
-    // menuList1.external = res.external.filter((v) => v.strtype != 'sys_surface_caimei');
-    menuList1.internal = res.internal;
-    menuList1.info = res.info;
-    if (!activeIndex.value && menuList1.internal && menuList1.internal.length > 0) {
-      menuList.value = menuList1.internal.map((el: any) => {
-        return {
-          name: el.systemname,
-          warn: el.warnDes,
-          type: 'on',
-          deviceID: el.id,
-          strtype: el.strtype,
-          detail: el.detail,
-        }
-      });
-      getDetailList(menuList.value[activeIndex1.value].detail);
-    } else if (activeIndex.value == 1 && menuList1.external && menuList1.external.length > 0) {
-      menuList.value = menuList1.external.map((el: any) => {
-        return {
-          name: el.systemname,
-          warn: el.warnDes,
-          type: 'out',
-          deviceID: el.id,
-          strtype: el.strtype,
-          detail: el.detail,
-        }
-      });
-      strType.value = menuList.value[activeIndex1.value].strtype;
-      getDetailList(menuList.value[activeIndex1.value].detail);
-    } else {
-      menuList.value = menuList1.info.map((el: any) => {
-        return {
-          name: el.sysname,
-          // warn: '正常',
-          deviceID: el.sysid,
-          strtype: '',
-          detail: el.list,
-        }
-      });
-      getDetailList(menuList.value[activeIndex1.value].detail);
-     
-    }
+  const res = await sysTypeWarnList({ type: 'fire' });
+  if (!res || res.length === 0) return;
+
+  menuList1.external = res.external || [];
+  menuList1.internal = res.internal || [];
+  menuList1.info = res.info || [];
+
+  if (!activeIndex.value && menuList1.internal.length > 0) {
+    menuList.value = menuList1.internal.map((el: any) => ({
+      name: el.systemname,
+      warn: el.warnDes,
+      type: 'on',
+      deviceID: el.id,
+      strtype: el.strtype,
+      detail: el.detail,
+    }));
+    getDetailList(menuList.value[activeIndex1.value]?.detail);
+  } else if (activeIndex.value === 1 && menuList1.external.length > 0) {
+    menuList.value = menuList1.external.map((el: any) => ({
+      name: el.systemname,
+      warn: el.warnDes,
+      type: 'out',
+      deviceID: el.id,
+      strtype: el.strtype,
+      detail: el.detail,
+    }));
+    strType.value = menuList.value[activeIndex1.value]?.strtype || '';
+    getDetailList(menuList.value[activeIndex1.value]?.detail);
+  } else if (activeIndex.value === 2 && menuList1.info.length > 0) {
+    menuList.value = menuList1.info.map((el: any) => ({
+      name: el.sysname,
+      deviceID: el.sysid,
+      strtype: '',
+      detail: el.list,
+    }));
+    getDetailList(menuList.value[activeIndex1.value]?.detail);
+  } else {
+    menuList.value = [];
   }
 }
-//获取右侧详情弹窗数据
+
 function getDetailList(param) {
-  if (activeIndex.value != 2) {
-    listData.bundletube = param.bundletube;
-    listData.fiber = param.fiber;
-    listData.fire = param.fire;
-    listData.co = param.co;
-    listData.smoke = param.smoke;
-    listData.spray = param.spray;
-    listData.temperature = param.temperature;
+  if (!param) return;
+  if (activeIndex.value !== 2) {
+    listData.bundletube = param.bundletube || [];
+    listData.fiber = param.fiber || [];
+    listData.fire = param.fire || [];
+    listData.co = param.co || [];
+    listData.smoke = param.smoke || [];
+    listData.spray = param.spray || [];
+    listData.temperature = param.temperature || [];
     loadZj();
   } else {
     tableLists.value = param;
     loadZb();
   }
 }
-//内外因火灾、预警指标选项切换
+
+function resetAndReload(extra?: () => void) {
+  loading.value = true;
+  setTimeout(() => {
+    clearPollTimer();
+    activeIndex1.value = 0;
+    currentLoad.value = '';
+    extra?.();
+    getMonitor(true);
+    loading.value = false;
+  }, 1000);
+}
+
 function fireMenuToggle(ind) {
   activeIndex.value = ind;
-   menuList.value=[]
-  switch (ind) {
-    case 0:
-      loading.value = true;
-      setTimeout(() => {
-        clearTimeout(timer);
-        activeIndex1.value = 0;
-        currentLoad.value = '';
-        getClearList();
-        getMonitor();
-        loading.value = false;
-      }, 1000);
-      break;
-    case 1:
-      loading.value = true;
-      setTimeout(() => {
-        clearTimeout(timer);
-        activeIndex1.value = 0;
-        currentLoad.value = '';
-        getClearList();
-        getMonitor();
-        loading.value = false;
-      }, 1000);
-      break;
-    case 2:
-      loading.value = true;
-      setTimeout(() => {
-        clearTimeout(timer);
-        activeIndex1.value = 0;
-        currentLoad.value = '';
-        getMonitor();
-        loading.value = false;
-      }, 1000);
-      break;
-  }
+  menuList.value = [];
+  resetAndReload(() => {
+    if (ind !== 2) getClearList();
+  });
 }
-//加载预警指标组件
+
 function loadZb() {
   const { sysOrgCode } = useGlobSetting();
-  switch (sysOrgCode) {
-    case 'sdmtjtbdmk': // 宝德
-      currentLoad.value = 'warnFireBd';
-      return currentLoad.value;
-    case 'sdmtjtbetmk': // 布尔台
-      currentLoad.value = 'warnFireBrt';
-      return currentLoad.value;
-    default:
-      currentLoad.value = 'warnFireBrt';
-      return currentLoad.value;
-  }
+  currentLoad.value = sysOrgCode === 'sdmtjtbdmk' ? 'warnFireBd' : 'warnFireBrt';
 }
-//菜单选项切换
+
 function cardClick(ind, item) {
-  if (activeIndex.value != 2) {
-    loading.value = true;
-    setTimeout(() => {
-      clearTimeout(timer);
-      activeIndex1.value = ind;
+  loading.value = true;
+  setTimeout(() => {
+    clearPollTimer();
+    activeIndex1.value = ind;
+    if (activeIndex.value !== 2) {
       getClearList();
       strType.value = item.strtype;
       currentLoad.value = '';
-      getMonitor();
-      loading.value = false;
-    }, 1000);
-  } else {
-    loading.value = true;
-    setTimeout(() => {
-      clearTimeout(timer);
-      activeIndex1.value = ind;
-      getMonitor();
-      loading.value = false;
-    }, 1000);
-  }
+    }
+    getMonitor(true);
+    loading.value = false;
+  }, 1000);
 }
-//加载组件
+
 function loadZj() {
-  if (!activeIndex.value && listData.fiber.length != 0 && listData.bundletube.length != 0) {
+  if (!activeIndex.value && listData.fiber.length !== 0 && listData.bundletube.length !== 0) {
     currentLoad.value = 'fireWork';
-  } else if (!activeIndex.value && listData.bundletube.length != 0) {
+  } else if (!activeIndex.value && listData.bundletube.length !== 0) {
     currentLoad.value = 'closeWall';
-  } else if (activeIndex.value && activeIndex.value != 2) {
+  } else if (activeIndex.value && activeIndex.value !== 2) {
     currentLoad.value = 'mainWell';
   } else {
     currentLoad.value = '';
   }
 }
-//清空数据
+
 function getClearList() {
   listData.common = {};
   listData.bundletube.length = 0;
@@ -252,14 +209,11 @@ function getClearList() {
 }
 
 onMounted(() => {
-  getMenuList();
-  getMonitor();
+  getMonitor(true);
 });
+
 onUnmounted(() => {
-  if (timer) {
-    clearTimeout(timer);
-    timer = undefined;
-  }
+  clearPollTimer();
 });
 </script>
 

+ 376 - 502
src/views/vent/monitorManager/alarmMonitor/warn/gasWarn.vue

@@ -1,20 +1,34 @@
 <template>
   <customHeader :options="options" @change="getSelectRow" :optionValue="optionValue"> 瓦斯监测预警 </customHeader>
   <div class="gasWarn">
-    <a-button v-if="!hasPermission('gasWarn:return')" preIcon="ant-design:rollback-outlined" type="text" size="small"
-      style="position: absolute; left: 15px; top: 15px; color: #fff" @click="getBack">
+    <a-button
+      v-if="!hasPermission('gasWarn:return')"
+      preIcon="ant-design:rollback-outlined"
+      type="text"
+      size="small"
+      style="position: absolute; left: 15px; top: 15px; color: #fff"
+      @click="getBack"
+    >
       返回
     </a-button>
     <div class="alarm-menu">
       <div class="type-btn">
-        <div :class="activeIndex == index ? 'btn1' : 'btn'" v-for="(item, index) in typeMenuListGas" :key="index"
-          @click="btnClick(index)">
+        <div
+          :class="activeIndex == index ? 'btn1' : 'btn'"
+          v-for="(item, index) in typeMenuListGas"
+          :key="index"
+          @click="btnClick(index)"
+        >
           {{ item.name }}
         </div>
       </div>
       <div class="card-btn">
-        <div :class="activeIndex1 == ind ? 'btn1' : 'btn'" v-for="(item, ind) in menuList" :key="ind"
-          @click="cardClick(ind, item)">
+        <div
+          :class="activeIndex1 == ind ? 'btn1' : 'btn'"
+          v-for="(item, ind) in menuList"
+          :key="ind"
+          @click="cardClick(ind, item)"
+        >
           <div class="text">{{ item.name }}</div>
           <div class="warn">{{ item.warn }}</div>
         </div>
@@ -22,7 +36,6 @@
     </div>
     <div class="gas-content">
       <div style="width: 100%; height: 100%" v-if="isShow == 'yjjc'">
-
         <div class="top-area" v-if="isShowTop">
           <div class="title-t">
             <div class="text-t">瓦斯抽采泵信息</div>
@@ -31,7 +44,6 @@
             <gasCcb :gasData="gasData" />
           </div>
         </div>
-
         <div :class="isShowTop ? 'bot-area' : 'bot-area1'">
           <MeasurePoint title="安全监控测点信息" :timeout="1000" :cards="cardListWs" :charts="chartListWs" />
         </div>
@@ -43,129 +55,149 @@
         <gasParamter />
       </div>
       <div style="width: 100%; height: 100%" v-else-if="isShow == 'gzzd'">
-        <faultDiagnose :timeData="gzTime" :topData="topData" :xAxisPropType="xAxisPropType"
-          :hisEchartData="hisEchartData" :echartData="echartData" @getSearchHis="getlistdaysData1" />
+        <faultDiagnose
+          :timeData="gzTime"
+          :topData="topData"
+          :xAxisPropType="xAxisPropType"
+          :hisEchartData="hisEchartData"
+          :echartData="echartData"
+          @getSearchHis="getlistdaysData1"
+        />
       </div>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted, onUnmounted, computed } from 'vue';
-import { sysTypeWarnList, sysWarn, getDevice, listdays, bjlist, getHistoryData } from '../common.api';
-import { useSystemSelect } from '/@/hooks/vent/useSystemSelect';
-import { useRouter } from 'vue-router';
-import CustomHeader from '/@/components/vent/customHeader.vue';
-import warnTargetGas from '../common/warnTargetGas.vue';
-import PipeWarn from '../common/PipeWarn.vue';
-import gasParamter from '../common/gasParamter.vue';
-import faultDiagnose from '../common/faultDiagnose.vue';
-import { getMonitorComponent, getMonitorFlag } from '../common.data';
-import { usePermission } from '/@/hooks/web/usePermission';
-import MeasurePoint from '../common/measurePoint.vue';
-import gasCcb from '../common/gasCcb.vue';
-import dayjs from 'dayjs';
-
-let typeMenuListGas = getMonitorComponent();
-let isflag = getMonitorFlag();
-const { hasPermission } = usePermission();
-const { options, optionValue, getSelectRow, getSysDataSource } = useSystemSelect('sys_surface_caimei'); // 参数为场景类型(设备类型管理中可以查询到)
-//当前左侧激活菜单的索引
-let activeIndex1 = ref(0);
-//左侧数据列表
-let menuList = reactive<any[]>([]);
-//瓦斯顶部区域
-let gasData = reactive({});
-//瓦斯监控列表数据
-let cardListWs = reactive<any[]>([]);
-const chartListWs = reactive<any[]>([]);
-let router = useRouter();
-//监测/指标激活索引
-let activeIndex = ref(0);
-let isShow = ref('yjjc');
-//故障诊断开始时间
-let gzTime = ref('');
-//故障诊断顶部区域数据
-let topData = ref<any[]>([]);
-//根据诊断类型判断调用哪个历史数据接口
-let zdType = ref('');
-let historySearch = reactive({
-  ttime_begin: '',
-  ttime_end: '',
-});
-//实时历史图表
-let xAxisPropType = ref('');
-let hisEchartData = ref<any[]>([]);
-//报警历史图表
-let echartData = reactive<any>({
-  maxData: {
-    lengedData: '',
-    data: [],
-  },
-  minData: {
-    lengedData: '',
-    data: [],
-  },
-  xData: [],
-});
-let isShowTop = ref(true)
-
-// https获取监测数据
-let timer: null | NodeJS.Timeout = null;
-function getMonitor(flag?) {
-  timer = setTimeout(
-    async () => {
-      await getMenuList();
-      if (timer) {
-        timer = null;
-      }
-      getMonitor(false);
+  import { ref, reactive, onMounted, onUnmounted } from 'vue';
+  import { sysTypeWarnList, getDevice, listdays, bjlist, getHistoryData } from '../common.api';
+  import { useSystemSelect } from '/@/hooks/vent/useSystemSelect';
+  import { useRouter } from 'vue-router';
+  import CustomHeader from '/@/components/vent/customHeader.vue';
+  import warnTargetGas from '../common/warnTargetGas.vue';
+  import gasParamter from '../common/gasParamter.vue';
+  import faultDiagnose from '../common/faultDiagnose.vue';
+  import { getMonitorComponent, getMonitorFlag } from '../common.data';
+  import { usePermission } from '/@/hooks/web/usePermission';
+  import MeasurePoint from '../common/measurePoint.vue';
+  import gasCcb from '../common/gasCcb.vue';
+  import dayjs from 'dayjs';
+
+  const typeMenuListGas = getMonitorComponent();
+  const isflag = getMonitorFlag();
+  const { hasPermission } = usePermission();
+  const { options, optionValue, getSelectRow } = useSystemSelect('sys_surface_caimei');
+  const activeIndex1 = ref(0);
+  const menuList = reactive<any[]>([]);
+  const gasData = reactive<any>({});
+  const cardListWs = reactive<any[]>([]);
+  const chartListWs = reactive<any[]>([]);
+  const router = useRouter();
+  const activeIndex = ref(0);
+  const isShow = ref('yjjc');
+  const gzTime = ref('');
+  const topData = ref<any[]>([]);
+  const zdType = ref('');
+  const historySearch = reactive({
+    ttime_begin: '',
+    ttime_end: '',
+  });
+  const xAxisPropType = ref('');
+  const hisEchartData = ref<any[]>([]);
+  const echartData = reactive<any>({
+    maxData: {
+      lengedData: '',
+      data: [],
     },
-    flag ? 0 : 1000
-  );
-}
-
-// https获取监测数据
-let timer1: null | NodeJS.Timeout = null;
-function getMonitor1(flag?) {
-  timer1 = setTimeout(
-    async () => {
-      await getBjlist();
-      if (timer1) {
-        timer1 = null;
-      }
-      getMonitor1(false);
+    minData: {
+      lengedData: '',
+      data: [],
     },
-    flag ? 0 : 1000
-  );
-}
-
-//返回首页
-function getBack() {
-  router.push('/monitorChannel/monitor-alarm-home');
-}
-
-//获取右侧详情数据列表
-function getDetailList(param) {
-  // listData.common = res;
-  cardListWs.length = 0;
-  chartListWs.length = 0;
-  if (JSON.stringify(param) != '{}') {
+    xData: [],
+  });
+  const isShowTop = ref(true);
+
+  let timer: null | NodeJS.Timeout = null;
+  let timer1: null | NodeJS.Timeout = null;
+
+  function clearPollTimer() {
+    if (timer) {
+      clearTimeout(timer);
+      timer = null;
+    }
+  }
+
+  function clearAlarmTimer() {
+    if (timer1) {
+      clearTimeout(timer1);
+      timer1 = null;
+    }
+  }
+
+  function clearAllTimers() {
+    clearPollTimer();
+    clearAlarmTimer();
+  }
+
+  function getMonitor(flag?) {
+    clearPollTimer();
+    timer = setTimeout(
+      async () => {
+        await getMenuList();
+        timer = null;
+        getMonitor(false);
+      },
+      flag ? 0 : 1000
+    );
+  }
+
+  function getMonitor1(flag?) {
+    clearAlarmTimer();
+    timer1 = setTimeout(
+      async () => {
+        await getBjlist();
+        timer1 = null;
+        getMonitor1(false);
+      },
+      flag ? 0 : 1000
+    );
+  }
+
+  function getBack() {
+    router.push('/monitorChannel/monitor-alarm-home');
+  }
+
+  function formatWarnLevel(level) {
+    const map = {
+      '101': '低风险',
+      '102': '一般风险',
+      '103': '较大风险',
+      '104': '重大风险',
+      '201': '报警',
+      '10000': '数据超限',
+      '1001': '网络中断',
+    };
+    return map[level] || '正常';
+  }
+
+  function getDetailList(param) {
+    cardListWs.length = 0;
+    chartListWs.length = 0;
+    if (!param || JSON.stringify(param) === '{}' || !param.gas) return;
+
     param.gas.forEach((el) => {
-      el.strinstallpos = el.strinstallpos.indexOf('&') == -1 ? el.strinstallpos : el.strinstallpos.substring(0, el.strinstallpos.indexOf('&'));
+      el.strinstallpos =
+        el.strinstallpos.indexOf('&') == -1 ? el.strinstallpos : el.strinstallpos.substring(0, el.strinstallpos.indexOf('&'));
       cardListWs.push({
         label: '甲烷',
         value: el.readData.gasC + '%' || '--',
-        // value: 0,
         listR: [
           { id: 0, label: '测点类型', dw: '', value: '瓦斯' },
           { id: 1, label: '测点位置', dw: '', value: el.strinstallpos || '--' },
-          // { id: 2, label: '数据时间', value: el.readData.datetime || '--' },
           { id: 2, label: '数据时间', dw: '', value: el.readTime || '--' },
           { id: 3, label: '测点状态', dw: '', value: el.warnFlag },
         ],
       });
-      // 初始化预测曲线配置,分别为x轴时间、平均、最大、最小、实时
       const avgParam = el.avgParam || {
         avg_gas_value: 0,
         max_gas_value: 0,
@@ -177,14 +209,12 @@ function getDetailList(param) {
         data: [avgParam.avg_gas_value, avgParam.max_gas_value, avgParam.min_gas_value, el.readData.gasC],
       });
     });
-
   }
-}
 
-//获取左侧菜单列表
-async function getMenuList() {
-  let res = await sysTypeWarnList({ type: 'gas' });
-  if (res.length != 0) {
+  async function getMenuList() {
+    const res = await sysTypeWarnList({ type: 'gas' });
+    if (!res || res.length === 0) return;
+
     menuList.length = 0;
     res.forEach((el) => {
       menuList.push({
@@ -195,16 +225,18 @@ async function getMenuList() {
         detail: el.detail,
       });
     });
-    gasData = Object.assign({}, menuList[activeIndex1.value].detail);
-    isShowTop.value = gasData.pump.length != 0 ? true : false
-    getDetailList(menuList[activeIndex1.value].detail);
+
+    const detail = menuList[activeIndex1.value]?.detail || {};
+    Object.keys(gasData).forEach((key) => delete gasData[key]);
+    Object.assign(gasData, detail);
+    isShowTop.value = !!(gasData.pump && gasData.pump.length);
+    getDetailList(detail);
   }
-}
-//寸草塔二矿左侧菜单
-async function getBjlist() {
-  let res = await bjlist({ devicetype: 'gasmonitor*', pageNo: 1, pageSize: 100, isok: '0' });
-  console.log(res, '寸草塔二矿左侧菜单');
-  if (res && res.records.length != 0) {
+
+  async function getBjlist() {
+    const res = await bjlist({ devicetype: 'gasmonitor*', pageNo: 1, pageSize: 100, isok: '0' });
+    if (!res?.records?.length) return;
+
     menuList.length = 0;
     res.records.forEach((el) => {
       menuList.push({
@@ -212,32 +244,31 @@ async function getBjlist() {
         warn: '故障',
         deviceID: el.deviceid,
         time: el.createTime,
-        // strtype: el.strtype,
-        // detail: el.detail
       });
     });
+
     gzTime.value = menuList[activeIndex1.value].time;
-    let index = gzTime.value.indexOf(' ');
-    let specificTime = dayjs(`${gzTime.value.substring(0, index)}T${gzTime.value.substring(index + 1)}`);
+    const index = gzTime.value.indexOf(' ');
+    const specificTime = dayjs(`${gzTime.value.substring(0, index)}T${gzTime.value.substring(index + 1)}`);
     if (!historySearch.ttime_begin && !historySearch.ttime_end) {
       historySearch.ttime_begin = specificTime.subtract(30, 'minute').format('YYYY-MM-DD HH:mm:ss');
       historySearch.ttime_end = specificTime.add(30, 'minute').format('YYYY-MM-DD HH:mm:ss');
     }
-    //获取实时数据-报警历史
-    getDeviceList(menuList[activeIndex1.value].deviceID);
-    //获取历史数据
-    getlistdaysData(menuList[activeIndex1.value].deviceID, historySearch);
+
+    const deviceID = menuList[activeIndex1.value].deviceID;
+    getDeviceList(deviceID);
+    getlistdaysData(deviceID, historySearch);
   }
-}
 
-//获取实时数据-寸草塔二矿-报警历史
-async function getDeviceList(param) {
-  let res = await getDevice({ ids: param, devicetype: 'gasmonitor' });
-  if (res && res.msgTxt.length != 0) {
+  async function getDeviceList(param) {
+    const res = await getDevice({ ids: param, devicetype: 'gasmonitor' });
+    if (!res?.msgTxt?.length) return;
+
     echartData.maxData.data.length = 0;
     echartData.minData.data.length = 0;
     echartData.xData.length = 0;
-    let data = res.msgTxt[0].datalist[0];
+
+    const data = res.msgTxt[0].datalist[0];
     topData.value = [
       {
         id: 0,
@@ -265,22 +296,7 @@ async function getDeviceList(param) {
         label: '报警状态',
         imgSrc: true,
         icon: 'alarm-warning',
-        value:
-          data.warnLevel == '101'
-            ? '低风险'
-            : data.warnLevel == '102'
-              ? '一般风险'
-              : data.warnLevel == '103'
-                ? '较大风险'
-                : data.warnLevel == '104'
-                  ? '重大风险'
-                  : data.warnLevel == '201'
-                    ? '报警'
-                    : data.warnLevel == '10000'
-                      ? '数据超限'
-                      : data.warnLevel == '1001'
-                        ? '网络中断'
-                        : '正常',
+        value: formatWarnLevel(data.warnLevel),
       },
       {
         id: 4,
@@ -290,414 +306,272 @@ async function getDeviceList(param) {
         value: data.netStatus == '0' ? '断开' : '连接',
       },
     ];
+
     zdType.value = data.stationtype || '';
-    let list = JSON.parse(data.readData.gasGlFaultDia);
+    const list = JSON.parse(data.readData.gasGlFaultDia);
     echartData.maxData.data = [list.y1 || 0, list.y2 || 0];
     echartData.minData.data = [list.y3 || 0, list.y4 || 0];
-    echartData.xData = [...new Set([list.x1, list.x2, list.x3, list.x4])].sort((a, b) => a - b);
+    echartData.xData = [...new Set([list.x1, list.x2, list.x3, list.x4])].sort((a: any, b: any) => a - b);
   }
-}
-//获取历史数据-寸草塔二矿
-async function getlistdaysData(Ids, param) {
-  if (zdType.value != 'redis') {
-    let res = await listdays({ strtype: 'gasmonitor_normal', gdeviceid: Ids, ...param });
-    console.log(res, '获取历史数据-寸草塔二矿');
-    xAxisPropType.value = 'ttime';
-    let data = res.datalist.records;
-    if (data.length != 0) {
+
+  async function getlistdaysData(Ids, param) {
+    if (zdType.value != 'redis') {
+      const res = await listdays({ strtype: 'gasmonitor_normal', gdeviceid: Ids, ...param });
+      xAxisPropType.value = 'ttime';
+      const data = res?.datalist?.records || [];
       data.forEach((el) => {
         Object.assign(el, el.readData);
       });
-    }
-    hisEchartData.value = data;
-    console.log(hisEchartData.value, '  hisEchartData.value');
-  } else {
-    let res = await getHistoryData({ pageNum: 1, pageSize: 100, deviceId: Ids, isEmployee: true, ...param });
-    console.log(res, '获取历史数据-寸草塔二矿');
-    xAxisPropType.value = 'time';
-    hisEchartData.value = res['records'];
-  }
-}
-async function getlistdaysData1(param) {
-  historySearch.ttime_begin = param.ttime_begin;
-  historySearch.ttime_end = param.ttime_end;
-  let res = await listdays({ strtype: 'gasmonitor_normal', gdeviceid: menuList[activeIndex1.value].deviceID, ...historySearch });
-  console.log(res, '获取历史数据-寸草塔二矿');
-
-  if (zdType.value != 'redis') {
-    let res = await listdays({ strtype: 'gasmonitor_normal', gdeviceid: menuList[activeIndex1.value].deviceID, ...historySearch });
-    console.log(res, '获取历史数据-寸草塔二矿');
-    xAxisPropType.value = 'ttime';
-    let data = res.datalist.records;
-    if (data.length != 0) {
-      data.forEach((el) => {
-        Object.assign(el, el.readData);
+      hisEchartData.value = data;
+    } else {
+      const res = await getHistoryData({
+        pageNum: 1,
+        pageSize: 100,
+        deviceId: Ids,
+        isEmployee: true,
+        ...param,
       });
+      xAxisPropType.value = 'time';
+      hisEchartData.value = res?.records || [];
     }
-    hisEchartData.value = data;
-  } else {
-    let res = await getHistoryData({ pageNum: 1, pageSize: 100, deviceId: menuList[activeIndex1.value].deviceID, isEmployee: true, ...param });
-    console.log(res, '获取历史数据-寸草塔二矿');
-    xAxisPropType.value = 'time';
-    hisEchartData.value = res['records'];
   }
-}
-
-//监测/预警指标选项切换
-function btnClick(ind) {
-  activeIndex.value = ind;
-  clearTimeout(timer);
-  clearTimeout(timer1)
-  switch (ind) {
-    case 0:
-      activeIndex1.value = 0;
+
+  async function getlistdaysData1(param) {
+    historySearch.ttime_begin = param.ttime_begin;
+    historySearch.ttime_end = param.ttime_end;
+    const deviceID = menuList[activeIndex1.value].deviceID;
+    await getlistdaysData(deviceID, { ...historySearch, ...param });
+  }
+
+  function btnClick(ind) {
+    activeIndex.value = ind;
+    clearAllTimers();
+    activeIndex1.value = 0;
+
+    if (ind === 0) {
       isShow.value = 'yjjc';
       getMonitor(true);
-      break;
-    case 1:
+    } else if (ind === 1) {
       if (isflag == '2') {
-        activeIndex1.value = 0;
         isShow.value = 'yjzb';
       } else if (isflag == '1') {
-        activeIndex1.value = 0;
         isShow.value = 'gzzd';
         getMonitor1(true);
       } else {
-        activeIndex1.value = 0;
         isShow.value = '';
       }
-      break;
-    case 2:
-      activeIndex1.value = 0;
+    } else if (ind === 2) {
       isShow.value = 'wscs';
-      break;
+    }
   }
-}
-
-//菜单选项切换
-function cardClick(ind, item) {
-  clearTimeout(timer);
-  clearTimeout(timer1)
-  activeIndex1.value = ind;
-  gzTime.value = item.time;
-  chartListWs.length = 0;
-  if (isflag != '1') {
-    getMonitor(true);
-  } else {
-    getMonitor1(true);
+
+  function cardClick(ind, item) {
+    clearAllTimers();
+    activeIndex1.value = ind;
+    gzTime.value = item.time;
+    chartListWs.length = 0;
+    if (isflag != '1') {
+      getMonitor(true);
+    } else {
+      getMonitor1(true);
+    }
   }
 
-}
+  onMounted(() => {
+    getMonitor(true);
+  });
 
-onMounted(() => {
-  getMenuList();
-  getMonitor(true);
-});
-onUnmounted(() => {
-  if (timer) {
-    clearTimeout(timer);
-    timer = undefined;
-  }
-  if (timer1) {
-    clearTimeout(timer1);
-    timer1 = undefined;
-  }
-});
+  onUnmounted(() => {
+    clearAllTimers();
+  });
 </script>
 
 <style lang="less" scoped>
-@import '/@/design/theme.less';
+  @import '/@/design/theme.less';
+
+  @{theme-deepblue} {
+    .gasWarn {
+      --image-no-choice: url('/@/assets/images/themify/deepblue/fire/no-choice.png');
+      --image-choice: url('/@/assets/images/themify/deepblue/fire/choice.png');
+      --image-border: url('/@/assets/images/themify/deepblue/fire/border.png');
+      --image-bj1: url('/@/assets/images/themify/deepblue/fire/bj1.png');
+    }
+  }
 
-@{theme-deepblue} {
   .gasWarn {
-    --image-no-choice: url('/@/assets/images/themify/deepblue/fire/no-choice.png');
-    --image-choice: url('/@/assets/images/themify/deepblue/fire/choice.png');
-    --image-border: url('/@/assets/images/themify/deepblue/fire/border.png');
-    --image-bj1: url('/@/assets/images/themify/deepblue/fire/bj1.png');
-    --image-top-area: url('/@/assets/images/themify/deepblue/fire/top-area.png');
-  }
-}
-
-.gasWarn {
-  --image-no-choice: url('/@/assets/images/fire/no-choice.png');
-  --image-choice: url('/@/assets/images/fire/choice.png');
-  --image-border: url('/@/assets/images/fire/border.png');
-  --image-bj1: url('/@/assets/images/fire/bj1.png');
-  --image-top-area: url('/@/assets/images/fire/top-area.png');
-  width: 100%;
-  height: 100%;
-  padding: 80px 10px 15px 10px;
-  box-sizing: border-box;
-  display: flex;
-  justify-content: space-between;
-
-  .alarm-menu {
+    --image-no-choice: url('/@/assets/images/fire/no-choice.png');
+    --image-choice: url('/@/assets/images/fire/choice.png');
+    --image-border: url('/@/assets/images/fire/border.png');
+    --image-bj1: url('/@/assets/images/fire/bj1.png');
+    width: 100%;
     height: 100%;
-    width: 15%;
-
-    .type-btn {
-      width: 100%;
-      height: 28px;
-      line-height: 28px;
-      border: 1px solid var(--vent-warn-tab-border);
-      margin-bottom: 20px;
-      border-radius: 5px;
-      box-sizing: border-box;
-      display: flex;
-      justify-content: space-between;
-
-      .btn {
-        width: 33.33%;
-        height: 100%;
-        font-size: 14px;
-        text-align: center;
-        color: #fff;
-        cursor: pointer;
-      }
+    padding: 80px 10px 15px 10px;
+    box-sizing: border-box;
+    display: flex;
+    justify-content: space-between;
 
-      .btn1 {
-        width: 33.33%;
-        height: 100%;
-        font-size: 14px;
-        color: #fff;
-        text-align: center;
-        border-radius: 2px;
-        background: var(--vent-warn-tab-bg-actived);
-        cursor: pointer;
-      }
-    }
+    .alarm-menu {
+      height: 100%;
+      width: 15%;
 
-    .card-btn {
-      width: 100%;
-      height: calc(100% - 48px);
-      overflow-y: auto;
-
-      .btn {
-        position: relative;
-        width: 81%;
-        height: 14%;
-        margin-bottom: 10%;
-        font-family: 'douyuFont';
-        background: var(--image-no-choice) no-repeat;
-        background-size: 100% 100%;
-        cursor: pointer;
-
-        .text {
-          width: 80%;
-          position: absolute;
-          left: 50%;
-          top: 28px;
-          font-size: 16px;
-          color: var(--vent-table-action-link);
-          text-align: center;
-          transform: translate(-50%, 0);
-        }
+      .type-btn {
+        width: 100%;
+        height: 28px;
+        line-height: 28px;
+        border: 1px solid var(--vent-warn-tab-border);
+        margin-bottom: 20px;
+        border-radius: 5px;
+        box-sizing: border-box;
+        display: flex;
+        justify-content: space-between;
 
-        .warn {
-          width: 100%;
-          position: absolute;
-          left: 50%;
-          bottom: 14px;
+        .btn {
+          width: 33.33%;
+          height: 100%;
           font-size: 14px;
-          color: #fff;
           text-align: center;
-          transform: translate(-50%, 0);
-        }
-      }
-
-      .btn1 {
-        position: relative;
-        width: 100%;
-        height: 14%;
-        margin-bottom: 10%;
-        font-family: 'douyuFont';
-        background: var(--image-choice) no-repeat;
-        background-size: 100% 100%;
-        cursor: pointer;
-
-        .text {
-          width: 80%;
-          position: absolute;
-          left: 50%;
-          top: 28px;
-          font-size: 16px;
-          color: var(--vent-table-action-link);
-          text-align: center;
-          transform: translate(-62%, 0);
+          color: #fff;
+          cursor: pointer;
         }
 
-        .warn {
-          width: 100%;
-          position: absolute;
-          left: 50%;
-          bottom: 14px;
+        .btn1 {
+          width: 33.33%;
+          height: 100%;
           font-size: 14px;
           color: #fff;
           text-align: center;
-          transform: translate(-60%, 0);
+          border-radius: 2px;
+          background: var(--vent-warn-tab-bg-actived);
+          cursor: pointer;
         }
       }
-    }
-  }
 
-  .gas-content {
-    position: relative;
-    width: calc(85% - 10px);
-    height: 100%;
-    margin-left: 10px;
-    padding: 15px;
-    background: var(--image-border) no-repeat;
-    background-size: 100% 100%;
-    box-sizing: border-box;
+      .card-btn {
+        width: 100%;
+        height: calc(100% - 48px);
+        overflow-y: auto;
 
-    .top-area {
-      height: 356px;
-      margin-bottom: 10px;
-      padding: 10px;
-      background: var(--image-bj1) no-repeat center;
-      background-size: 100% 100%;
-      box-sizing: border-box;
+        .btn {
+          position: relative;
+          width: 81%;
+          height: 14%;
+          margin-bottom: 10%;
+          font-family: 'douyuFont';
+          background: var(--image-no-choice) no-repeat;
+          background-size: 100% 100%;
+          cursor: pointer;
+
+          .text {
+            width: 80%;
+            position: absolute;
+            left: 50%;
+            top: 28px;
+            font-size: 16px;
+            color: var(--vent-table-action-link);
+            text-align: center;
+            transform: translate(-50%, 0);
+          }
 
-      .title-t {
-        height: 30px;
-        margin-bottom: 10px;
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
+          .warn {
+            width: 100%;
+            position: absolute;
+            left: 50%;
+            bottom: 14px;
+            font-size: 14px;
+            color: #fff;
+            text-align: center;
+            transform: translate(-50%, 0);
+          }
+        }
 
-        .text-t {
+        .btn1 {
+          position: relative;
+          width: 100%;
+          height: 14%;
+          margin-bottom: 10%;
           font-family: 'douyuFont';
-          font-size: 14px;
-          color: #fff;
-        }
-      }
+          background: var(--image-choice) no-repeat;
+          background-size: 100% 100%;
+          cursor: pointer;
+
+          .text {
+            width: 80%;
+            position: absolute;
+            left: 50%;
+            top: 28px;
+            font-size: 16px;
+            color: var(--vent-table-action-link);
+            text-align: center;
+            transform: translate(-62%, 0);
+          }
 
-      .content-t {
-        width: 100%;
-        height: 276px;
+          .warn {
+            width: 100%;
+            position: absolute;
+            left: 50%;
+            bottom: 14px;
+            font-size: 14px;
+            color: #fff;
+            text-align: center;
+            transform: translate(-60%, 0);
+          }
+        }
       }
     }
 
-    .bot-area {
-      height: calc(100% - 356px);
-      padding: 10px;
-      background: var(--image-bj1) no-repeat center;
-      background-size: 100% 100%;
-      box-sizing: border-box;
-    }
-
-    .bot-area1 {
+    .gas-content {
+      position: relative;
+      width: calc(85% - 10px);
       height: 100%;
-      padding: 10px 15px 0px 15px;
-      background: var(--image-bj1) no-repeat center;
+      margin-left: 10px;
+      padding: 15px;
+      background: var(--image-border) no-repeat;
       background-size: 100% 100%;
       box-sizing: border-box;
 
-      .title-b {
-        height: 30px;
+      .top-area {
+        height: 356px;
         margin-bottom: 10px;
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
+        padding: 10px;
+        background: var(--image-bj1) no-repeat center;
+        background-size: 100% 100%;
+        box-sizing: border-box;
 
-        .text-b {
-          font-family: 'douyuFont';
-          font-size: 14px;
-          color: #fff;
+        .title-t {
+          height: 30px;
+          margin-bottom: 10px;
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+
+          .text-t {
+            font-family: 'douyuFont';
+            font-size: 14px;
+            color: #fff;
+          }
+        }
+
+        .content-t {
+          width: 100%;
+          height: 276px;
         }
       }
 
-      .content-b {
-        width: 100%;
-        height: calc(100% - 40px);
-        overflow-y: auto;
+      .bot-area {
+        height: calc(100% - 356px);
+        padding: 10px;
+        background: var(--image-bj1) no-repeat center;
+        background-size: 100% 100%;
+        box-sizing: border-box;
+      }
 
-        .content-b-item {
-          display: flex;
-          justify-content: flex-start;
-          align-items: flex-start;
-          flex-wrap: wrap;
-
-          .card-b {
-            position: relative;
-            width: 30%;
-            height: 128px;
-            margin: 0px 15px 15px 15px;
-            background: var(--image-bot-area) no-repeat center;
-            background-size: 100% 100%;
-
-            .item-l {
-              position: absolute;
-              left: 32px;
-              top: 50%;
-              transform: translate(0, -50%);
-              width: 89px;
-              height: 98px;
-              background: var(--image-bot-area1) no-repeat center;
-
-              .label-l {
-                position: absolute;
-                left: 50%;
-                top: 7px;
-                font-size: 14px;
-                color: #fff;
-                transform: translate(-50%, 0);
-              }
-
-              .value-l {
-                position: absolute;
-                left: 50%;
-                top: 50px;
-                transform: translate(-50%, 0);
-                font-family: 'douyuFont';
-                font-size: 14px;
-                color: var(--vent-table-action-link);
-              }
-            }
-
-            .item-r {
-              position: absolute;
-              left: 132px;
-              top: 50%;
-              transform: translate(0, -50%);
-              height: 128px;
-              padding: 5px 0px;
-              display: flex;
-              flex-direction: column;
-              justify-content: space-around;
-              box-sizing: border-box;
-
-              .content-r {
-                display: flex;
-
-                span {
-                  font-size: 14px;
-                  color: #fff;
-
-                  &:first-child {
-                    display: inline-block;
-                    width: 68px;
-                  }
-
-                  &:last-child {
-                    display: inline-block;
-                    width: calc(100% - 68px);
-                  }
-                }
-
-                .status-f {
-                  color: #ff0000;
-                }
-
-                .status-l {
-                  color: var(--vent-table-action-link);
-                }
-              }
-            }
-          }
-        }
+      .bot-area1 {
+        height: 100%;
+        padding: 10px 15px 0;
+        background: var(--image-bj1) no-repeat center;
+        background-size: 100% 100%;
+        box-sizing: border-box;
       }
     }
   }
-}
 </style>

+ 174 - 210
src/views/vent/monitorManager/alarmMonitor/warn/ventilateWarn.vue

@@ -57,40 +57,34 @@
 </template>
 
 <script setup lang="ts">
-  import { ref, reactive, onMounted, onUnmounted, computed } from 'vue';
+  import { ref, reactive, onMounted, onUnmounted } from 'vue';
   import { useSystemSelect } from '/@/hooks/vent/useSystemSelect';
   import { usePermission } from '/@/hooks/web/usePermission';
   import { useGlobSetting } from '/@/hooks/setting';
   import { useRouter } from 'vue-router';
-  import { sysTypeWarnList, sysWarn, getDevice } from '../common.api';
+  import { sysTypeWarnList, getDevice } from '../common.api';
   import { ventilateTopList, typeMenuListTf, getMaxY, getMinY } from '../common.data';
   import CustomHeader from '/@/components/vent/customHeader.vue';
   import MeasurePoint from '../common/measurePoint.vue';
   import { realTimeNetCal, modalParam } from '../alarm.api';
   import TopArea from '../common/top-area.vue';
 
-  //巷道阻力分析数据
-  let hdData = reactive({
+  const hdData = reactive({
     maxLevel: '',
-    address: '',
   });
-  //通风选项激活索引
-  let activeIndex = ref(0);
-  let monitor = ref(true);
-  let toggleData = reactive<any>({});
+  const activeIndex = ref(0);
+  const monitor = ref(true);
+  let toggleData: any = {};
   const { hasPermission } = usePermission();
-  const { options, optionValue, getSelectRow, getSysDataSource } = useSystemSelect('sys_surface_caimei'); // 参数为场景类型(设备类型管理中可以查询到)
-  let router = useRouter();
-  //左侧数据列表
-  let menuList = reactive<any[]>([]);
-  const ventTunDataSource = ref([]);
-  //当前左侧激活菜单的索引
-  let activeIndex1 = ref(0);
-  let maxY = ref<any>(0);
-  let minY = ref<any>(0);
+  const { options, optionValue, getSelectRow } = useSystemSelect('sys_surface_caimei');
+  const router = useRouter();
+  const menuList = reactive<any[]>([]);
+  const ventTunDataSource = ref<any[]>([]);
+  const activeIndex1 = ref(0);
+  const maxY = ref<any>(0);
+  const minY = ref<any>(0);
   const loading = ref(false);
 
-  //通风图表数据
   const echartDataFc1 = reactive<any>({
     maxData: {
       lengedData: '进风量',
@@ -106,244 +100,227 @@
     },
     xData: [],
   });
-  let cardListTf = ref<any[]>([]);
+  const cardListTf = ref<any[]>([]);
   const chartListTf = ref<any[]>([]);
-  let showToggle = ref('report');
+  const showToggle = ref('report');
 
-  // https获取监测数据
   let timer: null | NodeJS.Timeout = null;
+  let timer1: null | NodeJS.Timeout = null;
+
+  function clearPollTimer() {
+    if (timer) {
+      clearTimeout(timer);
+      timer = null;
+    }
+  }
+
+  function clearNetTimer() {
+    if (timer1) {
+      clearTimeout(timer1);
+      timer1 = null;
+    }
+  }
+
+  function clearChartData() {
+    echartDataFc1.maxData.data.length = 0;
+    echartDataFc1.minData.data.length = 0;
+    echartDataFc1.aveValue.data.length = 0;
+    echartDataFc1.xData.length = 0;
+  }
+
+  function fillHistoryChart(list: any[], useReport: boolean) {
+    if (!list || !list.length) return;
+    const needValue = ventilateTopList[2].value;
+    const hasNeed = needValue && needValue != '--';
+    list.forEach((v) => {
+      if (useReport) {
+        echartDataFc1.maxData.data.push(parseFloat(v.faceIntM3));
+        echartDataFc1.minData.data.push(parseFloat(v.faceRetM3));
+        echartDataFc1.aveValue.data.push(0);
+      } else {
+        echartDataFc1.maxData.data.push(parseFloat(v.jin));
+        echartDataFc1.minData.data.push(parseFloat(v.hui));
+        echartDataFc1.aveValue.data.push(hasNeed ? needValue : 0);
+      }
+      echartDataFc1.xData.push(v.time);
+    });
+  }
+
+  function updateYAxisRange() {
+    const echartD = [echartDataFc1.maxData.data, echartDataFc1.minData.data];
+    maxY.value = getMaxY(echartD);
+    minY.value = getMinY(echartD);
+  }
+
   function getMonitor(flag?) {
+    clearPollTimer();
     timer = setTimeout(
       async () => {
-        //获取左侧菜单数据
         await getMenuList();
         await getWindDeviceList();
+        timer = null;
         getMonitor(false);
       },
       flag ? 0 : 1500
     );
   }
-  //获取巷道阻力分析数据
-  let timer1: null | NodeJS.Timeout = null;
+
   function getMonitor1(flag?) {
+    clearNetTimer();
     timer1 = setTimeout(
       async () => {
         await getRealTimeNetData();
+        timer1 = null;
         getMonitor1(false);
       },
       flag ? 0 : 30000
     );
   }
 
-  //返回首页
   function getBack() {
     router.push('/monitorChannel/monitor-alarm-home');
   }
-  //点击切换实时\报表数据
-  let handlerToggle = () => {
+
+  function handlerToggle() {
     monitor.value = !monitor.value;
-    echartDataFc1.maxData.data.length = 0;
-    echartDataFc1.minData.data.length = 0;
-    echartDataFc1.aveValue.data.length = 0;
-    echartDataFc1.xData.length = 0;
+    clearChartData();
     getData();
-  };
-  //实时报表数据
+  }
+
   function getData() {
     ventilateTopList[0].value = monitor.value ? toggleData.faceIntM3 : toggleData.jin;
     ventilateTopList[1].value = monitor.value ? toggleData.faceRetM3 : toggleData.hui;
     if (monitor.value) {
-      toggleData.history_report.forEach((v) => {
-        echartDataFc1.maxData.data.push(parseFloat(v.faceIntM3));
-        echartDataFc1.minData.data.push(parseFloat(v.faceRetM3));
-        echartDataFc1.aveValue.data.push(0);
-        echartDataFc1.xData.push(v.time);
-      });
+      fillHistoryChart(toggleData.history_report, true);
     } else {
-      toggleData.history.forEach((v) => {
-        echartDataFc1.maxData.data.push(parseFloat(v.jin));
-        echartDataFc1.minData.data.push(parseFloat(v.hui));
-        if (ventilateTopList[2].value && ventilateTopList[2].value != '--') {
-          echartDataFc1.aveValue.data.push(ventilateTopList[2].value);
-        } else {
-          echartDataFc1.aveValue.data.push(0);
-        }
-        echartDataFc1.xData.push(v.time);
-      });
+      fillHistoryChart(toggleData.history, false);
     }
   }
-  //获取左侧数据列表
+
   async function getMenuList() {
-    let res = await sysTypeWarnList({ type: 'vent' });
-    if (res.length != 0) {
-      menuList.length = 0;
-      res.forEach((el) => {
-        menuList.push({
-          name: el.deviceName,
-          warn: el.warnDes.indexOf('预警') != -1 ? '风量不足' : el.warnDes,
-          deviceID: el.deviceID,
-          strtype: el.deviceType,
-          detail: el.detail,
-        });
+    const res = await sysTypeWarnList({ type: 'vent' });
+    if (!res || res.length === 0) return;
+    menuList.length = 0;
+    res.forEach((el) => {
+      menuList.push({
+        name: el.deviceName,
+        warn: el.warnDes.indexOf('预警') != -1 ? '风量不足' : el.warnDes,
+        deviceID: el.deviceID,
+        strtype: el.deviceType,
+        detail: el.detail,
       });
-      //获取右侧详情数据
-      getDetailList(menuList[activeIndex1.value].detail);
-    }
+    });
+    getDetailList(menuList[activeIndex1.value]?.detail);
   }
 
-  //获取右侧详情数据
   function getDetailList(param) {
-    echartDataFc1.maxData.data.length = 0;
-    echartDataFc1.minData.data.length = 0;
-    echartDataFc1.aveValue.data.length = 0;
-    echartDataFc1.xData.length = 0;
-    if (JSON.stringify(param) != '{}') {
-      toggleData = Object.assign({}, param);
-      ventilateTopList[2].value = param.xufengliang || '--';
-      ventilateTopList[3].text = param.warnFlag ? param.warnDes : '正常';
-      if (showToggle.value == 'monitor') {
-        ventilateTopList[0].value = param.jin;
-        ventilateTopList[1].value = param.hui;
-        if (param.history.length != 0) {
-          param.history.forEach((v) => {
-            echartDataFc1.maxData.data.push(parseFloat(v.jin));
-            echartDataFc1.minData.data.push(parseFloat(v.hui));
-            if (ventilateTopList[2].value && ventilateTopList[2].value != '--') {
-              echartDataFc1.aveValue.data.push(ventilateTopList[2].value);
-            } else {
-              echartDataFc1.aveValue.data.push(0);
-            }
-            echartDataFc1.xData.push(v.time);
-          });
-        }
-      } else if (showToggle.value == 'report') {
-        ventilateTopList[0].value = param.faceIntM3;
-        ventilateTopList[1].value = param.faceRetM3;
-        param.history_report.forEach((v) => {
-          echartDataFc1.maxData.data.push(parseFloat(v.faceIntM3));
-          echartDataFc1.minData.data.push(parseFloat(v.faceRetM3));
-          echartDataFc1.aveValue.data.push(0);
-          echartDataFc1.xData.push(v.time);
-        });
-      } else {
-        getData();
-      }
-
-      let echartD = [echartDataFc1.maxData.data, echartDataFc1.minData.data];
-      maxY.value = getMaxY(echartD);
-      minY.value = getMinY(echartD);
+    clearChartData();
+    if (!param || JSON.stringify(param) === '{}') return;
+
+    toggleData = Object.assign({}, param);
+    ventilateTopList[2].value = param.xufengliang || '--';
+    ventilateTopList[3].text = param.warnFlag ? param.warnDes : '正常';
+
+    if (showToggle.value == 'monitor') {
+      ventilateTopList[0].value = param.jin;
+      ventilateTopList[1].value = param.hui;
+      fillHistoryChart(param.history, false);
+    } else if (showToggle.value == 'report') {
+      ventilateTopList[0].value = param.faceIntM3;
+      ventilateTopList[1].value = param.faceRetM3;
+      fillHistoryChart(param.history_report, true);
+    } else {
+      getData();
     }
+
+    updateYAxisRange();
   }
 
-  //通风选项切换
   function btnClick(ind) {
     activeIndex.value = ind;
-    switch (ind) {
-      case 0:
-        if (timer1) clearTimeout(timer1);
-        activeIndex1.value = 0;
-        getMenuList();
-        break;
-      case 1:
-        if (timer) clearTimeout(timer);
-        activeIndex1.value = 0;
-        menuList.length = 0;
-        getRealTimeNetData();
-        getMonitor1();
-        break;
+    activeIndex1.value = 0;
+    if (ind === 0) {
+      clearNetTimer();
+      getMenuList();
+    } else if (ind === 1) {
+      clearPollTimer();
+      menuList.length = 0;
+      getRealTimeNetData();
+      getMonitor1(true);
     }
   }
 
-  //菜单选项切换
-  function cardClick(ind, item) {
-    clearTimeout(timer);
-    if (!activeIndex.value && !loading.value) {
-      activeIndex1.value = ind;
-      loading.value = true;
-      setTimeout(() => {
-        getMonitor();
-        loading.value = false;
-      }, 2000);
-    }
+  function cardClick(ind) {
+    if (activeIndex.value || loading.value) return;
+    clearPollTimer();
+    activeIndex1.value = ind;
+    loading.value = true;
+    setTimeout(() => {
+      getMonitor(true);
+      loading.value = false;
+    }, 2000);
   }
 
-  //获取通风监控测点信息
   async function getWindDeviceList() {
     const cardTfList: any[] = [];
     const chartTfList: any[] = [];
-    let res = await getDevice({ devicetype: 'windrect', pagetype: 'normal' });
-    if (res && res.msgTxt[0]) {
-      let list = res.msgTxt[0].datalist || [];
-      if (list.length > 0) {
-        list.forEach((el: any) => {
-          const readData = el.readData;
-          el = Object.assign(el, readData);
-          cardTfList.push({
-            label: '通信状态',
-            value: el.netStatus == '0' ? '断开' : '连接',
-            listR: [
-              { id: 0, label: '安装位置', dw: '', value: el.strinstallpos },
-              { id: 1, label: '风量', dw: 'm³/min', value: el.m3 || '--' },
-              { id: 2, label: '风速', dw: 'm/s', value: el.va || '--' },
-              { id: 4, label: '时间', dw: '', value: el.readTime },
-              {
-                id: 3,
-                label: '是否报警',
-                dw: '',
-                value: el.warnFlag == '0' ? '正常' : el.warnFlag == 1 ? '报警' : el.warnFlag == 2 ? '断开' : '未监测',
-              },
-            ],
-          });
-          // 初始化预测曲线配置,分别为x轴时间、平均、最大、最小、实时
-          const avgParam = el.avgParam || {
-            avg_vent_value: 0,
-            max_vent_value: 0,
-            min_vent_value: 0,
-          };
-          chartTfList.push({
-            label: el.strinstallpos,
-            time: new Date(),
-            data: [avgParam.avg_m3_value, avgParam.max_m3_value, avgParam.min_m3_value, el.readData.m3],
-          });
-        });
-      }
-      cardListTf.value = cardTfList;
-      chartListTf.value = chartTfList;
-    }
+    const res = await getDevice({ devicetype: 'windrect', pagetype: 'normal' });
+    const list = res?.msgTxt?.[0]?.datalist || [];
+    list.forEach((el: any) => {
+      const readData = el.readData || {};
+      Object.assign(el, readData);
+      cardTfList.push({
+        label: '通信状态',
+        value: el.netStatus == '0' ? '断开' : '连接',
+        listR: [
+          { id: 0, label: '安装位置', dw: '', value: el.strinstallpos },
+          { id: 1, label: '风量', dw: 'm³/min', value: el.m3 || '--' },
+          { id: 2, label: '风速', dw: 'm/s', value: el.va || '--' },
+          { id: 4, label: '时间', dw: '', value: el.readTime },
+          {
+            id: 3,
+            label: '是否报警',
+            dw: '',
+            value: el.warnFlag == '0' ? '正常' : el.warnFlag == 1 ? '报警' : el.warnFlag == 2 ? '断开' : '未监测',
+          },
+        ],
+      });
+      const avgParam = el.avgParam || {};
+      chartTfList.push({
+        label: el.strinstallpos,
+        time: new Date(),
+        data: [avgParam.avg_m3_value || 0, avgParam.max_m3_value || 0, avgParam.min_m3_value || 0, readData.m3],
+      });
+    });
+    cardListTf.value = cardTfList;
+    chartListTf.value = chartTfList;
   }
+
   async function getRealTimeNetData() {
     const modalData = await modalParam({});
-    if (modalData && modalData.param && modalData.param.records.length && modalData.param.records.length > 0) {
-      const res = await realTimeNetCal({ modelID: modalData.param.records[0]['defaultmodelid'] });
-      if (res && res['result']) ventTunDataSource.value = res['result']['tuns'];
-      let data = [];
-      ventTunDataSource.value.forEach((el) => {
-        if (el['dHTotal'] && el['oldHTotal']) {
-          el['leveld'] = ((el['dHTotal'] - el['oldHTotal']) / el['oldHTotal']) * 100;
-          data.push(el['leveld']);
-        } else {
-          data = [];
-        }
-      });
-      hdData.maxLevel = '正常';
-    }
+    const records = modalData?.param?.records;
+    if (!records?.length) return;
+
+    const res = await realTimeNetCal({ modelID: records[0]['defaultmodelid'] });
+    const tuns = res?.result?.tuns || [];
+    ventTunDataSource.value = tuns;
+    tuns.forEach((el) => {
+      if (el['dHTotal'] && el['oldHTotal']) {
+        el['leveld'] = ((el['dHTotal'] - el['oldHTotal']) / el['oldHTotal']) * 100;
+      }
+    });
+    hdData.maxLevel = '正常';
   }
 
-  onMounted(async () => {
-    const { sysOrgCode, sysDataType } = useGlobSetting();
+  onMounted(() => {
+    const { sysDataType } = useGlobSetting();
     showToggle.value = sysDataType || 'report';
-    await getMenuList();
-    await getMonitor();
+    getMonitor(true);
   });
+
   onUnmounted(() => {
-    if (timer) {
-      clearTimeout(timer);
-      timer = undefined;
-    }
-    if (timer1) {
-      clearTimeout(timer1);
-      timer1 = undefined;
-    }
+    clearPollTimer();
+    clearNetTimer();
   });
 </script>
 
@@ -356,9 +333,6 @@
       --image-no-choice: url('/@/assets/images/themify/deepblue/fire/no-choice.png');
       --image-choice: url('/@/assets/images/themify/deepblue/fire/choice.png');
       --image-bj1: url('/@/assets/images/themify/deepblue/fire/bj1.png');
-      --image-jinfengliang: url('/@/assets/images/themify/deepblue/fire/jinfengliang.png');
-      --image-huifengliang: url('/@/assets/images/themify/deepblue/fire/huifengliang.png');
-      --image-xufengliang: url('/@/assets/images/themify/deepblue/fire/xufengliang.png');
     }
   }
 
@@ -367,12 +341,7 @@
     --image-no-choice: url('/@/assets/images/fire/no-choice.png');
     --image-choice: url('/@/assets/images/fire/choice.png');
     --image-bj1: url('/@/assets/images/fire/bj1.png');
-    --image-jinfengliang: url('/@/assets/images/fire/jinfengliang.png');
-    --image-huifengliang: url('/@/assets/images/fire/huifengliang.png');
-    --image-xufengliang: url('/@/assets/images/fire/xufengliang.png');
-    --border-image-1: linear-gradient(to bottom, #2d74a0, #2d74a0, #2d74a0);
-    --border-image-2: linear-gradient(to bottom, transparent, #024688, transparent);
-    position: reactive;
+    position: relative;
     width: 100%;
     height: 100%;
     padding: 80px 10px 15px 10px;
@@ -541,11 +510,6 @@
       }
     }
 
-    .tun-box {
-      width: 600px;
-      height: 300px;
-    }
-
     .ventilate-bottom {
       height: calc(50% - 15px);
       background: var(--image-border) no-repeat center;

+ 15 - 6
src/views/vent/monitorManager/dedustMonitor/components/DedustHome.vue

@@ -59,7 +59,7 @@
 
 <script setup lang="ts">
   import FourBorderBg from '/@/views/vent/comment/components/fourBorderBg.vue';
-  import { onBeforeMount, ref, onMounted, onUnmounted, computed } from 'vue';
+  import { onBeforeMount, ref, inject, onMounted, onUnmounted, computed } from 'vue';
   import { list } from '../dedust.api';
   import ventBox1 from '/@/components/vent/ventBox1.vue';
   import { mountedThree, destroy, setModelType } from '../dedust.threejs';
@@ -88,6 +88,8 @@
   });
 
   const loading = ref(false);
+  /** 全局配置(is2DModel 决定二维 / 三维模型) */
+  const globalConfig: any = inject('globalConfig');
   const deviceInfo = ref<any>({});
 
   // 将列表配置项转换为列表可用的prop
@@ -154,18 +156,25 @@
 
   onMounted(() => {
     loading.value = true;
-    mountedThree('#dedust3D', ['#dedustCss', '#dedustEnvA', '#dedustEnvB']).then(() => {
-      setModelType('dedust').finally(() => {
-        loading.value = false;
+    // 二维模式:不初始化三维,模型由 dedustSVG 装配(箭头隐藏)
+    if (!globalConfig?.is2DModel) {
+      mountedThree('#dedust3D', ['#dedustCss', '#dedustEnvA', '#dedustEnvB']).then(() => {
+        setModelType('dedust').finally(() => {
+          loading.value = false;
+        });
       });
-    });
+    } else {
+      loading.value = false;
+    }
     timer = setInterval(() => {
       getDataSource(props.deviceId);
     }, 1000);
   });
   onUnmounted(() => {
     clearInterval(timer);
-    destroy();
+    if (!globalConfig?.is2DModel) {
+      destroy();
+    }
   });
 </script>
 <style lang="less" scoped>

+ 15 - 6
src/views/vent/monitorManager/dedustMonitor/components/DedustHomeBet.vue

@@ -64,7 +64,7 @@
 
 <script setup lang="ts">
   import FourBorderBg from '/@/views/vent/comment/components/fourBorderBg.vue';
-  import { ref, onMounted, onUnmounted, computed, watch } from 'vue';
+  import { ref, inject, onMounted, onUnmounted, computed, watch } from 'vue';
   import { list } from '../dedust.api';
   import ventBox1 from '/@/components/vent/ventBox1.vue';
   import { mountedThree, destroy, setModelType } from '../dedust.threejs';
@@ -83,6 +83,8 @@
   });
 
   const loading = ref(false);
+  /** 全局配置(is2DModel 决定二维 / 三维模型) */
+  const globalConfig: any = inject('globalConfig');
   const deviceInfo = ref<any>({});
   const deviceType = computed(() => {
     return deviceInfo.value.deviceType;
@@ -169,18 +171,25 @@
 
   onMounted(() => {
     loading.value = true;
-    mountedThree('#dedust3D', ['#dedustCss', '#dedustEnvA', '#dedustEnvB']).then(() => {
-      setModelType('dedust').finally(() => {
-        loading.value = false;
+    // 二维模式:不初始化三维,模型由 dedustSVG 装配(箭头隐藏)
+    if (!globalConfig?.is2DModel) {
+      mountedThree('#dedust3D', ['#dedustCss', '#dedustEnvA', '#dedustEnvB']).then(() => {
+        setModelType('dedust').finally(() => {
+          loading.value = false;
+        });
       });
-    });
+    } else {
+      loading.value = false;
+    }
     timer = setInterval(() => {
       getDataSource(props.deviceId);
     }, 1000);
   });
   onUnmounted(() => {
     clearInterval(timer);
-    destroy();
+    if (!globalConfig?.is2DModel) {
+      destroy();
+    }
   });
 </script>
 <style lang="less" scoped>

+ 101 - 0
src/views/vent/monitorManager/dedustMonitor/components/dedustSVG.vue

@@ -0,0 +1,101 @@
+<template>
+  <!-- 二维模型:除尘风机(复用掘进工作面模型包 tun-face-2d);本组件只保留业务驱动 -->
+  <SvgModelViewer ref="viewerRef" model-id="tun-face-2d" :identify="identify" />
+</template>
+
+<script setup lang="ts">
+  import { onBeforeUnmount, ref, watch } from 'vue';
+  import SvgModelViewer from '/@/components/vent/SvgModelViewer.vue';
+  import { useEngineAnim } from '/@/utils/svg-model/useEngineAnim';
+
+  withDefaults(
+    defineProps<{
+      /** 组件唯一标识符,提供后可以确保多个组件复用时的元素定位依旧准确 */
+      identify?: string;
+    }>(),
+    { identify: '' }
+  );
+
+  /**
+   * 除尘风机页面复用掘进工作面模型:模型整体照常显示,但 3 个流动箭头不播放、直接隐藏。
+   * 驱动保留 animate(running) 开关:true → 箭头显示并循环流动,false(默认)→ 箭头隐藏。
+   */
+  const ARROW_IDS = ['auto_anim_0', 'auto_anim_1', 'auto_anim_2'];
+  /** 一轮循环时长(10 帧,按观感可调) */
+  const LOOP_PERIOD_MS = 2200;
+
+  const viewerRef = ref<any>(null);
+  const { engine } = useEngineAnim(viewerRef);
+
+  let anims: Animation[] = [];
+  let running = false;
+
+  function stop() {
+    anims.forEach((a) => {
+      try {
+        a.cancel();
+      } catch (e) {
+        /* ignore */
+      }
+    });
+    anims = [];
+  }
+
+  function setVisible(visible: boolean) {
+    const eng = engine.value;
+    if (!eng) return;
+    ARROW_IDS.forEach((id) => {
+      const el = eng.getElement(id) as HTMLElement | undefined;
+      if (el) el.style.display = visible ? '' : 'none';
+    });
+  }
+
+  /** 开始循环流动:帧序列本身就是一段完整的小动画,直接循环播放 */
+  function start() {
+    stop();
+    const eng = engine.value;
+    if (!eng) return;
+    ARROW_IDS.forEach((id) => {
+      const el = eng.getElement(id);
+      const frames = eng.getElementInfo(id)?.frames;
+      if (!el || !frames || frames.length < 2) return;
+      // model.json 里这排箭头的首尾位移 = 相邻箭头图案间距,循环回绕时正好落到相邻箭头上
+      const keyframes = frames.map((transform) => ({ transform }));
+      const anim = (el as HTMLElement).animate(keyframes, {
+        duration: LOOP_PERIOD_MS,
+        iterations: Infinity,
+        easing: 'linear',
+        fill: 'both',
+      });
+      anims.push(anim);
+    });
+  }
+
+  /**
+   * 除尘风机二维模型开关
+   *
+   * @param flag 是否播放箭头:false(默认)→ 箭头隐藏;true → 箭头显示并循环流动
+   */
+  function animate(flag: boolean = false) {
+    running = flag;
+    setVisible(flag);
+    if (flag) start();
+    else stop();
+  }
+
+  watch(engine, (eng) => {
+    if (!eng) return;
+    if (running) {
+      setVisible(true);
+      start();
+    } else {
+      setVisible(false);
+    }
+  });
+
+  onBeforeUnmount(() => {
+    stop();
+  });
+
+  defineExpose({ animate });
+</script>

+ 20 - 0
src/views/vent/monitorManager/dedustMonitor/dedust.data.ts

@@ -1,3 +1,5 @@
+import { defineAsyncComponent } from 'vue';
+
 /** 故障相关状态配置 0 正常 1 故障 */
 export const statusConfigA = [
   { value: '0', label: '' },
@@ -195,3 +197,21 @@ export const dedustStatusConfigC = [
 //     label: '停止控制',
 //   },
 // ];
+
+/** 二维 SVG 模型组件缓存(切换模型时不重复加载) */
+const componentsCaches = new Map<string, any>();
+
+/**
+ * 获取除尘风机二维模型组件(复用掘进工作面模型包 tun-face-2d)
+ *
+ * @param is2DModel 是否为二维模型;三维模型由 dedust.threejs 的 mountedThree 负责,此处仅返回二维组件
+ */
+export function getModelComponent(is2DModel: boolean = false) {
+  if (!is2DModel) return null;
+  const name = 'dedustSVG';
+  if (!componentsCaches.has(name)) {
+    componentsCaches.set(name, import(`./components/${name}.vue`));
+  }
+  // @ts-ignore
+  return defineAsyncComponent(() => componentsCaches.get(name));
+}

+ 23 - 2
src/views/vent/monitorManager/dedustMonitor/index.vue

@@ -1,7 +1,22 @@
 <!-- eslint-disable vue/multi-word-component-names -->
 <template>
+  <!-- 二维 SVG 模型(复用掘进工作面模型包,箭头隐藏不播放) -->
+  <div
+    v-if="globalConfig?.is2DModel"
+    v-show="activeKey == 'monitor'"
+    class="dedust-2d"
+    style="width: 100%; height: 100%; position: absolute; overflow: hidden; display: flex; justify-content: center; align-items: center"
+  >
+    <component :is="modelComponent" style="z-index: 2" />
+  </div>
   <!-- 把 dedust3D 放到这里则模型可以正常旋转 -->
-  <div id="dedust3D" v-show="activeKey == 'monitor'" style="width: 100%; height: 100%; position: absolute; overflow: hidden"> </div>
+  <div
+    v-if="!globalConfig?.is2DModel"
+    id="dedust3D"
+    v-show="activeKey == 'monitor'"
+    style="width: 100%; height: 100%; position: absolute; overflow: hidden"
+  >
+  </div>
   <div class="scene-box">
     <customHeader
       :fieldNames="{ label: 'systemname', value: 'id', options: 'children' }"
@@ -57,7 +72,7 @@
 </template>
 
 <script setup lang="ts">
-  import { onBeforeMount, ref, onMounted, computed, defineAsyncComponent } from 'vue';
+  import { onBeforeMount, ref, shallowRef, inject, onMounted, computed, defineAsyncComponent } from 'vue';
   import customHeader from '/@/components/vent/customHeader.vue';
   import BottomMenu from '/@/views/vent/comment/components/bottomMenu.vue';
   import DedustHistory from './components/DedustHistory.vue';
@@ -65,6 +80,7 @@
   import { useRouter } from 'vue-router';
   import { useSystemSelect } from '/@/hooks/vent/useSystemSelect';
   import { useGlobSetting } from '/@/hooks/setting';
+  import { getModelComponent } from './dedust.data';
 
   const { sysOrgCode } = useGlobSetting();
   const DedustHomeComponent = computed(() => {
@@ -91,6 +107,11 @@
   const { currentRoute } = useRouter();
   const activeKey = ref('monitor');
 
+  /** 全局配置(is2DModel 决定二维 / 三维模型) */
+  const globalConfig: any = inject('globalConfig');
+  /** 二维模型组件(三维由 DedustHome 的 mountedThree 负责,此处仅二维有值) */
+  const modelComponent = shallowRef(getModelComponent(globalConfig?.is2DModel));
+
   const historyTable = ref();
   const alarmHistoryTable = ref();
   const handlerHistoryTable = ref();

+ 33 - 29
src/views/vent/monitorManager/deviceMonitor/components/device/device.data.ts

@@ -81,13 +81,17 @@ export function getMonitorComponent() {
       FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal.wlml.vue'));
       break;
     default:
-      FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal.zdrk.vue'));
-    //FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal-Gx.vue'));
-    // FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal.wlml.vue'));
-    // FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal.sw.vue'));
-    // FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal.vue'));
-    // FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal-Gx.vue'));
-    // FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal.cct.vue'));
+      //FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal.zdrk.vue'));
+      //FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal-Gx.vue'));
+      // FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal.wlml.vue'));
+      // FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal-Gx.vue'));
+      //FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal.cct.vue'));
+      // FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal.bd.vue'));
+      // FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal.sjh.vue'));
+      // FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal.hlg.vue'));
+      //FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal.sw.vue'));
+      FiberModal = defineAsyncComponent(() => import('./modal/fiber.modal.vue'));
+
   }
   const BundleModal = defineAsyncComponent(() => import('./modal/bundle.modal.vue'));
   const FiremonModal = defineAsyncComponent(() => import('./modal/firemon.modal.vue'));
@@ -601,26 +605,26 @@ export const noLocationArr = () => {
 export const noHistoryArr = () =>
   History_Type['type'] == 'remote'
     ? [
-        'surface_history',
-        'gasDayReport',
-        'dustDayReport',
-        'bundleDayReport',
-        'bundleSpyDayReport',
-        'gasDay',
-        'gate_linkdlfm',
-        'bundleReport',
-        'bundleSpyReport',
-        'dustReport',
-      ]
+      'surface_history',
+      'gasDayReport',
+      'dustDayReport',
+      'bundleDayReport',
+      'bundleSpyDayReport',
+      'gasDay',
+      'gate_linkdlfm',
+      'bundleReport',
+      'bundleSpyReport',
+      'dustReport',
+    ]
     : [
-        'gasDayReport',
-        'dustDayReport',
-        'bundleDayReport',
-        'bundleSpyDayReport',
-        'gate_linkdlfm',
-        'gasDay',
-        'substation_normal',
-        'bundleReport',
-        'bundleSpyReport',
-        'dustReport',
-      ];
+      'gasDayReport',
+      'dustDayReport',
+      'bundleDayReport',
+      'bundleSpyDayReport',
+      'gate_linkdlfm',
+      'gasDay',
+      'substation_normal',
+      'bundleReport',
+      'bundleSpyReport',
+      'dustReport',
+    ];

+ 26 - 17
src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.vue

@@ -53,13 +53,13 @@
         <div class="right-center">
           <div class="table-box">
             <span class="base-title">测点监测详情</span>
-            <a-table size="small" :columns="columns" :data-source="posList" :pagination="false" :scroll="{ y: 200 }" />
+            <a-table size="small" :columns="columns" :data-source="posList" :pagination="false" :scroll="{ y: 160 }" />
           </div>
           <div class="warning-box">
             <span class="base-title">预警历史详情</span>
             <div class="warning-container">
               <dv-scroll-board ref="scrollBoard" :config="warningConfig"
-                style="width: 100%; height: 240px; overflow-y: auto; border: 1px solid #39e8ff33" />
+                style="width: 100%; height: 200px; border: 1px solid #39e8ff33" />
             </div>
           </div>
         </div>
@@ -325,12 +325,16 @@ export default defineComponent({
 
   .modal-right {
     width: calc(100% - 220px);
-    overflow-y: auto;
+    height: 100%;
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
 
     .base-title {
-      line-height: 32px;
+      line-height: 28px;
       position: relative;
       padding-left: 20px;
+      flex-shrink: 0;
 
       &::after {
         content: '';
@@ -349,18 +353,18 @@ export default defineComponent({
       display: flex;
       flex-direction: row;
       justify-content: space-between;
-      margin-bottom: 10px;
+      flex-shrink: 0;
 
       .top-item {
         width: 200px;
-        height: 80px;
+        height: 72px;
         display: flex;
         flex-direction: row;
         justify-content: center;
         border: 1px solid rgba(25, 237, 255, 0.4);
         box-shadow: inset 0 0 10px rgba(0, 197, 255, 0.6);
         background: rgba(0, 0, 0, 0.06666666666666667);
-        padding-top: 16px;
+        padding-top: 12px;
 
         .icon {
           margin-right: 10px;
@@ -388,16 +392,16 @@ export default defineComponent({
             font-style: normal;
             background-size: cover;
             font-family: electronicFont;
-            font-size: 30px;
+            font-size: 28px;
             -webkit-background-clip: text;
             background-clip: text;
             -webkit-text-fill-color: transparent;
             position: relative;
-            top: -8px;
+            top: -6px;
 
             span {
               font-family: Arial, Helvetica, sans-serif;
-              font-size: 18px;
+              font-size: 16px;
               color: aliceblue;
             }
           }
@@ -408,7 +412,7 @@ export default defineComponent({
         padding-top: 0px;
 
         .icon {
-          margin-top: 20px;
+          margin-top: 16px;
 
           .icon-style {
             color: #fdb146;
@@ -423,18 +427,19 @@ export default defineComponent({
     }
 
     .right-center {
-      margin-top: 20px;
+      margin-top: 12px;
       display: flex;
       flex-direction: row;
       justify-content: space-between;
+      flex-shrink: 0;
 
       .table-box {
         position: relative;
         width: 500px;
-        height: 250px;
+        height: 230px;
 
         :deep(.zxm-table-wrapper) {
-          height: 220px !important;
+          height: 200px !important;
           background: #ffffff05 !important;
           border-bottom: 1px solid #91e9fe80 !important;
         }
@@ -445,7 +450,6 @@ export default defineComponent({
 
         .warning-container {
           width: 100%;
-          height: convert;
           background: #009acd00;
 
           :deep(.dv-scroll-board) {
@@ -464,11 +468,16 @@ export default defineComponent({
     }
 
     .right-bottom {
-      margin-top: 20px;
+      margin-top: 12px;
+      flex: 1;
+      min-height: 0;
+      display: flex;
+      flex-direction: column;
 
       .echarts-box {
         width: 100%;
-        height: 230px;
+        flex: 1;
+        min-height: 180px;
       }
     }
   }

+ 103 - 0
src/views/vent/monitorManager/tunFaceMonitor/components/tunFaceSVG.vue

@@ -0,0 +1,103 @@
+<template>
+  <!-- 二维模型:掘进工作面(模型包 public/svg-models/tun-face-2d/);本组件只保留业务驱动 -->
+  <SvgModelViewer ref="viewerRef" model-id="tun-face-2d" :identify="identify" />
+</template>
+
+<script setup lang="ts">
+  import { onBeforeUnmount, ref, watch } from 'vue';
+  import SvgModelViewer from '/@/components/vent/SvgModelViewer.vue';
+  import { useEngineAnim } from '/@/utils/svg-model/useEngineAnim';
+
+  withDefaults(
+    defineProps<{
+      /** 组件唯一标识符,提供后可以确保多个组件复用时的元素定位依旧准确 */
+      identify?: string;
+    }>(),
+    { identify: '' }
+  );
+
+  /**
+   * 掘进工作面流动箭头(与 model.json 的 elements 一一对应):
+   * 模型里 3 个箭头元素共用一个开关,运行 → 循环流动,停止 → 隐藏。
+   */
+  const ARROW_IDS = ['auto_anim_0', 'auto_anim_1', 'auto_anim_2'];
+  /** 一轮循环时长(10 帧,按观感可调) */
+  const LOOP_PERIOD_MS = 2200;
+
+  const viewerRef = ref<any>(null);
+  const { engine } = useEngineAnim(viewerRef);
+
+  /** 每个箭头元素上的 WAAPI 循环动画 */
+  let anims: Animation[] = [];
+  let running = true;
+
+  function stop() {
+    anims.forEach((a) => {
+      try {
+        a.cancel();
+      } catch (e) {
+        /* ignore */
+      }
+    });
+    anims = [];
+  }
+
+  function setVisible(visible: boolean) {
+    const eng = engine.value;
+    if (!eng) return;
+    ARROW_IDS.forEach((id) => {
+      const el = eng.getElement(id) as HTMLElement | undefined;
+      if (el) el.style.display = visible ? '' : 'none';
+    });
+  }
+
+  /** 开始循环流动:帧序列本身就是一段完整的小动画,直接循环播放 */
+  function start() {
+    stop();
+    const eng = engine.value;
+    if (!eng) return;
+    ARROW_IDS.forEach((id) => {
+      const el = eng.getElement(id);
+      const frames = eng.getElementInfo(id)?.frames;
+      if (!el || !frames || frames.length < 2) return;
+      // model.json 里这排箭头的首尾位移 = 相邻箭头图案间距,循环回绕时正好落到相邻箭头上
+      const keyframes = frames.map((transform) => ({ transform }));
+      const anim = (el as HTMLElement).animate(keyframes, {
+        duration: LOOP_PERIOD_MS,
+        iterations: Infinity,
+        easing: 'linear',
+        fill: 'both',
+      });
+      anims.push(anim);
+    });
+  }
+
+  /**
+   * 掘进工作面二维模型开关
+   *
+   * @param flag 是否运行:true(默认)→ 箭头显示并循环流动;false → 箭头隐藏(停止态)
+   */
+  function animate(flag: boolean = true) {
+    running = flag;
+    setVisible(flag);
+    if (flag) start();
+    else stop();
+  }
+
+  watch(engine, (eng) => {
+    if (!eng) return;
+    if (running) {
+      setVisible(true);
+      start();
+    } else {
+      setVisible(false);
+    }
+  });
+
+  onBeforeUnmount(() => {
+    stop();
+  });
+
+  /** 页面通过 modelRef.value?.animate?.(running) 控制(当前页面打开即常驻循环) */
+  defineExpose({ animate });
+</script>

+ 24 - 8
src/views/vent/monitorManager/tunFaceMonitor/index.vue

@@ -4,14 +4,17 @@
     class="bg"
     style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; overflow: hidden">
     <a-spin :spinning="loading" />
+    <!-- 二维 SVG 模型(掘进工作面:箭头常驻循环流动) -->
+    <component v-if="globalConfig?.is2DModel" ref="modelRef" :is="modelComponent" style="z-index: 2" />
     <div
+        v-else
         id="tunFace3DCSS"
         class="threejs-Object-CSS"
         v-show="!loading"
         style="width: 100%; height: 100%; position: absolute; pointer-events: none; overflow: hidden; z-index: 1; top: 0"
       >
     </div>
-    <div id="tunFace3D" style="width: 100%; height: 100%; position: absolute; overflow: hidden"> </div>
+    <div v-if="!globalConfig?.is2DModel" id="tunFace3D" style="width: 100%; height: 100%; position: absolute; overflow: hidden"> </div>
   </div>
   <div class="scene-box">
     <customHeader :fieldNames="{ label: 'systemname', value: 'id', options: 'children' }" :options = 'options' @change="getSelectRow" :optionValue="optionValue">掘进工作面智能管控</customHeader>
@@ -45,10 +48,10 @@
 </template>
 
 <script setup lang="ts">
-import { onBeforeMount, ref, onMounted, onUnmounted, reactive, toRaw } from 'vue';
+import { onBeforeMount, ref, shallowRef, inject, onMounted, onUnmounted, reactive, toRaw } from 'vue';
 import { mountedThree, destroy, setModelType } from './tunFace.threejs';
 import { systemList, getTableList } from './tunFace.api';
-import { monitorNav } from './tunFace.data'
+import { monitorNav, getModelComponent } from './tunFace.data'
 import customHeader from '/@/components/vent/customHeader.vue';
 import BottomMenu from '/@/views/vent/comment/components/bottomMenu.vue';
 import tunFaceVentHome from './components/tunFaceVentHome.vue';
@@ -67,6 +70,12 @@ const { currentRoute } = useRouter();
 const activeKey = ref('monitor');
 const loading = ref(false);
 
+/** 全局配置(is2DModel 决定二维 / 三维模型) */
+const globalConfig: any = inject('globalConfig');
+/** 二维模型容器(三维由 mountedThree 负责,此处仅二维有值) */
+const modelRef = ref();
+const modelComponent = shallowRef(getModelComponent(globalConfig?.is2DModel));
+
 const monitorNavData = ref(monitorNav)
 const monitorActive = ref(0)
 
@@ -92,7 +101,7 @@ const selectData = reactive({});
 function changeActive(activeValue) {
   activeKey.value = activeValue
   loading.value = true
-  if(activeKey.value === 'monitor'){
+  if(activeKey.value === 'monitor' && !globalConfig?.is2DModel){
     setModelType('tunFace')
     setTimeout(() =>{
       loading.value = false
@@ -169,10 +178,15 @@ onBeforeMount(() => {
 
 onMounted(async() => { 
   loading.value = true;
-  mountedThree().then(async () => {
-    setModelType('tunFace')
+  // 二维模式:不初始化三维,模型由 tunFaceSVG 自行装配
+  if (globalConfig?.is2DModel) {
     loading.value = false;
-  });
+  } else {
+    mountedThree().then(async () => {
+      setModelType('tunFace')
+      loading.value = false;
+    });
+  }
   if (currentRoute.value && currentRoute.value['query'] && currentRoute.value['query']['id']) {
     optionValue.value = currentRoute.value['query']['id']
   }
@@ -182,7 +196,9 @@ onMounted(async() => {
 });
 
 onUnmounted(() => {
-  destroy();
+  if (!globalConfig?.is2DModel) {
+    destroy();
+  }
 });
 
 </script>

+ 19 - 1
src/views/vent/monitorManager/tunFaceMonitor/tunFace.data.ts

@@ -1,4 +1,4 @@
-import { reactive, ref } from 'vue';
+import { defineAsyncComponent, reactive, ref } from 'vue';
 import { BasicColumn } from '/@/components/Table';
 import echarts from '/@/utils/lib/echarts';
 
@@ -847,3 +847,21 @@ export const gasPumpCtr = reactive({
   gasPump1Open: '0',
   gasPump2Open: '0',
 });
+
+/** 二维 SVG 模型组件缓存(切换模型时不重复加载) */
+const componentsCaches = new Map<string, any>();
+
+/**
+ * 获取掘进工作面二维模型组件
+ *
+ * @param is2DModel 是否为二维模型;三维模型由 tunFace.threejs 的 mountedThree 负责,此处仅返回二维组件
+ */
+export function getModelComponent(is2DModel: boolean = false) {
+  if (!is2DModel) return null;
+  const name = 'tunFaceSVG';
+  if (!componentsCaches.has(name)) {
+    componentsCaches.set(name, import(`./components/${name}.vue`));
+  }
+  // @ts-ignore
+  return defineAsyncComponent(() => componentsCaches.get(name));
+}

+ 488 - 178
src/views/vent/testChart/FiberTempChart.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="fiber-temp-chart-wrap">
+  <div class="fiber-temp-chart-wrap" :class="{ compact }">
     <div class="chart-header">
       <div class="chart-title">{{ title }}</div>
       <div class="chart-header-desc">
@@ -20,8 +20,8 @@
 <script setup>
 import { ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue';
 import * as echarts from 'echarts';
-import firePng from '@/assets/images/fire.png';
-import warningSVG from '@/assets/images/warning.svg';
+import firePng from '/@/assets/images/fire.png';
+import warningSVG from '/@/assets/images/warning.svg';
 
 const props = defineProps({
   xAxisData: {
@@ -48,29 +48,196 @@ const props = defineProps({
     type: String,
     default: '光纤测温',
   },
+  compact: {
+    type: Boolean,
+    default: false,
+  },
+  markSymbolSize: {
+    type: Number,
+    default: 48,
+  },
+  // 纵轴名称、数值文案(束管浓度等可复用)
+  yAxisName: {
+    type: String,
+    default: '温度℃',
+  },
+  valueLabel: {
+    type: String,
+    default: '温度',
+  },
+  valueUnit: {
+    type: String,
+    default: '℃',
+  },
+  // dataZoom 默认窗口起始/结束;配合 dataZoomMode 使用
+  dataZoomStart: {
+    type: Number,
+    default: 0,
+  },
+  dataZoomEnd: {
+    type: Number,
+    default: undefined,
+  },
+  // percent: start/end 为 0-100 百分比;index: start/end 为测点下标(如 0-20 表示前 21 个点)
+  dataZoomMode: {
+    type: String,
+    default: 'percent',
+  },
 });
 
 const emit = defineEmits(['chart-click']);
 
 const chartDomRef = ref(null);
 let chartInstance = null;
+let optionReady = false;
+let lastDataKey = '';
+let resizeBound = false;
+let lastAxisLength = 0;
+let defaultZoomApplied = false;
+
+function isAlarmValue(val, minValue, maxValue) {
+  if (val === null || val === undefined || Number.isNaN(Number(val))) return false;
+  return val < minValue || val >= maxValue;
+}
+
+function getDataKey() {
+  return JSON.stringify({
+    x: props.xAxisData,
+    s: props.seriesData,
+    min: props.minValue,
+    max: props.maxValue,
+    ms: props.markSymbolSize,
+    c: props.compact,
+    yn: props.yAxisName,
+    vl: props.valueLabel,
+    vu: props.valueUnit,
+  });
+}
 
 function disposeChart() {
   if (chartInstance) {
+    chartInstance.off('click');
     chartInstance.dispose();
     chartInstance = null;
   }
+  optionReady = false;
+  lastDataKey = '';
+  lastAxisLength = 0;
+  defaultZoomApplied = false;
+}
+
+function pickZoomNumber(val, fallback) {
+  const raw = Array.isArray(val) ? val[0] : val;
+  const num = Number(raw);
+  return Number.isFinite(num) ? num : fallback;
+}
+
+function resolveDefaultZoom(xAxisData) {
+  const axisLength = xAxisData.length;
+  const mode = props.dataZoomMode === 'index' ? 'index' : 'percent';
+  const startRaw = Number(props.dataZoomStart);
+  const endRaw = Number(props.dataZoomEnd);
+
+  if (mode === 'index') {
+    if (!axisLength) {
+      return { mode, start: 0, end: 100, startValue: undefined, endValue: undefined };
+    }
+    const startIndex = Number.isFinite(startRaw) ? Math.max(0, Math.floor(startRaw)) : 0;
+    const endIndex = Number.isFinite(endRaw)
+      ? Math.min(axisLength - 1, Math.max(startIndex, Math.floor(endRaw)))
+      : Math.min(axisLength - 1, startIndex + 19);
+    return {
+      mode,
+      start: (startIndex / Math.max(axisLength - 1, 1)) * 100,
+      end: (endIndex / Math.max(axisLength - 1, 1)) * 100,
+      startValue: xAxisData[startIndex],
+      endValue: xAxisData[endIndex],
+    };
+  }
+
+  const start = Number.isFinite(startRaw) ? startRaw : 0;
+  let end;
+  if (Number.isFinite(endRaw)) {
+    end = endRaw;
+  } else if (axisLength <= 20) {
+    end = 100;
+  } else {
+    end = Math.min(100, Math.max(1, Math.round((20 / axisLength) * 100)));
+  }
+  return { mode, start, end, startValue: undefined, endValue: undefined };
+}
+
+function buildDataZoomOption(compact, zoom, preserve) {
+  const start = preserve?.start ?? zoom.start;
+  const end = preserve?.end ?? zoom.end;
+  const base = {
+    start,
+    end,
+    filterMode: 'filter',
+  };
+  // index 模式优先用类目值锁定窗口,避免百分比被 inside 缩放冲掉
+  if (zoom.mode === 'index' && zoom.startValue !== undefined && zoom.endValue !== undefined && !preserve) {
+    base.startValue = zoom.startValue;
+    base.endValue = zoom.endValue;
+  } else if (preserve?.startValue !== undefined && preserve?.endValue !== undefined) {
+    base.startValue = preserve.startValue;
+    base.endValue = preserve.endValue;
+  }
+
+  return [
+    {
+      type: 'slider',
+      show: true,
+      height: compact ? 16 : 20,
+      bottom: 6,
+      backgroundColor: '#152c47',
+      fillerColor: '#2ef3ef66',
+      handleStyle: { color: '#2ef3ef' },
+      textStyle: { color: '#ffffff' },
+      borderColor: 'rgba(46,243,239,0.3)',
+      zoomLock: false,
+      ...base,
+    },
+    {
+      type: 'inside',
+      ...base,
+    },
+  ];
+}
+
+function applyZoomAction(zoom, preserve) {
+  if (!chartInstance) return;
+  const payload = {
+    type: 'dataZoom',
+  };
+  if (preserve) {
+    payload.start = preserve.start;
+    payload.end = preserve.end;
+  } else if (zoom.mode === 'index' && zoom.startValue !== undefined && zoom.endValue !== undefined) {
+    payload.startValue = zoom.startValue;
+    payload.endValue = zoom.endValue;
+  } else {
+    payload.start = zoom.start;
+    payload.end = zoom.end;
+  }
+  // slider + inside 都强制同步到同一窗口
+  chartInstance.dispatchAction({ ...payload, dataZoomIndex: 0 });
+  chartInstance.dispatchAction({ ...payload, dataZoomIndex: 1 });
 }
 
 function initChart() {
   if (!chartDomRef.value) return;
-  disposeChart();
-  chartInstance = echarts.init(chartDomRef.value);
-  renderOption();
-  chartInstance.on('click', (params) => {
-    emit('chart-click', params);
-  });
-  window.addEventListener('resize', handleResize);
+  if (!chartInstance) {
+    chartInstance = echarts.init(chartDomRef.value);
+    chartInstance.on('click', (params) => {
+      emit('chart-click', params);
+    });
+  }
+  if (!resizeBound) {
+    window.addEventListener('resize', handleResize);
+    resizeBound = true;
+  }
+  renderOption(true);
 }
 
 function handleResize() {
@@ -79,13 +246,16 @@ function handleResize() {
 
 function getMarkPointData() {
   const markPoints = [];
-  const { seriesData, minValue, maxValue } = props;
+  const { seriesData, minValue, maxValue, markSymbolSize } = props;
+  const size = markSymbolSize || 48;
   seriesData.forEach((val, idx) => {
-    if (val < minValue || val >= maxValue) {
+    if (isAlarmValue(val, minValue, maxValue)) {
+      const offsetY = Math.max(8, size * 0.35);
       markPoints.push({
-        coord: [idx, val + 15],
+        coord: [idx, val],
         symbol: `image://${val < minValue ? warningSVG : firePng}`,
-        symbolSize: [60, 60],
+        symbolSize: [size, size],
+        symbolOffset: [0, -offsetY],
         tooltip: {
           formatter: `<div style="color:#ff4422;font-weight:bold">异常确认<br/>立即排查</div>`,
         },
@@ -100,203 +270,325 @@ function getAlarmContinuousData(data, minValue, maxValue) {
   const result = [];
   const n = data.length;
 
-  const isAlarm = (val) => val < minValue || val >= maxValue;
-
   for (let i = 0; i < n; i++) {
     const val = data[i];
-    if (isAlarm(val)) {
-      // 当前点是报警点,保留原值
+    if (isAlarmValue(val, minValue, maxValue)) {
       result.push(val);
     } else {
-      // 检查相邻点是否有报警点,如果有则保留当前值作为边界
-      const prevAlarm = i > 0 && isAlarm(data[i - 1]);
-      const nextAlarm = i < n - 1 && isAlarm(data[i + 1]);
-
-      if (prevAlarm || nextAlarm) {
-        // 使用实际边界值,与正常折线在切换点保持连续
-        result.push(val);
-      } else {
-        result.push(null);
-      }
+      const prevAlarm = i > 0 && isAlarmValue(data[i - 1], minValue, maxValue);
+      const nextAlarm = i < n - 1 && isAlarmValue(data[i + 1], minValue, maxValue);
+      result.push(prevAlarm || nextAlarm ? val : null);
     }
   }
   return result;
 }
 
-function getAlarmPointData(data, minValue, maxValue) {
-  return data.map((val) => (val < minValue || val >= maxValue ? val : null));
+function getYAxisMax(seriesData, minValue, maxValue) {
+  const nums = seriesData.map((v) => Number(v)).filter((v) => !Number.isNaN(v));
+  const dataMax = nums.length ? Math.max(...nums) : 0;
+  const dataMin = nums.length ? Math.min(...nums) : 0;
+  const base = Math.max(
+    dataMax,
+    Math.abs(dataMin),
+    Number(maxValue) || 0,
+    Math.abs(Number(minValue) || 0),
+    0.1
+  );
+  const padded = base * 1.2;
+  if (padded <= 1) return Math.ceil(padded * 10) / 10 || 1;
+  if (padded <= 10) return Math.ceil(padded);
+  if (padded <= 100) return Math.ceil(padded / 5) * 5;
+  return Math.ceil(padded / 10) * 10;
 }
 
-function renderOption() {
-  if (!chartInstance) return;
-  const { xAxisData, seriesData, minValue, maxValue } = props;
+// 始终从 props 读取,避免首次空数据初始化后 formatter 闭包旧数组导致 tooltip 空白
+function formatTooltip(params) {
+  const list = Array.isArray(params) ? params : [params];
+  const dataIndex = list.find((item) => item && item.dataIndex !== undefined)?.dataIndex;
+  if (dataIndex === undefined) return '';
+
+  const xLabel = props.xAxisData?.[dataIndex];
+  const val = props.seriesData?.[dataIndex];
+  if (val === undefined || val === null || Number.isNaN(Number(val))) return '';
+
+  const alarm = isAlarmValue(val, props.minValue, props.maxValue);
+  const unit = props.valueUnit || '';
+  return `<div style="font-weight:bold;${alarm ? 'color:#ff8800' : 'color:#35d9e5'}">
+    ${xLabel ?? ''}<br/>
+    ${props.valueLabel || '数值'}:${val}${unit}${alarm ? ' 超限' : ''}
+  </div>`;
+}
 
-  // 报警填充数据:包含实际边界点,使报警区域连续
+function getTooltipOption() {
+  return {
+    show: true,
+    trigger: 'axis',
+    triggerOn: 'mousemove|click',
+    // 挂到 body,避免父容器 overflow 裁剪;echarts6 使用 appendTo
+    appendToBody: true,
+    appendTo: typeof document !== 'undefined' ? document.body : undefined,
+    confine: false,
+    backgroundColor: 'rgba(0,20,40,0.85)',
+    borderColor: '#2ef3ef',
+    borderWidth: 1,
+    padding: [8, 12],
+    textStyle: { color: '#fff', fontSize: 13 },
+    axisPointer: {
+      type: 'line',
+      lineStyle: { color: 'rgba(46,243,239,0.45)' },
+    },
+    formatter: formatTooltip,
+  };
+}
+
+function buildSeriesPayload(seriesData, minValue, maxValue, compact) {
   const alarmFillData = getAlarmContinuousData(seriesData, minValue, maxValue);
+  const normalFillData = seriesData.map((val) => (isAlarmValue(val, minValue, maxValue) ? null : val));
+  const normalLineData = seriesData.map((val) => (isAlarmValue(val, minValue, maxValue) ? null : val));
+  const markPointData = getMarkPointData();
 
-  // 报警折线数据:只保留超过上限或低于下限的点
-  const alarmLineData = getAlarmPointData(seriesData, minValue, maxValue);
+  return [
+    {
+      name: '正常填充',
+      type: 'line',
+      smooth: true,
+      data: normalFillData,
+      symbol: 'none',
+      connectNulls: false,
+      lineStyle: { width: 0 },
+      tooltip: { show: false },
+      areaStyle: {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: 'rgba(53,217,229,0.35)' },
+          { offset: 1, color: 'rgba(53,217,229,0.05)' },
+        ]),
+      },
+      z: 1,
+      animationDurationUpdate: 0,
+    },
+    {
+      name: '报警填充',
+      type: 'line',
+      smooth: true,
+      data: alarmFillData,
+      symbol: 'none',
+      connectNulls: false,
+      lineStyle: { width: 0 },
+      tooltip: { show: false },
+      areaStyle: {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: 'rgba(255,136,0,0.5)' },
+          { offset: 1, color: 'rgba(255,136,0,0.05)' },
+        ]),
+      },
+      z: 2,
+      animationDurationUpdate: 0,
+    },
+    {
+      name: '正常测温',
+      type: 'line',
+      smooth: true,
+      data: normalLineData,
+      symbol: 'circle',
+      symbolSize: compact ? 6 : 8,
+      connectNulls: false,
+      lineStyle: { width: 3, color: '#35d9e5' },
+      itemStyle: { color: '#35d9e5' },
+      z: 3,
+      animationDurationUpdate: 0,
+    },
+    {
+      name: '报警测温',
+      type: 'line',
+      smooth: true,
+      data: alarmFillData,
+      symbol: 'circle',
+      symbolSize: (_value, params) => {
+        const val = seriesData[params.dataIndex];
+        return isAlarmValue(val, minValue, maxValue) ? (compact ? 8 : 10) : 0;
+      },
+      connectNulls: false,
+      lineStyle: { width: 3, color: '#ff8800' },
+      itemStyle: { color: '#ff8800' },
+      markPoint: {
+        data: markPointData,
+        animation: false,
+      },
+      markLine: {
+        silent: true,
+        symbol: ['none', 'arrow'],
+        symbolSize: 8,
+        animation: false,
+        data: [
+          {
+            yAxis: minValue,
+            label: {
+              show: true,
+              position: 'end',
+              formatter: '下限阈值',
+              color: '#39d98a',
+              fontSize: compact ? 11 : 12,
+            },
+            lineStyle: { type: 'dashed', width: 2, color: '#39d98a' },
+          },
+          {
+            yAxis: maxValue,
+            label: {
+              show: true,
+              position: 'end',
+              formatter: '上限阈值',
+              color: '#ff2222',
+              fontSize: compact ? 11 : 12,
+            },
+            lineStyle: { type: 'dashed', width: 2, color: '#ff2222' },
+          },
+        ],
+      },
+      z: 4,
+      animationDurationUpdate: 0,
+    },
+  ];
+}
+
+function renderOption(force = false) {
+  if (!chartInstance) return;
+
+  const dataKey = getDataKey();
+  if (!force && dataKey === lastDataKey) return;
+  lastDataKey = dataKey;
 
-  // 正常填充数据:仅保留上下限之间的值,报警值设为 null
-  const normalFillData = seriesData.map((val) => (val >= minValue && val < maxValue ? val : null));
+  const { xAxisData, seriesData, minValue, maxValue, compact } = props;
+  const yMax = getYAxisMax(seriesData, minValue, maxValue);
+  const series = buildSeriesPayload(seriesData, minValue, maxValue, compact);
+  const axisLength = xAxisData.length;
+  const defaultZoom = resolveDefaultZoom(xAxisData);
 
-  // 正常折线数据:只保留上下限之间的点
-  const normalLineData = seriesData.map((val) => (val >= minValue && val < maxValue ? val : null));
+  // 增量更新:只改数据;首次有数据或空数据→有数据时应用默认缩放,之后保留用户拖动的窗口
+  if (optionReady) {
+    const needResetZoom = !defaultZoomApplied || (lastAxisLength === 0 && axisLength > 0);
+    let preserve = null;
+
+    if (!needResetZoom) {
+      try {
+        const currentZoom = chartInstance.getOption()?.dataZoom?.[0];
+        if (currentZoom) {
+          preserve = {
+            start: pickZoomNumber(currentZoom.start, defaultZoom.start),
+            end: pickZoomNumber(currentZoom.end, defaultZoom.end),
+            startValue: Array.isArray(currentZoom.startValue) ? currentZoom.startValue[0] : currentZoom.startValue,
+            endValue: Array.isArray(currentZoom.endValue) ? currentZoom.endValue[0] : currentZoom.endValue,
+          };
+        }
+      } catch (e) {
+        // ignore
+      }
+    }
+
+    chartInstance.setOption(
+      {
+        tooltip: getTooltipOption(),
+        xAxis: { data: [...xAxisData] },
+        yAxis: { max: yMax, name: props.yAxisName || '温度℃' },
+        dataZoom: buildDataZoomOption(compact, defaultZoom, preserve),
+        series: series.map((item) => ({
+          name: item.name,
+          data: item.data,
+          symbolSize: item.symbolSize,
+          markPoint: item.markPoint,
+          markLine: item.markLine,
+        })),
+      },
+      {
+        notMerge: false,
+        lazyUpdate: true,
+      }
+    );
+
+    if (needResetZoom && axisLength > 0) {
+      nextTick(() => applyZoomAction(defaultZoom));
+    }
+
+    lastAxisLength = axisLength;
+    if (axisLength > 0) defaultZoomApplied = true;
+    return;
+  }
 
   const option = {
     backgroundColor: 'transparent',
+    animation: false,
+    animationDurationUpdate: 0,
     title: { show: false },
-    tooltip: {
-      trigger: 'axis',
-      backgroundColor: 'rgba(0,20,40,0.85)',
-      borderColor: '#2ef3ef',
-      textStyle: { color: '#fff' },
-      formatter: function (params) {
-        const dataIndex = params[0]?.dataIndex;
-        if (dataIndex !== undefined && seriesData[dataIndex] !== undefined) {
-          const val = seriesData[dataIndex];
-          const isAlarm = val < minValue || val >= maxValue;
-          return `<div style="font-weight:bold;${isAlarm ? 'color:#ff8800' : 'color:#35d9e5'}">
-            ${xAxisData[dataIndex] || ''}<br/>
-            温度:${val}℃${isAlarm ? ' 超限' : ''}
-          </div>`;
-        }
-        return '';
-      },
-    },
+    tooltip: getTooltipOption(),
     legend: { show: false },
     grid: {
-      left: '4%',
-      right: '3%',
-      top: '18%',
+      left: '3%',
+      right: '4%',
+      top: compact ? 26 : 32,
       bottom: '18%',
+      containLabel: true,
     },
     xAxis: {
       type: 'category',
-      data: xAxisData,
+      data: [...xAxisData],
+      boundaryGap: false,
       axisLine: { lineStyle: { color: '#2ef3ef' } },
-      axisLabel: { color: '#ffffff', fontSize: 14 },
+      axisLabel: {
+        color: '#ffffff',
+        fontSize: compact ? 11 : 14,
+        hideOverlap: true,
+      },
       splitLine: { show: true, lineStyle: { color: 'rgba(46,243,239,0.15)' } },
     },
     yAxis: {
       type: 'value',
-      name: '温度℃',
-      nameTextStyle: { color: '#fff' },
+      name: props.yAxisName || '温度℃',
+      nameLocation: 'end',
+      nameGap: 8,
+      nameTextStyle: {
+        color: '#fff',
+        fontSize: compact ? 12 : 14,
+        align: 'left',
+        padding: [0, 0, 0, 0],
+      },
       min: 0,
-      max: 130,
+      max: yMax,
       axisLine: { lineStyle: { color: '#2ef3ef' } },
-      axisLabel: { color: '#ffffff' },
+      axisLabel: { color: '#ffffff', fontSize: compact ? 11 : 12 },
       splitLine: { lineStyle: { color: 'rgba(46,243,239,0.15)' } },
     },
-    dataZoom: [
-      {
-        type: 'slider',
-        show: true,
-        height: 20,
-        bottom: 8,
-        backgroundColor: '#152c47',
-        fillerColor: '#2ef3ef66',
-        handleStyle: { color: '#2ef3ef' },
-        textStyle: { color: '#ffffff' },
-        start: 0,
-        end: 30,
-        zoomLock: false,
-      },
-      { type: 'inside' },
-    ],
-    series: [
-      // 正常填充(青色)
-      {
-        name: '正常填充',
-        type: 'line',
-        smooth: true,
-        data: normalFillData,
-        symbol: 'none',
-        connectNulls: false,
-        lineStyle: { width: 0 },
-        tooltip: { show: false },
-        areaStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: 'rgba(53,217,229,0.35)' },
-            { offset: 1, color: 'rgba(53,217,229,0.05)' },
-          ]),
-        },
-        z: 1,
-      },
-      // 报警填充(橙色)- 包含边界点,形成连续填充
-      {
-        name: '报警填充',
-        type: 'line',
-        smooth: true,
-        data: alarmFillData,
-        symbol: 'none',
-        connectNulls: false,
-        lineStyle: { width: 0 },
-        tooltip: { show: false },
-        areaStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: 'rgba(255,136,0,0.5)' },
-            { offset: 1, color: 'rgba(255,136,0,0.05)' },
-          ]),
-        },
-        z: 2,
-      },
-      // 正常折线(青色)
-      {
-        name: '正常测温',
-        type: 'line',
-        smooth: true,
-        data: normalLineData,
-        symbol: 'circle',
-        symbolSize: 8,
-        connectNulls: false,
-        lineStyle: { width: 3, color: '#35d9e5' },
-        itemStyle: { color: '#35d9e5' },
-        z: 3,
-      },
-      // 报警折线(橙色)- 包含边界点,形成连续折线
-      {
-        name: '报警测温',
-        type: 'line',
-        smooth: true,
-        data: alarmLineData,
-        symbol: 'circle',
-        symbolSize: 10,
-        connectNulls: false,
-        lineStyle: { width: 3, color: '#ff8800' },
-        itemStyle: {
-          color: function (params) {
-            const val = params.data;
-            // 边界点(等于阈值)不显示圆点
-            if (val === minValue || val === maxValue) return 'transparent';
-            return '#ff8800';
-          },
-        },
-        markPoint: {
-          data: getMarkPointData(),
-          offset: [0, -100],
-        },
-        markLine: {
-          silent: true,
-          lineStyle: { type: 'dashed', width: 2, color: '#ff2222' },
-          label: { show: true, position: 'end', fontSize: 12, color: '#ff2222' },
-          data: [
-            { yAxis: minValue, label: { formatter: '下限阈值' } },
-            { yAxis: maxValue, label: { formatter: '上限阈值' } },
-          ],
-        },
-        z: 4,
-      },
-    ],
+    dataZoom: buildDataZoomOption(compact, defaultZoom, null),
+    series,
   };
 
-  chartInstance.setOption(option, true);
+  chartInstance.setOption(option, { notMerge: true, lazyUpdate: false });
+  optionReady = true;
+  lastAxisLength = axisLength;
+  if (axisLength > 0) {
+    defaultZoomApplied = true;
+    nextTick(() => applyZoomAction(defaultZoom));
+  }
 }
 
 watch(
-  () => [props.xAxisData, props.seriesData, props.minValue, props.maxValue],
+  () => [
+    props.xAxisData,
+    props.seriesData,
+    props.minValue,
+    props.maxValue,
+    props.markSymbolSize,
+    props.compact,
+    props.yAxisName,
+    props.valueLabel,
+    props.valueUnit,
+  ],
   () => {
-    nextTick(() => renderOption());
+    nextTick(() => {
+      if (!chartInstance) {
+        initChart();
+        return;
+      }
+      renderOption(false);
+    });
   },
   { deep: true }
 );
@@ -307,7 +599,10 @@ onMounted(() => {
 
 onBeforeUnmount(() => {
   disposeChart();
-  window.removeEventListener('resize', handleResize);
+  if (resizeBound) {
+    window.removeEventListener('resize', handleResize);
+    resizeBound = false;
+  }
 });
 </script>
 
@@ -322,16 +617,17 @@ onBeforeUnmount(() => {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding: 10px 16px;
+  padding: 6px 12px 0;
+  flex-shrink: 0;
 }
 .chart-title {
   color: #ffffff;
-  font-size: 24px;
+  font-size: 20px;
   font-weight: bold;
 }
 .chart-header-desc {
   display: flex;
-  gap: 32px;
+  gap: 24px;
   align-items: center;
 }
 .desc-item {
@@ -340,8 +636,8 @@ onBeforeUnmount(() => {
   gap: 8px;
 }
 .color-block {
-  width: 32px;
-  height: 12px;
+  width: 28px;
+  height: 10px;
   border-radius: 2px;
 }
 .color-block.normal {
@@ -352,10 +648,24 @@ onBeforeUnmount(() => {
 }
 .text {
   color: #ffffff;
-  font-size: 16px;
+  font-size: 14px;
 }
 .chart-container {
   flex: 1;
   width: 100%;
+  min-height: 0;
+}
+.fiber-temp-chart-wrap.compact .chart-title {
+  font-size: 14px;
+}
+.fiber-temp-chart-wrap.compact .text {
+  font-size: 12px;
+}
+.fiber-temp-chart-wrap.compact .chart-header-desc {
+  gap: 16px;
+}
+.fiber-temp-chart-wrap.compact .color-block {
+  width: 22px;
+  height: 8px;
 }
-</style>
+</style>

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません