Browse Source

工程安全

BAIPCHOME\15818 2 weeks ago
parent
commit
9fcb1b895d

BIN
src/assets/images/Heilin/image.png


+ 200 - 0
src/components/DeviceArchivePanel.vue

@@ -0,0 +1,200 @@
+<template>
+  <div class="data-card">
+    <div class="card-header">
+      <h3 class="card-title">安装 / 运维档案</h3>
+    </div>
+    <div class="card-body chart-body">
+      <div class="archive-section">
+        <div class="archive-subtitle">
+          <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="#62f6fb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
+          安装信息
+        </div>
+        <div class="archive-grid">
+          <div class="archive-item">
+            <span class="archive-item-label">安装位置坐标</span>
+            <span class="archive-item-value">X: {{ installInfo.coordinates.x }} / Y: {{ installInfo.coordinates.y }} / Z: {{ installInfo.coordinates.z }}</span>
+          </div>
+          <div class="archive-item">
+            <span class="archive-item-label">安装人员</span>
+            <span class="archive-item-value">{{ installInfo.installer }}</span>
+          </div>
+          <div class="archive-item">
+            <span class="archive-item-label">验收日期</span>
+            <span class="archive-item-value">{{ installInfo.acceptanceDate }}</span>
+          </div>
+          <div class="archive-item">
+            <span class="archive-item-label">安装环境</span>
+            <span class="archive-item-value">坝顶高程 {{ installInfo.elevation }} / 埋深 {{ installInfo.burialDepth }} / {{ installInfo.protectionLevel }}</span>
+          </div>
+          <div class="archive-item">
+            <span class="archive-item-label">设备型号</span>
+            <span class="archive-item-value">{{ installInfo.model }}</span>
+          </div>
+          <div class="archive-item">
+            <span class="archive-item-label">生产厂家</span>
+            <span class="archive-item-value">{{ installInfo.manufacturer }}</span>
+          </div>
+        </div>
+      </div>
+      <div class="archive-divider"></div>
+      <div class="archive-section">
+        <div class="archive-subtitle">
+          <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="#62f6fb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
+          运维记录
+        </div>
+        <div class="maintenance-list">
+          <div class="maintenance-item" v-for="(record, idx) in maintenanceRecords" :key="idx">
+            <div class="maintenance-left">
+              <span class="maintenance-date">{{ record.date }}</span>
+              <span class="maintenance-type-tag" :class="'type-' + record.typeClass">{{ record.type }}</span>
+            </div>
+            <div class="maintenance-desc">{{ record.description }}</div>
+            <div class="maintenance-person">{{ record.person }}</div>
+          </div>
+          <div class="maintenance-empty" v-if="maintenanceRecords.length === 0">暂无运维记录</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "DeviceArchivePanel",
+  props: {
+    installInfo: {
+      type: Object,
+      default: () => ({
+        coordinates: { x: '123.45', y: '67.89', z: '102.3' },
+        installer: '王工',
+        acceptanceDate: '2023-06-20',
+        elevation: '185.5m',
+        burialDepth: '2.5m',
+        protectionLevel: 'IP68',
+        model: 'BGK-4450',
+        manufacturer: '基康仪器',
+      }),
+    },
+    maintenanceRecords: {
+      type: Array,
+      default: () => [
+        { date: '2026-05-20', type: '定期校准', typeClass: 'calibrate', description: '设备零点校准,误差<0.1mm', person: '张工' },
+        { date: '2026-04-10', type: '故障处理', typeClass: 'fault', description: '更换通讯模块,恢复在线', person: '李工' },
+        { date: '2026-03-01', type: '巡检记录', typeClass: 'inspect', description: '外观检查正常,数据稳定', person: '王工' },
+      ],
+    },
+  },
+};
+</script>
+
+<style scoped>
+.data-card { width: 100%; background: rgba(0,20,40,0.7); border-radius: 4px; overflow: hidden; box-shadow: 0 0 10px rgba(0,212,255,0.2); }
+.card-header { height: 42px; background-image: url("/src/assets/images/数据小标题.png"); background-size: 100% 100%; background-position: center; background-repeat: no-repeat; display: flex; align-items: flex-start; justify-content: space-between; padding: 4px 16px 0; }
+.card-title { font-size: var(--fs-card-title); font-weight: bold; color: #e0fcff; margin: 0; text-shadow: 0 0 5px rgba(0,212,255,0.5); padding-left: 20px; }
+.card-body { padding: 8px; min-height: auto; font-size: 13px; line-height: 1.5; margin-top: -6px; }
+.card-body.chart-body { padding: 2px 8px 4px; }
+.archive-section { margin-bottom: 2px; }
+.archive-subtitle {
+  color: #62f6fb;
+  font-size: 11px;
+  font-weight: bold;
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  padding: 3px 0;
+}
+.archive-grid {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+}
+.archive-item {
+  display: flex;
+  align-items: flex-start;
+  padding: 2px 0;
+  gap: 6px;
+}
+.archive-item-label {
+  color: #7bbef6;
+  font-size: 10px;
+  flex-shrink: 0;
+  min-width: 64px;
+}
+.archive-item-value {
+  color: #e0fcff;
+  font-size: 10px;
+  line-height: 1.4;
+}
+.archive-divider {
+  height: 1px;
+  background: rgba(0,212,255,0.12);
+  margin: 4px 0;
+}
+.maintenance-list {
+  display: flex;
+  flex-direction: column;
+  gap: 3px;
+  max-height: 120px;
+  overflow-y: auto;
+}
+.maintenance-list::-webkit-scrollbar { width: 3px; }
+.maintenance-list::-webkit-scrollbar-track { background: rgba(0,20,40,0.5); }
+.maintenance-list::-webkit-scrollbar-thumb { background: rgba(0,212,255,0.3); border-radius: 2px; }
+.maintenance-item {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  padding: 3px 6px;
+  background: rgba(0,20,40,0.3);
+  border-radius: 3px;
+  border: 1px solid rgba(0,212,255,0.06);
+}
+.maintenance-left {
+  display: flex;
+  flex-direction: column;
+  gap: 1px;
+  flex-shrink: 0;
+  min-width: 56px;
+}
+.maintenance-date {
+  color: #7bbef6;
+  font-size: 9px;
+}
+.maintenance-type-tag {
+  font-size: 8px;
+  font-weight: bold;
+  padding: 0 4px;
+  border-radius: 2px;
+  display: inline-block;
+  width: fit-content;
+}
+.type-calibrate {
+  color: #62f6fb;
+  background: rgba(98,246,251,0.12);
+}
+.type-fault {
+  color: #f97316;
+  background: rgba(249,115,22,0.12);
+}
+.type-inspect {
+  color: #22c55e;
+  background: rgba(34,197,94,0.12);
+}
+.maintenance-desc {
+  flex: 1;
+  color: #e0fcff;
+  font-size: 10px;
+  line-height: 1.3;
+}
+.maintenance-person {
+  color: #7bbef6;
+  font-size: 10px;
+  flex-shrink: 0;
+}
+.maintenance-empty {
+  color: #7bbef6;
+  font-size: 11px;
+  text-align: center;
+  padding: 8px 0;
+}
+</style>

+ 224 - 0
src/components/DeviceHealthPanel.vue

@@ -0,0 +1,224 @@
+<template>
+  <div class="data-card">
+    <div class="card-header">
+      <h3 class="card-title">设备健康与预警</h3>
+    </div>
+    <div class="card-body chart-body">
+      <div class="health-score-section">
+        <div class="health-ring-area">
+          <svg class="health-ring" width="72" height="72" viewBox="0 0 72 72">
+            <circle class="ring-bg" cx="36" cy="36" r="30" />
+            <circle class="ring-fill" cx="36" cy="36" r="30"
+              :stroke-dasharray="2 * Math.PI * 30"
+              :stroke-dashoffset="2 * Math.PI * 30 * (1 - healthScore / 100)"
+              stroke="#62f6fb" />
+            <text x="36" y="36" text-anchor="middle" dominant-baseline="central" fill="#ffffff" font-size="16" font-weight="bold">{{ healthScore }}</text>
+          </svg>
+        </div>
+        <div class="health-dims">
+          <div class="dim-item" v-for="dim in healthDimensions" :key="dim.label">
+            <div class="dim-header">
+              <span class="dim-label">{{ dim.label }}</span>
+              <span class="dim-value">{{ dim.value }}%</span>
+            </div>
+            <div class="dim-bar-bg">
+              <div class="dim-bar-fill" :style="{ width: dim.value + '%', background: dim.color }"></div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="archive-subtitle" style="margin-top:6px;">
+        <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="#62f6fb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
+        历史预警事件
+      </div>
+      <div class="warning-list">
+        <div class="warning-item" v-for="(evt, idx) in warningEvents" :key="idx" @click="$emit('locate-event', evt)">
+          <div class="warning-left">
+            <span class="warning-time">{{ evt.time }}</span>
+            <span class="warning-type">{{ evt.type }}</span>
+          </div>
+          <div class="warning-right">
+            <span class="warning-level" :class="'level-' + evt.levelClass">{{ evt.level }}</span>
+            <span class="warning-status" :class="'status-' + evt.statusClass">{{ evt.status }}</span>
+          </div>
+        </div>
+        <div class="warning-empty" v-if="warningEvents.length === 0">暂无预警事件</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "DeviceHealthPanel",
+  props: {
+    healthScore: { type: Number, default: 92 },
+    healthDimensions: {
+      type: Array,
+      default: () => [
+        { label: '数据完整性', value: 95, color: '#62f6fb' },
+        { label: '在线率', value: 98, color: '#22c55e' },
+        { label: '校准有效期', value: 85, color: '#fbbf24' },
+        { label: '异常次数', value: 90, color: '#a78bfa' },
+      ],
+    },
+    warningEvents: {
+      type: Array,
+      default: () => [
+        { time: '2026-05-15 14:30', type: '应力超限', level: '黄色', levelClass: 'yellow', status: '已处置', statusClass: 'resolved' },
+        { time: '2026-05-08 09:15', type: '通讯中断', level: '橙色', levelClass: 'orange', status: '已恢复', statusClass: 'recovered' },
+      ],
+    },
+  },
+  emits: ['locate-event'],
+};
+</script>
+
+<style scoped>
+.data-card { width: 100%; background: rgba(0,20,40,0.7); border-radius: 4px; overflow: hidden; box-shadow: 0 0 10px rgba(0,212,255,0.2); }
+.card-header { height: 42px; background-image: url("/src/assets/images/数据小标题.png"); background-size: 100% 100%; background-position: center; background-repeat: no-repeat; display: flex; align-items: flex-start; justify-content: space-between; padding: 4px 16px 0; }
+.card-title { font-size: var(--fs-card-title); font-weight: bold; color: #e0fcff; margin: 0; text-shadow: 0 0 5px rgba(0,212,255,0.5); padding-left: 20px; }
+.card-body { padding: 8px; min-height: auto; font-size: 13px; line-height: 1.5; margin-top: -6px; }
+.card-body.chart-body { padding: 2px 8px 4px; }
+.health-score-section {
+  display: flex;
+  gap: 10px;
+  align-items: center;
+}
+.health-ring-area {
+  flex-shrink: 0;
+}
+.health-ring { display: block; }
+.ring-bg { fill: none; stroke: rgba(255,255,255,0.08); stroke-width: 5; }
+.ring-fill { fill: none; stroke-width: 5; stroke-linecap: round; transform: rotate(-90deg); transform-origin: 36px 36px; }
+.health-dims {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+.dim-item {
+  display: flex;
+  flex-direction: column;
+  gap: 1px;
+}
+.dim-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.dim-label {
+  color: #7bbef6;
+  font-size: 9px;
+}
+.dim-value {
+  color: #e0fcff;
+  font-size: 10px;
+  font-weight: bold;
+}
+.dim-bar-bg {
+  height: 4px;
+  background: rgba(255,255,255,0.08);
+  border-radius: 2px;
+  overflow: hidden;
+}
+.dim-bar-fill {
+  height: 100%;
+  border-radius: 2px;
+  transition: width 0.5s;
+}
+.archive-subtitle {
+  color: #62f6fb;
+  font-size: 11px;
+  font-weight: bold;
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  padding: 3px 0;
+}
+.warning-list {
+  display: flex;
+  flex-direction: column;
+  gap: 3px;
+  max-height: 80px;
+  overflow-y: auto;
+}
+.warning-list::-webkit-scrollbar { width: 3px; }
+.warning-list::-webkit-scrollbar-track { background: rgba(0,20,40,0.5); }
+.warning-list::-webkit-scrollbar-thumb { background: rgba(0,212,255,0.3); border-radius: 2px; }
+.warning-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 3px 6px;
+  background: rgba(0,20,40,0.3);
+  border-radius: 3px;
+  border: 1px solid rgba(0,212,255,0.06);
+  cursor: pointer;
+  transition: background 0.15s;
+}
+.warning-item:hover {
+  background: rgba(0,212,255,0.06);
+}
+.warning-left {
+  display: flex;
+  flex-direction: column;
+  gap: 1px;
+}
+.warning-time {
+  color: #7bbef6;
+  font-size: 9px;
+}
+.warning-type {
+  color: #e0fcff;
+  font-size: 10px;
+  font-weight: bold;
+}
+.warning-right {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  flex-shrink: 0;
+}
+.warning-level {
+  font-size: 9px;
+  font-weight: bold;
+  padding: 1px 6px;
+  border-radius: 2px;
+}
+.level-yellow {
+  color: #ffd93d;
+  background: rgba(255,217,61,0.15);
+}
+.level-orange {
+  color: #f97316;
+  background: rgba(249,115,22,0.15);
+}
+.level-red {
+  color: #ef4444;
+  background: rgba(239,68,68,0.15);
+}
+.warning-status {
+  font-size: 9px;
+  padding: 1px 6px;
+  border-radius: 2px;
+}
+.status-resolved {
+  color: #22c55e;
+  background: rgba(34,197,94,0.12);
+}
+.status-recovered {
+  color: #62f6fb;
+  background: rgba(98,246,251,0.12);
+}
+.status-pending {
+  color: #ffd93d;
+  background: rgba(255,217,61,0.12);
+}
+.warning-empty {
+  color: #7bbef6;
+  font-size: 11px;
+  text-align: center;
+  padding: 8px 0;
+}
+</style>

+ 159 - 0
src/components/DeviceInfoPanel.vue

@@ -0,0 +1,159 @@
+<template>
+  <div class="data-card device-info-card">
+    <div class="card-header">
+      <h3 class="card-title">设备基础信息</h3>
+    </div>
+    <div class="card-body">
+      <div class="device-info-header">
+        <h4 class="device-name">{{ device.name }}</h4>
+        <span class="device-status-tag" :class="statusClass">{{ statusLabel }}</span>
+      </div>
+      <div class="device-info-grid">
+        <div class="info-row">
+          <span class="info-label">设备类型</span>
+          <span class="info-tag">{{ device.type || '振弦式应变计/位移计' }}</span>
+        </div>
+        <div class="info-row">
+          <span class="info-label">安装日期</span>
+          <span class="info-value">{{ device.installDate || '2023-06-15' }}</span>
+        </div>
+        <div class="info-row">
+          <span class="info-label">出厂编号</span>
+          <span class="info-value">{{ device.serialNumber || 'BK20230615001' }}</span>
+        </div>
+        <div class="info-row">
+          <span class="info-label">监测项</span>
+          <span class="info-value">{{ device.monitorType || '水平位移' }}</span>
+        </div>
+      </div>
+      <div class="device-image-row">
+        <div class="device-image-wrapper">
+          <img src="/src/assets/images/Heilin/image.png" class="device-image" alt="设备图片" />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "DeviceInfoPanel",
+  props: {
+    device: { type: Object, default: () => ({ name: '坝顶水平位移仪 - R201YL1' }) },
+  },
+  computed: {
+    statusLabel() {
+      const map = { normal: '正常', warning: '预警', fault: '故障' };
+      return map[this.device.status] || '正常';
+    },
+    statusClass() {
+      return 'status-' + (this.device.status || 'normal');
+    },
+  },
+};
+</script>
+
+<style scoped>
+.device-info-card { 
+  position: relative;
+  overflow: visible;
+}
+.card-header { height: 42px; background-image: url("/src/assets/images/数据小标题.png"); background-size: 100% 100%; background-position: center; background-repeat: no-repeat; display: flex; align-items: flex-start; justify-content: space-between; padding: 4px 16px 0; }
+.card-title { font-size: var(--fs-card-title); font-weight: bold; color: #e0fcff; margin: 0; text-shadow: 0 0 5px rgba(0,212,255,0.5); padding-left: 20px; }
+.card-body { 
+  padding: 10px 12px 8px;
+  background: rgba(0,20,40,0.7);
+  border-radius: 0 0 4px 4px;
+}
+.device-info-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 6px;
+}
+.device-name {
+  color: #e0fcff;
+  font-size: 15px;
+  font-weight: bold;
+  margin: 0;
+  text-shadow: 0 0 5px rgba(0,212,255,0.4);
+  flex: 1;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.device-status-tag {
+  font-size: 10px;
+  font-weight: bold;
+  padding: 2px 10px;
+  border-radius: 10px;
+  flex-shrink: 0;
+  margin-left: 8px;
+}
+.status-normal {
+  color: #22c55e;
+  background: rgba(34,197,94,0.15);
+  border: 1px solid rgba(34,197,94,0.3);
+}
+.status-warning {
+  color: #ffd93d;
+  background: rgba(255,217,61,0.15);
+  border: 1px solid rgba(255,217,61,0.3);
+}
+.status-fault {
+  color: #ef4444;
+  background: rgba(239,68,68,0.15);
+  border: 1px solid rgba(239,68,68,0.3);
+}
+.device-image-row {
+  margin-top: 8px;
+  display: flex;
+  justify-content: center;
+}
+.device-image-wrapper {
+  width: 350px;
+  height: 150px;
+  border-radius: 4px;
+  overflow: hidden;
+  border: 1px solid rgba(0,212,255,0.2);
+  background: rgba(0,20,40,0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.device-image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+.device-info-grid {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+.info-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 3px 0;
+}
+.info-label {
+  color: #7bbef6;
+  font-size: 11px;
+  flex-shrink: 0;
+}
+.info-value {
+  color: #e0fcff;
+  font-size: 11px;
+  font-weight: bold;
+}
+.info-tag {
+  color: #62f6fb;
+  font-size: 10px;
+  font-weight: bold;
+  padding: 1px 8px;
+  background: rgba(98,246,251,0.1);
+  border-radius: 2px;
+  border: 1px solid rgba(98,246,251,0.2);
+}
+</style>

+ 302 - 0
src/components/DeviceMonitorPanel.vue

@@ -0,0 +1,302 @@
+<template>
+  <div class="data-card">
+    <div class="card-header">
+      <h3 class="card-title">实时监测数据</h3>
+      <div class="header-actions">
+        <span class="update-time">单位:{{ unit }}</span>
+      </div>
+    </div>
+    <div class="card-body chart-body">
+      <div class="kpi-grid">
+        <div class="kpi-card">
+          <span class="kpi-label">当前监测值</span>
+          <span class="kpi-value">{{ metrics.current }}</span>
+          <span class="kpi-unit">{{ unit }}</span>
+        </div>
+        <div class="kpi-card">
+          <span class="kpi-label">最大值</span>
+          <span class="kpi-value kpi-highlight">{{ metrics.max }}</span>
+          <span class="kpi-unit">{{ unit }}</span>
+        </div>
+        <div class="kpi-card">
+          <span class="kpi-label">平均值</span>
+          <span class="kpi-value">{{ metrics.avg }}</span>
+          <span class="kpi-unit">{{ unit }}</span>
+        </div>
+        <div class="kpi-card">
+          <span class="kpi-label">状态</span>
+          <span class="kpi-status" :class="metrics.status === '正常' ? 'kpi-ok' : 'kpi-warn'">{{ metrics.status }}</span>
+        </div>
+      </div>
+      <div class="monitor-chart-box">
+        <div class="monitor-chart-label">监测趋势</div>
+        <div class="trend-chart-wrapper"><div :id="chartId" style="width:100%;height:150px;"></div></div>
+        <div class="chart-toolbar">
+          <div class="time-range-group">
+            <span class="range-btn" :class="{ active: timeRange === '7d' }" @click="switchRange('7d')">7天</span>
+            <span class="range-btn" :class="{ active: timeRange === '30d' }" @click="switchRange('30d')">30天</span>
+            <span class="range-btn" :class="{ active: timeRange === '90d' }" @click="switchRange('90d')">90天</span>
+          </div>
+          <div class="chart-legend">
+            <span class="legend-item">
+              <span class="legend-color" style="background:#62f6fb;"></span>监测值
+            </span>
+            <span class="legend-item">
+              <span class="legend-color" style="background:#ef4444;"></span>预警阈值
+            </span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import * as echarts from "echarts";
+
+export default {
+  name: "DeviceMonitorPanel",
+  props: {
+    device: { type: Object, default: null },
+    unit: { type: String, default: 'mm' },
+    metrics: {
+      type: Object,
+      default: () => ({ current: '23.5', max: '28.6', avg: '23.8', status: '正常' }),
+    },
+    startDate: { type: String, default: '' },
+    endDate: { type: String, default: '' },
+    threshold: { type: Number, default: 28 },
+  },
+  data() {
+    return {
+      timeRange: '30d',
+      chart: null,
+    };
+  },
+  computed: {
+    chartId() {
+      return 'deviceTrendChart_' + (this.device ? this.device.name.replace(/\s/g, '') : 'default');
+    },
+  },
+  watch: {
+    device: {
+      handler() {
+        this.$nextTick(() => this.renderChart());
+      },
+      deep: true,
+    },
+    timeRange() {
+      this.$nextTick(() => this.renderChart());
+    },
+    startDate() {
+      if (this.startDate) this.$nextTick(() => this.renderChart());
+    },
+    endDate() {
+      if (this.endDate) this.$nextTick(() => this.renderChart());
+    },
+  },
+  mounted() {
+    window.addEventListener("resize", this.handleResize);
+    this.$nextTick(() => this.renderChart());
+  },
+  beforeUnmount() {
+    window.removeEventListener("resize", this.handleResize);
+    if (this.chart) this.chart.dispose();
+  },
+  methods: {
+    handleResize() {
+      if (this.chart) this.chart.resize();
+    },
+    switchRange(range) {
+      this.timeRange = range;
+      this.$emit('range-change', range);
+    },
+    renderChart() {
+      if (!echarts) return;
+      if (this.chart) this.chart.dispose();
+      const el = document.getElementById(this.chartId);
+      if (!el) return;
+      this.chart = echarts.init(el, null, { renderer: "canvas", devicePixelRatio: window.devicePixelRatio || 1 });
+
+      const points = this.timeRange === '7d' ? 168 : this.timeRange === '30d' ? 720 : 2160;
+      const displayPoints = Math.min(points, 168);
+      const seed = this.device ? this.device.name.charCodeAt(0) + this.device.name.length : 42;
+      const timeList = [];
+      const dataValues = [];
+      const thresholdValues = [];
+      const now = new Date();
+      for (let i = displayPoints; i >= 0; i--) {
+        const d = new Date(now.getTime() - i * 60 * 60 * 1000);
+        const pad = n => String(n).padStart(2, "0");
+        timeList.push((d.getMonth() + 1) + "/" + pad(d.getDate()) + " " + pad(d.getHours()) + ":00");
+        const hourAngle = (d.getHours() / 24) * 2 * Math.PI;
+        const dayOffset = i * 0.08;
+        const val = +(22 + 4 * Math.sin(hourAngle + dayOffset + seed) + 2 * Math.cos(dayOffset * 0.3) + (Math.random() - 0.5) * 1.2).toFixed(1);
+        dataValues.push(val);
+        thresholdValues.push(this.threshold);
+      }
+
+      const exceedData = dataValues.map((v, i) => v > this.threshold ? v : null);
+
+      const labelInterval = Math.max(1, Math.floor(displayPoints / 10));
+
+      this.chart.setOption({
+        animation: false,
+        tooltip: {
+          trigger: "axis",
+          formatter: function(params) {
+            let html = params[0].axisValue + '<br/>';
+            params.forEach(p => {
+              if (p.value !== null && p.value !== undefined) {
+                html += p.marker + ' ' + p.seriesName + ': ' + p.value + '<br/>';
+              }
+            });
+            return html;
+          },
+        },
+        grid: { left: 35, right: 10, top: 12, bottom: 5 },
+        xAxis: {
+          type: "category",
+          boundaryGap: false,
+          data: timeList,
+          axisLine: { lineStyle: { color: "#7bbef6" } },
+          axisLabel: { color: "#7bbef6", fontSize: 7, interval: labelInterval },
+          show: true,
+        },
+        yAxis: {
+          type: "value",
+          name: this.unit,
+          nameTextStyle: { color: "#7bbef6", fontSize: 9 },
+          axisLine: { lineStyle: { color: "#7bbef6" } },
+          axisLabel: { color: "#7bbef6", fontSize: 8 },
+          splitLine: { lineStyle: { color: "rgba(123,190,246,0.12)" } },
+        },
+        series: [
+          {
+            name: "监测值",
+            type: "line",
+            data: dataValues,
+            smooth: true,
+            symbol: "none",
+            lineStyle: { color: "#62f6fb", width: 1.5 },
+            itemStyle: { color: "#62f6fb" },
+            areaStyle: { color: "rgba(98,246,251,0.08)" },
+            z: 2,
+          },
+          {
+            name: "超阈值",
+            type: "line",
+            data: exceedData,
+            smooth: true,
+            symbol: "circle",
+            symbolSize: 3,
+            lineStyle: { color: "#ef4444", width: 1.5 },
+            itemStyle: { color: "#ef4444" },
+            z: 3,
+          },
+          {
+            name: "预警阈值",
+            type: "line",
+            data: thresholdValues,
+            smooth: false,
+            symbol: "none",
+            lineStyle: { color: "#ef4444", width: 1, type: "dashed" },
+            itemStyle: { color: "#ef4444" },
+            z: 1,
+          },
+        ],
+      });
+    },
+  },
+};
+</script>
+
+<style scoped>
+.data-card { width: 100%; background: rgba(0,20,40,0.7); border-radius: 4px; overflow: hidden; box-shadow: 0 0 10px rgba(0,212,255,0.2); }
+.card-header { height: 42px; background-image: url("/src/assets/images/数据小标题.png"); background-size: 100% 100%; background-position: center; background-repeat: no-repeat; display: flex; align-items: flex-start; justify-content: space-between; padding: 4px 16px 0; }
+.card-title { font-size: var(--fs-card-title); font-weight: bold; color: #e0fcff; margin: 0; text-shadow: 0 0 5px rgba(0,212,255,0.5); padding-left: 20px; }
+.header-actions { display: flex; align-items: flex-start; gap: 10px; }
+.update-time { color: #7bbef6; font-size: 11px; font-family: monospace; }
+.card-body { padding: 8px; min-height: auto; font-size: 13px; line-height: 1.5; margin-top: -6px; }
+.card-body.chart-body { padding: 2px 8px 4px; }
+.kpi-grid {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 4px;
+  margin-bottom: 4px;
+}
+.kpi-card {
+  display: flex;
+  align-items: baseline;
+  gap: 4px;
+  padding: 5px 8px;
+  background: rgba(0,20,40,0.4);
+  border-radius: 3px;
+  border: 1px solid rgba(0,212,255,0.1);
+}
+.kpi-label {
+  color: #7bbef6;
+  font-size: 10px;
+  flex-shrink: 0;
+}
+.kpi-value {
+  color: #ffffff;
+  font-size: 16px;
+  font-weight: bold;
+  text-shadow: 0 0 6px rgba(0,212,255,0.4);
+}
+.kpi-highlight {
+  color: #fbbf24;
+}
+.kpi-unit {
+  color: #7bbef6;
+  font-size: 10px;
+}
+.kpi-status {
+  font-size: 12px;
+  font-weight: bold;
+  padding: 1px 8px;
+  border-radius: 2px;
+}
+.kpi-ok {
+  color: #22c55e;
+  background: rgba(34,197,94,0.15);
+}
+.kpi-warn {
+  color: #ffd93d;
+  background: rgba(255,217,61,0.15);
+}
+.monitor-chart-box { background: rgba(0,20,40,0.4); border-radius: 4px; border: 1px solid rgba(0,212,255,0.15); padding: 6px; }
+.monitor-chart-label { color: #62f6fb; font-size: var(--fs-label); font-weight: bold; margin-bottom: 2px; }
+.trend-chart-wrapper { width: 100%; }
+.chart-toolbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-top: 2px;
+}
+.time-range-group {
+  display: flex;
+  gap: 2px;
+}
+.range-btn {
+  color: #7bbef6;
+  font-size: 9px;
+  padding: 1px 6px;
+  border-radius: 2px;
+  cursor: pointer;
+  border: 1px solid rgba(0,212,255,0.15);
+  transition: all 0.15s;
+}
+.range-btn:hover {
+  background: rgba(0,212,255,0.1);
+}
+.range-btn.active {
+  color: #62f6fb;
+  background: rgba(98,246,251,0.15);
+  border-color: rgba(98,246,251,0.3);
+}
+.chart-legend { text-align: right; color: #7bbef6; font-size: 9px; display: flex; gap: 8px; }
+.legend-item { display: flex; align-items: center; gap: 3px; }
+.legend-color { display: inline-block; width: 8px; height: 3px; border-radius: 1px; }
+</style>

+ 122 - 0
src/components/DeviceRightPanel.vue

@@ -0,0 +1,122 @@
+<template>
+  <div class="right-panel-container" v-if="device">
+    <DeviceInfoPanel :device="device" />
+    <div class="panel-gap"></div>
+    <DeviceArchivePanel
+      :install-info="installInfo"
+      :maintenance-records="maintenanceRecords"
+    />
+    <div class="panel-gap"></div>
+    <DeviceHealthPanel
+      :health-score="healthScore"
+      :health-dimensions="healthDimensions"
+      :warning-events="warningEvents"
+      @locate-event="onLocateEvent"
+    />
+  </div>
+  <div class="right-panel-empty" v-else>
+    <div class="empty-icon">
+      <svg viewBox="0 0 24 24" width="48" height="48" fill="none" stroke="#7bbef6" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" opacity="0.4">
+        <rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
+        <line x1="8" y1="21" x2="16" y2="21" />
+        <line x1="12" y1="17" x2="12" y2="21" />
+      </svg>
+    </div>
+    <div class="empty-text">请在左侧设备树中选择设备</div>
+  </div>
+</template>
+
+<script>
+import DeviceInfoPanel from "./DeviceInfoPanel.vue";
+import DeviceArchivePanel from "./DeviceArchivePanel.vue";
+import DeviceHealthPanel from "./DeviceHealthPanel.vue";
+
+export default {
+  name: "DeviceRightPanel",
+  components: { DeviceInfoPanel, DeviceArchivePanel, DeviceHealthPanel },
+  props: {
+    device: { type: Object, default: null },
+    breadcrumb: { type: String, default: '' },
+  },
+  emits: ['locate-event'],
+  data() {
+    return {
+      healthScore: 92,
+      healthDimensions: [
+        { label: '数据完整性', value: 95, color: '#62f6fb' },
+        { label: '在线率', value: 98, color: '#22c55e' },
+        { label: '校准有效期', value: 85, color: '#fbbf24' },
+        { label: '异常次数', value: 90, color: '#a78bfa' },
+      ],
+      warningEvents: [
+        { time: '2026-05-15 14:30', type: '应力超限', level: '黄色', levelClass: 'yellow', status: '已处置', statusClass: 'resolved' },
+        { time: '2026-05-08 09:15', type: '通讯中断', level: '橙色', levelClass: 'orange', status: '已恢复', statusClass: 'recovered' },
+        { time: '2026-04-20 11:00', type: '数据异常', level: '黄色', levelClass: 'yellow', status: '待处理', statusClass: 'pending' },
+      ],
+      installInfo: {
+        coordinates: { x: '123.45', y: '67.89', z: '102.3' },
+        installer: '王工',
+        acceptanceDate: '2023-06-20',
+        elevation: '185.5m',
+        burialDepth: '2.5m',
+        protectionLevel: 'IP68',
+        model: 'BGK-4450',
+        manufacturer: '基康仪器',
+      },
+      maintenanceRecords: [
+        { date: '2026-05-20', type: '定期校准', typeClass: 'calibrate', description: '设备零点校准,误差<0.1mm', person: '张工' },
+        { date: '2026-04-10', type: '故障处理', typeClass: 'fault', description: '更换通讯模块,恢复在线', person: '李工' },
+        { date: '2026-03-01', type: '巡检记录', typeClass: 'inspect', description: '外观检查正常,数据稳定', person: '王工' },
+        { date: '2026-02-15', type: '定期校准', typeClass: 'calibrate', description: '季度校准,误差在允许范围内', person: '张工' },
+        { date: '2026-01-10', type: '巡检记录', typeClass: 'inspect', description: '冬季巡检,设备运行正常', person: '赵工' },
+      ],
+    };
+  },
+  watch: {
+    device: {
+      handler(newDev) {
+        if (!newDev) return;
+        this.refreshDeviceData(newDev);
+      },
+      immediate: true,
+    },
+  },
+  methods: {
+    refreshDeviceData(dev) {
+      const seed = dev.name ? dev.name.charCodeAt(0) + dev.name.length : 42;
+      this.healthScore = Math.min(99, 75 + (seed % 20) + Math.floor(Math.random() * 5));
+    },
+    onLocateEvent(evt) {
+      this.$emit('locate-event', evt);
+    },
+  },
+};
+</script>
+
+<style scoped>
+.right-panel-container {
+  display: flex;
+  flex-direction: column;
+}
+.panel-gap {
+  height: 8px;
+  flex-shrink: 0;
+}
+.right-panel-empty {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 60px 20px;
+  background: rgba(0,20,40,0.7);
+  border-radius: 4px;
+  box-shadow: 0 0 10px rgba(0,212,255,0.2);
+}
+.empty-icon {
+  margin-bottom: 12px;
+}
+.empty-text {
+  color: #7bbef6;
+  font-size: 13px;
+}
+</style>

+ 228 - 0
src/components/TempStressCard.vue

@@ -0,0 +1,228 @@
+<template>
+  <!-- 设备温度应力趋势卡片,通过父组件 visible 控制显隐 -->
+  <div class="middle-area-content" v-if="visible">
+    <div class="data-card">
+      <div class="card-header">
+        <!-- 标题:选中设备时显示"设备名 数据",否则显示默认标题 -->
+        <h3 class="card-title">{{ device ? device.name + ' 数据' : '温度与应力监测' }}</h3>
+        <div class="header-actions">
+          <!-- 选中设备时显示设备路径面包屑 -->
+          <span class="update-time" v-if="device">{{ breadcrumb }}</span>
+          <span class="update-time" v-else>单位:温度(℃) / 应力(MPa)</span>
+          <!-- 日期范围选择器:开始 / 结束 -->
+          <span class="panel-date-label">开始</span>
+          <input class="panel-date-input" type="date" v-model="startDate" @change="renderChart" />
+          <span class="panel-date-label">结束</span>
+          <input class="panel-date-input" type="date" v-model="endDate" @change="renderChart" />
+          <!-- 关闭按钮(X),触发父组件 close 事件 -->
+          <svg class="panel-close-btn" @click="$emit('close')" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="#7bbef6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
+        </div>
+      </div>
+      <div class="card-body chart-body">
+        <div class="monitor-chart-box">
+          <div class="monitor-chart-label">温度 / 应力趋势</div>
+          <!-- ECharts 渲染容器:去掉固定宽高,用 100% 占满父容器 -->
+          <div class="temp-chart-wrapper"><div id="tempStressChart" style="width:100%;height:200px;"></div></div>
+        </div>
+        <!-- 摘要数据卡片:当前温度、最大应力、平均温度、平均应力 -->
+        <div class="water-stats-row" style="margin-top:4px;">
+          <div class="water-stat-card">
+            <div class="water-stat-label">当前温度</div>
+            <div class="water-stat-value">28.6</div>
+          </div>
+          <div class="water-stat-card">
+            <div class="water-stat-label">最大应力</div>
+            <div class="water-stat-value">2.45</div>
+          </div>
+          <div class="water-stat-card">
+            <div class="water-stat-label">平均温度</div>
+            <div class="water-stat-value">23.8</div>
+          </div>
+          <div class="water-stat-card">
+            <div class="water-stat-label">平均应力</div>
+            <div class="water-stat-value">1.82</div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import * as echarts from "echarts";
+
+export default {
+  name: "TempStressCard",
+  props: {
+    device: { type: Object, default: null },
+    breadcrumb: { type: String, default: "" },
+    visible: { type: Boolean, default: false },
+  },
+  data() {
+    return {
+      startDate: "2026-01-01",
+      endDate: "2026-12-31",
+      chart: null,
+    };
+  },
+  watch: {
+    visible(val) {
+      if (val) {
+        this.startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
+        this.endDate = new Date().toISOString().slice(0, 10);
+        this.$nextTick(() => this.renderChart());
+      } else {
+        if (this.chart) {
+          this.chart.dispose();
+          this.chart = null;
+        }
+      }
+    },
+  },
+  mounted() {
+    window.addEventListener("resize", this.handleResize);
+  },
+  beforeUnmount() {
+    window.removeEventListener("resize", this.handleResize);
+    if (this.chart) this.chart.dispose();
+  },
+  methods: {
+    handleResize() {
+      if (!this.chart) return;
+      this.chart.resize();
+    },
+    renderChart() {
+      if (!echarts || !this.visible) return;
+      if (this.chart) this.chart.dispose();
+      const el = document.getElementById("tempStressChart");
+      if (!el) return;
+      const wrapper = el.parentElement;
+      const dpr = window.devicePixelRatio || 1;
+      // 让图表容器 100% 占满父容器
+      el.style.width = "100%";
+      el.style.height = "200px";
+      this.chart = echarts.init(el, null, { 
+        renderer: "canvas", 
+        devicePixelRatio: dpr 
+      });
+
+      const start = new Date(this.startDate);
+      const end = new Date(this.endDate);
+      const hours = Math.max(1, Math.round((end - start) / (60 * 60 * 1000)) + 1);
+      const timeList = [];
+      const tempData = [];
+      const stressData = [];
+      const seed = this.device ? this.device.name.charCodeAt(0) + this.device.name.length : 42;
+      for (let i = 0; i < hours; i++) {
+        const d = new Date(start.getTime() + i * 60 * 60 * 1000);
+        const pad = n => String(n).padStart(2, "0");
+        timeList.push((d.getMonth() + 1) + "/" + pad(d.getDate()) + " " + pad(d.getHours()) + ":00");
+        const hourAngle = (d.getHours() / 24) * 2 * Math.PI;
+        const dayOffset = i * 0.15;
+        tempData.push(+(22 + 5 * Math.sin(hourAngle + dayOffset + seed) + 3 * Math.cos(dayOffset * 0.3) + (Math.random() - 0.5) * 1.5).toFixed(1));
+        stressData.push(+(1.8 + 0.4 * Math.cos(hourAngle * 0.5 + dayOffset * 0.2 + seed) + 0.3 * Math.sin(dayOffset * 0.5) + (Math.random() - 0.5) * 0.2).toFixed(3));
+      }
+      const labelInterval = Math.max(1, Math.floor(hours / 12));
+
+      this.chart.setOption({
+        animation: false,
+        tooltip: { trigger: "axis" },
+        legend: {
+          data: ["温度(℃)", "应力(MPa)"],
+          textStyle: { color: "#7bbef6", fontSize: 10 },
+          bottom: 2, // 图例紧贴底部,不占额外空间
+          itemWidth: 10,
+          itemHeight: 8,
+        },
+        // 👇 关键修改:扩大 grid 边距,让图表填满容器
+        grid: { 
+          left: 40,   // 给左Y轴留出空间
+          right: 40,  // 给右Y轴留出空间
+          top: 15,    // 顶部留少量空间
+          bottom: 30, // 给X轴和图例留出空间
+          containLabel: false // 取消自动包含标签,避免额外留白
+        },
+        xAxis: {
+          type: "category",
+          boundaryGap: false,
+          data: timeList,
+          axisLine: { lineStyle: { color: "#7bbef6" } },
+          axisLabel: { 
+            color: "#7bbef6", 
+            fontSize: 8, 
+            interval: labelInterval,
+            rotate: 0 // 保持水平不倾斜
+          },
+        },
+        yAxis: [
+          {
+            type: "value",
+            name: "温度(℃)",
+            nameTextStyle: { color: "#62f6fb", fontSize: 10 },
+            axisLine: { lineStyle: { color: "#62f6fb" } },
+            axisLabel: { color: "#62f6fb", fontSize: 9 },
+            splitLine: { lineStyle: { color: "rgba(123,190,246,0.15)" } },
+          },
+          {
+            type: "value",
+            name: "应力(MPa)",
+            nameTextStyle: { color: "#f97316", fontSize: 10 },
+            axisLine: { lineStyle: { color: "#f97316" } },
+            axisLabel: { color: "#f97316", fontSize: 9 },
+            splitLine: { show: false },
+          },
+        ],
+        dataZoom: [
+          { type: "inside", start: 0, end: 100 },
+        ],
+        series: [
+          {
+            name: "温度(℃)",
+            type: "line",
+            data: tempData,
+            smooth: true,
+            symbol: "none",
+            lineStyle: { color: "#62f6fb", width: 1.5 },
+            itemStyle: { color: "#62f6fb" },
+            areaStyle: { color: "rgba(98,246,251,0.1)" },
+          },
+          {
+            name: "应力(MPa)",
+            type: "line",
+            yAxisIndex: 1,
+            data: stressData,
+            smooth: true,
+            symbol: "none",
+            lineStyle: { color: "#f97316", width: 1.5 },
+            itemStyle: { color: "#f97316" },
+            areaStyle: { color: "rgba(249,115,22,0.08)" },
+          },
+        ],
+      });
+    },
+  },
+};
+</script>
+
+<style scoped>
+.data-card { width: 100%; background: rgba(0,20,40,0.7); border-radius: 4px; overflow: hidden; box-shadow: 0 0 10px rgba(0,212,255,0.2); }
+.card-header { height: 42px; background-image: url("/src/assets/images/数据小标题.png"); background-size: 100% 100%; background-position: center; background-repeat: no-repeat; display: flex; align-items: flex-start; justify-content: space-between; padding: 4px 16px 0; }
+.card-title { font-size: var(--fs-card-title); font-weight: bold; color: #e0fcff; margin: 0; text-shadow: 0 0 5px rgba(0,212,255,0.5); padding-left: 20px; }
+.header-actions { display: flex; align-items: flex-start; gap: 10px; }
+.update-time { color: #7bbef6; font-size: 11px; font-family: monospace; }
+.card-body { padding: 8px; min-height: auto; font-size: 13px; line-height: 1.5; margin-top: -6px; }
+.card-body.chart-body { padding: 2px 8px 4px; }
+.monitor-chart-box { background: rgba(0,20,40,0.4); border-radius: 4px; border: 1px solid rgba(0,212,255,0.15); padding: 6px; }
+.monitor-chart-label { color: #62f6fb; font-size: var(--fs-label); font-weight: bold; margin-bottom: 2px; }
+.temp-chart-wrapper { width: 100%; }
+#tempStressChart { display: block; margin: 0 auto; }
+.water-stats-row { display: flex; gap: 6px; }
+.water-stat-card { flex: 1; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 3px 10px; background-image: url("/src/assets/images/卡片背景2.png"); background-size: 100% 100%; background-position: center; background-repeat: no-repeat; height: 42px; }
+.water-stat-label { color: #62f6fb; font-size: var(--fs-label); margin-bottom: 0; text-align: left; line-height: 1.2; white-space: nowrap; }
+.water-stat-value { color: #ffffff; font-size: var(--fs-value-xl); font-weight: bold; text-shadow: 0 0 6px rgba(0,212,255,0.5); line-height: 1.2; }
+.panel-date-label { color: #7bbef6; white-space: nowrap; }
+.panel-date-input { background: rgba(0,40,80,0.7); border: 1px solid rgba(0,212,255,0.25); border-radius: 3px; color: #e0fcff; font-weight: 300; padding: 0 6px; outline: none; cursor: pointer; }
+.panel-date-input::-webkit-calendar-picker-indicator { filter: invert(0.7); cursor: pointer; }
+.panel-close-btn { cursor: pointer; flex-shrink: 0; transition: opacity 0.15s; }
+.panel-close-btn:hover { opacity: 0.7; }
+</style>

+ 527 - 183
src/views/EngineeringSafetyView.vue

@@ -1,188 +1,503 @@
 <template>
   <div class="safety-container">
-    <div class="tab-bar">
-      <div class="tab-item" :class="{ active: currentTab === 'dam' }" @click="currentTab = 'dam'">大坝整体</div>
-      <div class="tab-item" :class="{ active: currentTab === 'spillway' }" @click="currentTab = 'spillway'">泄洪设施</div>
-      <div class="tab-item" :class="{ active: currentTab === 'conveyance' }" @click="currentTab = 'conveyance'">输水设施</div>
-      <div class="tab-item" :class="{ active: currentTab === 'bank' }" @click="currentTab = 'bank'">库岸稳定</div>
-    </div>
     <div class="safety-scroll-area">
-      <div v-if="currentTab === 'dam'" class="tab-content">
+      <div class="tab-content">
         <div class="left-sidebar">
-          <div class="data-card">
+          <div class="data-card tall-card">
             <div class="card-header">
-              <h3 class="card-title">大坝变形监测</h3>
-              <div class="header-actions"><span class="update-time">单位:mm</span></div>
+              <h3 class="card-title">安全状态评估</h3>
             </div>
             <div class="card-body chart-body">
-              <div class="monitor-chart-box">
-                <div class="monitor-chart-label">变形趋势</div>
-                <canvas id="deformationChart" width="320" height="170"></canvas>
-                <div class="chart-legend">
-                  <span class="legend-dot dot-horizontal"></span>水平位移
-                  <span class="legend-dot dot-vertical" style="margin-left:12px;"></span>垂直位移
-                </div>
-              </div>
-              <div class="water-stats-row" style="margin-top:4px;">
-                <div class="water-stat-card">
-                  <div class="water-stat-label">最大水平位移</div>
-                  <div class="water-stat-value">12.5</div>
+              <div class="safety-eval-section">
+                <div class="safety-eval-top">
+                  <span class="safety-eval-section-title">综合安全等级</span>
+                  <span class="safety-eval-grade grade-a">一类坝</span>
                 </div>
-                <div class="water-stat-card">
-                  <div class="water-stat-label">最大垂直位移</div>
-                  <div class="water-stat-value">8.3</div>
+                <div class="safety-eval-sub-row">
+                  <div class="safety-eval-sub-card">
+                    <span class="eval-sub-label">溢洪道侵占</span>
+                    <span class="eval-sub-value">无</span>
+                  </div>
+                  <div class="safety-eval-sub-card">
+                    <span class="eval-sub-label">近坝库岸状态</span>
+                    <span class="eval-sub-value status-ok">稳定</span>
+                  </div>
                 </div>
-              </div>
-            </div>
-          </div>
-          <div class="data-card mt-10">
-            <div class="card-header">
-              <h3 class="card-title">渗流监测</h3>
-              <div class="header-actions"><span class="update-time">单位:L/s</span></div>
-            </div>
-            <div class="card-body chart-body">
-              <div class="water-stats-row">
-                <div class="water-stat-card">
-                  <div class="water-stat-label">渗流量</div>
-                  <div class="water-stat-value">3.5</div>
+                <div class="safety-eval-mid">
+                  <div class="safety-eval-item">
+                    <span class="eval-item-name">防洪安全</span>
+                    <span class="eval-item-tag tag-ok">正常</span>
+                  </div>
+                  <div class="safety-eval-item">
+                    <span class="eval-item-name">渗流安全</span>
+                    <span class="eval-item-tag tag-ok">正常</span>
+                  </div>
+                  <div class="safety-eval-item">
+                    <span class="eval-item-name">坝体变形</span>
+                    <span class="eval-item-tag tag-ok">正常</span>
+                  </div>
+                  <div class="safety-eval-item">
+                    <span class="eval-item-name">设备运行</span>
+                    <span class="eval-item-tag tag-warn">预警</span>
+                  </div>
                 </div>
-                <div class="water-stat-card">
-                  <div class="water-stat-label">坝基扬压力</div>
-                  <div class="water-stat-value">18.2</div>
+                <div class="safety-eval-bot">
+                  <div class="safety-eval-stat">
+                    <span class="eval-stat-dot dot-ok"></span>正常 322
+                  </div>
+                  <div class="safety-eval-stat">
+                    <span class="eval-stat-dot dot-warn"></span>预警 8
+                  </div>
+                  <div class="safety-eval-stat">
+                    <span class="eval-stat-dot dot-err"></span>故障 3
+                  </div>
+                  <span class="safety-eval-time">最近巡检 2026-05-28</span>
                 </div>
               </div>
-              <div class="monitor-chart-box" style="margin-top:4px;">
-                <div class="monitor-chart-label">渗流趋势</div>
-                <canvas id="seepageChart" width="320" height="170"></canvas>
-                <div class="chart-legend">
-                  <span class="legend-dot dot-seepage"></span>渗流量
-                  <span class="legend-dot dot-pressure" style="margin-left:12px;"></span>渗流压力(kPa)
+              <div class="device-stats-row">
+                <div class="device-stat-card">
+                  <div class="device-stat-percent">89.55%</div>
+                  <div class="device-stat-icon"><img src="/src/assets/images/小图标库水位.png" class="stat-icon-img" /></div>
+                  <div class="device-stat-label">完好率</div>
                 </div>
-              </div>
-            </div>
-          </div>
-        </div>
-        <div class="right-sidebar">
-          <div class="data-card">
-            <div class="card-header">
-              <h3 class="card-title">安全状态评估</h3>
-            </div>
-            <div class="card-body chart-body">
-              <div class="safety-assessment-row">
-                <div class="safety-pie-section">
-                  <canvas id="statusPieChart" width="140" height="140"></canvas>
-                  <div class="pie-legend">
-                    <div class="pie-legend-item" v-for="item in pieData" :key="item.name">
-                      <span class="pie-dot" :style="{ background: item.color }"></span>
-                      <span class="pie-name">{{ item.name }}</span>
-                      <span class="pie-count">{{ item.count }}个</span>
-                    </div>
-                  </div>
+                <div class="device-gauge-section">
+                  <canvas id="deviceGaugeChart" width="140" height="150"></canvas>
                 </div>
-                <div class="safety-assessment-section">
-                  <div class="safety-assessment-bar">
-                    <div class="safety-assessment-grade">一类坝</div>
-                    <div class="safety-assessment-texts">
-                      <div class="safety-assessment-text">评估时间:2026-05-15</div>
-                      <div class="safety-assessment-text">运行正常,安全可靠</div>
-                    </div>
-                  </div>
-                  <div class="safety-assessment-tags">
-                    <div class="assess-tag good" v-for="item in assessmentIndicators" :key="item">{{ item }}</div>
-                  </div>
+                <div class="device-stat-card">
+                  <div class="device-stat-percent">84.36%</div>
+                  <div class="device-stat-icon"><img src="/src/assets/images/小图标库水位.png" class="stat-icon-img" /></div>
+                  <div class="device-stat-label">运行期 完好率</div>
                 </div>
               </div>
             </div>
-          </div>
-          <div class="data-card mt-10">
-            <div class="card-header">
-              <h3 class="card-title">监测点状态</h3>
-              <div class="header-actions"><span class="update-time">共 {{ monitorList.length }} 个测点</span></div>
-            </div>
+            <div class="monitor-divider"></div>
             <div class="card-body card-body-tight">
-              <div class="monitor-table">
-                <div class="monitor-table-header">
-                  <span class="col-name">测点名称</span>
-                  <span class="col-value">当前值</span>
-                  <span class="col-status">状态</span>
-                  <span class="col-trend">趋势</span>
+              <div class="monitor-summary-row">
+                <div class="monitor-summary-card" :class="{ active: activeSummaryCard === item.name }" v-for="item in monitorSummaryList" :key="item.name" @click="toggleSummaryCard(item.name)">
+                  <svg class="progress-ring" width="48" height="48" viewBox="0 0 48 48">
+                    <circle class="progress-ring-bg" cx="24" cy="24" r="20" />
+                    <circle class="progress-ring-fill" cx="24" cy="24" r="20"
+                      :stroke-dasharray="2 * Math.PI * 20"
+                      :stroke-dashoffset="2 * Math.PI * 20 * (1 - item.progress / 100)"
+                      :stroke="item.color" />
+                    <text x="24" y="24" text-anchor="middle" dominant-baseline="central"
+                      :fill="item.color" font-size="14" font-weight="bold">{{ item.progress }}%</text>
+                  </svg>
+                  <span class="monitor-summary-name">{{ item.name }}</span>
+                  <span class="monitor-summary-count" :style="{ color: item.color }">{{ item.count }}</span>
                 </div>
-                <div class="monitor-table-body">
-                  <div class="monitor-row" v-for="item in monitorList" :key="item.name">
-                    <span class="col-name">{{ item.name }}</span>
-                    <span class="col-value">{{ item.value }}{{ item.unit }}</span>
-                    <span class="col-status"><span class="status-tag" :class="item.status">{{ item.statusText }}</span></span>
-                    <span class="col-trend"><span class="trend-icon" :class="item.trend">{{ item.trend === 'up' ? '↑' : item.trend === 'down' ? '↓' : '→' }}</span></span>
+              </div>
+              <div class="summary-pie-area" v-if="activeSummaryCard">
+                <canvas :id="'summaryPieChart'" width="290" height="200"></canvas>
+              </div>
+              <div class="monitor-subtitle">设备清单</div>
+              <div class="tree-search-bar">
+                <svg class="tree-search-icon" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#7bbef6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="10.5" cy="10.5" r="7.5"/><line x1="16" y1="16" x2="22" y2="22"/></svg>
+                <input class="tree-search-input" v-model="searchKey" placeholder="请输入关键词" />
+              </div>
+              <div class="tree-container">
+                <div class="tree-node" v-for="node in filteredTree" :key="node.name">
+                  <div class="tree-node-row" :class="{ 'tree-leaf': !node.children }" @click="toggleNode(node)">
+                    <svg v-if="node.children" class="tree-arrow" :class="{ expanded: node.expanded }" viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="#7bbef6" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
+                    <span v-else class="tree-dot"></span>
+                    <span class="tree-node-label" :class="{ 'tree-node-bold': !node.children }">{{ node.name }}</span>
+                    <span v-if="node.count !== undefined" class="tree-node-count">{{ node.count }}</span>
+                    <div v-if="node.children" class="tree-node-actions">
+                      <svg class="tree-action-icon" title="定位" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#22c55e" stroke-width="2"><circle cx="12" cy="10.5" r="2.5"/><path d="M12 21C16 17 19 13.5 19 10.5C19 6.5 15.5 3 12 3C8.5 3 5 6.5 5 10.5C5 13.5 8 17 12 21Z"/></svg>
+                      <svg class="tree-action-icon" title="趋势" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#62f6fb" stroke-width="2"><polyline points="3 18 9 12 13 15 21 7"/><polyline points="15 7 21 7 21 13"/></svg>
+                    </div>
+                  </div>
+                  <div class="tree-children" v-if="node.children && node.expanded" v-for="child in node.children" :key="child.name">
+                    <div class="tree-node-row" :class="{ 'tree-leaf': !child.children }" @click="toggleNode(child)">
+                      <svg v-if="child.children" class="tree-arrow" :class="{ expanded: child.expanded }" viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="#7bbef6" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
+                      <span v-else class="tree-dot"></span>
+                      <span class="tree-node-label" :class="{ 'tree-node-bold': !child.children }">{{ child.name }}</span>
+                      <span v-if="child.count !== undefined" class="tree-node-count">{{ child.count }}</span>
+                      <div v-if="!child.children" class="tree-node-actions">
+                        <svg class="tree-action-icon" title="定位" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#22c55e" stroke-width="2"><circle cx="12" cy="10.5" r="2.5"/><path d="M12 21C16 17 19 13.5 19 10.5C19 6.5 15.5 3 12 3C8.5 3 5 6.5 5 10.5C5 13.5 8 17 12 21Z"/></svg>
+                        <svg class="tree-action-icon" title="趋势" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#62f6fb" stroke-width="2"><polyline points="3 18 9 12 13 15 21 7"/><polyline points="15 7 21 7 21 13"/></svg>
+                      </div>
+                    </div>
+                    <div class="tree-grandchildren" v-if="child.children && child.expanded" v-for="grand in child.children" :key="grand.name">
+                      <div class="tree-node-row" :class="{ 'tree-leaf': !grand.children }" @click="toggleNode(grand)">
+                        <svg v-if="grand.children" class="tree-arrow" :class="{ expanded: grand.expanded }" viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="#7bbef6" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
+                        <span v-else class="tree-dot"></span>
+                        <span class="tree-node-label" :class="{ 'tree-node-bold': !grand.children }">{{ grand.name }}</span>
+                        <span v-if="grand.count !== undefined" class="tree-node-count">{{ grand.count }}</span>
+                        <div v-if="grand.children" class="tree-node-actions">
+                          <svg class="tree-action-icon" title="定位" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#22c55e" stroke-width="2"><circle cx="12" cy="10.5" r="2.5"/><path d="M12 21C16 17 19 13.5 19 10.5C19 6.5 15.5 3 12 3C8.5 3 5 6.5 5 10.5C5 13.5 8 17 12 21Z"/></svg>
+                          <svg class="tree-action-icon" title="趋势" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#62f6fb" stroke-width="2"><polyline points="3 18 9 12 13 15 21 7"/><polyline points="15 7 21 7 21 13"/></svg>
+                        </div>
+                        <div v-else class="tree-node-actions">
+                          <svg class="tree-action-icon" title="定位" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#22c55e" stroke-width="2"><circle cx="12" cy="10.5" r="2.5"/><path d="M12 21C16 17 19 13.5 19 10.5C19 6.5 15.5 3 12 3C8.5 3 5 6.5 5 10.5C5 13.5 8 17 12 21Z"/></svg>
+                          <svg class="tree-action-icon" title="趋势" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#62f6fb" stroke-width="2"><polyline points="3 18 9 12 13 15 21 7"/><polyline points="15 7 21 7 21 13"/></svg>
+                        </div>
+                      </div>
+                      <div class="tree-locations" v-if="grand.children && grand.expanded" v-for="loc in grand.children" :key="loc.name">
+                        <div class="tree-node-row" :class="{ 'tree-leaf': !loc.children }" @click="toggleNode(loc)">
+                          <svg v-if="loc.children" class="tree-arrow" :class="{ expanded: loc.expanded }" viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="#7bbef6" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
+                          <span v-else class="tree-dot"></span>
+                          <span class="tree-node-label" :class="{ 'tree-node-bold': !loc.children }">{{ loc.name }}</span>
+                          <span v-if="loc.count !== undefined" class="tree-node-count">{{ loc.count }}</span>
+                          <div v-if="loc.children" class="tree-node-actions">
+                            <svg class="tree-action-icon" title="定位" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#22c55e" stroke-width="2"><circle cx="12" cy="10.5" r="2.5"/><path d="M12 21C16 17 19 13.5 19 10.5C19 6.5 15.5 3 12 3C8.5 3 5 6.5 5 10.5C5 13.5 8 17 12 21Z"/></svg>
+                            <svg class="tree-action-icon" title="趋势" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#62f6fb" stroke-width="2"><polyline points="3 18 9 12 13 15 21 7"/><polyline points="15 7 21 7 21 13"/></svg>
+                          </div>
+                          <div v-else class="tree-node-actions">
+                            <svg class="tree-action-icon" title="定位" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#22c55e" stroke-width="2"><circle cx="12" cy="10.5" r="2.5"/><path d="M12 21C16 17 19 13.5 19 10.5C19 6.5 15.5 3 12 3C8.5 3 5 6.5 5 10.5C5 13.5 8 17 12 21Z"/></svg>
+                            <svg class="tree-action-icon" title="趋势" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#62f6fb" stroke-width="2"><polyline points="3 18 9 12 13 15 21 7"/><polyline points="15 7 21 7 21 13"/></svg>
+                          </div>
+                        </div>
+                        <div class="tree-points" v-if="loc.children && loc.expanded" v-for="pt in loc.children" :key="pt.name">
+                          <div class="tree-node-row" :class="{ 'tree-leaf': !pt.children }" @click="toggleNode(pt)">
+                            <svg v-if="pt.children" class="tree-arrow" :class="{ expanded: pt.expanded }" viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="#7bbef6" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
+                            <span v-else class="tree-dot"></span>
+                            <span class="tree-node-label" :class="{ 'tree-node-bold': !pt.children }">{{ pt.name }}</span>
+                            <span v-if="pt.count !== undefined" class="tree-node-count">{{ pt.count }}</span>
+                            <div v-if="pt.children" class="tree-node-actions">
+                              <svg class="tree-action-icon" title="定位" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#22c55e" stroke-width="2"><circle cx="12" cy="10.5" r="2.5"/><path d="M12 21C16 17 19 13.5 19 10.5C19 6.5 15.5 3 12 3C8.5 3 5 6.5 5 10.5C5 13.5 8 17 12 21Z"/></svg>
+                              <svg class="tree-action-icon" title="趋势" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#62f6fb" stroke-width="2"><polyline points="3 18 9 12 13 15 21 7"/><polyline points="15 7 21 7 21 13"/></svg>
+                            </div>
+                            <div v-else class="tree-node-actions">
+                              <svg class="tree-action-icon" title="定位" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#22c55e" stroke-width="2"><circle cx="12" cy="10.5" r="2.5"/><path d="M12 21C16 17 19 13.5 19 10.5C19 6.5 15.5 3 12 3C8.5 3 5 6.5 5 10.5C5 13.5 8 17 12 21Z"/></svg>
+                              <svg class="tree-action-icon" title="趋势" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#62f6fb" stroke-width="2"><polyline points="3 18 9 12 13 15 21 7"/><polyline points="15 7 21 7 21 13"/></svg>
+                            </div>
+                          </div>
+                          <div class="tree-devices" v-if="pt.children && pt.expanded" v-for="dev in pt.children" :key="dev.name">
+                            <div class="tree-node-row tree-leaf" @click.stop="openDevicePanel(dev, grand, loc, pt, child)">
+                              <span class="tree-dot"></span>
+                              <span class="tree-node-label tree-node-bold" style="color:#62f6fb;">{{ dev.name }}</span>
+                              <div class="tree-node-actions">
+                                <svg class="tree-action-icon" title="定位" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#22c55e" stroke-width="2"><circle cx="12" cy="10.5" r="2.5"/><path d="M12 21C16 17 19 13.5 19 10.5C19 6.5 15.5 3 12 3C8.5 3 5 6.5 5 10.5C5 13.5 8 17 12 21Z"/></svg>
+                                <svg class="tree-action-icon" title="趋势" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#62f6fb" stroke-width="2"><polyline points="3 18 9 12 13 15 21 7"/><polyline points="15 7 21 7 21 13"/></svg>
+                              </div>
+                            </div>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
                   </div>
                 </div>
               </div>
             </div>
           </div>
         </div>
+        <div class="middle-area">
+          <TempStressCard
+            :device="selectedDevice"
+            :breadcrumb="deviceBreadcrumb"
+            :visible="!!selectedDevice"
+            @close="closeTempCard"
+          />
+        </div>
+        <div class="right-sidebar">
+          <DeviceRightPanel
+            :device="selectedDevice"
+            :breadcrumb="deviceBreadcrumb"
+          />
+        </div>
       </div>
-      <div v-if="currentTab === 'spillway'" class="tab-content tab-placeholder"><div class="placeholder-text">泄洪设施监测数据</div></div>
-      <div v-if="currentTab === 'conveyance'" class="tab-content tab-placeholder"><div class="placeholder-text">输水设施监测数据</div></div>
-      <div v-if="currentTab === 'bank'" class="tab-content tab-placeholder"><div class="placeholder-text">库岸稳定监测数据</div></div>
     </div>
+
   </div>
 </template>
 <script>
 import * as echarts from "echarts";
+import TempStressCard from "../components/TempStressCard.vue";
+import DeviceRightPanel from "../components/DeviceRightPanel.vue";
 export default {
   name: "EngineeringSafetyView",
+  components: { TempStressCard, DeviceRightPanel },
   data() {
     return {
-      currentTab: "dam",
-      monitorList: [
-        { name: "坝顶水平位移-01", value: "12.5", unit: "mm", status: "normal", statusText: "正常", trend: "up" },
-        { name: "坝顶水平位移-02", value: "11.8", unit: "mm", status: "normal", statusText: "正常", trend: "steady" },
-        { name: "坝基垂直位移-01", value: "8.3", unit: "mm", status: "normal", statusText: "正常", trend: "down" },
-        { name: "坝基垂直位移-02", value: "7.9", unit: "mm", status: "normal", statusText: "正常", trend: "steady" },
-        { name: "渗流压力-P01", value: "25.6", unit: "kPa", status: "normal", statusText: "正常", trend: "up" },
-        { name: "渗流压力-P02", value: "22.1", unit: "kPa", status: "normal", statusText: "正常", trend: "steady" },
-        { name: "绕坝渗流-S01", value: "1.2", unit: "L/s", status: "normal", statusText: "正常", trend: "steady" },
-        { name: "裂缝开合度-C01", value: "1.2", unit: "mm", status: "warning", statusText: "注意", trend: "up" },
-        { name: "钢筋应力-R01", value: "45.2", unit: "MPa", status: "normal", statusText: "正常", trend: "steady" },
-        { name: "库岸变形-B01", value: "3.2", unit: "mm", status: "normal", statusText: "正常", trend: "down" },
+      searchKey: "",
+      treeData: [
+        {
+          name: "大坝安全监测系统", expanded: true, children: [
+            {
+              name: "主坝", expanded: true, children: [
+                {
+                  name: "变形监测", expanded: true, children: [
+                    {
+                      name: "坝顶", children: [
+                        { name: "坝顶水平位移-01", children: [{ name: "R201YL1" }, { name: "R201YL2" }, { name: "R201YL3" }] },
+                        { name: "坝顶水平位移-02", children: [{ name: "R201YL4" }, { name: "R201YL5" }, { name: "R201YL6" }] },
+                        { name: "坝顶水平位移-03", children: [{ name: "R201YL7" }, { name: "R201YL8" }, { name: "R201YL9" }] },
+                        { name: "坝顶水平位移-04", children: [{ name: "R201YL10" }, { name: "R201YL11" }, { name: "R201YL12" }] },
+                        { name: "坝顶水平位移-05", children: [{ name: "R201YL13" }, { name: "R201YL14" }, { name: "R201YL15" }] },
+                      ],
+                    },
+                    {
+                      name: "坝基", children: [
+                        { name: "坝基垂直位移-01", children: [{ name: "B301YL1" }, { name: "B301YL2" }, { name: "B301YL3" }] },
+                        { name: "坝基垂直位移-02", children: [{ name: "B301YL4" }, { name: "B301YL5" }, { name: "B301YL6" }] },
+                        { name: "坝基垂直位移-03", children: [{ name: "B301YL7" }, { name: "B301YL8" }, { name: "B301YL9" }] },
+                        { name: "坝基垂直位移-04", children: [{ name: "B301YL10" }, { name: "B301YL11" }, { name: "B301YL12" }] },
+                        { name: "坝基垂直位移-05", children: [{ name: "B301YL13" }, { name: "B301YL14" }, { name: "B301YL15" }] },
+                        { name: "坝基垂直位移-06", children: [{ name: "B301YL16" }, { name: "B301YL17" }, { name: "B301YL18" }] },
+                      ],
+                    },
+                    {
+                      name: "左岸", children: [
+                        { name: "库岸变形-B01", children: [{ name: "K401YL1" }, { name: "K401YL2" }, { name: "K401YL3" }] },
+                        { name: "库岸变形-B02", children: [{ name: "K401YL4" }, { name: "K401YL5" }, { name: "K401YL6" }] },
+                        { name: "库岸变形-B03", children: [{ name: "K401YL7" }, { name: "K401YL8" }, { name: "K401YL9" }] },
+                        { name: "裂缝开合度-C01", children: [{ name: "F501YL1" }, { name: "F501YL2" }, { name: "F501YL3" }] },
+                        { name: "裂缝开合度-C02", children: [{ name: "F501YL4" }, { name: "F501YL5" }, { name: "F501YL6" }] },
+                      ],
+                    },
+                    {
+                      name: "右岸", children: [
+                        { name: "库岸变形-B04", children: [{ name: "K401YL10" }, { name: "K401YL11" }, { name: "K401YL12" }] },
+                        { name: "库岸变形-B05", children: [{ name: "K401YL13" }, { name: "K401YL14" }, { name: "K401YL15" }] },
+                        { name: "裂缝开合度-C03", children: [{ name: "F501YL7" }, { name: "F501YL8" }, { name: "F501YL9" }] },
+                      ],
+                    },
+                    {
+                      name: "坝肩", children: [
+                        { name: "坝肩位移-J01", children: [{ name: "J601YL1" }, { name: "J601YL2" }, { name: "J601YL3" }] },
+                        { name: "坝肩位移-J02", children: [{ name: "J601YL4" }, { name: "J601YL5" }, { name: "J601YL6" }] },
+                        { name: "精密水准-S01", children: [{ name: "S701YL1" }, { name: "S701YL2" }, { name: "S701YL3" }] },
+                      ],
+                    },
+                  ],
+                },
+                {
+                  name: "渗流监测", expanded: true, children: [
+                    {
+                      name: "坝基", children: [
+                        { name: "渗流压力-P01", children: [{ name: "P801YL1" }, { name: "P801YL2" }, { name: "P801YL3" }] },
+                        { name: "渗流压力-P02", children: [{ name: "P801YL4" }, { name: "P801YL5" }, { name: "P801YL6" }] },
+                        { name: "渗流压力-P03", children: [{ name: "P801YL7" }, { name: "P801YL8" }, { name: "P801YL9" }] },
+                        { name: "渗流压力-P04", children: [{ name: "P801YL10" }, { name: "P801YL11" }, { name: "P801YL12" }] },
+                        { name: "绕坝渗流-S01", children: [{ name: "R901YL1" }, { name: "R901YL2" }, { name: "R901YL3" }] },
+                        { name: "绕坝渗流-S02", children: [{ name: "R901YL4" }, { name: "R901YL5" }, { name: "R901YL6" }] },
+                      ],
+                    },
+                    {
+                      name: "坝体", children: [
+                        { name: "渗流压力-P05", children: [{ name: "P801YL13" }, { name: "P801YL14" }, { name: "P801YL15" }] },
+                        { name: "渗流压力-P06", children: [{ name: "P801YL16" }, { name: "P801YL17" }, { name: "P801YL18" }] },
+                        { name: "渗流压力-P07", children: [{ name: "P801YL19" }, { name: "P801YL20" }, { name: "P801YL21" }] },
+                        { name: "渗流压力-P08", children: [{ name: "P801YL22" }, { name: "P801YL23" }, { name: "P801YL24" }] },
+                      ],
+                    },
+                    {
+                      name: "廊道", children: [
+                        { name: "廊道渗流-L01", children: [{ name: "L011YL1" }, { name: "L011YL2" }, { name: "L011YL3" }] },
+                        { name: "廊道渗流-L02", children: [{ name: "L011YL4" }, { name: "L011YL5" }, { name: "L011YL6" }] },
+                        { name: "廊道渗流-L03", children: [{ name: "L011YL7" }, { name: "L011YL8" }, { name: "L011YL9" }] },
+                      ],
+                    },
+                  ],
+                },
+                {
+                  name: "应力应变", expanded: true, children: [
+                    {
+                      name: "坝体", children: [
+                        { name: "钢筋应力-R01", children: [{ name: "G111YL1" }, { name: "G111YL2" }, { name: "G111YL3" }] },
+                        { name: "钢筋应力-R02", children: [{ name: "G111YL4" }, { name: "G111YL5" }, { name: "G111YL6" }] },
+                        { name: "钢筋应力-R03", children: [{ name: "G111YL7" }, { name: "G111YL8" }, { name: "G111YL9" }] },
+                        { name: "钢筋应力-R04", children: [{ name: "G111YL10" }, { name: "G111YL11" }, { name: "G111YL12" }] },
+                      ],
+                    },
+                    {
+                      name: "坝基", children: [
+                        { name: "基岩应力-Y01", children: [{ name: "J121YL1" }, { name: "J121YL2" }, { name: "J121YL3" }] },
+                        { name: "基岩应力-Y02", children: [{ name: "J121YL4" }, { name: "J121YL5" }, { name: "J121YL6" }] },
+                        { name: "基岩应力-Y03", children: [{ name: "J121YL7" }, { name: "J121YL8" }, { name: "J121YL9" }] },
+                      ],
+                    },
+                  ],
+                },
+                {
+                  name: "环境量监测", expanded: true, children: [
+                    {
+                      name: "库水位", children: [
+                        { name: "库水位-W01", children: [{ name: "W131YL1" }, { name: "W131YL2" }, { name: "W131YL3" }] },
+                        { name: "库水位-W02", children: [{ name: "W131YL4" }, { name: "W131YL5" }, { name: "W131YL6" }] },
+                      ],
+                    },
+                    {
+                      name: "降雨量", children: [
+                        { name: "雨量计-R01", children: [{ name: "Y141YL1" }, { name: "Y141YL2" }, { name: "Y141YL3" }] },
+                        { name: "雨量计-R02", children: [{ name: "Y141YL4" }, { name: "Y141YL5" }, { name: "Y141YL6" }] },
+                      ],
+                    },
+                    {
+                      name: "温度", children: [
+                        { name: "温度计-T01", children: [{ name: "T151YL1" }, { name: "T151YL2" }, { name: "T151YL3" }] },
+                        { name: "温度计-T02", children: [{ name: "T151YL4" }, { name: "T151YL5" }, { name: "T151YL6" }] },
+                        { name: "温度计-T03", children: [{ name: "T151YL7" }, { name: "T151YL8" }, { name: "T151YL9" }] },
+                      ],
+                    },
+                  ],
+                },
+              ],
+            },
+          ],
+        },
       ],
-      pieData: [
-        { name: "正常", count: 8, color: "#22c55e" },
-        { name: "注意", count: 2, color: "#ffd93d" },
-        { name: "警戒", count: 0, color: "#f97316" },
-        { name: "危险", count: 0, color: "#ef4444" },
+      monitorSummaryList: [
+        { name: "变形检测", count: 42, progress: 82, color: "#62f6fb" },
+        { name: "渗流渗压", count: 28, progress: 65, color: "#a78bfa" },
+        { name: "应力应变", count: 16, progress: 45, color: "#fbbf24" },
       ],
-      assessmentIndicators: ["变形指标 合格", "渗流指标 合格", "结构指标 合格", "环境指标 合格"],
-      deformationChart: null,
-      seepageChart: null,
-      statusPieChart: null,
+      summaryPieMap: {
+        "变形检测": [
+          { name: "精密水准", value: 12, color: "#62f6fb" },
+          { name: "基岩变形计", value: 10, color: "#34d399" },
+          { name: "电磁沉降环", value: 8, color: "#fbbf24" },
+          { name: "测缝计", value: 7, color: "#f97316" },
+          { name: "多点位移计", value: 5, color: "#ef4444" },
+        ],
+        "渗流渗压": [
+          { name: "渗压计", value: 10, color: "#a78bfa" },
+          { name: "量水堰", value: 7, color: "#818cf8" },
+          { name: "测压管", value: 6, color: "#c084fc" },
+          { name: "孔隙水压力计", value: 5, color: "#e879f9" },
+        ],
+        "应力应变": [
+          { name: "钢筋计", value: 6, color: "#fbbf24" },
+          { name: "应变计", value: 5, color: "#f59e0b" },
+          { name: "无应力计", value: 5, color: "#f97316" },
+        ],
+      },
+      activeSummaryCard: "变形检测",
+      summaryPieChart: null,
+      selectedDevice: null,
+      deviceBreadcrumb: "",
+      deviceGaugeChart: null,
     };
   },
-  mounted() { this.$nextTick(() => this.initCharts()); },
+  computed: {
+    filteredTree() {
+      const kw = this.searchKey.trim().toLowerCase();
+      if (!kw) return this.treeData;
+      const result = [];
+      for (const root of this.treeData) {
+        const matched = this.filterNode(root, kw);
+        if (matched) result.push(matched);
+      }
+      return result;
+    },
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initCharts();
+      this.renderSummaryPieChart();
+    });
+  },
   beforeUnmount() {
-    if (this.deformationChart) this.deformationChart.dispose();
-    if (this.seepageChart) this.seepageChart.dispose();
-    if (this.statusPieChart) this.statusPieChart.dispose();
+    if (this.summaryPieChart) this.summaryPieChart.dispose();
+    if (this.deviceGaugeChart) this.deviceGaugeChart.dispose();
   },
   methods: {
-    initCharts() {
-      if (!echarts) return;
-      const dc = document.getElementById("deformationChart");
-      if (dc) {
-        if (this.deformationChart) this.deformationChart.dispose();
-        this.deformationChart = echarts.init(dc);
-        this.deformationChart.setOption({ animation: false, tooltip: { trigger: "axis" }, grid: { left: "8%", right: "5%", top: "8%", bottom: "12%" }, xAxis: { type: "category", data: ["1月","2月","3月","4月","5月","6月"], axisLine: { lineStyle: { color: "#7bbef6" } }, axisLabel: { color: "#7bbef6", fontSize: 9 } }, yAxis: { type: "value", name: "mm", nameTextStyle: { color: "#7bbef6", fontSize: 9 }, axisLine: { lineStyle: { color: "#7bbef6" } }, axisLabel: { color: "#7bbef6", fontSize: 9 }, splitLine: { lineStyle: { color: "rgba(123,190,246,0.15)" } } }, series: [{ name: "水平位移", type: "line", data: [8.2,9.5,10.8,11.2,12.0,12.5], smooth: true, symbol: "circle", symbolSize: 4, lineStyle: { color: "#62f6fb", width: 2 }, itemStyle: { color: "#62f6fb" }, areaStyle: { color: "rgba(98,246,251,0.1)" } }, { name: "垂直位移", type: "line", data: [5.1,6.2,7.0,7.5,8.0,8.3], smooth: true, symbol: "diamond", symbolSize: 4, lineStyle: { color: "#fbbf24", width: 2 }, itemStyle: { color: "#fbbf24" }, areaStyle: { color: "rgba(251,191,36,0.08)" } }] });
+    filterNode(node, kw) {
+      if (!node.children) {
+        return node.name.toLowerCase().includes(kw) ? { ...node } : null;
+      }
+      const matchedChildren = node.children.map(c => this.filterNode(c, kw)).filter(Boolean);
+      if (matchedChildren.length > 0 || node.name.toLowerCase().includes(kw)) {
+        return { ...node, children: matchedChildren.length > 0 ? matchedChildren : node.children, expanded: true };
       }
-      const sc = document.getElementById("seepageChart");
-      if (sc) {
-        if (this.seepageChart) this.seepageChart.dispose();
-        this.seepageChart = echarts.init(sc);
-        this.seepageChart.setOption({ animation: false, tooltip: { trigger: "axis" }, legend: { data: ["渗流量","渗流压力"], textStyle: { color: "#7bbef6", fontSize: 9 }, bottom: 0, itemWidth: 10, itemHeight: 8 }, grid: { left: "8%", right: "5%", top: "8%", bottom: "22%" }, xAxis: { type: "category", data: ["1月","2月","3月","4月","5月","6月"], axisLine: { lineStyle: { color: "#7bbef6" } }, axisLabel: { color: "#7bbef6", fontSize: 9 } }, yAxis: { type: "value", nameTextStyle: { color: "#7bbef6", fontSize: 9 }, axisLine: { lineStyle: { color: "#7bbef6" } }, axisLabel: { color: "#7bbef6", fontSize: 9 }, splitLine: { lineStyle: { color: "rgba(123,190,246,0.15)" } } }, series: [{ name: "渗流量", type: "line", data: [4.2,3.8,3.6,3.5,3.5,3.5], smooth: true, symbol: "circle", symbolSize: 4, lineStyle: { color: "#62f6fb", width: 2 }, itemStyle: { color: "#62f6fb" }, areaStyle: { color: "rgba(98,246,251,0.12)" } }, { name: "渗流压力", type: "line", data: [28.5,26.8,25.2,24.8,25.0,25.6], smooth: true, symbol: "diamond", symbolSize: 4, lineStyle: { color: "#a78bfa", width: 2 }, itemStyle: { color: "#a78bfa" }, areaStyle: { color: "rgba(167,139,250,0.08)" } }] });
+      return null;
+    },
+    toggleNode(node) {
+      if (node.children) {
+        node.expanded = !node.expanded;
       }
-      const pc = document.getElementById("statusPieChart");
-      if (pc) {
-        if (this.statusPieChart) this.statusPieChart.dispose();
-        this.statusPieChart = echarts.init(pc);
-        this.statusPieChart.setOption({ animation: false, tooltip: { trigger: "item", formatter: "{b}: {c}个 ({d}%)" }, series: [{ type: "pie", radius: ["45%","75%"], center: ["50%","50%"], avoidLabelOverlap: false, itemStyle: { borderColor: "rgba(0,30,60,0.6)", borderWidth: 2 }, label: { show: false }, labelLine: { show: false }, data: [{ value: 8, name: "正常", itemStyle: { color: "#22c55e" } }, { value: 2, name: "注意", itemStyle: { color: "#ffd93d" } }, { value: 0, name: "警戒", itemStyle: { color: "#f97316" } }, { value: 0, name: "危险", itemStyle: { color: "#ef4444" } }] }] });
+    },
+    toggleSummaryCard(name) {
+      if (this.activeSummaryCard === name) {
+        this.activeSummaryCard = null;
+        return;
+      }
+      this.activeSummaryCard = name;
+      this.$nextTick(() => this.renderSummaryPieChart());
+    },
+    renderSummaryPieChart() {
+      if (!echarts || !this.activeSummaryCard) return;
+      if (this.summaryPieChart) this.summaryPieChart.dispose();
+      const el = document.getElementById("summaryPieChart");
+      if (!el) return;
+      this.summaryPieChart = echarts.init(el, null, { renderer: "canvas", devicePixelRatio: window.devicePixelRatio || 1 });
+      const data = this.summaryPieMap[this.activeSummaryCard] || [];
+      const summaryItem = this.monitorSummaryList.find(i => i.name === this.activeSummaryCard);
+      const total = summaryItem ? summaryItem.count : 0;
+      this.summaryPieChart.setOption({
+        animation: false,
+        tooltip: { trigger: "item", formatter: "{b}: {c}个 ({d}%)" },
+        graphic: [{
+          type: "text",
+          left: "center",
+          top: "center",
+          style: {
+            text: `{total|${total}}\n{label|${this.activeSummaryCard}}`,
+            fill: "#ffffff",
+            rich: {
+              total: { color: "#ffffff", fontSize: 22, fontWeight: "bold", lineHeight: 30 },
+              label: { color: "#ffffff", fontSize: 11, lineHeight: 16 },
+            },
+            textAlign: "center",
+          },
+        }],
+        series: [{
+          type: "pie",
+          radius: ["52%", "72%"],
+          center: ["50%", "50%"],
+          avoidLabelOverlap: true,
+          itemStyle: { borderColor: "rgba(0,30,60,0.6)", borderWidth: 2 },
+          label: { show: true, formatter: "{b}\n{d}%", color: "#ffffff", fontSize: 10, lineHeight: 14 },
+          labelLine: { show: true, length: 10, length2: 8, lineStyle: { color: "rgba(0,212,255,0.3)" } },
+          emphasis: { label: { show: true, fontSize: 12, fontWeight: "bold" }, itemStyle: { shadowBlur: 6, shadowColor: "rgba(0,212,255,0.3)" } },
+          data: data.map(d => ({ value: d.value, name: d.name, itemStyle: { color: d.color } })),
+        }],
+      });
+    },
+    openDevicePanel(dev, grand, loc, pt, child) {
+      this.selectedDevice = dev;
+      this.deviceBreadcrumb = `${child.name} > ${grand.name} > ${loc.name} > ${pt.name} > ${dev.name}`;
+    },
+    closeTempCard() {
+      this.selectedDevice = null;
+    },
+    initCharts() {
+      if (!echarts) return;
+      const gc = document.getElementById("deviceGaugeChart");
+      if (gc) {
+        if (this.deviceGaugeChart) this.deviceGaugeChart.dispose();
+        this.deviceGaugeChart = echarts.init(gc, null, { renderer: "canvas", devicePixelRatio: window.devicePixelRatio || 1 });
+        this.deviceGaugeChart.setOption({
+           animation: false,
+           series: [{
+             type: "gauge",
+             center: ["50%", "55%"],
+             radius: "80%",
+             startAngle: 210,
+             endAngle: -30,
+             min: 0,
+             max: 3000,
+             splitNumber: 3,
+             progress: { show: true, width: 16, itemStyle: { color: { type: "linear", x: 0, y: 0, x2: 1, y2: 0, colorStops: [{ offset: 0, color: "#62f6fb" }, { offset: 0.5, color: "#a78bfa" }, { offset: 1, color: "#fbbf24" }] } } },
+              axisLine: { lineStyle: { width: 16, color: [[1, "rgba(255,255,255,0.08)"]] } },
+             axisTick: { show: false },
+             splitLine: { show: false },
+             axisLabel: { show: false },
+             pointer: { show: false },
+             anchor: { show: false },
+             title: { show: false },
+             detail: {
+                offsetCenter: [0, "20%"],
+                formatter: () => "{val|2,279}\n{label|在线仪器}",
+                rich: {
+                  val: { color: "#ffffff", fontSize: 22, fontWeight: "bold", lineHeight: 30 },
+                  label: { color: "#ffffff", fontSize: 15, lineHeight: 22 },
+                },
+              },
+             data: [{ value: 2279, name: "" }],
+           }],
+        });
       }
     },
   },
@@ -190,19 +505,15 @@ export default {
 </script>
 <style scoped>
 .safety-container { width: 100%; height: 100%; display: flex; flex-direction: column; position: relative; overflow: hidden; }
-.tab-bar { position: absolute; left: 20px; top: 80px; z-index: 10; display: flex; gap: 4px; }
-.tab-item { padding: 6px 24px; font-size: 14px; font-weight: bold; color: #7bbef6; background: rgba(0,20,40,0.6); border: 1px solid rgba(0,212,255,0.25); border-radius: 3px 3px 0 0; cursor: pointer; transition: all 0.2s; user-select: none; }
-.tab-item:hover { background: rgba(0,40,80,0.7); color: #62f6fb; }
-.tab-item.active { background: rgba(0,20,40,0.85); color: #e0fcff; border-color: rgba(0,212,255,0.5); border-bottom-color: transparent; text-shadow: 0 0 5px rgba(0,212,255,0.3); }
-.safety-scroll-area { position: absolute; left: 0; right: 0; top: 110px; bottom: 0; overflow-y: auto; overflow-x: hidden; z-index: 5; padding-bottom: 40px; }
+.safety-scroll-area { position: absolute; left: 0; right: 0; top: 80px; bottom: 0; overflow-y: auto; overflow-x: hidden; z-index: 5; padding-bottom: 40px; pointer-events: none; }
 .safety-scroll-area::-webkit-scrollbar { width: 4px; }
 .safety-scroll-area::-webkit-scrollbar-track { background: rgba(0,20,40,0.5); }
 .safety-scroll-area::-webkit-scrollbar-thumb { background: rgba(0,212,255,0.3); border-radius: 2px; }
 .tab-content { width: 100%; display: flex; justify-content: space-between; padding: 0 20px; box-sizing: border-box; }
-.left-sidebar { width: 350px; flex-shrink: 0; }
-.right-sidebar { width: 350px; flex-shrink: 0; }
-.tab-placeholder { justify-content: center; align-items: center; min-height: 300px; }
-.placeholder-text { color: #7bbef6; font-size: 18px; opacity: 0.6; padding: 80px 0; }
+.left-sidebar { width: 350px; flex-shrink: 0; pointer-events: auto; }
+.middle-area { flex: 1; margin: 0 10px; min-width: 0; pointer-events: auto; display: flex; flex-direction: column; }
+.middle-area-content { margin-top: auto; }
+.right-sidebar { width: 350px; flex-shrink: 0; pointer-events: auto; }
 .mt-10 { margin-top: 10px; }
 .data-card { width: 100%; background: rgba(0,20,40,0.7); border-radius: 4px; overflow: hidden; box-shadow: 0 0 10px rgba(0,212,255,0.2); }
 .card-header { height: 42px; background-image: url("/src/assets/images/数据小标题.png"); background-size: 100% 100%; background-position: center; background-repeat: no-repeat; display: flex; align-items: flex-start; justify-content: space-between; padding: 4px 16px 0; }
@@ -210,8 +521,8 @@ export default {
 .header-actions { display: flex; align-items: flex-start; gap: 10px; }
 .update-time { color: #7bbef6; font-size: 11px; font-family: monospace; }
 .card-body { padding: 8px; min-height: auto; font-size: 13px; line-height: 1.5; margin-top: -6px; }
-.card-body.chart-body { padding: 6px 8px 8px; }
-.card-body.card-body-tight { padding: 6px 8px; }
+.card-body.chart-body { padding: 2px 8px 4px; }
+.card-body.card-body-tight { padding: 2px 8px 4px; }
 .monitor-chart-box { background: rgba(0,20,40,0.4); border-radius: 4px; border: 1px solid rgba(0,212,255,0.15); padding: 6px; }
 .monitor-chart-label { color: #62f6fb; font-size: var(--fs-label); font-weight: bold; margin-bottom: 2px; }
 .chart-legend { text-align: center; margin-top: 3px; color: #7bbef6; font-size: 10px; }
@@ -221,36 +532,69 @@ export default {
 .water-stat-card { flex: 1; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 3px 10px; background-image: url("/src/assets/images/卡片背景2.png"); background-size: 100% 100%; background-position: center; background-repeat: no-repeat; height: 42px; }
 .water-stat-label { color: #62f6fb; font-size: var(--fs-label); margin-bottom: 0; text-align: left; line-height: 1.2; white-space: nowrap; }
 .water-stat-value { color: #ffffff; font-size: var(--fs-value-xl); font-weight: bold; text-shadow: 0 0 6px rgba(0,212,255,0.5); line-height: 1.2; }
-.safety-assessment-row { display: flex; gap: 8px; align-items: stretch; }
-.safety-pie-section { flex-shrink: 0; width: 45%; text-align: center; }
-.pie-legend { margin-top: 4px; display: flex; flex-wrap: wrap; gap: 3px; }
-.pie-legend-item { display: flex; align-items: center; gap: 3px; flex: 1 1 45%; min-width: 70px; }
-.pie-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
-.pie-name { color: #7bbef6; font-size: 10px; }
-.pie-count { color: #e0fcff; font-size: 11px; font-weight: bold; margin-left: auto; }
-.safety-assessment-section { flex: 1; display: flex; flex-direction: column; gap: 4px; }
-.safety-assessment-bar { background: rgba(0,20,40,0.5); border-radius: 4px; border: 1px solid rgba(0,212,255,0.15); padding: 8px 10px; display: flex; flex-direction: column; align-items: center; gap: 4px; }
-.safety-assessment-grade { font-size: 20px; font-weight: bold; padding: 2px 16px; border-radius: 4px; background: rgba(34,197,94,0.25); color: #22c55e; border: 1px solid rgba(34,197,94,0.5); text-align: center; }
-.safety-assessment-texts { display: flex; flex-direction: column; align-items: center; gap: 1px; }
-.safety-assessment-text { color: #7bbef6; font-size: 11px; }
-.safety-assessment-tags { display: flex; flex-wrap: wrap; gap: 3px; justify-content: center; }
-.assess-tag { font-size: 10px; font-weight: bold; padding: 2px 8px; border-radius: 2px; }
-.assess-tag.good { background: rgba(34,197,94,0.15); color: #22c55e; border: 1px solid rgba(34,197,94,0.3); }
-.monitor-table { width: 100%; font-size: 11px; }
-.monitor-table-header { display: flex; padding: 4px 6px; background: rgba(0,212,255,0.1); border-bottom: 1px solid rgba(0,212,255,0.2); color: #7bbef6; font-weight: bold; }
-.monitor-table-body { max-height: 260px; overflow-y: auto; }
-.monitor-table-body::-webkit-scrollbar { width: 3px; }
-.monitor-table-body::-webkit-scrollbar-track { background: rgba(0,20,40,0.5); }
-.monitor-table-body::-webkit-scrollbar-thumb { background: rgba(0,212,255,0.3); border-radius: 2px; }
-.monitor-row { display: flex; padding: 4px 6px; border-bottom: 1px solid rgba(0,212,255,0.08); align-items: flex-start; color: #e0fcff; }
-.monitor-row:last-child { border-bottom: none; }
-.monitor-row:hover { background: rgba(0,212,255,0.05); }
-.col-name { flex: 2.5; } .col-value { flex: 1.5; text-align: center; } .col-status { flex: 1.2; text-align: center; } .col-trend { flex: 0.8; text-align: center; }
-.status-tag { font-size: var(--fs-status); font-weight: bold; padding: 1px 6px; border-radius: 2px; display: inline-block; }
-.status-tag.normal { background: rgba(34,197,94,0.2); color: #22c55e; }
-.status-tag.warning { background: rgba(255,217,61,0.2); color: #ffd93d; }
-.status-tag.danger { background: rgba(239,68,68,0.2); color: #ff6b6b; }
-.trend-icon { font-size: 12px; font-weight: bold; }
-.trend-icon.up { color: #f97316; } .trend-icon.down { color: #22c55e; } .trend-icon.steady { color: #7bbef6; }
+.device-stats-row { display: flex; gap: 4px; align-items: center; }
+.safety-eval-section { background: rgba(0,20,40,0.5); border-radius: 4px; border: 1px solid rgba(0,212,255,0.12); padding: 8px 10px; margin-bottom: 6px; min-height: 120px; display: flex; flex-direction: column; justify-content: space-between; }
+.safety-eval-top { display: flex; justify-content: space-between; align-items: center; height: 30px; padding: 0 10px; border-radius: 3px; margin-bottom: 6px; background: #22c55e; }
+.safety-eval-section-title { color: #ffffff; font-size: 15px; }
+.safety-eval-grade { color: #ffffff; font-size: 16px; font-weight: bold; }
+.safety-eval-sub-row { display: flex; gap: 6px; margin-bottom: 6px; }
+.safety-eval-sub-card { flex: 1; display: flex; justify-content: space-between; align-items: center; padding: 4px 8px; background: rgba(0,20,40,0.4); border-radius: 3px; }
+.eval-sub-label { color: #7bbef6; font-size: 13px; }
+.eval-sub-value { color: #e0fcff; font-size: 13px; font-weight: bold; }
+.eval-sub-value.status-ok { color: #22c55e; }
+.safety-eval-mid { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; margin-bottom: 6px; }
+.safety-eval-item { display: flex; justify-content: space-between; align-items: center; padding: 4px 8px; background: rgba(0,20,40,0.4); border-radius: 3px; }
+.eval-item-name { color: #e0fcff; font-size: 11px; }
+.eval-item-tag { font-size: 10px; font-weight: bold; padding: 1px 8px; border-radius: 2px; }
+.eval-item-tag.tag-ok { color: #22c55e; background: rgba(34,197,94,0.15); }
+.eval-item-tag.tag-warn { color: #ffd93d; background: rgba(255,217,61,0.15); }
+.safety-eval-bot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
+.safety-eval-stat { display: flex; align-items: center; gap: 4px; color: #e0fcff; font-size: 10px; }
+.eval-stat-dot { width: 6px; height: 6px; border-radius: 50%; }
+.dot-ok { background: #22c55e; } .dot-warn { background: #ffd93d; } .dot-err { background: #ef4444; }
+.safety-eval-time { margin-left: auto; color: #7bbef6; font-size: 10px; white-space: nowrap; }
+.device-stat-card { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; border-radius: 4px; padding: 8px 4px; }
+.device-stat-percent { color: #e0fcff; font-size: 20px; font-weight: bold; }
+.device-stat-icon { display: flex; align-items: center; justify-content: center; height: 36px; }
+.stat-icon-img { height: auto; width: auto; max-height: none; }
+.device-stat-label { color: #ffffff; font-size: 13px; white-space: nowrap; }
+.device-gauge-section { flex-shrink: 0; display: flex; flex-direction: column; align-items: center; margin: 0 2px; }
+.tree-search-bar { display: flex; align-items: center; gap: 6px; padding: 4px 8px; background: rgba(0,20,40,0.5); border: 1px solid rgba(0,212,255,0.15); border-radius: 4px; margin-bottom: 4px; }
+.tree-search-icon { flex-shrink: 0; }
+.tree-search-input { flex: 1; background: none; border: none; outline: none; color: #e0fcff; font-size: 12px; line-height: 26px; }
+.tree-search-input::placeholder { color: #7bbef6; }
+.tree-container { font-size: 13px; max-height: 200px; overflow-y: auto; }
+.tree-container::-webkit-scrollbar { width: 3px; }
+.tree-container::-webkit-scrollbar-track { background: rgba(0,20,40,0.5); }
+.tree-container::-webkit-scrollbar-thumb { background: rgba(0,212,255,0.3); border-radius: 2px; }
+.tree-node-row { display: flex; align-items: center; gap: 4px; padding: 4px 6px; cursor: pointer; border-radius: 2px; transition: background 0.15s; }
+.tree-node-row:hover { background: rgba(0,212,255,0.06); }
+.tree-arrow { flex-shrink: 0; transition: transform 0.2s; }
+.tree-arrow.expanded { transform: rotate(90deg); }
+.tree-dot { flex-shrink: 0; width: 5px; height: 5px; border-radius: 50%; background: #7bbef6; margin-left: 3px; margin-right: 3px; }
+.tree-node-label { color: #ffffff; flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.tree-node-bold { color: #ffffff; font-weight: bold; }
+.tree-node-count { color: #ffffff; font-size: 12px; }
+.tree-node-actions { display: flex; gap: 4px; opacity: 0; transition: opacity 0.15s; }
+.tree-node-row:hover .tree-node-actions { opacity: 1; }
+.tree-action-icon { cursor: pointer; flex-shrink: 0; }
+.tree-action-icon:hover { opacity: 0.8; }
+.tree-children { padding-left: 16px; }
+.tree-grandchildren { padding-left: 24px; }
+.tree-locations { padding-left: 36px; }
+.tree-points { padding-left: 48px; }
+.tree-devices { padding-left: 60px; }
+.tall-card { height: calc(100% + 10px); min-height: 440px; }
+.monitor-divider { height: 1px; background: rgba(0,212,255,0.15); margin: 0 8px; }
+.monitor-subtitle { color: #e0fcff; font-size: var(--fs-label); font-weight: bold; padding: 3px 16px 2px 22px; background-image: url("/src/assets/images/小标题.png"); background-size: 100% 100%; background-position: center; background-repeat: no-repeat; height: 24px; display: inline-flex; align-items: center; margin-bottom: 2px; }
+.monitor-summary-row { display: flex; gap: 6px; margin-bottom: 4px; }
+.monitor-summary-card { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; background: rgba(0,20,40,0.5); border-radius: 4px; border: 1px solid rgba(0,212,255,0.12); padding: 8px 4px; cursor: pointer; transition: all 0.2s; }
+.monitor-summary-card:hover { border-color: rgba(0,212,255,0.35); background: rgba(0,30,60,0.6); }
+.monitor-summary-card.active { border-color: rgba(0,212,255,0.6); background: rgba(0,40,80,0.7); box-shadow: 0 0 8px rgba(0,212,255,0.2); }
+.summary-pie-area { background: rgba(0,20,40,0.4); border-radius: 4px; border: 1px solid rgba(0,212,255,0.1); padding: 2px; margin-bottom: 2px; }
+.progress-ring-bg { fill: none; stroke: rgba(255,255,255,0.08); stroke-width: 3; }
+.progress-ring-fill { fill: none; stroke-width: 3; stroke-linecap: round; transform: rotate(-90deg); transform-origin: 24px 24px; }
+.monitor-summary-name { color: #7bbef6; font-size: 10px; }
+.monitor-summary-count { font-size: 18px; font-weight: bold; }
 canvas { display: block; margin: 0 auto; }
 </style>

+ 1 - 5
src/views/HomeView.vue

@@ -2,7 +2,7 @@
   <div class="dashboard">
     <div class="background-image"></div>
     <div class="bottom-bg" v-if="!showMap"></div>
-    <div class="top-title" :class="{ 'title-alt': activeTab === '水文四预' }"></div>
+    <div class="top-title"></div>
     <TopNav :activeButton="navButtonMap[activeTab]" @navigate="handleNav" />
     
     <!-- 全局地图 -->
@@ -191,10 +191,6 @@ export default {
   z-index: 4;
 }
 
-.top-title.title-alt {
-  background-image: url('/src/assets/images/顶部大标题1.png');
-}
-
 /* 地图容器样式 */
 .map-container {
   position: absolute;