Przeglądaj źródła

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

bobo04052021@163.com 3 dni temu
rodzic
commit
3b8127deb0
71 zmienionych plików z 6409 dodań i 1823 usunięć
  1. 0 0
      docs/README.md
  2. BIN
      docs/UI_Defect_Report_FullRetest.xlsx
  3. 530 0
      docs/_agent_alarm_gas.json
  4. 338 0
      docs/_agent_fire_dust.json
  5. 132 0
      docs/_agent_fire_dust.raw.txt
  6. 302 0
      docs/_agent_monitor.json
  7. 569 0
      docs/_coverage.json
  8. 1652 0
      docs/_defects.json
  9. 482 0
      docs/_heuristic_hits.json
  10. 984 0
      docs/_scope_map.json
  11. 0 219
      docs/ai-changes/20260929-audit-r4.md
  12. 0 43
      docs/ai-changes/README.md
  13. 0 203
      docs/configurable/README.md
  14. BIN
      docs/configurable/image-1.png
  15. BIN
      docs/configurable/image-2.png
  16. BIN
      docs/configurable/image-3.png
  17. BIN
      docs/configurable/image-4.png
  18. BIN
      docs/configurable/image-5.png
  19. BIN
      docs/configurable/image-6.png
  20. BIN
      docs/configurable/image-7.png
  21. BIN
      docs/configurable/image-8.png
  22. BIN
      docs/configurable/image-9.png
  23. BIN
      docs/configurable/image.png
  24. BIN
      docs/界面缺陷测试报告_全量重测.xlsx
  25. BIN
      src/assets/images/vent/home/selfIgnition/1-7.png
  26. 0 9
      src/assets/images/vent/home/selfIgnition/form-icon.svg
  27. 0 0
      src/assets/images/vent/home/selfIgnition/goaf-card-bg.png
  28. 9 0
      src/assets/images/vent/home/selfIgnition/goaf-stat-closed.svg
  29. 14 0
      src/assets/images/vent/home/selfIgnition/goaf-stat-mining.svg
  30. 13 0
      src/assets/images/vent/home/selfIgnition/goaf-stat-stopped.svg
  31. 1 1
      src/assets/images/vent/home/selfIgnition/goaf-stat-total.svg
  32. 9 0
      src/assets/images/vent/home/selfIgnition/goaf-stat-withdraw.svg
  33. 0 18
      src/assets/images/vent/home/selfIgnition/info-icon.svg
  34. 0 9
      src/assets/images/vent/home/selfIgnition/install-wind-speed.svg
  35. 0 8
      src/assets/images/vent/home/selfIgnition/install-wind-volume.svg
  36. 3 0
      src/assets/images/vent/home/selfIgnition/level-safety-orange.svg
  37. 3 0
      src/assets/images/vent/home/selfIgnition/level-warning-orange.svg
  38. BIN
      src/assets/images/vent/home/selfIgnition/list-icon-bg.png
  39. BIN
      src/assets/images/vent/home/selfIgnition/module-border-bottom-wide.png
  40. BIN
      src/assets/images/vent/home/selfIgnition/module-border-bottom.png
  41. BIN
      src/assets/images/vent/home/selfIgnition/module-border-middle-wide.png
  42. BIN
      src/assets/images/vent/home/selfIgnition/module-border-middle.png
  43. BIN
      src/assets/images/vent/home/selfIgnition/module-border-top-wide.png
  44. BIN
      src/assets/images/vent/home/selfIgnition/module-border-top.png
  45. BIN
      src/assets/images/vent/home/selfIgnition/module-title-bg.png
  46. BIN
      src/assets/images/vent/home/selfIgnition/overall-bar-glow.png
  47. BIN
      src/assets/images/vent/home/selfIgnition/overall-label-bg.png
  48. BIN
      src/assets/images/vent/home/selfIgnition/result-block-bg.png
  49. BIN
      src/assets/images/vent/home/selfIgnition/submit-glow.png
  50. BIN
      src/assets/images/vent/home/selfIgnition/tab-active-bg.png
  51. BIN
      src/assets/images/vent/home/selfIgnition/tab-bg.png
  52. BIN
      src/assets/images/vent/home/selfIgnition/tab-item-bg.png
  53. BIN
      src/assets/images/vent/home/selfIgnition/tab-wing-left.png
  54. BIN
      src/assets/images/vent/home/selfIgnition/tab-wing-right.png
  55. BIN
      src/assets/images/vent/home/selfIgnition/top-bg.png
  56. 153 195
      src/views/vent/home/selfIgnition/components/ManualView.vue
  57. 141 0
      src/views/vent/home/selfIgnition/components/Module.vue
  58. 181 284
      src/views/vent/home/selfIgnition/components/MonitorView.vue
  59. 157 136
      src/views/vent/home/selfIgnition/components/OverviewView.vue
  60. 1 2
      src/views/vent/home/selfIgnition/components/PanelSelect.vue
  61. 1 514
      src/views/vent/home/selfIgnition/data.ts
  62. 16 18
      src/views/vent/home/selfIgnition/goafOverview.adapter.ts
  63. 128 86
      src/views/vent/home/selfIgnition/index.vue
  64. 16 3
      src/views/vent/home/selfIgnition/safetyEvaluation.api.ts
  65. 23 0
      src/views/vent/home/selfIgnition/scrollbar.less
  66. 1 0
      src/views/vent/monitorManager/alarmMonitor/common/external-mb.vue
  67. 1 0
      src/views/vent/monitorManager/alarmMonitor/common/internal-fire-bot.vue
  68. 14 0
      src/views/vent/performance/fileDetail/fileDetail.api.ts
  69. 436 42
      src/views/vent/performance/fileDetail/index.vue
  70. 15 1
      src/views/vent/testChart/FiberTempChart.vue
  71. 84 32
      src/views/ventAI/manageAssistent/components/AiAssistantModal.vue

+ 0 - 0
docs/README.md


BIN
docs/UI_Defect_Report_FullRetest.xlsx


+ 530 - 0
docs/_agent_alarm_gas.json

@@ -0,0 +1,530 @@
+[
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/common.api.ts",
+    "lines": "3-10,46",
+    "title": "bjlist 引用不存在的 Api.bjlist",
+    "severity": "致命",
+    "type": "undefined_api_ref",
+    "symptom": "瓦斯管道故障诊断轮询请求 url 为 undefined,列表/曲线无法加载",
+    "root_cause": "enum Api 无 bjlist 成员,但 bjlist() 仍使用 Api.bjlist",
+    "repro": "宝德矿打开瓦斯预警→管道故障诊断,抓包看请求 URL",
+    "fix_hint": "恢复 bjlist='/safety/ventanalyAlarmLog/list' 或改用 warningHistoryList 路径"
+  },
+  {
+    "module": "bundleSpyTable",
+    "file": "vent/bundleSpy/bundleSpyTable/index.vue",
+    "lines": "134-147",
+    "title": "色谱仪报表被硬编码 mock 覆盖真实 API",
+    "severity": "致命",
+    "type": "mock_override",
+    "symptom": "页面恒显示写死的 10月2日假报表,真实 queryReportData 不调用",
+    "root_cause": "getbundleSpyInfoList 被注释,改为本地写死 res",
+    "repro": "打开色谱仪报表分析,网络面板无真实报表请求仍能看到固定数据",
+    "fix_hint": "恢复 await getbundleSpyInfoList(...),删除硬编码 res"
+  },
+  {
+    "module": "workingFace",
+    "file": "vent/deviceManager/workingFace/workingFace.api.ts",
+    "lines": "18-21,79-90",
+    "title": "智能管控批量删除/导入导出误指向 /sys/user/*",
+    "severity": "致命",
+    "type": "wrong_endpoint",
+    "symptom": "批删打到用户批量删除接口,可能删错资源或失败;单删走 managesys 正确路径",
+    "root_cause": "deleteBatch/importExcel/exportXls 从用户模块拷贝未改",
+    "repro": "预警联动勾选多条批删,抓包确认 /sys/user/deleteBatch",
+    "fix_hint": "改为 /safety/ventanalyManageSystem/deleteBatch 及对应导入导出路径"
+  },
+  {
+    "module": "gasPumpMonitor",
+    "file": "vent/monitorManager/gasPumpMonitor/components/gasPumpHome.vue",
+    "lines": "159-178,355-375,329-333",
+    "title": "就地/远程与检修控制 paramcode 对调且入参分支不匹配",
+    "severity": "致命",
+    "type": "inverted_control_mapping",
+    "symptom": "点就地/远程下发 jxmsqh;点检修下发 ykjdqh;传 0 时 handlerType 不赋值仍弹密码框",
+    "root_cause": "changeCtr 写死 jxmsqh、changeMode 写死 ykjdqh;模板传 0/1,函数判 1/2",
+    "repro": "抽采泵站管控点就地/远程/检修开闭→确认密码→抓包 paramcode",
+    "fix_hint": "changeCtr→ykjdqh,changeMode→jxmsqh;统一 0/1 分支"
+  },
+  {
+    "module": "gasReportInspect",
+    "file": "vent/gas/gasReportInspect/index-time.vue",
+    "lines": "62-82",
+    "title": "空 records 时先访问 [0] 导致挂载崩溃",
+    "severity": "致命",
+    "type": "crash_on_mount",
+    "symptom": "无日报数据时 Cannot read properties of undefined,页面白屏",
+    "root_cause": "先读 tableList[0].id/content,后才判 length;JSON.parse 无空保护",
+    "repro": "瓦斯日报分析在 queryReportList 返回空 records 时进入页面",
+    "fix_hint": "先判空 return;JSON.parse 包 try/catch(index-type/index-lt 同改)"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/warn/gasWarn.vue",
+    "lines": "5",
+    "title": "返回按钮权限取反",
+    "severity": "严重",
+    "type": "inverted_permission",
+    "symptom": "有 gasWarn:return 权限看不到返回;无权限反而显示",
+    "root_cause": "v-if=\"!hasPermission(...)\";对照 deviceMonitorWarn 为正逻辑",
+    "repro": "分配 gasWarn:return 后对比返回按钮显隐",
+    "fix_hint": "改为 hasPermission;fire/dust/ventilate/device/waterLevel 同步"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/warn/fireWarn.vue",
+    "lines": "4",
+    "title": "火灾预警返回按钮权限取反",
+    "severity": "严重",
+    "type": "inverted_permission",
+    "symptom": "有 fireWarn:return 看不到返回按钮",
+    "root_cause": "v-if=\"!hasPermission('fireWarn:return')\"",
+    "repro": "分配权限后打开火灾监测预警",
+    "fix_hint": "去掉取反"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/warn/dustWarn.vue",
+    "lines": "6",
+    "title": "粉尘预警返回按钮权限取反",
+    "severity": "严重",
+    "type": "inverted_permission",
+    "symptom": "有 dustWarn:return 看不到返回",
+    "root_cause": "v-if=\"!hasPermission('dustWarn:return')\"",
+    "repro": "分配权限后打开粉尘监测预警",
+    "fix_hint": "改为正逻辑 hasPermission"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/warn/ventilateWarn.vue",
+    "lines": "10",
+    "title": "通风预警返回按钮权限取反",
+    "severity": "严重",
+    "type": "inverted_permission",
+    "symptom": "有 ventilateWarn:return 看不到返回",
+    "root_cause": "v-if=\"!hasPermission('ventilateWarn:return')\"",
+    "repro": "分配权限后打开通风监测预警",
+    "fix_hint": "改为正逻辑"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/warn/gasWarn.vue",
+    "lines": "193",
+    "title": "甲烷显示运算符优先级错误",
+    "severity": "严重",
+    "type": "wrong_operator_precedence",
+    "symptom": "gasC 为空时显示 \"undefined%\" 而非 '--'",
+    "root_cause": "el.readData.gasC + '%' || '--' 中字符串恒 truthy",
+    "repro": "测点 gasC 缺失时看左侧甲烷值",
+    "fix_hint": "(gasC!=null&&gasC!=='') ? gasC+'%' : '--'"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/warn/gasWarn.vue",
+    "lines": "346-365",
+    "title": "宝德矿第三 Tab 映射到瓦斯参数而非预警指标",
+    "severity": "严重",
+    "type": "wrong_field_binding",
+    "symptom": "菜单第三项「预警指标」点击却渲染 gasParamter(wscs)",
+    "root_cause": "getMonitorComponent(宝德)=[预警监测,故障诊断,预警指标];btnClick ind===2 固定 isShow='wscs'",
+    "repro": "sysOrgCode=sdmtjtbdmk 打开瓦斯预警点第三个类型按钮",
+    "fix_hint": "宝德 ind===2 → yjzb;布尔台 ind===2 保持 wscs"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/warn/gasWarn.vue",
+    "lines": "368-377",
+    "title": "宝德预警监测卡片误走故障诊断轮询",
+    "severity": "严重",
+    "type": "wrong_comparison",
+    "symptom": "预警监测点卡片会走 bjlist,菜单被故障设备覆盖",
+    "root_cause": "cardClick 仅按 isflag=='1' 分支,未看 isShow/activeIndex",
+    "repro": "宝德→预警监测→切换工作面卡片,观察是否请求 bjlist",
+    "fix_hint": "yjjc→getMonitor;gzzd→getMonitor1"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/warn/gasWarn.vue",
+    "lines": "311",
+    "title": "gasGlFaultDia 无防护 JSON.parse 可抛错",
+    "severity": "严重",
+    "type": "crash_on_mount",
+    "symptom": "缺/非法 gasGlFaultDia 时脚本异常,故障诊断页中断",
+    "root_cause": "JSON.parse(data.readData.gasGlFaultDia) 无空值与 try/catch",
+    "repro": "故障诊断选中无 gasGlFaultDia 的设备",
+    "fix_hint": "JSON.parse(x||'{}') 并 catch 给默认坐标"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/warn/dustWarn.vue",
+    "lines": "131-143,166-176",
+    "title": "粉尘预测曲线每轮 push 不清理导致无限膨胀",
+    "severity": "严重",
+    "type": "timer_state_leak",
+    "symptom": "停留越久曲线点越多、内存升高",
+    "root_cause": "fillChartByIndex 每秒 echartYc.push;仅 topAreaClick 会清空",
+    "repro": "粉尘预警停留数分钟观察 echartYc 长度",
+    "fix_hint": "每次 fill 前重置 echartYc,或替换最新点"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/warn/ventilateWarn.vue",
+    "lines": "239-250",
+    "title": "从巷道阻力分析切回通风监测后轮询不恢复",
+    "severity": "严重",
+    "type": "timer_not_restarted",
+    "symptom": "切回通风监测后顶部/测点停止自动刷新",
+    "root_cause": "btnClick(0) 只 clearNetTimer+getMenuList,未 getMonitor(true)",
+    "repro": "通风监测→巷道阻力→再点通风监测,等待>3s 看是否更新",
+    "fix_hint": "ind===0 时调用 getMonitor(true)"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/warn/fireWarn.vue",
+    "lines": "74-83,148-157,173-185,215-217",
+    "title": "匿名 setTimeout 未清理且 getMonitor 不先 clear",
+    "severity": "严重",
+    "type": "timer_leak",
+    "symptom": "快速切换/离开后仍请求 sysTypeWarn;可能双轮询",
+    "root_cause": "cardClick/reset 的 setTimeout 无句柄;getMonitor 赋值前不清旧 timer",
+    "repro": "火灾预警连点内因/外因后返回首页,抓包仍见请求",
+    "fix_hint": "保存 delay timer 并在 unmount clear;getMonitor 开头 clearPollTimer"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/common/external-threec.vue",
+    "lines": "118-124",
+    "title": "温度传感器列表误绑 fireval",
+    "severity": "严重",
+    "type": "wrong_field_binding",
+    "symptom": "外因火灾温度卡片显示火焰字段/空值",
+    "root_cause": "temperature.map 使用 readData.fireval;mainWell 正确用 temperature",
+    "repro": "外因火灾 temperature 有值、fireval 空时看温度卡",
+    "fix_hint": "改为 el.readData.temperature(fourc:147-153 同步)"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/common/external-fourc.vue",
+    "lines": "147-153",
+    "title": "四参外因温度误绑 fireval",
+    "severity": "严重",
+    "type": "wrong_field_binding",
+    "symptom": "温度监测显示错误/空",
+    "root_cause": "与 threec 相同拷贝错误 val: el.readData.fireval",
+    "repro": "打开外因四参温度列表",
+    "fix_hint": "改为 temperature"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/alarmHistoryTable.vue",
+    "lines": "166-167",
+    "title": "未传 deviceType 时 strtype 变成 \"undefined*\"",
+    "severity": "严重",
+    "type": "wrong_field_binding",
+    "symptom": "设备预警报警历史过滤错误,列表空或错数据",
+    "root_cause": "beforeFetch: props.deviceType + '*';deviceWarn.vue:43 未传 deviceType",
+    "repro": "设备监测预警→报警历史,检查请求 strtype",
+    "fix_hint": "传入正确 deviceType,或空时不拼 strtype"
+  },
+  {
+    "module": "gasAssessment",
+    "file": "vent/gas/gasAssessment/index.vue",
+    "lines": "43",
+    "title": "进入抽采单元页未传入 unitId",
+    "severity": "严重",
+    "type": "wrong_field_binding",
+    "symptom": "侧栏 list({ids}) 为空,实时数据无法对应选中单元",
+    "root_cause": "<gasUnit/> 未绑定 :unitId=\"activeUnitId\"",
+    "repro": "达标评判点选单元后看 list 请求 ids",
+    "fix_hint": "<gasUnit :unitId=\"activeUnitId\" />"
+  },
+  {
+    "module": "gasAssessment",
+    "file": "vent/gas/gasAssessment/components/gasUnit.vue",
+    "lines": "27,55,73-75",
+    "title": "单元混量/纯量图表写死 mock",
+    "severity": "严重",
+    "type": "mock_override",
+    "symptom": "图表恒为本地假数据,真实监测不驱动",
+    "root_cause": "CustomChart 绑定 mockGasUnitHlData/mockGasUnitClData",
+    "repro": "打开抽采单元对比接口与图表数据源",
+    "fix_hint": "用接口历史数据替换 mock"
+  },
+  {
+    "module": "gasPumpMonitor",
+    "file": "vent/gas/gasPumpMonitor/index.vue",
+    "lines": "57-68",
+    "title": "井下泵模型切换后被无条件覆盖为地上泵",
+    "severity": "严重",
+    "type": "copy_paste_bug",
+    "symptom": "选中 pump_under* 仍加载地上 gasPump 三维",
+    "root_cause": "if/else 设类型后末尾又 setModelType('gasPump')",
+    "repro": "抽采泵站监测切到井下泵站设备",
+    "fix_hint": "删除末尾无条件 setModelType('gasPump')"
+  },
+  {
+    "module": "gasPumpMonitor",
+    "file": "vent/gas/gasPumpMonitor/components/monitorChart.vue",
+    "lines": "50-65",
+    "title": "setInterval 用 clearTimeout 清理导致泄漏",
+    "severity": "严重",
+    "type": "timer_not_cleared",
+    "symptom": "离开监测图表后仍每 2s 推数据",
+    "root_cause": "setInterval + onUnmounted clearTimeout",
+    "repro": "进入监测图表后切走,观察回调是否继续",
+    "fix_hint": "改为 clearInterval(timer)"
+  },
+  {
+    "module": "gasZk",
+    "file": "vent/gas/gasZk/index.vue",
+    "lines": "61-79",
+    "title": "setInterval 回调内再 startPolling 造成重叠轮询",
+    "severity": "严重",
+    "type": "timer_logic_bug",
+    "symptom": "慢网时 /drill/get/status 并发堆积",
+    "root_cause": "interval 不等待完成可再次触发;成功后又 stop+new interval",
+    "repro": "打开钻孔监测并限速网络观察并发",
+    "fix_hint": "改为 await 后 setTimeout 串行轮询"
+  },
+  {
+    "module": "gasInspect",
+    "file": "vent/gas/gasInspect/index.vue",
+    "lines": "66-75",
+    "title": "早/中/晚是否巡检真假分支都渲染「是」",
+    "severity": "严重",
+    "type": "wrong_field_binding",
+    "symptom": "未巡检也显示「是」,状态失真",
+    "root_cause": "text=='1' 与 v-else 内均写「是」",
+    "repro": "NFC 地点管理查看未巡检班次列",
+    "fix_hint": "else 显示「否」;对齐 Nonfc 字段约定"
+  },
+  {
+    "module": "gasInspectNonfc",
+    "file": "vent/gas/gasInspectNonfc/components/inspectEdit.vue",
+    "lines": "12-14,79-81,71-76",
+    "title": "区队下拉缺 value 且整对象重赋值打断响应式",
+    "severity": "严重",
+    "type": "nfc_nonfc_copy_paste",
+    "symptom": "提交可能把区队名当 id;二次编辑表单不刷新",
+    "root_cause": "option 无 :value;formStateEdit=Object.assign 重赋值丢失 reactive 绑定",
+    "repro": "Nonfc 编辑改区队提交看 payload;连续编辑两条",
+    "fix_hint": "补 :value=\"item.value\";Object.assign(formStateEdit, newV) 原地更新"
+  },
+  {
+    "module": "gasReport",
+    "file": "vent/gas/gasReport/index.vue",
+    "lines": "357-440",
+    "title": "审核/导出异常校验 else-if 导致任一气体合格即放行",
+    "severity": "严重",
+    "type": "inverted_validation_logic",
+    "symptom": "O2 超限但 CO 合格仍可审核通过;多行以最后一行结果为准",
+    "root_cause": "getTs 对 O2/CO/CO2/T/CH4 用 else-if;forEach return 不能结束函数",
+    "repro": "仅 O2 异常其余合格→点审核通过",
+    "fix_hint": "全部字段均在阈内才放行;任异常即 true"
+  },
+  {
+    "module": "gasReport",
+    "file": "vent/gas/gasReport/index.vue",
+    "lines": "271-274",
+    "title": "巡检图片 Blob 二次封装错误",
+    "severity": "严重",
+    "type": "wrong_blob_usage",
+    "symptom": "查看班次图片失败或 Failed to construct 'Blob'",
+    "root_cause": "gasServerImg 已是 blob,又 new Blob(res,...);构造需 Array",
+    "repro": "日报管理点夜/早/中班「查看」",
+    "fix_hint": "URL.createObjectURL(res) 或 new Blob([res],...)"
+  },
+  {
+    "module": "gasAnalysis",
+    "file": "vent/gas/gasAnalysis/index.vue",
+    "lines": "177-182,216-219",
+    "title": "列表成功后强制 pagination.current=1 导致分页失效",
+    "severity": "严重",
+    "type": "pagination_logic_bug",
+    "symptom": "翻到第 2 页后被打回第 1 页",
+    "root_cause": "gasReportLogLists 在有 records 时无条件 current=1",
+    "repro": "业务分析多页数据点第 2 页",
+    "fix_hint": "删除该赋值;仅查询/重置时置 1"
+  },
+  {
+    "module": "gasPipeNet",
+    "file": "vent/gas/gasPipeNet/index.vue",
+    "lines": "142-147",
+    "title": "管网阀门控制提交未调控制接口",
+    "severity": "严重",
+    "type": "undefined_api_call",
+    "symptom": "点确定只 toast 表单 JSON,阀门无动作",
+    "root_cause": "submitHandler 仅 message.success,无 deviceControl",
+    "repro": "管网管控→阀门控制→提交",
+    "fix_hint": "接入泵站同款控制 API"
+  },
+  {
+    "module": "bundleSpyTable",
+    "file": "vent/bundleSpy/bundleSpyTable/index.vue",
+    "lines": "237-241,386-390",
+    "title": "活鸡兔/寸草塔二矿图表 O₂闭内 误绑 CH₄ 数据",
+    "severity": "严重",
+    "type": "wrong_field_binding",
+    "symptom": "图例 O₂闭内 柱状与 CH₄闭内完全相同",
+    "root_cause": "name:'O₂闭内' 的 data 写成 ch4AveValues",
+    "repro": "对应矿打开色谱仪报表对比 o2_ave 与图",
+    "fix_hint": "改为 o2AveValues"
+  },
+  {
+    "module": "bundleSpy+bundleMonitor",
+    "file": "vent/bundleSpy/bundleSpyTable/index.vue; vent/bundle/bundleMonitorTable/index.vue",
+    "lines": "bundleSpy:108-109; bundle:101-102",
+    "title": "保德 sysOrgCode 大小写错误导致专用列永不命中",
+    "severity": "严重",
+    "type": "copy_paste_orgcode",
+    "symptom": "保德落到 default 列配置,专用字段不显示",
+    "root_cause": "写 case 'sdmtjtBdmk';全库其余为 'sdmtjtbdmk'",
+    "repro": "sysOrgCode=sdmtjtbdmk 打开色谱仪/束管日报",
+    "fix_hint": "统一改为 sdmtjtbdmk"
+  },
+  {
+    "module": "bundleMonitorTable",
+    "file": "vent/bundle/bundleMonitorTable/index.vue",
+    "lines": "103-104,453",
+    "title": "补连塔图表分支 orgCode 写成 sdmtjtBltmk",
+    "severity": "严重",
+    "type": "copy_paste_orgcode",
+    "symptom": "表格用 Bltcolumns 正确,柱状图落入 default",
+    "root_cause": "computedColumns 用 sdmtjtbltmk,getSeriesConfig 用 sdmtjtBltmk",
+    "repro": "sysOrgCode=sdmtjtbltmk 对比表头与图例",
+    "fix_hint": "图表 case 改为 sdmtjtbltmk"
+  },
+  {
+    "module": "bundleMonitorTable",
+    "file": "vent/bundle/bundleMonitorTable/bundle-table.data.ts",
+    "lines": "595-610",
+    "title": "保德束管 NO₂ 最小值 dataIndex 写成 no22_max",
+    "severity": "严重",
+    "type": "wrong_column_dataIndex",
+    "symptom": "NO₂最小值空白或错值",
+    "root_cause": "最小值 dataIndex:'no22_max',key/dataIndex 交叉贴错",
+    "repro": "保德束管日报对比 no2_min/no2_max 与三列",
+    "fix_hint": "最大值 no2_max;最小值 no2_min"
+  },
+  {
+    "module": "bundleMonitorTable",
+    "file": "vent/bundle/bundleMonitorTable/bundle-table.data.ts",
+    "lines": "1465-1472,1848-1855",
+    "title": "寸草塔一矿/哈拉沟 O₂最大值列绑定 o2_min",
+    "severity": "严重",
+    "type": "wrong_column_dataIndex",
+    "symptom": "标题最大值实际显示最小值",
+    "root_cause": "title:最大值 却 dataIndex:'o2_min'",
+    "repro": "对应矿打开束管日报对比 o2_max/o2_min",
+    "fix_hint": "改为 dataIndex:'o2_max'"
+  },
+  {
+    "module": "betKy",
+    "file": "vent/monitorManager/betKy/index.vue",
+    "lines": "112-146,148-154,258-262",
+    "title": "矿压轮询 push 不重置 + setTimeout/clearInterval 错用",
+    "severity": "严重",
+    "type": "timer_leak",
+    "symptom": "图表与行索引错位、内存增长;卸载后仍可能请求",
+    "root_cause": "chartData 每轮 push 不清空;timer=setTimeout 却 clearInterval;重启只置 null 不 clear",
+    "repro": "矿压页多次刷新后切行;快速进出页观察请求",
+    "fix_hint": "每轮 chartData=[];统一 clearTimeout 并先 clear 再置空"
+  },
+  {
+    "module": "dustMonitorTable",
+    "file": "vent/dust/dustMonitorTable/index.vue",
+    "lines": "317-320,510-513",
+    "title": "保德当日粉尘总体最大值误用 zcjqpj/hcjqpj",
+    "severity": "严重",
+    "type": "wrong_field_binding",
+    "symptom": "当日分析最大值恒 -Infinity 或字段映射失败",
+    "root_cause": "BdMaxValues 用 qc/hc;overallBdMaxValues 误用布尔台字段",
+    "repro": "保德粉尘报表切「当日粉尘情况分析」",
+    "fix_hint": "overallBdMaxValues 改为 qc/hc"
+  },
+  {
+    "module": "dustMonitorTable",
+    "file": "vent/dust/dustMonitorTable/index.vue",
+    "lines": "269-270,429-436,472-475",
+    "title": "哈拉沟分组键 ccdd 被最大值循环改写成 jcdd",
+    "severity": "严重",
+    "type": "logic_state",
+    "symptom": "工作场所下拉变成测点名,过滤错乱",
+    "root_cause": "分组 gzcs=ccdd,HlgMaxValues 更新时 gzcs=item.jcdd 覆盖",
+    "repro": "哈拉沟粉尘报表看工作场所下拉与过滤",
+    "fix_hint": "更新最大值时不要用 jcdd 覆盖 gzcs"
+  },
+  {
+    "module": "workingFace",
+    "file": "vent/deviceManager/workingFace/CustomNormalTable.vue",
+    "lines": "250-251",
+    "title": "批量删除未传 handleSuccess 回调",
+    "severity": "一般",
+    "type": "undefined_api_ref",
+    "symptom": "确认后 handleSuccess is not a function,列表不刷新",
+    "root_cause": "batchDeleteById(params, handleSuccess) 被调用为 batchDelete({ids})",
+    "repro": "预警联动批删点确认看控制台",
+    "fix_hint": "传入 reload 作为第二参"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/warn/dustWarn.vue",
+    "lines": "255",
+    "title": "巷道温度单位错误依赖 humidity",
+    "severity": "一般",
+    "type": "wrong_field_binding",
+    "symptom": "有温度无湿度时单位空白",
+    "root_cause": "dw: el.humidity ? '(℃)' : ''",
+    "repro": "temperature 有值 humidity 空时看单位",
+    "fix_hint": "dw: el.temperature ? '(℃)' : ''"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/warn/ventilateWarn.vue",
+    "lines": "207",
+    "title": "warnDes 为空时 indexOf 抛错中断菜单构建",
+    "severity": "一般",
+    "type": "crash_on_mount",
+    "symptom": "左侧菜单空白且轮询中断",
+    "root_cause": "el.warnDes.indexOf('预警') 无空保护",
+    "repro": "sysTypeWarn 返回 warnDes 缺失条目",
+    "fix_hint": "(el.warnDes||'').indexOf(...)"
+  },
+  {
+    "module": "alarmMonitor",
+    "file": "vent/monitorManager/alarmMonitor/warn/gasWarn.vue",
+    "lines": "189-190",
+    "title": "strinstallpos 为空时 indexOf 抛错",
+    "severity": "一般",
+    "type": "crash_on_mount",
+    "symptom": "getDetailList 抛错,监测卡片不更新",
+    "root_cause": "假定 strinstallpos 必为 string",
+    "repro": "gas 明细 strinstallpos 为 null",
+    "fix_hint": "const pos=el.strinstallpos||'' 再操作"
+  },
+  {
+    "module": "gasInspect",
+    "file": "vent/gas/gasInspect/index.vue",
+    "lines": "331-342",
+    "title": "筛「两次」误把 pagination.current 写成 2",
+    "severity": "一般",
+    "type": "copy_paste_bug",
+    "symptom": "筛选两次后分页状态错乱",
+    "root_cause": "case '2': pagination.current=2",
+    "repro": "地点管理→巡检次数筛选→两次",
+    "fix_hint": "两次也 current=1"
+  },
+  {
+    "module": "bundleMonitorTable",
+    "file": "vent/bundle/bundleMonitorTable/bundle-table.data.ts",
+    "lines": "412-435",
+    "title": "保德 N₂ 子列重复两个平均值缺少最小值",
+    "severity": "一般",
+    "type": "wrong_column_dataIndex",
+    "symptom": "N₂ 两列相同平均值;最小值无法展示",
+    "root_cause": "第二、三列均为 title:平均值 + n2_ave",
+    "repro": "保德束管日报看氮气表头",
+    "fix_hint": "中间列改为最小值/n2_min"
+  }
+]

+ 338 - 0
docs/_agent_fire_dust.json

@@ -0,0 +1,338 @@
+[
+  {
+    "module": "喷淋监控系统",
+    "file": "src/views/vent/monitorManager/sprayMonitor/index.vue",
+    "lines": "68-140",
+    "title": "多路控制按钮共用同一 paramcode/value",
+    "severity": "critical",
+    "type": "wrong_control_payload",
+    "symptom": "除急停外,补水阀/搅拌阀/组化泵/上料机/喷洒自动等按钮下发完全相同的指令,设备无法按按钮区分控制,存在误控风险",
+    "root_cause": "operations 全部使用 key='run_auto_sign',且多数 value='0';BasicMonitoring.deviceControl 仅按 key/value 下发",
+    "repro": "打开喷淋监控→输入密码点击“补水阀启停”与“喷洒自动”→抓包可见 paramcode/value 相同",
+    "fix_hint": "为每个操作配置独立 paramcode,并按启停状态设置正确 value(参考业务点表)"
+  },
+  {
+    "module": "喷淋监控系统",
+    "file": "src/components/vent/BasicMonitoring.vue",
+    "lines": "19,193-201",
+    "title": "操作列表 :key 冲突 + 空场景崩溃",
+    "severity": "high",
+    "type": "vue_key_collision/null_crash",
+    "symptom": "喷淋多个按钮共用 key 导致 DOM 复用异常;无场景数据时页面直接抛错白屏",
+    "root_cause": "v-for 使用 item.key 作唯一键但喷淋键重复;getSysDataSource 无空数组守卫直接取 options[0].id",
+    "repro": "1) 打开喷淋页观察按钮交互状态错乱;2) 后端返回空 records 时进入页面立即报错",
+    "fix_hint": "key 使用 label 或唯一 id;取首项前判断 records.length"
+  },
+  {
+    "module": "智能注氮系统",
+    "file": "src/views/vent/monitorManager/compressor/index.vue",
+    "lines": "93-126",
+    "title": "空列表崩溃且 modalType 未随系统类型切换",
+    "severity": "high",
+    "type": "null_crash/stale_config",
+    "symptom": "无注氮系统时白屏;依赖 modalType 的矿端组件始终按 nitrogenUnderground 建模",
+    "root_cause": "options.value[0]['id'] 无空判断;changeModalType 中 modalType 赋值被注释,仅改了 moduleTypeName",
+    "repro": "空数据进页崩溃;潞宁等使用 modalType 的矿切换空压机/制氮机后模型类型不变",
+    "fix_hint": "空 records 提前 return;恢复按 strsystype 设置 modalType"
+  },
+  {
+    "module": "智能注氮系统",
+    "file": "src/views/vent/monitorManager/compressor/components/nitrogenHome_ln.vue",
+    "lines": "341-349",
+    "title": "监测数据忽略选中系统ID",
+    "severity": "high",
+    "type": "wrong_data_binding",
+    "symptom": "切换注氮系统后仍展示 msgTxt[0] 数据,监控内容与下拉选择不一致",
+    "root_cause": "getDataSource(systemID) 入参未用于请求,固定取 res.msgTxt[0].datalist",
+    "repro": "配置≥2套注氮系统→切换下拉→数据不随 systemId 变化",
+    "fix_hint": "按 systemID/deviceId 过滤或带参请求对应系统数据"
+  },
+  {
+    "module": "智能注浆系统",
+    "file": "src/views/vent/monitorManager/groutMonitor/index.vue",
+    "lines": "110-119,67,86",
+    "title": "空数据崩溃且 currentDeviceType 永不赋值",
+    "severity": "high",
+    "type": "null_crash/dead_state",
+    "symptom": "无注浆系统时白屏;传给 groutHome 的 device-type 恒为空字符串",
+    "root_cause": "options.value[0]['id'] 无守卫;deviceChange 只写 deviceType,从未写入 currentDeviceType",
+    "repro": "空列表进页崩溃;有数据时检查 groutHome props.deviceType 为空",
+    "fix_hint": "空 records 保护;deviceChange/getSelectRow 同步 currentDeviceType"
+  },
+  {
+    "module": "密闭监测系统",
+    "file": "src/views/vent/monitorManager/obfurageMonitor/index.vue",
+    "lines": "89-138",
+    "title": "空数据崩溃且切换系统不刷新关联设备",
+    "severity": "high",
+    "type": "null_crash/stale_state",
+    "symptom": "无密闭系统/无关联设备时崩溃;切换密闭点后历史页设备 Tab 仍是旧设备",
+    "root_cause": "直接访问 msgTxt[0]、deviceArr[0];getSelectRow 只改 optionValue,未调用 getDeviceList",
+    "repro": "空数据进页;有多套密闭→切换后打开历史监测看设备按钮未更新",
+    "fix_hint": "空数组守卫;getSelectRow 内调用 getDeviceList()"
+  },
+  {
+    "module": "防火门智能管控",
+    "file": "src/views/vent/home/configurable/firedoor/fireDoorMonitor.vue",
+    "lines": "76-99",
+    "title": "场景列表为空仍继续后续逻辑且配置增强未启用",
+    "severity": "medium",
+    "type": "incomplete_init",
+    "symptom": "无防火门场景时下拉为空、模块无有效数据;服务端配置未走 updateEnhancedConfigs",
+    "root_cause": "records 为空时仍调用 loadFireGateData;updateEnhancedConfigs 被注释",
+    "repro": "manageFireList 返回空→页面无场景可选且面板无数据",
+    "fix_hint": "无 records 时提示并 return;恢复 updateEnhancedConfigs(configs)"
+  },
+  {
+    "module": "机电硐室管控",
+    "file": "src/views/vent/home/configurable/electroChamber.vue",
+    "lines": "86-99,29-50",
+    "title": "拉取配置后强制覆盖为本地测试配置且历史无设备ID",
+    "severity": "high",
+    "type": "hardcoded_override/missing_param",
+    "symptom": "线上始终使用 testConfigElectro;切历史 Tab 时 deviceId 常为空导致查询无效",
+    "root_cause": "fetchConfigs 成功后 configs.value=testConfigElectro;optionValue 仅来自路由 query,无设备选择赋值",
+    "repro": "非 A02A02 环境打开页面看配置是否被测试数据覆盖;无 query.id 切历史记录",
+    "fix_hint": "生产去掉测试覆盖;补充硐室/系统选择并写入 optionValue"
+  },
+  {
+    "module": "报表数据映射",
+    "file": "src/views/vent/reportManager/comment/reportMap.vue",
+    "lines": "82-119,135-145",
+    "title": "reactive 表单被整体重赋值 + 部门树空 children 崩溃",
+    "severity": "high",
+    "type": "reactivity_break/null_crash",
+    "symptom": "新增/编辑后表单状态异常;部门节点无 children 时查询部门树直接报错",
+    "root_cause": "formState= {...} 丢弃 reactive 代理;el.children.forEach 未判空",
+    "repro": "编辑一条映射再点新增观察表单残留;部门树缺 children 的节点触发加载",
+    "fix_hint": "用 Object.assign(formState, ...) 或重置字段;forEach 前守卫 children"
+  },
+  {
+    "module": "报表数据映射",
+    "file": "src/views/vent/reportManager/comment/report-modal.vue",
+    "lines": "135-151",
+    "title": "编辑时 JSON.parse(mineCells) 在非字符串时抛错",
+    "severity": "high",
+    "type": "type_error",
+    "symptom": "编辑已有映射弹窗打开失败或控制台 TypeError",
+    "root_cause": "watch 中无条件 JSON.parse(formStates.mineCells);接口若已反序列化为数组则解析失败;且 Object.assign 不会清除旧 id",
+    "repro": "打开编辑,当 record.mineCells 为数组或空时复现",
+    "fix_hint": "typeof===string 再 parse,数组则直接使用;新增时显式清空 id"
+  },
+  {
+    "module": "安全监控分站管理",
+    "file": "src/views/vent/safetyList/index.vue",
+    "lines": "234-257,408-417",
+    "title": "卸载引用未定义 timer + children 空指针 + 分页 total 错误",
+    "severity": "high",
+    "type": "reference_error/null_crash",
+    "symptom": "离开页面报 timer is not defined;devInfoList 为空时列表渲染崩溃;分页总数异常",
+    "root_cause": "timer 声明被注释但 onUnmounted 仍引用;el.children=el.devInfoList 后直接 forEach;res.total 在数组结果上常为 undefined",
+    "repro": "进入页再离开;返回含 null devInfoList 的分站;观察 pagination.total",
+    "fix_hint": "删除或恢复 timer;`(el.devInfoList||[]).forEach`;按接口实际结构取 records/total"
+  },
+  {
+    "module": "158分站监测",
+    "file": "src/views/vent/safetyList/common/detail.vue",
+    "lines": "977-1002,1013-1022",
+    "title": "devInfoList 为空时 forEach 崩溃",
+    "severity": "high",
+    "type": "null_crash",
+    "symptom": "部分分站无子设备时详细信息表加载失败",
+    "root_cause": "el.children = el.devInfoList 后未判空直接 forEach(getStationList1/getStationListAll)",
+    "repro": "选择无测点的分站查看详细信息",
+    "fix_hint": "使用可选链或默认空数组后再遍历"
+  },
+  {
+    "module": "130分站监测",
+    "file": "src/views/vent/safetyList/common/detail-130.vue",
+    "lines": "865-892",
+    "title": "devInfoList 空指针(同 index/detail)",
+    "severity": "high",
+    "type": "null_crash",
+    "symptom": "130 分站无子设备时列表处理抛错",
+    "root_cause": "el.children.forEach 未处理 null/undefined devInfoList",
+    "repro": "打开 130 详情页并选中无设备分站",
+    "fix_hint": "遍历前 `el.children = el.devInfoList || []`"
+  },
+  {
+    "module": "235分站监测",
+    "file": "src/views/vent/safetyList/common/detail-235.vue",
+    "lines": "352-369,608-768,888,958-1013",
+    "title": "235 页面误用 130 协议 API",
+    "severity": "critical",
+    "type": "wrong_api",
+    "symptom": "235 分站参数下发、改名、删除等操作打到 130 接口,控制/配置失败或污染错误协议设备",
+    "root_cause": "从 detail-130 复制后未替换:set130StationData/update130DevName/remove130Device/remove130Substation;api 中亦无对应 235 写接口",
+    "repro": "在 235 监测详情编辑并下发任意参数,观察请求 URL 含 130 路径",
+    "fix_hint": "补齐并改用 235 专用读写/删除 API,移除 130 引用"
+  },
+  {
+    "module": "用户日志管理",
+    "file": "src/views/monitor/log/index.vue",
+    "lines": "2,56-84",
+    "title": "v-modal 拼写错误 + 登录统计破坏 searchInfo 响应式",
+    "severity": "high",
+    "type": "typo/reactivity_break",
+    "symptom": "Tab 无法双向绑定;切到“登录统计”后查询条件失效、列表参数丢失 logType",
+    "root_cause": "模板写 v-modal 而非 v-model;case '4' 执行 searchInfo={} 丢掉 reactive 代理",
+    "repro": "打开日志页切换 Tab;切到登录统计后再查询",
+    "fix_hint": "改为 v-model;用 Object.keys 清空或重置字段,禁止重赋 searchInfo"
+  },
+  {
+    "module": "用户日志管理",
+    "file": "src/views/monitor/log/index.vue",
+    "lines": "34,61-66",
+    "title": "初始 fieldMapToTime 格式与切换后不一致",
+    "severity": "medium",
+    "type": "config_mismatch",
+    "symptom": "首次进入登录日志时时间范围映射可能无效,切 Tab 后才正常",
+    "root_cause": "paramTime 初始为扁平数组,tabChange 才设为 [[field,[begin,end],fmt]] 结构",
+    "repro": "首次进入不切 Tab,使用时间筛选对比切 Tab 后行为",
+    "fix_hint": "初始化即使用与 case '1' 相同的 fieldMapToTime 结构"
+  },
+  {
+    "module": "文件共享中心",
+    "file": "src/views/vent/performance/fileDetail/index.vue",
+    "lines": "292-314,367-377,387",
+    "title": "空树上标崩溃 + 可空文件上传 + nodeParam 丢失响应式",
+    "severity": "high",
+    "type": "null_crash/invalid_upload",
+    "symptom": "无目录时白屏;未选文件仍可点确定上传;树节点切换后表格参数可能异常",
+    "root_cause": "listArr[0] 无守卫;handleOk 不校验 fileList;nodeParam=node 重赋放弃 reactive",
+    "repro": "空目录数据进页;打开上传不选文件点确定;切换目录观察列表 parentId",
+    "fix_hint": "空树提示;上传前校验 fileList.length;Object.assign(nodeParam, node)"
+  },
+  {
+    "module": "文件共享中心",
+    "file": "src/views/vent/performance/fileDetail/index.vue",
+    "lines": "243-261",
+    "title": "setInterval 用 clearTimeout 清理",
+    "severity": "medium",
+    "type": "resource_leak",
+    "symptom": "汇总报表轮询可能无法可靠停止,关闭弹窗后仍请求",
+    "root_cause": "timer=setInterval 但 handleCancelHz/onUnmounted 使用 clearTimeout",
+    "repro": "打开汇总报表后关闭弹窗,观察是否仍持续请求 queryVentMonReportJd",
+    "fix_hint": "统一使用 clearInterval(timer)"
+  },
+  {
+    "module": "视频墙",
+    "file": "src/views/vent/monitorManager/camera/index.vue",
+    "lines": "100-128,172-178",
+    "title": "设备树加载与分页 paramKind 计算脆弱",
+    "severity": "high",
+    "type": "null_crash/wrong_param",
+    "symptom": "itemValue 无 '&' 或 msgTxt 结构异常时树展开/翻页失败或请求错误类型",
+    "root_cause": "假定 msgTxt[0].datalist 存在;分页用 searchParam.devKind.indexOf('&') 对 nodeData.itemValue 做 substring,缺 '&' 得空串",
+    "repro": "点击 itemValue 不含 '&' 的分类再翻页;或返回空 datalist 的节点",
+    "fix_hint": "安全解析 itemValue;访问 datalist 前判空;分页直接用 nodeData.itemValue"
+  },
+  {
+    "module": "需风量分析系统",
+    "file": "src/components/vent/micro/createSubApp.vue",
+    "lines": "15-37",
+    "title": "非 micro-* 路径组件恒为 null",
+    "severity": "high",
+    "type": "shell_mount_failure",
+    "symptom": "菜单若未挂在 micro-need-air/micro-vent-doc 前缀下则壳空白",
+    "root_cause": "resetComponent 仅识别有限 appType",
+    "repro": "用非 micro 路径打开需风量/核定",
+    "fix_hint": "按实际菜单 path 扩展映射"
+  },
+  {
+    "module": "喷雾降尘监控",
+    "file": "src/router/helper/routeHelper.ts",
+    "lines": "21-28,98-113,122-146",
+    "title": "layouts/RouteView 组件文件缺失导致路由无法解析",
+    "severity": "critical",
+    "type": "missing_component",
+    "symptom": "菜单 component 配为 layouts/RouteView 的父级打开空白/路由失败",
+    "root_cause": "src/layouts 下无 RouteView.vue;LayoutMap 未注册;dynamicImport 只扫 views/components",
+    "repro": "后端菜单配置 component=layouts/RouteView 后进入喷雾降尘父路由",
+    "fix_hint": "新增 layouts/RouteView.vue 并加入 LayoutMap;菜单改为已存在布局"
+  },
+  {
+    "module": "喷雾降尘监控",
+    "file": "src/views/vent/home/configurable/dust.vue",
+    "lines": "87-93",
+    "title": "服务端配置被测试数据覆盖且只用 Mock",
+    "severity": "high",
+    "type": "hardcoded_override",
+    "symptom": "粉尘首页始终展示 testConfigDust + BDDustMock,真实监测数据不刷新",
+    "root_cause": "fetchConfigs('dust') 后强制 configs=testConfigDust,并用 Promise.resolve(BDDustMock) 代替 getHomeData",
+    "repro": "任意环境打开粉尘可配置首页,对比接口与页面数据源",
+    "fix_hint": "生产移除测试覆盖,恢复 getHomeData 轮询"
+  },
+  {
+    "module": "喷雾降尘监控",
+    "file": "src/views/vent/home/configurable/dustControl.vue",
+    "lines": "53-54",
+    "title": "错误 pageType 拉取后再覆盖测试配置",
+    "severity": "high",
+    "type": "wrong_param/hardcoded_override",
+    "symptom": "降尘管控页配置与粉尘业务不匹配,最终仍被本地测试配置覆盖",
+    "root_cause": "fetchConfigs('mine_fire') 后 configs.value=testConfigDustControl",
+    "repro": "打开 dustControl 页检查 configuration 请求 pageType 与最终 configs",
+    "fix_hint": "使用正确粉尘 pageType,并按环境决定是否使用测试配置"
+  },
+  {
+    "module": "安全监控分站管理",
+    "file": "src/views/vent/safetyList/index.vue",
+    "lines": "144-146,71-76",
+    "title": "调试风向取值与展示映射不一致",
+    "severity": "medium",
+    "type": "value_mismatch",
+    "symptom": "调试选“反向”写入 '0',列表展示逻辑把反风映射为 '2',显示异常",
+    "root_cause": "derictList 反向 value='0',而 valueJc 渲染把 '2' 当作反风",
+    "repro": "调试设备选反向并确认,查看列表风向展示",
+    "fix_hint": "统一风向枚举(建议 1 正/2 反)并同步调试与展示"
+  },
+  {
+    "module": "报表管理",
+    "file": "src/views/vent/reportManager/index.vue",
+    "lines": "89-106",
+    "title": "三类根节点重复挂载同一套子树",
+    "severity": "medium",
+    "type": "wrong_tree_build",
+    "symptom": "系统/手动/模板三类下出现完全相同的业务分类,筛选易混淆",
+    "root_cause": "getTreeList 对 listArr 每个根节点都 push 完整 res 树,未按业务类型分流",
+    "repro": "打开报表管理对比三个根节点的 children",
+    "fix_hint": "按返回字段或接口分类型填充到对应根节点"
+  },
+  {
+    "module": "智能注浆系统",
+    "file": "src/views/vent/monitorManager/groutMonitor/index.vue",
+    "lines": "1-35",
+    "title": "2D 模型与 groutHome 同时渲染可能叠层",
+    "severity": "medium",
+    "type": "ui_logic",
+    "symptom": "is2DModel 为 true 时流程图与监控壳同时存在,交互/遮罩异常",
+    "root_cause": "v-if is2DModel 渲染 modelComponent,同时 activeKey==monitor 仍挂载 groutHome",
+    "repro": "开启全局 is2DModel 进入注浆监控页",
+    "fix_hint": "2D 时由单一容器负责模型,或让 groutHome 在 2D 模式跳过三维初始化"
+  },
+  {
+    "module": "机电硐室管控",
+    "file": "src/views/vent/home/configurable/configurable.data.ts",
+    "lines": "6164,6217",
+    "title": "electro-champer / electro-chamber pageType 拼写分裂",
+    "severity": "medium",
+    "type": "config_typo",
+    "symptom": "部分配置按错误 pageType 入库后,fetchConfigs('electro-champer') 只能拿到半套模块",
+    "root_cause": "同一业务存在 champer/chamber 两种 pageType 字面量",
+    "repro": "在配置表按两种 pageType 各查一次 records 数量",
+    "fix_hint": "统一为一种拼写并做数据迁移"
+  },
+  {
+    "module": "密闭监测系统",
+    "file": "src/views/vent/monitorManager/obfurageMonitor/index.vue",
+    "lines": "107-113",
+    "title": "forEach 内 data=Object.assign 未回写",
+    "severity": "medium",
+    "type": "dead_assignment",
+    "symptom": "readData 字段未合并进列表项,监测展示缺字段",
+    "root_cause": "dataSource.forEach 中 `data = Object.assign(data, readData)` 只改局部绑定,未写回数组元素",
+    "repro": "对比接口 readData 与页面 options 字段是否缺失",
+    "fix_hint": "改为 Object.assign(data, readData) 不重绑,或显式写回"
+  }
+]

+ 132 - 0
docs/_agent_fire_dust.raw.txt

@@ -0,0 +1,132 @@
+[
+  {
+    "module": "喷淋监控系统",
+    "file": "src/views/vent/monitorManager/sprayMonitor/index.vue",
+    "lines": "68-140",
+    "title": "多路控制按钮共用同一 paramcode/value",
+    "severity": "critical",
+    "type": "wrong_control_payload",
+    "symptom": "除急停外,补水阀/搅拌阀/组化泵/上料机/喷洒自动等按钮下发完全相同的指令,设备无法按按钮区分控制,存在误控风险",
+    "root_cause": "operations 全部使用 key='run_auto_sign',且多数 value='0';BasicMonitoring.deviceControl 仅按 key/value 下发",
+    "repro": "打开喷淋监控→输入密码点击“补水阀启停”与“喷洒自动”→抓包可见 paramcode/value 相同",
+    "fix_hint": "为每个操作配置独立 paramcode,并按启停状态设置正确 value(参考业务点表)"
+  },
+  {
+    "module": "喷淋监控系统",
+    "file": "src/components/vent/BasicMonitoring.vue",
+    "lines": "19,193-201",
+    "title": "操作列表 :key 冲突 + 空场景崩溃",
+    "severity": "high",
+    "type": "vue_key_collision/null_crash",
+    "symptom": "喷淋多个按钮共用 key 导致 DOM 复用异常;无场景数据时页面直接抛错白屏",
+    "root_cause": "v-for 使用 item.key 作唯一键但喷淋键重复;getSysDataSource 无空数组守卫直接取 options[0].id",
+    "repro": "1) 打开喷淋页观察按钮交互状态错乱;2) 后端返回空 records 时进入页面立即报错",
+    "fix_hint": "key 使用 label 或唯一 id;取首项前判断 records.length"
+  },
+  {
+    "module": "智能注氮系统",
+    "file": "src/views/vent/monitorManager/compressor/index.vue",
+    "lines": "93-126",
+    "title": "空列表崩溃且 modalType 未随系统类型切换",
+    "severity": "high",
+    "type": "null_crash/stale_config",
+    "symptom": "无注氮系统时白屏;依赖 modalType 的矿端组件始终按 nitrogenUnderground 建模",
+    "root_cause": "options.value[0]['id'] 无空判断;changeModalType 中 modalType 赋值被注释,仅改了 moduleTypeName",
+    "repro": "空数据进页崩溃;潞宁等使用 modalType 的矿切换空压机/制氮机后模型类型不变",
+    "fix_hint": "空 records 提前 return;恢复按 strsystype 设置 modalType"
+  },
+  {
+    "module": "智能注氮系统",
+    "file": "src/views/vent/monitorManager/compressor/components/nitrogenHome_ln.vue",
+    "lines": "341-349",
+    "title": "监测数据忽略选中系统ID",
+    "severity": "high",
+    "type": "wrong_data_binding",
+    "symptom": "切换注氮系统后仍展示 msgTxt[0] 数据,监控内容与下拉选择不一致",
+    "root_cause": "getDataSource(systemID) 入参未用于请求,固定取 res.msgTxt[0].datalist",
+    "repro": "配置≥2套注氮系统→切换下拉→数据不随 systemId 变化",
+    "fix_hint": "按 systemID/deviceId 过滤或带参请求对应系统数据"
+  },
+  {
+    "module": "智能注浆系统",
+    "file": "src/views/vent/monitorManager/groutMonitor/index.vue",
+    "lines": "110-119,67,86",
+    "title": "空数据崩溃且 currentDeviceType 永不赋值",
+    "severity": "high",
+    "type": "null_crash/dead_state",
+    "symptom": "无注浆系统时白屏;传给 groutHome 的 device-type 恒为空字符串",
+    "root_cause": "options.value[0]['id'] 无守卫;deviceChange 只写 deviceType,从未写入 currentDeviceType",
+    "repro": "空列表进页崩溃;有数据时检查 groutHome props.deviceType 为空",
+    "fix_hint": "空 records 保护;deviceChange/getSelectRow 同步 currentDeviceType"
+  },
+  {
+    "module": "密闭监测系统",
+    "file": "src/views/vent/monitorManager/obfurageMonitor/index.vue",
+    "lines": "89-138",
+    "title": "空数据崩溃且切换系统不刷新关联设备",
+    "severity": "high",
+    "type": "null_crash/stale_state",
+    "symptom": "无密闭系统/无关联设备时崩溃;切换密闭点后历史页设备 Tab 仍是旧设备",
+    "root_cause": "直接访问 msgTxt[0]、deviceArr[0];getSelectRow 只改 optionValue,未调用 getDeviceList",
+    "repro": "空数据进页;有多套密闭→切换后打开历史监测看设备按钮未更新",
+    "fix_hint": "空数组守卫;getSelectRow 内调用 getDeviceList()"
+  },
+  {
+    "module": "防火门智能管控",
+    "file": "src/views/vent/home/configurable/firedoor/fireDoorMonitor.vue",
+    "lines": "76-99",
+    "title": "场景列表为空仍继续后续逻辑且配置增强未启用",
+    "severity": "medium",
+    "type": "incomplete_init",
+    "symptom": "无防火门场景时下拉为空、模块无有效数据;服务端配置未走 updateEnhancedConfigs",
+    "root_cause": "records 为空时仍调用 loadFireGateData;updateEnhancedConfigs 被注释",
+    "repro": "manageFireList 返回空→页面无场景可选且面板无数据",
+    "fix_hint": "无 records 时提示并 return;恢复 updateEnhancedConfigs(configs)"
+  },
+  {
+    "module": "机电硐室管控",
+    "file": "src/views/vent/home/configurable/electroChamber.vue",
+    "lines": "86-99,29-50",
+    "title": "拉取配置后强制覆盖为本地测试配置且历史无设备ID",
+    "severity": "high",
+    "type": "hardcoded_override/missing_param",
+    "symptom": "线上始终使用 testConfigElectro;切历史 Tab 时 deviceId 常为空导致查询无效",
+    "root_cause": "fetchConfigs 成功后 configs.value=testConfigElectro;optionValue 仅来自路由 query,无设备选择赋值",
+    "repro": "非 A02A02 环境打开页面看配置是否被测试数据覆盖;无 query.id 切历史记录",
+    "fix_hint": "生产去掉测试覆盖;补充硐室/系统选择并写入 optionValue"
+  },
+  {
+    "module": "报表管理",
+    "file": "src/views/vent/reportManager/comment/reportMap.vue",
+    "lines": "82-119,135-145",
+    "title": "reactive 表单被整体重赋值 + 部门树空 children 崩溃",
+    "severity": "high",
+    "type": "reactivity_break/null_crash",
+    "symptom": "新增/编辑后表单状态异常;部门节点无 children 时查询部门树直接报错",
+    "root_cause": "formState= {...} 丢弃 reactive 代理;el.children.forEach 未判空",
+    "repro": "编辑一条映射再点新增观察表单残留;部门树缺 children 的节点触发加载",
+    "fix_hint": "用 Object.assign(formState, ...) 或重置字段;forEach 前守卫 children"
+  },
+  {
+    "module": "报表数据映射",
+    "file": "src/views/vent/reportManager/comment/report-modal.vue",
+    "lines": "135-151",
+    "title": "编辑时 JSON.parse(mineCells) 在非字符串时抛错",
+    "severity": "high",
+    "type": "type_error",
+    "symptom": "编辑已有映射弹窗打开失败或控制台 TypeError",
+    "root_cause": "watch 中无条件 JSON.parse(formStates.mineCells);接口若已反序列化为数组则解析失败;且 Object.assign 不会清除旧 id",
+    "repro": "打开编辑,当 record.mineCells 为数组或空时复现",
+    "fix_hint": "typeof===string 再 parse,数组则直接使用;新增时显式清空 id"
+  },
+  {
+    "module": "安全监控分站管理",
+    "file": "src/views/vent/safetyList/index.vue",
+    "lines": "234-257,408-417",
+    "title": "卸载引用未定义 timer + children 空指针 + 分页 total 错误",
+    "severity": "high",
+    "type": "reference_error/null_crash",
+    "symptom": "离开页面报 timer is not defined;devInfoList 为空时列表渲染崩溃;分页总数异常",
+    "root_cause": "timer 声明被注释但 onUnmounted 仍引用;el.children=el.devInfoList 后直接 forEach;res.total 在数组结果上常为 undefined",
+    "repro": "进入页再离开;返回含 null devInfoList 的分站;观察 pagination.total",
+    "fix_hint": "删除或恢复 timer;

+ 302 - 0
docs/_agent_monitor.json

@@ -0,0 +1,302 @@
+[
+  {
+    "id-suggestion": "MON-WINDRECT-EXPORT-IMPORT-API",
+    "module": "测风装置管控",
+    "file path with line ranges": "src/views/vent/monitorManager/windrectMonitor/windrect.api.ts:59; src/views/vent/monitorManager/windrectMonitor/index.vue:769-771",
+    "title": "测风导出函数实际请求导入接口",
+    "severity": "致命",
+    "type": "export calling wrong import API",
+    "symptom": "一键测风结果导出不会调用 /safety/testwindDetail/exportXls,而是 POST /sys/user/importExcel,导出失败或误触发用户导入接口",
+    "root_cause": "exportXls 实现写成 defHttp.post({ url: Api.importExcel }),而 index.vue 的 exportExcel 直接调用该函数(未使用正确的 getExportUrl)",
+    "repro": "打开测风装置管控 → 触发 exportExcel(id) / 结果导出 → 抓包可见请求 importExcel",
+    "fix_hint": "将 exportXls 的 url 改为 Api.exportXls,或改为使用 getExportUrl 走正确导出下载链路"
+  },
+  {
+    "id-suggestion": "MON-MAINFAN-CHANGE-SMOKE1-SHARED-PARAM",
+    "module": "主风机管控",
+    "file path with line ranges": "src/views/vent/monitorManager/mainFanMonitor/index.vue:1609-1662",
+    "title": "倒机正转/反转共用同一 paramcode",
+    "severity": "致命",
+    "type": "control buttons sharing same paramcode",
+    "symptom": "changeSmoke1 场景下正启动与反启动都下发 CtrlFan1ToFan2 / CtrlFan2ToFan1,无法区分正反转倒机指令",
+    "root_cause": "同文件 changeSmoke 分支正确使用 CtrlFan1ToFan2Forward/Reverse 与 CtrlFan2ToFan1Forward/Reverse,但 changeSmoke1 分支正反转都调用无方向后缀的同一点位",
+    "repro": "风机处于正转/反转运行态触发一键倒机(modalType=changeSmoke1)→ 对比下发 paramcode",
+    "fix_hint": "与 changeSmoke 分支对齐,正转用 *Forward、反转用 *Reverse"
+  },
+  {
+    "id-suggestion": "MON-WINDOW-REAR-GATE-LABEL-MISMATCH",
+    "module": "风窗管控",
+    "file path with line ranges": "src/views/vent/monitorManager/windowMonitor/index.vue:37-41",
+    "title": "后门控制按钮文案错写成前门",
+    "severity": "严重",
+    "type": "control buttons sharing same paramcode / mislabeled control",
+    "symptom": "界面显示“打开前门/关闭前门”,实际下发 rearGateOpen_S/rearGateClose_S,操作员会误控后门",
+    "root_cause": "按钮展示文案复制前门未改,第二参数标题已是后门,UI 文本与指令不一致",
+    "repro": "具备 window:gateControl 且 ndoorcount==2 → 看后两颗按钮文案与 setControl 参数",
+    "fix_hint": "将后两颗按钮文案改为“打开后门/关闭后门”"
+  },
+  {
+    "id-suggestion": "MON-WINDOW-AREA-ANGLE-SHARED-PARAM",
+    "module": "风窗管控",
+    "file path with line ranges": "src/views/vent/monitorManager/windowMonitor/index.vue:11-14,623-651",
+    "title": "窗面积与窗角度控制共用 paramcode 1/2",
+    "severity": "严重",
+    "type": "control buttons sharing same paramcode",
+    "symptom": "窗1/窗2 面积设定与角度设定最终都下发 paramcode='1'/'2',后端无法区分面积/角度",
+    "root_cause": "twoAreaControl 与 twoAngleControl 都 setControl('1'|'2'),handleOK else 分支直接 data.paramcode=handlerState",
+    "repro": "同时开启 window:twoAreaControl 与 window:twoAngleControl → 分别设定面积/角度 → 抓包对比 paramcode",
+    "fix_hint": "角度使用独立点位(如 frontSetAngle1),或在 value/附加字段中区分控制类型"
+  },
+  {
+    "id-suggestion": "MON-WINDOW-SHOWANGLEAREA-USES-SETANGLE",
+    "module": "风窗管控",
+    "file path with line ranges": "src/views/vent/monitorManager/windowMonitor/index.vue:31-33,504-520",
+    "title": "设定风窗面积误走角度弹窗流程",
+    "severity": "严重",
+    "type": "control buttons sharing same paramcode",
+    "symptom": "权限 window:showAngleArea 的“设定风窗面积”调用 setAngle(1),弹窗标题按角度生成,但点位仍 frontSetValue",
+    "root_cause": "面积按钮错误绑定 setAngle 而非 setArea",
+    "repro": "开启 window:showAngleArea → 点击“设定风窗面积” → 观察 modalTitle 与下发参数",
+    "fix_hint": "改为 setArea(1),或单独实现面积设定入口"
+  },
+  {
+    "id-suggestion": "MON-FANLOCAL-DUP-DIAMETER-BTN",
+    "module": "局部风机管控",
+    "file path with line ranges": "src/views/vent/monitorManager/fanLocalMonitor/index.vue:782-791",
+    "title": "风筒直径控制按钮重复且同 key",
+    "severity": "严重",
+    "type": "control buttons sharing same paramcode",
+    "symptom": "控制区出现两个相同“风筒直径”按钮,key 均为 diameter,行为完全重复",
+    "root_cause": "leftBtnArr 中 diameter 配置项被复制粘贴两次",
+    "repro": "开启 fanLocal:diameter → 顶部控制按钮可见重复项",
+    "fix_hint": "删除重复配置;若本意为主/备直径,需不同 key 与 paramcode"
+  },
+  {
+    "id-suggestion": "MON-NETWORK-DHLOCAL-WRONG-FIELD",
+    "module": "通风系统监测/通风网络解算",
+    "file path with line ranges": "src/views/vent/monitorManager/deviceMonitor/components/network/index.vue:403-405",
+    "title": "局部阻力字段误读自然风压",
+    "severity": "严重",
+    "type": "wrong field mapping / logic bug",
+    "symptom": "巷道表“局部阻力”与“自然风压”显示相同值,局部阻力失真",
+    "root_cause": "dHLocal 赋值使用 item['dHNature'],与 dHNature 同源",
+    "repro": "打开网络解算结果表 → 对比局部阻力与自然风压列",
+    "fix_hint": "改为读取 item['dHLocal'](或后端真实局部阻力字段)"
+  },
+  {
+    "id-suggestion": "MON-EMERGENCY-DHLOCAL-WRONG-FIELD",
+    "module": "灾变应急控制",
+    "file path with line ranges": "src/views/vent/monitorManager/deviceMonitor/components/emergency/index.vue:273-275",
+    "title": "应急解算局部阻力同样误绑 dHNature",
+    "severity": "严重",
+    "type": "wrong field mapping / logic bug",
+    "symptom": "灾变应急巷道阻力表局部阻力错误",
+    "root_cause": "与 network 相同的复制错误:dHLocal: item['dHNature']",
+    "repro": "进入灾变应急页加载解算结果 → 查看巷道阻力列",
+    "fix_hint": "改为 item['dHLocal']"
+  },
+  {
+    "id-suggestion": "MON-EMERGENCY-SHARED-DETAIL-CODES",
+    "module": "灾变应急控制",
+    "file path with line ranges": "src/views/vent/monitorManager/deviceMonitor/components/emergency/index.vue:196-226",
+    "title": "工作面详情多项共用同一数据 code",
+    "severity": "严重",
+    "type": "wrong field mapping / logic bug",
+    "symptom": "工作面编号/采区/煤层都显示 dTotalArea;长度/走向/采高都显示 totallength",
+    "root_cause": "analysisDetailDataList 多项复用相同 code,未映射独立字段",
+    "repro": "打开灾变应急分析详情区 → 观察多行数值是否雷同",
+    "fix_hint": "为每项配置正确业务字段 code,并在 pageData 中补齐对应值"
+  },
+  {
+    "id-suggestion": "MON-NETWORK-READDATA-NULL-DEREF",
+    "module": "通风系统监测/通风网络解算",
+    "file path with line ranges": "src/views/vent/monitorManager/deviceMonitor/components/network/index.vue:447-454",
+    "title": "传感器列表未判空直接读 readData",
+    "severity": "严重",
+    "type": "null deref on mount/update",
+    "symptom": "monitors 中某条缺少 readData 时 analyzePageResult 抛错,页面解算结果渲染中断",
+    "root_cause": "直接访问 item['readData']['va']/['m3'],无空保护",
+    "repro": "构造 monitors 项 readData=null 的解算推送 → 打开网络页",
+    "fix_hint": "先取 const rd=item.readData||{} 再读字段"
+  },
+  {
+    "id-suggestion": "MON-EMERGENCY-READDATA-NULL-DEREF",
+    "module": "灾变应急控制",
+    "file path with line ranges": "src/views/vent/monitorManager/deviceMonitor/components/emergency/index.vue:314-321",
+    "title": "应急页传感器 readData 空指针",
+    "severity": "严重",
+    "type": "null deref on mount/update",
+    "symptom": "同网络页,缺 readData 时应急页崩溃",
+    "root_cause": "item['readData']['va'] 无空判断",
+    "repro": "推送缺 readData 的 monitors → 打开应急页",
+    "fix_hint": "对 readData 做空对象兜底"
+  },
+  {
+    "id-suggestion": "MON-FIBER-HARDCODE-WARN",
+    "module": "光纤测温监测",
+    "file path with line ranges": "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.vue:49,169-188,232-257",
+    "title": "光纤详情风险等级与预警历史写死假数据",
+    "severity": "严重",
+    "type": "mock hardcode replacing real API",
+    "symptom": "风险等级恒为“低风险”,预警历史永远是测点6/318℃等假记录,不随真实报警变化",
+    "root_cause": "模板写死低风险;warningConfig.data 硬编码数组,watch 未用真实告警填充",
+    "repro": "打开光纤测温弹窗 → 对比真实告警与滚动板内容",
+    "fix_hint": "绑定设备真实风险字段与告警历史接口/数据"
+  },
+  {
+    "id-suggestion": "MON-FIBER-READDATA-NULL-DEREF",
+    "module": "光纤测温监测",
+    "file path with line ranges": "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.vue:239-243",
+    "title": "光纤弹窗 Object.keys(readData) 空指针",
+    "severity": "严重",
+    "type": "null deref on mount/update",
+    "symptom": "设备无 readData 时打开弹窗直接 TypeError",
+    "root_cause": "未判断 item.readData 即 Object.keys(item.readData)",
+    "repro": "选中 readData 为空的光纤设备打开详情",
+    "fix_hint": "if (!item.readData) return; 后再解析分段温度"
+  },
+  {
+    "id-suggestion": "MON-DUST-HARDCODE-WARN",
+    "module": "防尘系统监测",
+    "file path with line ranges": "src/views/vent/monitorManager/deviceMonitor/components/device/modal/dust.modal.vue:216-238",
+    "title": "粉尘弹窗测点状态/预警历史硬编码",
+    "severity": "严重",
+    "type": "mock hardcode replacing real API",
+    "symptom": "测点状态 customRender 永远返回“正常”;预警滚动板为固定假数据",
+    "root_cause": "columns.state 写死正常;warningConfig.data 硬编码",
+    "repro": "打开粉尘监测详情弹窗观察状态与预警列表",
+    "fix_hint": "状态取真实字段;预警列表接真实告警数据"
+  },
+  {
+    "id-suggestion": "MON-FIREMON-TEMP-UNIT-WRONG",
+    "module": "防灭火系统监测",
+    "file path with line ranges": "src/views/vent/monitorManager/deviceMonitor/components/device/modal/firemon.modal.vue:63-65",
+    "title": "智慧球温度值单位误标为 %",
+    "severity": "一般",
+    "type": "logic/display bug",
+    "symptom": "温度值旁显示 % 而非 ℃,监控读数语义错误",
+    "root_cause": "模板单位复制错误",
+    "repro": "打开 firemon 详情查看温度项单位",
+    "fix_hint": "将单位改为 ℃"
+  },
+  {
+    "id-suggestion": "MON-ATOMIZING-LOCATION-UNIT-WRONG",
+    "module": "喷雾降尘/防尘系统监测",
+    "file path with line ranges": "src/views/vent/monitorManager/deviceMonitor/components/device/modal/atomizing.modal.vue:33-42",
+    "title": "主机位置字段错误附加 ℃ 单位",
+    "severity": "一般",
+    "type": "logic/display bug",
+    "symptom": "位置类字段后显示温度单位,信息误导",
+    "root_cause": "holderLocation 展示模板误加 ℃",
+    "repro": "打开喷雾监测详情查看“主机位置”",
+    "fix_hint": "去掉 ℃,位置字段无需单位"
+  },
+  {
+    "id-suggestion": "MON-BETKY-MOCK-AND-POLL-BUG",
+    "module": "工作面来压预警",
+    "file path with line ranges": "src/views/vent/monitorManager/betKy/index.vue:110-146,253-263",
+    "title": "矿压监测轮询逻辑错误且特定矿用假数据",
+    "severity": "严重",
+    "type": "mock hardcode + setInterval/setTimeout poll without proper clear",
+    "symptom": "A02A02 矿强制使用 ceshi.json;轮询前把 timer 置 null 未 clearTimeout,chartData 每次 push 不清理导致曲线数据膨胀错乱;卸载用 clearInterval 清 setTimeout",
+    "root_cause": "getDataSource 用本地 json 替换真实 list;timer 管理与 chartData 累积错误",
+    "repro": "进入 betKy 页(尤其 A02A02)观察请求与图表随时间变化",
+    "fix_hint": "去掉矿别假数据分支;轮询用 clearTimeout;每次刷新重置 chartData"
+  },
+  {
+    "id-suggestion": "MON-BETKY-READDATA-NULL",
+    "module": "工作面来压预警",
+    "file path with line ranges": "src/views/vent/monitorManager/betKy/index.vue:127-131",
+    "title": "矿压轮询 Object.values(readData) 空指针",
+    "severity": "严重",
+    "type": "null deref on mount",
+    "symptom": "设备缺 readData 时页面轮询崩溃,后续刷新停止",
+    "root_cause": "未判空直接 Object.values(readData)",
+    "repro": "返回含 readData=null 的 Minepressure 列表",
+    "fix_hint": "if (!readData) continue; 后再处理"
+  },
+  {
+    "id-suggestion": "MON-AIRDOOR-TIPMODAL-TIMER-LEAK",
+    "module": "风门集中同控",
+    "file path with line ranges": "src/views/vent/monitorManager/airDoor/components/tipModal.vue:28-54",
+    "title": "倒计时弹窗 setTimeout 递归不清理",
+    "severity": "严重",
+    "type": "setInterval/setTimeout poll without clear on unmount",
+    "symptom": "提示弹窗显示后每秒递归创建定时器;关闭/卸载后仍继续;timeout<=0 时先 timer=undefined 再 clearTimeout(undefined) 无效,且仍调用 getMonitor()",
+    "root_cause": "无 onUnmounted/Visible=false 时 clear;递归无退出;clear 顺序错误",
+    "repro": "触发 visibleTs30=true → 关闭弹窗或离开页面 → 观察仍有定时回调",
+    "fix_hint": "保存 timerId;Visible 变 false 与 onUnmounted 时 clearTimeout;倒计时结束停止递归"
+  },
+  {
+    "id-suggestion": "MON-AIRDOOR-MSGTXT-NULL-DEREF",
+    "module": "风门集中同控",
+    "file path with line ranges": "src/views/vent/monitorManager/airDoor/index.vue:44-51",
+    "title": "风门同控菜单拉取未判空 msgTxt[0]",
+    "severity": "严重",
+    "type": "null deref on mount",
+    "symptom": "接口返回空/异常结构时 getMenuList 抛错,左侧菜单无法加载",
+    "root_cause": "直接访问 res.msgTxt[0].datalist.length",
+    "repro": "mock /monitor/device 返回无 msgTxt 或空数组 → 打开风门集中同控",
+    "fix_hint": "使用可选链与默认 []:res?.msgTxt?.[0]?.datalist || []"
+  },
+  {
+    "id-suggestion": "MON-WORKFACE-WARNINFO-KEY-BUG",
+    "module": "综采面智能管控",
+    "file path with line ranges": "src/views/vent/monitorManager/workFaceMonitor/components/workFaceHome.vue:262-276",
+    "title": "综合预警判断键错误且可能空指针",
+    "severity": "严重",
+    "type": "null deref / wrong field mapping",
+    "symptom": "用 warnInfo.warnInfo 判断却读取 synthesizeS;键不匹配时综合预警不更新,或 synthesizeS 缺失时崩溃;surface_history/sysInfo 亦缺保护",
+    "root_cause": "条件写成 res['warnInfo']['warnInfo'];并对 surface_history[0]、sysInfo.readData 无空保护",
+    "repro": "返回缺 synthesizeS 或 surface_history 为空的综采面数据",
+    "fix_hint": "条件改为 synthesizeS;对各嵌套字段做空判断"
+  },
+  {
+    "id-suggestion": "MON-TUNFACE-WARN-NULL-DEREF",
+    "module": "掘进面智能管控",
+    "file path with line ranges": "src/views/vent/monitorManager/tunFaceMonitor/components/tunFaceHome.vue:254,262-271",
+    "title": "掘进面首页无条件解包 warnInfo/readData",
+    "severity": "严重",
+    "type": "null deref on mount",
+    "symptom": "缺 dustS/fireS/gasS/synthesizeS/ventS、缺 surface_history、或缺 location.readData 时页面直接崩溃",
+    "root_cause": "无守卫直接访问 res['warnInfo'][x]['devices']、res['surface_history'][0]、data['readData']['flen']",
+    "repro": "接口缺任一 warn 分组或 location.readData → 打开掘进面主页",
+    "fix_hint": "逐层可选链;缺省赋空数组/空对象"
+  },
+  {
+    "id-suggestion": "MON-BELTTUN-WARN-NULL-DEREF",
+    "module": "皮带巷管控系统",
+    "file path with line ranges": "src/views/vent/monitorManager/beltTunMonitor/components/beltTunHome.vue:260-268",
+    "title": "皮带巷首页 warnInfo/sysInfo 无空保护",
+    "severity": "严重",
+    "type": "null deref on mount",
+    "symptom": "sysInfo 或某类 warn 分组缺失时页面加载失败",
+    "root_cause": "直接 Object.assign(res['sysInfo'], res['sysInfo']['readData']) 与 warnInfo.*.devices",
+    "repro": "返回不完整 beltTun 监测包 → 打开皮带巷主页",
+    "fix_hint": "增加空判断与默认值"
+  },
+  {
+    "id-suggestion": "MON-WORKFACE-LOCATION-FLEN-NULL",
+    "module": "综采面智能管控",
+    "file path with line ranges": "src/views/vent/monitorManager/workFaceMonitor/components/workFaceHome.vue:251-254",
+    "title": "定位列表读取 readData.flen 空指针",
+    "severity": "严重",
+    "type": "null deref on mount/update",
+    "symptom": "location 设备无 readData 时过滤阶段抛错,后续监控数据不完整",
+    "root_cause": "locationData.push 中直接 data['readData']['flen']",
+    "repro": "location.datalist 含 readData=null 的项",
+    "fix_hint": "使用 data?.readData?.flen ?? '-'"
+  },
+  {
+    "id-suggestion": "MON-MAINFAN-AREA-SIM-INTERVAL-LEAK",
+    "module": "主风机管控",
+    "file path with line ranges": "src/views/vent/monitorManager/mainFanMonitor/hooks/useAreaSetSimulation.ts:37-109; useMainFanSimulation.ts:25-72",
+    "title": "天窗/闸板面积模拟 setInterval 卸载未清理",
+    "severity": "一般",
+    "type": "setInterval/setTimeout poll without clear on unmount",
+    "symptom": "开启 fanSwitchoverSimulation 后设定面积过程中离开页面,interval 仍改 selectData,可能写已销毁状态",
+    "root_cause": "useAreaSetSimulation 仅在过程结束 clear;无 onUnmounted 遍历 activeProcesses 清理(对比 useFanSwitchoverSimulation 有 stopTick)",
+    "repro": "开启模拟 → 设定天窗面积 → 过渡未完成即路由离开",
+    "fix_hint": "在 composable 增加 dispose/onUnmounted,清理 Map 内全部 interval"
+  }
+]

+ 569 - 0
docs/_coverage.json

@@ -0,0 +1,569 @@
+[
+  {
+    "module": "主风机管控",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue;src\\views\\vent\\monitorManager\\mainFanMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-002,BUG-088",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "局部风机管控",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue;src\\views\\vent\\monitorManager\\fanLocalMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-034,BUG-095",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "测风装置管控",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue;src\\views\\vent\\monitorManager\\windrectMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-007",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "风门管控",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue;src\\views\\vent\\monitorManager\\gateMonitor\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "风窗管控",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue;src\\views\\vent\\monitorManager\\windowMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-083,BUG-084,BUG-085",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "气压温湿度监测",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\sensorMonitor\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "风门集中同控",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\airDoor\\index.vue",
+    "static": "已静态审查;关联 BUG-086,BUG-087",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "需风量分析系统",
+    "map_status": "micro",
+    "paths": "src\\components\\vent\\micro\\createSubApp.vue",
+    "static": "已静态审查;关联 BUG-082",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "通风参数一张表",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\staticSheets\\ventilateSheet.vue",
+    "static": "已静态审查;关联 BUG-015,BUG-077",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "通风能力核定",
+    "map_status": "micro",
+    "paths": "src\\components\\vent\\micro\\createSubApp.vue",
+    "static": "微前端壳已映射;壳层已审查",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "130分站监测",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\safetyList\\common\\detail-130.vue",
+    "static": "已静态审查;关联 BUG-012,BUG-013",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "158分站监测",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\safetyList\\common\\detail.vue",
+    "static": "已静态审查;关联 BUG-014",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "235分站监测",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\safetyList\\common\\detail-235.vue",
+    "static": "已静态审查;关联 BUG-001",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "通风系统监测",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "通风网络解算",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-079,BUG-080",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "关键通风路线",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "实时网络解算",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "控风方案决策",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "多灾融合预警",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\alarmMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-005,BUG-021,BUG-022,BUG-023,BUG-024,BUG-025,BUG-026,BUG-027,BUG-028,BUG-029…",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "通风监测预警",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\alarmMonitor\\warn\\ventilateWarn.vue",
+    "static": "已静态审查;关联 BUG-078",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "火灾监测预警",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\alarmMonitor\\warn\\fireWarn.vue",
+    "static": "已静态审查;关联 BUG-054",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "粉尘监测预警",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\alarmMonitor\\warn\\dustWarn.vue",
+    "static": "已静态审查;关联 BUG-071",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "瓦斯监测预警",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\alarmMonitor\\warn\\gasWarn.vue",
+    "static": "已静态审查;关联 BUG-009,BUG-063",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "灾变应急控制",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-055,BUG-056,BUG-057",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "设备监测预警",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitorWarn\\index.vue",
+    "static": "已静态审查;关联 BUG-076",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "工作面来压预警",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\betKy\\index.vue",
+    "static": "已静态审查;关联 BUG-035",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "预警联动控制",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\deviceManager\\workingFace\\index.vue",
+    "static": "已静态审查;关联 BUG-011,BUG-109,BUG-110",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "光纤测温监测",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-016,BUG-017",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "束管监测",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "防火门监测",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "密闭监测系统",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\obfurageMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-033,BUG-094",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "喷淋监控系统",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\sprayMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-003,BUG-018",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "智能注氮系统",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\compressor\\index.vue",
+    "static": "已静态审查;关联 BUG-046,BUG-047",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "智能注浆系统",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\groutMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-048,BUG-100",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "火灾监测预警",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\alarmMonitor\\warn\\fireWarn.vue",
+    "static": "已静态审查;关联 BUG-054",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "防灭火系统监测",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-108",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "色谱仪报表分析",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\bundleSpy\\bundleSpyTable\\index.vue",
+    "static": "已静态审查;关联 BUG-010,BUG-074,BUG-105",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "束管日报分析",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\bundle\\bundleMonitorTable\\index.vue",
+    "static": "已静态审查;关联 BUG-050,BUG-051,BUG-052,BUG-053,BUG-102",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "瓦斯监测",
+    "map_status": "mapped",
+    "paths": "src\\layouts\\default\\index.vue",
+    "static": "已静态审查;关联 BUG-009,BUG-063",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "瓦斯监测预警",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\alarmMonitor\\warn\\gasWarn.vue",
+    "static": "已静态审查;关联 BUG-009,BUG-063",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "抽采泵站管控",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\gasPumpMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-006,BUG-038,BUG-039",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "抽采达标评判",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\gas\\gasAssessment\\index.vue",
+    "static": "已静态审查;关联 BUG-040,BUG-041,BUG-042,BUG-043",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "抽采综合管控",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\gas\\gasHome\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "瓦斯抽采单元",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\gas\\gasAssessment\\components\\gasUnit.vue",
+    "static": "已静态审查;关联 BUG-043",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "瓦斯管网管控",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\gas\\gasPipeNet\\index.vue",
+    "static": "已静态审查;关联 BUG-064",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "抽采泵站监测",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\gas\\gasPumpMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-098",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "瓦斯日报历史详情",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\gas\\gasReportInspect\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "瓦斯钻孔监测",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\gas\\gasZk\\index.vue",
+    "static": "已静态审查;关联 BUG-065",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "瓦斯巡检设备",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "瓦斯人工巡检报表管理",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\gas\\gasReport\\index.vue",
+    "static": "已静态审查;关联 BUG-061,BUG-062,BUG-097",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "瓦斯人工巡检地点管理(NFC)",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\gas\\gasInspect\\index.vue",
+    "static": "已静态审查;关联 BUG-059,BUG-060,BUG-103",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "瓦斯人工巡检业务分析",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\gas\\gasAnalysis\\index.vue",
+    "static": "已静态审查;关联 BUG-058",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "瓦斯人工巡检地点管理",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\gas\\gasInspectNonfc\\index.vue",
+    "static": "已静态审查;关联 BUG-059,BUG-060,BUG-103",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "一张表",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\staticSheets\\gasSheet.vue",
+    "static": "已静态审查;关联 BUG-015,BUG-068,BUG-077",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "评判参数设置",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\gas\\gasPumpSetting\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "瓦斯日报分析",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\gas\\gasReportInspect\\index.vue",
+    "static": "已静态审查;关联 BUG-008",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "喷雾降尘监控",
+    "map_status": "weak",
+    "paths": "src\\views\\vent\\home\\configurable\\dust.vue",
+    "static": "已静态审查;关联 BUG-004,BUG-019,BUG-020",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "除尘风机",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\dedustMonitor\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "粉尘监测预警",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\alarmMonitor\\warn\\dustWarn.vue",
+    "static": "已静态审查;关联 BUG-071",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "粉尘参数一张表",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\staticSheets\\dustSheet.vue",
+    "static": "已静态审查;关联 BUG-015,BUG-068",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "防尘系统监测",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-081,BUG-106",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "粉尘报表分析",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\dust\\dustMonitorTable\\index.vue",
+    "static": "已静态审查;关联 BUG-069,BUG-070",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "综采面智能管控",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\workFaceMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-072,BUG-073",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "掘进面智能管控",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\tunFaceMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-044",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "防火门智能管控",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\home\\configurable\\firedoor\\fireDoorMonitor.vue",
+    "static": "已静态审查;关联 BUG-107",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "皮带巷管控系统",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\beltTunMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-067",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "机电硐室管控",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\chamberMonitor\\index.vue",
+    "static": "已静态审查;关联 BUG-049,BUG-101",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "均压与低氧管控",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\balancePressMonitor\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "机电硐室管控",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\home\\configurable\\electroChamber.vue",
+    "static": "已静态审查;关联 BUG-049,BUG-101",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "压风管控系统",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\nitrogen\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "温度压差监测",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\sensorMonitor\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "安全监控系统",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\safetyMonitor\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "设备预警历史",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\warningMonitor\\index-mine.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "设备操作历史",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\handlerMonitor\\index.vue",
+    "static": "已静态审查入口与关键逻辑;本次未发现新增高置信缺陷",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "视频墙",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\monitorManager\\camera\\index.vue",
+    "static": "已静态审查;关联 BUG-075",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "文件共享中心",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\performance\\fileDetail\\index.vue",
+    "static": "已静态审查;关联 BUG-045,BUG-099",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "报表管理",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\reportManager\\index.vue",
+    "static": "已静态审查;关联 BUG-061,BUG-062,BUG-097",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "安全监控分站管理",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\safetyList\\index.vue",
+    "static": "已静态审查;关联 BUG-032,BUG-093",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "安全监控分站管理详情",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\safetyList\\common\\detail.vue",
+    "static": "已静态审查;关联 BUG-032,BUG-093",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "用户日志管理",
+    "map_status": "mapped",
+    "paths": "src\\views\\monitor\\log\\index.vue",
+    "static": "已静态审查;关联 BUG-066,BUG-104",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  },
+  {
+    "module": "报表数据映射",
+    "map_status": "mapped",
+    "paths": "src\\views\\vent\\reportManager\\comment\\reportMap.vue",
+    "static": "已静态审查;关联 BUG-036,BUG-037,BUG-096",
+    "runtime": "阻塞-前端不可达:192.168.1.29:3100 ConnectionRefused;本机 vite 缺 public/index.html 崩溃;后端9999可达但登录需验证码/加密失败,菜单冒烟未执行"
+  }
+]

+ 1652 - 0
docs/_defects.json

@@ -0,0 +1,1652 @@
+[
+  {
+    "module": "235分站监测",
+    "file": "src/views/vent/safetyList/common/detail-235.vue:352-369,608-768,888,958-1013",
+    "title": "235 页面误用 130 协议 API",
+    "severity": "致命",
+    "type": "wrong_api",
+    "symptom": "235 分站参数下发、改名、删除等操作打到 130 接口,控制/配置失败或污染错误协议设备",
+    "root_cause": "从 detail-130 复制后未替换:set130StationData/update130DevName/remove130Device/remove130Substation;api 中亦无对应 235 写接口",
+    "repro": "在 235 监测详情编辑并下发任意参数,观察请求 URL 含 130 路径",
+    "fix_hint": "补齐并改用 235 专用读写/删除 API,移除 130 引用",
+    "id": "BUG-001",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "主风机管控",
+    "file": "src/views/vent/monitorManager/mainFanMonitor/index.vue:1609-1662",
+    "title": "倒机正转/反转共用同一 paramcode",
+    "severity": "致命",
+    "type": "control buttons sharing same paramcode",
+    "symptom": "changeSmoke1 场景下正启动与反启动都下发 CtrlFan1ToFan2 / CtrlFan2ToFan1,无法区分正反转倒机指令",
+    "root_cause": "同文件 changeSmoke 分支正确使用 CtrlFan1ToFan2Forward/Reverse 与 CtrlFan2ToFan1Forward/Reverse,但 changeSmoke1 分支正反转都调用无方向后缀的同一点位",
+    "repro": "风机处于正转/反转运行态触发一键倒机(modalType=changeSmoke1)→ 对比下发 paramcode",
+    "fix_hint": "与 changeSmoke 分支对齐,正转用 *Forward、反转用 *Reverse",
+    "id": "BUG-002",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "喷淋监控系统",
+    "file": "src/views/vent/monitorManager/sprayMonitor/index.vue:68-140",
+    "title": "多路控制按钮共用同一 paramcode/value",
+    "severity": "致命",
+    "type": "wrong_control_payload",
+    "symptom": "急停/补水阀/搅拌阀/组化泵/上料机/搅拌电机/上料自动/喷洒自动等按钮下发同一 paramcode,无法区分控制对象",
+    "root_cause": "operations 全部使用 key='run_auto_sign',且多数 value='0';BasicMonitoring.deviceControl 仅按 key/value 下发",
+    "repro": "打开喷淋监控 → 分别点击不同操作按钮 → 抓包/日志观察 paramcode 全为 run_auto_sign",
+    "fix_hint": "为每个操作配置真实设备参数码(如急停、补水阀、搅拌阀等各自 key),并核对 value 语义",
+    "id": "BUG-003",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "喷雾降尘监控",
+    "file": "src/router/helper/routeHelper.ts:21-28,98-113,122-146",
+    "title": "layouts/RouteView 组件文件缺失导致路由无法解析",
+    "severity": "致命",
+    "type": "missing_component",
+    "symptom": "菜单 component 配为 layouts/RouteView 的父级打开空白/路由失败",
+    "root_cause": "src/layouts 下无 RouteView.vue;LayoutMap 未注册;dynamicImport 只扫 views/components",
+    "repro": "后端菜单配置 component=layouts/RouteView 后进入喷雾降尘父路由",
+    "fix_hint": "新增 layouts/RouteView.vue 并加入 LayoutMap;菜单改为已存在布局",
+    "id": "BUG-004",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "多灾融合预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/common.api.ts:3-10,46",
+    "title": "bjlist 引用不存在的 Api.bjlist",
+    "severity": "致命",
+    "type": "undefined_api_ref",
+    "symptom": "瓦斯管道故障诊断轮询请求 url 为 undefined,列表/曲线无法加载",
+    "root_cause": "enum Api 无 bjlist 成员,但 bjlist() 仍使用 Api.bjlist",
+    "repro": "宝德矿打开瓦斯预警→管道故障诊断,抓包看请求 URL",
+    "fix_hint": "恢复 bjlist='/safety/ventanalyAlarmLog/list' 或改用 warningHistoryList 路径",
+    "id": "BUG-005",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "抽采泵站管控",
+    "file": "src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHome.vue:159-178,355-375,329-333",
+    "title": "就地/远程与检修控制 paramcode 对调且入参分支不匹配",
+    "severity": "致命",
+    "type": "inverted_control_mapping",
+    "symptom": "点就地/远程下发 jxmsqh;点检修下发 ykjdqh;传 0 时 handlerType 不赋值仍弹密码框",
+    "root_cause": "changeCtr 写死 jxmsqh、changeMode 写死 ykjdqh;模板传 0/1,函数判 1/2",
+    "repro": "抽采泵站管控点就地/远程/检修开闭→确认密码→抓包 paramcode",
+    "fix_hint": "changeCtr→ykjdqh,changeMode→jxmsqh;统一 0/1 分支",
+    "id": "BUG-006",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "测风装置管控",
+    "file": "src/views/vent/monitorManager/windrectMonitor/windrect.api.ts:59; src/views/vent/monitorManager/windrectMonitor/index.vue:769-771",
+    "title": "测风导出函数实际请求导入接口",
+    "severity": "致命",
+    "type": "export calling wrong import API",
+    "symptom": "一键测风结果导出不会调用 /safety/testwindDetail/exportXls,而是 POST /sys/user/importExcel,导出失败或误触发用户导入接口",
+    "root_cause": "exportXls 实现写成 defHttp.post({ url: Api.importExcel }),而 index.vue 的 exportExcel 直接调用该函数(未使用正确的 getExportUrl)",
+    "repro": "打开测风装置管控 → 触发 exportExcel(id) / 结果导出 → 抓包可见请求 importExcel",
+    "fix_hint": "改为 defHttp.post({ url: Api.exportXls, params }),并确认 method/responseType 与导出一致",
+    "id": "BUG-007",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "瓦斯日报分析",
+    "file": "src/views/vent/gas/gasReportInspect/index-time.vue:62-82",
+    "title": "空 records 时先访问 [0] 导致挂载崩溃",
+    "severity": "致命",
+    "type": "crash_on_mount",
+    "symptom": "无日报数据时 Cannot read properties of undefined,页面白屏",
+    "root_cause": "先读 tableList[0].id/content,后才判 length;JSON.parse 无空保护",
+    "repro": "瓦斯日报分析在 queryReportList 返回空 records 时进入页面",
+    "fix_hint": "先判空 return;JSON.parse 包 try/catch(index-type/index-lt 同改)",
+    "id": "BUG-008",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "瓦斯监测预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/common.api.ts:3-10,46",
+    "title": "bjlist 引用未定义的 Api.bjlist",
+    "severity": "致命",
+    "type": "api",
+    "symptom": "gasWarn 拉取报警历史时 url 为 undefined,请求失败",
+    "root_cause": "enum Api 未声明 bjlist,但 bjlist() 使用 Api.bjlist",
+    "repro": "进入瓦斯监测预警并触发 bjlist 调用 → 控制台/网络见 undefined URL",
+    "fix_hint": "在 Api 中补齐报警历史路径(并与后端对齐),或改为已有正确枚举字段",
+    "id": "BUG-009",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "色谱仪报表分析",
+    "file": "src/views/vent/bundleSpy/bundleSpyTable/index.vue:134-147",
+    "title": "色谱仪报表被硬编码 mock 覆盖真实 API",
+    "severity": "致命",
+    "type": "mock_override",
+    "symptom": "页面恒显示写死的 10月2日假报表,真实 queryReportData 不调用",
+    "root_cause": "getbundleSpyInfoList 被注释,改为本地写死 res 对象",
+    "repro": "打开色谱仪报表分析首页列表 → 数据固定为 10月2日样例,与后端无关",
+    "fix_hint": "恢复 await getbundleSpyInfoList({ type: 'bundleSpy', ...params }) 并删除硬编码",
+    "id": "BUG-010",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "预警联动控制",
+    "file": "src/views/vent/deviceManager/workingFace/workingFace.api.ts:18-21,79-90",
+    "title": "智能管控批量删除/导入导出误指向 /sys/user/*",
+    "severity": "致命",
+    "type": "wrong_endpoint",
+    "symptom": "批删打到用户批量删除接口,可能删错资源或失败;单删走 managesys 正确路径",
+    "root_cause": "deleteBatch/importExcel/exportXls 从用户模块拷贝未改",
+    "repro": "预警联动勾选多条批删,抓包确认 /sys/user/deleteBatch",
+    "fix_hint": "改为 /safety/ventanalyManageSystem/deleteBatch 及对应导入导出路径",
+    "id": "BUG-011",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "130分站监测",
+    "file": "src/views/vent/safetyList/common/detail-130.vue:865-892",
+    "title": "devInfoList 空指针(同 index/detail)",
+    "severity": "严重",
+    "type": "null_crash",
+    "symptom": "130 分站无子设备时列表处理抛错",
+    "root_cause": "el.children.forEach 未处理 null/undefined devInfoList",
+    "repro": "打开 130 详情页并选中无设备分站",
+    "fix_hint": "遍历前 `el.children = el.devInfoList || []`",
+    "id": "BUG-012",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "130分站监测",
+    "file": "src/views/vent/safetyList/safetyList.api.ts:23-25,72,76",
+    "title": "删除130分站/传感器误调158删除接口",
+    "severity": "严重",
+    "type": "api",
+    "symptom": "130 分站删除可能删错资源或后端拒绝,与 158 接口串用",
+    "root_cause": "remove130Substation/remove130Device 分别使用 Api.remove158Substation/Api.remove158Device,尽管 enum 已定义 remove130*",
+    "repro": "130 分站详情执行删除分站或传感器 → 查看请求 URL 是否含 remove158*",
+    "fix_hint": "改为 Api.remove130Substation / Api.remove130Device",
+    "id": "BUG-013",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "158分站监测",
+    "file": "src/views/vent/safetyList/common/detail.vue:977-1002,1013-1022",
+    "title": "devInfoList 为空时 forEach 崩溃",
+    "severity": "严重",
+    "type": "null_crash",
+    "symptom": "部分分站无子设备时详细信息表加载失败",
+    "root_cause": "el.children = el.devInfoList 后未判空直接 forEach(getStationList1/getStationListAll)",
+    "repro": "选择无测点的分站查看详细信息",
+    "fix_hint": "使用可选链或默认空数组后再遍历",
+    "id": "BUG-014",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "一张表",
+    "file": "src/views/vent/monitorManager/deviceMonitor/staticSheets/gasSheet.vue:1-151",
+    "title": "瓦斯一张表静态硬编码 HTML,无实时数据",
+    "severity": "严重",
+    "type": "data",
+    "symptom": "瓦斯参数表固定静态内容,非实时监测",
+    "root_cause": "硬编码表格,无数据请求",
+    "repro": "打开瓦斯一张表观察数据不更新",
+    "fix_hint": "接入瓦斯监测/抽采接口动态填充",
+    "id": "BUG-015",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "光纤测温监测",
+    "file": "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.vue:239-243",
+    "title": "光纤弹窗 Object.keys(readData) 空指针",
+    "severity": "严重",
+    "type": "null deref on mount/update",
+    "symptom": "设备无 readData 时打开弹窗直接 TypeError",
+    "root_cause": "未判断 item.readData 即 Object.keys(item.readData)",
+    "repro": "选中 readData 为空的光纤设备打开详情",
+    "fix_hint": "if (!item.readData) return; 后再解析分段温度",
+    "id": "BUG-016",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "光纤测温监测",
+    "file": "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.vue:49,169-188,232-257",
+    "title": "光纤详情风险等级与预警历史写死假数据",
+    "severity": "严重",
+    "type": "mock hardcode replacing real API",
+    "symptom": "风险等级恒为“低风险”,预警历史永远是测点6/318℃等假记录,不随真实报警变化",
+    "root_cause": "模板写死低风险;warningConfig.data 硬编码数组,watch 未用真实告警填充",
+    "repro": "打开光纤测温弹窗 → 对比真实告警与滚动板内容",
+    "fix_hint": "绑定设备真实风险字段与告警历史接口/数据",
+    "id": "BUG-017",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "喷淋监控系统",
+    "file": "src/components/vent/BasicMonitoring.vue:19,193-201",
+    "title": "操作列表 :key 冲突 + 空场景崩溃",
+    "severity": "严重",
+    "type": "vue_key_collision/null_crash",
+    "symptom": "喷淋多个按钮共用 key 导致 DOM 复用异常;无场景数据时页面直接抛错白屏",
+    "root_cause": "v-for 使用 item.key 作唯一键但喷淋键重复;getSysDataSource 无空数组守卫直接取 options[0].id",
+    "repro": "1) 打开喷淋页观察按钮交互状态错乱;2) 后端返回空 records 时进入页面立即报错",
+    "fix_hint": "key 使用 label 或唯一 id;取首项前判断 records.length",
+    "id": "BUG-018",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "喷雾降尘监控",
+    "file": "src/views/vent/home/configurable/dust.vue:87-93",
+    "title": "服务端配置被测试数据覆盖且只用 Mock",
+    "severity": "严重",
+    "type": "hardcoded_override",
+    "symptom": "粉尘首页始终展示 testConfigDust + BDDustMock,真实监测数据不刷新",
+    "root_cause": "fetchConfigs('dust') 后强制 configs=testConfigDust,并用 Promise.resolve(BDDustMock) 代替 getHomeData",
+    "repro": "任意环境打开粉尘可配置首页,对比接口与页面数据源",
+    "fix_hint": "生产移除测试覆盖,恢复 getHomeData 轮询",
+    "id": "BUG-019",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "喷雾降尘监控",
+    "file": "src/views/vent/home/configurable/dustControl.vue:53-54",
+    "title": "错误 pageType 拉取后再覆盖测试配置",
+    "severity": "严重",
+    "type": "wrong_param/hardcoded_override",
+    "symptom": "降尘管控页配置与粉尘业务不匹配,最终仍被本地测试配置覆盖",
+    "root_cause": "fetchConfigs('mine_fire') 后 configs.value=testConfigDustControl",
+    "repro": "打开 dustControl 页检查 configuration 请求 pageType 与最终 configs",
+    "fix_hint": "使用正确粉尘 pageType,并按环境决定是否使用测试配置",
+    "id": "BUG-020",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "多灾融合预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/warn/gasWarn.vue:311",
+    "title": "gasGlFaultDia 无防护 JSON.parse 可抛错",
+    "severity": "严重",
+    "type": "crash_on_mount",
+    "symptom": "缺/非法 gasGlFaultDia 时脚本异常,故障诊断页中断",
+    "root_cause": "JSON.parse(data.readData.gasGlFaultDia) 无空值与 try/catch",
+    "repro": "故障诊断选中无 gasGlFaultDia 的设备",
+    "fix_hint": "JSON.parse(x||'{}') 并 catch 给默认坐标",
+    "id": "BUG-021",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "多灾融合预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/warn/ventilateWarn.vue:239-250",
+    "title": "从巷道阻力分析切回通风监测后轮询不恢复",
+    "severity": "严重",
+    "type": "timer_not_restarted",
+    "symptom": "切回通风监测后顶部/测点停止自动刷新",
+    "root_cause": "btnClick(0) 只 clearNetTimer+getMenuList,未 getMonitor(true)",
+    "repro": "通风监测→巷道阻力→再点通风监测,等待>3s 看是否更新",
+    "fix_hint": "ind===0 时调用 getMonitor(true)",
+    "id": "BUG-022",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "多灾融合预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/warn/fireWarn.vue:74-83,148-157,173-185,215-217",
+    "title": "匿名 setTimeout 未清理且 getMonitor 不先 clear",
+    "severity": "严重",
+    "type": "timer_leak",
+    "symptom": "快速切换/离开后仍请求 sysTypeWarn;可能双轮询",
+    "root_cause": "cardClick/reset 的 setTimeout 无句柄;getMonitor 赋值前不清旧 timer",
+    "repro": "火灾预警连点内因/外因后返回首页,抓包仍见请求",
+    "fix_hint": "保存 delay timer 并在 unmount clear;getMonitor 开头 clearPollTimer",
+    "id": "BUG-023",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "多灾融合预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/common/external-fourc.vue:147-153",
+    "title": "四参外因温度误绑 fireval",
+    "severity": "严重",
+    "type": "wrong_field_binding",
+    "symptom": "温度监测显示错误/空",
+    "root_cause": "与 threec 相同拷贝错误 val: el.readData.fireval",
+    "repro": "打开外因四参温度列表",
+    "fix_hint": "改为 temperature",
+    "id": "BUG-024",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "多灾融合预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/warn/gasWarn.vue:346-365",
+    "title": "宝德矿第三 Tab 映射到瓦斯参数而非预警指标",
+    "severity": "严重",
+    "type": "wrong_field_binding",
+    "symptom": "菜单第三项「预警指标」点击却渲染 gasParamter(wscs)",
+    "root_cause": "getMonitorComponent(宝德)=[预警监测,故障诊断,预警指标];btnClick ind===2 固定 isShow='wscs'",
+    "repro": "sysOrgCode=sdmtjtbdmk 打开瓦斯预警点第三个类型按钮",
+    "fix_hint": "宝德 ind===2 → yjzb;布尔台 ind===2 保持 wscs",
+    "id": "BUG-025",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "多灾融合预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/warn/gasWarn.vue:368-377",
+    "title": "宝德预警监测卡片误走故障诊断轮询",
+    "severity": "严重",
+    "type": "wrong_comparison",
+    "symptom": "预警监测点卡片会走 bjlist,菜单被故障设备覆盖",
+    "root_cause": "cardClick 仅按 isflag=='1' 分支,未看 isShow/activeIndex",
+    "repro": "宝德→预警监测→切换工作面卡片,观察是否请求 bjlist",
+    "fix_hint": "yjjc→getMonitor;gzzd→getMonitor1",
+    "id": "BUG-026",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "多灾融合预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/alarmHistoryTable.vue:166-167",
+    "title": "未传 deviceType 时 strtype 变成 \"undefined*\"",
+    "severity": "严重",
+    "type": "wrong_field_binding",
+    "symptom": "设备预警报警历史过滤错误,列表空或错数据",
+    "root_cause": "beforeFetch: props.deviceType + '*';deviceWarn.vue:43 未传 deviceType",
+    "repro": "设备监测预警→报警历史,检查请求 strtype",
+    "fix_hint": "传入正确 deviceType,或空时不拼 strtype",
+    "id": "BUG-027",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "多灾融合预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/common/external-threec.vue:118-124",
+    "title": "温度传感器列表误绑 fireval",
+    "severity": "严重",
+    "type": "wrong_field_binding",
+    "symptom": "外因火灾温度卡片显示火焰字段/空值",
+    "root_cause": "temperature.map 使用 readData.fireval;mainWell 正确用 temperature",
+    "repro": "外因火灾 temperature 有值、fireval 空时看温度卡",
+    "fix_hint": "改为 el.readData.temperature(fourc:147-153 同步)",
+    "id": "BUG-028",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "多灾融合预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/warn/gasWarn.vue:193",
+    "title": "甲烷显示运算符优先级错误",
+    "severity": "严重",
+    "type": "wrong_operator_precedence",
+    "symptom": "gasC 为空时显示 \"undefined%\" 而非 '--'",
+    "root_cause": "el.readData.gasC + '%' || '--' 中字符串恒 truthy",
+    "repro": "测点 gasC 缺失时看左侧甲烷值",
+    "fix_hint": "(gasC!=null&&gasC!=='') ? gasC+'%' : '--'",
+    "id": "BUG-029",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "多灾融合预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/warn/dustWarn.vue:131-143,166-176",
+    "title": "粉尘预测曲线每轮 push 不清理导致无限膨胀",
+    "severity": "严重",
+    "type": "timer_state_leak",
+    "symptom": "停留越久曲线点越多、内存升高",
+    "root_cause": "fillChartByIndex 每秒 echartYc.push;仅 topAreaClick 会清空",
+    "repro": "粉尘预警停留数分钟观察 echartYc 长度",
+    "fix_hint": "每次 fill 前重置 echartYc,或替换最新点",
+    "id": "BUG-030",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "多灾融合预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/warn/ventilateWarn.vue:10",
+    "title": "返回按钮权限取反",
+    "severity": "严重",
+    "type": "inverted_permission",
+    "symptom": "有 gasWarn:return 权限看不到返回;无权限反而显示",
+    "root_cause": "v-if=\"!hasPermission(...)\";对照 deviceMonitorWarn 为正逻辑",
+    "repro": "分配 gasWarn:return 后对比返回按钮显隐",
+    "fix_hint": "改为 hasPermission;fire/dust/ventilate/device/waterLevel 同步",
+    "id": "BUG-031",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "安全监控分站管理",
+    "file": "src/views/vent/safetyList/index.vue:234-257,408-417",
+    "title": "卸载引用未定义 timer + children 空指针 + 分页 total 错误",
+    "severity": "严重",
+    "type": "reference_error/null_crash",
+    "symptom": "离开页面报 timer is not defined;devInfoList 为空时列表渲染崩溃;分页总数异常",
+    "root_cause": "timer 声明被注释但 onUnmounted 仍引用;el.children=el.devInfoList 后直接 forEach;res.total 在数组结果上常为 undefined",
+    "repro": "进入页再离开;返回含 null devInfoList 的分站;观察 pagination.total",
+    "fix_hint": "删除或恢复 timer;`(el.devInfoList||[]).forEach`;按接口实际结构取 records/total",
+    "id": "BUG-032",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "密闭监测系统",
+    "file": "src/views/vent/monitorManager/obfurageMonitor/index.vue:89-138",
+    "title": "空数据崩溃且切换系统不刷新关联设备",
+    "severity": "严重",
+    "type": "null_crash/stale_state",
+    "symptom": "无密闭系统/无关联设备时崩溃;切换密闭点后历史页设备 Tab 仍是旧设备",
+    "root_cause": "直接访问 msgTxt[0]、deviceArr[0];getSelectRow 只改 optionValue,未调用 getDeviceList",
+    "repro": "空数据进页;有多套密闭→切换后打开历史监测看设备按钮未更新",
+    "fix_hint": "空数组守卫;getSelectRow 内调用 getDeviceList()",
+    "id": "BUG-033",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "局部风机管控",
+    "file": "src/views/vent/monitorManager/fanLocalMonitor/index.vue:782-791",
+    "title": "风筒直径控制按钮重复且同 key",
+    "severity": "严重",
+    "type": "control buttons sharing same paramcode",
+    "symptom": "控制区出现两个相同“风筒直径”按钮,key 均为 diameter,行为完全重复",
+    "root_cause": "leftBtnArr 中 diameter 配置项被复制粘贴两次",
+    "repro": "开启 fanLocal:diameter → 顶部控制按钮可见重复项",
+    "fix_hint": "删除重复配置;若本意为主/备直径,需不同 key 与 paramcode",
+    "id": "BUG-034",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "工作面来压预警",
+    "file": "src/views/vent/monitorManager/betKy/index.vue:112-146,148-154,258-262",
+    "title": "矿压监测轮询逻辑错误且特定矿用假数据",
+    "severity": "严重",
+    "type": "mock hardcode + setInterval/setTimeout poll without proper clear",
+    "symptom": "A02A02 矿强制使用 ceshi.json;轮询前把 timer 置 null 未 clearTimeout,chartData 每次 push 不清理导致曲线数据膨胀错乱;卸载用 clearInterval 清 setTimeout",
+    "root_cause": "chartData 每轮 push 不清空;timer=setTimeout 却 clearInterval;重启只置 null 不 clear",
+    "repro": "返回含 readData=null 的 Minepressure 列表",
+    "fix_hint": "每轮 chartData=[];统一 clearTimeout 并先 clear 再置空",
+    "id": "BUG-035",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "报表数据映射",
+    "file": "src/views/vent/reportManager/comment/reportMap.vue:82-119,135-145",
+    "title": "reactive 表单被整体重赋值 + 部门树空 children 崩溃",
+    "severity": "严重",
+    "type": "reactivity_break/null_crash",
+    "symptom": "新增/编辑后表单状态异常;部门节点无 children 时查询部门树直接报错",
+    "root_cause": "handleAdd/handleEdit 执行 formState = { ... },替换了 reactive 代理",
+    "repro": "编辑一条映射再点新增观察表单残留;部门树缺 children 的节点触发加载",
+    "fix_hint": "用 Object.assign(formState, ...) 或重置字段;forEach 前守卫 children",
+    "id": "BUG-036",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "报表数据映射",
+    "file": "src/views/vent/reportManager/comment/report-modal.vue:135-151",
+    "title": "编辑时 JSON.parse(mineCells) 在非字符串时抛错",
+    "severity": "严重",
+    "type": "type_error",
+    "symptom": "编辑已有映射弹窗打开失败或控制台 TypeError",
+    "root_cause": "watch 中无条件 JSON.parse(formStates.mineCells);接口若已反序列化为数组则解析失败;且 Object.assign 不会清除旧 id",
+    "repro": "打开编辑,当 record.mineCells 为数组或空时复现",
+    "fix_hint": "typeof===string 再 parse,数组则直接使用;新增时显式清空 id",
+    "id": "BUG-037",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "抽采泵站管控",
+    "file": "src/views/vent/gas/gasPumpMonitor/components/monitorChart.vue:50-65",
+    "title": "setInterval 用 clearTimeout 清理导致泄漏",
+    "severity": "严重",
+    "type": "timer_not_cleared",
+    "symptom": "离开监测图表后仍每 2s 推数据",
+    "root_cause": "setInterval + onUnmounted clearTimeout",
+    "repro": "进入监测图表后切走,观察回调是否继续",
+    "fix_hint": "改为 clearInterval(timer)",
+    "id": "BUG-038",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "抽采泵站管控",
+    "file": "src/views/vent/gas/gasPumpMonitor/index.vue:57-68",
+    "title": "井下泵模型切换后被无条件覆盖为地上泵",
+    "severity": "严重",
+    "type": "copy_paste_bug",
+    "symptom": "选中 pump_under* 仍加载地上 gasPump 三维",
+    "root_cause": "if/else 设类型后末尾又 setModelType('gasPump')",
+    "repro": "抽采泵站监测切到井下泵站设备",
+    "fix_hint": "删除末尾无条件 setModelType('gasPump')",
+    "id": "BUG-039",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "抽采达标评判",
+    "file": "src/views/vent/gas/gasAssessment/components/gasUnit.vue:27,55,73-75",
+    "title": "单元混量/纯量图表写死 mock",
+    "severity": "严重",
+    "type": "mock_override",
+    "symptom": "图表恒为本地假数据,真实监测不驱动",
+    "root_cause": "CustomChart 绑定 mockGasUnitHlData/mockGasUnitClData",
+    "repro": "打开抽采单元对比接口与图表数据源",
+    "fix_hint": "用接口历史数据替换 mock",
+    "id": "BUG-040",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "抽采达标评判",
+    "file": "src/views/vent/gas/gasAssessment/components/gasControl.vue:32-40,88-112",
+    "title": "调控页 getMonitor 空转且阀门无绑定",
+    "severity": "严重",
+    "type": "incomplete",
+    "symptom": "测点值始终为空;阀门开度输入/控制按钮无事件与接口",
+    "root_cause": "getMonitor 仅 setTimeout 自调用不请求数据;阀门区无 @click/API",
+    "repro": "打开抽采达标评判-调控相关页 → 测点 '-'、按钮无响应",
+    "fix_hint": "接入监测 list API 填充 dataSource;为阀门控制绑定下发接口",
+    "id": "BUG-041",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "抽采达标评判",
+    "file": "src/views/vent/gas/gasAssessment/index.vue:43",
+    "title": "进入抽采单元页未传入 unitId",
+    "severity": "严重",
+    "type": "wrong_field_binding",
+    "symptom": "侧栏 list({ids}) 为空,实时数据无法对应选中单元",
+    "root_cause": "<gasUnit/> 未绑定 :unitId=\"activeUnitId\"",
+    "repro": "达标评判点选单元后看 list 请求 ids",
+    "fix_hint": "<gasUnit :unitId=\"activeUnitId\" />",
+    "id": "BUG-042",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "抽采达标评判/瓦斯抽采单元",
+    "file": "src/views/vent/gas/gasAssessment/components/{gasEcharts,gasUnit,gasPipingEcharts}.vue:gasEcharts 14-47; gasUnit 27,55; gasPipingEcharts 13-99",
+    "title": "图表永久绑定 mock* 数据",
+    "severity": "严重",
+    "type": "data",
+    "symptom": "抽采单元/管网相关图表始终显示 gasAssessment.data 中的 mock 曲线/饼图,即使列表接口已取到真实测点",
+    "root_cause": "模板 :chart-data/:dataSource 直接绑定 mockGasUnitData/mockPieData/mockGasPiping* 等,未接入接口结果",
+    "repro": "进入抽采达标评判相关子页 → 图表不随实时监测变化",
+    "fix_hint": "用接口返回历史/累计数据替换 mock 绑定;gasUnit 已有 list(),应生成 chart-data",
+    "id": "BUG-043",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "掘进面智能管控",
+    "file": "src/views/vent/monitorManager/tunFaceMonitor/components/tunFaceHome.vue:254,262-271",
+    "title": "掘进面首页无条件解包 warnInfo/readData",
+    "severity": "严重",
+    "type": "null deref on mount",
+    "symptom": "缺 dustS/fireS/gasS/synthesizeS/ventS、缺 surface_history、或缺 location.readData 时页面直接崩溃",
+    "root_cause": "无守卫直接访问 res['warnInfo'][x]['devices']、res['surface_history'][0]、data['readData']['flen']",
+    "repro": "接口缺任一 warn 分组或 location.readData → 打开掘进面主页",
+    "fix_hint": "逐层可选链;缺省赋空数组/空对象",
+    "id": "BUG-044",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "文件共享中心",
+    "file": "src/views/vent/performance/fileDetail/index.vue:292-314,367-377,387",
+    "title": "空树上标崩溃 + 可空文件上传 + nodeParam 丢失响应式",
+    "severity": "严重",
+    "type": "null_crash/invalid_upload",
+    "symptom": "无目录时白屏;未选文件仍可点确定上传;树节点切换后表格参数可能异常",
+    "root_cause": "listArr[0] 无守卫;handleOk 不校验 fileList;nodeParam=node 重赋放弃 reactive",
+    "repro": "空目录数据进页;打开上传不选文件点确定;切换目录观察列表 parentId",
+    "fix_hint": "空树提示;上传前校验 fileList.length;Object.assign(nodeParam, node)",
+    "id": "BUG-045",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "智能注氮系统",
+    "file": "src/views/vent/monitorManager/compressor/components/nitrogenHome_ln.vue:341-349",
+    "title": "监测数据忽略选中系统ID",
+    "severity": "严重",
+    "type": "wrong_data_binding",
+    "symptom": "切换注氮系统后仍展示 msgTxt[0] 数据,监控内容与下拉选择不一致",
+    "root_cause": "getDataSource(systemID) 入参未用于请求,固定取 res.msgTxt[0].datalist",
+    "repro": "配置≥2套注氮系统→切换下拉→数据不随 systemId 变化",
+    "fix_hint": "按 systemID/deviceId 过滤或带参请求对应系统数据",
+    "id": "BUG-046",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "智能注氮系统",
+    "file": "src/views/vent/monitorManager/compressor/index.vue:93-126",
+    "title": "空列表崩溃且 modalType 未随系统类型切换",
+    "severity": "严重",
+    "type": "null_crash/stale_config",
+    "symptom": "无注氮系统时白屏;依赖 modalType 的矿端组件始终按 nitrogenUnderground 建模",
+    "root_cause": "options.value[0]['id'] 无空判断;changeModalType 中 modalType 赋值被注释,仅改了 moduleTypeName",
+    "repro": "空数据进页崩溃;潞宁等使用 modalType 的矿切换空压机/制氮机后模型类型不变",
+    "fix_hint": "空 records 提前 return;恢复按 strsystype 设置 modalType",
+    "id": "BUG-047",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "智能注浆系统",
+    "file": "src/views/vent/monitorManager/groutMonitor/index.vue:110-119,67,86",
+    "title": "空数据崩溃且 currentDeviceType 永不赋值",
+    "severity": "严重",
+    "type": "null_crash/dead_state",
+    "symptom": "无注浆系统时白屏;传给 groutHome 的 device-type 恒为空字符串",
+    "root_cause": "options.value[0]['id'] 无守卫;deviceChange 只写 deviceType,从未写入 currentDeviceType",
+    "repro": "空列表进页崩溃;有数据时检查 groutHome props.deviceType 为空",
+    "fix_hint": "空 records 保护;deviceChange/getSelectRow 同步 currentDeviceType",
+    "id": "BUG-048",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "机电硐室管控",
+    "file": "src/views/vent/home/configurable/electroChamber.vue:86-99,29-50",
+    "title": "拉取配置后强制覆盖为本地测试配置且历史无设备ID",
+    "severity": "严重",
+    "type": "hardcoded_override/missing_param",
+    "symptom": "线上始终使用 testConfigElectro;切历史 Tab 时 deviceId 常为空导致查询无效",
+    "root_cause": "fetchConfigs 成功后 configs.value=testConfigElectro;optionValue 仅来自路由 query,无设备选择赋值",
+    "repro": "非 A02A02 环境打开页面看配置是否被测试数据覆盖;无 query.id 切历史记录",
+    "fix_hint": "生产去掉测试覆盖;补充硐室/系统选择并写入 optionValue",
+    "id": "BUG-049",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "束管日报分析",
+    "file": "src/views/vent/bundleSpy/bundleSpyTable/index.vue; vent/bundle/bundleMonitorTable/index.vue:bundleSpy:108-109; bundle:101-102",
+    "title": "保德 sysOrgCode 大小写错误导致专用列永不命中",
+    "severity": "严重",
+    "type": "copy_paste_orgcode",
+    "symptom": "保德落到 default 列配置,专用字段不显示",
+    "root_cause": "写 case 'sdmtjtBdmk';全库其余为 'sdmtjtbdmk'",
+    "repro": "sysOrgCode=sdmtjtbdmk 打开色谱仪/束管日报",
+    "fix_hint": "统一改为 sdmtjtbdmk",
+    "id": "BUG-050",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "束管日报分析",
+    "file": "src/views/vent/bundle/bundleMonitorTable/bundle-table.data.ts:595-610",
+    "title": "保德束管 NO₂ 最小值 dataIndex 写成 no22_max",
+    "severity": "严重",
+    "type": "wrong_column_dataIndex",
+    "symptom": "NO₂最小值空白或错值",
+    "root_cause": "最小值 dataIndex:'no22_max',key/dataIndex 交叉贴错",
+    "repro": "保德束管日报对比 no2_min/no2_max 与三列",
+    "fix_hint": "最大值 no2_max;最小值 no2_min",
+    "id": "BUG-051",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "束管日报分析",
+    "file": "src/views/vent/bundle/bundleMonitorTable/bundle-table.data.ts:1465-1472,1848-1855",
+    "title": "寸草塔一矿/哈拉沟 O₂最大值列绑定 o2_min",
+    "severity": "严重",
+    "type": "wrong_column_dataIndex",
+    "symptom": "标题最大值实际显示最小值",
+    "root_cause": "title:最大值 却 dataIndex:'o2_min'",
+    "repro": "对应矿打开束管日报对比 o2_max/o2_min",
+    "fix_hint": "改为 dataIndex:'o2_max'",
+    "id": "BUG-052",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "束管日报分析",
+    "file": "src/views/vent/bundle/bundleMonitorTable/index.vue:103-104,453",
+    "title": "补连塔图表分支 orgCode 写成 sdmtjtBltmk",
+    "severity": "严重",
+    "type": "copy_paste_orgcode",
+    "symptom": "表格用 Bltcolumns 正确,柱状图落入 default",
+    "root_cause": "computedColumns 用 sdmtjtbltmk,getSeriesConfig 用 sdmtjtBltmk",
+    "repro": "sysOrgCode=sdmtjtbltmk 对比表头与图例",
+    "fix_hint": "图表 case 改为 sdmtjtbltmk",
+    "id": "BUG-053",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "火灾监测预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/warn/fireWarn.vue:4",
+    "title": "返回按钮权限判断取反(!hasPermission('fireWarn:return'))",
+    "severity": "严重",
+    "type": "权限逻辑错误",
+    "symptom": "有 *:return 权限的用户看不到返回;无权限用户反而可见(与 deviceMonitorWarn 正确写法相反)",
+    "root_cause": "使用 v-if=\"!hasPermission('xxx:return')\";对照 deviceMonitorWarn 应为正向 hasPermission",
+    "repro": "分别用有/无 gasWarn:return 等权限账号打开预警页,观察返回按钮显隐",
+    "fix_hint": "去掉 !,改为 v-if=\"hasPermission('...:return')\"",
+    "id": "BUG-054",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "灾变应急控制",
+    "file": "src/views/vent/monitorManager/deviceMonitor/components/emergency/index.vue:196-226",
+    "title": "工作面详情多项共用同一数据 code",
+    "severity": "严重",
+    "type": "wrong field mapping / logic bug",
+    "symptom": "工作面编号/采区/煤层都显示 dTotalArea;长度/走向/采高都显示 totallength",
+    "root_cause": "analysisDetailDataList 多项复用相同 code,未映射独立字段",
+    "repro": "打开灾变应急分析详情区 → 观察多行数值是否雷同",
+    "fix_hint": "为每项配置正确业务字段 code,并在 pageData 中补齐对应值",
+    "id": "BUG-055",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "灾变应急控制",
+    "file": "src/views/vent/monitorManager/deviceMonitor/components/emergency/index.vue:273-275",
+    "title": "应急解算局部阻力同样误绑 dHNature",
+    "severity": "严重",
+    "type": "wrong field mapping / logic bug",
+    "symptom": "灾变应急巷道阻力表局部阻力错误",
+    "root_cause": "与 network 相同的复制错误:dHLocal: item['dHNature']",
+    "repro": "进入灾变应急页加载解算结果 → 查看巷道阻力列",
+    "fix_hint": "改为 item['dHLocal']",
+    "id": "BUG-056",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "灾变应急控制",
+    "file": "src/views/vent/monitorManager/deviceMonitor/components/emergency/index.vue:314-321",
+    "title": "应急页传感器 readData 空指针",
+    "severity": "严重",
+    "type": "null deref on mount/update",
+    "symptom": "同网络页,缺 readData 时应急页崩溃",
+    "root_cause": "item['readData']['va'] 无空判断",
+    "repro": "推送缺 readData 的 monitors → 打开应急页",
+    "fix_hint": "对 readData 做空对象兜底",
+    "id": "BUG-057",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "瓦斯人工巡检业务分析",
+    "file": "src/views/vent/gas/gasAnalysis/index.vue:177-182,216-219",
+    "title": "列表成功后强制 pagination.current=1 导致分页失效",
+    "severity": "严重",
+    "type": "pagination_logic_bug",
+    "symptom": "翻到第 2 页后被打回第 1 页",
+    "root_cause": "gasReportLogLists 在有 records 时无条件 current=1",
+    "repro": "业务分析多页数据点第 2 页",
+    "fix_hint": "删除该赋值;仅查询/重置时置 1",
+    "id": "BUG-058",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "瓦斯人工巡检地点管理",
+    "file": "src/views/vent/gas/gasInspectNonfc/components/inspectEdit.vue:12-14,79-81,71-76",
+    "title": "区队下拉缺 value 且整对象重赋值打断响应式",
+    "severity": "严重",
+    "type": "nfc_nonfc_copy_paste",
+    "symptom": "提交可能把区队名当 id;二次编辑表单不刷新",
+    "root_cause": "option 无 :value;formStateEdit=Object.assign 重赋值丢失 reactive 绑定",
+    "repro": "Nonfc 编辑改区队提交看 payload;连续编辑两条",
+    "fix_hint": "补 :value=\"item.value\";Object.assign(formStateEdit, newV) 原地更新",
+    "id": "BUG-059",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "瓦斯人工巡检地点管理(NFC)",
+    "file": "src/views/vent/gas/gasInspect/index.vue:66-75",
+    "title": "早/中/晚是否巡检真假分支都渲染「是」",
+    "severity": "严重",
+    "type": "wrong_field_binding",
+    "symptom": "未巡检也显示「是」,状态失真",
+    "root_cause": "text=='1' 与 v-else 内均写「是」",
+    "repro": "NFC 地点管理查看未巡检班次列",
+    "fix_hint": "else 显示「否」;对齐 Nonfc 字段约定",
+    "id": "BUG-060",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "瓦斯人工巡检报表管理",
+    "file": "src/views/vent/gas/gasReport/index.vue:357-440",
+    "title": "审核/导出异常校验 else-if 导致任一气体合格即放行",
+    "severity": "严重",
+    "type": "inverted_validation_logic",
+    "symptom": "O2 超限但 CO 合格仍可审核通过;多行以最后一行结果为准",
+    "root_cause": "getTs 对 O2/CO/CO2/T/CH4 用 else-if;forEach return 不能结束函数",
+    "repro": "仅 O2 异常其余合格→点审核通过",
+    "fix_hint": "全部字段均在阈内才放行;任异常即 true",
+    "id": "BUG-061",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "瓦斯人工巡检报表管理",
+    "file": "src/views/vent/gas/gasReport/index.vue:271-274",
+    "title": "巡检图片 Blob 二次封装错误",
+    "severity": "严重",
+    "type": "wrong_blob_usage",
+    "symptom": "查看班次图片失败或 Failed to construct 'Blob'",
+    "root_cause": "gasServerImg 已是 blob,又 new Blob(res,...);构造需 Array",
+    "repro": "日报管理点夜/早/中班「查看」",
+    "fix_hint": "URL.createObjectURL(res) 或 new Blob([res],...)",
+    "id": "BUG-062",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "瓦斯监测预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/warn/gasWarn.vue:5",
+    "title": "返回按钮权限判断取反(!hasPermission('gasWarn:return'))",
+    "severity": "严重",
+    "type": "权限逻辑错误",
+    "symptom": "有 *:return 权限的用户看不到返回;无权限用户反而可见(与 deviceMonitorWarn 正确写法相反)",
+    "root_cause": "使用 v-if=\"!hasPermission('xxx:return')\";对照 deviceMonitorWarn 应为正向 hasPermission",
+    "repro": "分别用有/无 gasWarn:return 等权限账号打开预警页,观察返回按钮显隐",
+    "fix_hint": "去掉 !,改为 v-if=\"hasPermission('...:return')\"",
+    "id": "BUG-063",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "瓦斯管网管控",
+    "file": "src/views/vent/gas/gasPipeNet/index.vue:142-147",
+    "title": "管网阀门控制提交未调控制接口",
+    "severity": "严重",
+    "type": "undefined_api_call",
+    "symptom": "点确定只 toast 表单 JSON,阀门无动作",
+    "root_cause": "submitHandler 仅 message.success,无 deviceControl",
+    "repro": "管网管控→阀门控制→提交",
+    "fix_hint": "接入泵站同款控制 API",
+    "id": "BUG-064",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "瓦斯钻孔监测",
+    "file": "src/views/vent/gas/gasZk/index.vue:61-79",
+    "title": "setInterval 回调内再 startPolling 造成重叠轮询",
+    "severity": "严重",
+    "type": "timer_logic_bug",
+    "symptom": "慢网时 /drill/get/status 并发堆积",
+    "root_cause": "interval 不等待完成可再次触发;成功后又 stop+new interval",
+    "repro": "打开钻孔监测并限速网络观察并发",
+    "fix_hint": "改为 await 后 setTimeout 串行轮询",
+    "id": "BUG-065",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "用户日志管理",
+    "file": "src/views/monitor/log/index.vue:2,56-84",
+    "title": "v-modal 拼写错误 + 登录统计破坏 searchInfo 响应式",
+    "severity": "严重",
+    "type": "typo/reactivity_break",
+    "symptom": "Tab 无法双向绑定;切到“登录统计”后查询条件失效、列表参数丢失 logType",
+    "root_cause": "模板写 v-modal 而非 v-model;case '4' 执行 searchInfo={} 丢掉 reactive 代理",
+    "repro": "打开日志页切换 Tab;切到登录统计后再查询",
+    "fix_hint": "改为 v-model;用 Object.keys 清空或重置字段,禁止重赋 searchInfo",
+    "id": "BUG-066",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "皮带巷管控系统",
+    "file": "src/views/vent/monitorManager/beltTunMonitor/components/beltTunHome.vue:260-268",
+    "title": "皮带巷首页 warnInfo/sysInfo 无空保护",
+    "severity": "严重",
+    "type": "null deref on mount",
+    "symptom": "sysInfo 或某类 warn 分组缺失时页面加载失败",
+    "root_cause": "直接 Object.assign(res['sysInfo'], res['sysInfo']['readData']) 与 warnInfo.*.devices",
+    "repro": "返回不完整 beltTun 监测包 → 打开皮带巷主页",
+    "fix_hint": "增加空判断与默认值",
+    "id": "BUG-067",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "粉尘参数一张表",
+    "file": "src/views/vent/monitorManager/deviceMonitor/staticSheets/dustSheet.vue:1-103",
+    "title": "粉尘参数一张表静态硬编码 HTML,无实时数据",
+    "severity": "严重",
+    "type": "data",
+    "symptom": "粉尘参数表固定静态内容,非实时监测",
+    "root_cause": "硬编码表格,无数据请求",
+    "repro": "打开粉尘参数一张表观察数据不更新",
+    "fix_hint": "接入粉尘监测接口动态填充",
+    "id": "BUG-068",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "粉尘报表分析",
+    "file": "src/views/vent/dust/dustMonitorTable/index.vue:317-320,510-513",
+    "title": "保德当日粉尘总体最大值误用 zcjqpj/hcjqpj",
+    "severity": "严重",
+    "type": "wrong_field_binding",
+    "symptom": "当日分析最大值恒 -Infinity 或字段映射失败",
+    "root_cause": "BdMaxValues 用 qc/hc;overallBdMaxValues 误用布尔台字段",
+    "repro": "保德粉尘报表切「当日粉尘情况分析」",
+    "fix_hint": "overallBdMaxValues 改为 qc/hc",
+    "id": "BUG-069",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "粉尘报表分析",
+    "file": "src/views/vent/dust/dustMonitorTable/index.vue:269-270,429-436,472-475",
+    "title": "哈拉沟分组键 ccdd 被最大值循环改写成 jcdd",
+    "severity": "严重",
+    "type": "logic_state",
+    "symptom": "工作场所下拉变成测点名,过滤错乱",
+    "root_cause": "分组 gzcs=ccdd,HlgMaxValues 更新时 gzcs=item.jcdd 覆盖",
+    "repro": "哈拉沟粉尘报表看工作场所下拉与过滤",
+    "fix_hint": "更新最大值时不要用 jcdd 覆盖 gzcs",
+    "id": "BUG-070",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "粉尘监测预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/warn/dustWarn.vue:6",
+    "title": "返回按钮权限判断取反(!hasPermission('dustWarn:return'))",
+    "severity": "严重",
+    "type": "权限逻辑错误",
+    "symptom": "有 *:return 权限的用户看不到返回;无权限用户反而可见(与 deviceMonitorWarn 正确写法相反)",
+    "root_cause": "使用 v-if=\"!hasPermission('xxx:return')\";对照 deviceMonitorWarn 应为正向 hasPermission",
+    "repro": "分别用有/无 gasWarn:return 等权限账号打开预警页,观察返回按钮显隐",
+    "fix_hint": "去掉 !,改为 v-if=\"hasPermission('...:return')\"",
+    "id": "BUG-071",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "综采面智能管控",
+    "file": "src/views/vent/monitorManager/workFaceMonitor/components/workFaceHome.vue:251-254",
+    "title": "定位列表读取 readData.flen 空指针",
+    "severity": "严重",
+    "type": "null deref on mount/update",
+    "symptom": "location 设备无 readData 时过滤阶段抛错,后续监控数据不完整",
+    "root_cause": "locationData.push 中直接 data['readData']['flen']",
+    "repro": "location.datalist 含 readData=null 的项",
+    "fix_hint": "使用 data?.readData?.flen ?? '-'",
+    "id": "BUG-072",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "综采面智能管控",
+    "file": "src/views/vent/monitorManager/workFaceMonitor/components/workFaceHome.vue:262-276",
+    "title": "综合预警判断键错误且可能空指针",
+    "severity": "严重",
+    "type": "null deref / wrong field mapping",
+    "symptom": "用 warnInfo.warnInfo 判断却读取 synthesizeS;键不匹配时综合预警不更新,或 synthesizeS 缺失时崩溃;surface_history/sysInfo 亦缺保护",
+    "root_cause": "条件写成 res['warnInfo']['warnInfo'];并对 surface_history[0]、sysInfo.readData 无空保护",
+    "repro": "返回缺 synthesizeS 或 surface_history 为空的综采面数据",
+    "fix_hint": "条件改为 synthesizeS;对各嵌套字段做空判断",
+    "id": "BUG-073",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "色谱仪报表分析",
+    "file": "src/views/vent/bundleSpy/bundleSpyTable/index.vue:237-241,386-390",
+    "title": "活鸡兔/寸草塔二矿图表 O₂闭内 误绑 CH₄ 数据",
+    "severity": "严重",
+    "type": "wrong_field_binding",
+    "symptom": "图例 O₂闭内 柱状与 CH₄闭内完全相同",
+    "root_cause": "name:'O₂闭内' 的 data 写成 ch4AveValues",
+    "repro": "对应矿打开色谱仪报表对比 o2_ave 与图",
+    "fix_hint": "改为 o2AveValues",
+    "id": "BUG-074",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "视频墙",
+    "file": "src/views/vent/monitorManager/camera/index.vue:100-128,172-178",
+    "title": "设备树加载与分页 paramKind 计算脆弱",
+    "severity": "严重",
+    "type": "null_crash/wrong_param",
+    "symptom": "itemValue 无 '&' 或 msgTxt 结构异常时树展开/翻页失败或请求错误类型",
+    "root_cause": "假定 msgTxt[0].datalist 存在;分页用 searchParam.devKind.indexOf('&') 对 nodeData.itemValue 做 substring,缺 '&' 得空串",
+    "repro": "点击 itemValue 不含 '&' 的分类再翻页;或返回空 datalist 的节点",
+    "fix_hint": "安全解析 itemValue;访问 datalist 前判空;分页直接用 nodeData.itemValue",
+    "id": "BUG-075",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "设备监测预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/warn/deviceWarn.vue:5",
+    "title": "返回按钮权限判断取反(!hasPermission('deviceWarn:return'))",
+    "severity": "严重",
+    "type": "权限逻辑错误",
+    "symptom": "有 *:return 权限的用户看不到返回;无权限用户反而可见(与 deviceMonitorWarn 正确写法相反)",
+    "root_cause": "使用 v-if=\"!hasPermission('xxx:return')\";对照 deviceMonitorWarn 应为正向 hasPermission",
+    "repro": "分别用有/无 gasWarn:return 等权限账号打开预警页,观察返回按钮显隐",
+    "fix_hint": "去掉 !,改为 v-if=\"hasPermission('...:return')\"",
+    "id": "BUG-076",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "通风参数一张表",
+    "file": "src/views/vent/monitorManager/deviceMonitor/staticSheets/ventilateSheet.vue:1-263",
+    "title": "通风参数一张表为静态硬编码 HTML,无实时数据",
+    "severity": "严重",
+    "type": "data",
+    "symptom": "页面展示固定矿井参数表(如明安木独等),不刷新监测值",
+    "root_cause": "纯静态 table;script 仅注释转换工具,无 API/绑定",
+    "repro": "打开通风参数一张表 → 任意时刻数据不变、与监测无关",
+    "fix_hint": "改为接口驱动渲染;若仅为示意文档页需在需求中降级为非监测页",
+    "id": "BUG-077",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "通风监测预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/warn/ventilateWarn.vue:10",
+    "title": "返回按钮权限判断取反(!hasPermission('ventilateWarn:return'))",
+    "severity": "严重",
+    "type": "权限逻辑错误",
+    "symptom": "有 *:return 权限的用户看不到返回;无权限用户反而可见(与 deviceMonitorWarn 正确写法相反)",
+    "root_cause": "使用 v-if=\"!hasPermission('xxx:return')\";对照 deviceMonitorWarn 应为正向 hasPermission",
+    "repro": "分别用有/无 gasWarn:return 等权限账号打开预警页,观察返回按钮显隐",
+    "fix_hint": "去掉 !,改为 v-if=\"hasPermission('...:return')\"",
+    "id": "BUG-078",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "通风网络解算",
+    "file": "src/views/vent/monitorManager/deviceMonitor/components/network/index.vue:447-454",
+    "title": "传感器列表未判空直接读 readData",
+    "severity": "严重",
+    "type": "null deref on mount/update",
+    "symptom": "monitors 中某条缺少 readData 时 analyzePageResult 抛错,页面解算结果渲染中断",
+    "root_cause": "直接访问 item['readData']['va']/['m3'],无空保护",
+    "repro": "构造 monitors 项 readData=null 的解算推送 → 打开网络页",
+    "fix_hint": "先取 const rd=item.readData||{} 再读字段",
+    "id": "BUG-079",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "通风网络解算",
+    "file": "src/views/vent/monitorManager/deviceMonitor/components/network/index.vue:403-405",
+    "title": "局部阻力字段误读自然风压",
+    "severity": "严重",
+    "type": "wrong field mapping / logic bug",
+    "symptom": "巷道表“局部阻力”与“自然风压”显示相同值,局部阻力失真",
+    "root_cause": "dHLocal 赋值使用 item['dHNature'],与 dHNature 同源",
+    "repro": "打开网络解算结果表 → 对比局部阻力与自然风压列",
+    "fix_hint": "改为读取 item['dHLocal'](或后端真实局部阻力字段)",
+    "id": "BUG-080",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "防尘系统监测",
+    "file": "src/views/vent/monitorManager/deviceMonitor/components/device/modal/dust.modal.vue:216-238",
+    "title": "粉尘弹窗测点状态/预警历史硬编码",
+    "severity": "严重",
+    "type": "mock hardcode replacing real API",
+    "symptom": "测点状态 customRender 永远返回“正常”;预警滚动板为固定假数据",
+    "root_cause": "columns.state 写死正常;warningConfig.data 硬编码",
+    "repro": "打开粉尘监测详情弹窗观察状态与预警列表",
+    "fix_hint": "状态取真实字段;预警列表接真实告警数据",
+    "id": "BUG-081",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "需风量分析系统",
+    "file": "src/components/vent/micro/createSubApp.vue:15-37",
+    "title": "非 micro-* 路径组件恒为 null",
+    "severity": "严重",
+    "type": "shell_mount_failure",
+    "symptom": "菜单若未挂在 micro-need-air/micro-vent-doc 前缀下则壳空白",
+    "root_cause": "resetComponent 仅识别有限 appType",
+    "repro": "用非 micro 路径打开需风量/核定",
+    "fix_hint": "按实际菜单 path 扩展映射",
+    "id": "BUG-082",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "风窗管控",
+    "file": "src/views/vent/monitorManager/windowMonitor/index.vue:37-41",
+    "title": "后门控制按钮文案错写成前门",
+    "severity": "严重",
+    "type": "control buttons sharing same paramcode / mislabeled control",
+    "symptom": "界面显示“打开前门/关闭前门”,实际下发 rearGateOpen_S/rearGateClose_S,操作员会误控后门",
+    "root_cause": "按钮展示文案复制前门未改,第二参数标题已是后门,UI 文本与指令不一致",
+    "repro": "具备 window:gateControl 且 ndoorcount==2 → 看后两颗按钮文案与 setControl 参数",
+    "fix_hint": "将后两颗按钮文案改为“打开后门/关闭后门”",
+    "id": "BUG-083",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "风窗管控",
+    "file": "src/views/vent/monitorManager/windowMonitor/index.vue:11-14,623-651",
+    "title": "窗面积与窗角度控制共用 paramcode 1/2",
+    "severity": "严重",
+    "type": "control buttons sharing same paramcode",
+    "symptom": "窗1/窗2 面积设定与角度设定最终都下发 paramcode='1'/'2',后端无法区分面积/角度",
+    "root_cause": "twoAreaControl 与 twoAngleControl 都 setControl('1'|'2'),handleOK else 分支直接 data.paramcode=handlerState",
+    "repro": "同时开启 window:twoAreaControl 与 window:twoAngleControl → 分别设定面积/角度 → 抓包对比 paramcode",
+    "fix_hint": "角度使用独立点位(如 frontSetAngle1),或在 value/附加字段中区分控制类型",
+    "id": "BUG-084",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "风窗管控",
+    "file": "src/views/vent/monitorManager/windowMonitor/index.vue:31-33,504-520",
+    "title": "设定风窗面积误走角度弹窗流程",
+    "severity": "严重",
+    "type": "control buttons sharing same paramcode",
+    "symptom": "权限 window:showAngleArea 的“设定风窗面积”调用 setAngle(1),弹窗标题按角度生成,但点位仍 frontSetValue",
+    "root_cause": "面积按钮错误绑定 setAngle 而非 setArea",
+    "repro": "开启 window:showAngleArea → 点击“设定风窗面积” → 观察 modalTitle 与下发参数",
+    "fix_hint": "改为 setArea(1),或单独实现面积设定入口",
+    "id": "BUG-085",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "风门集中同控",
+    "file": "src/views/vent/monitorManager/airDoor/components/tipModal.vue:28-54",
+    "title": "倒计时弹窗 setTimeout 递归不清理",
+    "severity": "严重",
+    "type": "setInterval/setTimeout poll without clear on unmount",
+    "symptom": "提示弹窗显示后每秒递归创建定时器;关闭/卸载后仍继续;timeout<=0 时先 timer=undefined 再 clearTimeout(undefined) 无效,且仍调用 getMonitor()",
+    "root_cause": "getMonitor 无论 timeout 是否归零都递归调用;clearTimeout 写在 timer=undefined 之后;无取消句柄、watchEffect 可见时重复启动",
+    "repro": "触发 visibleTs30=true → 关闭弹窗或离开页面 → 观察仍有定时回调",
+    "fix_hint": "timeout<=0 时 return 不再递归;保存并 clearTimeout;Visible=false 时清理;避免 watchEffect 重复启动",
+    "id": "BUG-086",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "风门集中同控",
+    "file": "src/views/vent/monitorManager/airDoor/index.vue:44-51",
+    "title": "风门同控菜单拉取未判空 msgTxt[0]",
+    "severity": "严重",
+    "type": "null deref on mount",
+    "symptom": "接口返回空/异常结构时 getMenuList 抛错,左侧菜单无法加载",
+    "root_cause": "直接访问 res.msgTxt[0].datalist.length",
+    "repro": "mock /monitor/device 返回无 msgTxt 或空数组 → 打开风门集中同控",
+    "fix_hint": "使用可选链与默认 []:res?.msgTxt?.[0]?.datalist || []",
+    "id": "BUG-087",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "主风机管控",
+    "file": "src/views/vent/monitorManager/mainFanMonitor/hooks/useAreaSetSimulation.ts:37-109; useMainFanSimulation.ts:25-72",
+    "title": "天窗/闸板面积模拟 setInterval 卸载未清理",
+    "severity": "一般",
+    "type": "setInterval/setTimeout poll without clear on unmount",
+    "symptom": "开启 fanSwitchoverSimulation 后设定面积过程中离开页面,interval 仍改 selectData,可能写已销毁状态",
+    "root_cause": "useAreaSetSimulation 仅在过程结束 clear;无 onUnmounted 遍历 activeProcesses 清理(对比 useFanSwitchoverSimulation 有 stopTick)",
+    "repro": "开启模拟 → 设定天窗面积 → 过渡未完成即路由离开",
+    "fix_hint": "在 composable 增加 dispose/onUnmounted,清理 Map 内全部 interval",
+    "id": "BUG-088",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "多灾融合预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/warn/gasWarn.vue:189-190",
+    "title": "strinstallpos 为空时 indexOf 抛错",
+    "severity": "一般",
+    "type": "crash_on_mount",
+    "symptom": "getDetailList 抛错,监测卡片不更新",
+    "root_cause": "假定 strinstallpos 必为 string",
+    "repro": "gas 明细 strinstallpos 为 null",
+    "fix_hint": "const pos=el.strinstallpos||'' 再操作",
+    "id": "BUG-089",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "多灾融合预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/warn/ventilateWarn.vue:207",
+    "title": "warnDes 为空时 indexOf 抛错中断菜单构建",
+    "severity": "一般",
+    "type": "crash_on_mount",
+    "symptom": "左侧菜单空白且轮询中断",
+    "root_cause": "el.warnDes.indexOf('预警') 无空保护",
+    "repro": "sysTypeWarn 返回 warnDes 缺失条目",
+    "fix_hint": "(el.warnDes||'').indexOf(...)",
+    "id": "BUG-090",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "多灾融合预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/warn/dustWarn.vue:255",
+    "title": "巷道温度单位错误依赖 humidity",
+    "severity": "一般",
+    "type": "wrong_field_binding",
+    "symptom": "有温度无湿度时单位空白",
+    "root_cause": "dw: el.humidity ? '(℃)' : ''",
+    "repro": "temperature 有值 humidity 空时看单位",
+    "fix_hint": "dw: el.temperature ? '(℃)' : ''",
+    "id": "BUG-091",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "多灾融合预警",
+    "file": "src/views/vent/monitorManager/alarmMonitor/alarm.data.ts:483-780",
+    "title": "散点图动画 setInterval/resize 监听未清理",
+    "severity": "一般",
+    "type": "resource-leak",
+    "symptom": "调用 setScatterEcharts 后即使销毁图表,30ms 动画与 window resize 监听仍存活",
+    "root_cause": "setInterval(... ,30) 与 addEventListener('resize') 无返回清理函数",
+    "repro": "进入使用 setScatterEcharts 的预警可视化 → 离开页面后 interval 仍跑",
+    "fix_hint": "返回 { dispose }:clearInterval + removeEventListener + wwbzl.dispose()",
+    "id": "BUG-092",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "安全监控分站管理",
+    "file": "src/views/vent/safetyList/index.vue:144-146,71-76",
+    "title": "调试风向取值与展示映射不一致",
+    "severity": "一般",
+    "type": "value_mismatch",
+    "symptom": "调试选“反向”写入 '0',列表展示逻辑把反风映射为 '2',显示异常",
+    "root_cause": "derictList 反向 value='0',而 valueJc 渲染把 '2' 当作反风",
+    "repro": "调试设备选反向并确认,查看列表风向展示",
+    "fix_hint": "统一风向枚举(建议 1 正/2 反)并同步调试与展示",
+    "id": "BUG-093",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "密闭监测系统",
+    "file": "src/views/vent/monitorManager/obfurageMonitor/index.vue:107-113",
+    "title": "forEach 内 data=Object.assign 未回写",
+    "severity": "一般",
+    "type": "dead_assignment",
+    "symptom": "readData 字段未合并进列表项,监测展示缺字段",
+    "root_cause": "dataSource.forEach 中 `data = Object.assign(data, readData)` 只改局部绑定,未写回数组元素",
+    "repro": "对比接口 readData 与页面 options 字段是否缺失",
+    "fix_hint": "改为 Object.assign(data, readData) 不重绑,或显式写回",
+    "id": "BUG-094",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "局部风机管控",
+    "file": "src/views/vent/monitorManager/{fanLocalMonitor,gateMonitor,windowMonitor,mainFanMonitor,balancePressMonitor,compressor,windrectMonitor}/**; src/views/vent/gas/gasAssessment/threejs/gasAssessmen.threejs.ts:多处 active debugger",
+    "title": "生产路径残留 debugger 断点",
+    "severity": "一般",
+    "type": "debug-leftover",
+    "symptom": "DevTools 打开时模型切换/动画初始化/工况辅助计算会暂停页面",
+    "root_cause": "遗留未注释 debugger;(如 fanLocal.threejs.Two.ts:40-42, gate.threejs.two.ssl.ts:190, dandaoFcBd3.threejs.ts:170, mainWind.xj.threejs.ts:768/795, balancePress.threejs.ts:60, nitrogenHome_dtyj.vue:326, windrect.threejs.ts:366, gasAssessmen.threejs.ts:69, conditionAssistance.vue:207, fanLocal/index.vue:1549 等)",
+    "repro": "打开 DevTools → 进入对应三维监控页触发模型加载/动画",
+    "fix_hint": "删除全部 active debugger,或统一用开发开关包裹",
+    "id": "BUG-095",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "报表数据映射",
+    "file": "src/views/vent/reportManager/comment/report-modal.vue:135-138",
+    "title": "formStates 被重新赋值为 Object.assign 返回值",
+    "severity": "一般",
+    "type": "reactivity",
+    "symptom": "极端情况下表单代理引用混乱;与父级 reportMap 的 formState 断裂叠加更易丢字段",
+    "root_cause": "formStates = Object.assign(formStates, newV) 不必要地重绑定 let",
+    "repro": "编辑映射记录时反复打开弹窗观察字段回填",
+    "fix_hint": "仅 Object.assign(formStates, newV) 或 for..in 赋值,不要重赋值 let",
+    "id": "BUG-096",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "报表管理",
+    "file": "src/views/vent/reportManager/index.vue:89-106",
+    "title": "三类根节点重复挂载同一套子树",
+    "severity": "一般",
+    "type": "wrong_tree_build",
+    "symptom": "系统/手动/模板三类下出现完全相同的业务分类,筛选易混淆",
+    "root_cause": "getTreeList 对 listArr 每个根节点都 push 完整 res 树,未按业务类型分流",
+    "repro": "打开报表管理对比三个根节点的 children",
+    "fix_hint": "按返回字段或接口分类型填充到对应根节点",
+    "id": "BUG-097",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "抽采泵站监测",
+    "file": "src/views/vent/gas/gasPumpMonitor/components/monitorChart.vue:49-66",
+    "title": "setInterval 用 clearTimeout 清理",
+    "severity": "一般",
+    "type": "resource-leak",
+    "symptom": "离开页面后图表轮询可能继续,造成多余计算/内存占用",
+    "root_cause": "timer=setInterval(...),onUnmounted 调用 clearTimeout(timer)",
+    "repro": "打开含 monitorChart 的泵站页 → 离开路由 → 断点/日志可见 interval 仍触发",
+    "fix_hint": "改为 clearInterval(timer)",
+    "id": "BUG-098",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "文件共享中心",
+    "file": "src/views/vent/performance/fileDetail/index.vue:240-260,465-469",
+    "title": "setInterval 用 clearTimeout 清理",
+    "severity": "一般",
+    "type": "resource_leak",
+    "symptom": "关闭汇总弹窗或卸载后轮询未必停止,持续请求 queryVentMonReportJd",
+    "root_cause": "timer=setInterval 但 handleCancelHz/onUnmounted 使用 clearTimeout",
+    "repro": "打开汇总报表后关闭弹窗,观察是否仍持续请求 queryVentMonReportJd",
+    "fix_hint": "统一使用 clearInterval(timer)",
+    "id": "BUG-099",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "智能注浆系统",
+    "file": "src/views/vent/monitorManager/groutMonitor/index.vue:1-35",
+    "title": "2D 模型与 groutHome 同时渲染可能叠层",
+    "severity": "一般",
+    "type": "ui_logic",
+    "symptom": "is2DModel 为 true 时流程图与监控壳同时存在,交互/遮罩异常",
+    "root_cause": "v-if is2DModel 渲染 modelComponent,同时 activeKey==monitor 仍挂载 groutHome",
+    "repro": "开启全局 is2DModel 进入注浆监控页",
+    "fix_hint": "2D 时由单一容器负责模型,或让 groutHome 在 2D 模式跳过三维初始化",
+    "id": "BUG-100",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "机电硐室管控",
+    "file": "src/views/vent/home/configurable/configurable.data.ts:6164,6217",
+    "title": "electro-champer / electro-chamber pageType 拼写分裂",
+    "severity": "一般",
+    "type": "config_typo",
+    "symptom": "部分配置按错误 pageType 入库后,fetchConfigs('electro-champer') 只能拿到半套模块",
+    "root_cause": "同一业务存在 champer/chamber 两种 pageType 字面量",
+    "repro": "在配置表按两种 pageType 各查一次 records 数量",
+    "fix_hint": "统一为一种拼写并做数据迁移",
+    "id": "BUG-101",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "束管日报分析",
+    "file": "src/views/vent/bundle/bundleMonitorTable/bundle-table.data.ts:412-435",
+    "title": "保德 N₂ 子列重复两个平均值缺少最小值",
+    "severity": "一般",
+    "type": "wrong_column_dataIndex",
+    "symptom": "N₂ 两列相同平均值;最小值无法展示",
+    "root_cause": "第二、三列均为 title:平均值 + n2_ave",
+    "repro": "保德束管日报看氮气表头",
+    "fix_hint": "中间列改为最小值/n2_min",
+    "id": "BUG-102",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "瓦斯人工巡检地点管理(NFC)",
+    "file": "src/views/vent/gas/gasInspect/index.vue:331-342",
+    "title": "筛「两次」误把 pagination.current 写成 2",
+    "severity": "一般",
+    "type": "copy_paste_bug",
+    "symptom": "筛选两次后分页状态错乱",
+    "root_cause": "case '2': pagination.current=2",
+    "repro": "地点管理→巡检次数筛选→两次",
+    "fix_hint": "两次也 current=1",
+    "id": "BUG-103",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "用户日志管理",
+    "file": "src/views/monitor/log/index.vue:34,61-66",
+    "title": "初始 fieldMapToTime 格式与切换后不一致",
+    "severity": "一般",
+    "type": "config_mismatch",
+    "symptom": "首次进入登录日志时时间范围映射可能无效,切 Tab 后才正常",
+    "root_cause": "paramTime 初始为扁平数组,tabChange 才设为 [[field,[begin,end],fmt]] 结构",
+    "repro": "首次进入不切 Tab,使用时间筛选对比切 Tab 后行为",
+    "fix_hint": "初始化即使用与 case '1' 相同的 fieldMapToTime 结构",
+    "id": "BUG-104",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "色谱仪报表分析",
+    "file": "src/views/vent/bundleSpy/bundleSpyTable/index.vue; src/views/vent/monitorManager/comment/bundleSpyMonitorTable.vue:bundleSpyTable 175-195; bundleSpyMonitorTable 93-95",
+    "title": "发火阶段筛选项文案不一致",
+    "severity": "一般",
+    "type": "logic",
+    "symptom": "阶段统计为 0 或与报警字典不一致;同数据在不同组件统计结果不同",
+    "root_cause": "bundleSpyTable 过滤 '加速氧化升温阶段'(与 warnFire-brt 一致),comment/bundleSpyMonitorTable 过滤 '加速氧化阶段(自热期)';历史计算注释仍写 '加速氧化阶段'",
+    "repro": "同一份含阶段字段的报表分别在两处打开,对比进度条计数",
+    "fix_hint": "统一与后端/告警字典的阶段枚举(建议 '加速氧化升温阶段')",
+    "id": "BUG-105",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "防尘系统监测",
+    "file": "src/views/vent/monitorManager/deviceMonitor/components/device/modal/atomizing.modal.vue:33-42",
+    "title": "主机位置字段错误附加 ℃ 单位",
+    "severity": "一般",
+    "type": "logic/display bug",
+    "symptom": "位置类字段后显示温度单位,信息误导",
+    "root_cause": "holderLocation 展示模板误加 ℃",
+    "repro": "打开喷雾监测详情查看“主机位置”",
+    "fix_hint": "去掉 ℃,位置字段无需单位",
+    "id": "BUG-106",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "防火门智能管控",
+    "file": "src/views/vent/home/configurable/firedoor/fireDoorMonitor.vue:76-99",
+    "title": "场景列表为空仍继续后续逻辑且配置增强未启用",
+    "severity": "一般",
+    "type": "incomplete_init",
+    "symptom": "无防火门场景时下拉为空、模块无有效数据;服务端配置未走 updateEnhancedConfigs",
+    "root_cause": "records 为空时仍调用 loadFireGateData;updateEnhancedConfigs 被注释",
+    "repro": "manageFireList 返回空→页面无场景可选且面板无数据",
+    "fix_hint": "无 records 时提示并 return;恢复 updateEnhancedConfigs(configs)",
+    "id": "BUG-107",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "防灭火系统监测",
+    "file": "src/views/vent/monitorManager/deviceMonitor/components/device/modal/firemon.modal.vue:63-65",
+    "title": "智慧球温度值单位误标为 %",
+    "severity": "一般",
+    "type": "logic/display bug",
+    "symptom": "温度值旁显示 % 而非 ℃,监控读数语义错误",
+    "root_cause": "模板单位复制错误",
+    "repro": "打开 firemon 详情查看温度项单位",
+    "fix_hint": "将单位改为 ℃",
+    "id": "BUG-108",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "预警联动控制",
+    "file": "src/views/vent/deviceManager/workingFace/CustomNormalTable.vue:250-251",
+    "title": "批量删除未传 handleSuccess 回调",
+    "severity": "一般",
+    "type": "undefined_api_ref",
+    "symptom": "确认后 handleSuccess is not a function,列表不刷新",
+    "root_cause": "batchDeleteById(params, handleSuccess) 被调用为 batchDelete({ids})",
+    "repro": "预警联动批删点确认看控制台",
+    "fix_hint": "传入 reload 作为第二参",
+    "id": "BUG-109",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  },
+  {
+    "module": "预警联动控制",
+    "file": "src/views/vent/deviceManager/workingFace/index.vue:1-92",
+    "title": "预警联动控制落到通用智能管控 CRUD,无联动逻辑",
+    "severity": "一般",
+    "type": "incomplete",
+    "symptom": "范围名为预警联动控制,实际仅为 managesys 列表增删改,无预警联动控制流程",
+    "root_cause": "页面复用 CustomNormalTable + managesys_list;onMounted 空实现,无联动策略/告警动作 UI",
+    "repro": "按菜单进入预警联动控制 → 仅见设备/系统管理表",
+    "fix_hint": "补齐联动规则配置与执行界面,或修正菜单路由到正确页面",
+    "id": "BUG-110",
+    "status": "新建",
+    "date": "2026-11-02",
+    "method": "静态审查"
+  }
+]

+ 482 - 0
docs/_heuristic_hits.json

@@ -0,0 +1,482 @@
+{
+  "file_count": 902,
+  "summary": {
+    "debugger": 41,
+    "setInterval": 43,
+    "setTimeout": 264,
+    "clearInterval": 56,
+    "clearTimeout": 160,
+    "onUnmounted": 190,
+    "inverted-return-perm": 6,
+    "res-length": 32,
+    "mock-comment-api": 45,
+    "hardcode-excel": 46
+  },
+  "interval_no_clear": [
+    "src/views/vent/gas/gasPumpMonitor/components/monitorChart.vue:50:timer = setInterval(() => {",
+    "src/views/vent/monitorManager/alarmMonitor/alarm.data.ts:753:setInterval(function () {",
+    "src/views/vent/performance/fileDetail/index.vue:243:timer = setInterval(async () => {"
+  ],
+  "timeout_no_unmount": [
+    "src/views/vent/monitorManager/mainFanMonitor/mainWind.threejs.ts:223:setTimeout(() => {",
+    "src/views/vent/home/configurable/belt/threejs/belt.threejs.ts:91:// setTimeout(() => {",
+    "src/views/vent/monitorManager/gateMonitor/gate.threejs.three.tl.ts:376:// setTimeout(() => {",
+    "src/views/vent/monitorManager/gateMonitor/gate.threejs.three.hsw.ts:423:// setTimeout(() => {",
+    "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistanceCY.vue:283:setTimeout(() => {",
+    "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistanceCY.vue:290:setTimeout(() => {",
+    "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistanceCY.vue:351:setTimeout(() => {",
+    "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistanceCY.vue:360:setTimeout(() => {",
+    "src/views/vent/monitorManager/gateMonitor/gate.threejs.three.ts:422:// setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.wlml.vue:191:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.wlml.vue:202:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.bd.vue:162:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.bd.vue:173:setTimeout(() => {",
+    "src/views/vent/monitorManager/mainFanMonitor/mainWind.lj.threejs.ts:193:setTimeout(() => {",
+    "src/views/vent/monitorManager/windrectMonitor/duishe.threejs.ts:277:setTimeout(() => {",
+    "src/views/vent/monitorManager/windrectMonitor/duishe.threejs.ts:289:// setTimeout(() => {",
+    "src/views/vent/monitorManager/gateMonitor/gate.threejs.xc.ts:428:// setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.hlg.vue:152:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.hlg.vue:163:setTimeout(() => {",
+    "src/views/vent/monitorManager/gateMonitor/gate.threejs.two.yj.ts:435:// setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/wisdomball.modal.vue:292:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/wisdomball.modal.vue:303:setTimeout(() => {",
+    "src/views/vent/monitorManager/mainFanMonitor/components/conditionAssistance.vue:474:setTimeout(() => {",
+    "src/views/vent/monitorManager/mainFanMonitor/components/conditionAssistance.vue:484:setTimeout(() => {",
+    "src/views/vent/monitorManager/mainFanMonitor/components/conditionAssistance.vue:492:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.vue:197:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.vue:208:setTimeout(() => {",
+    "src/layouts/default/header/components/MessageBroadcast.vue:122:setTimeout(() => {",
+    "src/views/vent/monitorManager/windowMonitor/window.threejs.ts:195:setTimeout(() => {",
+    "src/views/vent/monitorManager/windowMonitor/window.threejs.ts:247:setTimeout(() => {",
+    "src/views/vent/monitorManager/windowMonitor/window.threejs.ts:251:setTimeout(() => {",
+    "src/views/vent/monitorManager/windowMonitor/window.threejs.ts:256:setTimeout(() => {",
+    "src/views/vent/monitorManager/windowMonitor/window.threejs.ts:259:setTimeout(() => {",
+    "src/views/vent/monitorManager/windowMonitor/window.threejs.ts:408:setTimeout(async () => {",
+    "src/views/vent/monitorManager/gateMonitor/gate.threejs.yj.ts:426:// setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/ballvalve.modal.vue:281:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/ballvalve.modal.vue:292:setTimeout(() => {",
+    "src/layouts/default/header/components/notify/index.vue:137:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.cct.vue:132:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.cct.vue:143:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.yjl.vue:123:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.yjl.vue:134:setTimeout(() => {",
+    "src/layouts/default/tabs/components/TabRedo.vue:25:setTimeout(() => {",
+    "src/views/vent/monitorManager/fanLocalMonitor/components/dischargeGas/index.vue:183:setTimeout(() => handleStart(), 500);",
+    "src/views/vent/monitorManager/gateMonitor/gate.threejs.xr.ts:400:// setTimeout(() => {",
+    "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistance.vue:140:setTimeout(() => {",
+    "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistance.vue:508:setTimeout(() => {",
+    "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistance.vue:522:setTimeout(() => {",
+    "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistance.vue:529:setTimeout(() => {",
+    "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistance.vue:544:timer = setTimeout(() => {",
+    "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistance.vue:699:setTimeout(() => {",
+    "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistance.vue:709:setTimeout(() => {",
+    "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistance.vue:719:setTimeout(() => {",
+    "src/views/vent/performance/fileDetail/commen/treeList.vue:242:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/dust.modal.vue:247:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/dust.modal.vue:258:setTimeout(() => {",
+    "src/views/vent/monitorManager/airDoor/components/door-content-r.vue:169:setTimeout(() => {",
+    "src/views/vent/monitorManager/mainFanMonitor/mainWind.xj.threejs.ts:196:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal-Gx.vue:174:setTimeout(() => {",
+    "src/views/vent/monitorManager/gateMonitor/gate.threejs.two.ss.ts:427:// setTimeout(() => {",
+    "src/views/vent/monitorManager/fanLocalMonitor/components/supplyAir.vue:126:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.zdrk.vue:186:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.zdrk.vue:197:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/firemon.modal.vue:164:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/firemon.modal.vue:175:setTimeout(() => {",
+    "src/views/vent/monitorManager/mainFanMonitor/components/conditionAssistanceCY.vue:474:setTimeout(() => {",
+    "src/views/vent/monitorManager/mainFanMonitor/components/conditionAssistanceCY.vue:484:setTimeout(() => {",
+    "src/views/vent/monitorManager/mainFanMonitor/components/conditionAssistanceCY.vue:492:setTimeout(() => {",
+    "src/views/vent/monitorManager/gateMonitor/gate.threejs.noStation.ts:337:// setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/gaspatrol.modal.vue:168:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/gaspatrol.modal.vue:179:setTimeout(() => {",
+    "src/views/vent/monitorManager/windowMonitor/shuangdaoFcYjl.threejs.ts:351://     // setTimeout(() => {",
+    "src/views/vent/monitorManager/fanLocalMonitor/components/dischargeGas.vue:127:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/atomizing.modal.vue:312:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/atomizing.modal.vue:323:setTimeout(() => {",
+    "src/views/vent/monitorManager/mainFanMonitor/mainWind.li3.threejs.ts:213:setTimeout(() => {",
+    "src/views/vent/monitorManager/gateMonitor/gate.threejs.yy.ts:372:// setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.sw.vue:122:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.sw.vue:133:setTimeout(() => {",
+    "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.sjh.vue:186:setTimeout(() => {"
+  ],
+  "hits": {
+    "debugger": [
+      "src/views/vent/monitorManager/workFaceMonitor/workFace.threejs.base.ts:266:// debugger;",
+      "src/views/vent/monitorManager/fanLocalMonitor/index.vue:1549:debugger;",
+      "src/views/vent/monitorManager/mainFanMonitor/mainWind.lj.threejs.ts:548:// debugger;",
+      "src/views/vent/home/configurable/belt/threejs/MonitorPanel.vue:152:debugger;",
+      "src/views/vent/monitorManager/windrectMonitor/duishe.threejs.ts:29:// debugger;",
+      "src/views/vent/safetyList/common/HistoryTable.vue:233:// debugger;",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.xc.ts:403://     debugger;",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.two.ssl.ts:190:debugger;",
+      "src/views/vent/monitorManager/windowMonitor/dandaoFcBd3.threejs.ts:170:debugger;",
+      "src/layouts/default/header/components/VoiceBroadcast.vue:67:// debugger;",
+      "src/views/vent/monitorManager/fanLocalMonitor/fanLocal.threejs.single.ts:81:// debugger;",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.yj.ts:401://     debugger;",
+      "src/views/vent/monitorManager/deviceMonitor/components/device/index.vue:1343:// debugger;",
+      "src/views/vent/monitorManager/deviceMonitor/components/device/index.vue:1345:// debugger;",
+      "src/views/vent/monitorManager/fanLocalMonitor/fanLocal.threejs.Two.ts:40:debugger;",
+      "src/views/vent/monitorManager/fanLocalMonitor/fanLocal.threejs.Two.ts:42:debugger;",
+      "src/views/vent/monitorManager/fanLocalMonitor/fanLocal.threejs.Two.ts:213:// debugger;",
+      "src/views/vent/monitorManager/windrectMonitor/index.vue:702:// debugger;",
+      "src/views/vent/monitorManager/windrectMonitor/windrect.threejs.ts:366:debugger;",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.xr.ts:375://     debugger;",
+      "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistance.vue:207:debugger;",
+      "src/views/vent/monitorManager/balancePressMonitor/index1.vue:161:debugger;",
+      "src/views/vent/monitorManager/mainFanMonitor/mainWind.xj.threejs.ts:578:// debugger;",
+      "src/views/vent/monitorManager/mainFanMonitor/mainWind.xj.threejs.ts:768:debugger;",
+      "src/views/vent/monitorManager/mainFanMonitor/mainWind.xj.threejs.ts:795:debugger;",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.two.ss.ts:402://     debugger;",
+      "src/views/vent/monitorManager/compressor/components/nitrogenHome_dtyj.vue:326:debugger;",
+      "src/views/vent/monitorManager/windrectMonitor/longmen.threejs.ts:319:// debugger;",
+      "src/views/vent/monitorManager/windrectMonitor/zhedie.threejs.ts:286://   debugger;",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.noStation.ts:312://     debugger;",
+      "src/views/vent/monitorManager/compressor/nitrogen.data.ts:235:// debugger;",
+      "src/views/vent/monitorManager/windowMonitor/shuangdaoFcYjl.threejs.ts:326://       //     debugger;",
+      "src/views/vent/monitorManager/fanLocalMonitor/fanLocal.threejs.base.ts:209:// debugger;",
+      "src/views/vent/monitorManager/balancePressMonitor/balancePress.threejs.ts:60:debugger;",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.one.sp.ts:190:debugger;",
+      "src/views/vent/gas/gasPipeNet/index.vue:120:// debugger;",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.yy.ts:347://     debugger;",
+      "src/views/vent/monitorManager/nitrogen/nitrogen.threejs.ts:127:// debugger;",
+      "src/views/vent/monitorManager/groutMonitor/components/groutHomelt.vue:157:// debugger;",
+      "src/views/vent/gas/gasAssessment/threejs/gasAssessmen.threejs.ts:69:debugger;"
+    ],
+    "setInterval": [
+      "src/views/vent/home/configurable/green/fire-green.vue:113:interval = window.setInterval(() => {",
+      "src/views/vent/gas/gasPumpMonitor/components/monitorChart.vue:50:timer = setInterval(() => {",
+      "src/views/vent/home/configurable/ventWLML.vue:118:interval = setInterval(() => {",
+      "src/views/vent/gas/gasPumpSetting/components/unitTable.vue:44:timer = setInterval(() => {",
+      "src/views/vent/home/configurable/dustControl.vue:59:interval = setInterval(() => {",
+      "src/views/vent/home/configurable/index.vue:91:interval = window.setInterval(() => {",
+      "src/views/vent/home/configurable/fireWZ.vue:88:interval = setInterval(() => {",
+      "src/views/vent/home/configurable/vent.vue:94:interval = setInterval(() => {",
+      "src/views/vent/gas/gasZk/index.vue:65:timer = setInterval(async () => {",
+      "src/views/vent/monitorManager/dedustMonitor/components/DedustHome.vue:169:timer = setInterval(() => {",
+      "src/views/vent/monitorManager/fanLocalMonitor/hooks/useGasDischargeSimulation.ts:342:tickTimer = setInterval(simulationTick, Math.max(50, 1000 / speed.value));",
+      "src/views/vent/monitorManager/mainFanMonitor/hooks/useFanSwitchoverSimulation.ts:93:tickTimer = setInterval(() => {",
+      "src/views/vent/home/configurable/blue/homeBlue.vue:57:interval = setInterval(fetchData, 60000);",
+      "src/views/vent/home/configurable/fireBD.vue:124:interval = setInterval(() => {",
+      "src/views/vent/home/configurable/blue/dustNew.vue:78:interval = setInterval(fetchData, 2000);",
+      "src/views/vent/home/configurable/green/vent-green.vue:74:interval = window.setInterval(() => {",
+      "src/views/vent/home/configurable/belt/belt.vue:113:timer = setInterval(() => {",
+      "src/views/vent/home/configurable/dust.vue:95:// setInterval(() => {",
+      "src/views/vent/home/configurable/ventSY.vue:108:interval = setInterval(() => {",
+      "src/views/vent/home/configurable/tester.vue:95:interval = setInterval(() => {",
+      "src/views/vent/home/configurable/vent182.vue:130:interval = setInterval(() => {",
+      "src/views/vent/home/configurable/ventDHZ.vue:107:interval = setInterval(() => {",
+      "src/views/vent/monitorManager/dedustMonitor/components/DedustHomeBet.vue:184:timer = setInterval(() => {",
+      "src/views/vent/home/configurable/dustBD.vue:98:interval = setInterval(() => {",
+      "src/views/vent/home/configurable/ventV6.vue:144:interval = setInterval(() => {",
+      "src/views/vent/home/configurable/fire.vue:95:// setInterval(() => {",
+      "src/views/vent/monitorManager/groutMonitor/components/zhijiangSVG.vue:57:mixerTimer = setInterval(() => {",
+      "src/views/vent/home/configurable/blue/ventNew copy.vue:49:interval = setInterval(() => {",
+      "src/views/vent/home/configurable/fireTS.vue:142:interval = setInterval(() => {",
+      "src/views/vent/home/configurable/fireMine.vue:91:interval = setInterval(() => {",
+      "src/views/vent/home/configurable/ventV5.vue:121:interval = setInterval(() => {",
+      "src/views/vent/monitorManager/mainFanMonitor/hooks/useAreaSetSimulation.ts:85:setInterval(() => {",
+      "src/views/vent/monitorManager/alarmMonitor/alarm.data.ts:753:setInterval(function () {",
+      "src/views/vent/home/configurable/electroChamber.vue:97:// setInterval(() => {",
+      "src/views/vent/performance/fileDetail/index.vue:243:timer = setInterval(async () => {",
+      "src/views/vent/home/configurable/ventSDG.vue:170:interval = setInterval(() => {",
+      "src/views/vent/monitorManager/fanLocalMonitor/components/dischargeGas/GasTrendChart.vue:234:const checkInit = setInterval(() => {",
+      "src/views/vent/home/configurable/blue/ventNew.vue:57:interval = setInterval(fetchData, 60000);",
+      "src/views/vent/home/configurable/green/dust-green.vue:83:interval = window.setInterval(() => {",
+      "src/views/vent/home/configurable/firedoor/fireDoorMonitor.vue:124:const interval = setInterval(() => {"
+    ],
+    "setTimeout": [
+      "src/views/vent/monitorManager/balancePressMonitor/components/balancePressHomeSP.vue:133:timer = setTimeout(",
+      "src/views/vent/monitorManager/mainFanMonitor/mainWind.threejs.ts:223:setTimeout(() => {",
+      "src/views/vent/home/configurable/belt/threejs/belt.threejs.ts:91:// setTimeout(() => {",
+      "src/views/vent/monitorManager/deviceMonitor/index.vue:47:setTimeout(",
+      "src/views/vent/monitorManager/deviceMonitor/index.vue:61:setTimeout(() => {",
+      "src/views/vent/monitorManager/workFaceMonitor/components/workFaceVentHome.vue:245:timer = setTimeout(",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.three.tl.ts:376:// setTimeout(() => {",
+      "src/views/vent/monitorManager/tunFaceMonitor/index.vue:106:setTimeout(() =>{",
+      "src/views/vent/monitorManager/compressor/nitrogen.threejs.ts:112:setTimeout(async () => {",
+      "src/views/vent/monitorManager/compressor/nitrogen.threejs.ts:131:setTimeout(async () => {",
+      "src/views/vent/monitorManager/compressor/nitrogen.threejs.ts:155:setTimeout(async () => {",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.three.hsw.ts:423:// setTimeout(() => {",
+      "src/views/vent/monitorManager/compressor/components/nitrogenHome_lt.vue:152:timer = await setTimeout(",
+      "src/views/vent/safetyList/index.vue:188://   timer = setTimeout(",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.ts:351:setTimeout(async () => {",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.ts:383:setTimeout(async () => {",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.ts:412:setTimeout(async () => {",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.ts:441:setTimeout(async () => {",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.ts:470:setTimeout(async () => {",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.ts:495:setTimeout(async () => {",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.ts:520:setTimeout(async () => {",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.ts:559:setTimeout(async () => {",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.ts:593:setTimeout(async () => {",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.ts:626:setTimeout(async () => {",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.ts:659:setTimeout(async () => {",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.ts:692:setTimeout(async () => {",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.ts:725:setTimeout(async () => {",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.ts:758:setTimeout(async () => {",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.ts:786:setTimeout(async () => {",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.ts:815:setTimeout(async () => {",
+      "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistanceCY.vue:283:setTimeout(() => {",
+      "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistanceCY.vue:290:setTimeout(() => {",
+      "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistanceCY.vue:351:setTimeout(() => {",
+      "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistanceCY.vue:360:setTimeout(() => {",
+      "src/views/vent/monitorManager/compressor/components/nitrogenHome_dltj.vue:297:timer = await setTimeout(",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.three.ts:422:// setTimeout(() => {",
+      "src/views/vent/monitorManager/workFaceMonitor/components/workFaceDustHome.vue:159:timer = setTimeout(",
+      "src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHomeBD.vue:93:timer = setTimeout(",
+      "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.wlml.vue:191:setTimeout(() => {",
+      "src/views/vent/monitorManager/deviceMonitor/components/device/modal/fiber.modal.wlml.vue:202:setTimeout(() => {"
+    ],
+    "clearInterval": [
+      "src/views/vent/home/configurable/green/fire-green.vue:106:clearInterval(interval);",
+      "src/views/vent/home/configurable/ventWLML.vue:117:clearInterval(interval);",
+      "src/views/vent/home/configurable/ventWLML.vue:139:clearInterval(interval);",
+      "src/views/vent/home/configurable/ventWLML.vue:154:clearInterval(interval);",
+      "src/views/vent/gas/gasPumpSetting/components/unitTable.vue:51:timer = clearInterval(timer);",
+      "src/views/vent/home/configurable/dustControl.vue:76:clearInterval(interval);",
+      "src/views/vent/home/configurable/index.vue:97:clearInterval(interval);",
+      "src/views/vent/home/configurable/fireWZ.vue:96:clearInterval(interval);",
+      "src/views/vent/home/configurable/vent.vue:100:clearInterval(interval);",
+      "src/views/vent/gas/gasZk/index.vue:85:clearInterval(timer);",
+      "src/views/vent/monitorManager/dedustMonitor/components/DedustHome.vue:174:clearInterval(timer);",
+      "src/views/vent/monitorManager/betKy/index.vue:260:clearInterval(timer);",
+      "src/views/vent/monitorManager/fanLocalMonitor/hooks/useGasDischargeSimulation.ts:346:clearInterval(tickTimer);",
+      "src/views/vent/monitorManager/mainFanMonitor/hooks/useFanSwitchoverSimulation.ts:103:clearInterval(tickTimer);",
+      "src/views/vent/home/configurable/blue/homeBlue.vue:61:clearInterval(interval);",
+      "src/views/vent/home/configurable/fireBD.vue:132:clearInterval(interval);",
+      "src/views/vent/home/configurable/blue/dustNew.vue:82:clearInterval(interval);",
+      "src/views/vent/home/configurable/green/vent-green.vue:67:clearInterval(interval);",
+      "src/views/vent/home/configurable/belt/belt.vue:112:if (timer) clearInterval(timer);",
+      "src/views/vent/home/configurable/belt/belt.vue:124:clearInterval(timer);",
+      "src/views/vent/home/configurable/dust.vue:101:clearInterval(interval);",
+      "src/views/vent/home/configurable/ventSY.vue:107:clearInterval(interval);",
+      "src/views/vent/home/configurable/ventSY.vue:129:clearInterval(interval);",
+      "src/views/vent/home/configurable/ventSY.vue:144:clearInterval(interval);",
+      "src/views/vent/home/configurable/tester.vue:102:clearInterval(interval);",
+      "src/views/vent/home/configurable/vent182.vue:129:clearInterval(interval);",
+      "src/views/vent/home/configurable/vent182.vue:164:clearInterval(interval);",
+      "src/views/vent/home/configurable/vent182.vue:179:clearInterval(interval);",
+      "src/views/vent/home/configurable/ventDHZ.vue:113:clearInterval(interval);",
+      "src/views/vent/monitorManager/dedustMonitor/components/DedustHomeBet.vue:189:clearInterval(timer);",
+      "src/views/vent/home/configurable/dustBD.vue:106:clearInterval(interval);",
+      "src/views/vent/home/configurable/ventV6.vue:143:clearInterval(interval);",
+      "src/views/vent/home/configurable/ventV6.vue:170:clearInterval(interval);",
+      "src/views/vent/home/configurable/ventV6.vue:185:clearInterval(interval);",
+      "src/views/vent/home/configurable/fire.vue:101:clearInterval(interval);",
+      "src/views/vent/monitorManager/groutMonitor/components/zhijiangSVG.vue:65:clearInterval(mixerTimer);",
+      "src/views/vent/home/configurable/blue/ventNew copy.vue:55:clearInterval(interval);",
+      "src/views/vent/home/configurable/fireTS.vue:397:clearInterval(interval);",
+      "src/views/vent/home/configurable/fireMine.vue:99:clearInterval(interval);",
+      "src/views/vent/home/configurable/ventV5.vue:120:clearInterval(interval);"
+    ],
+    "clearTimeout": [
+      "src/views/vent/monitorManager/compressor/nitrogen.dixia.threejs.ts:107:clearTimeout(this.animationTimer);",
+      "src/views/vent/monitorManager/balancePressMonitor/components/balancePressHomeSP.vue:460:clearTimeout(timer);",
+      "src/views/vent/monitorManager/windowMonitor/dandaoFcYjl.threejs.ts:203:clearTimeout(this.animationTimer);",
+      "src/views/vent/monitorManager/workFaceMonitor/components/workFaceVentHome.vue:335:clearTimeout(timer);",
+      "src/views/vent/gas/gasPumpMonitor/components/monitorChart.vue:65:clearTimeout(timer);",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.three.tl.ts:218:clearTimeout(this.animationTimer);",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.three.hsw.ts:211:clearTimeout(this.animationTimer);",
+      "src/views/vent/monitorManager/compressor/components/nitrogenHome_lt.vue:219:clearTimeout(timer);",
+      "src/views/vent/safetyList/index.vue:415:clearTimeout(timer);",
+      "src/views/vent/monitorManager/fanLocalMonitor/components/conditionAssistanceCY.vue:328:clearTimeout(timer);",
+      "src/views/vent/monitorManager/compressor/components/nitrogenHome_dltj.vue:744:clearTimeout(timer);",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.three.ts:211:clearTimeout(this.animationTimer);",
+      "src/views/vent/monitorManager/workFaceMonitor/components/workFaceDustHome.vue:238:clearTimeout(timer);",
+      "src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHomeBD.vue:196:clearTimeout(timer);",
+      "src/views/vent/monitorManager/windowMonitor/dandaoFcHjt.threejs.ts:215:clearTimeout(this.animationTimer);",
+      "src/views/vent/monitorManager/gateMonitor/dandaoFcBd3.threejs.ts:295:clearTimeout(this.animationTimer);",
+      "src/views/vent/monitorManager/windowMonitor/shuangdaoFc.threejs.ts:206:clearTimeout(this.animationTimer);",
+      "src/views/vent/home/configurable/green/fusion-warn-green.vue:49:clearTimeout(timer);",
+      "src/views/vent/home/configurable/green/fusion-warn-green.vue:97:clearTimeout(timer);",
+      "src/views/vent/safetyList/common/detail-235.vue:531:clearTimeout(timer);",
+      "src/views/vent/safetyList/common/detail-235.vue:532:clearTimeout(timer3)",
+      "src/views/vent/safetyList/common/detail-235.vue:790:clearTimeout(timer3)",
+      "src/views/vent/safetyList/common/detail-235.vue:791:clearTimeout(timer)",
+      "src/views/vent/safetyList/common/detail-235.vue:869:clearTimeout(timer)",
+      "src/views/vent/safetyList/common/detail-235.vue:870:clearTimeout(timer3)",
+      "src/views/vent/safetyList/common/detail-235.vue:1035:[timer, timer3].forEach((t) => t && clearTimeout(t));",
+      "src/views/vent/monitorManager/tunFaceMonitor/components/tunFaceGasHome.vue:246:clearTimeout(timer);",
+      "src/views/vent/monitorManager/fanLocalMonitor/index.vue:1767:clearTimeout(timer);",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.window.ts:202:clearTimeout(this.animationTimer);",
+      "src/views/vent/monitorManager/alarmMonitor/warn/gasWarn.vue:125:clearTimeout(timer);",
+      "src/views/vent/monitorManager/alarmMonitor/warn/gasWarn.vue:132:clearTimeout(timer1);",
+      "src/views/vent/monitorManager/fanLocalMonitor/components/faultEchartLine.vue:287:clearTimeout(timer);",
+      "src/views/vent/monitorManager/tunFaceMonitor/components/tunFaceHome.vue:294:clearTimeout(timer);",
+      "src/views/vent/monitorManager/tunFaceMonitor/components/tunFaceVentHome.vue:314:clearTimeout(timer);",
+      "src/views/vent/safetyList/common/detail-cl.vue:461:clearTimeout(timer);",
+      "src/views/vent/safetyList/common/detail-cl.vue:465:clearTimeout(timer1);",
+      "src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHomeCopy.vue:243:clearTimeout(timer);",
+      "src/views/vent/monitorManager/windowMonitor/dandaoFcBd2.threejs.ts:219:clearTimeout(this.animationTimer);",
+      "src/views/vent/monitorManager/windowMonitor/dandaoFcLt.threejs.ts:203:clearTimeout(this.animationTimer);",
+      "src/views/vent/monitorManager/gateMonitor/gate.threejs.qd.ts:209:clearTimeout(this.animationTimer);"
+    ],
+    "onUnmounted": [
+      "src/views/vent/home/configurable/components/belt/CameraList.vue:75:onBeforeUnmount(() => {",
+      "src/views/vent/home/configurable/green/fire-green.vue:149:onUnmounted(() => {",
+      "src/views/vent/monitorManager/balancePressMonitor/components/balancePressHomeSP.vue:449:onUnmounted(() => {",
+      "src/components/vent/micro/needAir.vue:53:onBeforeUnmount(async () => {",
+      "src/views/vent/monitorManager/balancePressMonitor/index3.vue:173:onUnmounted(() => {});",
+      "src/views/vent/monitorManager/deviceMonitor/index.vue:97:onBeforeUnmount(() => {});",
+      "src/views/vent/monitorManager/deviceMonitor/index.vue:98:onUnmounted(() => {",
+      "src/views/vent/monitorManager/workFaceMonitor/components/workFaceVentHome.vue:333:onUnmounted(() => {",
+      "src/views/vent/gas/gasPumpMonitor/components/monitorChart.vue:64:onUnmounted(() => {",
+      "src/views/vent/monitorManager/compressor/index.vue:135:onUnmounted(() => {});",
+      "src/views/vent/home/configurable/ventWLML.vue:153:onUnmounted(() => {",
+      "src/views/vent/monitorManager/tunFaceMonitor/index.vue:198:onUnmounted(() => {",
+      "src/layouts/default/header/components/VoiceBroadcastGsd.vue:149:onUnmounted(() => {",
+      "src/views/vent/monitorManager/alarmMonitor/warn/waterLevel.vue:69:onUnmounted(() => {});",
+      "src/views/vent/monitorManager/mainFanMonitor/components/setValueSetting.vue:65:onUnmounted(() => {});",
+      "src/views/vent/monitorManager/compressor/components/nitrogenHome_lt.vue:213:onBeforeUnmount(() => {",
+      "src/views/vent/monitorManager/compressor/components/nitrogenHome_lt.vue:216:onUnmounted(() => {",
+      "src/views/vent/gas/gasPumpSetting/components/unitTable.vue:50:onUnmounted(() => {",
+      "src/views/vent/safetyList/index.vue:413:onUnmounted(() => {",
+      "src/views/vent/monitorManager/compressor/components/nitrogenHome_dltj.vue:738:onBeforeUnmount(() => {",
+      "src/views/vent/monitorManager/compressor/components/nitrogenHome_dltj.vue:741:onUnmounted(() => {",
+      "src/views/vent/monitorManager/workFaceMonitor/components/workFaceDustHome.vue:236:onUnmounted(() => {",
+      "src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHomeBD.vue:189:// onBeforeUnmount(() => {",
+      "src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHomeBD.vue:193:onUnmounted(() => {",
+      "src/views/vent/home/configurable/dustControl.vue:74:onUnmounted(() => {",
+      "src/views/vent/home/configurable/index.vue:96:onUnmounted(() => {",
+      "src/views/vent/home/configurable/fireWZ.vue:95:onUnmounted(() => {",
+      "src/views/vent/home/configurable/vent.vue:99:onUnmounted(() => {",
+      "src/views/vent/home/configurable/green/fusion-warn-green.vue:94:onUnmounted(() => {",
+      "src/views/vent/safetyList/common/detail-235.vue:1034:onUnmounted(() => {",
+      "src/views/vent/gas/gasZk/index.vue:174:onUnmounted(() => {",
+      "src/views/vent/monitorManager/dedustMonitor/components/DedustHome.vue:173:onUnmounted(() => {",
+      "src/views/vent/monitorManager/tunFaceMonitor/components/tunFaceGasHome.vue:244:onUnmounted(() => {",
+      "src/views/vent/monitorManager/fanLocalMonitor/index.vue:1761:onBeforeUnmount(() => {",
+      "src/views/vent/monitorManager/fanLocalMonitor/index.vue:1764:onUnmounted(() => {",
+      "src/views/vent/monitorManager/alarmMonitor/warn/gasWarn.vue:384:onUnmounted(() => {",
+      "src/views/vent/monitorManager/fanLocalMonitor/components/faultEchartLine.vue:285:onUnmounted(() => {",
+      "src/views/vent/monitorManager/gasPumpMonitor/index-copy.vue:189:onUnmounted(() => {",
+      "src/views/vent/monitorManager/tunFaceMonitor/components/tunFaceHome.vue:292:onUnmounted(() => {",
+      "src/views/vent/monitorManager/tunFaceMonitor/components/tunFaceVentHome.vue:312:onUnmounted(() => {"
+    ],
+    "inverted-return-perm": [
+      "src/views/vent/monitorManager/alarmMonitor/warn/waterLevel.vue:5:v-if=\"!hasPermission('waterLevel:return')\"",
+      "src/views/vent/monitorManager/alarmMonitor/warn/gasWarn.vue:5:v-if=\"!hasPermission('gasWarn:return')\"",
+      "src/views/vent/monitorManager/alarmMonitor/warn/deviceWarn.vue:5:v-if=\"!hasPermission('deviceWarn:return')\"",
+      "src/views/vent/monitorManager/alarmMonitor/warn/fireWarn.vue:4:<a-button v-if=\"!hasPermission('fireWarn:return')\" preIcon=\"ant-design:rollback-outlined\" type=\"text\" size=\"small\"",
+      "src/views/vent/monitorManager/alarmMonitor/warn/ventilateWarn.vue:10:v-if=\"!hasPermission('ventilateWarn:return')\"",
+      "src/views/vent/monitorManager/alarmMonitor/warn/dustWarn.vue:6:v-if=\"!hasPermission('dustWarn:return')\""
+    ],
+    "res-length": [
+      "src/views/vent/safetyList/index.vue:203:if (res.length != 0) {",
+      "src/views/vent/safetyList/common/detail-235.vue:601:// if (res.length != 0) {",
+      "src/views/vent/safetyList/common/detail-235.vue:779:if (res.length != 0) {",
+      "src/views/vent/safetyList/common/detail-235.vue:817:if (res && res.length != 0) {",
+      "src/views/vent/safetyList/common/detail-235.vue:1025:if (res && res.length != 0) {",
+      "src/views/vent/monitorManager/alarmMonitor/warn/gasWarn.vue:216:if (!res || res.length === 0) return;",
+      "src/views/vent/safetyList/common/detail-cl.vue:326:if (res.length != 0) {",
+      "src/views/vent/gas/gasInspectNonfc/components/inspectEdit.vue:56:if (res.length != 0) {",
+      "src/views/vent/gas/gasReport/index.vue:313:if (res.length != 0) {",
+      "src/views/vent/monitorManager/mainFanMonitor/index.vue:1080:if (res.length > 0 && data.length > 0) {",
+      "src/views/vent/gas/gasReport/index1.vue:180:if (res.length != 0) {",
+      "src/views/vent/gas/gasInspectNonfc/components/inspectEdit2.vue:59:if (res.length != 0) {",
+      "src/views/vent/gas/gasInspect/components/inspectEdit.vue:62:if (res.length != 0) {",
+      "src/views/vent/gas/gasInspect/components/inspectEdit.vue:75:if (res.length != 0) {",
+      "src/views/vent/monitorManager/airDoor/index.vue:56:if (res.length) {",
+      "src/views/vent/gas/gasInspectNonfc/components/inspectTask.vue:78:if (res.length != 0) {",
+      "src/views/vent/monitorManager/alarmMonitor/warn/fireWarn.vue:91:if (!res || res.length === 0) return;",
+      "src/views/vent/monitorManager/camera/index.vue:66:if (res.length != 0) {",
+      "src/views/vent/reportManager/index.vue:91:if (res.length != 0) {",
+      "src/views/vent/safetyList/common/detail-130.vue:642:// if (res.length != 0) {",
+      "src/views/vent/safetyList/common/detail-130.vue:820:if (res.length != 0) {",
+      "src/views/vent/safetyList/common/detail-130.vue:858:if (res && res.length != 0) {",
+      "src/views/vent/safetyList/common/detail-130.vue:1066:if (res && res.length != 0) {",
+      "src/views/vent/monitorManager/alarmMonitor/warn/ventilateWarn.vue:202:if (!res || res.length === 0) return;",
+      "src/views/vent/safetyList/common/operateRecord.vue:76:if (res.length != 0) {",
+      "src/views/vent/safetyList/common/operateRecord.vue:89:if (res && res.length != 0) {",
+      "src/views/vent/safetyList/common/detail.vue:735:if (res.length != 0) {",
+      "src/views/vent/safetyList/common/detail.vue:930:if (res.length != 0) {",
+      "src/views/vent/safetyList/common/detail.vue:979:if (res && res.length != 0) {",
+      "src/views/vent/safetyList/common/detail.vue:1193:if (res && res.length != 0) {",
+      "src/views/vent/monitorManager/alarmMonitor/warn/dustWarn.vue:199:if (!res || res.length === 0) return;",
+      "src/views/vent/gas/gasInspect/components/inspectTask.vue:78:if (res.length != 0) {"
+    ],
+    "mock-comment-api": [
+      "src/views/vent/safetyList/index.vue:190://       // await getSubStationList()",
+      "src/views/vent/safetyList/index.vue:191://       await getStationList()",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:223://   await mainWindObj.setSmokeDirection('front', 'tubPositivePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:226://   await mainWindObj.setSmokeDirection('front', 'tubInversePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:423:// await mainObj.setSmokeDirection('back', 'tubPositivePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:425://   await mainObj.setSmokeDirection('back', 'windowPositivePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:427://   await mainObj.setSmokeDirection('back', 'tubPositivePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:439:// await mainObj.setSmokeDirection('back', 'tubInversePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:441://   await mainObj.setSmokeDirection('back', 'windowPositivePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:443://   await mainObj.setSmokeDirection('back', 'tubInversePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:454:// await mainObj.setSmokeDirection('back', 'tubPositivePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:456://   await mainObj.setSmokeDirection('back', 'windowPositivePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:458://   await mainObj.setSmokeDirection('back', 'tubPositivePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:487://   await mainObj.setSmokeDirection('front', 'windowPositivePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:489://   await mainObj.setSmokeDirection('front', 'tubPositivePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:502://   await mainObj.setSmokeDirection('front', 'windowPositivePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:504://   await mainObj.setSmokeDirection('front', 'tubInversePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:517://   await mainObj.setSmokeDirection('front', 'windowPositivePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:519://   await mainObj.setSmokeDirection('front', 'tubPositivePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:547:// await mainObj.setSmokeDirection('back', 'tubPositivePath');",
+      "src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts:562:// await mainObj.setSmokeDirection('back', 'tubInversePath');",
+      "src/views/vent/safetyList/common/detail-235.vue:534:// await getSubStationList();",
+      "src/views/vent/safetyList/common/detail-235.vue:535:// await getStationList1();",
+      "src/views/vent/monitorManager/mainFanMonitor/mainWind.lj.threejs.ts:211:// await this.openOrCloseValve(deviceType, 'open', duration);",
+      "src/views/vent/monitorManager/mainFanMonitor/mainWind.lj.threejs.ts:228:// await this.openOrCloseValve(deviceType, 'close');",
+      "src/views/vent/monitorManager/mainFanMonitor/mainWind.lj.threejs.ts:233:// await this.openOrCloseValve(deviceType, 'close', 0);",
+      "src/views/vent/gas/gasPumpMonitor/index.vue:87:// await getDeviceList();",
+      "src/views/vent/monitorManager/mainFanMonitor/index.vue:1969:// await setModelType('mainWindRect');",
+      "src/views/vent/monitorManager/fanLocalMonitor/fanLocal.threejs.Two.ts:609:// await animateCamera(",
+      "src/views/vent/monitorManager/balancePressMonitor/components/balancePressHome2.vue:569://   await setModelType('balancePressTun'); //balancePressBase",
+      "src/views/vent/monitorManager/balancePressMonitor/components/balancePressHome2.vue:572://   await getMonitor(true);",
+      "src/views/vent/safetyList/common/detail-130.vue:507://       await getShowReadList();",
+      "src/views/vent/safetyList/common/detail-130.vue:565:// await getSubStationList();",
+      "src/views/vent/safetyList/common/detail-130.vue:566:// await getStationList1();",
+      "src/views/vent/monitorManager/mainFanMonitor/mainWind.xj.threejs.ts:214:// await this.openOrCloseValve(deviceType, 'open', duration);",
+      "src/views/vent/monitorManager/mainFanMonitor/mainWind.xj.threejs.ts:231:// await this.openOrCloseValve(deviceType, 'close');",
+      "src/views/vent/monitorManager/mainFanMonitor/mainWind.xj.threejs.ts:236:// await this.openOrCloseValve(deviceType, 'close', 0);",
+      "src/views/vent/monitorManager/groutMonitor/components/groutHomehlg.vue:112:// await getDataSource(props.deviceId);",
+      "src/views/vent/monitorManager/groutMonitor/components/groutHomehlg.vue:162:// await setModelType('groutBase');",
+      "src/views/vent/monitorManager/alarmMonitor/index.vue:550:// await getDataSource();"
+    ],
+    "hardcode-excel": [
+      "src/views/vent/monitorManager/fanLocalMonitor/index.vue:1554:m3: 525.87, //5.0测试数据",
+      "src/views/vent/monitorManager/fanLocalMonitor/index.vue:1566:m3: 525.87, //5.0测试数据",
+      "src/views/vent/monitorManager/mainFanMonitor/components/AnalysisTable.vue:3:<a-table :dataSource=\"mockData\" :columns=\"Warncolumns\" :scroll=\"{ y: 300 }\"> </a-table>",
+      "src/views/vent/monitorManager/mainFanMonitor/components/AnalysisTable.vue:63:const mockData = [",
+      "src/views/vent/reportManager/comment/DeviceModal.vue:200:fileType: props.fileType == 'doc' ? 'docx' : props.fileType == 'xls' ? 'xlsx' : props.fileType == 'ppt' ? 'pptx' : props.fileType, //当文件类型为d",
+      "src/views/vent/gas/gasInspect/index.vue:276:let filename = `${new Date().getTime()}.xlsx`;",
+      "src/views/vent/gas/gasInspect/index.vue:296:const blob = new Blob([content], { type: 'application/xlsx;charset=UTF-8' }); // 构造一个blob对象来处理数据",
+      "src/views/vent/gas/gasAssessment/gasAssessment.data.ts:577:export const mockData = {",
+      "src/views/vent/gas/gasReport/index.vue:328:let filename = searchData.reportTime + '.xlsx';",
+      "src/views/vent/gas/gasReport/index.vue:340://     let filename = searchData.reportTime + '.xlsx';",
+      "src/views/vent/gas/gasReport/index.vue:457:const blob = new Blob([content], { type: 'application/xlsx;charset=UTF-8' }); // 构造一个blob对象来处理数据",
+      "src/views/vent/gas/gasReport/index.vue:479:let filename = searchData.reportTime + '.xlsx';",
+      "src/views/vent/gas/gasReport/index.vue:547:const filename = searchData.reportTime + '.xlsx';",
+      "src/views/vent/monitorManager/mainFanMonitor/index.vue:1924:const fileName = `${record['strname'] || '风机实时监测数据'}_${new Date().getTime()}.xlsx`;",
+      "src/views/vent/gas/gasInspectNonfc/index2.vue:235:let filename = `${new Date().getTime()}.xlsx`;",
+      "src/views/vent/gas/gasInspectNonfc/index2.vue:253:const blob = new Blob([content], { type: 'application/xlsx;charset=UTF-8' }); // 构造一个blob对象来处理数据",
+      "src/views/vent/gas/gasAssessment/components/gasVideo.vue:39:import { gasUnitOption, headerBadges, mockGasUnitData, gasUnitBarOption, mockData, gasUnitPieOption, mockPieData } from '../gasAssessment.da",
+      "src/views/vent/home/configurable/components/content.vue:358:const mockData = clone(props.chartData) || [];",
+      "src/views/vent/home/configurable/components/content.vue:510:const data = cfg?.type == 'scatter' ? getData(mockData, cfg.readFrom, cfg.parser) : getData(refData, cfg.readFrom, cfg.parser);",
+      "src/views/vent/gas/gasReport/index1.vue:204:const filename = searchData.reportTime + '.xlsx';",
+      "src/views/vent/gas/gasReport/index1.vue:308:const blob = new Blob([content], { type: 'application/xlsx;charset=UTF-8' }); // 构造一个blob对象来处理数据",
+      "src/views/vent/gas/gasReport/index1.vue:330:let filename = searchData.reportTime + '.xlsx';",
+      "src/views/vent/gas/gasAssessment/components/gasEcharts.vue:59:import { gasUnitOption, headerBadges, mockGasUnitData, gasUnitBarOption, mockData, gasUnitPieOption, mockPieData } from '../gasAssessment.da",
+      "src/views/vent/monitorManager/balancePressMonitor/components/balancePressHome.vue:73:<template #container> <CustomChart :chart-config=\"PressO2Option\" :chart-data=\"mockData\" height=\"220px\" /> </template>",
+      "src/views/vent/monitorManager/balancePressMonitor/components/balancePressHome.vue:145:import { PressO2Option, mockData1 } from '../balancePressO2.data';",
+      "src/views/vent/monitorManager/balancePressMonitor/components/balancePressHome.vue:211:const mockData = ref<{ chartData: { dateTime: string; o2Val: string; pressureVal: string }[] }>({",
+      "src/views/vent/monitorManager/balancePressMonitor/components/balancePressHome.vue:258:mockData.value = { chartData };",
+      "src/views/vent/monitorManager/balancePressMonitor/components/balancePressHome2.vue:12::chartData=\"mockData\"",
+      "src/views/vent/monitorManager/balancePressMonitor/components/balancePressHome2.vue:26:import { PressO2Option, mockData1 } from '../balancePressO2.data';",
+      "src/views/vent/monitorManager/balancePressMonitor/components/balancePressHome2.vue:42:const mockData = ref<{ chartData: { dateTime: string; o2Val: string; pressureVal: string }[] }>({",
+      "src/views/vent/monitorManager/balancePressMonitor/components/balancePressHome2.vue:559:mockData.value = { chartData };",
+      "src/views/vent/home/configurable/firedoor/components/fireDoorBoard.vue:121:const useMockData = ref(false);",
+      "src/views/vent/home/configurable/firedoor/components/fireDoorBoard.vue:123:const mockData = ref([",
+      "src/views/vent/home/configurable/firedoor/components/fireDoorBoard.vue:147:return useMockData.value ? mockData.value : props.data;",
+      "src/views/vent/home/configurable/firedoor/components/fireDoorBoard.vue:235:if (useMockData.value) {",
+      "src/views/vent/bundleSpy/bundleSpyTable/index.vue:139:fileName: '10月2日色谱分析报表.xlsx',",
+      "src/views/vent/gas/gasAnalysis/index.vue:225:let filename = `${new Date().getTime()}.xlsx`;",
+      "src/views/vent/gas/gasAnalysis/index.vue:232:const blob = new Blob([content], { type: 'application/xlsx;charset=UTF-8' }); // 构造一个blob对象来处理数据",
+      "src/views/vent/gas/gasAssessment/components/workFace.vue:68:import { gasMonitor, gasPumpValve, gasUnitBarOption, mockData, gasUnitPieOption, mockPieData } from '../gasAssessment.data';",
+      "src/views/vent/gas/gasAssessment/components/gasControl.vue:71:import { gasUnitOption, headerBadges, monitorValveData, monitorPosData, mockData, gasUnitPieOption, mockPieData } from '../gasAssessment.dat"
+    ]
+  }
+}

+ 984 - 0
docs/_scope_map.json

@@ -0,0 +1,984 @@
+[
+  {
+    "idx": 1,
+    "name": "主风机管控",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue",
+      "vent/monitorManager/mainFanMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+      "src\\views\\vent\\monitorManager\\mainFanMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 2,
+    "name": "局部风机管控",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue",
+      "vent/monitorManager/fanLocalMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+      "src\\views\\vent\\monitorManager\\fanLocalMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 3,
+    "name": "测风装置管控",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue",
+      "vent/monitorManager/windrectMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+      "src\\views\\vent\\monitorManager\\windrectMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 4,
+    "name": "风门管控",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue",
+      "vent/monitorManager/gateMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+      "src\\views\\vent\\monitorManager\\gateMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 5,
+    "name": "风窗管控",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue",
+      "vent/monitorManager/windowMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue",
+      "src\\views\\vent\\monitorManager\\windowMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 6,
+    "name": "气压温湿度监测",
+    "declared": [
+      "vent/monitorManager/sensorMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\sensorMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 7,
+    "name": "风门集中同控 d",
+    "declared": [
+      "vent/monitorManager/airDoor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\airDoor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 8,
+    "name": "需风量分析系统",
+    "declared": [
+      "vent/micro/createSubApp.vue"
+    ],
+    "resolved": [
+      "src\\components\\vent\\micro\\createSubApp.vue"
+    ],
+    "status": "micro",
+    "notes": ""
+  },
+  {
+    "idx": 9,
+    "name": "通风参数一张表",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/staticSheets/ventilateSheet.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\staticSheets\\ventilateSheet.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 10,
+    "name": "通风能力核定",
+    "declared": [
+      "vent/micro/createSubApp.vue"
+    ],
+    "resolved": [
+      "src\\components\\vent\\micro\\createSubApp.vue"
+    ],
+    "status": "micro",
+    "notes": ""
+  },
+  {
+    "idx": 11,
+    "name": "130分站监测",
+    "declared": [
+      "vent/safetyList/common/detail-130.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\safetyList\\common\\detail-130.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 12,
+    "name": "158分站监测",
+    "declared": [
+      "vent/safetyList/common/detail.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\safetyList\\common\\detail.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 13,
+    "name": "235分站监测",
+    "declared": [
+      "vent/safetyList/common/detail-235.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\safetyList\\common\\detail-235.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 14,
+    "name": "通风系统监测",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 15,
+    "name": "通风网络解算",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 16,
+    "name": "关键通风路线",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 17,
+    "name": "实时网络解算",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 18,
+    "name": "控风方案决策",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 19,
+    "name": "多灾融合预警",
+    "declared": [
+      "vent/monitorManager/alarmMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\alarmMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 20,
+    "name": "通风监测预警",
+    "declared": [
+      "vent/monitorManager/alarmMonitor/warn/ventilateWarn.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\alarmMonitor\\warn\\ventilateWarn.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 21,
+    "name": "火灾监测预警",
+    "declared": [
+      "vent/monitorManager/alarmMonitor/warn/fireWarn.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\alarmMonitor\\warn\\fireWarn.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 22,
+    "name": "粉尘监测预警",
+    "declared": [
+      "vent/monitorManager/alarmMonitor/warn/dustWarn.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\alarmMonitor\\warn\\dustWarn.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 23,
+    "name": "瓦斯监测预警",
+    "declared": [
+      "vent/monitorManager/alarmMonitor/warn/gasWarn.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\alarmMonitor\\warn\\gasWarn.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 24,
+    "name": "灾变应急控制",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 25,
+    "name": "设备监测预警",
+    "declared": [
+      "vent/monitorManager/deviceMonitorWarn/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitorWarn\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 26,
+    "name": "工作面来压预警",
+    "declared": [
+      "vent/monitorManager/betKy/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\betKy\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 27,
+    "name": "预警联动控制",
+    "declared": [
+      "vent/deviceManager/workingFace/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\deviceManager\\workingFace\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 28,
+    "name": "光纤测温监测",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 29,
+    "name": "束管监测",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 30,
+    "name": "防火门监测",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 31,
+    "name": "密闭监测系统",
+    "declared": [
+      "vent/monitorManager/obfurageMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\obfurageMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 32,
+    "name": "喷淋监控系统",
+    "declared": [
+      "vent/monitorManager/sprayMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\sprayMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 33,
+    "name": "智能注氮系统",
+    "declared": [
+      "vent/monitorManager/compressor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\compressor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 34,
+    "name": "智能注浆系统",
+    "declared": [
+      "vent/monitorManager/groutMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\groutMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 35,
+    "name": "火灾监测预警",
+    "declared": [
+      "vent/monitorManager/alarmMonitor/warn/fireWarn.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\alarmMonitor\\warn\\fireWarn.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 36,
+    "name": "防灭火系统监测",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 37,
+    "name": "色谱仪报表分析",
+    "declared": [
+      "vent/bundleSpy/bundleSpyTable/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\bundleSpy\\bundleSpyTable\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 38,
+    "name": "束管日报分析",
+    "declared": [
+      "vent/bundle/bundleMonitorTable/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\bundle\\bundleMonitorTable\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 39,
+    "name": "瓦斯监测",
+    "declared": [
+      "layouts/default/index.vue"
+    ],
+    "resolved": [
+      "src\\layouts\\default\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 40,
+    "name": "瓦斯监测预警",
+    "declared": [
+      "vent/monitorManager/alarmMonitor/warn/gasWarn.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\alarmMonitor\\warn\\gasWarn.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 41,
+    "name": "抽采泵站管控",
+    "declared": [
+      "vent/monitorManager/gasPumpMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\gasPumpMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 42,
+    "name": "抽采达标评判",
+    "declared": [
+      "vent/gas/gasAssessment/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\gas\\gasAssessment\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 43,
+    "name": "抽采综合管控",
+    "declared": [
+      "vent/gas/gasHome/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\gas\\gasHome\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 44,
+    "name": "瓦斯抽采单元",
+    "declared": [
+      "vent/gas/gasAssessment/gasUnit.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\gas\\gasAssessment\\components\\gasUnit.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 45,
+    "name": "瓦斯管网管控",
+    "declared": [
+      "vent/gas/gasPipeNet/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\gas\\gasPipeNet\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 46,
+    "name": "抽采泵站监测",
+    "declared": [
+      "vent/gas/gasPumpMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\gas\\gasPumpMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 47,
+    "name": "瓦斯日报历史详情",
+    "declared": [
+      "vent/gas/gasReportInspect/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\gas\\gasReportInspect\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 48,
+    "name": "瓦斯钻孔监测",
+    "declared": [
+      "vent/gas/gasZk/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\gas\\gasZk\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 49,
+    "name": "瓦斯巡检设备",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 50,
+    "name": "瓦斯人工巡检报表管理",
+    "declared": [
+      "vent/gas/gasReport/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\gas\\gasReport\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 51,
+    "name": "瓦斯人工巡检地点管理(NFC)",
+    "declared": [
+      "vent/gas/gasInspect/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\gas\\gasInspect\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 52,
+    "name": "瓦斯人工巡检业务分析",
+    "declared": [
+      "vent/gas/gasAnalysis/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\gas\\gasAnalysis\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 53,
+    "name": "瓦斯人工巡检地点管理",
+    "declared": [
+      "vent/gas/gasInspectNonfc/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\gas\\gasInspectNonfc\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 54,
+    "name": "一张表",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/staticSheets/gasSheet.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\staticSheets\\gasSheet.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 55,
+    "name": "评判参数设置",
+    "declared": [
+      "vent/gas/gasPumpSetting/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\gas\\gasPumpSetting\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 56,
+    "name": "瓦斯日报分析",
+    "declared": [
+      "vent/gas/gasReportInspect/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\gas\\gasReportInspect\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 57,
+    "name": "喷雾降尘监控",
+    "declared": [
+      "layouts/RouteView.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\home\\configurable\\dust.vue"
+    ],
+    "status": "weak",
+    "notes": ""
+  },
+  {
+    "idx": 58,
+    "name": "除尘风机",
+    "declared": [
+      "vent/monitorManager/dedustMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\dedustMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 59,
+    "name": "粉尘监测预警",
+    "declared": [
+      "vent/monitorManager/alarmMonitor/warn/dustWarn.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\alarmMonitor\\warn\\dustWarn.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 60,
+    "name": "粉尘参数一张表",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/staticSheets/dustSheet.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\staticSheets\\dustSheet.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 61,
+    "name": "防尘系统监测",
+    "declared": [
+      "vent/monitorManager/deviceMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\deviceMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 62,
+    "name": "粉尘报表分析",
+    "declared": [
+      "vent/dust/dustMonitorTable/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\dust\\dustMonitorTable\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 63,
+    "name": "综采面智能管控",
+    "declared": [
+      "vent/monitorManager/workFaceMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\workFaceMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 64,
+    "name": "掘进面智能管控",
+    "declared": [
+      "vent/monitorManager/tunFaceMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\tunFaceMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 65,
+    "name": "防火门智能管控",
+    "declared": [
+      "vent/home/configurable/firedoor/fireDoorMonitor.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\home\\configurable\\firedoor\\fireDoorMonitor.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 66,
+    "name": "皮带巷管控系统",
+    "declared": [
+      "vent/monitorManager/beltTunMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\beltTunMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 67,
+    "name": "机电硐室管控",
+    "declared": [
+      "vent/monitorManager/chamberMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\chamberMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 68,
+    "name": "均压与低氧管控",
+    "declared": [
+      "vent/monitorManager/balancePressMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\balancePressMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 69,
+    "name": "机电硐室管控",
+    "declared": [
+      "vent/home/configurable/electroChamber.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\home\\configurable\\electroChamber.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 70,
+    "name": "压风管控系统",
+    "declared": [
+      "vent/monitorManager/nitrogen/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\nitrogen\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 71,
+    "name": "温度压差监测",
+    "declared": [
+      "vent/monitorManager/sensorMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\sensorMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 72,
+    "name": "安全监控系统",
+    "declared": [
+      "vent/monitorManager/safetyMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\safetyMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 73,
+    "name": "设备预警历史",
+    "declared": [
+      "vent/monitorManager/warningMonitor/index-mine.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\warningMonitor\\index-mine.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 74,
+    "name": "设备操作历史",
+    "declared": [
+      "vent/monitorManager/handlerMonitor/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\handlerMonitor\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 75,
+    "name": "视频墙",
+    "declared": [
+      "vent/monitorManager/camera/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\monitorManager\\camera\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 76,
+    "name": "文件共享中心",
+    "declared": [
+      "vent/performance/fileDetail/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\performance\\fileDetail\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 77,
+    "name": "报表管理",
+    "declared": [
+      "vent/reportManager/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\reportManager\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 78,
+    "name": "安全监控分站管理",
+    "declared": [
+      "vent/safetyList/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\safetyList\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 79,
+    "name": "安全监控分站管理详情",
+    "declared": [
+      "vent/safetyList/common/detail.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\safetyList\\common\\detail.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 80,
+    "name": "用户日志管理",
+    "declared": [
+      "monitor/log/index.vue"
+    ],
+    "resolved": [
+      "src\\views\\monitor\\log\\index.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  },
+  {
+    "idx": 81,
+    "name": "报表数据映射",
+    "declared": [
+      "vent/reportManager/comment/reportMap.vue"
+    ],
+    "resolved": [
+      "src\\views\\vent\\reportManager\\comment\\reportMap.vue"
+    ],
+    "status": "mapped",
+    "notes": ""
+  }
+]

+ 0 - 219
docs/ai-changes/20260929-audit-r4.md

@@ -1,219 +0,0 @@
-# 第四轮审计整改 · mky-vent-base(M1/M2/M3 重放 + R4-2/R4-4/R4-5)
-
-## ① 头部
-
-- **对照网址**:`http://39.97.59.228:8013/hrx/mky-vent-base/compare/master...fix/audit-r4-20260929`
-- **依据文档**:
-  - `tools/verify/代码审核-第四轮-mky-vent-base-20260929.md`(§1 R4-1/R4-2/R4-4/R4-5,§2 M1/M2/M3 重放落点)
-  - `tools/verify/第四轮-施工任务书-20260929.md` 第二章(工作令)
-  - `AGENTS.md` §七(§7.2 基座口径)/§九(改动说明随分支提交)
-- **分支**:`fix/audit-r4-20260929`(worktree `/root/VentAnaly60/.wt/mky-r4`)
-- **基线**:`upstream/master` = `46df977e`(团队 AI 助手文件层面重构后的新结构)
-- **日期**:2026-09-29(Asia/Shanghai)
-- **交付版本**:以分支 `git log` 为准(**不写死自指 hash**)
-- **状态**:已交付,已推送,**已 merge 更新后的 master 并通过全量构建**(2026-09-30,见 §⑨);未部署,未浏览器实测。
-
-## ② 提交清单
-
-一项一提交,提交信息均带编号(以 `git log 46df977e..HEAD` 为准):
-
-| # | 提交 | 内容 |
-|---|---|---|
-| 1 | `[Fix R4-M1] …` | 点选解读 SSE 补 `X-Access-Token`(重放 fix/base-agent-token M1,保留令牌可选语义) |
-| 2 | `[Fix R4-M2] …` | `throwStreamHttpError` 加 export;点选链路复用它替换裸 `throw new Error`;`formatClickError`(重放 M2) |
-| 3 | `[Fix R4-M3] …` | `/ventAI/api/me` + `getMe()` + 按 `is_admin` 隐藏技能/子智能体/思考级别入口(重放 M3;**该路径 2026-09-30 实测校正为 `/ventAI/api/model/me`,见 §⑩**) |
-| 4 | `[Fix R4-R4-4] …` | 删除 `Modal/src/index.less` 与 `antCss.less` 双份定义的关闭按钮宽度规则 |
-| 5 | `[Fix R4-R4-2] …` | `extractMarkdownLinks` 加 `MD_LINK_CACHE_MAX=200` LRU |
-| 6 | `[Fix R4-R4-5] …` | `ConversationNav` 滚动跟随缓存 wrappers 列表 |
-| 7 | `[Fix R4-R4-7/8] …` | `markdownSafe` 注释旧路径更正;`useChatScroll` 卸载时 `cancelAnimationFrame` |
-| 8 | `[Docs] …` | 本说明 + `docs/ai-changes/README.md` |
-| 9 | `[Fix R4-R4-5] …(复核 FR-1 收口)` | 缓存失效源补 `props.messages.length`(删 AI 消息场景)+ 注释/文档同步 |
-| 10 | `[Merge] …` | 合并上游 `origin/master`(5 个新提交,见 §⑨):3 处冲突解决 + 复验 |
-
-## ③ 改动面
-
-| 文件 | 改了什么 |
-|---|---|
-| `src/views/ventAI/dataPicker/api.ts` | M1:import `getToken`;`ssePost` headers 增 `'X-Access-Token': getToken()`。M2:import `throwStreamHttpError`;`!response.ok` 分支改 `await throwStreamHttpError(response)` |
-| `src/views/ventAI/dataPicker/useDataPicker.ts` | M2:import `StreamError`;新增 `formatClickError(e)`(429 带 retryAfter→分钟、413/404 兜底文案);catch 分支改 `error.value = formatClickError(e)` |
-| `src/views/ventAI/manageAssistent/api/client.ts` | M2:`throwStreamHttpError` 加 `export`(唯一硬阻塞点,函数体零改动)。M3:Api 枚举加 `me = '/ventAI/api/me'`;新增 `MeInfo` 接口与 `getMe()`(`errorMessageMode: 'none'`) |
-| `src/views/ventAI/manageAssistent/components/AiAssistantModal.vue` | M3:import `getMe`;新增 `isAdmin` ref(默认 false);新增 `fetchMe()`(失败置 false 不缓存);visible watch 的 fetch 组加 `fetchMe()`;`TaskListPanel`/`ChatInputArea` 各透传 `:isAdmin="isAdmin"` |
-| `src/views/ventAI/manageAssistent/components/panels/TaskListPanel.vue` | M3:props 加 `isAdmin?: boolean`;`optionItems` 由普通数组转 computed,按 `!isAdmin` 过滤 `skill`/`sub-agents` |
-| `src/views/ventAI/manageAssistent/components/chat/ChatInputArea.vue` | M3:props 加 `isAdmin?: boolean`(默认 false);`ThinkLevelPopup` **连同前面的 `.action-divider`** 包 `v-if="isAdmin"`。未改 `ThinkLevelPopup` 组件内部 |
-| `src/components/Modal/src/index.less` | R4-4:删除与 `antCss.less` 重复的 `.zxm-modal-close-x` 96px 规则块;保留本块独有的 `font-weight/outline` |
-| `src/views/ventAI/manageAssistent/lib/markdown.ts` | R4-2:`extractMarkdownLinks` 加 `MD_LINK_CACHE_MAX=200` FIFO LRU(与 `renderMarkdownCached` 同型) |
-| `src/views/ventAI/manageAssistent/components/chat/ConversationNav.vue` | R4-5:新增 `getWrappers()` 缓存(失效源=`qaPairs.length` + `props.messages.length` + O(1) `container.contains` 存活校验),`updateActivePairByScroll`/`scrollToPair` 改用它,rAF 合并不变 |
-| `src/utils/markdownSafe.ts` | R4-7(顺手):头部注释的旧路径 `chatModal/utils.ts` 更正为 `lib/markdown.ts`(纯注释) |
-| `src/views/ventAI/manageAssistent/hooks/useChatScroll.ts` | R4-8(顺手):`onBeforeUnmount` 时 `cancelAnimationFrame(scrollRafId)` |
-
-**冻结面零触碰**:`permissionGuard.ts` / `useAutoLogin.ts` / mock-login 相关、`.env`、`public/js/config.js` 均未改(`git diff 46df977e..HEAD --name-only` 已核,无命中)。
-
-## ④ 逐项说明
-
-### M1 点选解读 SSE 补 `X-Access-Token`(重放自我方 fix/base-agent-token M1)
-
-- **原来的行为**:`dataPicker/api.ts` 的 `ssePost` 只带 `Content-Type`,是基座唯一一条匿名调 TfAgents 的路径;消耗记到兜底账号 click-anonymous,无法按登录人记账。
-- **现在的行为**:headers 增 `'X-Access-Token': getToken()`,与对话链路(`client.ts:217/266/317`)同名同源。
-- **为什么这么改**:恢复按登录人记账归因(TfAgents 决策 D5 / B1-20)。
-- **与原来的差别**:仅多一个请求头。**令牌是「可选」语义:空令牌照发请求**(TfAgents 有 click-anonymous 兜底),**未加任何「未登录拦截/跳登录」判断**——这是用户已定的 D5 决策,若改成强制登录会把未登录场景直接变成不可用。
-- 注意:新 master 已把 SSE 读流收敛为 `consumeSse`(`api/sse.ts`),本项只补头,**没有**把旧分支的手写 SSE 循环带回来。
-
-### M2 复用 `throwStreamHttpError`/`StreamError`,429 变可读文案(重放 M2)
-
-- **原来的行为**:`!response.ok` 时裸 `throw new Error('HTTP错误: 429')`;`useDataPicker.ts` 一行 `e?.message || '请求失败'`,用户看不懂 429/413/404。
-- **现在的行为**:
-  1. `client.ts:70` 的 `throwStreamHttpError` 加 `export`(新结构导致的**唯一硬阻塞点**,函数体零改动;`StreamError` 本就导出);
-  2. `dataPicker/api.ts` 改 `await throwStreamHttpError(response)`——解析响应体 `detail`(字符串或 `{code,message,retry_after,limit_tokens,spent_tokens}`),抛出带 `status/retryAfter` 的 `StreamError`;
-  3. `useDataPicker.ts` 新增 `formatClickError(e)`:429 带 `retryAfter` 换算分钟(不足 1 分钟显示「不到 1 分钟」);413 服务端无文案时兜底「附件体积超过上限…」;404 兜底「会话不存在或无权访问」;普通 Error 一律走原 fallback,行为不回归。
-- **为什么这么改**:TfAgents 已返回结构化错误体(D6/B1-15/B1-19/B1-08),且与对话链路共用一套解析器,避免两份实现各自漂移。
-- **与原来的差别**:错误展示从 `HTTP错误: xxx` 变为可读文案;其余链路(consumeSse 流处理、成功路径)不变。
-
-### M3 `/ventAI/api/model/me` + `getMe()` + 按 `is_admin` 隐藏全局变更入口(重放 M3;路径经 2026-09-30 实测校正,见 §⑩)
-
-- **原来的行为**:所有角色都能看到并点击「技能」「子智能体」管理入口与「思考级别」切换;非管理员点了才被后端 403。
-- **现在的行为**:
-  1. `client.ts`:Api 枚举加 `me`;新增 `MeInfo` + `getMe()`(走 defHttp,axios 拦截器自动带令牌;`errorMessageMode:'none'` 静默失败,防接口未上线时每次开弹框都弹红错)。
-  2. `AiAssistantModal.vue`:新增 `isAdmin` ref(默认 false)与 `fetchMe()`;visible watch(`immediate: true`)的 fetch 组每次打开都调 `fetchMe()`;向 `TaskListPanel`、`ChatInputArea` 透传 `:isAdmin`。
-  3. `TaskListPanel.vue`:`optionItems` 转 computed,`!isAdmin` 时过滤 `skill`/`sub-agents`;「新建任务/定时任务」保持开放(属个人功能)。
-  4. `ChatInputArea.vue`:`ThinkLevelPopup` **连同前面的 `.action-divider`** 一起包 `v-if="isAdmin"`(不连分隔线会残留竖线);思考级别的**读取**(getModelMsg)不受影响,对所有人可用。
-- **为什么这么改**:TfAgents 已把这三类全局变更端点收归管理员(用户定案方案 B:后端返回 is_admin,前端隐藏入口)。
-- **与原来的差别**:仅入口可见性收敛;**`fetchMe` 失败置 `isAdmin=false` 且不缓存**(每次打开重新拉取,管理员名单可能在服务端变更)——这是历史浏览器验收踩过的坑(失败停留 false 导致管理员看不到入口),已原样保留。**未改 `ThinkLevelPopup` 组件内部**(可能被别处复用)。旧分支的 `adminConfigured` ref 在模板中从未消费(死状态),本次未搬运。
-
-### R4-4 关闭按钮规则去重
-
-- **原来的行为**:`Modal/src/index.less:77-87` 与 `design/vent/antCss.less:201-209` 双份定义同一条 `.zxm-modal-close-x { 96px }` 规则(f4a47a3c 为修生产错位写了两遍),加载顺序不同会互相覆盖。
-- **现在的行为**:删组件层重复块,保留 `antCss.less` 一份(其注释说明了用后代选择器提升优先级的意图);`index.less` 该块独有的 `font-weight: normal; outline: none;` 保留。
-- **为什么这么改**:单一事实源,避免将来改一处漏一处。
-- **与原来的差别**:编译产物 CSS 少一份同规则;选择器与值不变,渲染行为等价(less 编译实测见 §⑥)。
-
-### R4-2 `extractMarkdownLinks` 加 LRU
-
-- **原来的行为**:每次调用都 `marked.lexer` 全量解析;流式期间胶囊 computed 每个 token 全量重扫所有 AI 消息,CPU 随会话长度线性放大。
-- **现在的行为**:加 `MD_LINK_CACHE_MAX=200` 的 FIFO Map(与 `renderMarkdownCached` 同型),以原文为 key;唯一消费方 `lib/capsule.ts:61` 只读遍历,返回数组被缓存共享安全。
-- **为什么这么改 / 差别**:流式期每 token 的链接提取从 O(全部 AI 消息全文) 降为 O(1)(未变化消息全部命中);缓存有上限,内存代价 ≤ 几十 KB。
-
-### R4-5 滚动跟随 wrappers 缓存
-
-- **原来的行为**:`updateActivePairByScroll` 在滚动 rAF 回调里每帧 `querySelectorAll('.message-wrapper')` 全量查询(>100 条时每帧 O(n))。
-- **现在的行为**:查询结果缓存为数组,失效源=**「消息对数量 + 消息条数」**(`watch([qaPairs.length, props.messages.length])`);另加 O(1) 存活校验(`container.contains` 首个 wrapper),防 DOM 整批替换(如切换任务同问答对数)后用到游离节点。`scrollToPair` 的点击路径也复用。rAF 合并不变。
-- **与原来的差别**:滚动期每帧省一次 O(n) DOM 查询;判定逻辑与选中行为不变。
-- **复核后修正(FR-1)**:初版只 watch `qaPairs.length`,而它只随 **user** 消息数变化——删除 AI 消息(`ChatMessages.vue` 的 delete 按钮)不会改它,缓存会陈旧(外观级、可自愈)。已把 `props.messages.length` 并入失效源并同步注释。
-
-### R4-7 / R4-8(顺手项)
-
-- R4-7:`utils/markdownSafe.ts` 头部注释引用的 `chatModal/utils.ts` 已不存在(重构迁至 `lib/markdown.ts`),纯注释更正,无行为变化。
-- R4-8:`useChatScroll` 的 `scrollRafId` 原仅在重入时 cancel 自身,卸载后不 cancel(残留回调全 optional chaining,无崩溃);现 `onBeforeUnmount` 对称 cancel。该 composable 仅在 `AiAssistantModal` setup 内调用,钩子等价。
-
-## ⑤ 验收证据
-
-- **静态解析/ Lint**:全部通过,见 §⑥。
-- **less 编译行为等价实测**:对改动后的 `Modal/src/index.less` 用仓库 `less@4.7.0` 编译(缺失的全局变量按 `generateModifyVars` 语义打桩),输出 CSS 中 `.zxm-modal-close-x` 规则已消失、`font-weight: normal` 保留 → 删除的是与 antCss.less 完全相同的选择器+值,渲染等价。
-- **引用完整性自查**:`getToken`(`/@/utils/auth`)、`throwStreamHttpError`/`StreamError`/`getMe`(`/@/views/ventAI/manageAssistent/api/client`)均为真实导出;`AiAssistantModal → TaskListPanel/ChatInputArea` 的 `:isAdmin` 透传与两侧 props 声明一一对应;`formatClickError` 在 `handleClick` 之前定义(函数声明提升亦成立)。
-- **未验证项(如实)**:
-  - **未跑 `vite build`**(NAS 16G 悬崖边,硬性禁止;由 Lead 统一走远程构建)。
-  - **未浏览器实测**:M1 令牌头实际到达 TfAgents、M2 的 429/413/404 真实文案、M3 管理员/非管理员入口显隐、R4-5 滚动跟随手感——均为静态推演 + 旧分支既有验收结论外推(旧分支 M1/M2/M3 曾浏览器验收通过)。
-  - `extractMarkdownLinks` 缓存命中收益未跑 profiler 量化(结构推导可靠)。
-
-## ⑥ 静态检查
-
-在 `.wt/mky-r4` 执行(node_modules 为指向主检出的只读软链):
-
-```bash
-# 1) .ts 解析(仓库自带 @babel/parser@7.29.7,经由 .pnpm 路径 createRequire)
-node /tmp/parse-ts.mjs <changed .ts files>
-# 结果:6 个 .ts 全部 OK(exit 0)
-
-# 2) .vue 解析(仓库自带 @vue/compiler-sfc@3.5.40 的 parse + compileScript)
-node /tmp/parse-vue.mjs AiAssistantModal.vue TaskListPanel.vue ChatInputArea.vue ConversationNav.vue
-# 结果:4 个 .vue 全部 OK(exit 0)
-
-# 3) ESLint(eslint@8.57.1,仓库 .eslintrc.js)
-node_modules/.bin/eslint --ext .ts,.vue <全部 10 个改动文件>
-# 结果:exit 0,0 error / 0 warning(-f json 复核:10 文件 messages=0)
-
-# 4) less 编译抽查(R4-4 行为等价)
-less.render(index.less, modifyVars 打桩)  # 输出无 modal-close-x、保留 font-weight → OK
-```
-
-- 既有问题说明:本次 eslint 目标文件零告警,**未发现需要标注的既有问题**;`--ext` 参数与直接文件列表混用时 eslint 仅处理匹配文件,已用 `-f json` 复核确认真处理了目标文件。
-- 未跑 `vite build`(任务书明令禁止,8092 构建属 Lead 收口步骤)。
-
-## ⑦ 已知取舍
-
-1. **M3 未搬运 `adminConfigured`**:旧分支该 ref 在模板中从未消费(死状态),按最小改动原则未重放。
-2. **R4-5 存活校验**:除 `qaPairs.length` 失效外,额外保留 O(1) 的 `container.contains` 校验,覆盖「切换任务后问答对数恰好相同」的边角——比审计建议略保守,无行为风险。
-3. **R4-2 只做了 `extractMarkdownLinks` LRU**:胶囊 computed 每 token 全量重扫 messages 的**外层循环**仍在(收益主要在免除最重的 lexer);按消息对象缓存 links 的更大改动未做(改动面与回归面变大,收益递减)。
-4. **未做项(仅记录,不属本批)**:R4-3(ThinkingCard 每卡 1s interval 空转)、R4-6(TaskListPanel `:key="index"`)——审计定级 🟢 且明确"非本轮施工/可延后"。
-5. **未推送/未部署**:按任务书,push / deliver / 部署由 Lead 复核后统一执行。
-
-## ⑧ 交付状态与上游同步
-
-- **分支**:`fix/audit-r4-20260929`(worktree `/root/VentAnaly60/.wt/mky-r4`),基线 `upstream/master = 46df977e`,无分叉冲突项。
-- **提交**:7 个功能提交 + 1 个 `[Docs]` 提交(清单见 §②,hash 以 `git log` 为准)。
-- **上游同步**:分支自 `upstream/master` 新起(`46df977e`);**2026-09-30 已 merge 更新后的 `origin/master`**(+5 提交,含 API 按域拆分重构、dxf 支持、文件预览修复、导航条缓存),冲突与复验见 §⑨。
-- **推送/部署**:未 push、未 deliver、未部署(任务书 §〇.2,由 Lead 收口)。
-- **对照页**:`http://39.97.59.228:8013/hrx/mky-vent-base/compare/master...fix/audit-r4-20260929`(推送后由 Lead 核验 HTTP 200)。
-
-## ⑨ 合并新 master(2026-09-30)与复验
-
-审计分支交付后,上游 `origin/master` 又前进 5 个提交(`83eb0a1e` 来源卡片默认折叠、`e7e89214` 导航条逻辑+警告修复、
-`6de78411` API 按域拆分重构、`5f41a3be` dxf 支持、`64cfbb1a` 文件预览修复)。本次将 `origin/master` 合入分支,
-逐项核对「审计修改在新结构上是否仍然成立」。
-
-**取值原则**:AI 助手相关代码冲突时**以审计分支为主**(M1/M2/M3/R4-* 的审计语义一律保留);
-上游若已删除/重构掉载体文件,则把审计逻辑**平移**到新结构(见下表 `api/client.ts` 一行),不改审计行为。
-核对方式:对每个审计改动文件比对 `git grep -c "审计-第四轮"` 在审计分支与合并结果上的计数,10 个文件全部一致。
-
-### 冲突解决(3 处)
-
-| 文件 | 冲突 | 解决 |
-|---|---|---|
-| `api/client.ts` | 上游删除(拆分成 `api/*.ts`)vs 本分支修改(M2 加 export + M3 `getMe`) | 接受删除。M2 的「加 export」在新结构中**天然满足**(`throwStreamHttpError`/`StreamError` 已是 `api/errors.ts` 的导出);M3 的 `MeInfo`/`getMe()` 迁到新建 `api/user.ts`(按 barrel 约定 `export * from './user'`) |
-| `components/AiAssistantModal.vue` | 双方改同一 import 行 | 取上游的 barrel 路径并补 `getMe`:`import { …, StreamError, getMe } from '.../api'` |
-| `hooks/useChatScroll.ts` | 双方各加了 `onBeforeUnmount` 取消 rAF(R4-8 与上游重构撞车) | 两边等价(上游为 destroyOnClose 语义,本分支为 R4-8),按**冲突以审计分支为主**取审计版本(`if (scrollRafId !== null)` + R4-8 注释) |
-
-### 其他适配(非冲突但必须改)
-
-- `dataPicker/api.ts`、`dataPicker/useDataPicker.ts`:`throwStreamHttpError`/`StreamError` 的 import 由已删除的
-  `api/client` 改为 `api/errors`;注释中的旧路径一并更正(与 R4-7 同类)。
-- 自动合并文件复核结论:`ConversationNav.vue`(R4-5 的 `getWrappers` 缓存与上游 `toPlainTextCached` 共存,
-  失效源仍为「qaPairs.length + props.messages.length」FR-1 收口版)、`ChatInputArea.vue`/`TaskListPanel.vue`
-  (M3 的 `isAdmin` 过滤/隐藏完整)、`lib/markdown.ts`(R4-2 LRU 完整)。
-
-### 复验证据
-
-- **ESLint**:11 个审计改动文件,关闭 `prettier/prettier` 后 **0 error / 0 warning**。
-  (prettier 报的 3422 处 `Delete ␍` 是 Windows 检出 CRLF 环境噪声——对照组:本次未触碰的 `api/chat.ts` 同样报 161 处。)
-- **TypeScript**:`.tmp-tsconfig.json`(scoped:`ventAI/**` + `dataPicker/**` + `utils/markdownSafe.ts`)编译,
-  **ventAI 相关 0 error**;其余 61 处均为既有问题(`store/`、`router/`、`utils/http/` 等,与本次无关)。
-- **`vite build`**:exit 0(全量生产构建)。
-- **产物核对**:`/ventAI/api/me`(M3)、`X-Access-Token`(M1,`useDataPicker` chunk)、
-  `formatClickError` 的 429/413/404 分支文案(M2)、`ConversationNav` chunk(R4-5)均出现在 dist;
-  R4-4 在编译后 CSS 中 `.zxm-modal-close-x{…width:96px…}` **仅剩 1 处**(`antCss.less` 一份,组件层重复已消失)。
-- **未做(如实)**:浏览器实测与连后端联调(无 TfAgents 环境);M1 令牌头到达后端、M2 真实 429/413/404
-  文案、M3 管理员/非管理员入口显隐仍待浏览器验收。
-
-## ⑩ M3 接口路径校正与浏览器实测(2026-09-30)
-
-在 dev(`:3100`)+ 部署后端上做了浏览器实测,发现 **M3 的 me 路径与部署不一致,已修正**:
-
-- **原**:`api/user.ts` 用 `/ventAI/api/me`(审计文档 §④ 当时写的契约是 "GET /api/me")。
-- **实测**:dev 代理会剥掉 `/ventAI` 前缀,实际请求 `/api/me` —— 部署后端返回 **404**;
-  该后端 OpenAPI 中实际提供的是 **`/api/model/me`**(描述与 `MeInfo` 契约一致:
-  `{"username": str, "is_admin": bool, "admin_configured": bool}`),带令牌请求 **200**。
-- **修正**:`me = '/ventAI/api/model/me'`,`getMe()` 注释同步更新(补 admin_configured 语义)。
-
-**实测结果(当前部署)**:
-
-- `/api/model/me` → `{"username":"admin","is_admin":false,"admin_configured":false}`(管理员账号也是 false);
-- 管理端点 `POST /api/subagents/{name}/toggle` → **403 `admin_not_configured`**:
-  "管理员未配置(TFAGENTS_ADMIN_USERNAMES 为空),全局变更端点对所有人拒绝;请运维在 .env 配置
-  TFAGENTS_ADMIN_USERNAMES 后重启"。
-
-即:**当前部署下前端隐藏入口与后端"对所有人拒绝"是一致的**,不是前端过严;要让管理员看到
-「技能 / 子智能体 / 思考级别」入口,需 TfAgents 侧配置 `TFAGENTS_ADMIN_USERNAMES` 并重启。
-
-**其余实测(隐藏分支)**:左侧仅「新建任务 / 定时任务」、输入区无思考级别控件、打开弹框无红色报错
-(`errorMessageMode:'none'` 生效);对话 SSE 流式、链接胶囊(R4-2)、导航条点击跳转与滚动跟随(R4-5)、
-关闭弹框(R4-4/R4-8)均已在浏览器中跑通。M1/M2 的点选解读因该环境主风机页无设备数据、无行可点,
-UI 触发未能实测(运行模块经 dev server 取源码确认已含 M1/M2 改动)。

+ 0 - 43
docs/ai-changes/README.md

@@ -1,43 +0,0 @@
-# AI 改动说明(ai-changes)
-
-本目录存放**由 AI 协助完成的改动**的说明文档,目的是让人工审核在 Gogs 的「改动对照」页面上
-就能直接看到「改了什么 / 为什么这么改 / 与原来的差别 / 怎么验收的 / 有什么已知取舍」,
-不必再去翻外部文档。
-
-## 为什么要放进分支里
-
-改动说明**必须随改动一起提交到分支**,这样它才会出现在改动对照(compare)页面里:
-
-```
-http://39.97.59.228:8013/hrx/<repo>/compare/master...<分支名>
-```
-
-放在仓库外面的说明文件(例如 `/root/TFAgents/docs/...`)不会出现在对照页,审核人看不到。
-
-## 命名约定
-
-```
-docs/ai-changes/<YYYYMMDD>-<简短主题>.md
-```
-
-- 日期用**改动/交付当天**的日期(本地时区 Asia/Shanghai),便于按文件名排序找到最新一批;
-- 主题用 ASCII 短横线连接的小写单词(例:`agent-token`、`sec-cleanup`、`perf-load-v5`);
-- 同一天同一分支多次追加说明时,直接**更新同一个文件**,不要新建(避免同一批改动出现多份说明)。
-
-## 每份说明应包含
-
-1. **头部**:对照网址、依据文档、分支、基线、日期、交付版本、状态(是否已验收/已交付/已部署);
-2. **提交清单**:本次分支上有哪些提交(功能提交 + merge 提交);
-3. **改动面**:涉及哪些文件、各改了什么;
-4. **逐项说明**:每项都写清「原来的行为 / 现在的行为 / 为什么这么改 / 与原来的差别」,
-   关键取舍(例如「令牌是可选语义,不能改成强制登录」)必须写明,便于人工判断有没有改错业务形态;
-5. **验收证据**:怎么验的、结果如何、失败项的实际现象;**验收中发现并修复的缺陷要单独标出**;
-6. **静态检查**:lint / 类型检查的命令与结果,以及**哪些是既有问题**(避免被误认为新引入);
-7. **已知取舍**:明确写清未做的部分与原因,不要让审核人以为漏了;
-8. **交付状态与上游同步**:fork / 主仓库 / 是否 merge 了最新 `upstream/master` / 是否部署。
-
-## 已有说明
-
-| 日期 | 文件 | 分支 | 主题 |
-|---|---|---|---|
-| 2026-09-29 | `20260929-audit-r4.md` | `fix/audit-r4-20260929` | 第四轮审计整改:M1/M2/M3 重放 + R4-2/R4-4/R4-5 + R4-7/R4-8 顺手项(2026-09-30 追记 §⑨:合并新 master 的冲突解决与复验) |

+ 0 - 203
docs/configurable/README.md

@@ -1,203 +0,0 @@
-### 可配置首页文档及开发指引
-
-可配置首页是一个能让用户在线上进行灵活变更的首页,它可以在配置正确的布局、模块后展示它们,提供了更为低成本的维护方式。
-
-## 配置详解
-
-具体的配置字段及注释请见 _/views/vent/deviceManager/configurationTable/types.ts_
-
-## 目录结构
-
-_/configurable_
-
---_xxx.vue_ 各个首页的具体文件,每个首页需要一个文件来承载它们的子模块,放置特殊的装饰以及实现特定的功能。例如 dustBD 即为保德煤矿专用的粉尘首页。
-
---_mock.ts_ 提供一些模拟数据
-
---_configurable.data.ts_ 该文件及类似命名的文件用来提供测试用的首页具体配置,测试通过后可以依据此配置上线。
-
---_configurable.api.ts_ 获取数据的 api 都在这里维护。
-
---_/hooks_ 包含了可供各个首页、子模块核心组件复用的 setup 内容,包括初始化配置、初始化选项等。
-
---_/components_
-
-----_/detail_ 包含了各个细分模块的具体组件,例如列表、表格、告示板、画廊、时间线等等组件,它们根据自己的 prop 实现自己的内容。
-
-----_/enhanced_ 包含了新版子模块的各样组件,例如底部特化、边缘特化等,子模块内由上述的细分模块组成。
-
-----_/original_ 包含了原版子模块的组件,例如底部特化、边缘特化等,子模块内由上述的细分模块组成。
-
-----_/preset_ 包含了预设类细分模块的具体组件,预设类指的是较为复杂,难以可配置化的组件,通常仅需提供数据,即首页配置内指向的数据。
-
-----_ModuleXXX.vue_ 各个子模块的具体组件,子模块通常负责边框、Header、Content的布局,样式在这里维护。特殊情况下更为底层的实现也可以在这里。
-
-----_content.vue_ 内容组件,核心组件之一,这个组件负责将首页配置转换为可以供细分模块使用的 prop,并且引用所有细分模块,负责它们的布局。
-
-----_header.vue_ 头部组件,核心组件之一,这个组件负责将首页配置的 Header 项实现为下拉框,并向外提供了获取所选内容的方法,供其他与 content 类同的组件使用。
-
-## 开发人员开发流程
-
-从开发一个新的首页开始,需要的步骤可能如下:
-
-1、新建 xxx.vue 作为该首页的具体文件,引入 hooks 里的初始化方法,并对配置进行初始化,初始化数据与页面类型编码即 pageType。
-
-2、根据页面类型编码向项目的数据字典里添加新项,code 在配置表处定义(目前为 configurable_homepage),字典内容为上述的 pageType。
-
-3、参照已有的首页文件,将需要支持的子模块类型加入其中,添加必要的装饰并提供子模块需要的 prop。
-
-4、新建 configurable.data.ts 或类同的文件,根据先前的文件配置,写出一份适用于本首页的配置来。
-
-5、测试完毕后提交代码、打包项目,部署后在线上的首页配置页面将写好的配置以JSON写入即可。
-
-如果需要开发新的细分模块,那么基本流程如下,更详细的内容见另一章节:
-
-1、在配置类型声明处添加子项,可参考 chart、table、list、board 项的结构。
-
-2、开发细分模块,将其放置在 _/components/detail_ 下。
-
-3、根据细分模块的 prop,在 content.vue 下参考已有实现将新的细分模块标识添加进处理结构中,添加能把配置转换为 prop 的逻辑,同时添加该细分模块至相关布局代码中。
-
-4、测试修改并提交。
-
-如果是子模块,那么参考 ModuleEnhanced.vue 即可。
-
-## 运维及字典相关配置
-
-1、配置可配置首页配置页面,组件名:_vent/deviceManager/configurationTable/index_,参考菜单:_/configurable/table_,参考页面位置:设置-首页管理。
-
-2、配置可配置首页依赖的字典,进入字典配置页面,添加 configurable_homepage 字典项,参考名称:可配置首页。
-
-3、在 2 中添加的字典里添加子项,添加的子项具体名称需要和相关同事确认,参考字典:vent(通风首页)、gas(瓦斯首页)、dust(粉尘首页)、vent_realtime(通风首页-实时数据版)等。
-
-4、配置可配置首页,组件名:_vent/home/configurable/xxx_(需要和开发同事确认),参考菜单:将需要替换的首页菜单配给可配置首页。
-
-5、进入可配置首页配置页面,点击导入,将开发同事给的配置导入即可,后续调整可以点击帮助按钮查看配置细则,更为复杂的配置建议交与开发同事。
-
-## 配置详解
-
-我们从一张最常用的通风可配置首页 vent182.vue 开始,这个页面的绝大多数配置都是很好的例子。同时,这份文档只针对与目录结构章节提到的标准内容,对于任何基于此分发的版本不做讨论。
-
-这是一个常用模块的样子![alt text](image.png),这个模块在页面中存在若干个组成整体页面,页面则类似![alt text](image-1.png);一个模块通常包括以下几部分内容:
-
-1、模块的外边框
-
-2、模块的标题
-
-3、模块的设备选择框
-
-4、模块的主体
-
-5、模块的背景
-
-让我们逐一介绍,比对配置和代码!
-
-### 模块的外边框
-
-模块外边框在配置中对应值为![alt text](image-2.png);在代码中的应用实例为![alt text](image-3.png)
-
-目前模块外边框通常是在页面中自己定义的,因为多数页面的外边框是既定的且不同页面有不同边框,所以配置中所提供的外边框配置项仅有少部分页面进行了实际应用,如果需要修改外边框样式,建议直接修改页面中导入的边框。
-
-一个外边框组件负责根据配置渲染模块边框(有短边框,长边框,异形框等),同时边框内设备选择器和主体部分可能需要额外的客制化样式,你可以在这里处理。你可以用 _/src/views/vent/home/configurable/components/ModuleCommon.vue_ 做参考,它们的结构类似:
-
-```vue
-<template>
-  <Transition>
-    <BorderComponent>
-      <Title>标题在此</Title>
-      <Header>设备选择器,可以使用已有的hooks编写逻辑,也可以用标准的Header组件与Content交互</Header>
-      <Content>主体,只建议使用标准的Header组件</Content>
-    </BorderComponent>
-  </Transition>
-</template>
-<script>
-  // extract config and data form props
-</script>
-```
-
-### 模块标题
-
-略
-
-### 模块设备选择框
-
-模块设备选择框是模块中非常重要的部分,它负责展示设备列表,并允许用户选择设备。负责将选择到的数据向外传递,页面传递过来的数据和配置里的模拟数据通常都在这里处理。你可以参考 _/src/views/vent/home/configurable/components/header.vue_ 文件,简要地来说它的工作流程如下:
-
-1、根据Props传入的页面数据进行初始化
-
-2、根据Props传入的配置将数据进行处理
-
-3、渲染内容并提交事件,将选中的设备提交
-
-其中,数据处理时,如果配置上提供了模拟数据(mock)那么模拟数据会优先使用,然后走数据处理逻辑,会根据配置是否需要显示设备选择器进行进一步处理,现假设传入数据为`{ a: 123, b: [1,2,3] }`:
-
-1、如果需要设备选择器,则会根据`Config.deviceType`、`Config.moduleData.header`筛选上述数据,然后将设备数组初始化并默认选择第一个设备,假设配置了`Config.deviceType = 'b'`,那么最终设备数据为`[1,2,3]`,首个设备数据为`1`。组件将把`1`提交出去交给主体。
-
-2、如果不要设备选择器,则会把上述数据转化为一个设备,那么最终设备数据为`[{ a: 123, b: [1,2,3] }]`,首个设备数据为`{ a: 123, b: [1,2,3] }`。组件将把前面的数据提交给主体。
-
-### 模块主体
-
-模块主体部分是模块中最重要的部分,它负责实现主体子模块布局,展示设备数据,展示背景。处理 ![alt text](###模块设备选择框) 传递过来的数据。你可以参考 _/src/views/vent/home/configurable/components/content.vue_ 文件,简要地来说它的工作流程如下:
-
-1、处理传入的主体配置即`Config.moduleData`和设备数据,将数据转化为主体子模块需要的格式
-
-2、渲染主体布局、背景以及子模块,这两张图片是很好的例子![alt text](image-4.png)、![alt text](image-5.png)
-
-3、根据`<template>`的判断,显示指定的子模型并传递数据
-
-### 模块的子模块
-
-模块的子模块是显示信息的最小组件,开发者需要在这个级别的组件里接收从![alt text](###模块主体)传递过来的数据,并根据数据渲染内容。你可以参考 _/src/views/vent/home/configurable/components/detail/MiniBoard.vue_ 文件,简要地来说它的工作流程如下:
-
-1、根据传入的数据渲染主体文本、图表等内容
-
-2、根据样式文件,针对不同的`Props.type`渲染不同的样式
-
-子模块的工作非常简单,也很容易理解。但是简单意味着支持的复杂度不高,它们分为几个大类,但都只能胜任简单的展示任务,如果需要复杂的展示任务,那么你需要看看`Preset`!预设类组件可以自定义实现并实现复用,你可以参考 _/src/views/vent/home/configurable/components/preset/QHCurve.vue_ 文件,这是一个根据数据渲染风量风压曲线图的组件。要使用这些`Preset`,首先要确认所用的![alt text](###模块主体)中是否注册了该组件,你需要导入并注册该组件。然后在配置中把配置项写入`Config.moduleData.preset`下并在`Config.moduleData.layout`下使用注册名称排版。图片说明参考![alt text](image-6.png)、![alt text](image-7.png)
-
-### 常见问题
-
-#### 为什么我配置写好了却没有数据展示在上面呢?
-
-A:阅读上面的文档后检查一下,是不是没有正确地配置。具体的检查步骤参考:
-
-- 检查`Config.deviceType`是否需要配置,仅在需要设备选择器的时候配置该项!
-- 检查`Config.moduleData.header`配置是否正确,在需要设备选择器时,确保`readForm`指向了可读数组!
-- 检查`Config.moduleData.mock`是否配置,即使是空对象也会被认为是存在模拟数据,请确保模拟数据的正确性!
-- 检查控制台是否存在可配置首页下的报错,如有报错应当立即处理,否则会影响后续模块读取数据!
-
-#### 为什么我配置好了,数据也有但是数据没有正确地展示出来呢?
-
-A:通常是因为配置项没有正确地配置,请仔细检查配置项,尤其是类似与`{ ..., readForm: 'outer', items: [{ ..., readForm: 'outer.inner' }] }`的错误配置,内层的配置重复读取了`outer`导致了错误,正确的配置应该让内外层的`readForm`拼接起来后指向所要的数据。
-
-#### 我不会配置模拟数据,总是达不到想要的效果怎么办?
-
-A:模拟数据即`Config.moduleData.mock`,它绝大多数情况下是一个对象,如果不需要设备选择器,那么可配置首页将直接把模拟数据传给子模块。但如果需要设备选择器那么需要配合`Config.deviceType`来使用并确保其指向数组,例如写成`{ ..., mock: { [Config.deviceType]: [ ... ] } }`
-
-#### 如果项目部署后访问首页(可配置首页)没有出现任何内容怎么办?
-
-A:可能是由于以下的原因之一
-
-- 没有正确地配置菜单及其权限,请检查设置-菜单配置
-- 没有正确地配置子应用的模型导致出现了类似 _/micro-vent-3dModal/404_ 的链接,请检查设置-菜单配置-模型设置
-- 没有正确地添加配置项导致页面无内容,应在获取配置导入信息后于设置-首页配置导入
-
-#### 首页正常显示其内容,但是点击左下角呼出菜单后,点击Home图标却跳转到了其他页面怎么办?
-
-A:这是因为项目配置不正确导致的,参考设置-项目设置-编辑功能编辑首页为可配置首页路径并保存。
-
-#### 首页能够显示其内容,但是各个模块位置不齐、出现不想要的滚动条或者矿方想要更宽/更窄/更高等需求怎么办?
-
-A:根据需求自行调整模块`Config.showStyle`定义的宽高位置即可。需要注意定位(即`Config.showStyle.position`)里的`Top`和`Left`是按照总标题下至最底部,最左侧至最右侧为框计算的绝对值,不会计算模块的相互关系。
-
-#### 首页能够显示内容且位置、样式正确,但是某项指标的数据不对、不需要某项指标、需要添加某项指标怎么办?
-
-A:修改某项指标大致可以按照如下流程处理,图片资料参考![alt text](image-8.png)、![alt text](image-9.png)
-
-- 在首页使用控制台(F12)查看数据来源,通常是与 `/homedata` 相似的接口
-- 进入首页配置页面找到需要修改的模块,点击配置
-- 在控制台确认某项指标需要显示的数据的正确读取路径并更新至配置里
-
-#### 需要开发一个全新的页面,但是不知道如何下手怎么办?
-
-A:首先确认新页面的各个模块有没有可复用模块,通常在 _/assets/images/home-container/configurable_ 下可以通过图片的命名方式和样子来作初步确认,如果没有需要按需开发同时建议将图片资源按已有的命名规则加入。涉及复杂逻辑且没有可复用组件的建议直接使用![alt text](###模块子模块)中提到的`Preset`组件。

BIN
docs/configurable/image-1.png


BIN
docs/configurable/image-2.png


BIN
docs/configurable/image-3.png


BIN
docs/configurable/image-4.png


BIN
docs/configurable/image-5.png


BIN
docs/configurable/image-6.png


BIN
docs/configurable/image-7.png


BIN
docs/configurable/image-8.png


BIN
docs/configurable/image-9.png


BIN
docs/configurable/image.png


BIN
docs/界面缺陷测试报告_全量重测.xlsx


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


+ 0 - 9
src/assets/images/vent/home/selfIgnition/form-icon.svg

@@ -1,9 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="15.267" height="17.644" viewBox="0 0 15.267 17.644">
-  <defs>
-    <linearGradient id="linear-gradient" x1="0.5" x2="0.5" y2="1" gradientUnits="objectBoundingBox">
-      <stop offset="0" stop-color="#3df6ff"/>
-      <stop offset="1" stop-color="#b3fff5"/>
-    </linearGradient>
-  </defs>
-  <path id="路径_58015" data-name="路径 58015" d="M136.97,64a.476.476,0,0,1,.476.476v.238a.477.477,0,0,0,.476.476h3.87a.477.477,0,0,1,.476.476v6.122a.476.476,0,0,1-.476.476h-.238a.476.476,0,0,1-.476-.476V66.853a.476.476,0,0,0-.476-.476h-1.427a.476.476,0,0,0-.476.476V68.28a.476.476,0,0,1-.476.476h-6.182a.476.476,0,0,1-.476-.476V66.853a.476.476,0,0,0-.476-.476h-1.427a.476.476,0,0,0-.476.476V78.98a.477.477,0,0,0,.476.476h3.8a.477.477,0,0,1,.476.476v.238a.476.476,0,0,1-.476.476h-4.993a.476.476,0,0,1-.476-.476v-14.5a.477.477,0,0,1,.476-.476h3.8a.476.476,0,0,0,.476-.476v-.238a.477.477,0,0,1,.476-.476Zm4.475,9.848.485.485a.476.476,0,0,1,.061.6l-.059.075-5.288,5.285a.476.476,0,0,1-.253.132l-.923.163a.238.238,0,0,1-.276-.275l.162-.924a.476.476,0,0,1,.133-.254l5.286-5.285a.476.476,0,0,1,.673,0Zm-7.976,2.041a.477.477,0,0,1,.476.476V76.6a.476.476,0,0,1-.476.476h-1.427a.476.476,0,0,1-.476-.476v-.238a.477.477,0,0,1,.476-.476Zm2.378-2.378a.477.477,0,0,1,.476.476v.238a.476.476,0,0,1-.476.476h-3.8a.476.476,0,0,1-.476-.476v-.238a.477.477,0,0,1,.476-.476Zm2.378-2.378a.477.477,0,0,1,.476.476v.238a.476.476,0,0,1-.476.476h-6.182a.476.476,0,0,1-.476-.476v-.238a.477.477,0,0,1,.476-.476Zm-2.14-5.944H134.23a.238.238,0,0,0-.238.238v.713a.238.238,0,0,1-.238.238h-.769a.238.238,0,0,0-.238.238v.713a.238.238,0,0,0,.238.238h4.288a.238.238,0,0,0,.238-.238v-.713a.238.238,0,0,0-.238-.238h-.713a.238.238,0,0,1-.238-.238v-.713A.238.238,0,0,0,136.084,65.189Z" transform="translate(-127.5 -63.5)" stroke="rgba(0,0,0,0)" stroke-width="1" fill="url(#linear-gradient)"/>
-</svg>

+ 0 - 0
src/assets/images/vent/home/selfIgnition/card-bg.png → src/assets/images/vent/home/selfIgnition/goaf-card-bg.png


+ 9 - 0
src/assets/images/vent/home/selfIgnition/goaf-stat-closed.svg

@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="22.367" height="22.367" viewBox="0 0 22.367 22.367">
+  <defs>
+    <linearGradient id="linear-gradient" x1="0.5" x2="0.5" y2="1" gradientUnits="objectBoundingBox">
+      <stop offset="0" stop-color="#abdcff"/>
+      <stop offset="1" stop-color="#fff"/>
+    </linearGradient>
+  </defs>
+  <path id="路径_58069" data-name="路径 58069" d="M78.256,67.544A10.683,10.683,0,1,0,67.572,78.227,10.683,10.683,0,0,0,78.256,67.544ZM64.9,59.223a8.756,8.756,0,0,1,5.352,0,.444.444,0,0,0-.005.067V75.8a.379.379,0,0,0,.005.069,8.769,8.769,0,0,1-5.352,0A.444.444,0,0,0,64.9,75.8V59.29a.442.442,0,0,0-.005-.069Zm-1.937.9a8.742,8.742,0,0,0,0,14.851V60.118Zm13.353,7.426a8.734,8.734,0,0,1-4.128,7.425V60.119A8.731,8.731,0,0,1,76.312,67.545Z" transform="translate(-56.389 -56.36)" stroke="rgba(0,0,0,0)" stroke-width="1" fill="url(#linear-gradient)"/>
+</svg>

+ 14 - 0
src/assets/images/vent/home/selfIgnition/goaf-stat-mining.svg

@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="17.797" height="15.727" viewBox="0 0 17.797 15.727">
+  <defs>
+    <linearGradient id="linear-gradient" x1="0.5" x2="0.5" y2="1" gradientUnits="objectBoundingBox">
+      <stop offset="0" stop-color="#abdcff"/>
+      <stop offset="1" stop-color="#fff"/>
+    </linearGradient>
+  </defs>
+  <g id="组_16943" data-name="组 16943" transform="translate(0.083)">
+    <path id="路径_58071" data-name="路径 58071" d="M856.147,23.258h4.289V38.271h-4.289Z" transform="translate(-843.079 -22.9)" fill="url(#linear-gradient)"/>
+    <path id="路径_58072" data-name="路径 58072" d="M-.083,0h17.8V.714H-.083Zm0,15.013h17.8v.714H-.083Z" transform="translate(0)" fill="url(#linear-gradient)"/>
+    <path id="路径_58073" data-name="路径 58073" d="M832.89,0h.714V15.727h-.714Zm4.289,0h.714V15.727h-.714Z" transform="translate(-820.179)" fill="url(#linear-gradient)"/>
+    <path id="路径_58074" data-name="路径 58074" d="M4.289,238.3v2.86L0,238.3v-2.86l4.289-2.86v2.86h7.865v2.86H4.29Z" transform="translate(-0.081 -229.005)" fill="url(#linear-gradient)"/>
+  </g>
+</svg>

+ 13 - 0
src/assets/images/vent/home/selfIgnition/goaf-stat-stopped.svg

@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20.499" height="20.46" viewBox="0 0 20.499 20.46">
+  <defs>
+    <linearGradient id="linear-gradient" x1="0.5" x2="0.5" y2="1" gradientUnits="objectBoundingBox">
+      <stop offset="0" stop-color="#abdcff"/>
+      <stop offset="1" stop-color="#fff"/>
+    </linearGradient>
+  </defs>
+  <g id="组_16944" data-name="组 16944" transform="translate(-61.7 -64.4)">
+    <path id="路径_58075" data-name="路径 58075" d="M81,72.052Zm-4.062.817a6.856,6.856,0,0,1,1.585.187L74.677,64.4,68.19,74.134,65.755,70.89,61.7,79h8.538A6.73,6.73,0,0,1,76.942,72.869Zm-6.656,7.755H62.511v1.621h8.236A6.678,6.678,0,0,1,70.286,80.624Z" transform="translate(0)" fill="url(#linear-gradient)"/>
+    <path id="路径_58076" data-name="路径 58076" d="M504.257,500a5.257,5.257,0,1,0,5.257,5.257A5.255,5.255,0,0,0,504.257,500Zm0,9.259a4.005,4.005,0,1,1,4.005-4.005A4.006,4.006,0,0,1,504.257,509.26Z" transform="translate(-427.314 -425.653)" fill="url(#linear-gradient)"/>
+    <path id="路径_58077" data-name="路径 58077" d="M612.88,707.27h-4.195a.685.685,0,1,1,0-1.37h4.2a.685.685,0,1,1,0,1.37Z" transform="translate(-533.825 -626.852)" fill="url(#linear-gradient)"/>
+  </g>
+</svg>

+ 1 - 1
src/assets/images/vent/home/selfIgnition/goaf-icon.svg → src/assets/images/vent/home/selfIgnition/goaf-stat-total.svg

@@ -5,5 +5,5 @@
       <stop offset="1" stop-color="#fff"/>
     </linearGradient>
   </defs>
-  <path id="路径_58031" data-name="路径 58031" d="M160.223,312.65l-.88,4.26a4,4,0,0,1-3.776,3.428,20.444,20.444,0,0,1-6.752,0,4,4,0,0,1-3.775-3.427h0a3.558,3.558,0,0,1,0-.775l-.019-3.285-.035-.2h-3.865V327.2h22.971V312.65h-3.865Z" transform="translate(-140.617 -312.15)" stroke="rgba(0,0,0,0)" stroke-width="1" fill="url(#linear-gradient)"/>
+  <path id="路径_58009" data-name="路径 58009" d="M160.223,312.65l-.88,4.26a4,4,0,0,1-3.776,3.428,20.444,20.444,0,0,1-6.752,0,4,4,0,0,1-3.775-3.427h0a3.558,3.558,0,0,1,0-.775l-.019-3.285-.035-.2h-3.865V327.2h22.971V312.65h-3.865Z" transform="translate(-140.617 -312.15)" stroke="rgba(0,0,0,0)" stroke-width="1" fill="url(#linear-gradient)"/>
 </svg>

+ 9 - 0
src/assets/images/vent/home/selfIgnition/goaf-stat-withdraw.svg

@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="22.535" height="17.503" viewBox="0 0 22.535 17.503">
+  <defs>
+    <linearGradient id="linear-gradient" x1="0.5" x2="0.5" y2="1" gradientUnits="objectBoundingBox">
+      <stop offset="0" stop-color="#abdcff"/>
+      <stop offset="1" stop-color="#fff"/>
+    </linearGradient>
+  </defs>
+  <path id="路径_58070" data-name="路径 58070" d="M50.082,40.99,56.575,34.5l1.838,1.838-2.205,2.205h8.577a6.126,6.126,0,0,1,0,12.252H53.758v-2.45H64.784a3.676,3.676,0,0,0,0-7.351h-14.7Z" transform="translate(-48.875 -33.788)" stroke="rgba(0,0,0,0)" stroke-width="1" fill="url(#linear-gradient)"/>
+</svg>

+ 0 - 18
src/assets/images/vent/home/selfIgnition/info-icon.svg

@@ -1,18 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12.369" height="11.645" viewBox="0 0 12.369 11.645">
-  <defs>
-    <linearGradient id="linear-gradient" x1="0.5" x2="0.5" y2="1" gradientUnits="objectBoundingBox">
-      <stop offset="0" stop-color="#3df6ff"/>
-      <stop offset="1" stop-color="#b3fff5"/>
-    </linearGradient>
-  </defs>
-  <g id="组_16798" data-name="组 16798" transform="translate(-64 -90.24)">
-    <path id="路径_58016" data-name="路径 58016" d="M420.1,90.24h2.485V99.4H420.1Z" transform="translate(-351.18)" fill="url(#linear-gradient)"/>
-    <path id="路径_58017" data-name="路径 58017" d="M180.224,397.632h2.484v4.917h-2.484Z" transform="translate(-114.62 -303.148)" fill="url(#linear-gradient)"/>
-    <path id="路径_58018" data-name="路径 58018" d="M660.032,278.656h2.485v6.56h-2.485Z" transform="translate(-587.804 -185.815)" fill="url(#linear-gradient)"/>
-    <path id="路径_58019" data-name="路径 58019" d="M64,816.64H76.369v1.617H64Z" transform="translate(0 -716.372)" fill="url(#linear-gradient)"/>
-    <path id="路径_58020" data-name="路径 58020" d="M485.76,159.616h1.579v8.2H485.76Z" transform="translate(-415.938 -68.418)" fill="url(#linear-gradient)"/>
-    <path id="路径_58021" data-name="路径 58021" d="M245.824,465.152H247.4v3.986h-1.579Z" transform="translate(-179.314 -369.736)" fill="url(#linear-gradient)"/>
-    <path id="路径_58022" data-name="路径 58022" d="M725.7,341.44h1.579v5.692H725.7Z" transform="translate(-652.561 -247.732)" fill="url(#linear-gradient)"/>
-    <path id="路径_58023" data-name="路径 58023" d="M123.072,875.648h11.554v.8H123.072Z" transform="translate(-58.257 -774.566)" fill="url(#linear-gradient)"/>
-  </g>
-</svg>

+ 0 - 9
src/assets/images/vent/home/selfIgnition/install-wind-speed.svg

@@ -1,9 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20.211" height="20.099" viewBox="0 0 20.211 20.099">
-  <defs>
-    <linearGradient id="linear-gradient" x1="0.5" x2="0.5" y2="1" gradientUnits="objectBoundingBox">
-      <stop offset="0" stop-color="#2cfff1"/>
-      <stop offset="1" stop-color="#ccedff"/>
-    </linearGradient>
-  </defs>
-  <path id="路径_58056" data-name="路径 58056" d="M15.25,1.189a4.4,4.4,0,0,0-4.356,3.794.8.8,0,0,0-.023.172l-.023.268H10.9a.852.852,0,0,0,1.641,0h.013l.023-.2a2.683,2.683,0,1,1,2.653,3.062H3.911a.861.861,0,1,0,0,1.722h11.58v0a4.411,4.411,0,0,0-.241-8.818ZM3.915,6.724H7.869V6.7A3.355,3.355,0,0,0,7.543,0H7.507A3.34,3.34,0,0,0,4.179,3.318V3.6h.034a.852.852,0,0,0,.792.6.82.82,0,0,0,.615-.232A.962.962,0,0,0,5.855,3.6h.028V3.365A1.641,1.641,0,1,1,7.524,5H3.911a.852.852,0,1,0,0,1.7Zm-2.5,4.809a.852.852,0,1,0,.862.852.867.867,0,0,0-.852-.852Zm0-3.26a.852.852,0,1,0,.862.852.875.875,0,0,0-.852-.852Zm0-3.27a.852.852,0,1,0,.862.862A.869.869,0,0,0,1.412,5Zm11.426,6.564v-.023H3.911a.861.861,0,1,0,0,1.722h8.5a2.061,2.061,0,1,1-2.054,2.063V15.1h-.034a.869.869,0,0,0-.792-.607H9.5a.852.852,0,0,0-.815.607H8.65v.266a3.779,3.779,0,0,0,3.57,3.736h.213a3.734,3.734,0,0,0,2.514-.967,3.779,3.779,0,0,0-2.106-6.564Z" transform="translate(-0.071 0.501)" stroke="rgba(0,0,0,0)" stroke-miterlimit="10" stroke-width="1" fill="url(#linear-gradient)"/>
-</svg>

Plik diff jest za duży
+ 0 - 8
src/assets/images/vent/home/selfIgnition/install-wind-volume.svg


+ 3 - 0
src/assets/images/vent/home/selfIgnition/level-safety-orange.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="11.832" height="14.462" viewBox="0 0 11.832 14.462">
+  <path id="路径_58053" data-name="路径 58053" d="M139.7,44.113a.625.625,0,0,0-.362-.23l-5.423-1.216-5.39,1.216a.61.61,0,0,0-.394.23.7.7,0,0,0-.131.394v6.574a4.151,4.151,0,0,0,.46,1.873,4.063,4.063,0,0,0,1.282,1.413l4.174,2.761,4.174-2.761a3.91,3.91,0,0,0,1.742-3.287V44.507a.7.7,0,0,0-.131-.394Zm-1.183,6.968a2.79,2.79,0,0,1-.3,1.249,2.4,2.4,0,0,1-.855.953l-3.418,2.3V49.24h-4.634V45.033l4.6-1.019V49.24h4.6Z" transform="translate(-128 -42.667)" fill="#ff9d1c"/>
+</svg>

+ 3 - 0
src/assets/images/vent/home/selfIgnition/level-warning-orange.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="13.607" height="13.627" viewBox="0 0 13.607 13.627">
+  <path id="路径_58048" data-name="路径 58048" d="M16.916,12.639h-.987V8.068a4.7,4.7,0,1,1,9.394,0v4.57H16.916Zm4.05-7.374L18.649,9.08H20.5l-.464,2.862,2.317-3.816H20.5l.464-2.862Zm3.553-4.192a.458.458,0,0,1,.166.624l-.833,1.448-.787-.456L23.9,1.24a.453.453,0,0,1,.618-.168h0ZM20.625,0a.476.476,0,0,1,.493.46V2.1h-.985V.459A.476.476,0,0,1,20.625,0ZM16.733,1.072a.454.454,0,0,1,.621.166l.833,1.448-.787.456-.834-1.449a.456.456,0,0,1,.167-.622ZM13.884,3.934a.453.453,0,0,1,.621-.166l1.443.837-.454.79L14.05,4.557a.456.456,0,0,1-.166-.622Zm13.485,0a.458.458,0,0,1-.167.624l-1.442.836-.454-.79,1.442-.837a.454.454,0,0,1,.619.165s0,0,0,0Zm-13.049,8.7H26.934a.494.494,0,1,1,0,.988H14.32a.494.494,0,1,1,0-.988Z" transform="translate(-13.822)" fill="#ff9d1c"/>
+</svg>

BIN
src/assets/images/vent/home/selfIgnition/list-icon-bg.png


BIN
src/assets/images/vent/home/selfIgnition/module-border-bottom-wide.png


BIN
src/assets/images/vent/home/selfIgnition/module-border-bottom.png


BIN
src/assets/images/vent/home/selfIgnition/module-border-middle-wide.png


BIN
src/assets/images/vent/home/selfIgnition/module-border-middle.png


BIN
src/assets/images/vent/home/selfIgnition/module-border-top-wide.png


BIN
src/assets/images/vent/home/selfIgnition/module-border-top.png


BIN
src/assets/images/vent/home/selfIgnition/module-title-bg.png


BIN
src/assets/images/vent/home/selfIgnition/overall-bar-glow.png


BIN
src/assets/images/vent/home/selfIgnition/overall-label-bg.png


BIN
src/assets/images/vent/home/selfIgnition/result-block-bg.png


BIN
src/assets/images/vent/home/selfIgnition/submit-glow.png


BIN
src/assets/images/vent/home/selfIgnition/tab-active-bg.png


BIN
src/assets/images/vent/home/selfIgnition/tab-bg.png


BIN
src/assets/images/vent/home/selfIgnition/tab-item-bg.png


BIN
src/assets/images/vent/home/selfIgnition/tab-wing-left.png


BIN
src/assets/images/vent/home/selfIgnition/tab-wing-right.png


BIN
src/assets/images/vent/home/selfIgnition/top-bg.png


+ 153 - 195
src/views/vent/home/selfIgnition/components/ManualView.vue

@@ -1,14 +1,7 @@
 <template>
   <div class="ignition-view manual-view">
     <!-- 左:人工录入 -->
-    <ModuleCommon
-      :show-style="panelShowStyle(20, 132, 353, 907)"
-      :module-data="EMPTY_MODULE_DATA"
-      module-name="人工录入"
-      device-type="ignition_manual_input"
-      :data="{}"
-      :visible="true"
-    >
+    <Module :x="20" :y="144" :width="393" :height="766" title="人工录入">
       <div class="manual-form">
         <div class="form-toolbar">
           <div class="eval-type-tabs">
@@ -21,29 +14,23 @@
           <div v-if="loadingOptions" class="form-loading">评价指标加载中...</div>
           <template v-else>
             <div v-for="group in fieldGroups" :key="group.key" class="form-group">
-              <div class="group-collapse" @click="toggleGroup(group.key)">
-                <img class="group-icon" src="@/assets/images/vent/home/selfIgnition/form-icon.svg" alt="" />
-                <span>{{ group.title }}</span>
-                <i class="collapse-arrow" :class="{ collapsed: collapsed[group.key] }"></i>
+              <div class="group-title">
+                <span class="title-bars">
+                  <i v-for="n in 4" :key="n"></i>
+                </span>
+                <span>{{ group.title }}指标</span>
+                <span class="title-dots">
+                  <i></i>
+                  <b></b>
+                  <i></i>
+                </span>
               </div>
-              <template v-if="!collapsed[group.key]">
-                <div class="group-title">
-                  <span class="title-bars">
-                    <i v-for="n in 4" :key="n"></i>
-                  </span>
-                  <span>{{ group.title }}指标</span>
-                  <span class="title-dots">
-                    <i v-for="n in 3" :key="n"></i>
-                    <b></b>
-                  </span>
+              <div class="field-list">
+                <div v-for="item in group.fields" :key="item.code" class="field-row">
+                  <span class="field-label" :title="item.name">{{ item.name }}:</span>
+                  <PanelSelect v-model="formValues[FIELD_BY_CODE[item.code]]" :options="selectOptions(item)" />
                 </div>
-                <div class="field-list">
-                  <div v-for="item in group.fields" :key="item.code" class="field-row">
-                    <span class="field-label" :title="item.name">{{ item.name }}:</span>
-                    <PanelSelect v-model="formValues[FIELD_BY_CODE[item.code]]" :options="selectOptions(item)" />
-                  </div>
-                </div>
-              </template>
+              </div>
             </div>
             <div v-if="!fieldGroups.length" class="form-loading">暂无评价指标</div>
           </template>
@@ -58,28 +45,19 @@
           </button>
         </div>
       </div>
-    </ModuleCommon>
+    </Module>
 
     <!-- 右:评价结果 -->
-    <ModuleCommon
-      :show-style="panelShowStyle(1545, 132, 353, 907)"
-      :module-data="EMPTY_MODULE_DATA"
-      module-name="评价结果"
-      device-type="ignition_evaluation_result"
-      :data="{}"
-      :visible="true"
-    >
+    <Module :x="1505" :y="144" :width="393" :height="766" title="评价结果">
       <div class="result-panel">
         <div class="result-top">
           <div class="score-line">
-            <span class="score-label">安全评价得分:</span>
-            <img class="score-info" src="@/assets/images/vent/home/selfIgnition/info-icon.svg" alt="由后端算法计算" />
+            <span class="score-label">风险分值:</span>
             <span class="score-value">{{ resultScore }}</span>
           </div>
           <div class="gauge-line">
             <div class="gauge">
-              <img class="gauge-segment gauge-segment-1" src="@/assets/images/vent/home/selfIgnition/risk-gauge.png" alt="" />
-              <img class="gauge-segment gauge-segment-2" src="@/assets/images/vent/home/selfIgnition/risk-gauge.png" alt="" />
+              <img class="gauge-segment" src="@/assets/images/vent/home/selfIgnition/risk-gauge.png" alt="" />
             </div>
             <span class="risk-tag" :style="{ '--risk-color': resultRiskColor }">{{ resultTagText }}</span>
           </div>
@@ -96,9 +74,9 @@
         </div>
 
         <div class="level-table">
-          <div class="level-head"><span>评价等级</span><span>评分范围</span><span>对应颜色</span></div>
+          <div class="level-head"><span>评价等级</span><span>风险分值范围</span><span>对应颜色</span></div>
           <div
-            v-for="item in levels"
+            v-for="(item, index) in levels"
             :key="item.level"
             class="level-row"
             :class="{ highlight: item.level === resultLevel }"
@@ -106,6 +84,7 @@
           >
             <span class="level-name">
               <i :style="{ background: item.color, boxShadow: `0 0 8px ${item.color}` }"></i>
+              <b>{{ levelRoman(index) }}</b>
               {{ item.level }}
             </span>
             <span>{{ item.range }}</span>
@@ -113,15 +92,14 @@
           </div>
         </div>
       </div>
-    </ModuleCommon>
+    </Module>
   </div>
 </template>
 
 <script lang="ts" setup>
   import { computed, onMounted, reactive, ref, watch } from 'vue';
-  import ModuleCommon from '../../configurable/components/ModuleCommon.vue';
+  import Module from './Module.vue';
   import PanelSelect from './PanelSelect.vue';
-  import { EMPTY_MODULE_DATA, panelShowStyle } from '../utils';
   import { useMessage } from '/@/hooks/web/useMessage';
   import { calculateSafetyEvaluation, getSafetyEvaluationByScene, getSafetyItemOptions, saveSafetyEvaluation } from '../safetyEvaluation.api';
   import { EVAL_RISK_LEVELS, EVAL_RISK_MAP, FIELD_BY_CODE, FALLBACK_SAFETY_ITEMS } from '../evaluation.config';
@@ -137,7 +115,6 @@
   const items = ref<SafetyItem[]>([]);
   const evalType = ref(1);
   const formValues = reactive<Record<string, string>>({});
-  const collapsed = reactive<Record<string, boolean>>({});
   const evaluation = ref<SafetyEvaluationEntity | null>(null);
   const loadingOptions = ref(false);
   const previewing = ref(false);
@@ -172,7 +149,20 @@
   const resultLevel = computed(() => evaluation.value?.riskLevel || '');
   const resultLevelMeta = computed(() => EVAL_RISK_MAP[resultLevel.value] || null);
   const resultRiskColor = computed(() => resultLevelMeta.value?.color || '#3df6ff');
-  const resultTagText = computed(() => resultLevel.value || '未评价');
+
+  const ROMAN_NUMERALS = ['I', 'II', 'III', 'IV', 'V', 'VI'];
+
+  function levelRoman(index: number) {
+    return ROMAN_NUMERALS[index] || '';
+  }
+
+  const resultTagText = computed(() => {
+    const level = resultLevel.value;
+    if (!level) return '未评价';
+    const index = levels.findIndex((item) => item.level === level);
+    return index >= 0 ? `${levelRoman(index)} ${level}` : level;
+  });
+
   const resultTarget = computed(() => evaluation.value?.workfaceName || targetName.value);
   const resultTime = computed(() => evaluation.value?.createTime || '--');
 
@@ -180,7 +170,8 @@
     const value = evaluation.value?.totalScore;
     if (value === null || value === undefined || value === '') return '--';
     const score = Number(value);
-    return Number.isFinite(score) ? score.toFixed(2) : String(value);
+    // 与设计稿一致:最多保留两位小数并去掉末尾的 0(58.60 → 58.6)
+    return Number.isFinite(score) ? `${Number(score.toFixed(2))}` : String(value);
   });
 
   function selectOptions(item: SafetyItem) {
@@ -191,10 +182,6 @@
     }));
   }
 
-  function toggleGroup(key: string) {
-    collapsed[key] = !collapsed[key];
-  }
-
   function formatDateTime(date: Date) {
     const pad = (value: number) => `${value}`.padStart(2, '0');
     return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
@@ -356,6 +343,8 @@
 </script>
 
 <style lang="less" scoped>
+@import '/@/views/vent/home/selfIgnition/scrollbar.less';
+
   .manual-view {
     position: absolute;
     inset: 0;
@@ -427,14 +416,7 @@
     overflow-x: hidden;
     padding-right: 2px;
 
-    &::-webkit-scrollbar {
-      width: 4px;
-    }
-
-    &::-webkit-scrollbar-thumb {
-      background: rgba(61, 246, 255, 0.35);
-      border-radius: 2px;
-    }
+    .scrollbar-cyan();
   }
 
   .form-group {
@@ -445,56 +427,48 @@
     }
   }
 
-  .group-collapse {
+  .group-title {
+    position: relative;
     display: flex;
     align-items: center;
-    gap: 6px;
-    height: 34px;
-    padding: 0 8px;
-    border-left: 2px solid #3df6ff;
-    background: linear-gradient(90deg, rgba(15, 77, 120, 0.9), rgba(9, 43, 70, 0.15));
+    gap: 3px;
+    height: 22px;
+    margin-bottom: 4px;
+    padding: 0;
+    box-sizing: border-box;
     color: #fff;
-    font-size: 15px;
-    letter-spacing: 0.5px;
-    cursor: pointer;
-    user-select: none;
-  }
-
-  .group-icon {
-    width: 13px;
-    height: 15px;
-  }
-
-  .collapse-arrow {
-    margin-left: auto;
-    width: 0;
-    height: 0;
-    border-left: 4px solid transparent;
-    border-right: 4px solid transparent;
-    border-top: 5px solid #cfefff;
-    transition: transform 0.2s;
+    font-size: 16px;
+    font-weight: 600;
+    letter-spacing: 1px;
 
-    &.collapsed {
-      transform: rotate(-90deg);
+    // 底部的渐变光带(左侧亮、向右淡出至消失)
+    &::before {
+      content: '';
+      position: absolute;
+      left: 4px;
+      right: 0;
+      bottom: 0;
+      height: 13px;
+      background: linear-gradient(90deg, rgba(43, 143, 214, 0.55) 0%, rgba(24, 86, 140, 0.18) 45%, rgba(12, 40, 70, 0) 78%);
+      pointer-events: none;
     }
-  }
 
-  .group-title {
-    display: flex;
-    align-items: center;
-    gap: 6px;
-    height: 24px;
-    padding: 0 4px;
-    color: #a9dcf4;
-    font-size: 12px;
-    letter-spacing: 0.5px;
+    > * {
+      position: relative;
+      z-index: 1;
+    }
   }
 
   .title-bars {
+    position: relative;
     display: flex;
     align-items: flex-end;
-    gap: 2px;
-    height: 12px;
+    gap: 1px;
+    width: 14px;
+    height: 14px;
+    padding-bottom: 2px;
+    box-sizing: border-box;
+    border-bottom: 2px solid #6bd9ff;
 
     i {
       width: 3px;
@@ -505,43 +479,45 @@
       }
 
       &:nth-child(2) {
-        height: 12px;
+        height: 9px;
       }
 
       &:nth-child(3) {
-        height: 8px;
+        height: 12px;
       }
 
       &:nth-child(4) {
-        height: 4px;
+        height: 6px;
       }
     }
   }
 
   .title-dots {
+    position: absolute;
+    right: 10px;
+    bottom: 4px;
     display: flex;
     align-items: center;
     gap: 3px;
-    margin-left: auto;
 
     i {
-      width: 2px;
-      height: 2px;
-      background: rgba(61, 246, 255, 0.65);
+      width: 3px;
+      height: 3px;
+      background: rgba(110, 200, 255, 0.75);
     }
 
     b {
-      width: 3px;
-      height: 3px;
-      background: #3df6ff;
-      box-shadow: 0 0 4px rgba(61, 246, 255, 0.9);
+      width: 5px;
+      height: 5px;
+      background: #a9e6ff;
+      box-shadow: 0 0 5px rgba(61, 246, 255, 0.9);
     }
   }
 
   .field-list {
     display: flex;
     flex-direction: column;
-    gap: 7px;
+    gap: 12px;
   }
 
   .field-row {
@@ -549,7 +525,12 @@
     align-items: center;
     gap: 6px;
     min-width: 0;
-    min-height: 42px;
+    min-height: 34px;
+    padding: 0 8px;
+    box-sizing: border-box;
+    border-radius: 2px;
+    // 设计稿:整行铺一条左亮右暗的渐变底
+    background: linear-gradient(90deg, rgba(32, 115, 175, 0.48) 0%, rgba(16, 62, 100, 0.22) 55%, rgba(10, 40, 70, 0.06) 100%);
 
     :deep(.ignition-select) {
       flex: 1;
@@ -562,7 +543,7 @@
   .field-label {
     width: 126px;
     flex: none;
-    text-align: right;
+    text-align: left;
     color: #fff;
     font-size: 12px;
     line-height: 1.3;
@@ -604,90 +585,72 @@
   .result-panel {
     display: flex;
     flex-direction: column;
-    justify-content: space-evenly;
     height: 100%;
-    padding: 10px 12px 14px;
+    padding: 24px 10px 0;
     box-sizing: border-box;
   }
 
   .result-top {
-    display: flex;
     flex: none;
-    flex-direction: column;
-    justify-content: center;
-    height: 180px;
-    box-sizing: border-box;
-    background: url('@/assets/images/vent/home/selfIgnition/result-top-bg.png') no-repeat center / 100% 100%;
+    padding: 0 14px;
   }
 
   .score-line {
     display: flex;
     align-items: baseline;
-    padding: 0 20px;
   }
 
   .score-label {
+    flex: none;
+    white-space: nowrap;
     color: #fff;
-    font-size: 19px;
+    font-size: 26px;
+    line-height: 1;
     letter-spacing: 1px;
   }
 
-  .score-info {
-    width: 12px;
-    height: 12px;
-    margin-left: 5px;
-    align-self: center;
-    opacity: 0.85;
-  }
-
   .score-value {
-    margin-left: 16px;
+    // 占满标签之外的剩余空间并居中
+    flex: 1 1 auto;
+    text-align: center;
     color: #fff;
-    font-size: 42px;
+    font-size: 49px;
     font-weight: 700;
     line-height: 1;
     letter-spacing: 1px;
-    text-shadow: 0 0 16px rgba(255, 255, 255, 0.35);
+    text-shadow: 0 0 16px rgba(255, 255, 255, 0.3);
   }
 
   .gauge-line {
     display: flex;
     align-items: center;
-    justify-content: space-between;
-    margin-top: 26px;
-    padding: 0 18px;
+    margin-top: 10px;
   }
 
   .gauge {
     display: flex;
+    flex: 0 0 50%;
     align-items: center;
-    width: 150px;
-    height: 26px;
+    min-width: 0;
+    height: 23px;
   }
 
   .gauge-segment {
-    width: 80px;
-    height: 22px;
-    object-fit: fill;
-
-    & + & {
-      margin-left: -8px;
-    }
-  }
-
-  .gauge-segment-2 {
-    opacity: 0.72;
+    display: block;
+    width: 100%;
+    height: 100%;
   }
 
   .risk-tag {
     display: flex;
     align-items: center;
-    width: 159px;
-    height: 34px;
-    padding-left: 22px;
+    flex: 0 0 50%;
+    justify-content: center;
+    height: 29px;
+    padding: 0 10px 0 5px;
     box-sizing: border-box;
     color: #fff;
-    font-size: 15px;
+    font-size: 16px;
     font-weight: 600;
     letter-spacing: 0.5px;
     white-space: nowrap;
@@ -701,32 +664,34 @@
     display: flex;
     flex-direction: column;
     justify-content: center;
-    gap: 4px;
-    padding: 12px 16px;
-    border-left: 1px solid rgba(61, 246, 255, 0.16);
-    border-right: 1px solid rgba(61, 246, 255, 0.16);
-    background: rgba(7, 42, 70, 0.62);
+    width: calc(100% - 15px);
+    height: 119px;
+    margin: 33px auto 0;
+    padding: 0 17px;
+    box-sizing: border-box;
+    background: url('@/assets/images/vent/home/selfIgnition/result-top-bg.png') no-repeat center / 100% 100%;
   }
 
   .info-row {
     display: flex;
     align-items: center;
     justify-content: space-between;
-    height: 46px;
+    height: 29px;
     color: #fff;
-    font-size: 15px;
+    font-size: 17px;
     letter-spacing: 0.5px;
 
     em {
       color: #3df6ff;
       font-style: normal;
-      font-size: 16px;
+      font-size: 17px;
       text-shadow: 0 0 8px rgba(61, 246, 255, 0.35);
     }
   }
 
   .level-table {
     flex: none;
+    margin-top: 28px;
   }
 
   .level-head,
@@ -737,19 +702,17 @@
   }
 
   .level-head {
-    height: 40px;
-    border: 1px solid rgba(61, 246, 255, 0.4);
-    border-radius: 4px;
+    height: 27px;
     color: #3df6ff;
-    font-size: 13px;
+    font-size: 14px;
     background: url('@/assets/images/vent/home/selfIgnition/result-table-head.png') no-repeat center / 100% 100%;
 
     span:nth-child(1) {
-      width: 42%;
+      width: 36%;
     }
 
     span:nth-child(2) {
-      width: 32%;
+      width: 38%;
     }
 
     span:nth-child(3) {
@@ -759,17 +722,17 @@
 
   .level-row {
     position: relative;
-    height: 50px;
+    height: 51px;
     color: #e8f8ff;
     font-size: 14px;
-    background: url('@/assets/images/vent/home/selfIgnition/result-row-glow.png') no-repeat center bottom / 100% 10px;
+    background: url('@/assets/images/vent/home/selfIgnition/result-row-glow.png') no-repeat center bottom / 100% 21px;
 
     span:nth-child(1) {
-      width: 42%;
+      width: 36%;
     }
 
     span:nth-child(2) {
-      width: 32%;
+      width: 38%;
     }
 
     span:nth-child(3) {
@@ -777,10 +740,15 @@
     }
 
     &.highlight {
-      outline: 1px solid var(--highlight-color, #ff9d1c);
-      outline-offset: -1px;
-      background-color: rgba(255, 157, 28, 0.08);
-      box-shadow: inset 0 0 18px rgba(255, 157, 28, 0.16);
+      &::after {
+        content: '';
+        position: absolute;
+        inset: 2px;
+        border: 2px solid var(--highlight-color, #ff9d1c);
+        border-radius: 6px;
+        box-shadow: 0 0 10px rgba(255, 157, 28, 0.25);
+        pointer-events: none;
+      }
     }
   }
 
@@ -791,10 +759,15 @@
     gap: 6px;
 
     i {
-      width: 7px;
-      height: 7px;
+      flex: none;
+      width: 8px;
+      height: 8px;
       border-radius: 50%;
     }
+
+    b {
+      font-weight: 600;
+    }
   }
 
   .action-button {
@@ -823,21 +796,6 @@
     &:disabled {
       opacity: 0.5;
       cursor: not-allowed;
-
-      &::after {
-        display: none;
-      }
-    }
-
-    &::after {
-      content: '';
-      position: absolute;
-      left: 0;
-      right: 0;
-      bottom: -8px;
-      height: 16px;
-      background: url('@/assets/images/vent/home/selfIgnition/submit-glow.png') no-repeat center / 100% 100%;
-      pointer-events: none;
     }
   }
 </style>

+ 141 - 0
src/views/vent/home/selfIgnition/components/Module.vue

@@ -0,0 +1,141 @@
+<template>
+  <div class="ignition-module" :class="{ 'is-wide': isWide }" :style="rootStyle">
+    <div class="module-top"></div>
+    <div class="module-body">
+      <slot></slot>
+    </div>
+    <div class="module-footer"></div>
+    <div class="module-header">
+      <span class="module-title" :title="title">{{ title }}</span>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { computed } from 'vue';
+
+  const props = defineProps<{
+    /** 设计稿坐标(1920 × 1080) */
+    x: number;
+    y: number;
+    width: number;
+    height: number;
+    title: string;
+  }>();
+
+  /** 宽模块(> 800)使用宽版三段边框图 */
+  const isWide = computed(() => props.width > 800);
+
+  const rootStyle = computed(() => ({
+    left: `${props.x}px`,
+    top: `${props.y}px`,
+    width: `${props.width}px`,
+    height: `${props.height}px`,
+  }));
+</script>
+
+<style lang="less" scoped>
+  .ignition-module {
+    position: absolute;
+    z-index: 1;
+    pointer-events: auto;
+
+    .module-top,
+    .module-footer {
+      position: absolute;
+      left: 0;
+      right: 0;
+      height: 48px;
+      background-repeat: no-repeat;
+      background-position: center;
+      background-size: 100% 100%;
+    }
+    .module-footer {
+      height: 30px;
+    }
+
+    .module-top {
+      top: 0;
+      background-image: url('@/assets/images/vent/home/selfIgnition/module-border-top.png');
+    }
+
+    .module-footer {
+      bottom: 0;
+      z-index: 1;
+      background-image: url('@/assets/images/vent/home/selfIgnition/module-border-bottom.png');
+      pointer-events: none;
+    }
+
+    // 正文从中段贴图开始,向上压住上盖下缘,与标题带衔接
+    .module-body {
+      position: absolute;
+      left: 0;
+      right: 0;
+      top: 48px;
+      bottom: 30px;
+      overflow: hidden;
+      background: url('@/assets/images/vent/home/selfIgnition/module-border-middle.png') no-repeat center / 100% 100%;
+    }
+
+    // 标题带(1-6)贴在模块上边框内侧
+    .module-header {
+      position: absolute;
+      top: 8px;
+      left: 8px;
+      right: 1px;
+      z-index: 2;
+      display: flex;
+      align-items: center;
+      height: 35px;
+      width: 400px;
+      padding: 0 12px 0 20px;
+      box-sizing: border-box;
+      background: url('@/assets/images/vent/home/selfIgnition/module-title-bg.png') no-repeat center / 100% 100%;
+      pointer-events: none;
+    }
+
+    .module-title {
+      display: flex;
+      align-items: center;
+      color: #fff;
+      font-size: 16px;
+      font-weight: 700;
+      letter-spacing: 1px;
+      white-space: nowrap;
+      text-shadow: 0 0 8px rgba(61, 246, 255, 0.45);
+
+      &::before {
+        content: '';
+        flex: none;
+        width: 9px;
+        height: 9px;
+        margin-right: 6px;
+        border: 2px solid #3df6ff;
+        background: linear-gradient(135deg, transparent 46%, rgba(61, 246, 255, 0.9) 47%, rgba(61, 246, 255, 0.9) 53%, transparent 54%);
+        box-shadow: 0 0 8px rgba(61, 246, 255, 0.45);
+        transform: rotate(45deg);
+      }
+    }
+
+    &.is-wide {
+      .module-top {
+        background-image: url('@/assets/images/vent/home/selfIgnition/module-border-top-wide.png');
+      }
+
+      .module-body {
+        background-image: url('@/assets/images/vent/home/selfIgnition/module-border-middle-wide.png');
+      }
+
+      .module-footer {
+        background-image: url('@/assets/images/vent/home/selfIgnition/module-border-bottom-wide.png');
+      }
+    }
+    :deep(.data-table) {
+      border: none !important;
+
+      .table-head {
+        border: 1px solid rgba(61, 246, 255, 0.55);
+      }
+    }
+  }
+</style>

+ 181 - 284
src/views/vent/home/selfIgnition/components/MonitorView.vue

@@ -1,14 +1,7 @@
 <template>
   <div ref="viewRef" class="ignition-view monitor-view">
     <!-- 左上:自然发火阶段 -->
-    <ModuleCommon
-      :show-style="panelShowStyle(20, 132, 353, 578)"
-      :module-data="EMPTY_MODULE_DATA"
-      module-name="自然发火阶段"
-      device-type="ignition_fire_stage"
-      :data="{}"
-      :visible="true"
-    >
+    <Module :x="20" :y="144" :width="393" :height="497" title="自然发火阶段">
       <div class="stage-panel">
         <span class="stage-emblem">
           <img class="stage-emblem-frame" src="@/assets/images/vent/home/selfIgnition/2-1.png" alt="" />
@@ -38,20 +31,13 @@
           </div>
         </div>
       </div>
-    </ModuleCommon>
+    </Module>
 
     <!-- 右上:传感器监测 -->
-    <ModuleCommon
-      :show-style="panelShowStyle(1545, 77, 353, 328)"
-      :module-data="EMPTY_MODULE_DATA"
-      module-name="传感器监测"
-      device-type="ignition_sensor_monitor"
-      :data="{}"
-      :visible="true"
-    >
+    <Module :x="1505" :y="98" :width="393" :height="282" title="传感器监测">
       <div v-if="displaySensorGroups.length" class="sensor-panel">
         <div v-for="group in displaySensorGroups" :key="group.name" class="sensor-group">
-          <div class="sensor-group-title">{{ group.name }}</div>
+          <div class="sensor-group-title">{{ sceneName || group.name }}</div>
           <div v-for="row in group.rows" :key="row.label" class="sensor-row">
             <span :title="row.label">{{ row.label }}:</span>
             <em>{{ row.value }}</em>
@@ -62,18 +48,10 @@
         <img src="@/assets/images/vent/no-data.png" alt="暂无数据" />
         <span>暂无数据</span>
       </div>
-    </ModuleCommon>
+    </Module>
 
     <!-- 右中:光纤测温 -->
-    <ModuleCommon
-      class="fiber-module"
-      :show-style="panelShowStyle(1545, 420, 353, 290)"
-      :module-data="EMPTY_MODULE_DATA"
-      module-name="光纤测温"
-      device-type="ignition_fiber_temperature"
-      :data="{}"
-      :visible="true"
-    >
+    <Module class="fiber-module" :x="1505" :y="393" :width="393" :height="249" title="光纤测温">
       <div v-if="displayFiberData.solid.length" class="fiber-panel">
         <div v-if="fiberOptions.length > 1" class="fiber-toolbar">
           <span class="fiber-toolbar-label">光纤设备</span>
@@ -103,18 +81,10 @@
         <img src="@/assets/images/vent/no-data.png" alt="暂无数据" />
         <span>暂无数据</span>
       </div>
-    </ModuleCommon>
+    </Module>
 
     <!-- 左下:束管监测 -->
-    <ModuleCommon
-      class="wide-module"
-      :show-style="panelShowStyle(20, 732, 930, 326)"
-      :module-data="EMPTY_MODULE_DATA"
-      module-name="束管监测"
-      device-type="ignition_bundle_monitor"
-      :data="{}"
-      :visible="true"
-    >
+    <Module class="wide-module" :x="20" :y="649" :width="930" :height="260" title="束管监测">
       <div v-if="displayBundleRows.length" class="table-panel">
         <div class="data-table bundle-table">
           <div class="table-head">
@@ -145,18 +115,10 @@
         <img src="@/assets/images/vent/no-data.png" alt="暂无数据" />
         <span>暂无数据</span>
       </div>
-    </ModuleCommon>
+    </Module>
 
     <!-- 右下:密闭监测 -->
-    <ModuleCommon
-      class="wide-module"
-      :show-style="panelShowStyle(969, 732, 929, 326)"
-      :module-data="EMPTY_MODULE_DATA"
-      module-name="密闭监测"
-      device-type="ignition_closed_monitor"
-      :data="{}"
-      :visible="true"
-    >
+    <Module class="wide-module" :x="969" :y="649" :width="929" :height="260" title="密闭监测">
       <div v-if="displayClosedRows.length" class="table-panel">
         <div class="data-table device-table">
           <div class="table-head">
@@ -187,17 +149,15 @@
         <img src="@/assets/images/vent/no-data.png" alt="暂无数据" />
         <span>暂无数据</span>
       </div>
-    </ModuleCommon>
+    </Module>
   </div>
 </template>
 
 <script lang="ts" setup>
-  import ModuleCommon from '../../configurable/components/ModuleCommon.vue';
+  import Module from './Module.vue';
   import FiberTemperatureChart from './FiberTemperatureChart.vue';
   import PanelSelect from './PanelSelect.vue';
-  import { EMPTY_MODULE_DATA, panelShowStyle } from '../utils';
   import { computed, nextTick, onUnmounted, ref, watch } from 'vue';
-  import { BUNDLE_MONITOR_ROWS, DEVICE_MONITOR_ROWS, FIBER_CHART_DATA, SENSOR_GROUPS } from '../data';
   import type { FiberChartData, SensorGroup } from '../data';
   import { getMonitorDeviceList } from '../monitorDevice.api';
   import { getSafetyEvaluationByScene } from '../safetyEvaluation.api';
@@ -270,109 +230,33 @@
     }
   }
 
-  const sceneSeed = computed(() => {
-    const key = `${props.manageSystemId || ''}|${props.sceneName || ''}`;
-    let hash = 0;
-    for (let i = 0; i < key.length; i++) {
-      hash = (hash * 31 + key.charCodeAt(i)) >>> 0;
-    }
-    return hash;
-  });
-
-  function rotate<T>(list: T[], offset: number): T[] {
-    if (!list.length) return list;
-    const start = offset % list.length;
-    return [...list.slice(start), ...list.slice(0, start)];
-  }
-
-  const fallbackSensorGroups = computed<SensorGroup[]>(() => rotate(SENSOR_GROUPS, sceneSeed.value % Math.max(1, SENSOR_GROUPS.length)));
-
-  const fallbackBundleRows = computed<BundleDisplayRow[]>(() => {
-    const rows = rotate(BUNDLE_MONITOR_ROWS, sceneSeed.value % Math.max(1, BUNDLE_MONITOR_ROWS.length));
-    return rows.map((row, index) => ({
-      deviceNo: row.deviceNo,
-      o2: (Number(row.o2) + ((sceneSeed.value + index) % 3) * 0.01).toFixed(2),
-      co: String(row.co),
-      co2: row.co2,
-      ch4: row.ch4,
-      c2h4: row.c2h4,
-      c2h2: row.c2h2,
-      temp: '--',
-    }));
-  });
-
-  const deviceRows = computed(() => {
-    const rows = rotate(DEVICE_MONITOR_ROWS, sceneSeed.value % Math.max(1, DEVICE_MONITOR_ROWS.length));
-    return rows.map((row, index) => ({
-      ...row,
-      temp: (Number(row.temp) + ((sceneSeed.value + index) % 3) * 0.1).toFixed(1),
-    }));
-  });
-
-  const fallbackFiberData = computed<FiberChartData>(() => {
-    const delta = (sceneSeed.value % 7) * 12;
-    return {
-      ...FIBER_CHART_DATA,
-      solid: FIBER_CHART_DATA.solid.map((value) => Math.min(1000, value + delta)),
-      dashed: FIBER_CHART_DATA.dashed.map((value) => Math.min(1000, value + delta)),
-    };
-  });
-
-  const fallbackClosedRows = computed<ClosedMonitorRow[]>(() =>
-    deviceRows.value.map((row) => ({
-      deviceNo: row.deviceNo,
-      o2: row.o2,
-      co: row.co,
-      co2: row.co2,
-      ch4: row.ch4,
-      c2h4: '--',
-      c2h2: '--',
-      temp: row.temp,
-    }))
-  );
-
-  const apiSensorGroups = ref<SensorGroup[] | null>(null);
-  const apiBundleRows = ref<BundleDisplayRow[] | null>(null);
-  const apiFiberSeries = ref<FiberSeriesOption[] | null>(null);
-  const apiClosedRows = ref<ClosedMonitorRow[] | null>(null);
+  const apiSensorGroups = ref<SensorGroup[]>([]);
+  const apiBundleRows = ref<BundleDisplayRow[]>([]);
+  const apiFiberSeries = ref<FiberSeriesOption[]>([]);
+  const apiClosedRows = ref<ClosedMonitorRow[]>([]);
   const selectedFiberId = ref('');
 
   const fiberOptions = computed(() =>
-    (apiFiberSeries.value || []).map((item) => ({
+    apiFiberSeries.value.map((item) => ({
       label: item.name,
       value: item.id,
       score: 0,
     }))
   );
 
-  const displaySensorGroups = computed(() => apiSensorGroups.value ?? fallbackSensorGroups.value);
-  const displayBundleRows = computed(() => apiBundleRows.value ?? fallbackBundleRows.value);
-  const displayFiberData = computed(() => {
+  const displaySensorGroups = computed(() => apiSensorGroups.value);
+  const displayBundleRows = computed(() => apiBundleRows.value);
+  const displayFiberData = computed<FiberChartData>(() => {
     const series = apiFiberSeries.value;
-    if (series !== null) {
-      if (!series.length) {
-        return { categories: [], solid: [], dashed: [], peakIndex: 0 } as FiberChartData;
-      }
-      return (series.find((item) => item.id === selectedFiberId.value) ?? series[0]).data;
-    }
-    return fallbackFiberData.value;
+    if (!series.length) return { categories: [], solid: [], dashed: [], peakIndex: 0 };
+    return (series.find((item) => item.id === selectedFiberId.value) ?? series[0]).data;
   });
-  const displayClosedRows = computed(() => apiClosedRows.value ?? fallbackClosedRows.value);
+  const displayClosedRows = computed(() => apiClosedRows.value);
   const selectedFiberName = computed(() => {
     const series = apiFiberSeries.value;
-    if (!series?.length) return '光纤测温';
+    if (!series.length) return '光纤测温';
     return (series.find((item) => item.id === selectedFiberId.value) ?? series[0]).name;
   });
-  const fiberStats = computed(() => {
-    const values = displayFiberData.value.solid.filter((value) => Number.isFinite(Number(value))).map(Number);
-    if (!values.length) return null;
-    const total = values.reduce((sum, value) => sum + value, 0);
-    return {
-      max: Math.max(...values).toFixed(1),
-      min: Math.min(...values).toFixed(1),
-      avg: (total / values.length).toFixed(1),
-    };
-  });
 
   let deviceTimer: number | undefined;
   let deviceRequestSeq = 0;
@@ -416,33 +300,25 @@
       });
   }
 
-  function buildSensorGroups(devices: any[]): SensorGroup[] {
-    const pick = (type: string) => devices.filter((device) => device?.deviceType === type);
-    const o2List = pick('modelsensor_o2');
-    const coList = pick('modelsensor_co');
-    const tempList = pick('modelsensor_temperature');
-    if (!o2List.length && !coList.length && !tempList.length) return [];
-
-    const maxLength = Math.min(2, Math.max(o2List.length, coList.length, tempList.length));
-    return Array.from({ length: maxLength }, (_, index) => {
-      const o2 = o2List[index];
-      const co = coList[index];
-      const temp = tempList[index];
-      const sourceName = (device: any) => device?.strname || device?.strinstallpos || '';
-      const rowLabel = (label: string, device: any) => {
-        const source = sourceName(device);
-        return source ? `${label} · ${source}` : label;
-      };
-
-      return {
-        name: `监测组#${index + 1}`,
+  /** 安全监控传感器(safetymonitor_normal):组标题取当前场景名称,行标题取设备安装位置 */
+  function buildSensorGroups(devices: any[], sceneName: string): SensorGroup[] {
+    const pick = (match: (read: any) => boolean) => devices.find((device) => match(device?.readData || {}));
+    const o2 = pick((read) => read.o2val !== undefined);
+    const ch4 = pick((read) => read.ch4val !== undefined || read.gasval !== undefined);
+    const temp = pick((read) => read.temperature !== undefined || read.temp !== undefined);
+    if (!o2 && !ch4 && !temp) return [];
+
+    const rowLabel = (device: any, fallback: string) => device?.strinstallpos || device?.strname || fallback;
+    return [
+      {
+        name: sceneName || '传感器监测',
         rows: [
-          { label: rowLabel('氧气', o2), value: withUnit(o2?.readData?.o2val, '%', 2) },
-          { label: rowLabel('一氧化碳', co), value: withUnit(co?.readData?.coval, 'ppm', 2) },
-          { label: rowLabel('温度', temp), value: withUnit(temp?.readData?.temperature, '℃', 1) },
+          { label: rowLabel(o2, '回风隅角氧气'), value: withUnit(o2?.readData?.o2val, '%', 2) },
+          { label: rowLabel(ch4, '回风巷甲烷'), value: withUnit(ch4?.readData?.gasval ?? ch4?.readData?.ch4val, '%', 2) },
+          { label: rowLabel(temp, '回风巷温度'), value: withUnit(temp?.readData?.temperature ?? temp?.readData?.temp, '℃', 1) },
         ],
-      };
-    });
+      },
+    ];
   }
 
   function buildFiberSeries(devices: any[]): FiberSeriesOption[] {
@@ -476,7 +352,6 @@
         id: String(device.deviceID ?? device.strname ?? `fiber-${deviceIndex}`),
         name: device.strname || device.strinstallpos || `光纤${deviceIndex + 1}`,
         data: {
-          ...FIBER_CHART_DATA,
           categories: validPoints.map((point) => `测点${point.pos}`),
           solid,
           dashed: [],
@@ -534,14 +409,42 @@
   }
 
   function resetMonitorDeviceData() {
+    // 密闭监测数据与场景无关,不随场景切换清空
     apiBundleRows.value = [];
     apiSensorGroups.value = [];
     apiFiberSeries.value = [];
-    apiClosedRows.value = [];
     selectedFiberId.value = '';
     resetScrollPositions();
   }
 
+  /** 解析设备清单接口的返回(兼容 records 与 msgTxt 两种结构) */
+  function extractDeviceRows(res: any): any[] {
+    const payload = res?.result ?? res;
+    if (Array.isArray(payload?.records)) return payload.records;
+    if (Array.isArray(payload?.msgTxt)) {
+      return payload.msgTxt.reduce<any[]>((all, group) => all.concat(Array.isArray(group?.datalist) ? group.datalist : []), []);
+    }
+    return [];
+  }
+
+  /** 按系统统一的设备清单写法查询密闭设备(无需按场景过滤) */
+  async function loadClosedDevices(): Promise<any[]> {
+    try {
+      const res: any = await getMonitorDeviceList({ devicetype: 'obfurage_normal', pageSize: 10000 });
+      return extractDeviceRows(res);
+    } catch {
+      return [];
+    }
+  }
+
+  /** 密闭监测:数据与场景无关,只在进入页面和轮询时刷新 */
+  async function loadClosedMonitor() {
+    const devices = await loadClosedDevices();
+    if (devices.length) {
+      apiClosedRows.value = buildClosedRows(devices);
+    }
+  }
+
   async function loadMonitorDevices() {
     const systemID = props.manageSystemId;
     if (!systemID) return;
@@ -553,8 +456,7 @@
 
       const groups = getSceneGroups(res);
       apiBundleRows.value = buildBundleRows(collectGroupDevices(groups, 'bundletube'));
-      apiSensorGroups.value = buildSensorGroups(collectGroupDevices(groups, 'modelsensor'));
-      apiClosedRows.value = buildClosedRows(collectGroupDevices(groups, 'obfurage'));
+      apiSensorGroups.value = buildSensorGroups(collectGroupDevices(groups, 'safetymonitor'), props.sceneName || '');
 
       const series = buildFiberSeries(collectGroupDevices(groups, 'fiber'));
       apiFiberSeries.value = series;
@@ -569,6 +471,7 @@
     deviceTimer = window.setInterval(() => {
       if (document.hidden) return;
       void loadMonitorDevices();
+      void loadClosedMonitor();
       void loadStageEvaluation();
     }, 60000);
   }
@@ -597,6 +500,7 @@
       if (active) {
         resetScrollPositions();
         void loadMonitorDevices();
+        void loadClosedMonitor();
         void loadStageEvaluation();
         startPolling();
       } else {
@@ -612,6 +516,8 @@
 </script>
 
 <style lang="less" scoped>
+@import '/@/views/vent/home/selfIgnition/scrollbar.less';
+
   .monitor-view {
     position: absolute;
     inset: 0;
@@ -648,17 +554,17 @@
     flex-direction: column;
     align-items: center;
     height: 100%;
-    padding: 74px 3px 12px;
+    padding: 50px 4px 0;
     box-sizing: border-box;
   }
 
   .stage-emblem {
     position: absolute;
     left: 50%;
-    top: 14px;
+    top: 6px;
     z-index: 2;
     width: 110px;
-    height: 56px;
+    height: 46px;
     transform: translateX(-50%);
   }
 
@@ -672,8 +578,8 @@
     position: absolute;
     left: 50%;
     top: 50%;
-    width: 34px;
-    height: 42px;
+    width: 30px;
+    height: 36px;
     transform: translate(-50%, -50%);
     filter: drop-shadow(0 0 8px rgba(61, 246, 255, 0.9)) brightness(1.12);
   }
@@ -686,33 +592,35 @@
     flex-direction: column;
     align-items: center;
     width: 347px;
-    height: 137px;
-    padding-top: 14px;
+    height: 122px;
+    padding-top: 20px;
     box-sizing: border-box;
     background: url('@/assets/images/vent/home/selfIgnition/2-2.png') no-repeat center / 100% 100%;
   }
 
   .stage-level {
-    font-size: 38px;
-    font-weight: 700;
-    line-height: 40px;
-    letter-spacing: 2px;
+    font-size: 24px;
+    letter-spacing: 1px;
     white-space: nowrap;
-    text-shadow: 0 0 14px currentColor;
+    font-family: 'douyuFont';
   }
 
   .stage-eval-button {
-    width: 302px;
-    height: 72px;
-    margin-top: 6px;
+    position: absolute;
+    left: 50%;
+    bottom: 12px;
+    width: 280px;
+    height: 64px;
+    margin: 0;
     padding: 0;
     border: none;
     background: url('@/assets/images/vent/home/selfIgnition/2-3.png') no-repeat center / 100% 100%;
     color: #fff;
-    font-size: 20px;
+    font-size: 18px;
     letter-spacing: 1px;
     cursor: pointer;
     transition: filter 0.2s;
+    transform: translateX(-50%);
 
     &:hover {
       filter: brightness(1.12);
@@ -723,12 +631,13 @@
     display: flex;
     flex: 1;
     flex-direction: column;
-    width: 100%;
+    width: 364px;
     min-height: 0;
-    margin-top: 14px;
-    padding: 0 12px 12px;
     box-sizing: border-box;
+    overflow-x: hidden;
+    overflow-y: auto;
     background: url('@/assets/images/vent/home/selfIgnition/2-5.png') no-repeat center / 100% 100%;
+    .scrollbar-cyan();
   }
 
   .stage-warning {
@@ -736,33 +645,33 @@
     flex: none;
     align-items: flex-start;
     gap: 0;
-    min-height: 128px;
-    padding: 20px 4px 0 16px;
+    min-height: 108px;
+    padding: 8px 4px 0 24px;
     box-sizing: border-box;
   }
 
   .stage-warning-icon {
     position: relative;
     flex: none;
-    width: 90px;
-    height: 92px;
+    width: 92px;
+    height: 94px;
   }
 
   .stage-warning-pedestal {
     position: absolute;
     left: 0;
     bottom: 0;
-    width: 90px;
-    height: 92px;
+    width: 92px;
+    height: 94px;
     filter: drop-shadow(0 0 10px rgba(61, 246, 255, 0.35));
   }
 
   .stage-warning-bolt {
     position: absolute;
     left: 50%;
-    top: 12px;
-    width: 46px;
-    height: 46px;
+    top: 14px;
+    width: 44px;
+    height: 44px;
     transform: translateX(-50%);
     filter: drop-shadow(0 0 8px rgba(61, 246, 255, 0.65));
   }
@@ -771,90 +680,100 @@
     display: flex;
     flex-direction: column;
     min-width: 0;
+    margin-top: 16px;
     margin-left: 4px;
   }
 
   .stage-warning-title {
     color: #fff;
-    font-size: 17px;
-    font-weight: 600;
+    font-size: 16px;
     letter-spacing: 0.5px;
     white-space: nowrap;
   }
 
   .stage-warning-level {
-    margin-top: 26px;
-    padding-left: 12px;
-    font-size: 28px;
-    font-weight: 700;
-    font-style: italic;
-    line-height: 28px;
-    letter-spacing: 2px;
-    text-shadow: 0 0 12px currentColor;
+    margin-top: 14px;
+    font-size: 16px;
+    line-height: 22px;
+    letter-spacing: 1px;
+    font-family: 'douyuFont';
   }
 
   .stage-advice {
     position: relative;
     flex: none;
-    height: 125px;
+    width: 280px;
+    height: 110px;
     min-height: 0;
-    margin: 4px 18px 0;
+    margin: 6px auto 0;
+    box-sizing: border-box;
+    border: 1px solid rgba(61, 246, 255, 0.55);
+    background: url('@/assets/images/vent/home/selfIgnition/2-6.png') no-repeat center / 100% 100%;
+
+    // 与上方预警信息之间的分隔线
+    &::before {
+      content: '';
+      position: absolute;
+      left: -2px;
+      right: -2px;
+      top: -12px;
+      height: 1px;
+      background: rgba(61, 246, 255, 0.5);
+    }
   }
 
   .stage-advice-title {
     position: absolute;
     z-index: 2;
+    top: -1px;
+    left: -1px;
     display: flex;
     align-items: center;
-    width: 146px;
-    height: 24px;
-    padding-left: 12px;
+    width: 132px;
+    height: 22px;
+    padding-left: 10px;
     box-sizing: border-box;
     background: url('@/assets/images/vent/home/selfIgnition/2-7.png') no-repeat center / 100% 100%;
     color: #fff;
-    font-size: 15px;
+    font-size: 14px;
     font-weight: 600;
     letter-spacing: 0.5px;
   }
 
   .stage-advice-content {
-    position: relative;
-    height: 100%;
-    padding: 34px 14px 12px 42px;
+    // 从标题下方开始滚动,避免滚动时文字钻到标题签下面
+    position: absolute;
+    left: 0;
+    right: 0;
+    top: 26px;
+    bottom: 0;
+    padding: 4px 8px 6px 34px;
     box-sizing: border-box;
-    overflow: hidden;
-    background: url('@/assets/images/vent/home/selfIgnition/2-6.png') no-repeat center / 100% 100%;
-    scrollbar-width: none;
-
-    &::-webkit-scrollbar {
-      display: none;
-    }
+    overflow-x: hidden;
+    overflow-y: auto;
+    .scrollbar-cyan();
 
     p {
       margin: 0;
       color: #fff;
-      font-size: 14px;
-      line-height: 23px;
+      font-size: 15px;
+      line-height: 22px;
       letter-spacing: 0.3px;
     }
   }
 
   .stage-advice-icon {
     position: absolute;
-    left: 18px;
-    top: 40px;
-    width: 9px;
-    height: 9px;
-    background: #f5222d;
-    box-shadow: 0 0 9px rgba(245, 34, 45, 0.85);
-    transform: rotate(45deg);
+    left: 12px;
+    top: 8px;
+    width: 12px;
+    height: 12px;
+    border-radius: 50%;
+    background: #e8362f;
+    box-shadow: 0 0 10px rgba(232, 54, 47, 0.85);
   }
 
   /* 光纤测温 */
-  :deep(.fiber-module .box1-center) {
-    height: calc(100% - 35px) !important;
-  }
-
   .fiber-panel {
     display: flex;
     flex-direction: column;
@@ -934,16 +853,19 @@
     height: 100%;
     padding: 8px 14px 0;
     box-sizing: border-box;
-    overflow: hidden;
+    overflow-x: hidden;
+    overflow-y: auto;
+    background: rgba(8, 66, 116, 0.45);
+    .scrollbar-cyan();
   }
 
   .sensor-group {
     position: relative;
     flex: none;
-    padding-top: 7px;
+    padding-top: 0;
 
     & + & {
-      margin-top: 4px;
+      margin-top: 8px;
     }
 
     &::before {
@@ -952,32 +874,30 @@
       top: 0;
       left: 0;
       right: 0;
-      height: 1px;
-      background: linear-gradient(
-        90deg,
-        rgba(61, 246, 255, 0.04),
-        rgba(61, 246, 255, 0.95) 12%,
-        rgba(61, 246, 255, 0.95) 88%,
-        rgba(61, 246, 255, 0.04)
-      );
-      box-shadow: 0 0 7px rgba(61, 246, 255, 0.45);
+      height: 2px;
+      background: #2cb6ff;
     }
   }
 
   .sensor-group-title {
     position: relative;
-    display: flex;
+    display: inline-flex;
     align-items: center;
-    width: 128px;
+    width: auto;
+    min-width: 128px;
+    max-width: 100%;
     height: 27px;
-    padding-left: 6px;
+    padding: 0 22px 0 15px;
     box-sizing: border-box;
     background: url('@/assets/images/vent/home/selfIgnition/3-1.png') no-repeat center / 100% 100%;
     color: #3df6ff;
-    font-size: 17px;
+    font-size: 18px;
     font-weight: 700;
     line-height: 27px;
     letter-spacing: 1px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
     text-shadow: 0 0 8px rgba(61, 246, 255, 0.45);
   }
 
@@ -985,13 +905,12 @@
     display: flex;
     align-items: center;
     justify-content: space-between;
-    height: 28px;
-    margin-top: 4px;
-    padding: 0 12px;
+    height: 34px;
+    margin-top: 6px;
+    padding: 0 18px;
     box-sizing: border-box;
-    border: 1px solid rgba(61, 246, 255, 0.16);
-    background: linear-gradient(90deg, rgba(9, 64, 104, 0.96), rgba(6, 45, 75, 0.9));
-    box-shadow: inset 0 0 8px rgba(61, 246, 255, 0.05);
+    border: none;
+    background: linear-gradient(90deg, rgba(14, 63, 109, 0.95), rgba(8, 49, 89, 0.9));
     color: #fff;
     font-size: 16px;
 
@@ -1010,21 +929,13 @@
       margin-left: 8px;
       color: #3df6ff;
       font-style: normal;
-      font-size: 16px;
+      font-size: 12px;
+      font-family: 'douyuFont';
       text-shadow: 0 0 8px rgba(61, 246, 255, 0.45);
     }
   }
 
-  /* 底部宽表格面板:使用短边框图,保证标题和装饰点居中 */
-  :deep(.wide-module .box1-top) {
-    --image-box1-top: url('@/assets/images/vent/border/box1-top.png');
-    background-image: var(--image-box1-top) !important;
-  }
-
-  :deep(.wide-module .box1-bottom) {
-    --image-box1-bottom: url('@/assets/images/vent/border/box1-bottom.png');
-    background-image: var(--image-box1-bottom) !important;
-  }
+  /* 底部宽表格面板:使用宽版边框图 */
 
   .table-panel {
     display: flex;
@@ -1042,21 +953,7 @@
     overflow-y: auto;
     border: 1px solid rgba(61, 246, 255, 0.55);
     background: rgba(6, 30, 52, 0.2);
-    scrollbar-width: thin;
-    scrollbar-color: rgba(61, 246, 255, 0.75) transparent;
-
-    &::-webkit-scrollbar {
-      width: 8px;
-    }
-
-    &::-webkit-scrollbar-track {
-      background: transparent;
-    }
-
-    &::-webkit-scrollbar-thumb {
-      border-radius: 4px;
-      background: linear-gradient(180deg, #3df6ff 0%, #0d7fc1 100%);
-    }
+    .scrollbar-cyan();
   }
 
   .table-head,

+ 157 - 136
src/views/vent/home/selfIgnition/components/OverviewView.vue

@@ -1,37 +1,25 @@
 <template>
   <div class="ignition-view overview-view">
     <!-- 左:矿井采空区概览 -->
-    <ModuleCommon
-      class="overview-module"
-      :show-style="panelShowStyle(20, 114, 393, 576)"
-      :module-data="EMPTY_MODULE_DATA"
-      module-name="矿井采空区概览"
-      device-type="ignition_goaf_overview"
-      :data="{}"
-      :visible="true"
-    >
-      <div class="overview-panel">
-        <div v-for="item in overview" :key="item.label" class="goaf-item">
+    <Module class="overview-module" :x="20" :y="98" :width="393" :height="495" title="矿井采空区概览">
+      <div v-if="overview.length" class="overview-panel">
+        <div v-for="(item, index) in overview" :key="item.label" class="goaf-item">
           <span class="goaf-icon">
-            <img src="@/assets/images/vent/home/selfIgnition/goaf-icon.svg" alt="" />
+            <img :src="goafIcon(item.label, index)" alt="" />
           </span>
           <span class="goaf-label">{{ item.label }}:</span>
           <span class="goaf-value">{{ item.value }}</span>
         </div>
       </div>
-    </ModuleCommon>
+      <div v-else class="module-empty">
+        <img src="@/assets/images/vent/no-data.png" alt="暂无数据" />
+        <span>暂无数据</span>
+      </div>
+    </Module>
 
     <!-- 左下:安装设备情况 -->
-    <ModuleCommon
-      class="install-module"
-      :show-style="panelShowStyle(20, 722, 393, 336)"
-      :module-data="EMPTY_MODULE_DATA"
-      module-name="安装设备情况"
-      device-type="ignition_device_install"
-      :data="{}"
-      :visible="true"
-    >
-      <div class="install-panel">
+    <Module class="install-module" :x="20" :y="620" :width="393" :height="289" title="安装设备情况">
+      <div v-if="devices.length" class="install-panel">
         <div
           v-for="(device, index) in devices"
           :key="`${device.name}-${index}`"
@@ -52,19 +40,15 @@
           >
         </div>
       </div>
-    </ModuleCommon>
+      <div v-else class="module-empty">
+        <img src="@/assets/images/vent/no-data.png" alt="暂无数据" />
+        <span>暂无数据</span>
+      </div>
+    </Module>
 
     <!-- 右下:分级预警表 -->
-    <ModuleCommon
-      class="table-module"
-      :show-style="panelShowStyle(433, 722, 1465, 336)"
-      :module-data="EMPTY_MODULE_DATA"
-      module-name="矿井采空区分级预警"
-      device-type="ignition_warning_table"
-      :data="{}"
-      :visible="true"
-    >
-      <div class="warning-table-panel">
+    <Module class="table-module" :x="433" :y="620" :width="1465" :height="289" title="矿井采空区分级预警">
+      <div v-if="warnings.length" class="warning-table-panel">
         <div class="warning-table">
           <div class="table-head">
             <span class="col-index">序号</span>
@@ -74,16 +58,24 @@
             <span class="col-safety">安全评价等级</span>
           </div>
           <div class="table-body">
-            <div v-for="row in warnings" :key="row.id" class="table-row">
+            <div
+              v-for="row in warnings"
+              :key="row.id"
+              class="table-row"
+              :class="{ 'table-row-alert': !!warnRowRgb(row.level, row.safetyLevel) }"
+              :style="warnRowRgb(row.level, row.safetyLevel) ? { '--warn-rgb': warnRowRgb(row.level, row.safetyLevel) } : undefined"
+            >
               <span class="col-index">{{ row.id }}</span>
               <span class="col-name">{{ row.name }}</span>
               <span class="col-seam">{{ row.seam }}</span>
               <span class="col-level level-cell">
-                <img v-if="row.level && row.level !== '未预警'" class="level-icon warning-icon" :src="warningLevelIcon" alt="" />
+                <img v-if="isHighLevel(row.level)" class="level-icon warning-icon" :src="warningLevelIcon" alt="" />
+                <img v-else-if="isMidLevel(row.level)" class="level-icon warning-icon" :src="warningLevelOrangeIcon" alt="" />
                 <span :title="row.level">{{ row.level }}</span>
               </span>
               <span class="col-safety level-cell">
-                <img v-if="showSafetyIcon(row.safetyLevel || row.level)" class="level-icon safety-icon" :src="safetyLevelIcon" alt="" />
+                <img v-if="isHighLevel(row.safetyLevel || row.level)" class="level-icon safety-icon" :src="safetyLevelIcon" alt="" />
+                <img v-else-if="isMidLevel(row.safetyLevel || row.level)" class="level-icon safety-icon" :src="safetyLevelOrangeIcon" alt="" />
                 <span :title="row.safetyLevel || row.level">{{ row.safetyLevel || row.level }}</span>
               </span>
             </div>
@@ -91,20 +83,43 @@
           </div>
         </div>
       </div>
-    </ModuleCommon>
+      <div v-else class="module-empty">
+        <img src="@/assets/images/vent/no-data.png" alt="暂无数据" />
+        <span>暂无数据</span>
+      </div>
+    </Module>
   </div>
 </template>
 
 <script lang="ts" setup>
   import { computed } from 'vue';
-  import ModuleCommon from '../../configurable/components/ModuleCommon.vue';
-  import { EMPTY_MODULE_DATA, panelShowStyle } from '../utils';
+  import Module from './Module.vue';
   import type { DeviceInstallItem, GoafOverviewItem, WarningRow } from '../data';
   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 warningLevelIcon from '@/assets/images/vent/home/selfIgnition/level-warning.svg';
   import safetyLevelIcon from '@/assets/images/vent/home/selfIgnition/level-safety.svg';
+  import warningLevelOrangeIcon from '@/assets/images/vent/home/selfIgnition/level-warning-orange.svg';
+  import safetyLevelOrangeIcon from '@/assets/images/vent/home/selfIgnition/level-safety-orange.svg';
+  import mineGoafIcon from '@/assets/images/vent/home/selfIgnition/goaf-stat-total.svg';
+  import closedGoafIcon from '@/assets/images/vent/home/selfIgnition/goaf-stat-closed.svg';
+  import withdrawGoafIcon from '@/assets/images/vent/home/selfIgnition/goaf-stat-withdraw.svg';
+  import miningGoafIcon from '@/assets/images/vent/home/selfIgnition/goaf-stat-mining.svg';
+  import stoppedGoafIcon from '@/assets/images/vent/home/selfIgnition/goaf-stat-stopped.svg';
+
+  const GOAF_ICONS: Record<string, string> = {
+    矿井采空区总数: mineGoafIcon,
+    已封闭采空区数: closedGoafIcon,
+    回撤工作面采空区数: withdrawGoafIcon,
+    回采工作面采空区数: miningGoafIcon,
+    停产工作面采空区数: stoppedGoafIcon,
+  };
+  const GOAF_ICON_LIST = [mineGoafIcon, closedGoafIcon, withdrawGoafIcon, miningGoafIcon, stoppedGoafIcon];
+
+  function goafIcon(label: string, index: number) {
+    return GOAF_ICONS[label] || GOAF_ICON_LIST[index] || mineGoafIcon;
+  }
 
   const props = defineProps<{
     overview: GoafOverviewItem[];
@@ -122,8 +137,20 @@
     return DEVICE_ICONS[name] || '';
   }
 
-  function showSafetyIcon(level: string | undefined) {
-    return !!level && level !== '安全' && level !== '未评价';
+  function isHighLevel(level: string | undefined) {
+    return !!level && level.includes('高风险');
+  }
+
+  function isMidLevel(level: string | undefined) {
+    return !!level && /中(等)?风险/.test(level);
+  }
+
+  /** 整行预警框颜色:预警等级与安全评价等级取较高者,只有高风险(红)、中风险(橙)显示 */
+  function warnRowRgb(warningLevel: string | undefined, safetyLevel: string | undefined) {
+    const levels = [warningLevel, safetyLevel];
+    if (levels.some(isHighLevel)) return '255, 77, 79';
+    if (levels.some(isMidLevel)) return '255, 157, 28';
+    return '';
   }
 
   // 表体固定 5 行视口,数据不足时用空行补足;超过 5 行时在表体内部滚动
@@ -131,96 +158,113 @@
 </script>
 
 <style lang="less" scoped>
+  @import '/@/views/vent/home/selfIgnition/scrollbar.less';
+
   .overview-view {
     position: absolute;
     inset: 0;
   }
 
-  :deep(.overview-module .box1-center),
-  :deep(.install-module .box1-center),
-  :deep(.table-module .box1-center) {
-    height: calc(100% - 35px) !important;
-  }
-
-  .overview-panel {
+  .module-empty {
     display: flex;
     flex-direction: column;
-    justify-content: space-evenly;
-    gap: 24px;
+    align-items: center;
+    justify-content: center;
+    gap: 8px;
+    width: 100%;
     height: 100%;
-    padding: 16px 10px;
+    padding: 8px;
     box-sizing: border-box;
-    overflow-x: hidden;
-    overflow-y: auto;
-    scrollbar-gutter: stable;
-    scrollbar-width: thin;
-    scrollbar-color: rgba(61, 246, 255, 0.75) transparent;
 
-    &::-webkit-scrollbar {
-      width: 8px;
+    img {
+      max-width: 70%;
+      max-height: 80%;
+      object-fit: contain;
     }
 
-    &::-webkit-scrollbar-track {
-      background: transparent;
+    span {
+      color: #8fc7e8;
+      font-size: 13px;
+      letter-spacing: 1px;
     }
+  }
 
-    &::-webkit-scrollbar-thumb {
-      border-radius: 4px;
-      background: linear-gradient(180deg, #3df6ff 0%, #0d7fc1 100%);
-    }
+  .overview-panel {
+    display: flex;
+    flex-direction: column;
+    gap: 31px;
+    height: 100%;
+    padding: 31px 23px 2px;
+    box-sizing: border-box;
+    overflow: hidden;
   }
 
   .goaf-item {
-    position: relative;
     display: flex;
-    align-items: center;
     flex: none;
-    height: 48px;
-    padding: 0 14px 0 60px;
+    align-items: center;
+    height: 52px;
+    padding: 0 15px 0 2px;
     box-sizing: border-box;
-    border: none;
     border-radius: 5px;
-    overflow: hidden;
-    background-color: #072b49;
-    background-image: url('@/assets/images/vent/home/selfIgnition/card-bg.png');
-    background-repeat: no-repeat;
-    background-size: 100% 100%;
+    background: url('@/assets/images/vent/home/selfIgnition/goaf-card-bg.png') no-repeat center / 100% 100%;
   }
 
   .goaf-icon {
-    position: absolute;
-    left: 2px;
-    width: 50px;
-    height: 50px;
-    background: url('@/assets/images/vent/home/selfIgnition/list-icon-bg.png') no-repeat center / 100% 100%;
+    position: relative;
     display: flex;
+    flex: none;
     align-items: center;
     justify-content: center;
+    width: 45px;
+    height: 45px;
+    box-sizing: border-box;
+    border: 1px solid transparent;
+    border-radius: 50%;
+    background: linear-gradient(180deg, #3df6ff 0%, #2fc3d8 100%) border-box;
+    box-shadow: 0 0 10px rgba(61, 246, 255, 0.5);
+
+    // 圆环内侧深色底
+    &::before {
+      content: '';
+      position: absolute;
+      inset: 2px;
+      border-radius: 50%;
+      background: radial-gradient(circle at 50% 35%, #16587f 0%, #0a2f4d 78%);
+    }
 
     img {
-      width: 20px;
-      height: 15px;
+      position: relative;
+      z-index: 1;
+      width: auto;
+      max-width: 26px;
+      height: auto;
+      max-height: 22px;
     }
   }
 
   .goaf-label {
     flex: 1;
+    min-width: 0;
+    margin-left: 18px;
+    overflow: hidden;
     color: #fff;
     font-size: 16px;
-    font-weight: 600;
-    letter-spacing: 0;
+    letter-spacing: 1px;
+    white-space: nowrap;
+    text-overflow: ellipsis;
   }
 
   .goaf-value {
-    display: inline-block;
-    color: #eaffff;
-    font-family: Arial, sans-serif;
-    font-size: 19px;
-    font-weight: 800;
+    flex: none;
+    margin-left: 8px;
+    color: #fff;
+    font-family: 'douyuFont';
+    font-size: 16px;
     font-style: italic;
     line-height: 1;
     letter-spacing: 1px;
-    transform: scaleX(1.2);
+    transform: scaleX(1.15);
     transform-origin: right center;
   }
 
@@ -233,21 +277,7 @@
     box-sizing: border-box;
     overflow-x: hidden;
     overflow-y: auto;
-    scrollbar-width: thin;
-    scrollbar-color: rgba(61, 246, 255, 0.75) transparent;
-
-    &::-webkit-scrollbar {
-      width: 8px;
-    }
-
-    &::-webkit-scrollbar-track {
-      background: transparent;
-    }
-
-    &::-webkit-scrollbar-thumb {
-      border-radius: 4px;
-      background: linear-gradient(180deg, #3df6ff 0%, #0d7fc1 100%);
-    }
+    .scrollbar-cyan();
   }
 
   .install-item {
@@ -258,7 +288,7 @@
     display: flex;
     align-items: center;
     flex: none;
-    height: 69px;
+    height: 48px;
     padding: 0 20px 0 12px;
     box-sizing: border-box;
 
@@ -299,7 +329,6 @@
     background: var(--install-name-bg) no-repeat center / 100% 100%;
     color: #fff;
     font-size: 15px;
-    font-weight: 600;
     letter-spacing: 0;
     white-space: nowrap;
   }
@@ -341,33 +370,19 @@
     display: flex;
     flex-direction: column;
     height: 100%;
-    padding: 23px 22px 0;
+    padding: 8px 22px 0;
     box-sizing: border-box;
   }
 
   .warning-table {
-    flex: none;
-    height: 251px;
+    flex: 1 1 auto;
+    height: 100%;
     min-height: 0;
     box-sizing: border-box;
     overflow-y: auto;
     border: 1px solid rgba(61, 246, 255, 0.55);
     background: rgba(6, 30, 52, 0.2);
-    scrollbar-width: thin;
-    scrollbar-color: rgba(61, 246, 255, 0.75) transparent;
-
-    &::-webkit-scrollbar {
-      width: 8px;
-    }
-
-    &::-webkit-scrollbar-track {
-      background: transparent;
-    }
-
-    &::-webkit-scrollbar-thumb {
-      border-radius: 4px;
-      background: linear-gradient(180deg, #3df6ff 0%, #0d7fc1 100%);
-    }
+    .scrollbar-cyan();
   }
 
   .table-head,
@@ -392,6 +407,8 @@
   }
 
   .table-row {
+    position: relative;
+    z-index: 0;
     height: 42px;
     box-sizing: border-box;
     color: #fff;
@@ -402,6 +419,21 @@
     &:nth-child(even) {
       background: #0d4167;
     }
+
+    // 按预警等级着色的整行预警框
+    &.table-row-alert::before {
+      content: '';
+      position: absolute;
+      z-index: -1;
+      inset: 2px;
+      border: 1px solid rgba(var(--warn-rgb), 0.95);
+      border-radius: 6px;
+      background: linear-gradient(180deg, rgba(var(--warn-rgb), 0.2), rgba(var(--warn-rgb), 0.04) 50%, rgba(var(--warn-rgb), 0.18));
+      box-shadow:
+        0 0 10px rgba(var(--warn-rgb), 0.45),
+        inset 0 0 14px rgba(var(--warn-rgb), 0.3);
+      pointer-events: none;
+    }
   }
 
   .table-row-empty {
@@ -456,15 +488,4 @@
     width: 12px;
     height: 15px;
   }
-
-  /* 表格面板较宽,使用短边框图拉伸,保证顶部/底部装饰点居中 */
-  :deep(.table-module .box1-top) {
-    --image-box1-top: url('@/assets/images/vent/border/box1-top.png');
-    background-image: var(--image-box1-top) !important;
-  }
-
-  :deep(.table-module .box1-bottom) {
-    --image-box1-bottom: url('@/assets/images/vent/border/box1-bottom.png');
-    background-image: var(--image-box1-bottom) !important;
-  }
 </style>

+ 1 - 2
src/views/vent/home/selfIgnition/components/PanelSelect.vue

@@ -185,9 +185,8 @@
     height: 34px;
     padding: 0 28px 0 10px;
     box-sizing: border-box;
-    border: 1px solid rgba(61, 246, 255, 0.42);
+    border: 1px solid #0199ea;
     border-radius: 4px;
-    background: url('@/assets/images/vent/home/selfIgnition/card-bg.png') no-repeat center / 100% 100%;
     color: #fff;
     font-size: 14px;
     cursor: pointer;

+ 1 - 514
src/views/vent/home/selfIgnition/data.ts

@@ -1,8 +1,5 @@
 /**
- * 张家峁煤自燃监测预警页面数据
- *
- * 当前阶段后端接口尚未提供,页面先使用本地模拟数据,
- * 后续接入接口时只需要替换本文件中的模拟常量即可。
+ * 张家峁煤自燃监测预警页面公共类型
  */
 
 export interface GoafOverviewItem {
@@ -16,14 +13,6 @@ export interface DeviceInstallItem {
   running: number;
 }
 
-export interface OverallWarning {
-  level: number;
-  levelText: string;
-  levelName: string;
-  total: number;
-  color: string;
-}
-
 export interface WarningRow {
   id: number;
   name: string;
@@ -33,430 +22,12 @@ export interface WarningRow {
   status?: string;
 }
 
-export interface AlertItem {
-  id: number;
-  time: string;
-  level: number;
-  location: string;
-  message: string;
-}
-
-export interface BundleRow {
-  point: string;
-  o2: string;
-  co: number;
-  ch4: string;
-  c2h4: string;
-  temp: number;
-  status: '正常' | '关注' | '预警';
-}
-
-export interface FiberTrend {
-  times: string[];
-  maxTemp: number[];
-  avgTemp: number[];
-  maxValue: number;
-  avgValue: number;
-  pointCount: number;
-}
-
-export interface RiskLevel {
-  level: number;
-  name: string;
-  range: string;
-  colorName: string;
-  color: string;
-}
-
 export interface ManualOption {
   label: string;
   value: string;
   score: number;
 }
 
-export interface ManualField {
-  key: string;
-  label: string;
-  value: string;
-  options: ManualOption[];
-}
-
-export interface ManualGroup {
-  key: string;
-  title: string;
-  fields: ManualField[];
-}
-
-export interface EvaluationResult {
-  score: number;
-  level: number;
-  levelText: string;
-  color: string;
-  highlightLevel: number;
-  target: string;
-  time: string;
-  source: string;
-  suggestion: string;
-}
-
-export const GOAF_OVERVIEW: GoafOverviewItem[] = [
-  { label: '矿井采空区总数', value: 28 },
-  { label: '已封闭采空区数', value: 6 },
-  { label: '回撤工作面采空区数', value: 4 },
-  { label: '回采工作面采空区数', value: 12 },
-  { label: '停产工作面采空区数', value: 6 },
-];
-
-export const INSTALLED_DEVICES: DeviceInstallItem[] = [
-  { name: '束管监测', installed: 15, running: 13 },
-  { name: '光纤测温', installed: 15, running: 13 },
-  { name: '密闭监测', installed: 15, running: 13 },
-];
-
-export const OVERALL_WARNING: OverallWarning = {
-  level: 4,
-  levelText: 'IV',
-  levelName: '高风险',
-  total: 12,
-  color: '#ff9d1c',
-};
-
-export const WARNING_ROWS: WarningRow[] = [
-  { id: 1, name: '12采空区', seam: '5-2煤', level: '一般风险', status: '回采中' },
-  { id: 2, name: '14采空区', seam: '5-2煤', level: '一般风险', status: '回撤中' },
-  { id: 3, name: '16采空区', seam: '5-1煤', level: '一般风险', status: '停产' },
-  { id: 4, name: '18采空区', seam: '5-2煤', level: '低风险', status: '回采中' },
-  { id: 5, name: '20采空区', seam: '5-1煤', level: '中等风险', status: '回采中' },
-  { id: 6, name: '22采空区', seam: '5-2煤', level: '低风险', status: '已封闭' },
-  { id: 7, name: '24采空区', seam: '4-2煤', level: '一般风险', status: '回采中' },
-  { id: 8, name: '26采空区', seam: '4-2煤', level: '低风险', status: '已封闭' },
-  { id: 9, name: '28采空区', seam: '5-1煤', level: '中等风险', status: '回撤中' },
-  { id: 10, name: '30采空区', seam: '5-2煤', level: '一般风险', status: '停产' },
-  { id: 11, name: '32采空区', seam: '4-2煤', level: '低风险', status: '已封闭' },
-  { id: 12, name: '34采空区', seam: '5-1煤', level: '一般风险', status: '回采中' },
-];
-
-export const ALERTS: AlertItem[] = [
-  { id: 1, time: '09:28:16', level: 4, location: '12采空区', message: '束管测点 CO 浓度上升至 24ppm' },
-  { id: 2, time: '09:16:42', level: 3, location: '20采空区', message: '密闭内 O₂ 浓度低于 15%,请关注' },
-  { id: 3, time: '08:57:03', level: 2, location: '14采空区', message: '光纤测温 3# 测点温度波动 1.8℃' },
-  { id: 4, time: '08:35:27', level: 4, location: '28采空区', message: '回风顺槽 CH₄ 浓度达到 0.86%' },
-  { id: 5, time: '08:12:51', level: 2, location: '18采空区', message: '风速传感器数据短时中断,已恢复' },
-];
-
-export const BUNDLE_ROWS: BundleRow[] = [
-  { point: '12采空区-1#', o2: '18.62', co: 16, ch4: '0.42', c2h4: '0.00', temp: 26.4, status: '正常' },
-  { point: '12采空区-2#', o2: '17.85', co: 24, ch4: '0.56', c2h4: '0.02', temp: 28.1, status: '关注' },
-  { point: '14采空区-1#', o2: '19.04', co: 9, ch4: '0.35', c2h4: '0.00', temp: 25.2, status: '正常' },
-  { point: '16采空区-1#', o2: '16.92', co: 31, ch4: '0.63', c2h4: '0.04', temp: 30.6, status: '预警' },
-  { point: '20采空区-1#', o2: '18.11', co: 18, ch4: '0.48', c2h4: '0.01', temp: 27.3, status: '正常' },
-  { point: '28采空区-1#', o2: '17.36', co: 27, ch4: '0.74', c2h4: '0.03', temp: 29.4, status: '关注' },
-];
-
-export const FIBER_TREND: FiberTrend = {
-  times: [
-    '00:00',
-    '01:00',
-    '02:00',
-    '03:00',
-    '04:00',
-    '05:00',
-    '06:00',
-    '07:00',
-    '08:00',
-    '09:00',
-    '10:00',
-    '11:00',
-    '12:00',
-    '13:00',
-    '14:00',
-    '15:00',
-    '16:00',
-    '17:00',
-    '18:00',
-    '19:00',
-    '20:00',
-    '21:00',
-    '22:00',
-    '23:00',
-  ],
-  maxTemp: [
-    24.1, 23.8, 23.6, 23.4, 23.5, 24.0, 25.2, 26.8, 28.4, 30.1, 31.6, 32.6, 33.1, 32.8, 32.2, 31.5, 30.6, 29.4, 28.2, 27.3, 26.6, 26.0, 25.4, 24.8,
-  ],
-  avgTemp: [
-    22.2, 22.0, 21.9, 21.8, 21.9, 22.1, 22.6, 23.2, 23.9, 24.5, 25.1, 25.6, 25.9, 25.8, 25.6, 25.3, 24.9, 24.5, 24.1, 23.8, 23.4, 23.1, 22.8, 22.5,
-  ],
-  maxValue: 33.1,
-  avgValue: 23.9,
-  pointCount: 15,
-};
-
-export const RISK_LEVELS: RiskLevel[] = [
-  { level: 1, name: 'I 低风险', range: '0-20', colorName: '绿色', color: '#7ed321' },
-  { level: 2, name: 'II 较低风险', range: '20-40', colorName: '蓝色', color: '#3df6ff' },
-  { level: 3, name: 'III 中等风险', range: '40-60', colorName: '黄色', color: '#ffd15c' },
-  { level: 4, name: 'IV 高风险', range: '60-80', colorName: '橙色', color: '#ff9d1c' },
-  { level: 5, name: 'V 极高风险', range: '80-100', colorName: '红色', color: '#ff4d4f' },
-];
-
-export const MANUAL_GROUPS: ManualGroup[] = [
-  {
-    key: 'pre',
-    title: '采前评价',
-    fields: [
-      {
-        key: 'spontaneous',
-        label: '自燃倾向性',
-        value: '自燃',
-        options: [
-          { label: '不易自燃', value: '不易自燃', score: 3 },
-          { label: '自燃', value: '自燃', score: 10 },
-          { label: '容易自燃', value: '容易自燃', score: 18 },
-        ],
-      },
-      {
-        key: 'sealQuality',
-        label: '封闭质量',
-        value: '一般',
-        options: [
-          { label: '好', value: '好', score: 2 },
-          { label: '一般', value: '一般', score: 8 },
-          { label: '差', value: '差', score: 15 },
-        ],
-      },
-      {
-        key: 'residualCoal',
-        label: '遗煤厚度(m)',
-        value: '1.0',
-        options: [
-          { label: '0.5', value: '0.5', score: 3 },
-          { label: '1.0', value: '1.0', score: 7 },
-          { label: '1.5', value: '1.5', score: 11 },
-          { label: '2.0', value: '2.0', score: 15 },
-        ],
-      },
-      {
-        key: 'advanceSpeed',
-        label: '推进速度(m/d)',
-        value: '3.5',
-        options: [
-          { label: '4.8', value: '4.8', score: 3 },
-          { label: '3.5', value: '3.5', score: 7 },
-          { label: '2.5', value: '2.5', score: 11 },
-          { label: '1.5', value: '1.5', score: 15 },
-        ],
-      },
-      {
-        key: 'airLeakage',
-        label: '漏风强度',
-        value: '中',
-        options: [
-          { label: '低', value: '低', score: 3 },
-          { label: '中', value: '中', score: 8 },
-          { label: '高', value: '高', score: 15 },
-        ],
-      },
-    ],
-  },
-  {
-    key: 'condition',
-    title: '开采条件',
-    fields: [
-      {
-        key: 'roof',
-        label: '顶板岩性',
-        value: '泥岩',
-        options: [
-          { label: '砂岩', value: '砂岩', score: 3 },
-          { label: '泥岩', value: '泥岩', score: 8 },
-          { label: '复合顶板', value: '复合顶板', score: 12 },
-        ],
-      },
-      {
-        key: 'structure',
-        label: '地质构造',
-        value: '较复杂',
-        options: [
-          { label: '简单', value: '简单', score: 3 },
-          { label: '较复杂', value: '较复杂', score: 8 },
-          { label: '复杂', value: '复杂', score: 12 },
-        ],
-      },
-      {
-        key: 'miningMethod',
-        label: '采煤方法',
-        value: '综放',
-        options: [
-          { label: '综采', value: '综采', score: 4 },
-          { label: '综放', value: '综放', score: 10 },
-        ],
-      },
-      {
-        key: 'ventMode',
-        label: '通风方式',
-        value: 'U型',
-        options: [
-          { label: 'U型', value: 'U型', score: 4 },
-          { label: 'W型', value: 'W型', score: 8 },
-          { label: 'U+L型', value: 'U+L型', score: 10 },
-        ],
-      },
-      {
-        key: 'sealMode',
-        label: '封闭方式',
-        value: '永久密闭',
-        options: [
-          { label: '永久密闭', value: '永久密闭', score: 3 },
-          { label: '临时密闭', value: '临时密闭', score: 8 },
-          { label: '未封闭', value: '未封闭', score: 13 },
-        ],
-      },
-    ],
-  },
-  {
-    key: 'monitor',
-    title: '监测数据',
-    fields: [
-      {
-        key: 'o2',
-        label: 'O₂浓度(%)',
-        value: '15-18',
-        options: [
-          { label: '≥18', value: '≥18', score: 3 },
-          { label: '15-18', value: '15-18', score: 7 },
-          { label: '10-15', value: '10-15', score: 12 },
-          { label: '<10', value: '<10', score: 16 },
-        ],
-      },
-      {
-        key: 'co',
-        label: 'CO浓度(ppm)',
-        value: '10-24',
-        options: [
-          { label: '<10', value: '<10', score: 3 },
-          { label: '10-24', value: '10-24', score: 7 },
-          { label: '24-50', value: '24-50', score: 12 },
-          { label: '>50', value: '>50', score: 16 },
-        ],
-      },
-      {
-        key: 'ch4',
-        label: 'CH₄浓度(%)',
-        value: '0.5-1.0',
-        options: [
-          { label: '<0.5', value: '<0.5', score: 3 },
-          { label: '0.5-1.0', value: '0.5-1.0', score: 6 },
-          { label: '>1.0', value: '>1.0', score: 10 },
-        ],
-      },
-      {
-        key: 'temperature',
-        label: '温度(℃)',
-        value: '26-30',
-        options: [
-          { label: '<26', value: '<26', score: 3 },
-          { label: '26-30', value: '26-30', score: 7 },
-          { label: '30-35', value: '30-35', score: 12 },
-          { label: '>35', value: '>35', score: 16 },
-        ],
-      },
-    ],
-  },
-];
-
-export const SUGGESTIONS: Record<number, string> = {
-  1: '保持现有监测频次,按计划开展日常巡检,关注束管测点气体变化趋势。',
-  2: '关注束管测点变化趋势,定期校验传感器,保持采空区密闭完好。',
-  3: '加密束管采样频次,加强回风顺槽气体分析;对高温测点进行连续测温跟踪;必要时调整推进速度并落实注浆与注氮措施。',
-  4: '立即加密监测并核查高温测点,落实注浆、注氮等综合防灭火措施,必要时调整推进速度。',
-  5: '立即停止作业并撤出受影响区域人员,启动应急预案,实施连续监测与注浆注氮措施。',
-};
-
-const LEVEL_TEXT: Record<number, string> = {
-  1: 'I 低风险',
-  2: 'II 较低风险',
-  3: 'III 中等风险',
-  4: 'IV 高风险',
-  5: 'V 极高风险',
-};
-
-export const INITIAL_EVALUATION: EvaluationResult = {
-  score: 58.6,
-  level: 3,
-  levelText: 'III 高风险',
-  color: '#ff9d1c',
-  highlightLevel: 4,
-  target: '12采空区工作面',
-  time: '2026-10-12 09:30',
-  source: '人工录入',
-  suggestion: SUGGESTIONS[3],
-};
-
-/** 按 0-20 / 20-40 / 40-60 / 60-80 / 80-100 划分风险等级 */
-export function getRiskLevel(score: number): { level: number; name: string; color: string } {
-  if (score < 20) return { level: 1, name: LEVEL_TEXT[1], color: RISK_LEVELS[0].color };
-  if (score < 40) return { level: 2, name: LEVEL_TEXT[2], color: RISK_LEVELS[1].color };
-  if (score < 60) return { level: 3, name: LEVEL_TEXT[3], color: RISK_LEVELS[2].color };
-  if (score < 80) return { level: 4, name: LEVEL_TEXT[4], color: RISK_LEVELS[3].color };
-  return { level: 5, name: LEVEL_TEXT[5], color: RISK_LEVELS[4].color };
-}
-
-/** 根据人工录入的各项得分计算 0-100 的风险分值 */
-export function calcManualScore(groups: ManualGroup[]): number {
-  let score = 0;
-  let maxScore = 0;
-  groups.forEach((group) => {
-    group.fields.forEach((field) => {
-      const selected = field.options.find((option) => option.value === field.value);
-      score += selected?.score ?? 0;
-      maxScore += Math.max(...field.options.map((option) => option.score));
-    });
-  });
-  if (!maxScore) return 0;
-  return Math.round((score / maxScore) * 1000) / 10;
-}
-
-/** 深度复制人工录入表单,避免多个标签页共享同一份可变数据 */
-export function createManualGroups(): ManualGroup[] {
-  return MANUAL_GROUPS.map((group) => ({
-    ...group,
-    fields: group.fields.map((field) => ({
-      ...field,
-      options: field.options.map((option) => ({ ...option })),
-    })),
-  }));
-}
-
-export interface SelfIgnitionPageData {
-  overview: GoafOverviewItem[];
-  devices: DeviceInstallItem[];
-  overall: OverallWarning;
-  warnings: WarningRow[];
-  alerts: AlertItem[];
-  bundleRows: BundleRow[];
-  fiber: FiberTrend;
-}
-
-/** 生成页面数据副本;数据接口就绪后替换此函数即可 */
-export function createSelfIgnitionData(): SelfIgnitionPageData {
-  return {
-    overview: GOAF_OVERVIEW.map((item) => ({ ...item })),
-    devices: INSTALLED_DEVICES.map((item) => ({ ...item })),
-    overall: { ...OVERALL_WARNING },
-    warnings: WARNING_ROWS.map((item) => ({ ...item })),
-    alerts: ALERTS.map((item) => ({ ...item })),
-    bundleRows: BUNDLE_ROWS.map((item) => ({ ...item })),
-    fiber: {
-      ...FIBER_TREND,
-      times: [...FIBER_TREND.times],
-      maxTemp: [...FIBER_TREND.maxTemp],
-      avgTemp: [...FIBER_TREND.avgTemp],
-    },
-  };
-}
-
-/* ==================== 设备实时监测与分析 ==================== */
-
 export interface SensorReading {
   label: string;
   value: string;
@@ -467,93 +38,9 @@ export interface SensorGroup {
   rows: SensorReading[];
 }
 
-export const SENSOR_GROUPS: SensorGroup[] = [
-  {
-    name: '工作面#1',
-    rows: [
-      { label: '回风隅角氧气', value: '20.9%' },
-      { label: '回风巷一氧化碳', value: '12ppm' },
-      { label: '回风巷温度', value: '26.5℃' },
-    ],
-  },
-  {
-    name: '工作面#2',
-    rows: [
-      { label: '回风隅角氧气', value: '20.8%' },
-      { label: '回风巷一氧化碳', value: '15ppm' },
-      { label: '回风巷温度', value: '27.1℃' },
-    ],
-  },
-];
-
-export const NATURAL_FIRE_STAGE = {
-  levelText: 'IV',
-  levelName: '高风险',
-  color: '#ff9d1c',
-};
-
-export const ALERT_ADVICE = '加密束管采样频次,加强回风顺槽气体分析;对高温测点进行连续测温跟踪;必要时调整推进速度并落实注浆与注氮措施。';
-
 export interface FiberChartData {
   categories: string[];
   solid: number[];
   dashed: number[];
   peakIndex: number;
 }
-
-export const FIBER_CHART_DATA: FiberChartData = {
-  categories: ['测点01', '测点02', '测点03', '测点04', '测点05', '测点06', '测点07', '测点08', '测点09', '测点10', '测点11', '测点12'],
-  solid: [120, 180, 140, 160, 110, 140, 130, 210, 800, 520, 360, 280],
-  dashed: [160, 200, 170, 190, 140, 170, 150, 240, 700, 480, 320, 260],
-  peakIndex: 8,
-};
-
-export interface BundleMonitorRow {
-  deviceNo: string;
-  o2: string;
-  n2: string;
-  co: number;
-  co2: string;
-  ch4: string;
-  c2h4: string;
-  c2h6: string;
-  c2h2: string;
-}
-
-const BUNDLE_DEVICE_NAMES = ['1#束管', '2#束管', '3#束管', '4#束管'];
-
-export const BUNDLE_MONITOR_ROWS: BundleMonitorRow[] = Array.from({ length: 40 }, (_, index) => {
-  return {
-    deviceNo: BUNDLE_DEVICE_NAMES[index % 4],
-    o2: (20.9 - (index % 12) * 0.08).toFixed(2),
-    n2: (78.1 + (index % 9) * 0.06).toFixed(2),
-    co: 6 + (index % 15) * 2,
-    co2: (0.03 + (index % 10) * 0.005).toFixed(3),
-    ch4: (0.18 + (index % 8) * 0.03).toFixed(2),
-    c2h4: (index % 5 === 0 ? 0.02 : 0).toFixed(2),
-    c2h6: (index % 6 === 0 ? 0.01 : 0).toFixed(2),
-    c2h2: (index % 7 === 0 ? 0.01 : 0).toFixed(2),
-  };
-});
-
-export interface DeviceMonitorRow {
-  deviceNo: string;
-  o2: string;
-  co: number;
-  co2: string;
-  ch4: string;
-  temp: string;
-  pressureDiff: string;
-}
-
-export const DEVICE_MONITOR_ROWS: DeviceMonitorRow[] = Array.from({ length: 40 }, (_, index) => {
-  return {
-    deviceNo: `${(index % 4) + 1}#密闭`,
-    o2: (20.4 - (index % 10) * 0.07).toFixed(2),
-    co: 8 + (index % 12) * 2,
-    co2: (0.04 + (index % 9) * 0.004).toFixed(3),
-    ch4: (0.22 + (index % 8) * 0.03).toFixed(2),
-    temp: (25.8 + (index % 10) * 0.5).toFixed(1),
-    pressureDiff: (12 + (index % 7) * 3).toFixed(0),
-  };
-});

+ 16 - 18
src/views/vent/home/selfIgnition/goafOverview.adapter.ts

@@ -44,19 +44,15 @@ function includesAny(text: string, keywords: string[]) {
   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);
+function buildOverview(counts: GoafCountOverview | undefined): GoafOverviewItem[] {
+  if (!counts) return [];
 
   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, '停产工作面采空区数') },
+    { label: '矿井采空区总数', value: toNumber(counts.totalCount) },
+    { label: '已封闭采空区数', value: toNumber(counts.closedCount) },
+    { label: '回撤工作面采空区数', value: toNumber(counts.withdrawCount) },
+    { label: '回采工作面采空区数', value: toNumber(counts.miningCount) },
+    { label: '停产工作面采空区数', value: toNumber(counts.stoppedCount) },
   ];
 }
 
@@ -74,12 +70,12 @@ function sumKindStatistics(statistics: DeviceKindStatistic[], keywords: string[]
   }, 0);
 }
 
-function buildDevices(statistics: DeviceStatistics | undefined, fallback: DeviceInstallItem[]): DeviceInstallItem[] {
-  if (!statistics) return fallback;
+function buildDevices(statistics: DeviceStatistics | undefined): DeviceInstallItem[] {
+  if (!statistics) return [];
 
   const typeStatistics = statistics.typeStatistics || [];
   const kindStatistics = statistics.kindStatistics || [];
-  if (!typeStatistics.length && !kindStatistics.length) return fallback;
+  if (!typeStatistics.length && !kindStatistics.length) return [];
 
   return DEVICE_BUCKETS.map((bucket) => {
     const typeCount = sumTypeStatistics(typeStatistics, bucket.typeKeywords);
@@ -144,12 +140,14 @@ function buildWarnings(summary: GoafOverviewSummary): WarningRow[] {
 }
 
 /** 将综合汇总接口转换为系统概览页面的展示数据 */
-export function mapGoafSummaryData(summary: GoafOverviewSummary | undefined, fallback: OverviewSummaryViewData): OverviewSummaryViewData {
-  if (!summary) return fallback;
+export function mapGoafSummaryData(summary: GoafOverviewSummary | undefined): OverviewSummaryViewData {
+  if (!summary) {
+    return { overview: [], devices: [], warnings: [] };
+  }
 
   return {
-    overview: buildOverview(summary.goafCountOverview, fallback.overview),
-    devices: buildDevices(summary.deviceStatistics, fallback.devices),
+    overview: buildOverview(summary.goafCountOverview),
+    devices: buildDevices(summary.deviceStatistics),
     warnings: buildWarnings(summary),
   };
 }

+ 128 - 86
src/views/vent/home/selfIgnition/index.vue

@@ -1,7 +1,7 @@
 <!-- eslint-disable vue/multi-word-component-names -->
 <template>
   <div ref="rootRef" class="ignition-home">
-    <div class="ignition-stage" :style="stageStyle">
+    <div class="ignition-stage">
       <!-- 顶部标题 -->
       <div v-if="!isEmbed" class="ignition-header">
         <div class="ignition-title">{{ pageTitle }}</div>
@@ -19,6 +19,11 @@
         <PanelSelect v-model="manageSystemId" :options="sceneOptions" />
       </div>
 
+      <!-- 中间三维模型 -->
+      <div class="ignition-model" :style="modelStyle">
+        <SubApp />
+      </div>
+
       <!-- 系统概览 -->
       <OverviewView v-show="activeTab === 0" :overview="data.overview" :devices="data.devices" :warnings="data.warnings" />
 
@@ -38,36 +43,56 @@
 </template>
 
 <script lang="ts" setup>
-  import { computed, onMounted, onUnmounted, ref } from 'vue';
-  import { useRoute } from 'vue-router';
+  import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
   import OverviewView from './components/OverviewView.vue';
   import MonitorView from './components/MonitorView.vue';
   import ManualView from './components/ManualView.vue';
   import PanelSelect from './components/PanelSelect.vue';
-  import { createSelfIgnitionData } from './data';
+  import SubApp from '/@/components/vent/micro/createSubApp.vue';
   import { getSafetySceneList } from './safetyEvaluation.api';
   import { getGoafOverviewSummary } from './goafOverview.api';
   import { mapGoafSummaryData } from './goafOverview.adapter';
   import { FALLBACK_SCENE } from './evaluation.config';
+  import type { DeviceInstallItem, GoafOverviewItem, WarningRow } from './data';
   import type { SafetyScene } from './evaluation.config';
 
   const route = useRoute();
+  const router = useRouter();
   const rootRef = ref<HTMLElement>();
-  const scaleX = ref(1);
-  const scaleY = ref(1);
   const activeTab = ref(0);
   const pageTitle = '张家峁煤自燃监测预警';
   const tabs = ['系统概览', '设备实时监测与分析', '人工录入与分析'];
-  const data = ref(createSelfIgnitionData());
+  const data = ref<{ overview: GoafOverviewItem[]; devices: DeviceInstallItem[]; warnings: WarningRow[] }>({
+    overview: [],
+    devices: [],
+    warnings: [],
+  });
   const scenes = ref<SafetyScene[]>([]);
   const manageSystemId = ref('');
 
-  let resizeObserver: ResizeObserver | undefined;
   let dataInterval: number | undefined;
   let summaryLoading = false;
 
   const isEmbed = computed(() => !!route.query.embed);
 
+  /** 三维模型按各页签的空白区域铺满(坐标基于 1920 × 928 画布) */
+  const MODEL_RECTS = [
+    { left: 413, top: 118, width: 1485, height: 502 }, // 系统概览
+    { left: 413, top: 118, width: 1092, height: 531 }, // 设备实时监测与分析
+    { left: 413, top: 118, width: 1092, height: 792 }, // 人工录入与分析
+  ];
+
+  const modelStyle = computed(() => {
+    const rect = MODEL_RECTS[activeTab.value] || MODEL_RECTS[0];
+    return {
+      left: `${rect.left}px`,
+      top: `${rect.top}px`,
+      width: `${rect.width}px`,
+      height: `${rect.height}px`,
+    };
+  });
+
   const sceneOptions = computed(() =>
     scenes.value.map((scene) => ({
       label: scene.systemname,
@@ -78,30 +103,6 @@
 
   const selectedSceneName = computed(() => scenes.value.find((scene) => scene.id === manageSystemId.value)?.systemname || '');
 
-  const stageStyle = computed(() => ({
-    transform: `translate(-50%, -50%) scale(${scaleX.value}, ${scaleY.value})`,
-  }));
-
-  /**
-   * 设计稿为 1920 × 1080。外层 AdaptiveContainer 已经把 1920 × 928 拉伸到视口,
-   * 这里按外层实际的 x / y 缩放比例做反向补偿,保证 1920 × 1080 的坐标始终精确落位。
-   */
-  function updateScale() {
-    const root = rootRef.value;
-    if (!root) return;
-    const rootWidth = root.clientWidth;
-    const rootHeight = root.clientHeight;
-    if (!rootWidth || !rootHeight) return;
-
-    const rect = root.getBoundingClientRect();
-    const outerScaleX = rect.width / rootWidth || 1;
-    const outerScaleY = rect.height / rootHeight || 1;
-    const fitScale = Math.min(rect.width / 1920, rect.height / 1080);
-
-    scaleX.value = fitScale / outerScaleX;
-    scaleY.value = fitScale / outerScaleY;
-  }
-
   /** 概览数据来自采空区综合汇总接口;接口异常时保留现有兜底数据 */
   async function refresh() {
     if (summaryLoading) return;
@@ -110,7 +111,7 @@
       const res: any = await getGoafOverviewSummary();
       const summary = res?.result ?? res;
       if (!summary || typeof summary !== 'object') return;
-      const mapped = mapGoafSummaryData(summary, createSelfIgnitionData());
+      const mapped = mapGoafSummaryData(summary);
       data.value = { ...data.value, ...mapped };
     } catch {
       // 保持上一次成功数据,避免接口短暂异常导致页面回退为模拟数据
@@ -151,7 +152,7 @@
   async function loadScenes() {
     let list: SafetyScene[] = [];
     try {
-      const res: any = await getSafetySceneList({ pagetype: 'normal' });
+      const res: any = await getSafetySceneList();
       list = extractSceneRows(res)
         .map(toSafetyScene)
         .filter((scene) => !!scene.id);
@@ -159,19 +160,47 @@
       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;
+    // sysID 为当前定位参数,优先于旧版入口携带的场景参数
+    const queryScene = (route.query.sysID ||
+      route.query.systemID ||
+      route.query.manageSystemId ||
+      route.query.sceneId ||
+      route.query.deviceid) as string;
     if (queryScene && !scenes.value.some((scene) => scene.id === queryScene)) {
       scenes.value.unshift({ id: queryScene, systemname: '当前场景' });
     }
     manageSystemId.value = queryScene || scenes.value[0]?.id || '';
   }
 
+  /** 场景切换时通过路由参数通知三维模型定位到对应场景 */
+  function locateSceneInModel(scene?: SafetyScene) {
+    const sysID = scene?.id || manageSystemId.value;
+    if (!sysID) return;
+
+    const currentSysID = Array.isArray(route.query.sysID) ? route.query.sysID[0] : route.query.sysID;
+    // 旧版参数(type/deviceType/deviceid)会切到子应用带工具栏的模型页,这里一并清掉
+    const hasLegacyQuery = route.query.type === 'tunMonitor' || !!(route.query.deviceType || route.query.deviceid);
+    if (!hasLegacyQuery && currentSysID === sysID) return;
+
+    const query: Record<string, any> = { ...route.query, sysID };
+    if (query.type === 'tunMonitor') delete query.type;
+    delete query.deviceType;
+    delete query.deviceid;
+
+    router.replace({ path: route.path, query });
+  }
+
+  watch(manageSystemId, (id) => {
+    if (!id) return;
+    locateSceneInModel(scenes.value.find((scene) => scene.id === id));
+  });
+
   function initDataInterval() {
     clearInterval(dataInterval);
     dataInterval = window.setInterval(() => {
@@ -185,18 +214,10 @@
     void loadScenes();
     void refresh();
     initDataInterval();
-    updateScale();
-    if (typeof ResizeObserver !== 'undefined' && rootRef.value) {
-      resizeObserver = new ResizeObserver(updateScale);
-      resizeObserver.observe(rootRef.value);
-    }
-    window.addEventListener('resize', updateScale);
   });
 
   onUnmounted(() => {
     clearInterval(dataInterval);
-    resizeObserver?.disconnect();
-    window.removeEventListener('resize', updateScale);
   });
 </script>
 
@@ -213,12 +234,21 @@
   }
 
   .ignition-stage {
+    position: relative;
+    width: 100%;
+    height: 100%;
+  }
+
+  // 中间三维模型区域(与可配置首页一致,用 SubApp 挂载微前端三维场景)
+  .ignition-model {
     position: absolute;
-    left: 50%;
-    top: 50%;
-    width: 1920px;
-    height: 1080px;
-    transform-origin: center center;
+    z-index: 0;
+  }
+
+  // 视图容器本身不拦截鼠标,让中间的三维模型可拖拽/点击;
+  // 模块根节点已设置 pointer-events: auto,模块内交互不受影响。
+  :deep(.ignition-view) {
+    pointer-events: none;
   }
 
   .ignition-header {
@@ -227,7 +257,7 @@
     top: 0;
     z-index: 3;
     width: 100%;
-    height: 69px;
+    height: 59px;
     background: url('@/assets/images/vent/home/selfIgnition/top-bg.png') no-repeat center / 100% 100%;
   }
 
@@ -236,6 +266,7 @@
     align-items: center;
     justify-content: center;
     height: 100%;
+    padding-top: 10px;
     padding-bottom: 2px;
     box-sizing: border-box;
     font-family: 'douyuFont', sans-serif;
@@ -250,45 +281,70 @@
   .ignition-tabs {
     position: absolute;
     left: 50%;
-    top: 64px;
+    top: 69px;
     z-index: 4;
     display: flex;
     align-items: center;
     justify-content: center;
-    gap: 12px;
-    width: 579px;
-    height: 68px;
-    padding: 13px 16px 9px;
-    box-sizing: border-box;
+    gap: 10px;
+    height: 48px;
     transform: translateX(-50%);
-    background: url('@/assets/images/vent/home/selfIgnition/tab-bg.png') no-repeat center / 100% 100%;
+
+    // 标签组两侧向外延伸的斜翼装饰线
+    &::before,
+    &::after {
+      content: '';
+      position: absolute;
+      bottom: 0;
+      width: 216px;
+      height: 39px;
+      background-repeat: no-repeat;
+      background-position: center;
+      background-size: 100% 100%;
+      pointer-events: none;
+    }
+
+    &::before {
+      right: 100%;
+      background-image: url('@/assets/images/vent/home/selfIgnition/tab-wing-left.png');
+    }
+
+    &::after {
+      left: 100%;
+      background-image: url('@/assets/images/vent/home/selfIgnition/tab-wing-right.png');
+    }
   }
 
   .ignition-tab {
     display: flex;
     align-items: center;
     justify-content: center;
-    width: 162px;
+    width: 215px;
     height: 40px;
     box-sizing: border-box;
     border: none;
     background: url('@/assets/images/vent/home/selfIgnition/tab-item-bg.png') no-repeat center / 100% 100%;
-    color: rgba(255, 255, 255, 0.86);
-    font-size: 15px;
+    color: #e6f7ff;
+    font-size: 18px;
+    font-weight: 700;
     letter-spacing: 0.5px;
     white-space: nowrap;
     cursor: pointer;
     transition:
       color 0.2s,
-      border-color 0.2s,
-      box-shadow 0.2s;
+      filter 0.2s;
 
     &:hover {
       color: #fff;
+      filter: drop-shadow(0 0 6px rgba(61, 246, 255, 0.45));
     }
 
     &.active {
       background: url('@/assets/images/vent/home/selfIgnition/tab-active-bg.png') no-repeat center / 100% 100%;
+      color: #fff;
+      text-shadow:
+        0 0 8px rgba(61, 246, 255, 0.55),
+        0 1px 2px rgba(0, 0, 0, 0.6);
     }
   }
 
@@ -297,19 +353,19 @@
     left: 20px;
     top: 76px;
     z-index: 6;
-    width: 357px;
-    height: 52px;
+    width: 393px;
+    height: 46px;
   }
 
   :deep(.scene-toolbar .ignition-select) {
     width: 100%;
-    height: 52px;
-    padding: 0 48px 0 86px;
+    height: 46px;
+    padding: 0 44px 0 20px;
     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;
+    font-size: 18px;
     letter-spacing: 0.5px;
 
     &:hover,
@@ -320,10 +376,10 @@
   }
 
   :deep(.scene-toolbar .ignition-select-arrow) {
-    right: 30px;
-    width: 12px;
-    height: 12px;
-    margin-top: -7px;
+    right: 18px;
+    width: 14px;
+    height: 14px;
+    margin-top: -9px;
     border: none;
     border-right: 2px solid #eaffff;
     border-bottom: 2px solid #eaffff;
@@ -331,21 +387,7 @@
   }
 
   :deep(.scene-toolbar .ignition-select.open .ignition-select-arrow) {
-    margin-top: -3px;
+    margin-top: -4px;
     transform: rotate(225deg);
   }
-
-  :deep(.box1-top .title) {
-    color: #fff;
-    font-size: 16px;
-    font-weight: 700;
-    letter-spacing: 1px;
-    text-shadow: 0 0 8px rgba(61, 246, 255, 0.35);
-    transform: translateY(2px);
-  }
-
-  :deep(.box1-center > .box-container) {
-    background-color: #062441 !important;
-    background-image: none;
-  }
 </style>

+ 16 - 3
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 = '/monitor/system',
+  sceneList = '/safety/ventanalyManageSystem/list',
 }
 
 /** 22 项评价指标、权重和选项字典 */
@@ -66,5 +66,18 @@ export const getSafetyEvaluationByScene = (sceneId: string) => {
   return promise;
 };
 
-/** 采空区场景列表,用于评价对象绑定 */
-export const getSafetySceneList = (params?: Record<string, any>) => defHttp.post({ url: Api.sceneList, params: params ?? {} });
+/** 场景类型:与系统其它页面一致,按场景类型 + pagetype=normal 查询 */
+const SCENE_STRTYPE = 'sys_empty';
+
+/** 场景下拉列表,复用系统统一的场景管理接口(res.records: id / systemname) */
+export const getSafetySceneList = (params?: { strtype?: string; pagetype?: string; pageSize?: number }) =>
+  defHttp.get({
+    url: Api.sceneList,
+    params: {
+      strtype: params?.strtype ?? SCENE_STRTYPE,
+      pagetype: params?.pagetype ?? 'normal',
+      // 该接口默认每页 10 条,需显式传 pageSize 才能取全
+      pageSize: params?.pageSize ?? 1000,
+      pageNo: 1,
+    },
+  });

+ 23 - 0
src/views/vent/home/selfIgnition/scrollbar.less

@@ -0,0 +1,23 @@
+/**
+ * 自燃监测页统一滚动条样式:
+ * 6px 宽、青色渐变滑块、透明轨道、无上下箭头按钮。
+ */
+.scrollbar-cyan() {
+  &::-webkit-scrollbar {
+    width: 6px;
+    height: 6px;
+  }
+
+  &::-webkit-scrollbar-button {
+    display: none;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: transparent;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    border-radius: 3px;
+    background: linear-gradient(180deg, #3df6ff 0%, #0d7fc1 100%);
+  }
+}

+ 1 - 0
src/views/vent/monitorManager/alarmMonitor/common/external-mb.vue

@@ -133,6 +133,7 @@
           data-zoom-mode="index"
           :data-zoom-start="0"
           :data-zoom-end="10"
+          :alarm-symbol-mode="type === 'O₂' ? 'triangle' : 'auto'"
         />
       </div>
     </div>

+ 1 - 0
src/views/vent/monitorManager/alarmMonitor/common/internal-fire-bot.vue

@@ -117,6 +117,7 @@
         data-zoom-mode="index"
         :data-zoom-start="0"
         :data-zoom-end="10"
+        :alarm-symbol-mode="currentGas === 'O₂' ? 'triangle' : 'auto'"
       />
     </div>
   </div>

+ 14 - 0
src/views/vent/performance/fileDetail/fileDetail.api.ts

@@ -16,6 +16,8 @@ enum Api {
   sumVentReport = '/company/reportData/sumVentReport',
   zipdownload = '/ventanaly-sharefile/fileServer/zipdownload',
   queryVentMonReportJd = '/company/reportData/queryVentMonReportJd',
+  createMonthlySummaryJob = '/ventAI/api/monthly-summary/jobs',
+  queryMonthlySummaryJob = '/ventAI/api/monthly-summary/jobs',
   updataFileParam = '/ventanaly-sharefile/fileServer/updataFileParam',
 }
 
@@ -44,6 +46,18 @@ export const sumVentReport = () => defHttp.post({ url: Api.sumVentReport });
  */
 export const queryVentMonReportJd = () => defHttp.get({ url: Api.queryVentMonReportJd });
 
+/**
+ * 创建通风月报汇总任务
+ */
+export const createMonthlySummaryJob = (data: { year: number; month: number; force?: boolean }) =>
+  defHttp.post({ url: Api.createMonthlySummaryJob, data }, { isTransformResponse: false });
+
+/**
+ * 轮询通风月报汇总任务进度
+ */
+export const queryMonthlySummaryJob = (jobId: string) =>
+  defHttp.get({ url: `${Api.queryMonthlySummaryJob}/${jobId}` }, { isTransformResponse: false });
+
 /**
  * 左侧树接口
  * @param params

+ 436 - 42
src/views/vent/performance/fileDetail/index.vue

@@ -52,12 +52,25 @@
           <span style="color: #fff">文件类型:</span>
           <a-select v-model:value="searchForm.fileType" placeholder="请选择..." style="width: 220px; margin-right: 20px"
             :disabled="activeKey != '1'">
-            <a-select-option v-for="item in filetypeList" :value="item.value">{{ item.label }}</a-select-option>
+            <a-select-option v-for="item in filetypeList" :key="item.value" :value="item.value">{{ item.label }}</a-select-option>
           </a-select>
+          <span style="color: #fff">报表月份:</span>
+          <a-date-picker
+            v-model:value="reportMonth"
+            picker="month"
+            format="YYYY-MM"
+            valueFormat="YYYY-MM"
+            placeholder="请选择年月"
+            style="width: 220px; margin-right: 20px"
+            :allowClear="false"
+          />
           <a-button type="primary" preIcon="ant-design:search-outlined" @click="onSearch"
             :disabled="activeKey != '1'">查询</a-button>
-          <a-button type="primary" style="float: right" @click="reportSum"
-            v-if="sysOrgCode == 'sdmtjtgsd'">汇总报表</a-button>
+          <!-- <a-button type="primary" style="float: right" @click="reportSum"
+            v-if="sysOrgCode == 'sdmtjtgsd'">汇总报表</a-button> -->
+          <span class="report-sum-btn-wrap" @click="reportSum">
+            <a-button type="primary" :loading="reportSumLoading">汇总报表</a-button>
+          </span>
           <a-button type="primary" style="float: right; margin-right: 20px" @click="openModal(true)">文件上传</a-button>
         </div>
         <div class="list">
@@ -106,22 +119,58 @@
 
     </a-modal>
     <!-- 汇总报表弹窗 -->
-    <a-modal v-model:visible="visiblehHz" centered :width="600" title="报表汇总" :footer="null" @cancel="handleCancelHz">
-      <div
-        style="height: 240px; overflow-y: auto; display: flex; flex-direction: column; align-items: center; padding: 10px; box-sizing: border-box">
-        <vue3-seamless-scroll hover-stop="true" :list="reportHzList" :hover="true" :step="0.06" class="seamless-warp1">
-          <div v-for="(item, index) in reportHzList" :key="index"
-            style="width: 100%; height: 40px; display: flex; justify-content: space-between; align-items: center">
-            <span>{{ item.time }}</span>
-            <span>{{ item.msg }}</span>
+    <a-modal
+      v-model:visible="visiblehHz"
+      centered
+      :width="860"
+      title="报表汇总"
+      :footer="null"
+      wrap-class-name="report-hz-modal-wrap"
+      @cancel="handleCancelHz"
+    >
+      <div class="report-hz-modal">
+        <section class="report-hz-section report-hz-section-progress">
+          <div class="report-hz-section-title">
+            <span class="report-hz-section-mark"></span>
+            <span>任务执行进度</span>
           </div>
-        </vue3-seamless-scroll>
+          <div class="report-hz-progress">
+            <div class="report-hz-progress-header">
+              <span class="report-hz-progress-label">当前进度</span>
+              <span class="report-hz-progress-text">{{ progressDone }} / {{ progressTotal }}({{ progressPercent }}%)</span>
+            </div>
+            <a-progress
+              :percent="progressPercent"
+              :status="progressStatus"
+              :stroke-color="progressStrokeColor"
+              :stroke-width="14"
+              :show-info="false"
+            />
+          </div>
+        </section>
+        <section class="report-hz-section report-hz-section-list">
+          <div class="report-hz-section-title">
+            <span class="report-hz-section-mark"></span>
+            <span>矿井处理明细</span>
+            <span class="report-hz-section-count" v-if="reportHzList.length">共 {{ reportHzList.length }} 项</span>
+          </div>
+          <div class="report-hz-list">
+            <div v-if="!reportHzList.length" class="report-hz-empty">暂无矿井汇总数据</div>
+            <div v-for="(item, index) in reportHzList" :key="`${item.mine}-${index}`" class="report-hz-item">
+              <div class="report-hz-item-index">{{ index + 1 }}</div>
+              <div class="report-hz-item-main">
+                <span class="report-hz-mine">{{ item.mine || '-' }}</span>
+                <span class="report-hz-file" :title="item.file">{{ item.file || '-' }}</span>
+              </div>
+              <span class="report-hz-status" :class="item.ok ? 'is-ok' : 'is-fail'">{{ item.ok ? '成功' : '失败' }}</span>
+            </div>
+          </div>
+        </section>
       </div>
     </a-modal>
   </div>
 </template>
 <script lang="ts" setup name="system-user">
-import { Vue3SeamlessScroll } from 'vue3-seamless-scroll';
 import customHeader from '/@/components/vent/customHeader.vue';
 import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
 import fileSystem from './commen/fileSystem.vue';
@@ -132,7 +181,8 @@ import endEd from '../endEd/index.vue';
 import { useRouter } from 'vue-router';
 import { useMessage } from '/@/hooks/web/useMessage';
 import { SvgIcon } from '/@/components/Icon';
-import { ref, onMounted, reactive, nextTick, watch, onUnmounted } from 'vue';
+import { ref, computed, onMounted, reactive, watch, onUnmounted } from 'vue';
+import dayjs from 'dayjs';
 import { columns } from './fileDetail.data';
 import {
   getTree,
@@ -144,17 +194,41 @@ import {
   deleteById,
   listData,
   getNowUserAgencyData,
-  sumVentReport,
-  queryVentMonReportJd,
+  createMonthlySummaryJob,
+  queryMonthlySummaryJob,
   zipdownload,
 } from './fileDetail.api';
 import { useGlobSetting } from '/@/hooks/setting';
 
+interface MineSummaryItem {
+  mine?: string;
+  file?: string;
+  json_rel_path?: string;
+  attempts?: number;
+  ok?: boolean;
+}
+
 let spinning = ref(false)
 const { sysOrgCode } = useGlobSetting();
 let visiblehHz = ref(false);
-let reportHzList = ref<any[]>([]);
-let timer: null | NodeJS.Timeout = null;
+let reportHzList = ref<MineSummaryItem[]>([]);
+let reportSumLoading = ref(false);
+let reportMonth = ref(dayjs().format('YYYY-MM'));
+let progressTotal = ref(0);
+let progressDone = ref(0);
+let jobStatus = ref('');
+let currentJobId = ref('');
+let timer: null | ReturnType<typeof setInterval> = null;
+const progressStrokeColor = { '0%': '#31bccc', '100%': '#0863c3' };
+const progressPercent = computed(() => {
+  if (!progressTotal.value) return 0;
+  return Math.min(100, Math.round((progressDone.value / progressTotal.value) * 100));
+});
+const progressStatus = computed(() => {
+  if (jobStatus.value === 'failed') return 'exception';
+  if (jobStatus.value === 'succeeded' || (progressTotal.value > 0 && progressDone.value >= progressTotal.value)) return 'success';
+  return 'active';
+});
 let activeKey = ref('1');
 let selfParam = reactive({
   //各矿参数
@@ -236,29 +310,130 @@ function downFilePublic(content, fileName) {
     navigator.msSaveBlob(blob, fileName);
   }
 }
+function clearReportTimer() {
+  if (timer) {
+    clearInterval(timer);
+    timer = null;
+  }
+}
+
+function resetReportHzState() {
+  reportHzList.value = [];
+  progressTotal.value = 0;
+  progressDone.value = 0;
+  jobStatus.value = '';
+  currentJobId.value = '';
+}
+
+function updateReportProgress(res: any) {
+  const job = res?.job || res || {};
+  const progress = job.progress || {};
+  progressTotal.value = Number(progress.total || 0);
+  progressDone.value = Number(progress.done || 0);
+  jobStatus.value = job.status || '';
+  const mines = progress.mines || job.mines || res?.mines || [];
+  reportHzList.value = Array.isArray(mines) ? mines : [];
+  return job;
+}
+
+function isJobFinished(status?: string) {
+  return status === 'succeeded' || status === 'failed';
+}
+
+function isProgressFull() {
+  return progressTotal.value > 0 && progressDone.value >= progressTotal.value;
+}
+
+function shouldStopPolling(status?: string) {
+  return isJobFinished(status) || isProgressFull();
+}
+
+function stopReportSumLoading() {
+  reportSumLoading.value = false;
+}
+
+async function pollMonthlySummaryJob(jobId: string) {
+  try {
+    const res = await queryMonthlySummaryJob(jobId);
+    const job = updateReportProgress(res);
+    if (shouldStopPolling(job.status)) {
+      clearReportTimer();
+      stopReportSumLoading();
+      if (job.status === 'failed') {
+        const errMsg = job.error?.message || '报表汇总失败';
+        createMessage.error(errMsg);
+      } else {
+        createMessage.success('报表汇总完成');
+        alive.value = new Date().getTime();
+      }
+    }
+    return job;
+  } catch (error) {
+    clearReportTimer();
+    stopReportSumLoading();
+    return null;
+  }
+}
+
 //汇总报表
-function reportSum() {
-  visiblehHz.value = true;
-  sumVentReport();
-  timer = setInterval(async () => {
-    let res = await queryVentMonReportJd();
-    reportHzList.value = res.jdList;
-    if (res.flag) {
-      visiblehHz.value = false;
-      clearTimeout(timer);
-      reportHzList.value.length = 0;
-      alive.value = new Date().getTime();
-    } else {
+async function reportSum() {
+  // 轮询进行中再次点击:仅重新打开弹窗,不新建任务
+  if (reportSumLoading.value) {
+    if (currentJobId.value) {
       visiblehHz.value = true;
     }
-  }, 3000);
+    return;
+  }
+
+  if (!reportMonth.value) {
+    createMessage.warning('请选择报表月份');
+    return;
+  }
+  const [yearStr, monthStr] = reportMonth.value.split('-');
+  const year = Number(yearStr);
+  const month = Number(monthStr);
+  if (!year || !month) {
+    createMessage.warning('报表月份格式不正确');
+    return;
+  }
+
+  reportSumLoading.value = true;
+  clearReportTimer();
+  resetReportHzState();
+
+  try {
+    const res = await createMonthlySummaryJob({ year, month, force: true });
+    if (!res?.success) {
+      const missing = Array.isArray(res?.missing_mines) ? res.missing_mines.join('、') : '';
+      createMessage.error(missing ? `汇总失败,缺矿:${missing}` : res?.message || '创建汇总任务失败');
+      stopReportSumLoading();
+      return;
+    }
+
+    const jobId = res.job_id;
+    if (!jobId) {
+      createMessage.error('未获取到任务编号');
+      stopReportSumLoading();
+      return;
+    }
+
+    currentJobId.value = jobId;
+    visiblehHz.value = true;
+    const firstJob = await pollMonthlySummaryJob(jobId);
+    if (!shouldStopPolling(firstJob?.status)) {
+      timer = setInterval(() => {
+        pollMonthlySummaryJob(jobId);
+      }, 1500);
+    }
+  } catch (error: any) {
+    createMessage.error(error?.message || '创建汇总任务失败');
+    stopReportSumLoading();
+  }
 }
-//关闭汇总弹窗
+
+//关闭汇总弹窗(轮询中仅关闭弹窗,不中断轮询与按钮加载)
 function handleCancelHz() {
-  reportHzList.value.length = 0;
   visiblehHz.value = false;
-  clearTimeout(timer);
-  alive.value = new Date().getTime();
 }
 //上传文件
 let openModal = (val) => {
@@ -463,10 +638,8 @@ onMounted(() => {
   getTypeTableList();
 });
 onUnmounted(() => {
-  if (timer) {
-    clearTimeout(timer);
-    timer = undefined;
-  }
+  clearReportTimer();
+  stopReportSumLoading();
 });
 </script>
 
@@ -543,6 +716,16 @@ onUnmounted(() => {
         height: 34px;
         line-height: 34px;
         margin-bottom: 15px;
+
+        .report-sum-btn-wrap {
+          float: right;
+          display: inline-block;
+          cursor: pointer;
+
+          .zxm-btn {
+            pointer-events: none;
+          }
+        }
       }
     }
 
@@ -557,10 +740,166 @@ onUnmounted(() => {
   }
 }
 
-.seamless-warp1 {
-  width: 100%;
-  height: 100%;
+.report-hz-modal {
+  padding: 8px 12px 12px;
+}
+
+.report-hz-section {
+  margin-bottom: 20px;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+}
+
+.report-hz-section-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 12px;
+  color: #eaf8ff;
+  font-size: 15px;
+  font-weight: 600;
+  letter-spacing: 0.5px;
+}
+
+.report-hz-section-mark {
+  width: 3px;
+  height: 14px;
+  border-radius: 2px;
+  background: linear-gradient(180deg, #5ce1ff, #1a7fd4);
+  box-shadow: 0 0 8px rgba(92, 225, 255, 0.45);
+}
+
+.report-hz-section-count {
+  margin-left: auto;
+  color: #7fbdd4;
+  font-size: 12px;
+  font-weight: 400;
+}
+
+.report-hz-progress {
+  padding: 18px 20px 16px;
+  border-radius: 10px;
+  background: linear-gradient(120deg, rgba(18, 78, 118, 0.55), rgba(10, 42, 72, 0.72));
+  border: 1px solid rgba(92, 225, 255, 0.35);
+  box-shadow: inset 0 0 24px rgba(49, 188, 204, 0.08);
+}
+
+.report-hz-progress-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 14px;
+}
+
+.report-hz-progress-label {
+  color: #b7e6f7;
+  font-size: 13px;
+}
+
+.report-hz-progress-text {
+  color: #5ce1ff;
+  font-size: 14px;
+  font-weight: 600;
+  font-variant-numeric: tabular-nums;
+}
+
+.report-hz-list {
+  max-height: 380px;
+  overflow-y: auto;
+  padding: 10px 12px;
+  border-radius: 10px;
+  background: rgba(8, 28, 46, 0.45);
+  border: 1px dashed rgba(153, 232, 255, 0.22);
+}
+
+.report-hz-empty {
+  text-align: center;
+  color: #9ec9db;
+  padding: 48px 0;
+}
+
+.report-hz-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 14px;
+  padding: 12px 14px;
+  margin-bottom: 8px;
+  border-radius: 8px;
+  background: rgba(255, 255, 255, 0.03);
+  border: 1px solid transparent;
+  border-left: 3px solid rgba(92, 225, 255, 0.55);
+  transition: background 0.2s ease, border-color 0.2s ease;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+
+  &:hover {
+    background: rgba(49, 188, 204, 0.08);
+    border-color: rgba(153, 232, 255, 0.12);
+    border-left-color: #5ce1ff;
+  }
+}
+
+.report-hz-item-index {
+  flex-shrink: 0;
+  width: 24px;
+  height: 24px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #8ecde2;
+  font-size: 12px;
+  background: rgba(49, 188, 204, 0.12);
+  border: 1px solid rgba(153, 232, 255, 0.2);
+}
+
+.report-hz-item-main {
+  flex: 1;
+  min-width: 0;
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.report-hz-mine {
+  color: #ffffff;
+  font-size: 14px;
+  font-weight: 500;
+}
+
+.report-hz-file {
+  color: #8eb9cc;
+  font-size: 12px;
   overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.report-hz-status {
+  flex-shrink: 0;
+  min-width: 56px;
+  text-align: center;
+  padding: 2px 12px;
+  border-radius: 999px;
+  font-size: 12px;
+  line-height: 20px;
+}
+
+.report-hz-status.is-ok {
+  color: #73d13d;
+  background: rgba(82, 196, 26, 0.12);
+  border: 1px solid rgba(82, 196, 26, 0.32);
+}
+
+.report-hz-status.is-fail {
+  color: #ff7875;
+  background: rgba(255, 77, 79, 0.12);
+  border: 1px solid rgba(255, 77, 79, 0.32);
 }
 
 ::v-deep .jeecg-svg-icon {
@@ -604,6 +943,26 @@ onUnmounted(() => {
   color: #fff !important;
 }
 
+::v-deep .zxm-picker {
+  width: 220px;
+  height: 30px;
+  background: transparent !important;
+  border: 1px solid var(--input-border) !important;
+  border-radius: 5px !important;
+  color: #fff !important;
+  margin-right: 20px;
+}
+
+::v-deep .zxm-picker-input > input {
+  color: #fff !important;
+  height: 28px;
+}
+
+::v-deep .zxm-picker-suffix,
+::v-deep .zxm-picker-clear {
+  color: #fff !important;
+}
+
 ::v-deep .zxm-btn-group {
   margin-right: 25px;
 }
@@ -640,4 +999,39 @@ onUnmounted(() => {
 ::v-deep .zxm-form-item-control-input {
   width: 90%;
 }
+
+::v-deep .report-hz-progress .zxm-progress-inner {
+  background: rgba(8, 28, 46, 0.7) !important;
+  border-radius: 999px !important;
+}
+
+::v-deep .report-hz-progress .zxm-progress-bg {
+  border-radius: 999px !important;
+}
+</style>
+
+<style lang="less">
+.report-hz-modal-wrap {
+  .zxm-modal {
+    top: auto;
+  }
+
+  .zxm-modal-content {
+    padding-bottom: 4px;
+  }
+
+  .zxm-modal-header {
+    padding: 16px 28px 12px;
+  }
+
+  .zxm-modal-body {
+    padding: 8px 28px 24px;
+  }
+
+  .zxm-modal-close-x {
+    width: 48px;
+    height: 48px;
+    line-height: 48px;
+  }
+}
 </style>

+ 15 - 1
src/views/vent/testChart/FiberTempChart.vue

@@ -83,6 +83,11 @@ const props = defineProps({
     type: String,
     default: 'percent',
   },
+  // auto: 超上限火焰 / 低于下限三角;triangle: 超限统一用三角(束管 O₂)
+  alarmSymbolMode: {
+    type: String,
+    default: 'auto',
+  },
 });
 
 const emit = defineEmits(['chart-click']);
@@ -111,9 +116,17 @@ function getDataKey() {
     yn: props.yAxisName,
     vl: props.valueLabel,
     vu: props.valueUnit,
+    asm: props.alarmSymbolMode,
   });
 }
 
+function resolveAlarmSymbol(val, minValue) {
+  if (props.alarmSymbolMode === 'triangle') {
+    return `image://${warningSVG}`;
+  }
+  return `image://${val < minValue ? warningSVG : firePng}`;
+}
+
 function disposeChart() {
   if (chartInstance) {
     chartInstance.off('click');
@@ -253,7 +266,7 @@ function getMarkPointData() {
       const offsetY = Math.max(8, size * 0.35);
       markPoints.push({
         coord: [idx, val],
-        symbol: `image://${val < minValue ? warningSVG : firePng}`,
+        symbol: resolveAlarmSymbol(val, minValue),
         symbolSize: [size, size],
         symbolOffset: [0, -offsetY],
         tooltip: {
@@ -580,6 +593,7 @@ watch(
     props.yAxisName,
     props.valueLabel,
     props.valueUnit,
+    props.alarmSymbolMode,
   ],
   () => {
     nextTick(() => {

+ 84 - 32
src/views/ventAI/manageAssistent/components/AiAssistantModal.vue

@@ -203,6 +203,7 @@
   import { getScheduleList } from '/@/views/ventAI/manageAssistent/api';
   import type { AttachedFile, Message, ScheduleTask, SubAppRoute, Task } from '/@/views/ventAI/manageAssistent/types/index';
   import { buildCapsuleFiles, buildCapsuleLinks, buildCapsuleAgents } from '/@/views/ventAI/manageAssistent/lib/capsule';
+  import { extractMarkdownLinks } from '/@/views/ventAI/manageAssistent/lib/index';
   import { parseModalPreviewLink, isFileDownloadLink, downloadLinkFile } from '/@/views/ventAI/manageAssistent/lib/index';
   import { locateDevice, locateDeviceReport, locateTunnel, onLocateResult } from '/@/views/ventAI/manageAssistent/lib/modalPreviewBridge';
   import { useChatStream } from '../hooks/useChatStream';
@@ -321,15 +322,13 @@
     route: SubAppRoute | null;
   }
 
-  // 标签关闭/切换到其他面板时模型只隐藏;超过该时长未再次显示才真正销毁
-  const MODEL_PREVIEW_IDLE_MS = 5 * 60 * 1000;
+  // 模型在当前对话任务内常驻:标签切换、面板切换都只隐藏,切走任务或关闭助手时才销毁
   const modelPreview = reactive<ModelPreviewCache>({
     loaded: false,
     ownerSessionId: '',
     modelId: '',
     route: null,
   });
-  let modelPreviewIdleTimer: ReturnType<typeof setTimeout> | null = null;
   let modelPreviewTransition: Promise<void> | null = null;
   let pendingModelPreviewRoute: SubAppRoute | null = null;
   let modelPreviewEpoch = 0;
@@ -349,17 +348,9 @@
     return active?.type === 'model2d';
   });
 
-  const clearModelPreviewIdleTimer = () => {
-    if (modelPreviewIdleTimer !== null) {
-      clearTimeout(modelPreviewIdleTimer);
-      modelPreviewIdleTimer = null;
-    }
-  };
-
   const destroyModelPreview = () => {
     modelPreviewEpoch++;
     pendingModelPreviewRoute = null;
-    clearModelPreviewIdleTimer();
     modelPreview.loaded = false;
     modelPreview.modelId = '';
     modelPreview.route = null;
@@ -367,24 +358,21 @@
     lastModelLocateSignature = '';
   };
 
-  const scheduleModelPreviewIdleDestroy = () => {
-    clearModelPreviewIdleTimer();
-    if (!modelPreview.loaded) return;
-    modelPreviewIdleTimer = setTimeout(() => {
-      modelPreviewIdleTimer = null;
-      destroyModelPreview();
-    }, MODEL_PREVIEW_IDLE_MS);
-  };
-
   /**
    * 同一模型下只做定位,不重载 iframe:
    * tunid → 巷道定位,locationId → 设备定位,locationName → 报表定位。
    */
+  const locateSignature = (route: SubAppRoute | null): string => {
+    const query = route?.query || {};
+    const modelId = query.model_id || modelPreview.modelId;
+    return [modelId, query.tunid || '', query.locationId || '', query.locationName || '', query.deviceType || ''].join('|');
+  };
+
   const applyModelLocate = (route: SubAppRoute | null): boolean => {
     const query = route?.query || {};
     const modelId = query.model_id || modelPreview.modelId;
     if (!modelId) return false;
-    const signature = [modelId, query.tunid || '', query.locationId || '', query.locationName || '', query.deviceType || ''].join('|');
+    const signature = locateSignature(route);
     if (signature === lastModelLocateSignature) return false;
     lastModelLocateSignature = signature;
 
@@ -412,7 +400,7 @@
     if (modelPreview.loaded && modelPreview.modelId === modelId) {
       modelPreview.ownerSessionId = currentSessionKey();
       // 同一个模型只做定位;定位参数也没变时界面不会有任何变化,明确提示一下
-      if (!applyModelLocate(route)) message.info('当前显示的已是该模型');
+      applyModelLocate(route);
       return;
     }
 
@@ -427,12 +415,11 @@
 
         if (modelPreview.loaded && modelPreview.modelId === targetModelId) {
           modelPreview.ownerSessionId = currentSessionKey();
-          if (!applyModelLocate(target)) message.info('当前显示的已是该模型');
+          applyModelLocate(target);
           continue;
         }
 
         const epoch = modelPreviewEpoch;
-        clearModelPreviewIdleTimer();
         modelPreview.loaded = false;
         // 先让旧 iframe 真正卸载,再挂载新模型,避免同帧复用组件导致旧模型残留
         await nextTick();
@@ -446,7 +433,7 @@
 
         // 子应用只靠模型 id 不会主动加载模型;首次打开必须补发一次链接里的定位消息,
         // 由子应用在 locate 处理里完成“加载模型 + 定位”。等 ModalPreview 挂载/注册桥接后再发,
-        // 未 ready 时桥接会自动排队,ready 后补发。
+        // 未 ready 时桥接会自动排队,ready 后补发。预加载时不发,等用户点链接再定位。
         await nextTick();
         applyModelLocate(target);
       }
@@ -470,6 +457,9 @@
   };
 
   const openModelPreview = (route: SubAppRoute, title?: string) => {
+    // 面板本来就显示着同一模型、而且定位参数也没变 → 点下去界面上不会有任何变化,提示一下
+    const alreadyShowing = modelPreviewVisible.value && modelPreview.loaded && locateSignature(route) === lastModelLocateSignature;
+
     const tab = openModalPreviewTab({ title: title || '二维模型', subAppRoute: route });
     if (!tab) {
       // 没有当前会话时开不了标签页,不提示的话点击就是“没反应”
@@ -477,19 +467,82 @@
       return;
     }
     void ensureModelPreview(route);
+    if (alreadyShowing) message.info('当前显示的已是该模型');
   };
 
+  /**
+   * 预加载二维模型:在后台把子应用和模型加载好,不打开标签页,用户点链接时直接可用。
+   * 必须连同定位一起发:子应用不是靠地址里的模型 id 主动加载,而是收到定位消息后才去加载模型,
+   * 只挂 iframe 不定位的话,真正点开时还是得等模型下载。
+   */
+  const preloadModelPreview = (route: SubAppRoute | null) => {
+    if (!props.visible || !route || !route.query?.model_id) return;
+    void ensureModelPreview(route);
+  };
+
+  /**
+   * 从已完成的 AI 回答里找模型预览链接(实时回答和历史消息都走这里)。
+   * 优先选带巷道参数的链接:设备链接的编号有可能本身就对不上(子应用会定位失败),
+   * 拿巷道链接预加载更稳,模型能确保真正加载起来。
+   */
+  const scanModelLinkRoute = (): SubAppRoute | null => {
+    const list = messages.value;
+    let fallback: SubAppRoute | null = null;
+    for (let i = list.length - 1; i >= 0; i--) {
+      const msg = list[i];
+      if (msg.type !== 'ai' || msg.isLoading) continue;
+      for (const link of extractMarkdownLinks(msg.content || '')) {
+        const route = parseModalPreviewLink(link.href);
+        if (!route || !route.query.model_id) continue;
+        if (route.query.tunid) return route;
+        if (!fallback) fallback = route;
+      }
+    }
+    return fallback;
+  };
+
+  // 预加载防抖:流式输出时链接可能还不完整,等回答稳定下来再扫
+  let preloadTimer: ReturnType<typeof setTimeout> | null = null;
+  const scheduleModelPreload = () => {
+    if (!props.visible) return;
+    if (preloadTimer) clearTimeout(preloadTimer);
+    preloadTimer = setTimeout(() => {
+      preloadTimer = null;
+      preloadModelPreview(scanModelLinkRoute());
+    }, 1200);
+  };
+
+  // 实时对话:最后一条回答内容变化;历史对话:消息加载完(条数变化);重新打开助手时也补一次
+  watch(
+    () => (messages.value.length ? messages.value[messages.value.length - 1].content : ''),
+    () => scheduleModelPreload()
+  );
+  watch(
+    () => messages.value.length,
+    () => scheduleModelPreload()
+  );
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) scheduleModelPreload();
+    }
+  );
+
   // 子应用没能定位(例如链接里的设备在模型里找不到)时界面不会有任何变化,必须提示用户,
   // 否则表现就是“点了没反应”
   const offLocateResult = onLocateResult((result) => {
     if (result.ok) return;
+    // 预加载时模型面板还没显示,失败不打扰用户;等用户真的点开(面板可见)再提示
+    if (!modelPreviewVisible.value) return;
     message.warning(result.message || '未能在模型中找到该位置,请检查链接参数');
   });
 
-  // 隐藏后开始 5 分钟空闲倒计时;重新显示立即取消
-  watch(modelPreviewVisible, (visible) => {
-    if (visible) clearModelPreviewIdleTimer();
-    else scheduleModelPreviewIdleDestroy();
+  // 切走当前对话任务时销毁模型(只保留当前任务的模型常驻);标签/面板切换只隐藏,不销毁
+  watch(currentTaskId, () => {
+    if (!modelPreview.loaded) return;
+    if (modelPreview.ownerSessionId && modelPreview.ownerSessionId !== currentSessionKey()) {
+      destroyModelPreview();
+    }
   });
 
   watch(
@@ -919,7 +972,7 @@
           if (task.sessionId) {
             await deleteSession(task.sessionId);
           }
-          // 删除的是当前已加载模型所属的会话:直接销毁模型(而不是等空闲超时)
+          // 删除的是当前已加载模型所属的会话:直接销毁模型
           const ownerKey = task.sessionId || task.id;
           if (modelPreview.ownerSessionId && modelPreview.ownerSessionId === ownerKey) {
             destroyModelPreview();
@@ -1180,6 +1233,7 @@
 
   onBeforeUnmount(() => {
     offLocateResult();
+    if (preloadTimer) clearTimeout(preloadTimer);
     // 移除挂载空壳,避免残留
     portalWrapper.value?.remove();
     portalWrapper.value = null;
@@ -1190,8 +1244,6 @@
       activeFileReader.value.abort();
       activeFileReader.value = null;
     }
-    // 清掉二维模型的空闲销毁定时器,避免卸载后仍回调
-    clearModelPreviewIdleTimer();
   });
 </script>
 

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików