Преглед на файлове

[Feat 0000]张家峁煤自燃监测预警页面基础样式搭建

wangkeyi преди 3 дни
родител
ревизия
7d3cddadfd
променени са 24 файла, в които са добавени 2658 реда и са изтрити 0 реда
  1. 9 0
      src/assets/images/vent/home/selfIgnition/form-icon.svg
  2. 9 0
      src/assets/images/vent/home/selfIgnition/goaf-icon.svg
  3. 18 0
      src/assets/images/vent/home/selfIgnition/info-icon.svg
  4. BIN
      src/assets/images/vent/home/selfIgnition/list-icon-bg.png
  5. BIN
      src/assets/images/vent/home/selfIgnition/result-block-bg.png
  6. BIN
      src/assets/images/vent/home/selfIgnition/result-row-glow.png
  7. BIN
      src/assets/images/vent/home/selfIgnition/result-table-head.png
  8. BIN
      src/assets/images/vent/home/selfIgnition/result-top-bg.png
  9. BIN
      src/assets/images/vent/home/selfIgnition/risk-bar.png
  10. BIN
      src/assets/images/vent/home/selfIgnition/risk-gauge.png
  11. 11 0
      src/assets/images/vent/home/selfIgnition/risk-tag.svg
  12. BIN
      src/assets/images/vent/home/selfIgnition/select-bg.png
  13. BIN
      src/assets/images/vent/home/selfIgnition/submit-bg.png
  14. BIN
      src/assets/images/vent/home/selfIgnition/submit-glow.png
  15. BIN
      src/assets/images/vent/home/selfIgnition/tab-bg.png
  16. BIN
      src/assets/images/vent/home/selfIgnition/tab-item-bg.png
  17. BIN
      src/assets/images/vent/home/selfIgnition/top-bg.png
  18. 678 0
      src/views/vent/home/selfIgnition/components/ManualView.vue
  19. 586 0
      src/views/vent/home/selfIgnition/components/MonitorView.vue
  20. 435 0
      src/views/vent/home/selfIgnition/components/OverviewView.vue
  21. 200 0
      src/views/vent/home/selfIgnition/components/PanelSelect.vue
  22. 457 0
      src/views/vent/home/selfIgnition/data.ts
  23. 241 0
      src/views/vent/home/selfIgnition/index.vue
  24. 14 0
      src/views/vent/home/selfIgnition/utils.ts

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

@@ -0,0 +1,9 @@
+<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>

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

@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="23.971" height="15.553" viewBox="0 0 23.971 15.553">
+  <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="路径_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)"/>
+</svg>

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

@@ -0,0 +1,18 @@
+<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>

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


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


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


BIN
src/assets/images/vent/home/selfIgnition/result-table-head.png


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


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


BIN
src/assets/images/vent/home/selfIgnition/risk-gauge.png


+ 11 - 0
src/assets/images/vent/home/selfIgnition/risk-tag.svg

@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="158.716" height="33.632" viewBox="0 0 158.716 33.632">
+  <defs>
+    <linearGradient id="linear-gradient" x1="0.029" y1="0.5" x2="0.961" y2="0.5" gradientUnits="objectBoundingBox">
+      <stop offset="0" stop-color="#ff9d1c"/>
+      <stop offset="1" stop-color="#ff861c"/>
+    </linearGradient>
+  </defs>
+  <g id="组_16818" data-name="组 16818" transform="translate(-1123.784 -221.585)">
+    <path id="路径_58028" data-name="路径 58028" d="M-13164.9,3557.585l19.338,33.631h117.321l22.057-33.631Z" transform="translate(14288.682 -3336)" fill="url(#linear-gradient)"/>
+  </g>
+</svg>

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


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


BIN
src/assets/images/vent/home/selfIgnition/submit-glow.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/top-bg.png


+ 678 - 0
src/views/vent/home/selfIgnition/components/ManualView.vue

@@ -0,0 +1,678 @@
+<template>
+  <div class="ignition-view manual-view">
+    <!-- 左:人工录入 -->
+    <ModuleCommon
+      :show-style="panelShowStyle(23, 105, 335, 934)"
+      :module-data="EMPTY_MODULE_DATA"
+      module-name="人工录入"
+      device-type="ignition_manual_input"
+      :data="{}"
+      :visible="true"
+    >
+      <div class="manual-form">
+        <div class="group-list">
+          <div v-for="group in groups" :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>
+            <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>
+              <div class="field-list">
+                <div v-for="field in group.fields" :key="field.key" class="field-row">
+                  <span class="field-label">{{ field.label }}:</span>
+                  <PanelSelect v-model="field.value" :options="field.options" />
+                </div>
+              </div>
+            </template>
+          </div>
+        </div>
+        <button class="action-button submit-button" @click="submit">提交</button>
+      </div>
+    </ModuleCommon>
+
+    <!-- 右:评价结果 -->
+    <ModuleCommon
+      :show-style="panelShowStyle(1523, 105, 337, 934)"
+      :module-data="EMPTY_MODULE_DATA"
+      module-name="评价结果"
+      device-type="ignition_evaluation_result"
+      :data="{}"
+      :visible="true"
+    >
+      <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-value">{{ result.score.toFixed(1) }}</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="" />
+            </div>
+            <span class="risk-tag" :style="{ '--risk-color': result.color }">{{ result.levelText }}</span>
+          </div>
+        </div>
+
+        <div class="result-info">
+          <div class="info-row"
+            ><span>评价对象:</span><em>{{ result.target }}</em></div
+          >
+          <div class="info-row"
+            ><span>数据时间:</span><em>{{ result.time }}</em></div
+          >
+          <div class="info-row"
+            ><span>数据来源:</span><em>{{ result.source }}</em></div
+          >
+        </div>
+
+        <div class="level-table">
+          <div class="level-head"><span>评价等级</span><span>风险分值范围</span><span>对应颜色</span></div>
+          <div v-for="item in levels" :key="item.level" class="level-row" :class="{ highlight: item.level === result.highlightLevel }">
+            <span class="level-name">
+              <i :style="{ background: item.color, boxShadow: `0 0 8px ${item.color}` }"></i>
+              {{ item.name }}
+            </span>
+            <span>{{ item.range }}</span>
+            <span>{{ item.colorName }}</span>
+          </div>
+        </div>
+
+        <div class="suggestion-box">
+          <span class="suggestion-icon"></span>
+          <p>{{ result.suggestion }}</p>
+        </div>
+
+        <button class="action-button report-button" @click="showReport = true">查看评价报告</button>
+      </div>
+    </ModuleCommon>
+
+    <Transition name="ignition-fade">
+      <div v-if="showReport" class="report-mask" @click.self="showReport = false">
+        <div class="report-dialog">
+          <div class="report-title">采空区自燃风险评价报告</div>
+          <div class="report-body">
+            <div class="report-row"
+              ><span>评价对象</span><em>{{ result.target }}</em></div
+            >
+            <div class="report-row"
+              ><span>风险分值</span><em>{{ result.score.toFixed(1) }}</em></div
+            >
+            <div class="report-row"
+              ><span>评价等级</span><em :style="{ color: result.color }">{{ result.levelText }}</em></div
+            >
+            <div class="report-row"
+              ><span>数据时间</span><em>{{ result.time }}</em></div
+            >
+            <div class="report-row"
+              ><span>数据来源</span><em>{{ result.source }}</em></div
+            >
+            <div class="report-suggestion">
+              <span class="suggestion-icon"></span>
+              <p>{{ result.suggestion }}</p>
+            </div>
+          </div>
+          <button class="action-button report-close" @click="showReport = false">关闭</button>
+        </div>
+      </div>
+    </Transition>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { reactive, ref } from 'vue';
+  import ModuleCommon from '../../configurable/components/ModuleCommon.vue';
+  import PanelSelect from './PanelSelect.vue';
+  import { EMPTY_MODULE_DATA, panelShowStyle } from '../utils';
+  import { INITIAL_EVALUATION, RISK_LEVELS, SUGGESTIONS, calcManualScore, createManualGroups, getRiskLevel } from '../data';
+  import type { EvaluationResult, ManualGroup } from '../data';
+  import { useMessage } from '/@/hooks/web/useMessage';
+
+  const { createMessage } = useMessage();
+
+  const groups = ref<ManualGroup[]>(createManualGroups());
+  const collapsed = reactive<Record<string, boolean>>({});
+  const levels = RISK_LEVELS;
+  const result = ref<EvaluationResult>({ ...INITIAL_EVALUATION });
+  const showReport = ref(false);
+
+  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())}`;
+  }
+
+  function submit() {
+    const score = calcManualScore(groups.value);
+    const risk = getRiskLevel(score);
+    result.value = {
+      score,
+      level: risk.level,
+      levelText: risk.name,
+      color: risk.color,
+      highlightLevel: risk.level,
+      target: result.value.target,
+      time: formatDateTime(new Date()),
+      source: '人工录入',
+      suggestion: SUGGESTIONS[risk.level],
+    };
+    createMessage.success('评价完成,风险分值已更新');
+  }
+</script>
+
+<style lang="less" scoped>
+  .manual-view {
+    position: absolute;
+    inset: 0;
+  }
+
+  .manual-form {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    padding: 8px 10px 12px;
+    box-sizing: border-box;
+  }
+
+  .group-list {
+    flex: 1;
+    min-height: 0;
+    padding-right: 2px;
+  }
+
+  .form-group {
+    margin-bottom: 12px;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+
+  .group-collapse {
+    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));
+    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;
+
+    &.collapsed {
+      transform: rotate(-90deg);
+    }
+  }
+
+  .group-title {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    height: 24px;
+    padding: 0 4px;
+    color: #a9dcf4;
+    font-size: 12px;
+    letter-spacing: 0.5px;
+  }
+
+  .title-bars {
+    display: flex;
+    align-items: flex-end;
+    gap: 2px;
+    height: 12px;
+
+    i {
+      width: 3px;
+      background: linear-gradient(180deg, #b3fff5, #17b7ff);
+
+      &:nth-child(1) {
+        height: 5px;
+      }
+
+      &:nth-child(2) {
+        height: 12px;
+      }
+
+      &:nth-child(3) {
+        height: 8px;
+      }
+
+      &:nth-child(4) {
+        height: 4px;
+      }
+    }
+  }
+
+  .title-dots {
+    display: flex;
+    align-items: center;
+    gap: 3px;
+    margin-left: auto;
+
+    i {
+      width: 2px;
+      height: 2px;
+      background: rgba(61, 246, 255, 0.65);
+    }
+
+    b {
+      width: 3px;
+      height: 3px;
+      background: #3df6ff;
+      box-shadow: 0 0 4px rgba(61, 246, 255, 0.9);
+    }
+  }
+
+  .field-list {
+    display: flex;
+    flex-direction: column;
+    gap: 7px;
+  }
+
+  .field-row {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    min-width: 0;
+
+    :deep(.ignition-select) {
+      flex: 1;
+      min-width: 0;
+      height: 38px;
+      font-size: 14px;
+    }
+  }
+
+  .field-label {
+    width: 88px;
+    flex: none;
+    text-align: right;
+    color: #fff;
+    font-size: 14px;
+    white-space: nowrap;
+  }
+
+  .result-panel {
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+    height: 100%;
+    padding: 8px 12px 12px;
+    box-sizing: border-box;
+  }
+
+  .result-top {
+    flex: none;
+    height: 165px;
+    padding-top: 10px;
+    box-sizing: border-box;
+    background: url('@/assets/images/vent/home/selfIgnition/result-top-bg.png') no-repeat center / 100% 100%;
+  }
+
+  .score-line {
+    display: flex;
+    align-items: baseline;
+    padding: 0 20px;
+  }
+
+  .score-label {
+    color: #fff;
+    font-size: 20px;
+    letter-spacing: 1px;
+  }
+
+  .score-info {
+    width: 12px;
+    height: 12px;
+    margin-left: 5px;
+    align-self: center;
+    opacity: 0.85;
+  }
+
+  .score-value {
+    margin-left: 16px;
+    color: #fff;
+    font-size: 42px;
+    font-weight: 700;
+    line-height: 1;
+    letter-spacing: 1px;
+    text-shadow: 0 0 16px rgba(255, 255, 255, 0.35);
+  }
+
+  .gauge-line {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-top: 16px;
+    padding: 0 18px;
+  }
+
+  .gauge {
+    display: flex;
+    align-items: center;
+    width: 150px;
+    height: 26px;
+  }
+
+  .gauge-segment {
+    width: 80px;
+    height: 22px;
+    object-fit: fill;
+
+    & + & {
+      margin-left: -8px;
+    }
+  }
+
+  .gauge-segment-2 {
+    opacity: 0.72;
+  }
+
+  .risk-tag {
+    display: flex;
+    align-items: center;
+    width: 159px;
+    height: 34px;
+    padding-left: 22px;
+    box-sizing: border-box;
+    color: #fff;
+    font-size: 15px;
+    font-weight: 600;
+    letter-spacing: 0.5px;
+    white-space: nowrap;
+    -webkit-mask: url('@/assets/images/vent/home/selfIgnition/risk-tag.svg') no-repeat center / 100% 100%;
+    mask: url('@/assets/images/vent/home/selfIgnition/risk-tag.svg') no-repeat center / 100% 100%;
+    background: var(--risk-color, #ff9d1c);
+  }
+
+  .result-info {
+    flex: none;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    gap: 2px;
+    padding: 10px 14px;
+    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);
+  }
+
+  .info-row {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    height: 38px;
+    color: #fff;
+    font-size: 15px;
+    letter-spacing: 0.5px;
+
+    em {
+      color: #3df6ff;
+      font-style: normal;
+      font-size: 16px;
+      text-shadow: 0 0 8px rgba(61, 246, 255, 0.35);
+    }
+  }
+
+  .level-table {
+    flex: none;
+    padding-top: 2px;
+  }
+
+  .level-head,
+  .level-row {
+    display: flex;
+    align-items: center;
+    text-align: center;
+  }
+
+  .level-head {
+    height: 34px;
+    border: 1px solid rgba(61, 246, 255, 0.4);
+    border-radius: 4px;
+    color: #3df6ff;
+    font-size: 13px;
+    background: url('@/assets/images/vent/home/selfIgnition/result-table-head.png') no-repeat center / 100% 100%;
+
+    span:nth-child(1) {
+      width: 42%;
+    }
+
+    span:nth-child(2) {
+      width: 32%;
+    }
+
+    span:nth-child(3) {
+      width: 26%;
+    }
+  }
+
+  .level-row {
+    position: relative;
+    height: 38px;
+    color: #e8f8ff;
+    font-size: 14px;
+    background: url('@/assets/images/vent/home/selfIgnition/result-row-glow.png') no-repeat center bottom / 100% 10px;
+
+    span:nth-child(1) {
+      width: 42%;
+    }
+
+    span:nth-child(2) {
+      width: 32%;
+    }
+
+    span:nth-child(3) {
+      width: 26%;
+    }
+
+    &.highlight {
+      outline: 1px solid #ff9d1c;
+      outline-offset: -1px;
+      background-color: rgba(255, 157, 28, 0.08);
+      box-shadow: inset 0 0 18px rgba(255, 157, 28, 0.16);
+    }
+  }
+
+  .level-name {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 6px;
+
+    i {
+      width: 7px;
+      height: 7px;
+      border-radius: 50%;
+    }
+  }
+
+  .suggestion-box {
+    position: relative;
+    flex: 1;
+    min-height: 0;
+    padding: 14px 16px 14px 34px;
+    box-sizing: border-box;
+    overflow-y: auto;
+    border: 1px solid rgba(61, 246, 255, 0.4);
+    background: url('@/assets/images/vent/home/selfIgnition/result-block-bg.png') no-repeat center / 100% 100%;
+
+    &::-webkit-scrollbar {
+      width: 4px;
+    }
+
+    &::-webkit-scrollbar-thumb {
+      background: rgba(61, 246, 255, 0.35);
+      border-radius: 2px;
+    }
+
+    p {
+      margin: 0;
+      color: #fff;
+      font-size: 15px;
+      line-height: 30px;
+      letter-spacing: 0.5px;
+    }
+  }
+
+  .suggestion-icon {
+    position: absolute;
+    left: 16px;
+    top: 20px;
+    width: 9px;
+    height: 9px;
+    background: #f5222d;
+    box-shadow: 0 0 9px rgba(245, 34, 45, 0.85);
+    transform: rotate(45deg);
+  }
+
+  .action-button {
+    position: relative;
+    flex: none;
+    width: 100%;
+    height: 42px;
+    border: 1px solid rgba(61, 246, 255, 0.55);
+    border-radius: 3px;
+    background:
+      url('@/assets/images/vent/home/selfIgnition/submit-bg.png') no-repeat center / 100% 100%,
+      linear-gradient(180deg, #1496e6 0%, #0a6cb5 100%);
+    color: #fff;
+    font-size: 16px;
+    letter-spacing: 3px;
+    cursor: pointer;
+    transition:
+      filter 0.2s,
+      box-shadow 0.2s;
+
+    &:hover {
+      filter: brightness(1.12);
+      box-shadow: 0 0 16px rgba(61, 246, 255, 0.35);
+    }
+
+    &::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;
+    }
+  }
+
+  .submit-button {
+    margin-top: 8px;
+  }
+
+  .report-dialog {
+    width: 540px;
+    padding: 22px 26px 24px;
+    box-sizing: border-box;
+    border: 1px solid rgba(61, 246, 255, 0.75);
+    background: linear-gradient(180deg, #0b3a5e 0%, #071d33 100%);
+    box-shadow: 0 0 40px rgba(61, 246, 255, 0.35);
+  }
+
+  .report-title {
+    padding-bottom: 14px;
+    border-bottom: 1px solid rgba(61, 246, 255, 0.3);
+    color: #3df6ff;
+    font-family: 'douyuFont', sans-serif;
+    font-size: 20px;
+    letter-spacing: 2px;
+    text-align: center;
+  }
+
+  .report-body {
+    padding: 16px 0;
+  }
+
+  .report-row {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    height: 36px;
+    color: #cfefff;
+    font-size: 14px;
+
+    em {
+      color: #fff;
+      font-style: normal;
+      font-size: 15px;
+    }
+  }
+
+  .report-suggestion {
+    position: relative;
+    margin-top: 12px;
+    padding: 14px 16px 14px 34px;
+    border: 1px solid rgba(61, 246, 255, 0.3);
+    background: rgba(7, 42, 70, 0.7);
+
+    p {
+      margin: 0;
+      color: #fff;
+      font-size: 14px;
+      line-height: 26px;
+    }
+  }
+
+  .report-close {
+    margin-top: 4px;
+  }
+
+  .report-mask {
+    position: absolute;
+    inset: 0;
+    z-index: 100;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: rgba(2, 10, 20, 0.72);
+    backdrop-filter: blur(3px);
+  }
+
+  .ignition-fade-enter-active,
+  .ignition-fade-leave-active {
+    transition: opacity 0.2s ease;
+  }
+
+  .ignition-fade-enter-from,
+  .ignition-fade-leave-to {
+    opacity: 0;
+  }
+</style>

+ 586 - 0
src/views/vent/home/selfIgnition/components/MonitorView.vue

@@ -0,0 +1,586 @@
+<template>
+  <div class="ignition-view monitor-view">
+    <!-- 左上:设备运行状态 -->
+    <ModuleCommon
+      :show-style="panelShowStyle(23, 105, 328, 430)"
+      :module-data="EMPTY_MODULE_DATA"
+      module-name="设备运行状态"
+      device-type="ignition_device_status"
+      :data="{}"
+      :visible="true"
+    >
+      <div class="device-panel">
+        <div v-for="device in devices" :key="device.name" class="device-item">
+          <span class="device-badge"><i></i></span>
+          <div class="device-main">
+            <div class="device-line">
+              <span class="device-name">{{ device.name }}</span>
+              <span class="device-rate">{{ runningRate(device) }}%</span>
+            </div>
+            <div class="device-bar"><i :style="{ width: `${runningRate(device)}%` }"></i></div>
+            <div class="device-meta">
+              安装 <em>{{ device.installed }}</em> 套 · 运行 <em>{{ device.running }}</em> 套
+            </div>
+          </div>
+        </div>
+      </div>
+    </ModuleCommon>
+
+    <!-- 左下:实时预警信息 -->
+    <ModuleCommon
+      :show-style="panelShowStyle(23, 555, 328, 420)"
+      :module-data="EMPTY_MODULE_DATA"
+      module-name="实时预警信息"
+      device-type="ignition_realtime_alert"
+      :data="{}"
+      :visible="true"
+    >
+      <div class="alert-panel">
+        <div v-for="alert in alerts" :key="alert.id" class="alert-item" :style="{ '--alert-color': alertColor(alert.level) }">
+          <div class="alert-top">
+            <span class="alert-time">{{ alert.time }}</span>
+            <span class="alert-level">{{ alertLevelName(alert.level) }}</span>
+            <span class="alert-location">{{ alert.location }}</span>
+          </div>
+          <div class="alert-message">{{ alert.message }}</div>
+        </div>
+      </div>
+    </ModuleCommon>
+
+    <!-- 右上:束管监测实时分析 -->
+    <ModuleCommon
+      :show-style="panelShowStyle(380, 145, 1477, 390)"
+      :module-data="EMPTY_MODULE_DATA"
+      module-name="束管监测实时分析"
+      device-type="ignition_bundle_monitor"
+      :data="{}"
+      :visible="true"
+    >
+      <div class="bundle-panel">
+        <div class="bundle-head">
+          <span class="col-point">测点名称</span>
+          <span class="col-value">O₂(%)</span>
+          <span class="col-value">CO(ppm)</span>
+          <span class="col-value">CH₄(%)</span>
+          <span class="col-value">C₂H₄(ppm)</span>
+          <span class="col-value">温度(℃)</span>
+          <span class="col-status">状态</span>
+        </div>
+        <div class="bundle-body">
+          <div v-for="row in bundleRows" :key="row.point" class="bundle-row">
+            <span class="col-point">{{ row.point }}</span>
+            <span class="col-value" :class="{ warning: Number(row.o2) < 18 }">{{ row.o2 }}</span>
+            <span class="col-value" :class="{ warning: row.co >= 24 }">{{ row.co }}</span>
+            <span class="col-value">{{ row.ch4 }}</span>
+            <span class="col-value">{{ row.c2h4 }}</span>
+            <span class="col-value">{{ row.temp.toFixed(1) }}</span>
+            <span class="col-status">
+              <i class="status-tag" :class="statusClass(row.status)">{{ row.status }}</i>
+            </span>
+          </div>
+        </div>
+      </div>
+    </ModuleCommon>
+
+    <!-- 右下:光纤测温趋势分析 -->
+    <ModuleCommon
+      :show-style="panelShowStyle(380, 555, 1477, 420)"
+      :module-data="EMPTY_MODULE_DATA"
+      module-name="光纤测温趋势分析"
+      device-type="ignition_fiber_trend"
+      :data="{}"
+      :visible="true"
+    >
+      <div class="fiber-panel">
+        <div class="chart-area">
+          <div class="chart-legend">
+            <span class="legend-item"><i class="legend-max"></i>最高温度</span>
+            <span class="legend-item"><i class="legend-avg"></i>平均温度</span>
+          </div>
+          <svg class="chart-svg" viewBox="0 0 1100 300" preserveAspectRatio="none">
+            <defs>
+              <linearGradient id="ignitionFiberArea" x1="0" y1="0" x2="0" y2="1">
+                <stop offset="0" stop-color="#3df6ff" stop-opacity="0.28" />
+                <stop offset="1" stop-color="#3df6ff" stop-opacity="0" />
+              </linearGradient>
+            </defs>
+            <g>
+              <line
+                v-for="tick in yTicks"
+                :key="`grid-${tick}`"
+                :x1="chartPadding.left"
+                :x2="chartWidth - chartPadding.right"
+                :y1="yPosition(tick)"
+                :y2="yPosition(tick)"
+                stroke="rgba(61, 246, 255, 0.14)"
+                stroke-width="1"
+                vector-effect="non-scaling-stroke"
+              />
+              <text
+                v-for="tick in yTicks"
+                :key="`label-${tick}`"
+                :x="chartPadding.left - 10"
+                :y="yPosition(tick) + 4"
+                fill="#8fc7e8"
+                font-size="11"
+                text-anchor="end"
+              >
+                {{ tick }}℃
+              </text>
+            </g>
+            <path :d="maxAreaPath" fill="url(#ignitionFiberArea)" />
+            <polyline :points="maxPoints" fill="none" stroke="#3df6ff" stroke-width="2" vector-effect="non-scaling-stroke" stroke-linejoin="round" />
+            <polyline
+              :points="avgPoints"
+              fill="none"
+              stroke="#ffd15c"
+              stroke-width="1.5"
+              stroke-dasharray="5 3"
+              vector-effect="non-scaling-stroke"
+              stroke-linejoin="round"
+            />
+            <g>
+              <text v-for="item in xLabels" :key="item.text" :x="item.x" :y="chartHeight - 8" fill="#8fc7e8" font-size="11" text-anchor="middle">
+                {{ item.text }}
+              </text>
+            </g>
+          </svg>
+        </div>
+        <div class="fiber-summary">
+          <div class="summary-item">
+            <span>最高温度</span>
+            <em class="max">{{ fiber.maxValue.toFixed(1) }}℃</em>
+          </div>
+          <div class="summary-item">
+            <span>平均温度</span>
+            <em class="avg">{{ fiber.avgValue.toFixed(1) }}℃</em>
+          </div>
+          <div class="summary-item">
+            <span>测温点数</span>
+            <em>{{ fiber.pointCount }} 个</em>
+          </div>
+        </div>
+      </div>
+    </ModuleCommon>
+  </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 { RISK_LEVELS } from '../data';
+  import type { AlertItem, BundleRow, DeviceInstallItem, FiberTrend } from '../data';
+
+  const props = defineProps<{
+    devices: DeviceInstallItem[];
+    alerts: AlertItem[];
+    bundleRows: BundleRow[];
+    fiber: FiberTrend;
+  }>();
+
+  const chartWidth = 1100;
+  const chartHeight = 300;
+  const chartPadding = { left: 52, right: 16, top: 16, bottom: 30 };
+  const yMin = 10;
+  const yMax = 40;
+  const yTicks = [10, 20, 30, 40];
+
+  function runningRate(device: DeviceInstallItem) {
+    if (!device.installed) return '0';
+    return ((device.running / device.installed) * 100).toFixed(1);
+  }
+
+  function alertColor(level: number) {
+    return RISK_LEVELS[Math.min(Math.max(level, 1), 5) - 1].color;
+  }
+
+  function alertLevelName(level: number) {
+    return RISK_LEVELS[Math.min(Math.max(level, 1), 5) - 1].name.replace(/^[IVX]+\s*/, '');
+  }
+
+  function statusClass(status: BundleRow['status']) {
+    if (status === '预警') return 'warning';
+    if (status === '关注') return 'attention';
+    return 'normal';
+  }
+
+  function xPosition(index: number) {
+    const plotWidth = chartWidth - chartPadding.left - chartPadding.right;
+    const step = plotWidth / Math.max(1, props.fiber.times.length - 1);
+    return chartPadding.left + index * step;
+  }
+
+  function yPosition(value: number) {
+    const plotHeight = chartHeight - chartPadding.top - chartPadding.bottom;
+    const ratio = (value - yMin) / (yMax - yMin);
+    return chartPadding.top + (1 - Math.min(Math.max(ratio, 0), 1)) * plotHeight;
+  }
+
+  function toPoints(values: number[]) {
+    return values.map((value, index) => `${xPosition(index)},${yPosition(value)}`).join(' ');
+  }
+
+  const maxPoints = computed(() => toPoints(props.fiber.maxTemp));
+  const avgPoints = computed(() => toPoints(props.fiber.avgTemp));
+
+  const maxAreaPath = computed(() => {
+    const plotBottom = chartHeight - chartPadding.bottom;
+    const firstX = xPosition(0);
+    const lastX = xPosition(props.fiber.maxTemp.length - 1);
+    return `M ${firstX},${plotBottom} L ${maxPoints.value.replace(/ /g, ' L ')} L ${lastX},${plotBottom} Z`;
+  });
+
+  const xLabels = computed(() => {
+    const indexes = [0, 4, 8, 12, 16, 20, props.fiber.times.length - 1];
+    return indexes.map((index) => ({
+      x: xPosition(index),
+      text: index === props.fiber.times.length - 1 ? '24:00' : props.fiber.times[index],
+    }));
+  });
+</script>
+
+<style lang="less" scoped>
+  .monitor-view {
+    position: absolute;
+    inset: 0;
+  }
+
+  .device-panel {
+    display: flex;
+    flex-direction: column;
+    gap: 9px;
+    height: 100%;
+    padding: 12px 12px 14px;
+    box-sizing: border-box;
+  }
+
+  .device-item {
+    display: flex;
+    align-items: center;
+    flex: 1;
+    min-height: 0;
+    gap: 10px;
+    padding: 6px 12px 6px 10px;
+    box-sizing: border-box;
+    border: 1px solid rgba(61, 246, 255, 0.16);
+    background: linear-gradient(90deg, rgba(11, 57, 90, 0.72), rgba(8, 37, 61, 0.3));
+  }
+
+  .device-badge {
+    position: relative;
+    width: 30px;
+    height: 30px;
+    flex: none;
+    border: 1px solid rgba(61, 246, 255, 0.55);
+    border-radius: 50%;
+    background: radial-gradient(circle, rgba(61, 246, 255, 0.28), rgba(5, 32, 55, 0.4));
+
+    i {
+      position: absolute;
+      left: 50%;
+      top: 50%;
+      width: 10px;
+      height: 10px;
+      margin: -5px 0 0 -5px;
+      border-radius: 2px;
+      background: #3df6ff;
+      box-shadow: 0 0 9px rgba(61, 246, 255, 0.9);
+    }
+  }
+
+  .device-main {
+    flex: 1;
+    min-width: 0;
+  }
+
+  .device-line {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+
+  .device-name {
+    color: #fff;
+    font-size: 14px;
+  }
+
+  .device-rate {
+    color: #3df6ff;
+    font-family: electronicFont, sans-serif;
+    font-size: 18px;
+  }
+
+  .device-bar {
+    position: relative;
+    height: 4px;
+    margin: 7px 0 6px;
+    overflow: hidden;
+    background: rgba(61, 246, 255, 0.12);
+
+    i {
+      display: block;
+      height: 100%;
+      background: linear-gradient(90deg, #0e7fc1, #3df6ff);
+      box-shadow: 0 0 8px rgba(61, 246, 255, 0.7);
+      transition: width 0.4s ease;
+    }
+  }
+
+  .device-meta {
+    color: #9fcde3;
+    font-size: 12px;
+
+    em {
+      color: #3df6ff;
+      font-style: normal;
+      font-size: 13px;
+    }
+  }
+
+  .alert-panel {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+    height: 100%;
+    padding: 12px;
+    box-sizing: border-box;
+    overflow-y: auto;
+
+    &::-webkit-scrollbar {
+      width: 4px;
+    }
+
+    &::-webkit-scrollbar-thumb {
+      background: rgba(61, 246, 255, 0.35);
+      border-radius: 2px;
+    }
+  }
+
+  .alert-item {
+    flex: none;
+    padding: 8px 10px 8px 12px;
+    border-left: 2px solid var(--alert-color);
+    background: linear-gradient(90deg, rgba(11, 57, 90, 0.72), rgba(8, 37, 61, 0.25));
+  }
+
+  .alert-top {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    height: 20px;
+  }
+
+  .alert-time {
+    color: #8fc7e8;
+    font-size: 12px;
+    font-family: electronicFont, sans-serif;
+    letter-spacing: 1px;
+  }
+
+  .alert-level {
+    padding: 0 5px;
+    border: 1px solid var(--alert-color);
+    color: var(--alert-color);
+    font-size: 11px;
+    line-height: 16px;
+  }
+
+  .alert-location {
+    margin-left: auto;
+    color: #fff;
+    font-size: 12px;
+  }
+
+  .alert-message {
+    margin-top: 5px;
+    color: #fff;
+    font-size: 13px;
+    line-height: 20px;
+  }
+
+  .bundle-panel {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    padding: 12px 16px 14px;
+    box-sizing: border-box;
+  }
+
+  .bundle-head,
+  .bundle-row {
+    display: flex;
+    align-items: center;
+    text-align: center;
+  }
+
+  .bundle-head {
+    flex: none;
+    height: 38px;
+    border: 1px solid rgba(61, 246, 255, 0.4);
+    color: #3df6ff;
+    font-size: 14px;
+    letter-spacing: 1px;
+    background: linear-gradient(180deg, rgba(14, 70, 108, 0.85), rgba(10, 49, 79, 0.6));
+  }
+
+  .bundle-body {
+    display: flex;
+    flex: 1;
+    min-height: 0;
+    flex-direction: column;
+    border: 1px solid rgba(61, 246, 255, 0.18);
+    border-top: none;
+  }
+
+  .bundle-row {
+    flex: 1;
+    min-height: 0;
+    color: #fff;
+    font-size: 14px;
+    background: rgba(11, 51, 85, 0.55);
+
+    &:nth-child(even) {
+      background: rgba(13, 65, 103, 0.62);
+    }
+
+    & + & {
+      border-top: 1px solid rgba(61, 246, 255, 0.1);
+    }
+  }
+
+  .col-point {
+    width: 24%;
+    text-align: left;
+    padding-left: 22px;
+    box-sizing: border-box;
+  }
+
+  .col-value {
+    width: 12%;
+  }
+
+  .col-status {
+    width: 16%;
+  }
+
+  .warning {
+    color: #ffd15c;
+    text-shadow: 0 0 8px rgba(255, 209, 92, 0.45);
+  }
+
+  .status-tag {
+    display: inline-block;
+    min-width: 52px;
+    padding: 2px 10px;
+    box-sizing: border-box;
+    border: 1px solid currentColor;
+    font-style: normal;
+    font-size: 12px;
+    line-height: 18px;
+
+    &.normal {
+      color: #3df6ff;
+      background: rgba(61, 246, 255, 0.08);
+    }
+
+    &.attention {
+      color: #ffd15c;
+      background: rgba(255, 209, 92, 0.08);
+    }
+
+    &.warning {
+      color: #ff9d1c;
+      background: rgba(255, 157, 28, 0.1);
+    }
+  }
+
+  .fiber-panel {
+    display: flex;
+    gap: 16px;
+    height: 100%;
+    padding: 12px 16px 14px;
+    box-sizing: border-box;
+  }
+
+  .chart-area {
+    display: flex;
+    flex: 1;
+    min-width: 0;
+    flex-direction: column;
+  }
+
+  .chart-legend {
+    display: flex;
+    align-items: center;
+    gap: 20px;
+    height: 26px;
+    flex: none;
+    color: #a9dcf4;
+    font-size: 12px;
+  }
+
+  .legend-item {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+  }
+
+  .legend-max,
+  .legend-avg {
+    display: inline-block;
+    width: 16px;
+    height: 2px;
+  }
+
+  .legend-max {
+    background: #3df6ff;
+    box-shadow: 0 0 6px rgba(61, 246, 255, 0.8);
+  }
+
+  .legend-avg {
+    background: #ffd15c;
+  }
+
+  .chart-svg {
+    flex: 1;
+    min-height: 0;
+    width: 100%;
+  }
+
+  .fiber-summary {
+    display: flex;
+    flex: none;
+    width: 230px;
+    flex-direction: column;
+    justify-content: center;
+    gap: 18px;
+    padding-left: 22px;
+    border-left: 1px solid rgba(61, 246, 255, 0.18);
+  }
+
+  .summary-item {
+    display: flex;
+    align-items: baseline;
+    justify-content: space-between;
+    color: #a9dcf4;
+    font-size: 13px;
+
+    em {
+      color: #fff;
+      font-style: normal;
+      font-family: electronicFont, sans-serif;
+      font-size: 26px;
+      line-height: 1;
+      text-shadow: 0 0 10px rgba(255, 255, 255, 0.25);
+
+      &.max {
+        color: #3df6ff;
+      }
+
+      &.avg {
+        color: #ffd15c;
+      }
+    }
+  }
+</style>

+ 435 - 0
src/views/vent/home/selfIgnition/components/OverviewView.vue

@@ -0,0 +1,435 @@
+<template>
+  <div class="ignition-view overview-view">
+    <!-- 左:矿井采空区概览 -->
+    <ModuleCommon
+      class="overview-module"
+      :show-style="panelShowStyle(20, 105, 334, 436)"
+      :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">
+          <span class="goaf-icon">
+            <img src="@/assets/images/vent/home/selfIgnition/goaf-icon.svg" alt="" />
+          </span>
+          <span class="goaf-label">{{ item.label }}:</span>
+          <span class="goaf-value">{{ item.value }}</span>
+        </div>
+      </div>
+    </ModuleCommon>
+
+    <!-- 左下:安装设备情况 -->
+    <ModuleCommon
+      class="install-module"
+      :show-style="panelShowStyle(20, 568, 334, 472)"
+      :module-data="EMPTY_MODULE_DATA"
+      module-name="安装设备情况"
+      device-type="ignition_device_install"
+      :data="{}"
+      :visible="true"
+    >
+      <div class="install-panel">
+        <div v-for="device in devices" :key="device.name" class="install-item">
+          <span class="install-square"></span>
+          <span class="install-name">{{ device.name }}</span>
+          <span class="install-metric"
+            >安装:<em>{{ device.installed }}套</em></span
+          >
+          <span class="install-metric"
+            >运行:<em>{{ device.running }}套</em></span
+          >
+        </div>
+      </div>
+    </ModuleCommon>
+
+    <!-- 中下:整体预警等级 -->
+    <ModuleCommon
+      :show-style="panelShowStyle(413, 683, 343, 357)"
+      :module-data="EMPTY_MODULE_DATA"
+      module-name="矿井采空区整体预警等级"
+      device-type="ignition_overall_warning"
+      :data="{}"
+      :visible="true"
+    >
+      <div class="overall-panel">
+        <div class="overall-level" :style="{ color: overall.color }">{{ overall.levelText }} {{ overall.levelName }}</div>
+        <div class="overall-bar">
+          <img src="@/assets/images/vent/home/selfIgnition/risk-bar.png" alt="" />
+        </div>
+        <div class="overall-label">系统采空区总预警</div>
+        <div class="overall-total" :style="{ color: overall.color }">{{ overall.total }}</div>
+      </div>
+    </ModuleCommon>
+
+    <!-- 右下:分级预警表 -->
+    <ModuleCommon
+      class="table-module"
+      :show-style="panelShowStyle(793, 683, 1105, 357)"
+      :module-data="EMPTY_MODULE_DATA"
+      module-name="矿井采空区分级预警"
+      device-type="ignition_warning_table"
+      :data="{}"
+      :visible="true"
+    >
+      <div class="warning-table-panel">
+        <div class="warning-table">
+          <div class="table-head">
+            <span class="col-index">序号</span>
+            <span class="col-name">采空区名称</span>
+            <span class="col-seam">所属煤层</span>
+            <span class="col-level">预警等级</span>
+            <span class="col-status">状态</span>
+          </div>
+          <div class="table-body">
+            <div v-for="row in pagedWarnings" :key="row.id" class="table-row">
+              <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">{{ row.level }}</span>
+              <span class="col-status">{{ row.status }}</span>
+            </div>
+          </div>
+        </div>
+        <div class="table-pager">
+          <i v-for="pageNo in totalPages" :key="pageNo" :class="{ active: pageNo === currentPage }" @click="currentPage = pageNo"></i>
+        </div>
+      </div>
+    </ModuleCommon>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { computed, ref } from 'vue';
+  import ModuleCommon from '../../configurable/components/ModuleCommon.vue';
+  import { EMPTY_MODULE_DATA, panelShowStyle } from '../utils';
+  import type { DeviceInstallItem, GoafOverviewItem, OverallWarning, WarningRow } from '../data';
+
+  const props = defineProps<{
+    overview: GoafOverviewItem[];
+    devices: DeviceInstallItem[];
+    overall: OverallWarning;
+    warnings: WarningRow[];
+  }>();
+
+  const PAGE_SIZE = 3;
+  const currentPage = ref(1);
+
+  const totalPages = computed(() => Math.max(1, Math.ceil(props.warnings.length / PAGE_SIZE)));
+
+  const pagedWarnings = computed(() => {
+    const start = (currentPage.value - 1) * PAGE_SIZE;
+    return props.warnings.slice(start, start + PAGE_SIZE);
+  });
+</script>
+
+<style lang="less" scoped>
+  .overview-view {
+    position: absolute;
+    inset: 0;
+  }
+
+  :deep(.overview-module .box1-center),
+  :deep(.install-module .box1-center) {
+    height: calc(100% - 35px) !important;
+  }
+
+  .overview-panel {
+    display: flex;
+    flex-direction: column;
+    gap: 34px;
+    height: 100%;
+    padding: 35px 9px 0 18px;
+    box-sizing: border-box;
+  }
+
+  .goaf-item {
+    position: relative;
+    display: flex;
+    align-items: center;
+    flex: none;
+    height: 46px;
+    padding: 0 14px 0 56px;
+    box-sizing: border-box;
+    border: 1px solid rgba(61, 246, 255, 0.5);
+    border-radius: 5px;
+    background: linear-gradient(90deg, #07304f 0%, #0a3b5f 42%, #0e4e77 100%);
+    box-shadow: inset 0 0 16px rgba(61, 246, 255, 0.08);
+  }
+
+  .goaf-icon {
+    position: absolute;
+    left: 4px;
+    top: 50%;
+    width: 42px;
+    height: 42px;
+    margin-top: -21px;
+    background: url('@/assets/images/vent/home/selfIgnition/list-icon-bg.png') no-repeat center / 100% 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    img {
+      width: 21px;
+      height: 14px;
+    }
+  }
+
+  .goaf-label {
+    flex: 1;
+    color: #fff;
+    font-size: 16px;
+    font-weight: 500;
+    letter-spacing: 0.5px;
+  }
+
+  .goaf-value {
+    color: #fff;
+    font-family: electronicFont, sans-serif;
+    font-size: 26px;
+    line-height: 1;
+    text-shadow:
+      0 0 10px rgba(255, 255, 255, 0.6),
+      0 0 20px rgba(61, 246, 255, 0.6);
+  }
+
+  .install-panel {
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    padding: 24px 9px 0 18px;
+    box-sizing: border-box;
+
+    &::before {
+      content: '';
+      position: absolute;
+      top: 24px;
+      bottom: 0;
+      left: 19px;
+      width: 1px;
+      background: linear-gradient(180deg, rgba(61, 246, 255, 0), rgba(61, 246, 255, 0.7), rgba(61, 246, 255, 0));
+    }
+  }
+
+  .install-item {
+    position: relative;
+    display: flex;
+    align-items: center;
+    flex: 1;
+    min-height: 0;
+    gap: 8px;
+    padding: 0 4px 0 26px;
+    box-sizing: border-box;
+    border-bottom: 1px solid rgba(61, 246, 255, 0.15);
+    background: transparent;
+  }
+
+  .install-square {
+    position: absolute;
+    left: 0;
+    top: 50%;
+    width: 10px;
+    height: 10px;
+    margin-top: -5px;
+    background: linear-gradient(135deg, #b3fff5, #17b7ff);
+    box-shadow: 0 0 9px rgba(61, 246, 255, 0.9);
+  }
+
+  .install-name {
+    width: 96px;
+    flex: none;
+    color: #fff;
+    font-size: 15px;
+  }
+
+  .install-metric {
+    flex: 1;
+    color: #d9f7ff;
+    font-size: 14px;
+    white-space: nowrap;
+
+    em {
+      color: #3df6ff;
+      font-style: normal;
+      font-size: 16px;
+      text-shadow: 0 0 8px rgba(61, 246, 255, 0.55);
+    }
+  }
+
+  .overall-panel {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    height: 100%;
+    padding: 0 14px;
+    box-sizing: border-box;
+  }
+
+  .overall-level {
+    font-size: 40px;
+    font-weight: 500;
+    line-height: 1.15;
+    letter-spacing: 4px;
+    white-space: nowrap;
+    text-shadow:
+      0 0 16px currentColor,
+      0 0 30px rgba(255, 157, 28, 0.35);
+  }
+
+  .overall-bar {
+    position: relative;
+    width: 278px;
+    height: 38px;
+    margin-top: 33px;
+    clip-path: polygon(2% 0, 98% 0, 89% 100%, 11% 100%);
+    filter: drop-shadow(0 6px 12px rgba(61, 246, 255, 0.3));
+
+    img {
+      width: 100%;
+      height: 100%;
+      object-fit: fill;
+      opacity: 0.78;
+    }
+
+    &::after {
+      content: '';
+      position: absolute;
+      top: 0;
+      left: 8%;
+      right: 8%;
+      height: 2px;
+      background: linear-gradient(90deg, transparent, #ff9d1c, #ffd15c, #ff9d1c, transparent);
+      box-shadow: 0 0 10px rgba(255, 157, 28, 0.65);
+    }
+  }
+
+  .overall-label {
+    position: relative;
+    margin-top: 27px;
+    color: #fff;
+    font-size: 17px;
+    letter-spacing: 1px;
+
+    &::before {
+      content: '';
+      position: absolute;
+      left: 50%;
+      top: -12px;
+      width: 260px;
+      height: 1px;
+      margin-left: -130px;
+      background: linear-gradient(90deg, transparent, rgba(61, 246, 255, 0.85), transparent);
+      box-shadow: 0 0 8px rgba(61, 246, 255, 0.7);
+    }
+  }
+
+  .overall-total {
+    margin-top: 20px;
+    font-family: electronicFont, sans-serif;
+    font-size: 54px;
+    line-height: 1;
+    text-shadow: 0 0 14px currentColor;
+  }
+
+  .warning-table-panel {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    padding: 4px 22px 8px;
+    box-sizing: border-box;
+  }
+
+  .warning-table {
+    flex: 1;
+    min-height: 0;
+    border: 1px solid rgba(61, 246, 255, 0.55);
+    background: rgba(6, 30, 52, 0.2);
+  }
+
+  .table-head,
+  .table-row {
+    display: flex;
+    align-items: center;
+    text-align: center;
+  }
+
+  .table-head {
+    height: 48px;
+    color: #3df6ff;
+    font-size: 15px;
+    font-weight: 500;
+    letter-spacing: 1px;
+    background: linear-gradient(180deg, #0d3b58 0%, #0a2c48 100%);
+  }
+
+  .table-row {
+    height: 54px;
+    color: #fff;
+    font-size: 15px;
+    background: #0b3355;
+    border-top: 1px solid rgba(61, 246, 255, 0.14);
+
+    &:nth-child(even) {
+      background: #0d4167;
+    }
+  }
+
+  .col-index {
+    width: 17%;
+  }
+
+  .col-name {
+    width: 24%;
+  }
+
+  .col-seam {
+    width: 16%;
+  }
+
+  .col-level {
+    width: 21%;
+  }
+
+  .col-status {
+    width: 22%;
+  }
+
+  /* 表格面板较宽,使用短边框图拉伸,保证顶部/底部装饰点居中 */
+  :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;
+  }
+
+  .table-pager {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 8px;
+    height: 24px;
+    flex: none;
+
+    i {
+      width: 6px;
+      height: 6px;
+      border-radius: 1px;
+      background: rgba(61, 246, 255, 0.35);
+      cursor: pointer;
+      transition: all 0.2s;
+
+      &.active {
+        width: 16px;
+        background: #3df6ff;
+        box-shadow: 0 0 8px rgba(61, 246, 255, 0.85);
+      }
+    }
+  }
+</style>

+ 200 - 0
src/views/vent/home/selfIgnition/components/PanelSelect.vue

@@ -0,0 +1,200 @@
+<template>
+  <div
+    ref="rootRef"
+    class="ignition-select"
+    :class="{ open, 'drop-up': dropUp }"
+    role="combobox"
+    tabindex="0"
+    :aria-expanded="open"
+    @click.stop="toggle"
+    @keydown.enter.prevent="toggle"
+    @keydown.space.prevent="toggle"
+    @keydown.esc="open = false"
+  >
+    <span class="ignition-select-value">{{ selectedLabel }}</span>
+    <i class="ignition-select-arrow"></i>
+    <Transition name="ignition-drop">
+      <div v-if="open" class="ignition-select-dropdown">
+        <div
+          v-for="option in options"
+          :key="option.value"
+          class="ignition-select-option"
+          role="option"
+          :aria-selected="option.value === modelValue"
+          :class="{ active: option.value === modelValue }"
+          @click.stop="choose(option)"
+        >
+          {{ option.label }}
+        </div>
+      </div>
+    </Transition>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
+  import type { ManualOption } from '../data';
+
+  const props = defineProps<{
+    modelValue: string;
+    options: ManualOption[];
+  }>();
+
+  const emit = defineEmits<{
+    (event: 'update:modelValue', value: string): void;
+  }>();
+
+  const rootRef = ref<HTMLElement>();
+  const open = ref(false);
+  const dropUp = ref(false);
+
+  const selectedLabel = computed(() => {
+    return props.options.find((option) => option.value === props.modelValue)?.label ?? '';
+  });
+
+  function toggle() {
+    open.value = !open.value;
+    if (open.value) {
+      nextTick(() => {
+        const rect = rootRef.value?.getBoundingClientRect();
+        if (rect) {
+          dropUp.value = window.innerHeight - rect.bottom < 220;
+        }
+      });
+    }
+  }
+
+  function choose(option: ManualOption) {
+    emit('update:modelValue', option.value);
+    open.value = false;
+  }
+
+  function handleDocumentClick(event: MouseEvent) {
+    if (!open.value) return;
+    if (rootRef.value && !rootRef.value.contains(event.target as Node)) {
+      open.value = false;
+    }
+  }
+
+  onMounted(() => {
+    document.addEventListener('click', handleDocumentClick);
+  });
+
+  onUnmounted(() => {
+    document.removeEventListener('click', handleDocumentClick);
+  });
+</script>
+
+<style lang="less" scoped>
+  .ignition-select {
+    position: relative;
+    display: flex;
+    align-items: center;
+    height: 34px;
+    padding: 0 28px 0 10px;
+    box-sizing: border-box;
+    border: 1px solid rgba(61, 246, 255, 0.42);
+    border-radius: 4px;
+    background: url('@/assets/images/vent/home/selfIgnition/select-bg.png') no-repeat center / 100% 100%;
+    color: #fff;
+    font-size: 14px;
+    cursor: pointer;
+    user-select: none;
+    transition:
+      border-color 0.2s,
+      box-shadow 0.2s;
+
+    &:hover,
+    &.open {
+      border-color: rgba(61, 246, 255, 0.9);
+      box-shadow: 0 0 10px rgba(61, 246, 255, 0.25);
+    }
+
+    &.open {
+      z-index: 30;
+    }
+  }
+
+  .ignition-select-value {
+    width: 100%;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  .ignition-select-arrow {
+    position: absolute;
+    right: 11px;
+    top: 50%;
+    margin-top: -2px;
+    width: 0;
+    height: 0;
+    border-left: 4px solid transparent;
+    border-right: 4px solid transparent;
+    border-top: 5px solid #3df6ff;
+    transition: transform 0.2s;
+  }
+
+  .open .ignition-select-arrow {
+    transform: rotate(180deg);
+  }
+
+  .ignition-select-dropdown {
+    position: absolute;
+    top: calc(100% + 4px);
+    left: 0;
+    bottom: auto;
+    z-index: 50;
+    width: 100%;
+    max-height: 196px;
+    padding: 4px;
+    box-sizing: border-box;
+    overflow-y: auto;
+    border: 1px solid rgba(61, 246, 255, 0.55);
+    border-radius: 4px;
+    background: linear-gradient(180deg, #0b3355 0%, #071d33 100%);
+    box-shadow:
+      0 8px 24px rgba(0, 0, 0, 0.55),
+      inset 0 0 16px rgba(17, 110, 165, 0.28);
+  }
+
+  .drop-up .ignition-select-dropdown {
+    top: auto;
+    bottom: calc(100% + 4px);
+  }
+
+  .ignition-select-option {
+    height: 30px;
+    padding: 0 8px;
+    line-height: 30px;
+    color: #d9f7ff;
+    font-size: 13px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    cursor: pointer;
+
+    &:hover {
+      color: #fff;
+      background: rgba(61, 246, 255, 0.16);
+    }
+
+    &.active {
+      color: #3df6ff;
+      background: rgba(61, 246, 255, 0.1);
+    }
+  }
+
+  .ignition-drop-enter-active,
+  .ignition-drop-leave-active {
+    transition:
+      opacity 0.16s ease,
+      transform 0.16s ease;
+  }
+
+  .ignition-drop-enter-from,
+  .ignition-drop-leave-to {
+    opacity: 0;
+    transform: translateY(-4px);
+  }
+</style>

+ 457 - 0
src/views/vent/home/selfIgnition/data.ts

@@ -0,0 +1,457 @@
+/**
+ * 张家峁煤自燃监测预警页面数据
+ *
+ * 当前阶段后端接口尚未提供,页面先使用本地模拟数据,
+ * 后续接入接口时只需要替换本文件中的模拟常量即可。
+ */
+
+export interface GoafOverviewItem {
+  label: string;
+  value: number;
+}
+
+export interface DeviceInstallItem {
+  name: string;
+  installed: number;
+  running: number;
+}
+
+export interface OverallWarning {
+  level: number;
+  levelText: string;
+  levelName: string;
+  total: number;
+  color: string;
+}
+
+export interface WarningRow {
+  id: number;
+  name: string;
+  seam: string;
+  level: string;
+  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 },
+  { 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],
+    },
+  };
+}

+ 241 - 0
src/views/vent/home/selfIgnition/index.vue

@@ -0,0 +1,241 @@
+<!-- eslint-disable vue/multi-word-component-names -->
+<template>
+  <div ref="rootRef" class="ignition-home">
+    <div class="ignition-stage" :style="stageStyle">
+      <!-- 顶部标题 -->
+      <div v-if="!isEmbed" class="ignition-header">
+        <div class="ignition-title">{{ pageTitle }}</div>
+      </div>
+
+      <!-- 顶部切换 -->
+      <div v-if="!isEmbed" class="ignition-tabs">
+        <div v-for="(tab, index) in tabs" :key="tab" class="ignition-tab" :class="{ active: activeTab === index }" @click="activeTab = index">
+          {{ tab }}
+        </div>
+      </div>
+
+      <!-- 系统概览 -->
+      <OverviewView v-show="activeTab === 0" :overview="data.overview" :devices="data.devices" :overall="data.overall" :warnings="data.warnings" />
+
+      <!-- 设备实时监测与分析 -->
+      <MonitorView v-show="activeTab === 1" :devices="data.devices" :alerts="data.alerts" :bundle-rows="data.bundleRows" :fiber="data.fiber" />
+
+      <!-- 人工录入与分析 -->
+      <ManualView v-show="activeTab === 2" />
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { computed, onActivated, onDeactivated, onMounted, onUnmounted, ref } from 'vue';
+  import { useRoute } from 'vue-router';
+  import OverviewView from './components/OverviewView.vue';
+  import MonitorView from './components/MonitorView.vue';
+  import ManualView from './components/ManualView.vue';
+  import { createSelfIgnitionData } from './data';
+  import { noHeadeLink } from '/@/layouts/default/layout.data';
+
+  // 与其他 configurable 首页一致:把当前路由登记到布局的“无 header”白名单。
+  // 该数组在布局 header 渲染前由本页面模块写入,不修改公共组件。
+  const PAGE_PATH = '/fire/monitor/selfIgnition';
+  if (!noHeadeLink.includes(PAGE_PATH)) {
+    noHeadeLink.push(PAGE_PATH);
+  }
+
+  const PAGE_STYLE_ID = 'self-ignition-fullscreen-style';
+
+  function mountPageStyle() {
+    if (document.getElementById(PAGE_STYLE_ID)) return;
+    const style = document.createElement('style');
+    style.id = PAGE_STYLE_ID;
+    style.textContent = `.jeecg-layout-header { display: none !important; }`;
+    document.head.appendChild(style);
+  }
+
+  function unmountPageStyle() {
+    document.getElementById(PAGE_STYLE_ID)?.remove();
+  }
+
+  const route = useRoute();
+  const rootRef = ref<HTMLElement>();
+  const scaleX = ref(1);
+  const scaleY = ref(1);
+  const activeTab = ref(0);
+  const pageTitle = '张家峁煤自燃监测预警';
+  const tabs = ['系统概览', '设备实时监测与分析', '人工录入与分析'];
+  const data = ref(createSelfIgnitionData());
+
+  let resizeObserver: ResizeObserver | undefined;
+  let dataInterval: number | undefined;
+
+  const isEmbed = computed(() => !!route.query.embed);
+
+  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;
+  }
+
+  /** 数据接口就绪后,在这里替换成接口请求 */
+  function refresh() {
+    data.value = createSelfIgnitionData();
+  }
+
+  function initDataInterval() {
+    clearInterval(dataInterval);
+    dataInterval = window.setInterval(refresh, 60000);
+  }
+
+  onMounted(() => {
+    mountPageStyle();
+    initDataInterval();
+    updateScale();
+    if (typeof ResizeObserver !== 'undefined' && rootRef.value) {
+      resizeObserver = new ResizeObserver(updateScale);
+      resizeObserver.observe(rootRef.value);
+    }
+    window.addEventListener('resize', updateScale);
+  });
+
+  onActivated(() => {
+    mountPageStyle();
+  });
+
+  onDeactivated(() => {
+    unmountPageStyle();
+  });
+
+  onUnmounted(() => {
+    unmountPageStyle();
+    clearInterval(dataInterval);
+    resizeObserver?.disconnect();
+    window.removeEventListener('resize', updateScale);
+  });
+</script>
+
+<style lang="less" scoped>
+  @import '/@/design/theme.less';
+
+  .ignition-home {
+    position: relative;
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+    color: #fff;
+    background: linear-gradient(180deg, #0a2037 0%, #09172c 46%, #062441 100%);
+  }
+
+  .ignition-stage {
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    width: 1920px;
+    height: 1080px;
+    transform-origin: center center;
+  }
+
+  .ignition-header {
+    position: absolute;
+    left: 0;
+    top: 0;
+    z-index: 3;
+    width: 100%;
+    height: 69px;
+    background: url('@/assets/images/vent/home/selfIgnition/top-bg.png') no-repeat center / 100% 100%;
+  }
+
+  .ignition-title {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 100%;
+    padding-bottom: 2px;
+    box-sizing: border-box;
+    font-family: 'douyuFont', sans-serif;
+    font-size: 30px;
+    font-weight: 500;
+    letter-spacing: 1px;
+    text-shadow:
+      0 0 12px rgba(61, 246, 255, 0.35),
+      0 2px 4px rgba(0, 0, 0, 0.65);
+  }
+
+  .ignition-tabs {
+    position: absolute;
+    left: 50%;
+    top: 64px;
+    z-index: 4;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 6px;
+    width: 579px;
+    height: 68px;
+    padding: 13px 16px 9px;
+    box-sizing: border-box;
+    transform: translateX(-50%);
+    background: url('@/assets/images/vent/home/selfIgnition/tab-bg.png') no-repeat center / 100% 100%;
+  }
+
+  .ignition-tab {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 158px;
+    height: 40px;
+    border: 1px solid rgba(61, 246, 255, 0.14);
+    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;
+    letter-spacing: 0.5px;
+    white-space: nowrap;
+    cursor: pointer;
+    transition:
+      color 0.2s,
+      border-color 0.2s,
+      box-shadow 0.2s;
+
+    &:hover {
+      color: #fff;
+      border-color: rgba(61, 246, 255, 0.4);
+    }
+
+    &.active {
+      border-color: rgba(61, 246, 255, 0.55);
+      background: linear-gradient(180deg, #2a5985 0%, #1d4773 100%);
+      color: #fff;
+      box-shadow: inset 0 0 12px rgba(61, 246, 255, 0.18);
+    }
+  }
+
+  :deep(.box1-top .title) {
+    color: #fff;
+    font-size: 16px;
+    font-weight: 600;
+    letter-spacing: 1px;
+    text-shadow: 0 0 8px rgba(61, 246, 255, 0.35);
+  }
+
+  :deep(.box1-center > .box-container) {
+    background-color: #062441 !important;
+    background-image: none;
+  }
+</style>

+ 14 - 0
src/views/vent/home/selfIgnition/utils.ts

@@ -0,0 +1,14 @@
+/** 生成 ModuleCommon 需要的 showStyle,坐标基于 1920 × 1080 设计稿 */
+export function panelShowStyle(x: number, y: number, width: number, height: number) {
+  return {
+    size: `width:${width}px;height:${height}px;`,
+    position: `left:${x}px;top:${y}px;`,
+  };
+}
+
+/** 页面模块不需要配置下拉框,给 ModuleCommon 一个最小可用的 moduleData */
+export const EMPTY_MODULE_DATA = {
+  header: {
+    show: false,
+  },
+};