Просмотр исходного кода

绘制尾流页面及健康评估页面、页面主题新增设置

liujiejie 2 месяцев назад
Родитель
Сommit
4b8203158c
48 измененных файлов с 6011 добавлено и 1041 удалено
  1. 12 0
      src/components/HeaderCom.vue
  2. 11 0
      src/components/selecttree.vue
  3. 1 1
      src/icons/svg/WindPower2.svg
  4. 6 0
      src/main.js
  5. 4 4
      src/styles/base.css
  6. 2 2
      src/styles/global.scss
  7. 2 1
      src/styles/index.scss
  8. 471 0
      src/styles/legacy-theme.scss
  9. 128 13
      src/styles/tech-theme.scss
  10. 11 2
      src/themes/blue.scss
  11. 12 2
      src/themes/dark.scss
  12. 12 2
      src/themes/green.scss
  13. 13 4
      src/themes/light.scss
  14. 29 0
      src/themes/tech-blue.scss
  15. 223 0
      src/views/health/components/health/HealthHeader.vue
  16. 417 0
      src/views/health/components/health/HealthRankingPanel.vue
  17. 196 0
      src/views/health/components/health/HealthScorePanel.vue
  18. 167 0
      src/views/health/components/health/HealthSubsystemPanel.vue
  19. 327 0
      src/views/health/components/health/HealthUnitCard.vue
  20. 565 0
      src/views/health/components/health/HealthUnitDetailDrawer.vue
  21. 132 0
      src/views/health/components/health/dashboardData.js
  22. 132 0
      src/views/health/components/health/health-dashboard.scss
  23. 150 0
      src/views/health/components/health/unitDetailData.js
  24. 93 744
      src/views/health/index.vue
  25. 792 0
      src/views/health/indexOld.vue
  26. 24 17
      src/views/home/Index.vue
  27. 111 10
      src/views/home/components/MenuDt.vue
  28. 43 115
      src/views/overview/components/analysis_information/index.vue
  29. 2 3
      src/views/overview/components/filterChart/index.vue
  30. 23 42
      src/views/overview/index.vue
  31. 1 0
      src/views/performance/assetssMag.vue
  32. 13 34
      src/views/performance/components/analysisEvent.vue
  33. 2 2
      src/views/performance/components/custonAsCom/dragChart/components/chartConfig/form/chartTitle.vue
  34. 7 7
      src/views/performance/components/custonAsCom/dragChart/components/chartsAttributes.vue
  35. 22 7
      src/views/performance/components/custonAsCom/dragChart/components/chartsContent.vue
  36. 6 6
      src/views/performance/components/custonAsCom/dragChart/components/chartsData.vue
  37. 13 13
      src/views/performance/components/custonAsCom/dragChart/index.vue
  38. 10 10
      src/views/performance/customAnalysis.vue
  39. 145 0
      src/views/windResourceWake/components/WrwFilterBar.vue
  40. 110 0
      src/views/windResourceWake/components/WrwOverviewTab.vue
  41. 83 0
      src/views/windResourceWake/components/WrwWakeTab.vue
  42. 142 0
      src/views/windResourceWake/components/WrwWakeTable.vue
  43. 131 0
      src/views/windResourceWake/data/mockData.js
  44. BIN
      src/views/windResourceWake/images/image(3).png
  45. 166 0
      src/views/windResourceWake/index.vue
  46. 455 0
      src/views/windResourceWake/styles/windResourceWake.scss
  47. 320 0
      src/views/windResourceWake/utils/overviewChartOptions.js
  48. 274 0
      src/views/windResourceWake/utils/wakeChartOptions.js

+ 12 - 0
src/components/HeaderCom.vue

@@ -5,6 +5,7 @@
       <h2 class="conversion">中国大唐集团公司</h2> -->
     </div>
     <div class="personalCenter">
+      <div id="header-page-tools" class="header-page-tools"></div>
       <!-- <p class="current-time">{{ currentDate }}</p> -->
       <el-popover
         placement="bottom"
@@ -224,6 +225,17 @@ export default {
     align-items: center;
     color: var(--text-cyan, #52c8f2);
 
+    .header-page-tools {
+      display: flex;
+      align-items: center;
+      margin-right: 16px;
+
+      &:empty {
+        display: none;
+        margin-right: 0;
+      }
+    }
+
     .current-time {
       font-size: 12px;
       padding-right: 10px;

+ 11 - 0
src/components/selecttree.vue

@@ -6,6 +6,7 @@
       v-model="newVal"
       :placeholder="placeholder"
       :filter-method="filterTree"
+      :popper-class="popperClassName"
       @change="$forceUpdate()"
       @clear="clear"
       popper-append-to-body
@@ -56,6 +57,10 @@ export default {
       type: String,
       default: "", // 可选:'medium' | 'small' | 'mini'
     },
+    popperClass: {
+      type: String,
+      default: "",
+    },
     defaultParentProps: {
       type: Object,
       default: () => {
@@ -123,6 +128,12 @@ export default {
       this.$options.components
     ).ElSelect.options.methods.handleFocus = () => {};
   },
+  computed: {
+    popperClassName() {
+      const base = "selecttree-popper";
+      return this.popperClass ? `${base} ${this.popperClass}` : base;
+    },
+  },
   mounted() {},
   methods: {
     //节点选择事件

+ 1 - 1
src/icons/svg/WindPower2.svg

@@ -1 +1 @@
-<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1736736328501" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2127" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M1011.26 691.3L726.42 539.69l-71.33 0.48-43.32-14.49c-12 21.34-34.76 39-71.94 41.78l468.54 164.7 10.88-11.5c9.16-9.72 5.83-22-8-29.43v0.06zM37.27 706.52l315.62-100L395.2 558.41l39.82-20.35c-13.15-20.87-16.39-46.74 3.3-73.31L0 680.06l4.45 14.2c3.78 12 17.47 17.09 32.88 12.3h-0.06zM530.2 18.35l-57 285.76 28 55 0.76 40.09c28.13 1.07 56.38 11.83 73.73 39.53l0.62-433.89-16.86-4C545.2-2.5 533.02 4.8 530.15 18.37h0.07zM512.2 549.87a59.71 59.71 0 0 0 29.45-111.19 58.34 58.34 0 0 0-58.88 0A59.71 59.71 0 0 0 512.2 549.87zM553.33 617.55L492.67 572a80.82 80.82 0 0 1-17-11l-30 463 131.17-0.81-23.51-405.64z" fill="#707070" p-id="2128"></path></svg>
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1736736328501" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2127" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M1011.26 691.3L726.42 539.69l-71.33 0.48-43.32-14.49c-12 21.34-34.76 39-71.94 41.78l468.54 164.7 10.88-11.5c9.16-9.72 5.83-22-8-29.43v0.06zM37.27 706.52l315.62-100L395.2 558.41l39.82-20.35c-13.15-20.87-16.39-46.74 3.3-73.31L0 680.06l4.45 14.2c3.78 12 17.47 17.09 32.88 12.3h-0.06zM530.2 18.35l-57 285.76 28 55 0.76 40.09c28.13 1.07 56.38 11.83 73.73 39.53l0.62-433.89-16.86-4C545.2-2.5 533.02 4.8 530.15 18.37h0.07zM512.2 549.87a59.71 59.71 0 0 0 29.45-111.19 58.34 58.34 0 0 0-58.88 0A59.71 59.71 0 0 0 512.2 549.87zM553.33 617.55L492.67 572a80.82 80.82 0 0 1-17-11l-30 463 131.17-0.81-23.51-405.64z" fill="currentColor" p-id="2128"></path></svg>

+ 6 - 0
src/main.js

@@ -139,6 +139,11 @@ window.addEventListener("beforeunload", () => {
   // 调用清空 Vuex 仓库的 Action
   store.commit("dragChart/clearchart");
 });
+document.documentElement.setAttribute(
+  "data-theme",
+  store.state.themes.theme || process.env.VUE_APP_THEM || "green"
+);
+
 new Vue({
   router,
   store,
@@ -156,6 +161,7 @@ new Vue({
   methods: {
     async applyTheme(theme, oldTheme) {
       const themeFile = theme || this.$store.state.themes.theme;
+      document.documentElement.setAttribute("data-theme", themeFile);
       // 动态加载新的主题样式
       try {
         // 动态导入 SCSS 文件

+ 4 - 4
src/styles/base.css

@@ -221,8 +221,8 @@ input {
 }
 
 body {
-  background: var(--content-bg, #000510);
-  color: var(--text-cyan, #52c8f2);
+  background: var(--content-bg, #f5f7fa);
+  color: var(--text-cyan, #303133);
   font-family: "HYQiHei", "Helvetica Neue", Helvetica, Arial, "PingFang SC",
     "Hiragino Sans GB", "Heiti SC", "MicrosoftYaHei", "WenQuanYi Micro Hei",
     sans-serif;
@@ -253,10 +253,10 @@ body {
 .global-variable {
   margin: 18px;
   padding: 16px;
-  background: var(--panel-bg, #000c17);
+  background: var(--panel-bg, #ffffff);
   /* border: 1px solid var(--panel-border, rgba(0, 170, 255, 0.45)); */
   min-height: 88vh;
-  color: var(--text-cyan, #52c8f2);
+  color: var(--text-cyan, #303133);
 }
 
 /* 列表点击 */

+ 2 - 2
src/styles/global.scss

@@ -59,12 +59,12 @@
 }
 
 .el-table th.el-table__cell {
-  background: var(--table-header-bg, #001a33) !important;
+  background: var(--table-header-bg, #9c9a3f) !important;
   color: var(--table-header-text, #ffffff) !important;
 }
 
 .el-table td.el-table__cell {
-  color: var(--table-cell-text, #d1d1d1) !important;
+  color: var(--table-cell-text, #606266) !important;
 }
 
 .el-pager li.active {

+ 2 - 1
src/styles/index.scss

@@ -3,9 +3,10 @@
 @import "./tailwind.css";
 @import "./base.css";
 @import "./tech-theme.scss";
+@import "./legacy-theme.scss";
 
 body {
-  background-color: var(--content-bg, #000510);
+  background-color: var(--content-bg, #f5f7fa);
 }
 
 img,

+ 471 - 0
src/styles/legacy-theme.scss

@@ -0,0 +1,471 @@
+/* 非科技蓝主题:恢复老版业务页样式(浅底 + 主题色表头/按钮) */
+html:not([data-theme="tech-blue"]) {
+  /* 自定义分析 / 图表拖拽编辑 */
+  --custom-analysis-bg: #f5f7fa;
+  --custom-analysis-text: #303133;
+  --custom-analysis-sidebar-bg: #f5f7fa;
+  --custom-analysis-border: #ebeef5;
+  --custom-analysis-tab-text: #606266;
+  --custom-analysis-tab-active-text: var(--primary-color, #82780c);
+  --custom-analysis-tab-active-bg: rgba(0, 0, 0, 0.04);
+  --custom-analysis-content-bg: #f5f7fa;
+  --drag-chart-page-bg: #f0f2f5;
+  --drag-chart-canvas-bg: #ffffff;
+  --drag-chart-canvas-border: #dcdfe6;
+  --drag-chart-panel-bg: #ffffff;
+  --drag-chart-panel-border: #ebeef5;
+  --drag-chart-text: #303133;
+  --drag-chart-label: #606266;
+  --drag-chart-muted: #909399;
+  --drag-chart-header-bg: var(--primary-color, #82780c);
+  --drag-chart-header-text: #ffffff;
+  --drag-chart-drawer-collapsed-bg: #f5f7fa;
+  --drag-chart-drawer-collapsed-text: #606266;
+  --drag-chart-drawer-collapsed-border: #dcdfe6;
+  --drag-chart-ruler-color: #909399;
+  --drag-chart-ruler-bg: #f5f7fa;
+  --drag-chart-corner-color: #606266;
+  --drag-chart-tab-text: #606266;
+  --drag-chart-tab-active-text: var(--primary-color, #82780c);
+  --drag-chart-divider: #ebeef5;
+  --drag-chart-tree-hover: #f5f7fa;
+
+  body,
+  #app {
+    background-color: var(--content-bg, #f5f7fa) !important;
+    color: var(--text-cyan, #303133);
+  }
+
+  .global-variable {
+    margin: 18px;
+    padding: 16px;
+    background: var(--panel-bg, #ffffff) !important;
+    border: 1px solid #ebeef5;
+    border-radius: 4px;
+    box-shadow: none;
+    min-height: calc(100vh - 120px);
+    color: var(--text-cyan, #303133);
+  }
+
+  .el-form-item__label {
+    color: #606266 !important;
+  }
+
+  .el-input__inner,
+  .el-textarea__inner,
+  .el-select .el-input__inner {
+    background-color: #ffffff !important;
+    border-color: #dcdfe6 !important;
+    color: #606266 !important;
+  }
+
+  .el-input__inner::placeholder,
+  .el-textarea__inner::placeholder {
+    color: #c0c4cc !important;
+  }
+
+  .el-button--primary {
+    background-color: var(--primary-color, #82780c) !important;
+    border-color: var(--primary-color, #82780c) !important;
+    color: #ffffff !important;
+  }
+
+  .el-button--primary:hover,
+  .el-button--primary:focus {
+    opacity: 0.9;
+    color: #ffffff !important;
+  }
+
+  .el-button--default {
+    background: #ffffff !important;
+    border-color: #dcdfe6 !important;
+    color: #606266 !important;
+  }
+
+  .el-table {
+    background-color: #ffffff !important;
+    color: #606266;
+  }
+
+  .el-table::before,
+  .el-table::after {
+    background-color: #ebeef5 !important;
+  }
+
+  .el-table th.el-table__cell {
+    background: var(--table-header-bg, #9c9a3f) !important;
+    color: var(--table-header-text, #ffffff) !important;
+    border-color: #ebeef5 !important;
+  }
+
+  .el-table th.el-table__cell .cell {
+    color: var(--table-header-text, #ffffff) !important;
+  }
+
+  .el-table td.el-table__cell {
+    background-color: #ffffff !important;
+    color: var(--table-cell-text, #606266) !important;
+    border-color: #ebeef5 !important;
+  }
+
+  .el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell {
+    background-color: #fafafa !important;
+  }
+
+  .el-table tbody tr:hover > td.el-table__cell {
+    background-color: #f5f7fa !important;
+    color: var(--table-cell-text, #606266) !important;
+    box-shadow: none !important;
+  }
+
+  .el-table__empty-text {
+    color: #909399 !important;
+  }
+
+  .headerBox {
+    background: var(--header-bg, #ffffff) !important;
+    border-bottom: none !important;
+    box-shadow: none !important;
+  }
+
+  .headerBox,
+  .headerBox .header,
+  .headerBox .personalCenter {
+    color: var(--header-text-color, var(--text-color, #303133)) !important;
+  }
+
+  /* 分析概览页:老样式 Tab / 树 / 工具条 */
+  .overview-page {
+    color: #303133;
+
+    .left {
+      border-right-color: #ebeef5 !important;
+    }
+
+    .filter-tree .el-tree-node__content {
+      color: #606266;
+    }
+
+    .el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content,
+    .el-tree-node.is-current > .el-tree-node__content {
+      background-color: var(--primary-color, #82780c) !important;
+      color: #ffffff !important;
+      border-radius: 4px;
+    }
+
+    .el-tree-node.is-current > .el-tree-node__content .el-tree-node__label {
+      color: #ffffff !important;
+    }
+
+    .overview-top-menu.el-menu--horizontal {
+      border-bottom: 1px solid #ebeef5 !important;
+
+      > .el-menu-item,
+      > .el-submenu .el-submenu__title {
+        color: #606266 !important;
+        background-color: transparent !important;
+        border-bottom: 2px solid transparent !important;
+      }
+
+      > .el-menu-item.is-active,
+      > .el-submenu.is-active > .el-submenu__title {
+        color: var(--primary-color, #82780c) !important;
+        background-color: transparent !important;
+        border-bottom-color: var(--primary-color, #82780c) !important;
+      }
+
+      > .el-menu-item:hover,
+      > .el-menu-item:focus,
+      > .el-submenu .el-submenu__title:hover,
+      > .el-submenu .el-submenu__title:focus {
+        color: var(--primary-color, #82780c) !important;
+        background-color: rgba(0, 0, 0, 0.03) !important;
+      }
+    }
+
+    .filterChart h3 {
+      color: #303133 !important;
+    }
+
+    .titleCharts {
+      color: #303133 !important;
+    }
+
+    .el-empty__description,
+    .el-empty__description p {
+      color: #909399 !important;
+    }
+
+    .tousutiwen_box,
+    .tousutiwen_box1 {
+      background: #ffffff;
+      border: 1px solid #dcdfe6;
+      color: #606266;
+      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+    }
+
+    /* 分析概述(analysis_information) */
+    .analysis-info-card {
+      color: #303133 !important;
+      background: transparent !important;
+
+      .box-header,
+      .box-header-min,
+      .box-min,
+      .left .box-content-min {
+        color: #303133 !important;
+
+        h4,
+        h6 {
+          color: #303133 !important;
+        }
+      }
+
+      .box-header {
+        border-bottom-color: #ebeef5 !important;
+      }
+
+      .content {
+        border-bottom-color: #ebeef5 !important;
+      }
+
+      .headerMessage {
+        color: var(--primary-color, #82780c) !important;
+      }
+
+      .el-descriptions,
+      .el-descriptions__body,
+      .el-descriptions-item__label,
+      .el-descriptions-item__content {
+        color: #606266 !important;
+        background-color: transparent !important;
+      }
+
+      .itemAnalysisCompletionUnit {
+        border: 1px solid #dcdfe6 !important;
+        background: #ffffff !important;
+        color: #606266 !important;
+
+        span {
+          color: #606266 !important;
+        }
+
+        &:hover {
+          background: #f5f7fa !important;
+          border-color: #c0c4cc !important;
+        }
+      }
+
+      .completeAssetssType .el-tag.el-tag--warning {
+        background-color: var(--bgopacity-color, rgba(130, 120, 12, 0.12)) !important;
+        border-color: rgba(130, 120, 12, 0.35) !important;
+        color: var(--primary-color, #82780c) !important;
+      }
+
+      .analysisTypes h6 {
+        color: #303133 !important;
+        font-weight: 600 !important;
+      }
+
+      .el-empty__description,
+      .el-empty__description p {
+        color: #909399 !important;
+      }
+    }
+  }
+
+  /* 分析事件页(editAssets / analysisEvent) */
+  .analysis-event-page {
+    color: #303133;
+
+    .el-form-item__label {
+      color: #606266 !important;
+    }
+
+    .el-form-item__content {
+      color: #303133 !important;
+    }
+
+    .el-collapse-item__header {
+      background: var(--table-header-bg, #9c9a3f) !important;
+      color: #ffffff !important;
+      border-color: #ebeef5 !important;
+    }
+
+    .el-collapse-item__wrap {
+      background: #ffffff !important;
+      border-color: #ebeef5 !important;
+    }
+
+    .el-collapse-item__content {
+      color: #303133 !important;
+    }
+
+    .setting {
+      color: var(--primary-color, #82780c) !important;
+    }
+
+    .box-card {
+      border-color: #ebeef5 !important;
+      background: #ffffff !important;
+    }
+
+    .filedView {
+      background: var(--table-header-bg, #9c9a3f) !important;
+      color: #ffffff !important;
+    }
+
+    .box-card .el-card__body {
+      background: #ffffff !important;
+      color: #303133 !important;
+    }
+  }
+
+  .analysis-event-drawer {
+    .el-drawer__body {
+      background: #ffffff !important;
+      color: #303133 !important;
+    }
+
+    .drawer-section-title,
+    .drawerLeft,
+    .drawerRight,
+    .drawerRight ul,
+    .drawerRight li {
+      color: #303133 !important;
+    }
+
+    .el-checkbox__label {
+      color: #606266 !important;
+    }
+  }
+
+  .overview-menu-popup {
+    .el-menu--popup {
+      background: #ffffff !important;
+      border: 1px solid #ebeef5 !important;
+      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+    }
+
+    .el-menu-item {
+      color: #606266 !important;
+      background: transparent !important;
+
+      span {
+        color: #606266 !important;
+      }
+    }
+
+    .el-menu-item:hover,
+    .el-menu-item:focus {
+      background-color: #f5f7fa !important;
+      color: var(--primary-color, #82780c) !important;
+    }
+
+    .el-menu-item.is-active {
+      color: var(--primary-color, #82780c) !important;
+      background-color: rgba(130, 120, 12, 0.08) !important;
+    }
+  }
+
+  .el-pagination {
+    color: #606266;
+  }
+
+  .el-pager li.active {
+    background-color: var(--primary-color, #82780c) !important;
+    border-color: var(--primary-color, #82780c) !important;
+    color: #ffffff !important;
+  }
+
+  /* MessageBox / $confirm:确定按钮需主题色底 + 白字,避免白底白字看不见 */
+  .el-message-box {
+    background-color: #ffffff !important;
+    border: 1px solid #ebeef5 !important;
+
+    .el-message-box__title,
+    .el-message-box__content {
+      color: #606266 !important;
+    }
+
+    .el-message-box__btns {
+      .el-button--default {
+        background-color: #ffffff !important;
+        background-image: none !important;
+        border-color: #dcdfe6 !important;
+        color: #606266 !important;
+        text-shadow: none !important;
+        box-shadow: none !important;
+      }
+
+      .el-button--primary {
+        background-color: var(--primary-color, #82780c) !important;
+        background-image: none !important;
+        border-color: var(--primary-color, #82780c) !important;
+        color: #ffffff !important;
+        text-shadow: none !important;
+        box-shadow: none !important;
+
+        &:hover,
+        &:focus,
+        &:active {
+          background-color: var(--primary-color, #82780c) !important;
+          border-color: var(--primary-color, #82780c) !important;
+          color: #ffffff !important;
+          opacity: 0.88;
+        }
+      }
+    }
+  }
+
+  .el-dialog__footer {
+    .el-button--default {
+      background-color: #ffffff !important;
+      background-image: none !important;
+      border-color: #dcdfe6 !important;
+      color: #606266 !important;
+    }
+
+    .el-button--primary {
+      background-color: var(--primary-color, #82780c) !important;
+      background-image: none !important;
+      border-color: var(--primary-color, #82780c) !important;
+      color: #ffffff !important;
+
+      &:hover,
+      &:focus,
+      &:active {
+        background-color: var(--primary-color, #82780c) !important;
+        border-color: var(--primary-color, #82780c) !important;
+        color: #ffffff !important;
+        opacity: 0.88;
+      }
+    }
+  }
+
+  /* 自定义分析 / 图表编辑:浅色老样式 */
+  .custom-analysis-page,
+  .drag-chart-page {
+    .el-form-item__label {
+      color: var(--drag-chart-label, #606266) !important;
+    }
+
+    .el-form-item__content {
+      color: var(--drag-chart-text, #303133) !important;
+    }
+
+    .el-empty__description,
+    .el-empty__description p {
+      color: var(--drag-chart-muted, #909399) !important;
+    }
+
+    .el-select .el-input__inner,
+    .el-input__inner {
+      color: #606266 !important;
+    }
+
+    .el-input__inner::placeholder {
+      color: #c0c4cc !important;
+    }
+  }
+}

+ 128 - 13
src/styles/tech-theme.scss

@@ -1,16 +1,26 @@
-/* 科技蓝全局 UI — 与 themes/tech-blue.scss 变量一致,始终生效 */
-@import "../themes/tech-blue.scss";
+/* 科技蓝全局 UI — 仅在 tech-blue 主题生效 */
+html[data-theme="tech-blue"] {
+  body,
+  #app {
+    background-color: var(--content-bg) !important;
+    color: var(--text-cyan);
+  }
 
-html,
-body,
-#app {
-  background-color: var(--content-bg) !important;
-  color: var(--text-cyan);
-}
+  body {
+    color: var(--text-cyan);
+  }
 
-body {
-  color: var(--text-cyan);
-}
+  .headerBox {
+    background: var(--header-bg, #001529) !important;
+    border-bottom: 1px solid var(--panel-border, rgba(0, 170, 255, 0.35)) !important;
+    box-shadow: none !important;
+    color: var(--text-cyan, #b7dff5) !important;
+
+    .header,
+    .personalCenter {
+      color: var(--text-cyan, #b7dff5) !important;
+    }
+  }
 
 a {
   color: var(--primary-color);
@@ -629,6 +639,20 @@ a {
   color: var(--text-cyan) !important;
 }
 
+.el-message-box__btns {
+  .el-button--primary:not(.el-button--text) {
+    background-color: var(--btn-primary-bg, #177ddc) !important;
+    border-color: var(--btn-primary-border, #177ddc) !important;
+    color: #ffffff !important;
+  }
+
+  .el-button--default:not(.el-button--text) {
+    background-color: var(--btn-outline-bg, rgba(0, 16, 32, 0.92)) !important;
+    border: 1px solid var(--btn-outline-border, rgba(0, 120, 200, 0.42)) !important;
+    color: var(--btn-outline-text, #b7dff5) !important;
+  }
+}
+
 .el-dropdown-menu {
   background-color: var(--panel-bg) !important;
   border: 1px solid var(--panel-border) !important;
@@ -1246,6 +1270,64 @@ a {
     font-weight: 600;
   }
 
+  .analysis-info-card {
+    color: #ffffff !important;
+
+    .box-header,
+    .box-header-min,
+    .box-min,
+    .left .box-content-min {
+      color: #ffffff !important;
+
+      h4,
+      h6 {
+        color: #ffffff !important;
+      }
+    }
+
+    .box-header,
+    .content {
+      border-bottom-color: var(--panel-border, rgba(0, 170, 255, 0.25)) !important;
+    }
+
+    .headerMessage {
+      color: #ffffff !important;
+    }
+
+    .el-descriptions-item__label {
+      color: var(--text-cyan, #b7dff5) !important;
+    }
+
+    .el-descriptions-item__content {
+      color: #ffffff !important;
+    }
+
+    .itemAnalysisCompletionUnit {
+      border: 1px solid var(--panel-border, rgba(0, 170, 255, 0.35)) !important;
+      background: rgba(0, 16, 32, 0.55) !important;
+      color: #ffffff !important;
+
+      span {
+        color: #ffffff !important;
+      }
+
+      &:hover {
+        background: var(--bgopacity-color, rgba(0, 170, 255, 0.12)) !important;
+        border-color: rgba(0, 170, 255, 0.55) !important;
+      }
+    }
+
+    .completeAssetssType .el-tag.el-tag--warning {
+      background-color: rgba(0, 170, 255, 0.12) !important;
+      border-color: rgba(0, 170, 255, 0.35) !important;
+      color: #ffffff !important;
+    }
+
+    .analysisTypes h6 {
+      color: var(--text-cyan, #b7dff5) !important;
+    }
+  }
+
   .filterChart {
     h3 {
       color: #ffffff;
@@ -1339,18 +1421,26 @@ a {
 .transition-page {
   color: #ffffff;
 
+  .el-form-item__label {
+    color: #b7dff5 !important;
+  }
+
+  .el-form-item__content {
+    color: #ffffff !important;
+  }
+
   .el-collapse {
     border-color: var(--panel-border) !important;
   }
 
   .el-collapse-item__header {
-    background-color: rgba(0, 16, 32, 0.85) !important;
+    background: linear-gradient(90deg, #001529 0%, #002a5c 100%) !important;
     color: #ffffff !important;
     border-bottom-color: var(--panel-border) !important;
   }
 
   .el-collapse-item__wrap {
-    background-color: transparent !important;
+    background: var(--panel-bg, #000c17) !important;
     border-bottom-color: var(--panel-border) !important;
   }
 
@@ -1358,6 +1448,29 @@ a {
     color: #ffffff !important;
   }
 
+  .setting {
+    color: #b7dff5 !important;
+
+    &:hover {
+      color: #ffffff !important;
+    }
+  }
+
+  .box-card {
+    border-color: var(--panel-border) !important;
+    background: transparent !important;
+  }
+
+  .filedView {
+    background: linear-gradient(90deg, #001529 0%, #002a5c 100%) !important;
+    color: #ffffff !important;
+  }
+
+  .box-card .el-card__body {
+    background: var(--panel-bg, #000c17) !important;
+    color: #ffffff !important;
+  }
+
   .el-select .el-input__inner {
     color: #ffffff !important;
   }
@@ -1545,3 +1658,5 @@ a {
     }
   }
 }
+
+}

+ 11 - 2
src/themes/blue.scss

@@ -7,6 +7,15 @@ $bgopacity-color: rgba(0, 187, 212, 0.145);
   --background-color: #{$background-color};
   --text-color: #{$text-color};
   --bgopacity-color: #{$bgopacity-color};
-  --header-bg: #1A3B7F;
-  --content-bg: #1a3c7fb4;
+  --header-bg: #ffffff;
+  --header-text-color: #303133;
+  --content-bg: #f5f7fa;
+  --text-cyan: #303133;
+  --panel-bg: #ffffff;
+  --input-bg: #ffffff;
+  --input-border: #dcdfe6;
+  --table-header-bg: #2f5aa0;
+  --table-header-text: #ffffff;
+  --table-row-bg: #ffffff;
+  --table-cell-text: #606266;
 }

+ 12 - 2
src/themes/dark.scss

@@ -7,6 +7,16 @@ $bgopacity-color: #607d8b31;
   --background-color: #{$background-color};
   --text-color: #{$text-color};
   --bgopacity-color: #{$bgopacity-color};
-  --header-bg: #607d8b;
-  --content-bg: #607d8bc2;
+  --header-bg: #ffffff;
+  --header-text-color: #303133;
+  --content-bg: #f5f7fa;
+  --text-cyan: #303133;
+  --panel-bg: #ffffff;
+  --input-bg: #ffffff;
+  --input-border: #dcdfe6;
+  --table-header-bg: #607d8b;
+  --table-header-text: #ffffff;
+  --table-row-bg: #ffffff;
+  --table-cell-text: #606266;
+  --primary-color: #607d8b;
 }

+ 12 - 2
src/themes/green.scss

@@ -7,6 +7,16 @@ $bgopacity-color: #00968730;
   --background-color: #{$background-color};
   --text-color: #{$text-color};
   --bgopacity-color: #{$bgopacity-color};
-  --header-bg: #111627;
-  --content-bg: #090a0f;
+  --header-bg: #ffffff;
+  --header-text-color: #303133;
+  --content-bg: #f5f7fa;
+  --text-cyan: #303133;
+  --panel-bg: #ffffff;
+  --input-bg: #ffffff;
+  --input-border: #dcdfe6;
+  --table-header-bg: #2f8f62;
+  --table-header-text: #ffffff;
+  --table-row-bg: #ffffff;
+  --table-cell-text: #606266;
+  --primary-color: #008080;
 }

+ 13 - 4
src/themes/light.scss

@@ -1,5 +1,5 @@
-$primary-color: #82780ccf;
-$background-color: #82780ccf;
+$primary-color: #82780c;
+$background-color: #82780c;
 $text-color: #ffffff;
 $bgopacity-color: #82780c27;
 
@@ -8,6 +8,15 @@ $bgopacity-color: #82780c27;
   --background-color: #{$background-color};
   --bgopacity-color: #{$bgopacity-color};
   --text-color: #{$text-color};
-  --header-bg: #82780c;
-  --content-bg: #82780ca5;
+  --header-bg: #ffffff;
+  --header-text-color: #303133;
+  --content-bg: #f5f7fa;
+  --text-cyan: #303133;
+  --panel-bg: #ffffff;
+  --input-bg: #ffffff;
+  --input-border: #dcdfe6;
+  --table-header-bg: #9c9a3f;
+  --table-header-text: #ffffff;
+  --table-row-bg: #ffffff;
+  --table-cell-text: #606266;
 }

+ 29 - 0
src/themes/tech-blue.scss

@@ -45,4 +45,33 @@ $bgopacity-color: rgba(0, 170, 255, 0.12);
   --btn-primary-bg: #177ddc;
   --btn-primary-bg-hover: #1d8cff;
   --btn-primary-border: #177ddc;
+  /* 自定义分析 / 图表拖拽编辑(科技蓝) */
+  --custom-analysis-bg: #000510;
+  --custom-analysis-text: #ffffff;
+  --custom-analysis-sidebar-bg: #000c17;
+  --custom-analysis-border: rgba(0, 170, 255, 0.35);
+  --custom-analysis-tab-text: #b7dff5;
+  --custom-analysis-tab-active-text: #ffffff;
+  --custom-analysis-tab-active-bg: rgba(24, 144, 255, 0.18);
+  --custom-analysis-content-bg: #000510;
+  --drag-chart-page-bg: #000510;
+  --drag-chart-canvas-bg: #0a1628;
+  --drag-chart-canvas-border: rgba(0, 170, 255, 0.35);
+  --drag-chart-panel-bg: #000c17;
+  --drag-chart-panel-border: rgba(0, 170, 255, 0.35);
+  --drag-chart-text: #ffffff;
+  --drag-chart-label: #ffffff;
+  --drag-chart-muted: #b7dff5;
+  --drag-chart-header-bg: linear-gradient(90deg, #001529 0%, #002a5c 100%);
+  --drag-chart-header-text: #ffffff;
+  --drag-chart-drawer-collapsed-bg: linear-gradient(180deg, #001529 0%, #002a5c 100%);
+  --drag-chart-drawer-collapsed-text: #ffffff;
+  --drag-chart-drawer-collapsed-border: rgba(0, 170, 255, 0.35);
+  --drag-chart-ruler-color: #3a6ea5;
+  --drag-chart-ruler-bg: #061020;
+  --drag-chart-corner-color: #b7dff5;
+  --drag-chart-tab-text: #b7dff5;
+  --drag-chart-tab-active-text: #ffffff;
+  --drag-chart-divider: rgba(0, 170, 255, 0.25);
+  --drag-chart-tree-hover: rgba(24, 144, 255, 0.12);
 }

+ 223 - 0
src/views/health/components/health/HealthHeader.vue

@@ -0,0 +1,223 @@
+<template>
+  <div class="health-header__farm" ref="farmRoot">
+    <i class="el-icon-location health-header__farm-icon"></i>
+    <selecttree
+      class="health-header__selecttree"
+      size="small"
+      popper-class="health-header-tree-popper"
+      placeholder="请选择所属公司"
+      :list="parentOpt"
+      type="1"
+      v-model="companyCode"
+      @change="handleSelectChange"
+      :defaultParentProps="defaultParentProps"
+    />
+  </div>
+</template>
+
+<script>
+import selecttree from "@/components/selecttree.vue";
+import { getSysOrganizationAuthTreeByRoleId } from "@/api/ledger.js";
+
+export default {
+  name: "HealthHeader",
+  components: {
+    selecttree,
+  },
+  props: {
+    value: {
+      type: String,
+      default: "",
+    },
+  },
+  data() {
+    return {
+      companyCode: this.value,
+      parentOpt: [],
+      defaultParentProps: {
+        children: "children",
+        label: "companyName",
+        value: "codeNumber",
+      },
+    };
+  },
+  watch: {
+    value(val) {
+      this.companyCode = val;
+    },
+    companyCode(val) {
+      this.$emit("input", val);
+    },
+  },
+  created() {
+    this.GETtree();
+  },
+  mounted() {
+    this.$nextTick(this.mountToHeader);
+  },
+  beforeDestroy() {
+    this.unmountFromHeader();
+  },
+  methods: {
+    mountToHeader() {
+      const target = document.getElementById("header-page-tools");
+      const el = this.$refs.farmRoot;
+      if (!target || !el || target.contains(el)) return;
+
+      this._portalPlaceholder = document.createComment("health-header-anchor");
+      if (el.parentNode) {
+        el.parentNode.insertBefore(this._portalPlaceholder, el);
+      }
+      target.appendChild(el);
+    },
+    unmountFromHeader() {
+      const el = this.$refs.farmRoot;
+      if (
+        this._portalPlaceholder &&
+        this._portalPlaceholder.parentNode &&
+        el
+      ) {
+        this._portalPlaceholder.parentNode.insertBefore(
+          el,
+          this._portalPlaceholder
+        );
+        this._portalPlaceholder.parentNode.removeChild(this._portalPlaceholder);
+      } else if (el && el.parentNode) {
+        el.parentNode.removeChild(el);
+      }
+      this._portalPlaceholder = null;
+    },
+    async GETtree() {
+      try {
+        const res = await getSysOrganizationAuthTreeByRoleId();
+        const processedData = this.processTreeData(res.data || []);
+        this.parentOpt = processedData;
+
+        const defaultdata = (res.data || [])[0];
+        if (defaultdata) {
+          this.$nextTick(() => {
+            this.handleSelectChange(defaultdata);
+          });
+        }
+      } catch (error) {
+        console.error("加载组织树失败:", error);
+      }
+    },
+    processTreeData(treeData) {
+      const processedData = [];
+      const processNode = (node) => {
+        if (node.codeType === "field") {
+          node.companyName = node.fieldName;
+        }
+        if (node.children && node.children.length > 0) {
+          node.children.forEach(processNode);
+        }
+      };
+      treeData.forEach((root) => {
+        processNode(root);
+        processedData.push(root);
+      });
+      return processedData;
+    },
+    handleSelectChange(data) {
+      if (!data || !data.codeNumber) {
+        return;
+      }
+      this.companyCode = data.codeNumber;
+      this.$emit("input", data.codeNumber);
+      this.$emit("change", data);
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.health-header__farm {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 4px 12px 4px 10px;
+  background: rgba(15, 23, 42, 0.85);
+  border-radius: 6px;
+  border: 1px solid rgba(0, 242, 255, 0.25);
+  backdrop-filter: blur(8px);
+  transition: border-color 0.3s ease;
+
+  &:hover {
+    border-color: rgba(6, 182, 212, 0.5);
+  }
+}
+
+.health-header__farm-icon {
+  color: #22d3ee;
+  font-size: 16px;
+  flex-shrink: 0;
+}
+
+.health-header__selecttree {
+  width: 220px;
+
+  ::v-deep .el-input__inner {
+    background: transparent !important;
+    border: none !important;
+    color: #e2e8f0 !important;
+    padding-left: 0 !important;
+    height: 28px;
+    line-height: 28px;
+  }
+
+  ::v-deep .el-input__icon {
+    color: #64748b !important;
+  }
+}
+</style>
+
+<style lang="scss">
+.health-header-tree-popper.selecttree-popper {
+  background: #0d1930 !important;
+  border: 1px solid rgba(0, 242, 255, 0.25) !important;
+
+  .popper__arrow::after {
+    border-bottom-color: #0d1930 !important;
+  }
+
+  .el-select-dropdown__item.hasTree {
+    background: #0d1930 !important;
+
+    &.hover,
+    &:hover,
+    &.selected {
+      background: #0d1930 !important;
+    }
+  }
+
+  .el-tree {
+    background: transparent !important;
+    color: #b7dff5 !important;
+  }
+
+  .el-tree-node__label {
+    color: #b7dff5 !important;
+  }
+
+  .el-tree-node__content:hover {
+    background: rgba(0, 170, 255, 0.15) !important;
+  }
+
+  .el-tree-node__content:hover .el-tree-node__label {
+    color: #ffffff !important;
+  }
+
+  .el-tree-node.is-current > .el-tree-node__content,
+  .el-tree--highlight-current
+    .el-tree-node.is-current
+    > .el-tree-node__content {
+    background: #1890ff !important;
+    color: #ffffff !important;
+  }
+
+  .el-tree-node.is-current > .el-tree-node__content .el-tree-node__label {
+    color: #ffffff !important;
+  }
+}
+</style>

+ 417 - 0
src/views/health/components/health/HealthRankingPanel.vue

@@ -0,0 +1,417 @@
+<template>
+  <section class="health-ranking-panel">
+    <div
+      class="health-ranking-panel__card health-ranking-panel__card--good glass-card"
+    >
+      <h3 class="health-ranking-panel__title health-ranking-panel__title--good">
+        <i class="el-icon-top health-ranking-panel__title-icon"></i>
+        设备运行健康榜 (Top 5)
+      </h3>
+      <div
+        class="health-ranking-panel__viewport health-ranking-panel__viewport--good"
+      >
+        <div
+          class="health-ranking-panel__track"
+          :style="{ transform: `translateY(-${healthyTrackOffset}px)` }"
+        >
+          <div
+            v-for="(item, index) in healthyList"
+            :key="item.name"
+            class="health-ranking-panel__row health-ranking-panel__row--good"
+          >
+            <span
+              class="health-ranking-panel__rank-tag"
+              :class="`health-ranking-panel__rank-tag--good-${index + 1}`"
+            >
+              {{ index + 1 }}
+            </span>
+            <span class="health-ranking-panel__name">{{ item.name }}</span>
+            <span
+              class="health-ranking-panel__score health-ranking-panel__score--good"
+            >
+              {{ item.score }}
+            </span>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div
+      class="health-ranking-panel__card health-ranking-panel__card--risk glass-card neon-border-red"
+    >
+      <h3
+        class="health-ranking-panel__title health-ranking-panel__title--danger"
+      >
+        <i
+          class="el-icon-warning health-ranking-panel__title-icon health-ranking-panel__pulse"
+        ></i>
+        设备异常预警榜 (Top 5)
+      </h3>
+      <div
+        class="health-ranking-panel__viewport health-ranking-panel__viewport--risk"
+      >
+        <div
+          class="health-ranking-panel__track"
+          :style="{ transform: `translateY(-${riskTrackOffset}px)` }"
+        >
+          <div
+            v-for="(item, index) in riskList"
+            :key="item.name"
+            class="health-ranking-panel__row"
+            :class="[
+              item.rowClass === 'strong'
+                ? 'health-ranking-panel__row--danger'
+                : 'health-ranking-panel__row--warning',
+            ]"
+          >
+            <span
+              class="health-ranking-panel__rank-tag"
+              :class="`health-ranking-panel__rank-tag--risk-${index + 1}`"
+            >
+              {{ index + 1 }}
+            </span>
+            <div class="health-ranking-panel__risk-info">
+              <span
+                class="health-ranking-panel__name health-ranking-panel__name--bold"
+              >
+                {{ item.name }}
+              </span>
+            </div>
+            <span
+              class="health-ranking-panel__badge"
+              :class="`health-ranking-panel__badge--${item.scoreClass}`"
+            >
+              {{ item.score }}
+            </span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </section>
+</template>
+
+<script>
+const ROW_HEIGHT = 36;
+const ROW_GAP = 8;
+const ROW_STEP = ROW_HEIGHT + ROW_GAP;
+const SCROLL_INTERVAL = 3000;
+
+export default {
+  name: "HealthRankingPanel",
+  props: {
+    healthyList: {
+      type: Array,
+      default: () => [],
+    },
+    riskList: {
+      type: Array,
+      default: () => [],
+    },
+  },
+  data() {
+    return {
+      healthyScrollIndex: 0,
+      riskScrollIndex: 0,
+      healthyTimer: null,
+      riskTimer: null,
+      healthyVisibleCount: 3,
+      riskVisibleCount: 4,
+    };
+  },
+  computed: {
+    healthyMaxScrollIndex() {
+      return Math.max(0, this.healthyList.length - this.healthyVisibleCount);
+    },
+    riskMaxScrollIndex() {
+      return Math.max(0, this.riskList.length - this.riskVisibleCount);
+    },
+    healthyTrackOffset() {
+      return this.healthyScrollIndex * ROW_STEP;
+    },
+    riskTrackOffset() {
+      return this.riskScrollIndex * ROW_STEP;
+    },
+  },
+  watch: {
+    healthyList: {
+      deep: true,
+      handler() {
+        this.resetHealthyScroll();
+      },
+    },
+    riskList: {
+      deep: true,
+      handler() {
+        this.resetRiskScroll();
+      },
+    },
+  },
+  mounted() {
+    this.startHealthyScroll();
+    this.startRiskScroll();
+  },
+  beforeDestroy() {
+    this.clearScrollTimers();
+  },
+  methods: {
+    clearScrollTimers() {
+      if (this.healthyTimer) {
+        clearInterval(this.healthyTimer);
+        this.healthyTimer = null;
+      }
+      if (this.riskTimer) {
+        clearInterval(this.riskTimer);
+        this.riskTimer = null;
+      }
+    },
+    resetHealthyScroll() {
+      this.healthyScrollIndex = 0;
+      if (this.healthyTimer) {
+        clearInterval(this.healthyTimer);
+        this.healthyTimer = null;
+      }
+      this.startHealthyScroll();
+    },
+    resetRiskScroll() {
+      this.riskScrollIndex = 0;
+      if (this.riskTimer) {
+        clearInterval(this.riskTimer);
+        this.riskTimer = null;
+      }
+      this.startRiskScroll();
+    },
+    startHealthyScroll() {
+      if (this.healthyList.length <= this.healthyVisibleCount) return;
+
+      this.healthyTimer = setInterval(() => {
+        this.healthyScrollIndex =
+          this.healthyScrollIndex >= this.healthyMaxScrollIndex
+            ? 0
+            : this.healthyScrollIndex + 1;
+      }, SCROLL_INTERVAL);
+    },
+    startRiskScroll() {
+      if (this.riskList.length <= this.riskVisibleCount) return;
+
+      this.riskTimer = setInterval(() => {
+        this.riskScrollIndex =
+          this.riskScrollIndex >= this.riskMaxScrollIndex
+            ? 0
+            : this.riskScrollIndex + 1;
+      }, SCROLL_INTERVAL);
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+$rank-row-height: 36px;
+$rank-gap: 8px;
+
+.health-ranking-panel {
+  display: flex;
+  flex-direction: column;
+  gap: 24px;
+  min-width: 0;
+  height: 100%;
+}
+
+.health-ranking-panel__card {
+  padding: 16px;
+  display: flex;
+  flex-direction: column;
+}
+
+.health-ranking-panel__card--good {
+  flex: 0 0 auto;
+}
+
+.health-ranking-panel__card--risk {
+  flex: 1 1 auto;
+  min-height: 0;
+}
+
+.health-ranking-panel__title {
+  flex-shrink: 0;
+  margin: 0 0 12px;
+  font-size: 14px;
+  font-weight: 700;
+  display: flex;
+  align-items: center;
+}
+
+.health-ranking-panel__title--good {
+  color: #00ff88;
+}
+
+.health-ranking-panel__title--danger {
+  color: #ff3131;
+}
+
+.health-ranking-panel__title-icon {
+  margin-right: 8px;
+}
+
+.health-ranking-panel__pulse {
+  animation: health-pulse 2s ease-in-out infinite;
+}
+
+@keyframes health-pulse {
+  0%,
+  100% {
+    opacity: 1;
+  }
+  50% {
+    opacity: 0.45;
+  }
+}
+
+.health-ranking-panel__viewport {
+  flex-shrink: 0;
+  overflow: hidden;
+}
+
+.health-ranking-panel__viewport--good {
+  height: calc(#{$rank-row-height} * 3 + #{$rank-gap} * 2);
+}
+
+.health-ranking-panel__viewport--risk {
+  height: calc(#{$rank-row-height} * 4 + #{$rank-gap} * 3);
+}
+
+.health-ranking-panel__track {
+  display: flex;
+  flex-direction: column;
+  gap: $rank-gap;
+  transition: transform 0.6s ease-in-out;
+}
+
+.health-ranking-panel__row {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  height: $rank-row-height;
+  min-height: $rank-row-height;
+  padding: 0 8px;
+  border-radius: 6px;
+  box-sizing: border-box;
+}
+
+.health-ranking-panel__row--good {
+  background: rgba(16, 185, 129, 0.05);
+  border: 1px solid rgba(16, 185, 129, 0.1);
+}
+
+.health-ranking-panel__row--danger {
+  background: rgba(239, 68, 68, 0.1);
+  border: 1px solid rgba(239, 68, 68, 0.2);
+}
+
+.health-ranking-panel__row--warning {
+  background: rgba(239, 68, 68, 0.05);
+  border: 1px solid rgba(239, 68, 68, 0.1);
+  opacity: 0.8;
+}
+
+.health-ranking-panel__risk-info {
+  flex: 1;
+  min-width: 0;
+}
+
+.health-ranking-panel__rank-tag {
+  flex-shrink: 0;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 26px;
+  height: 18px;
+  font-size: 11px;
+  font-weight: 700;
+  line-height: 1;
+  color: #ffffff;
+  clip-path: polygon(
+    0 0,
+    calc(100% - 7px) 0,
+    100% 50%,
+    calc(100% - 7px) 100%,
+    0 100%
+  );
+}
+
+.health-ranking-panel__rank-tag--good-1 {
+  background: #00ff88;
+}
+
+.health-ranking-panel__rank-tag--good-2 {
+  background: #1e7cf8;
+}
+
+.health-ranking-panel__rank-tag--good-3 {
+  background: #22d3ee;
+}
+
+.health-ranking-panel__rank-tag--good-4,
+.health-ranking-panel__rank-tag--good-5 {
+  background: #64748b;
+}
+
+.health-ranking-panel__rank-tag--risk-1 {
+  background: #ff3131;
+}
+
+.health-ranking-panel__rank-tag--risk-2 {
+  background: #ff8c42;
+}
+
+.health-ranking-panel__rank-tag--risk-3 {
+  background: #facc15;
+  color: #1e293b;
+}
+
+.health-ranking-panel__rank-tag--risk-4,
+.health-ranking-panel__rank-tag--risk-5 {
+  background: #22d3ee;
+}
+
+.health-ranking-panel__name {
+  flex: 1;
+  min-width: 0;
+  font-size: 12px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.health-ranking-panel__name--bold {
+  font-weight: 700;
+}
+
+.health-ranking-panel__score {
+  flex-shrink: 0;
+  font-size: 12px;
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+  font-weight: 700;
+}
+
+.health-ranking-panel__score--good {
+  color: #00ff88;
+}
+
+.health-ranking-panel__badge {
+  flex-shrink: 0;
+  padding: 2px 8px;
+  border-radius: 4px;
+  font-size: 10px;
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+}
+
+.health-ranking-panel__badge--danger {
+  background: rgba(239, 68, 68, 0.2);
+  color: #ff3131;
+}
+
+.health-ranking-panel__badge--warning {
+  font-size: 12px;
+  color: #faad14;
+}
+</style>

+ 196 - 0
src/views/health/components/health/HealthScorePanel.vue

@@ -0,0 +1,196 @@
+<template>
+  <section class="health-score-panel glass-card">
+    <div class="scan-line"></div>
+    <h3 class="health-score-panel__title">
+      <i class="el-icon-success health-score-panel__icon"></i>
+      全场健康度实时评分
+    </h3>
+    <div ref="chartRef" class="health-score-panel__chart"></div>
+    <div class="health-score-panel__stats">
+      <div
+        v-for="item in summary"
+        :key="item.label"
+        class="health-score-panel__stat"
+      >
+        <div class="health-score-panel__stat-value" :style="{ color: item.color }">
+          {{ item.value }}
+        </div>
+        <div class="health-score-panel__stat-label">{{ item.label }}</div>
+      </div>
+    </div>
+  </section>
+</template>
+
+<script>
+import * as echarts from "echarts";
+
+export default {
+  name: "HealthScorePanel",
+  props: {
+    distribution: {
+      type: Array,
+      default: () => [],
+    },
+    summary: {
+      type: Array,
+      default: () => [],
+    },
+    totalScore: {
+      type: Number,
+      default: 92,
+    },
+  },
+  data() {
+    return {
+      chart: null,
+    };
+  },
+  mounted() {
+    this.initChart();
+    window.addEventListener("resize", this.handleResize);
+  },
+  beforeDestroy() {
+    window.removeEventListener("resize", this.handleResize);
+    if (this.chart) {
+      this.chart.dispose();
+      this.chart = null;
+    }
+  },
+  watch: {
+    distribution: {
+      deep: true,
+      handler() {
+        this.renderChart();
+      },
+    },
+    totalScore() {
+      this.renderChart();
+    },
+  },
+  methods: {
+    initChart() {
+      if (!this.$refs.chartRef) return;
+      this.chart = echarts.init(this.$refs.chartRef);
+      this.renderChart();
+    },
+    renderChart() {
+      if (!this.chart) return;
+      this.chart.setOption({
+        backgroundColor: "transparent",
+        series: [
+          {
+            type: "pie",
+            radius: ["60%", "85%"],
+            avoidLabelOverlap: false,
+            itemStyle: {
+              borderRadius: 10,
+              borderColor: "#050E23",
+              borderWidth: 5,
+            },
+            label: {
+              show: true,
+              position: "center",
+              formatter: `{score|${this.totalScore}}\n{label|综合评分}`,
+              rich: {
+                score: {
+                  fontSize: 48,
+                  fontWeight: "bold",
+                  color: "#00F2FF",
+                  fontFamily: "Orbitron, monospace",
+                  padding: [10, 0],
+                },
+                label: {
+                  fontSize: 12,
+                  color: "#64748b",
+                },
+              },
+            },
+            emphasis: {
+              label: {
+                show: true,
+              },
+            },
+            data: this.distribution.map((item) => ({
+              value: item.value,
+              name: item.name,
+              itemStyle: { color: item.color },
+            })),
+          },
+        ],
+      });
+    },
+    handleResize() {
+      if (this.chart) {
+        this.chart.resize();
+      }
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.health-score-panel {
+  padding: 24px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  position: relative;
+  overflow: hidden;
+  height: 100%;
+  min-width: 0;
+}
+
+.health-score-panel__title {
+  width: 100%;
+  margin: 0 0 16px;
+  text-align: left;
+  color: #94a3b8;
+  font-size: 14px;
+  display: flex;
+  align-items: center;
+}
+
+.health-score-panel__icon {
+  margin-right: 8px;
+  color: #22d3ee;
+  font-size: 16px;
+}
+
+.health-score-panel__chart {
+  width: 100%;
+  height: 220px;
+}
+
+@media (min-width: 1280px) {
+  .health-score-panel__chart {
+    height: 256px;
+  }
+}
+
+.health-score-panel__stats {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+  gap: 8px;
+  width: 100%;
+  margin-top: 16px;
+}
+
+.health-score-panel__stat {
+  text-align: center;
+  padding: 8px;
+  background: rgba(255, 255, 255, 0.05);
+  border-radius: 8px;
+  border: 1px solid rgba(255, 255, 255, 0.05);
+}
+
+.health-score-panel__stat-value {
+  font-size: 18px;
+  font-weight: 700;
+}
+
+.health-score-panel__stat-label {
+  font-size: 10px;
+  color: #64748b;
+  margin-top: 2px;
+}
+</style>

+ 167 - 0
src/views/health/components/health/HealthSubsystemPanel.vue

@@ -0,0 +1,167 @@
+<template>
+  <section class="health-subsystem-panel glass-card subsystem-card">
+    <h3 class="health-subsystem-panel__title">
+      <i class="el-icon-data-analysis health-subsystem-panel__icon"></i>
+      子系统健康度多维对比
+    </h3>
+    <div ref="chartRef" class="health-subsystem-panel__chart"></div>
+  </section>
+</template>
+
+<script>
+import * as echarts from "echarts";
+
+export default {
+  name: "HealthSubsystemPanel",
+  props: {
+    chartData: {
+      type: Array,
+      default: () => [],
+    },
+  },
+  data() {
+    return {
+      chart: null,
+    };
+  },
+  mounted() {
+    this.initChart();
+    window.addEventListener("resize", this.handleResize);
+  },
+  beforeDestroy() {
+    window.removeEventListener("resize", this.handleResize);
+    if (this.chart) {
+      this.chart.dispose();
+      this.chart = null;
+    }
+  },
+  watch: {
+    chartData: {
+      deep: true,
+      handler() {
+        this.renderChart();
+      },
+    },
+  },
+  methods: {
+    initChart() {
+      if (!this.$refs.chartRef) return;
+      this.chart = echarts.init(this.$refs.chartRef);
+      this.renderChart();
+    },
+    renderChart() {
+      if (!this.chart) return;
+      this.chart.setOption({
+        backgroundColor: "transparent",
+        tooltip: {
+          trigger: "axis",
+          backgroundColor: "rgba(16, 30, 66, 0.95)",
+          borderColor: "rgba(120, 156, 255, 0.4)",
+          textStyle: { color: "#dbeafe" },
+          axisPointer: { type: "shadow" },
+        },
+        grid: {
+          top: "10%",
+          left: "3%",
+          right: "4%",
+          bottom: "6%",
+          containLabel: true,
+        },
+        xAxis: {
+          type: "category",
+          data: this.chartData.map((item) => item.name),
+          axisLine: {
+            lineStyle: {
+              color: "rgba(122, 149, 218, 0.35)",
+            },
+          },
+          axisTick: { show: false },
+          axisLabel: {
+            color: "#8da3c7",
+            fontSize: 13,
+            fontWeight: 600,
+          },
+        },
+        yAxis: {
+          type: "value",
+          max: 100,
+          axisLine: { show: false },
+          axisTick: { show: false },
+          splitLine: {
+            lineStyle: {
+              color: "rgba(98, 121, 186, 0.2)",
+              type: "dashed",
+            },
+          },
+          axisLabel: {
+            color: "#7f94b7",
+            fontSize: 12,
+          },
+        },
+        series: [
+          {
+            data: this.chartData.map((item) => ({
+              value: item.value,
+              itemStyle: {
+                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                  { offset: 0, color: item.colors[0] },
+                  { offset: 1, color: item.colors[1] },
+                ]),
+              },
+            })),
+            type: "bar",
+            barWidth: "42%",
+            barCategoryGap: "42%",
+            showBackground: true,
+            backgroundStyle: {
+              color: "rgba(119, 143, 199, 0.12)",
+              borderRadius: [12, 12, 0, 0],
+            },
+            itemStyle: {
+              borderRadius: [8, 8, 0, 0],
+            },
+          },
+        ],
+      });
+    },
+    handleResize() {
+      if (this.chart) {
+        this.chart.resize();
+      }
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.health-subsystem-panel {
+  padding: 24px;
+  height: 100%;
+  min-width: 0;
+}
+
+.health-subsystem-panel__title {
+  margin: 0 0 16px;
+  color: #94a3b8;
+  font-size: 14px;
+  display: flex;
+  align-items: center;
+}
+
+.health-subsystem-panel__icon {
+  margin-right: 8px;
+  color: #c4b5fd;
+  font-size: 16px;
+}
+
+.health-subsystem-panel__chart {
+  width: 100%;
+  height: 260px;
+}
+
+@media (min-width: 1280px) {
+  .health-subsystem-panel__chart {
+    height: 320px;
+  }
+}
+</style>

+ 327 - 0
src/views/health/components/health/HealthUnitCard.vue

@@ -0,0 +1,327 @@
+<template>
+  <div
+    class="health-unit-card glass-card"
+    :class="{
+      'health-unit-card--dimmed': unit.dimmed,
+      'health-unit-card--interactive': unit.interactive,
+    }"
+  >
+    <div class="health-unit-card__header">
+      <div>
+        <h3
+          class="health-unit-card__name"
+          :class="{ 'health-unit-card__name--interactive': unit.interactive }"
+        >
+          {{ unit.name }}
+        </h3>
+        <p class="health-unit-card__model">{{ unit.model }}</p>
+      </div>
+      <span class="health-unit-card__status" :class="statusClass">
+        {{ unit.statusText }}
+      </span>
+    </div>
+
+    <div
+      class="health-unit-card__score-wrap"
+      :class="{ 'health-unit-card__score-wrap--glow': unit.showGlow }"
+    >
+      <div v-if="unit.showGlow" class="health-unit-card__score-glow"></div>
+      <span class="health-unit-card__score" :class="scoreClass">{{
+        unit.score
+      }}</span>
+      <span v-if="unit.showScoreLabel" class="health-unit-card__score-label"
+        >Score</span
+      >
+    </div>
+
+    <div class="health-unit-card__metrics">
+      <div
+        v-for="(metric, index) in unit.metrics"
+        :key="`${metric.label}-${index}`"
+      >
+        <div class="health-unit-card__metric-row">
+          <span class="health-unit-card__metric-label">{{ metric.label }}</span>
+          <span
+            class="health-unit-card__metric-value"
+            :class="metricToneClass(metric.tone)"
+          >
+            {{ metric.value }}%
+          </span>
+        </div>
+        <div class="health-unit-card__metric-bar">
+          <div
+            class="health-unit-card__metric-fill"
+            :class="[
+              metricBarClass(metric.tone),
+              { 'health-unit-card__metric-fill--glow': metric.glow },
+            ]"
+            :style="{ width: `${metric.value}%` }"
+          ></div>
+        </div>
+      </div>
+    </div>
+
+    <a
+      class="health-unit-card__link"
+      href="javascript:void(0)"
+      @click.prevent="drawerVisible = true"
+    >
+      查看诊断详情
+      <i class="el-icon-right health-unit-card__link-icon"></i>
+    </a>
+
+    <HealthUnitDetailDrawer :visible.sync="drawerVisible" :unit="unit" />
+  </div>
+</template>
+
+<script>
+import HealthUnitDetailDrawer from "./HealthUnitDetailDrawer.vue";
+
+export default {
+  name: "HealthUnitCard",
+  components: {
+    HealthUnitDetailDrawer,
+  },
+  props: {
+    unit: {
+      type: Object,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      drawerVisible: false,
+    };
+  },
+  computed: {
+    statusClass() {
+      return `health-unit-card__status--${this.unit.statusType}`;
+    },
+    scoreClass() {
+      return `health-unit-card__score--${this.unit.scoreTone}`;
+    },
+  },
+  methods: {
+    metricToneClass(tone) {
+      return `health-unit-card__metric-value--${tone}`;
+    },
+    metricBarClass(tone) {
+      return `health-unit-card__metric-fill--${tone}`;
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.health-unit-card {
+  padding: 16px;
+
+  &--dimmed {
+    opacity: 0.9;
+  }
+}
+
+.health-unit-card__header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 16px;
+}
+
+.health-unit-card__name {
+  margin: 0;
+  font-size: 14px;
+  font-weight: 700;
+}
+
+.health-unit-card--interactive:hover .health-unit-card__name--interactive {
+  text-shadow: 0 0 8px rgba(0, 242, 255, 0.8);
+  color: #00f2ff;
+}
+
+.health-unit-card__model {
+  margin: 4px 0 0;
+  font-size: 10px;
+  color: #64748b;
+}
+
+.health-unit-card__status {
+  padding: 2px 6px;
+  border-radius: 4px;
+  font-size: 9px;
+  line-height: 1.4;
+  white-space: nowrap;
+}
+
+.health-unit-card__status--fault {
+  background: rgba(239, 68, 68, 0.2);
+  color: #f87171;
+  border: 1px solid rgba(239, 68, 68, 0.3);
+}
+
+.health-unit-card__status--excellent {
+  background: rgba(16, 185, 129, 0.2);
+  color: #34d399;
+  border: 1px solid rgba(16, 185, 129, 0.3);
+}
+
+.health-unit-card__status--excellent-plain {
+  background: rgba(16, 185, 129, 0.2);
+  color: #34d399;
+}
+
+.health-unit-card__status--good {
+  background: rgba(59, 130, 246, 0.2);
+  color: #60a5fa;
+}
+
+.health-unit-card__status--warning {
+  background: rgba(234, 179, 8, 0.2);
+  color: #facc15;
+}
+
+.health-unit-card__score-wrap {
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 16px 0;
+}
+
+.health-unit-card__score-wrap--glow {
+  position: relative;
+}
+
+.health-unit-card__score-glow {
+  position: absolute;
+  inset: 0;
+  background: rgba(239, 68, 68, 0.05);
+  filter: blur(24px);
+  border-radius: 9999px;
+}
+
+.health-unit-card__score {
+  position: relative;
+  font-size: 30px;
+  font-weight: 700;
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+}
+
+.health-unit-card__score--danger {
+  color: #ef4444;
+}
+
+.health-unit-card__score--excellent {
+  color: #34d399;
+}
+
+.health-unit-card__score--good {
+  color: #60a5fa;
+}
+
+.health-unit-card__score--warning {
+  color: #facc15;
+}
+
+.health-unit-card__score-label {
+  font-size: 10px;
+  color: #64748b;
+  margin-left: 4px;
+  margin-top: 8px;
+}
+
+.health-unit-card__metrics {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+  margin-bottom: 16px;
+}
+
+.health-unit-card__metric-row {
+  display: flex;
+  justify-content: space-between;
+  font-size: 10px;
+}
+
+.health-unit-card__metric-label {
+  color: #64748b;
+}
+
+.health-unit-card__metric-value--emerald {
+  color: #34d399;
+}
+
+.health-unit-card__metric-value--danger {
+  color: #f87171;
+}
+
+.health-unit-card__metric-value--good {
+  color: #60a5fa;
+}
+
+.health-unit-card__metric-value--warning {
+  color: #facc15;
+}
+
+.health-unit-card__metric-bar {
+  width: 100%;
+  height: 4px;
+  background: #1e293b;
+  border-radius: 9999px;
+  overflow: hidden;
+}
+
+.health-unit-card__metric-fill {
+  height: 100%;
+  border-radius: 9999px;
+}
+
+.health-unit-card__metric-fill--emerald {
+  background: #10b981;
+}
+
+.health-unit-card__metric-fill--danger {
+  background: #ef4444;
+}
+
+.health-unit-card__metric-fill--good {
+  background: #3b82f6;
+}
+
+.health-unit-card__metric-fill--warning {
+  background: #eab308;
+}
+
+.health-unit-card__metric-fill--glow.health-unit-card__metric-fill--emerald {
+  box-shadow: 0 0 8px #10b981;
+}
+
+.health-unit-card__metric-fill--glow.health-unit-card__metric-fill--danger {
+  box-shadow: 0 0 8px #ef4444;
+}
+
+.health-unit-card__link {
+  display: block;
+  text-align: center;
+  padding: 6px 0;
+  background: rgba(255, 255, 255, 0.05);
+  border-radius: 6px;
+  font-size: 10px;
+  color: inherit;
+  text-decoration: none;
+  border: 1px solid rgba(255, 255, 255, 0.1);
+  transition: all 0.3s ease;
+
+  &:hover {
+    background: rgba(6, 182, 212, 0.2);
+    border-color: rgba(6, 182, 212, 0.5);
+    color: #e2e8f0;
+  }
+}
+
+.health-unit-card__link-icon {
+  margin-left: 4px;
+  font-size: 12px;
+  vertical-align: middle;
+}
+</style>

+ 565 - 0
src/views/health/components/health/HealthUnitDetailDrawer.vue

@@ -0,0 +1,565 @@
+<template>
+  <el-drawer
+    :visible.sync="drawerVisible"
+    direction="rtl"
+    size="480px"
+    :title="drawerTitle"
+    custom-class="health-unit-detail-drawer"
+    append-to-body
+    @opened="handleOpened"
+    @closed="handleClosed"
+  >
+    <div class="unit-detail">
+      <section class="unit-detail__section">
+        <h4 class="unit-detail__heading">{{ unitName }}风机综合健康评分</h4>
+        <div ref="gaugeRef" class="unit-detail__gauge"></div>
+      </section>
+
+      <section class="unit-detail__section">
+        <div class="unit-detail__section-head">
+          <h4 class="unit-detail__heading">综合健康评分趋势图</h4>
+          <el-radio-group
+            v-model="timeRange.overall"
+            size="mini"
+            class="unit-detail__range"
+            @change="renderOverallTrend"
+          >
+            <el-radio-button label="7">近7天</el-radio-button>
+            <el-radio-button label="30">近30天</el-radio-button>
+            <el-radio-button label="365">近1年</el-radio-button>
+          </el-radio-group>
+        </div>
+        <div ref="overallTrendRef" class="unit-detail__chart"></div>
+      </section>
+
+      <section
+        v-for="section in detail.healthSections"
+        :key="section.title"
+        class="unit-detail__health-block"
+      >
+        <div class="unit-detail__health-title">{{ section.title }}</div>
+        <div
+          v-for="item in section.items"
+          :key="item.name"
+          class="unit-detail__health-row"
+        >
+          <span class="unit-detail__health-name">
+            {{ item.name }}
+            <i v-if="item.help" class="el-icon-question unit-detail__help"></i>
+          </span>
+          <span class="unit-detail__health-score">
+            {{ item.score == null ? "/" : item.score }}
+          </span>
+          <span v-if="item.status" class="unit-detail__health-status">
+            <i
+              class="unit-detail__status-dot"
+              :style="{ backgroundColor: statusInfo(item.status).color }"
+            ></i>
+            {{ statusInfo(item.status).label }}
+          </span>
+          <span v-else class="unit-detail__health-status unit-detail__health-status--empty"
+            >/</span
+          >
+        </div>
+      </section>
+
+      <section class="unit-detail__section">
+        <div class="unit-detail__section-head">
+          <h4 class="unit-detail__heading">结构健康趋势图</h4>
+          <el-radio-group
+            v-model="timeRange.structural"
+            size="mini"
+            class="unit-detail__range"
+            @change="renderStructuralTrend"
+          >
+            <el-radio-button label="7">近7天</el-radio-button>
+            <el-radio-button label="30">近30天</el-radio-button>
+            <el-radio-button label="365">近1年</el-radio-button>
+          </el-radio-group>
+        </div>
+        <div ref="structuralTrendRef" class="unit-detail__chart unit-detail__chart--tall"></div>
+      </section>
+
+      <section class="unit-detail__section">
+        <div class="unit-detail__section-head">
+          <h4 class="unit-detail__heading">系统健康趋势图</h4>
+          <el-radio-group
+            v-model="timeRange.system"
+            size="mini"
+            class="unit-detail__range"
+            @change="renderSystemTrend"
+          >
+            <el-radio-button label="7">近7天</el-radio-button>
+            <el-radio-button label="30">近30天</el-radio-button>
+            <el-radio-button label="365">近1年</el-radio-button>
+          </el-radio-group>
+        </div>
+        <div ref="systemTrendRef" class="unit-detail__chart unit-detail__chart--tall"></div>
+      </section>
+
+      <section class="unit-detail__section">
+        <div class="unit-detail__section-head">
+          <h4 class="unit-detail__heading">部件健康趋势图</h4>
+          <el-radio-group
+            v-model="timeRange.component"
+            size="mini"
+            class="unit-detail__range"
+            @change="renderComponentTrend"
+          >
+            <el-radio-button label="7">近7天</el-radio-button>
+            <el-radio-button label="30">近30天</el-radio-button>
+            <el-radio-button label="365">近1年</el-radio-button>
+          </el-radio-group>
+        </div>
+        <div ref="componentTrendRef" class="unit-detail__chart unit-detail__chart--tall"></div>
+      </section>
+    </div>
+
+    <div class="unit-detail__footer">
+      <el-button size="small" @click="drawerVisible = false">取消</el-button>
+    </div>
+  </el-drawer>
+</template>
+
+<script>
+import * as echarts from "echarts";
+import {
+  buildTrendData,
+  getUnitDetailData,
+  HEALTH_STATUS,
+} from "./unitDetailData";
+
+export default {
+  name: "HealthUnitDetailDrawer",
+  props: {
+    visible: {
+      type: Boolean,
+      default: false,
+    },
+    unit: {
+      type: Object,
+      default: () => ({}),
+    },
+  },
+  data() {
+    return {
+      detail: getUnitDetailData(),
+      timeRange: {
+        overall: "7",
+        structural: "7",
+        system: "7",
+        component: "7",
+      },
+      charts: {},
+    };
+  },
+  computed: {
+    drawerVisible: {
+      get() {
+        return this.visible;
+      },
+      set(val) {
+        this.$emit("update:visible", val);
+      },
+    },
+    drawerTitle() {
+      return "机组详情";
+    },
+    unitName() {
+      return (this.unit && this.unit.name) || "XXX";
+    },
+  },
+  watch: {
+    unit: {
+      deep: true,
+      handler(val) {
+        this.detail = getUnitDetailData(val);
+        if (this.visible) {
+          this.$nextTick(() => {
+            this.renderGauge();
+            this.renderAllTrends();
+          });
+        }
+      },
+    },
+  },
+  beforeDestroy() {
+    window.removeEventListener("resize", this.handleResize);
+    this.disposeCharts();
+  },
+  methods: {
+    statusInfo(status) {
+      return HEALTH_STATUS[status] || { label: "--", color: "#64748b" };
+    },
+    handleOpened() {
+      this.timeRange = {
+        overall: "7",
+        structural: "7",
+        system: "7",
+        component: "7",
+      };
+      this.detail = getUnitDetailData(this.unit);
+      this.$nextTick(() => {
+        this.initCharts();
+        window.addEventListener("resize", this.handleResize);
+      });
+    },
+    handleClosed() {
+      window.removeEventListener("resize", this.handleResize);
+      this.disposeCharts();
+    },
+    initCharts() {
+      this.initChart("gauge", this.$refs.gaugeRef);
+      this.initChart("overall", this.$refs.overallTrendRef);
+      this.initChart("structural", this.$refs.structuralTrendRef);
+      this.initChart("system", this.$refs.systemTrendRef);
+      this.initChart("component", this.$refs.componentTrendRef);
+      this.renderGauge();
+      this.renderAllTrends();
+    },
+    initChart(key, el) {
+      if (!el) return;
+      if (this.charts[key]) {
+        this.charts[key].dispose();
+      }
+      this.charts[key] = echarts.init(el);
+    },
+    disposeCharts() {
+      Object.keys(this.charts).forEach((key) => {
+        if (this.charts[key]) {
+          this.charts[key].dispose();
+        }
+      });
+      this.charts = {};
+    },
+    renderGauge() {
+      const chart = this.charts.gauge;
+      if (!chart) return;
+      chart.setOption({
+        backgroundColor: "transparent",
+        series: [
+          {
+            type: "gauge",
+            startAngle: 180,
+            endAngle: 0,
+            min: 0,
+            max: 100,
+            radius: "100%",
+            center: ["50%", "72%"],
+            splitNumber: 5,
+            axisLine: {
+              lineStyle: {
+                width: 18,
+                color: [
+                  [0.2, "#FF4A4A"],
+                  [0.4, "#FF8C42"],
+                  [0.6, "#F7C43B"],
+                  [0.8, "#5BC0EB"],
+                  [1, "#1E7CF8"],
+                ],
+              },
+            },
+            pointer: { show: false },
+            axisTick: { show: false },
+            splitLine: { show: false },
+            axisLabel: {
+              show: true,
+              distance: -36,
+              color: "#64748b",
+              fontSize: 10,
+            },
+            detail: {
+              valueAnimation: true,
+              formatter: "{value}",
+              fontSize: 42,
+              fontWeight: 700,
+              color: "#e2e8f0",
+              offsetCenter: [0, "-8%"],
+            },
+            data: [{ value: this.detail.gaugeScore }],
+          },
+        ],
+      });
+    },
+    buildLineOption(trendData, showLegend = false) {
+      const pointCount = trendData.labels.length;
+      const showPointLabel = !showLegend && pointCount <= 12;
+
+      return {
+        backgroundColor: "transparent",
+        tooltip: {
+          trigger: "axis",
+          backgroundColor: "rgba(16, 30, 66, 0.95)",
+          borderColor: "rgba(120, 156, 255, 0.4)",
+          textStyle: { color: "#dbeafe" },
+        },
+        legend: showLegend
+          ? {
+              top: 0,
+              left: "center",
+              itemWidth: 10,
+              itemHeight: 10,
+              textStyle: { color: "#94a3b8", fontSize: 10 },
+            }
+          : { show: false },
+        grid: {
+          top: showLegend ? "20%" : showPointLabel ? "14%" : "8%",
+          left: "3%",
+          right: "4%",
+          bottom: "8%",
+          containLabel: true,
+        },
+        xAxis: {
+          type: "category",
+          boundaryGap: false,
+          data: trendData.labels,
+          axisLine: { lineStyle: { color: "rgba(122, 149, 218, 0.35)" } },
+          axisTick: { show: false },
+          axisLabel: {
+            color: "#64748b",
+            fontSize: 10,
+            interval: pointCount > 15 ? Math.floor(pointCount / 8) : 0,
+          },
+        },
+        yAxis: {
+          type: "value",
+          min: 0,
+          max: 100,
+          splitNumber: 4,
+          splitLine: { lineStyle: { color: "rgba(122, 149, 218, 0.12)" } },
+          axisLine: { show: false },
+          axisTick: { show: false },
+          axisLabel: { color: "#64748b", fontSize: 10 },
+        },
+        series: trendData.series.map((item, index) => ({
+          name: item.name,
+          type: "line",
+          smooth: true,
+          symbol: "circle",
+          symbolSize: 6,
+          showSymbol: pointCount <= 15,
+          lineStyle: { width: 2, color: item.color },
+          itemStyle: { color: item.color, borderColor: "#0d1930", borderWidth: 1 },
+          label: showPointLabel && index === 0
+            ? {
+                show: true,
+                position: "top",
+                distance: 6,
+                color: "#94a3b8",
+                fontSize: 10,
+              }
+            : { show: false },
+          areaStyle: showLegend
+            ? undefined
+            : {
+                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                  { offset: 0, color: `${item.color}55` },
+                  { offset: 1, color: `${item.color}08` },
+                ]),
+              },
+          data: item.data,
+        })),
+      };
+    },
+    renderOverallTrend() {
+      const chart = this.charts.overall;
+      if (!chart) return;
+      const trendData = buildTrendData(
+        this.detail.trendMeta.overall,
+        this.timeRange.overall
+      );
+      chart.setOption(this.buildLineOption(trendData, false), true);
+    },
+    renderStructuralTrend() {
+      const chart = this.charts.structural;
+      if (!chart) return;
+      const trendData = buildTrendData(
+        this.detail.trendMeta.structural,
+        this.timeRange.structural
+      );
+      chart.setOption(this.buildLineOption(trendData, true), true);
+    },
+    renderSystemTrend() {
+      const chart = this.charts.system;
+      if (!chart) return;
+      const trendData = buildTrendData(
+        this.detail.trendMeta.system,
+        this.timeRange.system
+      );
+      chart.setOption(this.buildLineOption(trendData, true), true);
+    },
+    renderComponentTrend() {
+      const chart = this.charts.component;
+      if (!chart) return;
+      const trendData = buildTrendData(
+        this.detail.trendMeta.component,
+        this.timeRange.component
+      );
+      chart.setOption(this.buildLineOption(trendData, true), true);
+    },
+    renderAllTrends() {
+      this.renderOverallTrend();
+      this.renderStructuralTrend();
+      this.renderSystemTrend();
+      this.renderComponentTrend();
+    },
+    handleResize() {
+      Object.values(this.charts).forEach((chart) => {
+        if (chart) chart.resize();
+      });
+    },
+  },
+};
+</script>
+
+<style lang="scss">
+.health-unit-detail-drawer {
+  .el-drawer__body {
+    display: flex;
+    flex-direction: column;
+    padding: 0;
+    overflow: hidden;
+  }
+}
+
+.unit-detail__range {
+  flex-shrink: 0;
+
+  .el-radio-button__inner {
+    padding: 5px 10px;
+    font-size: 10px;
+    color: #94a3b8;
+    background: rgba(255, 255, 255, 0.04);
+    border-color: rgba(255, 255, 255, 0.12) !important;
+    box-shadow: none !important;
+  }
+
+  .el-radio-button:first-child .el-radio-button__inner {
+    border-radius: 4px 0 0 4px;
+  }
+
+  .el-radio-button:last-child .el-radio-button__inner {
+    border-radius: 0 4px 4px 0;
+  }
+
+  .el-radio-button__orig-radio:checked + .el-radio-button__inner {
+    color: #ffffff;
+    background: #0891b2;
+    border-color: #0891b2 !important;
+    box-shadow: -1px 0 0 0 #0891b2 !important;
+  }
+}
+</style>
+
+<style lang="scss" scoped>
+.unit-detail {
+  flex: 1;
+  overflow-y: auto;
+  padding: 16px 20px 12px;
+}
+
+.unit-detail__section {
+  margin-bottom: 20px;
+}
+
+.unit-detail__section-head {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 8px;
+  margin-bottom: 8px;
+}
+
+.unit-detail__heading {
+  margin: 0;
+  font-size: 13px;
+  font-weight: 600;
+  color: #cbd5e1;
+}
+
+.unit-detail__gauge {
+  width: 100%;
+  height: 180px;
+}
+
+.unit-detail__chart {
+  width: 100%;
+  height: 180px;
+}
+
+.unit-detail__chart--tall {
+  height: 220px;
+}
+
+.unit-detail__health-block {
+  margin-bottom: 16px;
+  border: 1px solid rgba(255, 255, 255, 0.06);
+  border-radius: 6px;
+  overflow: hidden;
+}
+
+.unit-detail__health-title {
+  padding: 8px 12px;
+  background: rgba(255, 255, 255, 0.05);
+  font-size: 12px;
+  font-weight: 600;
+  color: #94a3b8;
+}
+
+.unit-detail__health-row {
+  display: grid;
+  grid-template-columns: 1fr 48px 56px;
+  align-items: center;
+  gap: 8px;
+  padding: 10px 12px;
+  font-size: 12px;
+  border-top: 1px solid rgba(255, 255, 255, 0.04);
+
+  &:first-of-type {
+    border-top: none;
+  }
+}
+
+.unit-detail__health-name {
+  color: #e2e8f0;
+}
+
+.unit-detail__help {
+  margin-left: 4px;
+  color: #64748b;
+  font-size: 12px;
+}
+
+.unit-detail__health-score {
+  text-align: center;
+  color: #cbd5e1;
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+}
+
+.unit-detail__health-status {
+  display: inline-flex;
+  align-items: center;
+  justify-content: flex-end;
+  gap: 4px;
+  color: #94a3b8;
+}
+
+.unit-detail__health-status--empty {
+  justify-content: center;
+}
+
+.unit-detail__status-dot {
+  display: inline-block;
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+}
+
+.unit-detail__footer {
+  flex-shrink: 0;
+  padding: 12px 20px 16px;
+  border-top: 1px solid rgba(255, 255, 255, 0.08);
+  text-align: right;
+  background: rgba(5, 14, 35, 0.6);
+}
+</style>

+ 132 - 0
src/views/health/components/health/dashboardData.js

@@ -0,0 +1,132 @@
+export const scoreDistribution = [
+  { value: 82, name: "优", color: "#14F39A" },
+  { value: 14, name: "良", color: "#1E7CF8" },
+  { value: 3, name: "中", color: "#F7C43B" },
+  { value: 1, name: "差", color: "#FF4A4A" },
+];
+
+export const scoreSummary = [
+  { label: "优 (台)", value: 82, color: "#00FF88" },
+  { label: "良 (台)", value: 14, color: "#0077FF" },
+  { label: "中 (台)", value: 3, color: "#FAAD14" },
+  { label: "差 (台)", value: 1, color: "#FF3131" },
+];
+
+export const subsystemData = [
+  { name: "结构健康", value: 95, colors: ["#22e8ff", "#1f7fff"] },
+  { name: "系统健康", value: 86, colors: ["#c58cff", "#7b3dff"] },
+  { name: "部件健康", value: 91, colors: ["#1df3a0", "#14a67a"] },
+];
+
+export const healthyRankList = [
+  { name: "风机 C-130", score: 98.5 },
+  { name: "风机 A-042", score: 97.2 },
+  { name: "风机 B-012", score: 96.8 },
+  { name: "风机 C-118", score: 96.1 },
+  { name: "风机 D-205", score: 95.4 },
+];
+
+export const riskRankList = [
+  {
+    name: "贵州红阳二期明阳42号风机",
+    score: 34,
+    scoreClass: "danger",
+    rowClass: "strong",
+  },
+  {
+    name: "风机 A-008",
+    score: 40,
+    scoreClass: "warning",
+    rowClass: "muted",
+  },
+  {
+    name: "风机 A-009",
+    score: 40,
+    scoreClass: "warning",
+    rowClass: "muted",
+  },
+  {
+    name: "风机 A-010",
+    score: 40,
+    scoreClass: "warning",
+    rowClass: "muted",
+  },
+  {
+    name: "风机 A-011",
+    score: 41,
+    scoreClass: "warning",
+    rowClass: "muted",
+  },
+];
+
+export const unitCards = [
+  {
+    name: "贵州红阳二期明阳42号...",
+    model: "型号: WT-5.5MW-158",
+    statusText: "故障中",
+    statusType: "fault",
+    score: 42,
+    scoreTone: "danger",
+    showScoreLabel: true,
+    showGlow: true,
+    interactive: true,
+    metrics: [
+      { label: "结构健康", value: 88, tone: "emerald", glow: true },
+      { label: "系统健康", value: 32, tone: "danger", glow: true },
+      { label: "部件健康", value: 95, tone: "emerald", glow: false },
+    ],
+  },
+  {
+    name: "风机 C-118",
+    model: "型号: WT-5.5MW-158",
+    statusText: "运行优",
+    statusType: "excellent",
+    score: 96,
+    scoreTone: "excellent",
+    metrics: [
+      { label: "部件健康", value: 95, tone: "emerald", glow: false },
+      { label: "系统健康", value: 32, tone: "danger", glow: true },
+      { label: "部件健康", value: 95, tone: "emerald", glow: false },
+    ],
+  },
+  {
+    name: "风机 C-119",
+    model: "WT-5.5MW",
+    statusText: "运行良",
+    statusType: "good",
+    score: 88,
+    scoreTone: "good",
+    dimmed: true,
+    metrics: [
+      { label: "部件健康", value: 82, tone: "good", glow: false },
+      { label: "系统健康", value: 32, tone: "danger", glow: true },
+      { label: "部件健康", value: 95, tone: "emerald", glow: false },
+    ],
+  },
+  {
+    name: "风机 C-120",
+    model: "WT-5.5MW",
+    statusText: "运行优",
+    statusType: "excellent-plain",
+    score: 94,
+    scoreTone: "excellent",
+    metrics: [
+      { label: "部件健康", value: 92, tone: "emerald", glow: false },
+      { label: "系统健康", value: 32, tone: "danger", glow: true },
+      { label: "部件健康", value: 95, tone: "emerald", glow: false },
+    ],
+  },
+  {
+    name: "风机 C-121",
+    model: "WT-5.5MW",
+    statusText: "运行中",
+    statusType: "warning",
+    score: 75,
+    scoreTone: "warning",
+    metrics: [
+      { label: "部件健康", value: 68, tone: "warning", glow: false },
+      { label: "系统健康", value: 32, tone: "danger", glow: true },
+      { label: "部件健康", value: 95, tone: "emerald", glow: false },
+    ],
+  },
+];

+ 132 - 0
src/views/health/components/health/health-dashboard.scss

@@ -0,0 +1,132 @@
+.health-dashboard-page {
+  height: 100%;
+  min-height: 0;
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  padding: 0;
+  margin: 0;
+  background-color: var(--content-bg, #000510);
+  background-image: none;
+  color: var(--text-cyan, #b7dff5);
+  font-family: "Inter", system-ui, -apple-system, sans-serif;
+  overflow-x: hidden;
+  box-sizing: border-box;
+
+  /* 仅科技蓝主题:使用固定的科技蓝底纹 */
+  html[data-theme="tech-blue"] & {
+    background-color: #050e23;
+    background-image: radial-gradient(
+        circle at 50% 50%,
+        rgba(16, 33, 79, 0.8) 0%,
+        transparent 80%
+      ),
+      linear-gradient(rgba(18, 16, 16, 0.4) 1px, transparent 1px),
+      linear-gradient(90deg, rgba(18, 16, 16, 0.4) 1px, transparent 1px);
+    background-size: 100% 100%, 30px 30px, 30px 30px;
+    color: #e2e8f0;
+  }
+
+  *,
+  *::before,
+  *::after {
+    box-sizing: border-box;
+  }
+
+  .glass-card {
+    background: rgba(13, 25, 48, 0.7);
+    backdrop-filter: blur(12px);
+    border: 1px solid rgba(0, 242, 255, 0.2);
+    box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.8);
+    border-radius: 12px;
+    position: relative;
+    transition: all 0.3s ease;
+
+    &:hover {
+      border-color: rgba(0, 242, 255, 0.5);
+      box-shadow: 0 0 20px rgba(0, 242, 255, 0.2);
+    }
+  }
+
+  .subsystem-card {
+    background: radial-gradient(
+        circle at 50% -15%,
+        rgba(91, 77, 255, 0.18),
+        transparent 48%
+      ),
+      linear-gradient(180deg, rgba(10, 24, 58, 0.92), rgba(7, 18, 44, 0.94));
+    border: 1px solid rgba(92, 124, 255, 0.22);
+    box-shadow: inset 0 0 0 1px rgba(70, 112, 255, 0.12),
+      0 18px 36px rgba(5, 10, 32, 0.58);
+  }
+
+  .neon-text-blue {
+    text-shadow: 0 0 8px rgba(0, 242, 255, 0.8);
+    color: #00f2ff;
+  }
+
+  .neon-border-red {
+    border-color: rgba(255, 49, 49, 0.5) !important;
+    box-shadow: inset 0 0 10px rgba(255, 49, 49, 0.2);
+  }
+
+  .scan-line {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 2px;
+    background: linear-gradient(
+      90deg,
+      transparent,
+      rgba(0, 242, 255, 0.5),
+      transparent
+    );
+    animation: health-scan 4s linear infinite;
+    pointer-events: none;
+  }
+
+  @keyframes health-scan {
+    0% {
+      top: 0;
+    }
+    100% {
+      top: 100%;
+    }
+  }
+
+  .health-page__body {
+    flex: 1 1 auto;
+    min-height: 0;
+    overflow-y: auto;
+    overflow-x: hidden;
+  }
+
+  .dashboard-main {
+    display: flex;
+    flex-direction: column;
+    gap: 24px;
+    max-width: 1600px;
+    margin: 0 auto;
+    width: 100%;
+    min-width: 0;
+    padding: 16px;
+    box-sizing: border-box;
+  }
+
+  .dashboard-top-row > * {
+    min-width: 0;
+    min-height: 0;
+  }
+
+  .dashboard-footer {
+    margin-top: 32px;
+    padding: 16px 16px 24px;
+    border-top: 1px solid #1e293b;
+    text-align: center;
+    color: #475569;
+    font-size: 10px;
+    letter-spacing: 0.2em;
+    text-transform: uppercase;
+  }
+}

+ 150 - 0
src/views/health/components/health/unitDetailData.js

@@ -0,0 +1,150 @@
+export const HEALTH_STATUS = {
+  excellent: { label: "优", color: "#14F39A" },
+  good: { label: "良", color: "#1E7CF8" },
+  fair: { label: "中", color: "#F7C43B" },
+  poor: { label: "差", color: "#FF4A4A" },
+};
+
+const TREND_COLORS = ["#22d3ee", "#1E7CF8", "#14F39A", "#F7C43B", "#FF4A4A", "#C58CFF"];
+
+function getDateLabels(count) {
+  const labels = [];
+  const now = new Date();
+  for (let i = count - 1; i >= 0; i -= 1) {
+    const date = new Date(now);
+    date.setDate(date.getDate() - i);
+    const month = String(date.getMonth() + 1).padStart(2, "0");
+    const day = String(date.getDate()).padStart(2, "0");
+    labels.push(`${month}/${day}`);
+  }
+  return labels;
+}
+
+function getMonthLabels(count = 12) {
+  const labels = [];
+  const now = new Date();
+  for (let i = count - 1; i >= 0; i -= 1) {
+    const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
+    labels.push(`${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, "0")}`);
+  }
+  return labels;
+}
+
+function getIndexLabels(count) {
+  return Array.from({ length: count }, (_, i) => String(i + 1));
+}
+
+function clampScore(value) {
+  return Math.max(0, Math.min(100, Math.round(value)));
+}
+
+function buildSeriesValues(count, base, offset = 0) {
+  return Array.from({ length: count }, (_, i) =>
+    clampScore(base + Math.sin((i + offset) * 0.75) * 14 + ((i + offset) % 5) * 3)
+  );
+}
+
+export function getTrendDays(range) {
+  if (range === "30") return 30;
+  if (range === "365") return 12;
+  return 7;
+}
+
+function getTrendLabels(range) {
+  if (range === "365") return getMonthLabels(12);
+  if (range === "30") return getDateLabels(30);
+  return getIndexLabels(7);
+}
+
+function buildSingleTrendByRange(range, baseScore) {
+  if (range === "7") {
+    return {
+      labels: getIndexLabels(7),
+      values: [23, 13, 12, 23, 55, 23, clampScore(baseScore || 80)],
+    };
+  }
+
+  const count = getTrendDays(range);
+  const labels = getTrendLabels(range);
+  const values = buildSeriesValues(count, baseScore, 1);
+  return { labels, values };
+}
+
+function buildMultiTrendByRange(names, bases, range) {
+  const count = getTrendDays(range);
+  const labels = getTrendLabels(range);
+
+  return {
+    labels,
+    series: names.map((name, index) => ({
+      name,
+      color: TREND_COLORS[index % TREND_COLORS.length],
+      data: buildSeriesValues(count, bases[index] || 70, index + 1),
+    })),
+  };
+}
+
+export function getUnitDetailData(unit = {}) {
+  const score = unit.score || 85;
+
+  return {
+    gaugeScore: score,
+    healthSections: [
+      {
+        title: "结构健康",
+        items: [
+          { name: "叶轮", score: 92, status: "excellent" },
+          { name: "塔筒", score: 76, status: "good" },
+        ],
+      },
+      {
+        title: "系统健康",
+        items: [
+          { name: "偏航系统", score: 40, status: "poor" },
+          { name: "变桨系统", score: 64, status: "fair" },
+          { name: "液压系统", score: 71, status: "good" },
+          { name: "主控系统", score: 71, status: "good" },
+        ],
+      },
+      {
+        title: "部件健康",
+        items: [
+          { name: "发电机", score: 76, status: "good" },
+          { name: "齿轮箱", score: 71, status: "good" },
+          { name: "主轴", score: null, status: null, help: true },
+          { name: "变流器", score: 95, status: "excellent" },
+        ],
+      },
+    ],
+    trendMeta: {
+      overall: { type: "single", base: score },
+      structural: {
+        type: "multi",
+        names: ["叶轮", "塔筒"],
+        bases: [92, 76],
+      },
+      system: {
+        type: "multi",
+        names: ["偏航系统", "变桨系统", "液压系统", "主控系统"],
+        bases: [40, 64, 71, 71],
+      },
+      component: {
+        type: "multi",
+        names: ["发电机", "齿轮箱", "主轴", "变流器"],
+        bases: [76, 71, 68, 95],
+      },
+    },
+  };
+}
+
+export function buildTrendData(meta, range) {
+  if (meta.type === "single") {
+    const trend = buildSingleTrendByRange(range, meta.base);
+    return {
+      labels: trend.labels,
+      series: [{ name: "综合健康", color: "#22d3ee", data: trend.values }],
+    };
+  }
+
+  return buildMultiTrendByRange(meta.names, meta.bases, range);
+}

+ 93 - 744
src/views/health/index.vue

@@ -1,792 +1,141 @@
 <template>
-  <div class="global-variable">
-    <div class="searchbox">
-      <p>
-        单位:
-        <selecttree
-          size="small"
-          style="width: 180px"
-          placeholder="请选择所属公司"
-          :list="parentOpt"
-          type="1"
-          v-model="companyCode"
-          :defaultParentProps="{
-            children: 'children',
-            label: 'companyName',
-            value: 'codeNumber',
-          }"
-        />
-      </p>
-
-      <p>
-        时间:
-        <el-date-picker
-          v-model="timevalue"
-          size="small"
-          type="month"
-          placeholder="选择月"
-          :picker-options="monthPickerOptions"
-        />
-      </p>
-
-      <el-button type="primary" size="small" @click="conditions">
-        查询
-      </el-button>
-    </div>
-
-    <div class="kuang">
-      <!-- 加载中动画 -->
-      <div v-if="loading" class="loader-wrapper">
-        <div class="loader"></div>
-      </div>
-
-      <!-- 空状态 -->
-      <el-empty
-        class="Anempty"
-        v-if="!loading && ListData.length === 0"
-        :image-size="200"
-      />
-
-      <!-- 主体内容 -->
-      <div class="main-body" v-if="!loading && ListData.length > 0">
-        <div
-          class="basics"
-          v-for="(item, index) in ListData"
-          :key="item + index"
-        >
-          <div class="title">
-            <span>
-              <SvgIcons
-                name="WindPower3"
-                class="WindPower3"
-                width="26"
-                height="26"
-              />
-            </span>
-            <h4>风机:{{ item.wind_turbine_name }}</h4>
-            <span class="mill">{{ millTypeMap[item.mill_type] || "-" }}</span>
-          </div>
-
-          <div class="content">
-            <div class="summary" style="color: #28a745">
-              <span :style="{ color: getHealthColor(item.total_health_score) }">
-                {{
-                  item.total_health_score < 0
-                    ? "-"
-                    : Math.floor(item.total_health_score)
-                }}
-              </span>
-              <span :style="{ color: getHealthColor(item.total_health_score) }">
-                {{ getHealthLabel(item.total_health_score) }}
-              </span>
-            </div>
-
-            <ul class="health">
-              <li>
-                <span>发电机组</span>
-                <span :class="'health-' + item.val">
-                  {{
-                    item.subsystems.generator.health_score >= 0
-                      ? String(
-                          Math.floor(item.subsystems.generator.health_score),
-                        ).slice(0, 3)
-                      : "-"
-                  }}
-                </span>
-              </li>
-
-              <li v-if="item.subsystems.drive_train">
-                <span>传动系统</span>
-                <span :class="'health-' + item.val">
-                  {{
-                    item.subsystems.drive_train.health_score >= 0
-                      ? String(
-                          Math.floor(item.subsystems.drive_train.health_score),
-                        ).slice(0, 3)
-                      : "-"
-                  }}
-                </span>
-              </li>
-
-              <li>
-                <span>机舱系统</span>
-                <span :class="'health-' + item.val">
-                  {{
-                    item.subsystems.nacelle.health_score >= 0
-                      ? String(
-                          Math.floor(item.subsystems.nacelle.health_score),
-                        ).slice(0, 3)
-                      : "-"
-                  }}
-                </span>
-              </li>
-
-              <li>
-                <span>电网环境</span>
-                <span :class="'health-' + item.val">
-                  {{
-                    item.subsystems.grid.health_score >= 0
-                      ? String(
-                          Math.floor(item.subsystems.grid.health_score),
-                        ).slice(0, 3)
-                      : "-"
-                  }}
-                </span>
-              </li>
-            </ul>
-          </div>
-
-          <!-- 健康评估建议(通用展示) -->
-          <!-- <div class="advice-box">
-            <div class="advice-head">
-              <span class="advice-title">健康评估建议</span>
-              <span
-                class="advice-badge"
-                :class="`is-${getHealthLevelKey(item.total_health_score)}`"
-              >
-                {{ getHealthLabel(item.total_health_score) }}
-              </span>
-            </div>
-            <p class="advice-text">
-              {{ getHealthAdvice(item.total_health_score) }}
-            </p>
-          </div> -->
+  <div class="health-dashboard-page">
+    <HealthHeader v-model="companyCode" @change="handleFarmChange" />
+
+    <div class="health-page__body">
+      <main class="dashboard-main">
+        <div class="dashboard-top-row">
+          <HealthScorePanel
+            :distribution="scoreDistribution"
+            :summary="scoreSummary"
+            :total-score="totalScore"
+          />
+
+          <HealthSubsystemPanel :chart-data="subsystemData" />
+
+          <HealthRankingPanel
+            :healthy-list="healthyRankList"
+            :risk-list="riskRankList"
+          />
         </div>
-      </div>
-    </div>
 
-    <!-- 健康度说明(卡片版) -->
-    <div class="health-legend">
-      <div class="legend-head">
-        <span class="legend-title">健康度说明</span>
-      </div>
-      <div class="legend-body">
-        <div class="legend-item is-good">
-          <div class="legend-icon" aria-hidden="true">
-            <svg viewBox="0 0 24 24" class="legend-icon-svg">
-              <path
-                class="shield"
-                d="M12 2.5 19 6.2v6.4c0 5.2-3.6 9.8-7 10.9-3.4-1.1-7-5.7-7-10.9V6.2L12 2.5Z"
-              />
-              <path class="mark" d="M8.2 12.2l2.3 2.3 5.3-5.3" />
-            </svg>
-          </div>
-          <div class="legend-content">
-            <p class="legend-range">100–85分(健康)</p>
-            <p class="legend-advice">
-              建议:按计划进行常规巡检与维护,保持当前运行状态,无需额外检修。
-            </p>
-          </div>
-        </div>
-        <div class="legend-item is-subhealthy">
-          <div class="legend-icon" aria-hidden="true">
-            <svg viewBox="0 0 24 24" class="legend-icon-svg">
-              <path
-                class="shield"
-                d="M12 2.5 19 6.2v6.4c0 5.2-3.6 9.8-7 10.9-3.4-1.1-7-5.7-7-10.9V6.2L12 2.5Z"
-              />
-              <path class="mark" d="M12 7.6v6.2" />
-              <path class="mark" d="M12 16.9h.01" />
-            </svg>
-          </div>
-          <div class="legend-content">
-            <p class="legend-range">84–70分(亚健康)</p>
-            <p class="legend-advice">
-              建议:加强状态监测,缩短巡检周期,排查潜在隐患,可安排预防性维护。
-            </p>
-          </div>
-        </div>
-        <div class="legend-item is-normal">
-          <div class="legend-icon" aria-hidden="true">
-            <svg viewBox="0 0 24 24" class="legend-icon-svg">
-              <path
-                class="shield"
-                d="M12 2.5 19 6.2v6.4c0 5.2-3.6 9.8-7 10.9-3.4-1.1-7-5.7-7-10.9V6.2L12 2.5Z"
-              />
-              <path class="mark" d="M12 7.6v6.2" />
-              <path class="mark" d="M12 16.9h.01" />
-            </svg>
-          </div>
-          <div class="legend-content">
-            <p class="legend-range">69–55分(一般)</p>
-            <p class="legend-advice">
-              建议:开展专项检测与故障诊断,制定针对性检修计划,及时处理发现的问题,避免状态进一步恶化。
-            </p>
-          </div>
-        </div>
-        <div class="legend-item is-fault">
-          <div class="legend-icon" aria-hidden="true">
-            <svg viewBox="0 0 24 24" class="legend-icon-svg">
-              <path
-                class="shield"
-                d="M12 2.5 19 6.2v6.4c0 5.2-3.6 9.8-7 10.9-3.4-1.1-7-5.7-7-10.9V6.2L12 2.5Z"
-              />
-              <path class="mark" d="M9 9l6 6" />
-              <path class="mark" d="M15 9l-6 6" />
-            </svg>
-          </div>
-          <div class="legend-content">
-            <p class="legend-range">55分以下(故障)</p>
-            <p class="legend-advice">
-              建议:立即停机或安排紧急检修,进行全面故障排查与修复,必要时更换关键部件,确保设备恢复正常运行能力。
-            </p>
-          </div>
-        </div>
-      </div>
+        <section class="health-unit-grid">
+          <HealthUnitCard
+            v-for="unit in unitCards"
+            :key="unit.name"
+            :unit="unit"
+          />
+        </section>
+      </main>
     </div>
   </div>
 </template>
 
 <script>
-import { getSysOrganizationAuthTreeByRoleId } from "@/api/ledger.js";
-import selecttree from "../../components/selecttree.vue";
-import axios from "axios";
+import HealthHeader from "./components/health/HealthHeader.vue";
+import HealthScorePanel from "./components/health/HealthScorePanel.vue";
+import HealthSubsystemPanel from "./components/health/HealthSubsystemPanel.vue";
+import HealthRankingPanel from "./components/health/HealthRankingPanel.vue";
+import HealthUnitCard from "./components/health/HealthUnitCard.vue";
+import {
+  healthyRankList,
+  riskRankList,
+  scoreDistribution,
+  scoreSummary,
+  subsystemData,
+  unitCards,
+} from "./components/health/dashboardData";
 
 export default {
+  name: "HealthDashboard",
   components: {
-    selecttree,
+    HealthHeader,
+    HealthScorePanel,
+    HealthSubsystemPanel,
+    HealthRankingPanel,
+    HealthUnitCard,
   },
   data() {
     return {
-      parentOpt: [],
       companyCode: "",
-      ListData: [],
-      loading: false, // ✅ 加载状态.
-      // demo演示
-      timevalue: new Date(2024, 6), // 7 月是 6,因为月份从 0 开始
+      totalScore: 92,
+      scoreDistribution,
+      scoreSummary,
+      subsystemData,
+      healthyRankList,
+      riskRankList,
+      unitCards,
     };
   },
-  computed: {
-    millTypeMap() {
-      return {
-        dfig: "双馈",
-        direct: "直驱",
-        semi: "半直驱",
-      };
-    },
-
-    monthPickerOptions() {
-      return {
-        disabledDate: (time) => {
-          const code = this.companyCode;
-          const date = time;
-
-          if (code === "WOF046400029") {
-            const start = new Date("2023-10-01");
-            const end = new Date("2024-10-30");
-            return date < start || date > end;
-          } else if (code === "WOF091200030") {
-            const start = new Date("2023-01-01");
-            const end = new Date("2024-11-30");
-            return date < start || date > end;
-          } else if (code === "WOF093400005") {
-            const start = new Date("2023-12-01");
-            const end = new Date("2024-05-30");
-            return date < start || date > end;
-          } else {
-            // 其他公司不限制时间选择
-            return false;
-          }
-        },
-      };
-    },
-  },
-  created() {
-    this.GETtree();
+  mounted() {
+    this.$nextTick(() => {
+      window.dispatchEvent(new Event("resize"));
+    });
   },
   methods: {
-    async GETtree() {
-      const res = await getSysOrganizationAuthTreeByRoleId();
-      const treedata = res.data;
-      const processedData = this.processTreeData(treedata);
-      this.parentOpt = processedData;
-      this.defaultdata = res.data[0];
-      // demo演示
-      this.$nextTick(() => {
-        this.companyCode = "WOF046400029";
-      });
-    },
-
-    processTreeData(treeData) {
-      const processedData = [];
-      function processNode(node) {
-        if (node.codeType === "field") {
-          node.companyName = node.fieldName;
-        }
-        if (node.children && node.children.length > 0) {
-          node.children.forEach((child) => {
-            processNode(child);
-          });
-        }
-      }
-      treeData.forEach((root) => {
-        processNode(root);
-        processedData.push(root);
-      });
-      return processedData;
-    },
-
-    getHealthLabel(val) {
-      if (!Number.isFinite(val) || val < 0) return "无数据";
-      if (val >= 85) return "健康";
-      if (val >= 70) return "亚健康";
-      if (val >= 55) return "一般";
-      return "故障";
-    },
-
-    getHealthLevelKey(val) {
-      if (!Number.isFinite(val) || val < 0) return "nodata";
-      if (val >= 85) return "good";
-      if (val >= 70) return "subhealthy";
-      if (val >= 55) return "normal";
-      return "fault";
-    },
-
-    getHealthAdvice(val) {
-      if (!Number.isFinite(val) || val < 0) return "暂无健康评估数据。";
-      if (val >= 85) {
-        return "按计划进行常规巡检与维护,保持当前运行状态,无需额外检修。";
-      }
-      if (val >= 70) {
-        return "加强状态监测,缩短巡检周期,排查潜在隐患,可安排预防性维护。";
-      }
-      if (val >= 55) {
-        return "开展专项检测与故障诊断,制定针对性检修计划,及时处理发现的问题,避免状态进一步恶化。";
-      }
-      return "立即停机或安排紧急检修,进行全面故障排查与修复,必要时更换关键部件,确保设备恢复正常运行能力。";
-    },
-
-    getHealthColor(val) {
-      if (!Number.isFinite(val) || val < 0) return "#6c757d"; // 灰色
-      if (val >= 85) return "#28a745"; // 绿色
-      if (val >= 70) return "#ffc107"; // 黄色
-      if (val >= 55) return "#fd7e14"; // 橙色
-      return "#dc3545"; // 红色
-    },
-
-    conditions() {
-      const fullStr = this.$formatDateTWO(this.timevalue);
-      const datePart = typeof fullStr === "string" ? fullStr.slice(0, 7) : "";
-      const parms = {
-        windcode: this.companyCode,
-        month: datePart,
-      };
-
-      this.loading = true; // ✅ 显示 loading
-      const start = performance.now(); // ⏱️ 请求开始时间
-
-      axios
-        .post(`/AnalysisMulti/health_assess`, parms)
-        .then((res) => {
-          this.ListData = res.data;
-          const end = performance.now(); // ⏱️ 请求结束时间
-          const duration = ((end - start) / 1000).toFixed(2);
-          console.log(`接口请求耗时:${duration} 秒`);
-        })
-        .catch((error) => {
-          console.error("Error fetching data:", error);
-          const end = performance.now(); // ⏱️ 请求结束时间
-          const duration = ((end - start) / 1000).toFixed(2);
-          console.log(`接口请求耗时:${duration} 秒`);
-        })
-        .finally(() => {
-          this.loading = false; // ✅ 隐藏 loading
-        });
+    handleFarmChange(data) {
+      if (!data || !data.codeNumber) return;
+      // TODO: 切换风场/组织后刷新仪表盘数据
+      console.log("当前选中:", data.companyName || data.fieldName, data);
     },
   },
 };
 </script>
 
-<style lang="scss" scoped>
-.searchbox {
-  display: flex;
-  align-items: center;
-  margin: 0 0 20px 0;
-  color: #ffffff;
+<style lang="scss">
+@import "./components/health/health-dashboard.scss";
 
-  p {
-    margin-right: 10px;
-    color: #b7dff5;
-  }
-  .el-select {
-    width: 180px;
+.health-dashboard-page {
+  .dashboard-top-row {
+    display: grid;
+    grid-template-columns: 1fr;
+    gap: 24px;
+    min-width: 0;
   }
-}
-
-.el-input__inner {
-  width: 340px;
-}
-
-.main-body {
-  margin: 0 auto;
-  display: grid;
-  grid-template-columns: repeat(auto-fill, 260px); /* 严格保持300px宽度 */
-  justify-content: space-evenly; /* 均匀分布 */
-  gap: 10px;
-
-  // height: min-content;
-  // max-height: 700px;
-  overflow: hidden;
-  overflow-y: auto;
-  .basics {
-    width: 260px; /* 固定宽度 */
-    display: flex;
-    flex-direction: column;
-    margin-bottom: 20px;
-    border: 1px solid var(--panel-border, rgba(0, 170, 255, 0.35));
-    background: rgba(0, 16, 32, 0.45);
-
-    .title {
-      padding: 8px 10px;
-      width: 100%;
-      border-bottom: 1px solid var(--panel-border, rgba(0, 170, 255, 0.25));
-      text-align: left;
-
-      display: flex;
-      align-items: center; // 垂直居中
-      //   justify-content: center; // 水平居中
 
-      span {
-        display: inline-block;
-        margin-right: 6px;
-
-        svg {
-          display: block;
-        }
-      }
-
-      h4 {
-        flex: 1;
-        min-width: 0;
-        color: #ffffff;
-        font-size: 14px;
-        font-weight: 600;
-        margin: 0;
-        white-space: nowrap;
-        overflow: hidden;
-        text-overflow: ellipsis;
-      }
-
-      .mill {
-        flex-shrink: 0;
-        font-size: 12px;
-        color: #b7dff5;
-        background: rgba(0, 170, 255, 0.12);
-        border: 1px solid rgba(0, 170, 255, 0.35);
-        padding: 2px 8px;
-        border-radius: 4px;
-        line-height: 1.4;
-      }
-    }
-
-    .content {
-      display: flex;
-      width: 100%;
-      gap: 28px;
-      .summary {
-        width: 70px;
-        display: flex;
-        flex-direction: column; // 垂直排列
-        align-items: center; // 居中对齐
-        justify-content: center;
-        font-weight: bold;
-        padding-left: 26px;
-        span {
-          display: block;
-          text-align: center;
-          line-height: 1.3;
-          &:first-child {
-            font-size: 1.6em; // 更大的字体
-            margin-bottom: 4px;
-          }
-          &:last-child {
-            font-size: 0.8em; // 稍小一点
-          }
-        }
-      }
-
-      .health {
-        flex: 1;
-        display: flex;
-        flex-wrap: wrap;
-        gap: 6px;
-        list-style: none;
-        padding: 0;
-        margin: 0;
-
-        li {
-          display: flex;
-          flex-direction: column;
-          align-items: center;
-          justify-content: center;
-          padding: 6px 6px;
-          border-radius: 4px;
-          min-width: 60px;
-          font-size: 0.8em;
-
-          span {
-            display: block;
-            text-align: center;
-            line-height: 1.3;
-
-            &:first-child {
-              font-size: 0.9em;
-              font-weight: 600;
-              margin-bottom: 2px;
-              color: #b7dff5;
-            }
-
-            &:last-child {
-              color: #ffffff;
-              font-weight: 600;
-            }
-          }
-        }
-      }
+  @media (min-width: 900px) {
+    .dashboard-top-row {
+      grid-template-columns: repeat(2, minmax(0, 1fr));
     }
 
-    .advice-box {
-      margin-top: 10px;
-      padding: 10px 10px;
-      border-top: 1px solid var(--header-bg);
-      background: #fff;
-
-      .advice-head {
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-        gap: 8px;
-        margin-bottom: 6px;
-      }
-      .advice-title {
-        font-size: 12px;
-        font-weight: 700;
-        color: #303133;
-      }
-      .advice-badge {
-        font-size: 12px;
-        font-weight: 700;
-        padding: 2px 8px;
-        border-radius: 999px;
-        &.is-good {
-          color: #1f7a3a;
-          background: #e7f6ee;
-        }
-        &.is-subhealthy {
-          color: #8a6d00;
-          background: #fff6e5;
-        }
-        &.is-normal {
-          color: #9a4d00;
-          background: #fff0e6;
-        }
-        &.is-fault {
-          color: #b42318;
-          background: #ffebee;
-        }
-        &.is-nodata {
-          color: #606266;
-          background: #f4f4f5;
-        }
-      }
-      .advice-text {
-        margin: 0;
-        font-size: 12px;
-        line-height: 1.45;
-        color: #606266;
-        word-break: break-all;
-      }
+    .dashboard-top-row > :last-child {
+      grid-column: span 2;
     }
   }
-}
-.jia {
-  color: var(--header-bg);
-}
-.kuang {
-  min-height: 79vh;
-  height: 680px;
-  position: relative;
-  overflow: hidden;
-  overflow-y: auto;
-  .Anempty {
-    position: absolute;
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
-  }
-}
 
-.health-legend {
-  margin-top: 14px;
-  border-radius: 14px;
-  background: #fff;
-  box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.08);
-  border: 1px solid rgba(228, 231, 237, 0.8);
-  overflow: hidden;
+  @media (min-width: 1280px) {
+    .dashboard-top-row {
+      grid-template-columns: repeat(3, minmax(0, 1fr));
+    }
 
-  .legend-head {
-    padding: 12px 14px 0;
-  }
-  .legend-title {
-    font-size: 14px;
-    font-weight: 800;
-    color: #303133;
+    .dashboard-top-row > :last-child {
+      grid-column: auto;
+    }
   }
 
-  .legend-body {
+  .health-unit-grid {
     display: grid;
-    grid-template-columns: repeat(4, minmax(0, 1fr));
-    gap: 0;
-    padding: 10px 10px 12px;
-  }
-
-  .legend-item {
-    display: flex;
-    align-items: flex-start;
-    gap: 10px;
-    padding: 10px 12px;
-    position: relative;
-    min-height: 88px;
-  }
-  .legend-item + .legend-item::before {
-    content: "";
-    position: absolute;
-    left: 0;
-    top: 16px;
-    bottom: 16px;
-    width: 1px;
-    background: rgba(220, 223, 230, 0.7);
-  }
-
-  .legend-icon {
-    width: 34px;
-    height: 34px;
-    flex: 0 0 auto;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    color: currentColor;
-  }
-  .legend-icon-svg {
-    width: 22px;
-    height: 22px;
-  }
-  .legend-icon-svg .shield {
-    fill: none;
-    stroke: currentColor;
-    stroke-width: 1.8;
-    stroke-linejoin: round;
-  }
-  .legend-icon-svg .mark {
-    fill: none;
-    stroke: currentColor;
-    stroke-width: 2.2;
-    stroke-linecap: round;
-    stroke-linejoin: round;
-  }
-  .legend-content {
-    flex: 1;
+    grid-template-columns: repeat(1, minmax(0, 1fr));
+    gap: 16px;
     min-width: 0;
   }
-  .legend-range {
-    margin: 0 0 6px;
-    font-size: 13px;
-    font-weight: 800;
-    line-height: 1.2;
-  }
-  .legend-advice {
-    margin: 0;
-    font-size: 12px;
-    line-height: 1.45;
-    color: #606266;
-    word-break: break-all;
-  }
 
-  .legend-item.is-good {
-    color: #28a745;
-    .legend-range {
-      color: inherit;
-    }
-  }
-  .legend-item.is-subhealthy {
-    color: #ffc107;
-    .legend-range {
-      color: inherit;
-    }
-  }
-  .legend-item.is-normal {
-    color: #fd7e14;
-    .legend-range {
-      color: inherit;
-    }
-  }
-  .legend-item.is-fault {
-    color: #dc3545;
-    .legend-range {
-      color: inherit;
+  @media (min-width: 768px) {
+    .health-unit-grid {
+      grid-template-columns: repeat(2, minmax(0, 1fr));
     }
   }
-}
 
-@media (max-width: 768px) {
-  .health-legend {
-    .legend-body {
-      grid-template-columns: 1fr;
-    }
-    .legend-item + .legend-item::before {
-      display: none;
-    }
-    .legend-item {
-      border-top: 1px solid rgba(220, 223, 230, 0.7);
-    }
-    .legend-item:first-child {
-      border-top: 0;
+  @media (min-width: 1024px) {
+    .health-unit-grid {
+      grid-template-columns: repeat(3, minmax(0, 1fr));
     }
   }
-}
 
-// 动画
-/* ✅ loading 包裹容器 */
-.loader-wrapper {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  height: 100%; /* 可根据页面需要调整高度 */
-}
-
-/* ✅ loading 样式动画 */
-
-.loader {
-  width: 50px;
-  --b: 8px;
-  aspect-ratio: 1;
-  border-radius: 50%;
-  padding: 1px;
-  background: conic-gradient(#0000 10%, var(--header-bg)) content-box;
-
-  -webkit-mask: repeating-conic-gradient(
-      #0000 0deg,
-      #000 1deg 20deg,
-      #0000 21deg 36deg
-    ),
-    radial-gradient(
-      farthest-side,
-      #0000 calc(100% - var(--b) - 1px),
-      #000 calc(100% - var(--b))
-    );
-  mask: repeating-conic-gradient(#0000 0deg, #000 1deg 20deg, #0000 21deg 36deg),
-    radial-gradient(
-      farthest-side,
-      #0000 calc(100% - var(--b) - 1px),
-      #000 calc(100% - var(--b))
-    );
-  -webkit-mask-composite: destination-in;
-  mask-composite: intersect;
-  animation: l4 1s infinite steps(10);
-}
-@keyframes l4 {
-  to {
-    transform: rotate(1turn);
+  @media (min-width: 1280px) {
+    .health-unit-grid {
+      grid-template-columns: repeat(5, minmax(0, 1fr));
+    }
   }
 }
 </style>

+ 792 - 0
src/views/health/indexOld.vue

@@ -0,0 +1,792 @@
+<template>
+  <div class="global-variable">
+    <div class="searchbox">
+      <p>
+        单位:
+        <selecttree
+          size="small"
+          style="width: 180px"
+          placeholder="请选择所属公司"
+          :list="parentOpt"
+          type="1"
+          v-model="companyCode"
+          :defaultParentProps="{
+            children: 'children',
+            label: 'companyName',
+            value: 'codeNumber',
+          }"
+        />
+      </p>
+
+      <p>
+        时间:
+        <el-date-picker
+          v-model="timevalue"
+          size="small"
+          type="month"
+          placeholder="选择月"
+          :picker-options="monthPickerOptions"
+        />
+      </p>
+
+      <el-button type="primary" size="small" @click="conditions">
+        查询
+      </el-button>
+    </div>
+
+    <div class="kuang">
+      <!-- 加载中动画 -->
+      <div v-if="loading" class="loader-wrapper">
+        <div class="loader"></div>
+      </div>
+
+      <!-- 空状态 -->
+      <el-empty
+        class="Anempty"
+        v-if="!loading && ListData.length === 0"
+        :image-size="200"
+      />
+
+      <!-- 主体内容 -->
+      <div class="main-body" v-if="!loading && ListData.length > 0">
+        <div
+          class="basics"
+          v-for="(item, index) in ListData"
+          :key="item + index"
+        >
+          <div class="title">
+            <span>
+              <SvgIcons
+                name="WindPower3"
+                class="WindPower3"
+                width="26"
+                height="26"
+              />
+            </span>
+            <h4>风机:{{ item.wind_turbine_name }}</h4>
+            <span class="mill">{{ millTypeMap[item.mill_type] || "-" }}</span>
+          </div>
+
+          <div class="content">
+            <div class="summary" style="color: #28a745">
+              <span :style="{ color: getHealthColor(item.total_health_score) }">
+                {{
+                  item.total_health_score < 0
+                    ? "-"
+                    : Math.floor(item.total_health_score)
+                }}
+              </span>
+              <span :style="{ color: getHealthColor(item.total_health_score) }">
+                {{ getHealthLabel(item.total_health_score) }}
+              </span>
+            </div>
+
+            <ul class="health">
+              <li>
+                <span>发电机组</span>
+                <span :class="'health-' + item.val">
+                  {{
+                    item.subsystems.generator.health_score >= 0
+                      ? String(
+                          Math.floor(item.subsystems.generator.health_score),
+                        ).slice(0, 3)
+                      : "-"
+                  }}
+                </span>
+              </li>
+
+              <li v-if="item.subsystems.drive_train">
+                <span>传动系统</span>
+                <span :class="'health-' + item.val">
+                  {{
+                    item.subsystems.drive_train.health_score >= 0
+                      ? String(
+                          Math.floor(item.subsystems.drive_train.health_score),
+                        ).slice(0, 3)
+                      : "-"
+                  }}
+                </span>
+              </li>
+
+              <li>
+                <span>机舱系统</span>
+                <span :class="'health-' + item.val">
+                  {{
+                    item.subsystems.nacelle.health_score >= 0
+                      ? String(
+                          Math.floor(item.subsystems.nacelle.health_score),
+                        ).slice(0, 3)
+                      : "-"
+                  }}
+                </span>
+              </li>
+
+              <li>
+                <span>电网环境</span>
+                <span :class="'health-' + item.val">
+                  {{
+                    item.subsystems.grid.health_score >= 0
+                      ? String(
+                          Math.floor(item.subsystems.grid.health_score),
+                        ).slice(0, 3)
+                      : "-"
+                  }}
+                </span>
+              </li>
+            </ul>
+          </div>
+
+          <!-- 健康评估建议(通用展示) -->
+          <!-- <div class="advice-box">
+            <div class="advice-head">
+              <span class="advice-title">健康评估建议</span>
+              <span
+                class="advice-badge"
+                :class="`is-${getHealthLevelKey(item.total_health_score)}`"
+              >
+                {{ getHealthLabel(item.total_health_score) }}
+              </span>
+            </div>
+            <p class="advice-text">
+              {{ getHealthAdvice(item.total_health_score) }}
+            </p>
+          </div> -->
+        </div>
+      </div>
+    </div>
+
+    <!-- 健康度说明(卡片版) -->
+    <div class="health-legend">
+      <div class="legend-head">
+        <span class="legend-title">健康度说明</span>
+      </div>
+      <div class="legend-body">
+        <div class="legend-item is-good">
+          <div class="legend-icon" aria-hidden="true">
+            <svg viewBox="0 0 24 24" class="legend-icon-svg">
+              <path
+                class="shield"
+                d="M12 2.5 19 6.2v6.4c0 5.2-3.6 9.8-7 10.9-3.4-1.1-7-5.7-7-10.9V6.2L12 2.5Z"
+              />
+              <path class="mark" d="M8.2 12.2l2.3 2.3 5.3-5.3" />
+            </svg>
+          </div>
+          <div class="legend-content">
+            <p class="legend-range">100–85分(健康)</p>
+            <p class="legend-advice">
+              建议:按计划进行常规巡检与维护,保持当前运行状态,无需额外检修。
+            </p>
+          </div>
+        </div>
+        <div class="legend-item is-subhealthy">
+          <div class="legend-icon" aria-hidden="true">
+            <svg viewBox="0 0 24 24" class="legend-icon-svg">
+              <path
+                class="shield"
+                d="M12 2.5 19 6.2v6.4c0 5.2-3.6 9.8-7 10.9-3.4-1.1-7-5.7-7-10.9V6.2L12 2.5Z"
+              />
+              <path class="mark" d="M12 7.6v6.2" />
+              <path class="mark" d="M12 16.9h.01" />
+            </svg>
+          </div>
+          <div class="legend-content">
+            <p class="legend-range">84–70分(亚健康)</p>
+            <p class="legend-advice">
+              建议:加强状态监测,缩短巡检周期,排查潜在隐患,可安排预防性维护。
+            </p>
+          </div>
+        </div>
+        <div class="legend-item is-normal">
+          <div class="legend-icon" aria-hidden="true">
+            <svg viewBox="0 0 24 24" class="legend-icon-svg">
+              <path
+                class="shield"
+                d="M12 2.5 19 6.2v6.4c0 5.2-3.6 9.8-7 10.9-3.4-1.1-7-5.7-7-10.9V6.2L12 2.5Z"
+              />
+              <path class="mark" d="M12 7.6v6.2" />
+              <path class="mark" d="M12 16.9h.01" />
+            </svg>
+          </div>
+          <div class="legend-content">
+            <p class="legend-range">69–55分(一般)</p>
+            <p class="legend-advice">
+              建议:开展专项检测与故障诊断,制定针对性检修计划,及时处理发现的问题,避免状态进一步恶化。
+            </p>
+          </div>
+        </div>
+        <div class="legend-item is-fault">
+          <div class="legend-icon" aria-hidden="true">
+            <svg viewBox="0 0 24 24" class="legend-icon-svg">
+              <path
+                class="shield"
+                d="M12 2.5 19 6.2v6.4c0 5.2-3.6 9.8-7 10.9-3.4-1.1-7-5.7-7-10.9V6.2L12 2.5Z"
+              />
+              <path class="mark" d="M9 9l6 6" />
+              <path class="mark" d="M15 9l-6 6" />
+            </svg>
+          </div>
+          <div class="legend-content">
+            <p class="legend-range">55分以下(故障)</p>
+            <p class="legend-advice">
+              建议:立即停机或安排紧急检修,进行全面故障排查与修复,必要时更换关键部件,确保设备恢复正常运行能力。
+            </p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { getSysOrganizationAuthTreeByRoleId } from "@/api/ledger.js";
+import selecttree from "../../components/selecttree.vue";
+import axios from "axios";
+
+export default {
+  components: {
+    selecttree,
+  },
+  data() {
+    return {
+      parentOpt: [],
+      companyCode: "",
+      ListData: [],
+      loading: false, // ✅ 加载状态.
+      // demo演示
+      timevalue: new Date(2024, 6), // 7 月是 6,因为月份从 0 开始
+    };
+  },
+  computed: {
+    millTypeMap() {
+      return {
+        dfig: "双馈",
+        direct: "直驱",
+        semi: "半直驱",
+      };
+    },
+
+    monthPickerOptions() {
+      return {
+        disabledDate: (time) => {
+          const code = this.companyCode;
+          const date = time;
+
+          if (code === "WOF046400029") {
+            const start = new Date("2023-10-01");
+            const end = new Date("2024-10-30");
+            return date < start || date > end;
+          } else if (code === "WOF091200030") {
+            const start = new Date("2023-01-01");
+            const end = new Date("2024-11-30");
+            return date < start || date > end;
+          } else if (code === "WOF093400005") {
+            const start = new Date("2023-12-01");
+            const end = new Date("2024-05-30");
+            return date < start || date > end;
+          } else {
+            // 其他公司不限制时间选择
+            return false;
+          }
+        },
+      };
+    },
+  },
+  created() {
+    this.GETtree();
+  },
+  methods: {
+    async GETtree() {
+      const res = await getSysOrganizationAuthTreeByRoleId();
+      const treedata = res.data;
+      const processedData = this.processTreeData(treedata);
+      this.parentOpt = processedData;
+      this.defaultdata = res.data[0];
+      // demo演示
+      this.$nextTick(() => {
+        this.companyCode = "WOF046400029";
+      });
+    },
+
+    processTreeData(treeData) {
+      const processedData = [];
+      function processNode(node) {
+        if (node.codeType === "field") {
+          node.companyName = node.fieldName;
+        }
+        if (node.children && node.children.length > 0) {
+          node.children.forEach((child) => {
+            processNode(child);
+          });
+        }
+      }
+      treeData.forEach((root) => {
+        processNode(root);
+        processedData.push(root);
+      });
+      return processedData;
+    },
+
+    getHealthLabel(val) {
+      if (!Number.isFinite(val) || val < 0) return "无数据";
+      if (val >= 85) return "健康";
+      if (val >= 70) return "亚健康";
+      if (val >= 55) return "一般";
+      return "故障";
+    },
+
+    getHealthLevelKey(val) {
+      if (!Number.isFinite(val) || val < 0) return "nodata";
+      if (val >= 85) return "good";
+      if (val >= 70) return "subhealthy";
+      if (val >= 55) return "normal";
+      return "fault";
+    },
+
+    getHealthAdvice(val) {
+      if (!Number.isFinite(val) || val < 0) return "暂无健康评估数据。";
+      if (val >= 85) {
+        return "按计划进行常规巡检与维护,保持当前运行状态,无需额外检修。";
+      }
+      if (val >= 70) {
+        return "加强状态监测,缩短巡检周期,排查潜在隐患,可安排预防性维护。";
+      }
+      if (val >= 55) {
+        return "开展专项检测与故障诊断,制定针对性检修计划,及时处理发现的问题,避免状态进一步恶化。";
+      }
+      return "立即停机或安排紧急检修,进行全面故障排查与修复,必要时更换关键部件,确保设备恢复正常运行能力。";
+    },
+
+    getHealthColor(val) {
+      if (!Number.isFinite(val) || val < 0) return "#6c757d"; // 灰色
+      if (val >= 85) return "#28a745"; // 绿色
+      if (val >= 70) return "#ffc107"; // 黄色
+      if (val >= 55) return "#fd7e14"; // 橙色
+      return "#dc3545"; // 红色
+    },
+
+    conditions() {
+      const fullStr = this.$formatDateTWO(this.timevalue);
+      const datePart = typeof fullStr === "string" ? fullStr.slice(0, 7) : "";
+      const parms = {
+        windcode: this.companyCode,
+        month: datePart,
+      };
+
+      this.loading = true; // ✅ 显示 loading
+      const start = performance.now(); // ⏱️ 请求开始时间
+
+      axios
+        .post(`/AnalysisMulti/health_assess`, parms)
+        .then((res) => {
+          this.ListData = res.data;
+          const end = performance.now(); // ⏱️ 请求结束时间
+          const duration = ((end - start) / 1000).toFixed(2);
+          console.log(`接口请求耗时:${duration} 秒`);
+        })
+        .catch((error) => {
+          console.error("Error fetching data:", error);
+          const end = performance.now(); // ⏱️ 请求结束时间
+          const duration = ((end - start) / 1000).toFixed(2);
+          console.log(`接口请求耗时:${duration} 秒`);
+        })
+        .finally(() => {
+          this.loading = false; // ✅ 隐藏 loading
+        });
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.searchbox {
+  display: flex;
+  align-items: center;
+  margin: 0 0 20px 0;
+  color: #ffffff;
+
+  p {
+    margin-right: 10px;
+    color: #b7dff5;
+  }
+  .el-select {
+    width: 180px;
+  }
+}
+
+.el-input__inner {
+  width: 340px;
+}
+
+.main-body {
+  margin: 0 auto;
+  display: grid;
+  grid-template-columns: repeat(auto-fill, 260px); /* 严格保持300px宽度 */
+  justify-content: space-evenly; /* 均匀分布 */
+  gap: 10px;
+
+  // height: min-content;
+  // max-height: 700px;
+  overflow: hidden;
+  overflow-y: auto;
+  .basics {
+    width: 260px; /* 固定宽度 */
+    display: flex;
+    flex-direction: column;
+    margin-bottom: 20px;
+    border: 1px solid var(--panel-border, rgba(0, 170, 255, 0.35));
+    background: rgba(0, 16, 32, 0.45);
+
+    .title {
+      padding: 8px 10px;
+      width: 100%;
+      border-bottom: 1px solid var(--panel-border, rgba(0, 170, 255, 0.25));
+      text-align: left;
+
+      display: flex;
+      align-items: center; // 垂直居中
+      //   justify-content: center; // 水平居中
+
+      span {
+        display: inline-block;
+        margin-right: 6px;
+
+        svg {
+          display: block;
+        }
+      }
+
+      h4 {
+        flex: 1;
+        min-width: 0;
+        color: #ffffff;
+        font-size: 14px;
+        font-weight: 600;
+        margin: 0;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
+
+      .mill {
+        flex-shrink: 0;
+        font-size: 12px;
+        color: #b7dff5;
+        background: rgba(0, 170, 255, 0.12);
+        border: 1px solid rgba(0, 170, 255, 0.35);
+        padding: 2px 8px;
+        border-radius: 4px;
+        line-height: 1.4;
+      }
+    }
+
+    .content {
+      display: flex;
+      width: 100%;
+      gap: 28px;
+      .summary {
+        width: 70px;
+        display: flex;
+        flex-direction: column; // 垂直排列
+        align-items: center; // 居中对齐
+        justify-content: center;
+        font-weight: bold;
+        padding-left: 26px;
+        span {
+          display: block;
+          text-align: center;
+          line-height: 1.3;
+          &:first-child {
+            font-size: 1.6em; // 更大的字体
+            margin-bottom: 4px;
+          }
+          &:last-child {
+            font-size: 0.8em; // 稍小一点
+          }
+        }
+      }
+
+      .health {
+        flex: 1;
+        display: flex;
+        flex-wrap: wrap;
+        gap: 6px;
+        list-style: none;
+        padding: 0;
+        margin: 0;
+
+        li {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          justify-content: center;
+          padding: 6px 6px;
+          border-radius: 4px;
+          min-width: 60px;
+          font-size: 0.8em;
+
+          span {
+            display: block;
+            text-align: center;
+            line-height: 1.3;
+
+            &:first-child {
+              font-size: 0.9em;
+              font-weight: 600;
+              margin-bottom: 2px;
+              color: #b7dff5;
+            }
+
+            &:last-child {
+              color: #ffffff;
+              font-weight: 600;
+            }
+          }
+        }
+      }
+    }
+
+    .advice-box {
+      margin-top: 10px;
+      padding: 10px 10px;
+      border-top: 1px solid var(--header-bg);
+      background: #fff;
+
+      .advice-head {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        gap: 8px;
+        margin-bottom: 6px;
+      }
+      .advice-title {
+        font-size: 12px;
+        font-weight: 700;
+        color: #303133;
+      }
+      .advice-badge {
+        font-size: 12px;
+        font-weight: 700;
+        padding: 2px 8px;
+        border-radius: 999px;
+        &.is-good {
+          color: #1f7a3a;
+          background: #e7f6ee;
+        }
+        &.is-subhealthy {
+          color: #8a6d00;
+          background: #fff6e5;
+        }
+        &.is-normal {
+          color: #9a4d00;
+          background: #fff0e6;
+        }
+        &.is-fault {
+          color: #b42318;
+          background: #ffebee;
+        }
+        &.is-nodata {
+          color: #606266;
+          background: #f4f4f5;
+        }
+      }
+      .advice-text {
+        margin: 0;
+        font-size: 12px;
+        line-height: 1.45;
+        color: #606266;
+        word-break: break-all;
+      }
+    }
+  }
+}
+.jia {
+  color: var(--header-bg);
+}
+.kuang {
+  min-height: 79vh;
+  height: 680px;
+  position: relative;
+  overflow: hidden;
+  overflow-y: auto;
+  .Anempty {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+  }
+}
+
+.health-legend {
+  margin-top: 14px;
+  border-radius: 14px;
+  background: #fff;
+  box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.08);
+  border: 1px solid rgba(228, 231, 237, 0.8);
+  overflow: hidden;
+
+  .legend-head {
+    padding: 12px 14px 0;
+  }
+  .legend-title {
+    font-size: 14px;
+    font-weight: 800;
+    color: #303133;
+  }
+
+  .legend-body {
+    display: grid;
+    grid-template-columns: repeat(4, minmax(0, 1fr));
+    gap: 0;
+    padding: 10px 10px 12px;
+  }
+
+  .legend-item {
+    display: flex;
+    align-items: flex-start;
+    gap: 10px;
+    padding: 10px 12px;
+    position: relative;
+    min-height: 88px;
+  }
+  .legend-item + .legend-item::before {
+    content: "";
+    position: absolute;
+    left: 0;
+    top: 16px;
+    bottom: 16px;
+    width: 1px;
+    background: rgba(220, 223, 230, 0.7);
+  }
+
+  .legend-icon {
+    width: 34px;
+    height: 34px;
+    flex: 0 0 auto;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: currentColor;
+  }
+  .legend-icon-svg {
+    width: 22px;
+    height: 22px;
+  }
+  .legend-icon-svg .shield {
+    fill: none;
+    stroke: currentColor;
+    stroke-width: 1.8;
+    stroke-linejoin: round;
+  }
+  .legend-icon-svg .mark {
+    fill: none;
+    stroke: currentColor;
+    stroke-width: 2.2;
+    stroke-linecap: round;
+    stroke-linejoin: round;
+  }
+  .legend-content {
+    flex: 1;
+    min-width: 0;
+  }
+  .legend-range {
+    margin: 0 0 6px;
+    font-size: 13px;
+    font-weight: 800;
+    line-height: 1.2;
+  }
+  .legend-advice {
+    margin: 0;
+    font-size: 12px;
+    line-height: 1.45;
+    color: #606266;
+    word-break: break-all;
+  }
+
+  .legend-item.is-good {
+    color: #28a745;
+    .legend-range {
+      color: inherit;
+    }
+  }
+  .legend-item.is-subhealthy {
+    color: #ffc107;
+    .legend-range {
+      color: inherit;
+    }
+  }
+  .legend-item.is-normal {
+    color: #fd7e14;
+    .legend-range {
+      color: inherit;
+    }
+  }
+  .legend-item.is-fault {
+    color: #dc3545;
+    .legend-range {
+      color: inherit;
+    }
+  }
+}
+
+@media (max-width: 768px) {
+  .health-legend {
+    .legend-body {
+      grid-template-columns: 1fr;
+    }
+    .legend-item + .legend-item::before {
+      display: none;
+    }
+    .legend-item {
+      border-top: 1px solid rgba(220, 223, 230, 0.7);
+    }
+    .legend-item:first-child {
+      border-top: 0;
+    }
+  }
+}
+
+// 动画
+/* ✅ loading 包裹容器 */
+.loader-wrapper {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%; /* 可根据页面需要调整高度 */
+}
+
+/* ✅ loading 样式动画 */
+
+.loader {
+  width: 50px;
+  --b: 8px;
+  aspect-ratio: 1;
+  border-radius: 50%;
+  padding: 1px;
+  background: conic-gradient(#0000 10%, var(--header-bg)) content-box;
+
+  -webkit-mask: repeating-conic-gradient(
+      #0000 0deg,
+      #000 1deg 20deg,
+      #0000 21deg 36deg
+    ),
+    radial-gradient(
+      farthest-side,
+      #0000 calc(100% - var(--b) - 1px),
+      #000 calc(100% - var(--b))
+    );
+  mask: repeating-conic-gradient(#0000 0deg, #000 1deg 20deg, #0000 21deg 36deg),
+    radial-gradient(
+      farthest-side,
+      #0000 calc(100% - var(--b) - 1px),
+      #000 calc(100% - var(--b))
+    );
+  -webkit-mask-composite: destination-in;
+  mask-composite: intersect;
+  animation: l4 1s infinite steps(10);
+}
+@keyframes l4 {
+  to {
+    transform: rotate(1turn);
+  }
+}
+</style>

+ 24 - 17
src/views/home/Index.vue

@@ -14,11 +14,11 @@
 
         <!-- <MenuTag></MenuTag> -->
         <!-- 主体内容-->
-        <transition name="slide-fade">
-          <div class="main-content">
+        <div class="main-content">
+          <transition name="slide-fade">
             <router-view />
-          </div>
-        </transition>
+          </transition>
+        </div>
       </el-main>
     </el-container>
     <!-- 默认 -->
@@ -28,11 +28,11 @@
         <HeaderCom class="headerBox" @setLoding="setLoding"></HeaderCom>
         <!-- <MenuTag></MenuTag> -->
         <!-- 主体内容-->
-        <transition name="slide-fade">
-          <div class="main-content">
+        <div class="main-content">
+          <transition name="slide-fade">
             <router-view />
-          </div>
-        </transition>
+          </transition>
+        </div>
       </el-main>
     </el-container>
   </div>
@@ -68,28 +68,34 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-::v-deep.el-main::-webkit-scrollbar {
-  width: 0px !important; /* 滚动条宽度 */
-}
 .homeWrap {
   height: 100vh;
+  overflow: hidden;
+
   .el-container {
-    // height: 100%;
-    min-height: 89vh;
+    height: 100%;
+    min-height: 0;
   }
+
   .el-main {
     min-width: 800px;
-    // min-height: 88vh;
     height: 100%;
-    background-color: #090a0f;
+    display: flex;
+    flex-direction: column;
     padding: 0;
     overflow: hidden;
+    background-color: var(--content-bg, #f5f7fa);
+
     .main-content {
-      height: 100%;
+      flex: 1 1 auto;
+      min-height: 0;
+      overflow-y: auto;
+      overflow-x: hidden;
     }
   }
 }
 ::v-deep.el-header {
+  flex-shrink: 0;
   padding: 0 !important;
   background: #1b3b7f;
   color: #fff !important;
@@ -113,9 +119,10 @@ export default {
   }
 }
 .headerBox {
+  flex-shrink: 0;
   background: var(--header-bg, #001529) !important;
   // border-bottom: 1px solid var(--panel-border, rgba(0, 170, 255, 0.35));
-  color: var(--text-cyan, #52c8f2);
+  color: var(--text-color, #ffffff);
 }
 .slide-fade-enter-active {
   transition: all 0.3s ease-out;

+ 111 - 10
src/views/home/components/MenuDt.vue

@@ -7,17 +7,20 @@
  * @FilePath: /performance-test/src/views/home/components/MenuDt.vue
 -->
 <template>
-  <el-aside :width="!isCollapse ? '250px' : '100px'" class="asideBox">
+  <el-aside
+    :width="!isCollapse ? '250px' : '100px'"
+    :class="['asideBox', isTechBlueTheme ? 'asideBox--tech' : 'asideBox--legacy']"
+  >
     <div class="homeBox"></div>
     <el-menu
       collapse-transition
       ref="menu"
-      class="mt-3 el-menu-vertical-demo menu-dt-dark"
+      :class="['mt-3 el-menu-vertical-demo', menuThemeClass]"
       @open="handleOpen"
       @close="handleClose"
-      background-color="#141414"
-      text-color="#999999"
-      active-text-color="#ffffff"
+      :background-color="menuBackgroundColor"
+      :text-color="menuTextColor"
+      :active-text-color="menuActiveTextColor"
       :router="false"
       :default-active="menuDefaultActive"
       :collapse="isCollapse"
@@ -31,7 +34,7 @@
           "
           :index="normalizedMenuParentIndex(item)"
           :popper-append-to-body="true"
-          popper-class="menu-dt-dark-popup"
+          :popper-class="menuPopperClass"
         >
           <template slot="title">
             <i v-if="isElPrefix(item.iconName)" class="el-icon-menu"></i>
@@ -150,7 +153,30 @@ export default {
   computed: {
     ...mapState({
       currentMenuIndex: (state) => state.breadStore?.currentUrl?.routeUrl,
+      currentTheme: (state) => state.themes?.theme,
     }),
+    isTechBlueTheme() {
+      return this.currentTheme === "tech-blue";
+    },
+    menuThemeClass() {
+      return this.isTechBlueTheme ? "menu-dt-dark" : "menu-dt-light";
+    },
+    menuBackgroundColor() {
+      return this.isTechBlueTheme ? "#141414" : "#eff1f3";
+    },
+    menuTextColor() {
+      return this.isTechBlueTheme ? "#999999" : "#606266";
+    },
+    menuActiveTextColor() {
+      if (this.isTechBlueTheme) return "#ffffff";
+      const primary = getComputedStyle(document.documentElement)
+        .getPropertyValue("--primary-color")
+        .trim();
+      return primary || "#82780c";
+    },
+    menuPopperClass() {
+      return this.isTechBlueTheme ? "menu-dt-dark-popup" : "";
+    },
     allMenuIndexes() {
       return this.routerList.map((item) =>
         item.children && item.children.length && item.meta?.hidden === false
@@ -435,11 +461,36 @@ $dt-radius: 8px;
   align-items: flex-start;
   justify-content: flex-start;
   flex-direction: column;
-  background: $dt-bg;
-  border-right: 1px solid #2a2a2a;
-  border-radius: 0 16px 16px 0;
   position: relative;
 
+  &.asideBox--tech {
+    background: $dt-bg;
+    border-right: 1px solid #2a2a2a;
+    border-radius: 0 16px 16px 0;
+
+    .el-menu {
+      background-color: $dt-bg !important;
+    }
+
+    .foldBox .icon {
+      color: #a6a6a6;
+    }
+  }
+
+  &.asideBox--legacy {
+    background: #eff1f3;
+    border-right: 1px solid #e4e7ed;
+    border-radius: 0;
+
+    .el-menu {
+      background-color: #eff1f3 !important;
+    }
+
+    .foldBox .icon {
+      color: #666666;
+    }
+  }
+
   .homeBox {
     width: 100%;
     padding: 0 20px;
@@ -539,7 +590,57 @@ $dt-radius: 8px;
     overflow-y: auto;
     width: 100% !important;
     height: 100%;
-    background-color: $dt-bg !important;
+  }
+}
+
+::v-deep .menu-dt-light.el-menu {
+  .el-menu-item,
+  .el-submenu__title {
+    color: #606266 !important;
+    border-radius: 4px;
+    margin: 2px 8px;
+    height: 44px;
+    line-height: 44px;
+  }
+
+  .el-menu-item:not(.is-active):not(.dt-menu-leaf-active) .svnIcon,
+  .el-submenu__title .svnIcon {
+    color: #606266;
+    svg {
+      fill: #606266;
+    }
+  }
+
+  .el-menu-item.dt-menu-leaf-active,
+  .el-menu-item.is-active {
+    background-color: transparent !important;
+    color: var(--primary-color, #82780c) !important;
+
+    span {
+      color: var(--primary-color, #82780c) !important;
+    }
+
+    .el-icon-menu,
+    .svnIcon {
+      color: var(--primary-color, #82780c) !important;
+    }
+
+    .svnIcon svg {
+      fill: var(--primary-color, #82780c) !important;
+    }
+  }
+
+  .el-menu-item:hover:not(.is-active):not(.dt-menu-leaf-active),
+  .el-submenu__title:hover {
+    background-color: rgba(0, 0, 0, 0.04) !important;
+  }
+
+  .el-menu--inline {
+    background-color: #eff1f3 !important;
+  }
+
+  .el-submenu .el-menu-item {
+    padding-left: 44px !important;
   }
 }
 

+ 43 - 115
src/views/overview/components/analysis_information/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-card shadow="always" class="box-card" v-loading="loading">
+  <el-card shadow="always" class="box-card analysis-info-card" v-loading="loading">
     <div class="box-header">
       <h4 style="font-weight: 700">分析介绍:</h4>
       <div class="box-header-min">
@@ -149,7 +149,7 @@
                     class="WindPower3"
                     width="40px"
                     height="42px"
-                    color="#b7dff5"
+                    :color="turbineIconColor"
                   ></SvgIcons>
                   <span>{{ itemData.engineName }}机组</span>
                 </div>
@@ -207,6 +207,14 @@ export default {
     },
   },
 
+  computed: {
+    isTechBlueTheme() {
+      return this.$store.state.themes?.theme === "tech-blue";
+    },
+    turbineIconColor() {
+      return this.isTechBlueTheme ? "#b7dff5" : "#606266";
+    },
+  },
   data() {
     return {
       loading: false,
@@ -292,6 +300,22 @@ export default {
 </script>
 
 <style scoped lang="scss">
+.analysis-info-card {
+  width: 100%;
+  height: 100%;
+  overflow-y: auto;
+  background: transparent !important;
+  border: none !important;
+  box-shadow: none !important;
+  color: var(--text-cyan, #303133);
+}
+
+::v-deep .analysis-info-card .el-card__body {
+  width: 100%;
+  height: 100%;
+  background: transparent !important;
+}
+
 .completeAssetssType {
   display: flex;
   flex-wrap: wrap;
@@ -300,18 +324,6 @@ export default {
     margin-right: 10px;
     margin-bottom: 10px;
   }
-
-  ::v-deep .el-tag--warning {
-    background-color: rgba(0, 170, 255, 0.12) !important;
-    border-color: rgba(0, 170, 255, 0.35) !important;
-    color: #ffffff !important;
-  }
-}
-
-::v-deep .el-card__body {
-  width: 100%;
-  height: 100%;
-  background: transparent !important;
 }
 
 .content {
@@ -319,49 +331,31 @@ export default {
   display: flex;
   justify-content: space-between;
   height: 100%;
-  border-bottom: 1px solid var(--panel-border, rgba(0, 170, 255, 0.25));
+  border-bottom: 1px solid #ebeef5;
 }
 
-.box-min {
+.box-min,
+.box-header,
+.left .box-content-min {
   padding: 10px;
-  color: #ffffff;
+  color: var(--text-cyan, #303133);
 
-  h4 {
-    color: #ffffff;
+  h4,
+  h6 {
     font-size: 14px;
     font-weight: 500;
     margin-bottom: 10px;
+    color: var(--text-cyan, #303133);
   }
+}
 
-  ::v-deep .analysisTypes {
-    h6 {
-      font-weight: 500 !important;
-      font-size: 12px;
-      margin: 5px 0;
-      color: var(--text-cyan, #b7dff5);
-    }
-  }
-
-  .analysisTypes:last-child {
-    margin-bottom: 50px;
-  }
+.box-min .analysisTypes:last-child {
+  margin-bottom: 50px;
 }
 
 .left {
   width: 70%;
 
-  .box-content-min {
-    padding: 10px;
-    color: #ffffff;
-
-    h4 {
-      color: #ffffff;
-      font-size: 14px;
-      font-weight: 500;
-      margin-bottom: 10px;
-    }
-  }
-
   .analysisCompletionUnit {
     display: flex;
     flex-wrap: wrap;
@@ -371,28 +365,19 @@ export default {
     .itemAnalysisCompletionUnit {
       text-align: center;
       margin-bottom: 10px;
-      border: 1px solid var(--panel-border, rgba(0, 170, 255, 0.35));
-      background: rgba(0, 16, 32, 0.55);
       margin-right: 10px;
-      border-radius: 10px;
+      border-radius: 4px;
       padding: 10px 5px;
-      color: #ffffff;
       cursor: pointer;
       transition: background-color 0.2s ease, border-color 0.2s ease;
 
-      &:hover {
-        background: var(--bgopacity-color, rgba(0, 170, 255, 0.12));
-        border-color: rgba(0, 170, 255, 0.55);
+      .WindPower3 {
+        padding: 10px 5px;
+        text-align: center;
       }
 
       span {
         font-size: 12px;
-        color: #ffffff;
-      }
-
-      .WindPower3 {
-        padding: 10px 5px;
-        text-align: center;
       }
     }
   }
@@ -412,76 +397,19 @@ export default {
   border-radius: 90px;
 }
 
-.box-card {
-  width: 100%;
-  height: 100%;
-  overflow-y: scroll;
-  background: transparent !important;
-  border: none !important;
-  box-shadow: none !important;
-
-  &::-webkit-scrollbar {
-    width: 6px;
-  }
-
-  &::-webkit-scrollbar-track {
-    border-radius: 3px;
-  }
-
-  &::-webkit-scrollbar-thumb {
-    border-radius: 3px;
-  }
-
+.box-card.analysis-info-card {
   .box-header {
-    padding: 10px;
-    border-bottom: 1px solid var(--panel-border, rgba(0, 170, 255, 0.25));
-    color: #ffffff;
-
-    h4 {
-      color: #ffffff;
-      font-size: 14px;
-      font-weight: 500;
-    }
+    border-bottom: 1px solid #ebeef5;
   }
 
   .box-header-min {
-    padding: 10px;
     font-size: 14px;
-    color: #ffffff;
     line-height: 22px;
 
     .headerMessage {
       font-size: 16px;
-      color: #ffffff !important;
       font-weight: 600;
     }
   }
-
-  ::v-deep .el-descriptions {
-    color: #ffffff;
-  }
-
-  ::v-deep .el-descriptions__body {
-    background-color: transparent !important;
-    color: #ffffff !important;
-  }
-
-  ::v-deep .el-descriptions__body .el-descriptions-item__cell {
-    color: #ffffff !important;
-    background-color: transparent !important;
-  }
-
-  ::v-deep .el-descriptions-item__label {
-    color: var(--text-cyan, #b7dff5) !important;
-  }
-
-  ::v-deep .el-descriptions-item__content {
-    color: #ffffff !important;
-  }
-
-  ::v-deep .el-empty__description,
-  ::v-deep .el-empty__description p {
-    color: #ffffff !important;
-  }
 }
 </style>

+ 2 - 3
src/views/overview/components/filterChart/index.vue

@@ -69,12 +69,11 @@
           >查询</el-button
         >
         <el-button
-          type="primary"
           size="small"
           @click="onSubmit('handlePrevious')"
           >上一次分析结果</el-button
         >
-        <el-button type="primary" size="small" @click="onSubmit('handleNext')"
+        <el-button size="small" @click="onSubmit('handleNext')"
           >下一次分析结果</el-button
         >
       </el-form-item>
@@ -199,7 +198,7 @@ export default {
     font-size: 16px;
     font-weight: 500;
     margin: 10px 0;
-    color: #ffffff;
+    color: var(--text-cyan, #303133);
   }
 }
 // ::v-deep.el-input--small .el-input__inner {

+ 23 - 42
src/views/overview/index.vue

@@ -43,8 +43,8 @@
         class="el-menu-demo overview-top-menu"
         mode="horizontal"
         background-color="transparent"
-        text-color="#b7dff5"
-        active-text-color="#ffffff"
+        :text-color="overviewMenuTextColor"
+        :active-text-color="overviewMenuActiveTextColor"
         @select="handleSelect"
       >
         <template v-for="menuItem in assetssTypeData">
@@ -154,6 +154,24 @@
 import { queryAllAnalysisType } from "@/api/performance";
 import { getAnalysisCodeInfo } from "@/api/overview";
 export default {
+  computed: {
+    isTechBlueTheme() {
+      return this.$store.state.themes?.theme === "tech-blue";
+    },
+    overviewMenuTextColor() {
+      return this.isTechBlueTheme ? "#b7dff5" : "#606266";
+    },
+    overviewMenuActiveTextColor() {
+      if (this.isTechBlueTheme) return "#ffffff";
+      const themeColors = {
+        light: "#82780c",
+        green: "#008080",
+        blue: "#1a3b7f",
+        dark: "#607d8b",
+      };
+      return themeColors[this.$store.state.themes?.theme] || "#82780c";
+    },
+  },
   data() {
     return {
       defalueExpandAll: process.env.VUE_APP_Helath === "demo" ? true : false,
@@ -463,14 +481,14 @@ export default {
   display: flex;
   height: 89vh;
   overflow: hidden;
-  color: #ffffff;
+  color: var(--text-cyan, #303133);
 
   .left {
     width: 200px;
     padding-right: 20px;
     margin-bottom: 20px;
     overflow-y: scroll;
-    border-right: 1px solid var(--panel-border, rgba(0, 170, 255, 0.25));
+    border-right: 1px solid #ebeef5;
 
     .leftTitle {
       display: flex;
@@ -478,7 +496,7 @@ export default {
       justify-content: space-between;
 
       .el-dropdown-link {
-        color: var(--text-cyan, #b7dff5);
+        color: var(--text-cyan, #606266);
         cursor: pointer;
       }
 
@@ -551,43 +569,6 @@ export default {
     padding: 0 10px !important;
   }
 
-  ::v-deep
-    .el-tree--highlight-current
-    .el-tree-node.is-current
-    > .el-tree-node__content {
-    background-color: #1890ff !important;
-    color: #ffffff !important;
-    border-radius: 4px;
-  }
-
-  ::v-deep
-    .el-tree-node.is-current
-    > .el-tree-node__content
-    .el-tree-node__label {
-    color: #ffffff !important;
-  }
-
-  ::v-deep .overview-top-menu.el-menu--horizontal {
-    > .el-menu-item.is-active {
-      color: #ffffff !important;
-      background-color: rgba(24, 144, 255, 0.22) !important;
-      border-bottom-color: #00aaff !important;
-    }
-
-    > .el-submenu.is-active > .el-submenu__title {
-      color: #ffffff !important;
-      background-color: rgba(24, 144, 255, 0.22) !important;
-      border-bottom-color: #00aaff !important;
-    }
-
-    > .el-menu-item:hover,
-    > .el-menu-item:focus,
-    > .el-submenu .el-submenu__title:hover,
-    > .el-submenu .el-submenu__title:focus {
-      background-color: rgba(24, 144, 255, 0.12) !important;
-      color: #ffffff !important;
-    }
-  }
 }
 
 .el-dialog__wrapper {

+ 1 - 0
src/views/performance/assetssMag.vue

@@ -314,6 +314,7 @@
               type="text"
               size="small"
               :disabled="downloadDisabled"
+              v-hasPermi="['home:performance:autoAssetssDownload']"
               v-if="
                 (scope.row.errState == 1 && scope.row.analysisState == 30) ||
                 scope.row.analysisState == 30

+ 13 - 34
src/views/performance/components/analysisEvent.vue

@@ -807,47 +807,32 @@ export default {
 </script>
 <style scoped lang="scss">
 .analysis-event-page {
-  color: #ffffff;
-
-  ::v-deep .el-form-item__label {
-    color: #b7dff5 !important;
-  }
-
-  ::v-deep .el-form-item__content {
-    color: #ffffff;
-  }
+  color: var(--text-cyan, #303133);
 
   ::v-deep .el-collapse {
-    // border-color: var(--panel-border, rgba(0, 170, 255, 0.35)) !important;
     background: transparent;
     margin: 0 10px;
+    border-color: #ebeef5;
   }
 
   ::v-deep .el-collapse-item__header {
-    background: linear-gradient(90deg, #001529 0%, #002a5c 100%) !important;
-    color: #ffffff !important;
-    border-color: var(--panel-border, rgba(0, 170, 255, 0.35)) !important;
     font-size: 15px;
-    padding: 0px 16px;
+    padding: 0 16px;
     font-weight: 500;
   }
 
-  ::v-deep .el-collapse-item__wrap {
-    background: var(--panel-bg, #000c17) !important;
-    // border-color: var(--panel-border, rgba(0, 170, 255, 0.35)) !important;
-  }
-
   ::v-deep .el-collapse-item__content {
-    color: #ffffff !important;
     padding: 16px 12px;
   }
 
   ::v-deep .setting {
-    color: #b7dff5;
+    color: var(--primary-color, #82780c);
     cursor: pointer;
+    font-size: 24px;
+    margin-left: 10px;
 
     &:hover {
-      color: #ffffff;
+      opacity: 0.85;
     }
   }
 }
@@ -858,15 +843,12 @@ export default {
   font-size: 14px;
   height: 100%;
   background: transparent !important;
-  // border: 1px solid var(--panel-border, rgba(0, 170, 255, 0.35)) !important;
   box-shadow: none !important;
+  border: 1px solid #ebeef5;
 
   .filedView {
     height: 60px;
-    background: linear-gradient(90deg, #001529 0%, #002a5c 100%);
-    color: #ffffff;
     cursor: pointer;
-    // border-bottom: 1px solid var(--panel-border, rgba(0, 170, 255, 0.35));
     font-size: 16px;
     text-align: center;
     display: flex;
@@ -880,8 +862,6 @@ export default {
   ::v-deep .el-card__body {
     width: 100%;
     height: 100% !important;
-    background: var(--panel-bg, #000c17) !important;
-    color: #ffffff;
 
     .left {
       > div {
@@ -901,7 +881,6 @@ export default {
     .right {
       height: 500px;
       width: 100%;
-      // border: 1px solid var(--panel-border, rgba(0, 170, 255, 0.25));
       border-radius: 4px;
       overflow: hidden;
 
@@ -962,19 +941,19 @@ export default {
 
   .drawerLeft {
     margin-left: 20px;
-    color: #ffffff !important;
+    color: var(--text-cyan, #303133) !important;
   }
 
   .drawer-section-title {
     text-align: center;
     font-size: 16px;
-    color: #ffffff;
+    color: var(--text-cyan, #303133);
     margin-bottom: 8px;
   }
 
   .checkFromRow {
     .el-col-12 {
-      color: #ffffff !important;
+      color: var(--text-cyan, #303133) !important;
       font-size: 14px;
     }
 
@@ -985,13 +964,13 @@ export default {
 
   .drawerRight {
     margin-right: 20px;
-    color: #ffffff !important;
+    color: var(--text-cyan, #303133) !important;
     font-size: 14px;
 
     ul {
       list-style-type: none;
       padding: 0;
-      color: #ffffff !important;
+      color: var(--text-cyan, #303133) !important;
 
       li {
         display: flex;

+ 2 - 2
src/views/performance/components/custonAsCom/dragChart/components/chartConfig/form/chartTitle.vue

@@ -1454,14 +1454,14 @@ export default {
 
   .el-form-item__label {
     width: 100% !important;
-    color: #ffffff !important;
+    color: var(--drag-chart-label, #606266) !important;
   }
 
   .attributeItemData {
     display: flex;
     justify-content: space-between;
     align-items: center;
-    color: #ffffff;
+    color: var(--drag-chart-text, #303133);
   }
 }
 .el-divider--horizontal {

+ 7 - 7
src/views/performance/components/custonAsCom/dragChart/components/chartsAttributes.vue

@@ -211,8 +211,8 @@ export default {
 .chartContiner {
   width: 268px;
   height: 100%;
-  background: var(--panel-bg, #000c17);
-  color: #ffffff;
+  background: var(--drag-chart-panel-bg, #ffffff);
+  color: var(--drag-chart-text, #303133);
 
   ::v-deep .card {
     height: 100%;
@@ -225,23 +225,23 @@ export default {
 
   ::v-deep .el-card__body {
     background: transparent !important;
-    color: #ffffff;
+    color: var(--drag-chart-text, #303133);
   }
 
   ::v-deep .el-tabs__item {
-    color: #b7dff5 !important;
+    color: var(--drag-chart-tab-text, #606266) !important;
   }
 
   ::v-deep .el-tabs__item.is-active {
-    color: #ffffff !important;
+    color: var(--drag-chart-tab-active-text, var(--primary-color, #82780c)) !important;
   }
 
   ::v-deep .el-form-item__label {
-    color: #ffffff !important;
+    color: var(--drag-chart-label, #606266) !important;
   }
 
   ::v-deep .el-divider {
-    background-color: rgba(0, 170, 255, 0.25) !important;
+    background-color: var(--drag-chart-divider, #ebeef5) !important;
   }
 
   .toolbox {

+ 22 - 7
src/views/performance/components/custonAsCom/dragChart/components/chartsContent.vue

@@ -14,9 +14,9 @@
       :unit="10"
       :scale="1"
       :precision="0"
-      :rulerColor="'#3a6ea5'"
-      :bgColor="'#061020'"
-      :cornerColor="'#b7dff5'"
+      :rulerColor="rulerColor"
+      :bgColor="rulerBgColor"
+      :cornerColor="rulerCornerColor"
       :guidelineColor="'#f00'"
       :cornerSize="10"
       :scrollLeft="0"
@@ -115,6 +115,15 @@ export default {
       currentChartList: "currentChartList",
       curChart: "curEdit",
     }),
+    rulerColor() {
+      return this.getThemeCssVar("--drag-chart-ruler-color", "#909399");
+    },
+    rulerBgColor() {
+      return this.getThemeCssVar("--drag-chart-ruler-bg", "#f5f7fa");
+    },
+    rulerCornerColor() {
+      return this.getThemeCssVar("--drag-chart-corner-color", "#606266");
+    },
   },
   mounted() {
     this.getScrollSize();
@@ -130,6 +139,12 @@ export default {
       // "recordOriginChartList",
       // "restoreOriginChartList",
     ]),
+    getThemeCssVar(name, fallback) {
+      const value = getComputedStyle(document.documentElement)
+        .getPropertyValue(name)
+        .trim();
+      return value || fallback;
+    },
     // ruler-tool 组件标尺滚动事件
     handleScroll({ scrollLeft, scrollTop }) {
       console.log("标尺滚动:", scrollLeft, scrollTop);
@@ -245,16 +260,16 @@ export default {
   height: 100%;
   width: 1800px;
   overflow: scroll;
-  background: var(--content-bg, #000510);
+  background: var(--drag-chart-page-bg, var(--content-bg, #f0f2f5));
 
   .canvas-wrapper {
     position: relative;
     width: var(--canvasWidth);
     height: var(--canvasHeight);
-    background: #0a1628;
+    background: var(--drag-chart-canvas-bg, #ffffff);
     border-radius: 4px;
-    border: 1px solid rgba(0, 170, 255, 0.35);
-    box-shadow: inset 0 0 24px rgba(0, 25, 50, 0.35);
+    border: 1px solid var(--drag-chart-canvas-border, #dcdfe6);
+    box-shadow: none;
 
     .info {
       opacity: 0;

+ 6 - 6
src/views/performance/components/custonAsCom/dragChart/components/chartsData.vue

@@ -197,14 +197,14 @@ export default {
 .el-tree {
   overflow: hidden !important;
   background: transparent !important;
-  color: #ffffff !important;
+  color: var(--drag-chart-text, #303133) !important;
 }
 
 .chartContiner {
   width: 300px;
   height: 100%;
-  background: var(--panel-bg, #000c17);
-  color: #ffffff;
+  background: var(--drag-chart-panel-bg, #ffffff);
+  color: var(--drag-chart-text, #303133);
 
   ::v-deep .card {
     height: 100%;
@@ -217,15 +217,15 @@ export default {
 
   ::v-deep .el-card__body {
     background: transparent !important;
-    color: #ffffff;
+    color: var(--drag-chart-text, #303133);
   }
 
   ::v-deep .el-tree-node__content {
-    color: #ffffff !important;
+    color: var(--drag-chart-text, #303133) !important;
   }
 
   ::v-deep .el-tree-node__content:hover {
-    background-color: rgba(24, 144, 255, 0.12) !important;
+    background-color: var(--drag-chart-tree-hover, #f5f7fa) !important;
   }
 
   ::v-deep .el-radio {

+ 13 - 13
src/views/performance/components/custonAsCom/dragChart/index.vue

@@ -106,8 +106,8 @@ export default {
   display: flex;
   gap: 5px;
   align-items: flex-start;
-  background-color: var(--content-bg, #000510);
-  color: #ffffff;
+  background-color: var(--drag-chart-page-bg, var(--content-bg, #f0f2f5));
+  color: var(--drag-chart-text, #303133);
 }
 
 .drawer-box {
@@ -118,9 +118,9 @@ export default {
   overflow: scroll;
 
   .drawer-text {
-    background: linear-gradient(180deg, #001529 0%, #002a5c 100%);
-    border: 1px solid var(--panel-border, rgba(0, 170, 255, 0.35));
-    color: #ffffff;
+    background: var(--drag-chart-drawer-collapsed-bg, #f5f7fa);
+    border: 1px solid var(--drag-chart-drawer-collapsed-border, #dcdfe6);
+    color: var(--drag-chart-drawer-collapsed-text, #606266);
     writing-mode: vertical-rl;
     text-orientation: upright;
     height: 100%;
@@ -129,13 +129,13 @@ export default {
     transition: background-color 0.2s ease;
 
     &:hover {
-      background: rgba(24, 144, 255, 0.18);
+      opacity: 0.88;
     }
   }
 }
 
 .drawer-btn {
-  color: #ffffff;
+  color: var(--drag-chart-header-text, #ffffff);
   background: transparent;
   border: none;
   border-radius: 4px;
@@ -147,10 +147,10 @@ export default {
 
 .drawer-panel {
   width: 270px;
-  background: var(--panel-bg, #000c17);
-  box-shadow: 0 0 16px rgba(0, 100, 255, 0.12);
+  background: var(--drag-chart-panel-bg, #ffffff);
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
   border-radius: 4px;
-  border: 1px solid var(--panel-border, rgba(0, 170, 255, 0.35));
+  border: 1px solid var(--drag-chart-panel-border, #ebeef5);
   height: 100%;
   transition: all 0.3s ease;
   overflow: hidden;
@@ -160,12 +160,12 @@ export default {
     padding: 12px 14px;
     justify-content: space-between;
     align-items: center;
-    background: linear-gradient(90deg, #001529 0%, #002a5c 100%);
-    border-bottom: 1px solid var(--panel-border, rgba(0, 170, 255, 0.35));
+    background: var(--drag-chart-header-bg, var(--primary-color, #82780c));
+    border-bottom: 1px solid var(--drag-chart-panel-border, #ebeef5);
     cursor: pointer;
 
     .el-icon-s-fold {
-      color: #b7dff5;
+      color: var(--drag-chart-header-text, #ffffff);
       font-size: 16px;
     }
   }

+ 10 - 10
src/views/performance/customAnalysis.vue

@@ -660,27 +660,27 @@ export default {
 .content {
   width: 100%;
   height: 88vh;
-  background: var(--content-bg, #000510);
-  color: #ffffff;
+  background: var(--custom-analysis-bg, var(--content-bg, #f5f7fa));
+  color: var(--custom-analysis-text, #303133);
 
   ::v-deep .el-tabs__active-bar {
     transition: transform 0.3s ease;
   }
 
   ::v-deep .el-tabs--left .el-tabs__header {
-    background: var(--panel-bg, #000c17);
-    border-right: 1px solid var(--panel-border, rgba(0, 170, 255, 0.35));
+    background: var(--custom-analysis-sidebar-bg, #f5f7fa);
+    border-right: 1px solid var(--custom-analysis-border, #ebeef5);
   }
 
   ::v-deep .el-tabs--left .el-tabs__item {
     height: 55px;
     line-height: 55px;
-    color: #b7dff5 !important;
+    color: var(--custom-analysis-tab-text, #606266) !important;
   }
 
   ::v-deep .el-tabs--left .el-tabs__item.is-active {
-    color: #ffffff !important;
-    background: rgba(24, 144, 255, 0.18);
+    color: var(--custom-analysis-tab-active-text, var(--primary-color, #82780c)) !important;
+    background: var(--custom-analysis-tab-active-bg, rgba(0, 0, 0, 0.04));
   }
 
   ::v-deep .el-tabs__item {
@@ -689,7 +689,7 @@ export default {
 
   ::v-deep .el-tabs__content {
     height: 100% !important;
-    background: var(--content-bg, #000510);
+    background: var(--custom-analysis-content-bg, var(--content-bg, #f5f7fa));
   }
 
   .popover-footer {
@@ -719,7 +719,7 @@ export default {
     .line {
       display: flex;
       justify-content: center;
-      color: #ffffff;
+      color: var(--custom-analysis-text, #303133);
     }
   }
 
@@ -734,6 +734,6 @@ export default {
   flex: 1;
   align-items: center;
   cursor: pointer;
-  color: #ffffff;
+  color: var(--custom-analysis-text, #303133);
 }
 </style>

+ 145 - 0
src/views/windResourceWake/components/WrwFilterBar.vue

@@ -0,0 +1,145 @@
+<template>
+  <div class="wrw-filter-bar">
+    <div class="wrw-filter-item wrw-filter-item--farm">
+      <span class="wrw-filter-label">风场:</span>
+      <selecttree
+        class="wrw-filter-selecttree"
+        size="small"
+        popper-class="wrw-tree-popper"
+        placeholder="请选择所属公司"
+        :list="parentOpt"
+        type="1"
+        v-model="localFarm"
+        @change="handleSelectChange"
+        :defaultParentProps="defaultParentProps"
+      />
+    </div>
+    <div class="wrw-filter-item">
+      <span class="wrw-filter-label">时间:</span>
+      <el-date-picker
+        v-model="localDate"
+        type="date"
+        placeholder="选择日期"
+        size="small"
+        value-format="yyyy-MM-dd"
+        class="wrw-filter-date"
+        clearable
+        popper-class="wrw-date-popper"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+import selecttree from "@/components/selecttree.vue";
+import { getSysOrganizationAuthTreeByRoleId } from "@/api/ledger.js";
+
+export default {
+  name: "WrwFilterBar",
+  components: {
+    selecttree,
+  },
+  props: {
+    farm: { type: String, default: "" },
+    date: { type: String, default: "2024-04-28" },
+  },
+  data() {
+    return {
+      localFarm: this.farm,
+      localDate: this.date,
+      parentOpt: [],
+      defaultParentProps: {
+        children: "children",
+        label: "companyName",
+        value: "codeNumber",
+      },
+    };
+  },
+  watch: {
+    farm(val) {
+      this.localFarm = val;
+    },
+    date(val) {
+      this.localDate = val;
+    },
+    localFarm(val) {
+      this.$emit("update:farm", val);
+    },
+    localDate(val) {
+      this.$emit("update:date", val);
+    },
+  },
+  created() {
+    this.loadTree();
+  },
+  methods: {
+    async loadTree() {
+      try {
+        const res = await getSysOrganizationAuthTreeByRoleId();
+        const processedData = this.processTreeData(res.data || []);
+        this.parentOpt = processedData;
+
+        const defaultData = (res.data || [])[0];
+        if (defaultData && !this.localFarm) {
+          this.$nextTick(() => {
+            this.handleSelectChange(defaultData);
+          });
+        }
+      } catch (error) {
+        console.error("加载组织树失败:", error);
+      }
+    },
+    processTreeData(treeData) {
+      const processedData = [];
+      const processNode = (node) => {
+        if (node.codeType === "field") {
+          node.companyName = node.fieldName;
+        }
+        if (node.children && node.children.length > 0) {
+          node.children.forEach(processNode);
+        }
+      };
+      treeData.forEach((root) => {
+        processNode(root);
+        processedData.push(root);
+      });
+      return processedData;
+    },
+    handleSelectChange(data) {
+      if (!data || !data.codeNumber) return;
+      this.localFarm = data.codeNumber;
+      this.$emit("update:farm", data.codeNumber);
+      this.$emit("farm-change", data);
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.wrw-filter-item--farm {
+  gap: 6px;
+}
+
+.wrw-filter-farm-icon {
+  color: #5ab3dc;
+  font-size: 14px;
+  flex-shrink: 0;
+}
+
+.wrw-filter-selecttree {
+  width: 180px;
+
+  ::v-deep .el-input__inner {
+    background: transparent !important;
+    border: none !important;
+    color: #d9efff !important;
+    padding-left: 0 !important;
+    height: 28px;
+    line-height: 28px;
+  }
+
+  ::v-deep .el-input__icon {
+    color: #5ab3dc !important;
+  }
+}
+</style>

+ 110 - 0
src/views/windResourceWake/components/WrwOverviewTab.vue

@@ -0,0 +1,110 @@
+<template>
+  <div class="wrw-overview-tab">
+    <div class="wrw-overview-section">
+      <div class="wrw-overview-section-title">A2 各机组风速分布</div>
+      <p class="wrw-overview-section-desc">各机组风速概率密度对比</p>
+      <div class="wrw-card wrw-overview-card">
+        <div ref="windDistRef" class="wrw-overview-chart"></div>
+      </div>
+    </div>
+    <div class="wrw-overview-section">
+      <div class="wrw-overview-section-title">风向玫瑰图</div>
+      <p class="wrw-overview-section-desc">
+        各机组风向与风速段分布(N 为正北)
+      </p>
+      <div class="wrw-card wrw-overview-card">
+        <div class="wrw-wind-rose-grid">
+          <div
+            v-for="(name, index) in turbineNames"
+            :key="name"
+            class="wrw-wind-rose-cell"
+          >
+            <div class="wrw-wind-rose-cell-title">{{ name }}</div>
+            <div
+              :ref="'windRoseRef' + index"
+              class="wrw-wind-rose-cell-chart"
+            ></div>
+          </div>
+        </div>
+        <div class="wrw-wind-rose-legend">
+          <span
+            v-for="(band, index) in roseSpeedBands"
+            :key="band"
+            class="wrw-wind-rose-legend-item"
+          >
+            <i :style="{ backgroundColor: roseColors[index] }"></i>
+            {{ band }}
+          </span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import * as echarts from "echarts";
+import { turbineNames } from "../data/mockData";
+import {
+  getWindDistOption,
+  getSingleWindRoseOption,
+  ROSE_SPEED_BANDS,
+  ROSE_COLORS,
+} from "../utils/overviewChartOptions";
+
+export default {
+  name: "WrwOverviewTab",
+  data() {
+    return {
+      turbineNames,
+      roseSpeedBands: ROSE_SPEED_BANDS,
+      roseColors: ROSE_COLORS,
+      charts: {},
+      inited: false,
+    };
+  },
+  mounted() {
+    window.addEventListener("resize", this.handleResize);
+  },
+  beforeDestroy() {
+    window.removeEventListener("resize", this.handleResize);
+    this.disposeCharts();
+  },
+  methods: {
+    initCharts() {
+      if (this.inited) {
+        this.handleResize();
+        return;
+      }
+      this.initChart("windDist", this.$refs.windDistRef, getWindDistOption());
+      this.turbineNames.forEach((_, index) => {
+        const ref = this.$refs[`windRoseRef${index}`];
+        const el = Array.isArray(ref) ? ref[0] : ref;
+        this.initChart(`windRose${index}`, el, getSingleWindRoseOption(index));
+      });
+      this.inited = true;
+    },
+    initChart(key, el, option) {
+      if (!el) return;
+      if (this.charts[key]) {
+        this.charts[key].dispose();
+      }
+      this.charts[key] = echarts.init(el);
+      this.charts[key].setOption(option);
+    },
+    disposeCharts() {
+      Object.keys(this.charts).forEach((key) => {
+        if (this.charts[key]) {
+          this.charts[key].dispose();
+        }
+      });
+      this.charts = {};
+      this.inited = false;
+    },
+    handleResize() {
+      Object.values(this.charts).forEach((chart) => {
+        if (chart) chart.resize();
+      });
+    },
+  },
+};
+</script>

+ 83 - 0
src/views/windResourceWake/components/WrwWakeTab.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="wrw-wake-tab">
+    <div class="wrw-section-title">速度与损失分布</div>
+
+    <div class="wrw-row">
+      <div class="wrw-col wrw-card">
+        <div ref="scatter1Ref" class="wrw-chart"></div>
+      </div>
+      <div class="wrw-col wrw-card">
+        <div ref="scatter2Ref" class="wrw-chart"></div>
+      </div>
+    </div>
+
+    <div class="wrw-section-title">尾流影响评估</div>
+
+    <div class="wrw-card">
+      <div ref="barRef" class="wrw-chart wrw-chart--bar"></div>
+      <WrwWakeTable />
+    </div>
+  </div>
+</template>
+
+<script>
+import * as echarts from "echarts";
+import WrwWakeTable from "./WrwWakeTable.vue";
+import {
+  getScatter1Option,
+  getScatter2Option,
+  getBarOption,
+} from "../utils/wakeChartOptions";
+
+export default {
+  name: "WrwWakeTab",
+  components: { WrwWakeTable },
+  data() {
+    return {
+      charts: {},
+      inited: false,
+    };
+  },
+  mounted() {
+    window.addEventListener("resize", this.handleResize);
+  },
+  beforeDestroy() {
+    window.removeEventListener("resize", this.handleResize);
+    this.disposeCharts();
+  },
+  methods: {
+    initCharts() {
+      if (this.inited) {
+        this.handleResize();
+        return;
+      }
+      this.initChart("scatter1", this.$refs.scatter1Ref, getScatter1Option());
+      this.initChart("scatter2", this.$refs.scatter2Ref, getScatter2Option());
+      this.initChart("bar", this.$refs.barRef, getBarOption());
+      this.inited = true;
+    },
+    initChart(key, el, option) {
+      if (!el) return;
+      if (this.charts[key]) {
+        this.charts[key].dispose();
+      }
+      this.charts[key] = echarts.init(el);
+      this.charts[key].setOption(option);
+    },
+    disposeCharts() {
+      Object.keys(this.charts).forEach((key) => {
+        if (this.charts[key]) {
+          this.charts[key].dispose();
+        }
+      });
+      this.charts = {};
+      this.inited = false;
+    },
+    handleResize() {
+      Object.values(this.charts).forEach((chart) => {
+        if (chart) chart.resize();
+      });
+    },
+  },
+};
+</script>

+ 142 - 0
src/views/windResourceWake/components/WrwWakeTable.vue

@@ -0,0 +1,142 @@
+<template>
+  <div class="wrw-wake-table">
+    <div class="wrw-table-wrapper">
+      <table class="wrw-table">
+        <thead>
+          <tr>
+            <th>风机名称</th>
+            <th>是否受尾流影响</th>
+            <th>附加湍流强度</th>
+            <th>尾流速度损耗</th>
+            <th>置信度</th>
+            <th>图片</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="item in pageData" :key="item.name">
+            <td>{{ item.name }}</td>
+            <td>
+              <span
+                :class="
+                  item.affected === 'yes' ? 'wrw-status-yes' : 'wrw-status-no'
+                "
+              >
+                {{ item.affected === "yes" ? "是" : "否" }}
+              </span>
+            </td>
+            <td :class="getTurbulenceClass(item.turbulence)">
+              {{ item.turbulence }}
+            </td>
+            <td :class="getLossClass(item.loss)">{{ item.loss }}</td>
+            <td :class="getConfidenceClass(item.confidence)">
+              {{ item.confidence }}
+            </td>
+            <td>
+              <div class="wrw-image-container" title="点击查看大图">
+                <el-image
+                  class="wrw-table-thumb"
+                  :src="item.image"
+                  :preview-src-list="[item.image]"
+                  fit="cover"
+                >
+                  <div slot="error" class="wrw-table-thumb-error">
+                    <i class="el-icon-picture-outline"></i>
+                  </div>
+                </el-image>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <div class="wrw-pagination">
+      <button type="button" :disabled="currentPage === 1" @click="goToPage(1)">
+        首页
+      </button>
+      <button type="button" :disabled="currentPage === 1" @click="prevPage">
+        上一页
+      </button>
+      <span class="wrw-page-info"
+        >第 {{ currentPage }} 页 / 共 {{ totalPages }} 页</span
+      >
+      <button
+        type="button"
+        :disabled="currentPage === totalPages"
+        @click="nextPage"
+      >
+        下一页
+      </button>
+      <button
+        type="button"
+        :disabled="currentPage === totalPages"
+        @click="goToPage(totalPages)"
+      >
+        末页
+      </button>
+      <select
+        v-model.number="pageSize"
+        class="wrw-page-size-select"
+        @change="handlePageSizeChange"
+      >
+        <option :value="3">每页3条</option>
+        <option :value="5">每页5条</option>
+        <option :value="10">每页10条</option>
+      </select>
+    </div>
+  </div>
+</template>
+
+<script>
+import {
+  tableData,
+  getTurbulenceClass,
+  getLossClass,
+  getConfidenceClass,
+} from "../data/mockData";
+
+export default {
+  name: "WrwWakeTable",
+  data() {
+    return {
+      tableData,
+      currentPage: 1,
+      pageSize: 5,
+    };
+  },
+  computed: {
+    totalPages() {
+      return Math.max(1, Math.ceil(this.tableData.length / this.pageSize));
+    },
+    pageData() {
+      const start = (this.currentPage - 1) * this.pageSize;
+      return this.tableData.slice(start, start + this.pageSize);
+    },
+  },
+  watch: {
+    pageSize() {
+      if (this.currentPage > this.totalPages) {
+        this.currentPage = this.totalPages;
+      }
+    },
+  },
+  methods: {
+    getTurbulenceClass,
+    getLossClass,
+    getConfidenceClass,
+    goToPage(page) {
+      if (page < 1 || page > this.totalPages) return;
+      this.currentPage = page;
+    },
+    prevPage() {
+      if (this.currentPage > 1) this.currentPage -= 1;
+    },
+    nextPage() {
+      if (this.currentPage < this.totalPages) this.currentPage += 1;
+    },
+    handlePageSizeChange() {
+      this.currentPage = 1;
+    },
+  },
+};
+</script>

+ 131 - 0
src/views/windResourceWake/data/mockData.js

@@ -0,0 +1,131 @@
+export const turbineNames = [
+  "JH03",
+  "JH04",
+  "JH05",
+  "JH06",
+  "JH07",
+  "JH08",
+  "JH09",
+  "JH10",
+  "JH11",
+];
+
+const wakeTableImage = require("../images/image(3).png");
+
+export const tableData = [
+  {
+    name: "风机01",
+    affected: "yes",
+    turbulence: 80,
+    loss: 2,
+    confidence: "90%",
+    image: wakeTableImage,
+  },
+  {
+    name: "风机02",
+    affected: "no",
+    turbulence: 90,
+    loss: 1,
+    confidence: "80%",
+    image: wakeTableImage,
+  },
+  {
+    name: "风机03",
+    affected: "no",
+    turbulence: 90,
+    loss: 1,
+    confidence: "80%",
+    image: wakeTableImage,
+  },
+  {
+    name: "风机04",
+    affected: "yes",
+    turbulence: 75,
+    loss: 3,
+    confidence: "85%",
+    image: wakeTableImage,
+  },
+  {
+    name: "风机05",
+    affected: "no",
+    turbulence: 45,
+    loss: 1,
+    confidence: "95%",
+    image: wakeTableImage,
+  },
+  {
+    name: "风机06",
+    affected: "yes",
+    turbulence: 68,
+    loss: 4,
+    confidence: "78%",
+    image: wakeTableImage,
+  },
+  {
+    name: "风机07",
+    affected: "no",
+    turbulence: 55,
+    loss: 1,
+    confidence: "92%",
+    image: wakeTableImage,
+  },
+  {
+    name: "风机08",
+    affected: "yes",
+    turbulence: 72,
+    loss: 3,
+    confidence: "88%",
+    image: wakeTableImage,
+  },
+  {
+    name: "风机09",
+    affected: "no",
+    turbulence: 48,
+    loss: 2,
+    confidence: "90%",
+    image: wakeTableImage,
+  },
+  {
+    name: "风机10",
+    affected: "yes",
+    turbulence: 85,
+    loss: 5,
+    confidence: "75%",
+    image: wakeTableImage,
+  },
+  {
+    name: "风机11",
+    affected: "no",
+    turbulence: 52,
+    loss: 1,
+    confidence: "94%",
+    image: wakeTableImage,
+  },
+  {
+    name: "风机12",
+    affected: "yes",
+    turbulence: 70,
+    loss: 3,
+    confidence: "86%",
+    image: wakeTableImage,
+  },
+];
+
+export function getTurbulenceClass(value) {
+  if (value >= 80) return "wrw-value-high";
+  if (value >= 60) return "wrw-value-medium";
+  return "wrw-value-low";
+}
+
+export function getLossClass(value) {
+  if (value >= 3) return "wrw-value-high";
+  if (value >= 2) return "wrw-value-medium";
+  return "wrw-value-low";
+}
+
+export function getConfidenceClass(value) {
+  const num = parseInt(value, 10);
+  if (num >= 90) return "wrw-value-high";
+  if (num >= 80) return "wrw-value-medium";
+  return "wrw-value-low";
+}

BIN
src/views/windResourceWake/images/image(3).png


+ 166 - 0
src/views/windResourceWake/index.vue

@@ -0,0 +1,166 @@
+<template>
+  <div class="wind-resource-wake-page">
+    <WrwFilterBar
+      :farm.sync="selectedFarm"
+      :date.sync="selectedDate"
+      @farm-change="handleFarmChange"
+    />
+
+    <div class="wrw-tab-bar">
+      <button
+        v-for="tab in tabs"
+        :key="tab.key"
+        type="button"
+        class="wrw-tab-btn"
+        :class="{ active: activeTab === tab.key }"
+        @click="switchTab(tab.key)"
+      >
+        {{ tab.label }}
+      </button>
+    </div>
+
+    <WrwOverviewTab v-show="activeTab === 'overview'" ref="overviewTab" />
+    <WrwWakeTab v-show="activeTab === 'wake'" ref="wakeTab" />
+  </div>
+</template>
+
+<script>
+import WrwFilterBar from "./components/WrwFilterBar.vue";
+import WrwOverviewTab from "./components/WrwOverviewTab.vue";
+import WrwWakeTab from "./components/WrwWakeTab.vue";
+
+export default {
+  name: "WindResourceWake",
+  components: {
+    WrwFilterBar,
+    WrwOverviewTab,
+    WrwWakeTab,
+  },
+  data() {
+    return {
+      activeTab: "overview",
+      selectedFarm: "",
+      selectedDate: "2024-04-28",
+      tabs: [
+        { key: "overview", label: "资源概览" },
+        { key: "wake", label: "尾流情况" },
+      ],
+    };
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initActiveTabCharts();
+      window.dispatchEvent(new Event("resize"));
+    });
+  },
+  methods: {
+    switchTab(tabKey) {
+      this.activeTab = tabKey;
+      this.$nextTick(() => {
+        this.initActiveTabCharts();
+      });
+    },
+    initActiveTabCharts() {
+      if (this.activeTab === "overview" && this.$refs.overviewTab) {
+        this.$refs.overviewTab.initCharts();
+      }
+      if (this.activeTab === "wake" && this.$refs.wakeTab) {
+        this.$refs.wakeTab.initCharts();
+      }
+    },
+    handleFarmChange(data) {
+      if (!data || !data.codeNumber) return;
+      // TODO: 切换风场/组织后刷新尾流分析数据
+      console.log("当前选中:", data.companyName || data.fieldName, data);
+    },
+  },
+};
+</script>
+
+<style lang="scss">
+@import "./styles/windResourceWake.scss";
+
+.wind-resource-wake-page {
+  .wrw-filter-date {
+    .el-input__prefix {
+      display: none !important;
+    }
+
+    .el-input__inner {
+      background: transparent !important;
+      border: none !important;
+      color: #d9efff !important;
+      padding-left: 0 !important;
+      padding-right: 28px !important;
+    }
+
+    .el-input__suffix,
+    .el-input__icon {
+      color: #5ab3dc !important;
+    }
+  }
+
+  .wrw-filter-selecttree {
+    .el-input__inner {
+      background: transparent !important;
+      border: none !important;
+      color: #d9efff !important;
+      padding-left: 0 !important;
+      padding-right: 28px !important;
+    }
+
+    .el-input__suffix,
+    .el-input__icon {
+      color: #5ab3dc !important;
+    }
+  }
+}
+
+.wrw-tree-popper.selecttree-popper {
+  background: #0d1930 !important;
+  border: 1px solid rgba(0, 242, 255, 0.25) !important;
+
+  .popper__arrow::after {
+    border-bottom-color: #0d1930 !important;
+  }
+
+  .el-select-dropdown__item.hasTree {
+    background: #0d1930 !important;
+
+    &.hover,
+    &:hover,
+    &.selected {
+      background: #0d1930 !important;
+    }
+  }
+
+  .el-tree {
+    background: transparent !important;
+    color: #b7dff5 !important;
+  }
+
+  .el-tree-node__label {
+    color: #b7dff5 !important;
+  }
+
+  .el-tree-node__content:hover {
+    background: rgba(0, 170, 255, 0.15) !important;
+  }
+
+  .el-tree-node__content:hover .el-tree-node__label {
+    color: #ffffff !important;
+  }
+
+  .el-tree-node.is-current > .el-tree-node__content,
+  .el-tree--highlight-current
+    .el-tree-node.is-current
+    > .el-tree-node__content {
+    background: #1890ff !important;
+    color: #ffffff !important;
+  }
+
+  .el-tree-node.is-current > .el-tree-node__content .el-tree-node__label {
+    color: #ffffff !important;
+  }
+}
+</style>

+ 455 - 0
src/views/windResourceWake/styles/windResourceWake.scss

@@ -0,0 +1,455 @@
+.wind-resource-wake-page {
+  height: 100%;
+  min-height: 0;
+  overflow-y: auto;
+  overflow-x: hidden;
+  padding: 20px;
+  box-sizing: border-box;
+  background: var(--content-bg, #f5f7fa);
+  color: var(--text-cyan, #303133);
+  font-family: "Microsoft YaHei", system-ui, sans-serif;
+
+  *,
+  *::before,
+  *::after {
+    box-sizing: border-box;
+  }
+}
+
+html[data-theme="tech-blue"] .wind-resource-wake-page {
+  background: radial-gradient(circle at 20% 20%, #0f2a44, #050b16);
+  color: #e6f1ff;
+}
+
+.wrw-filter-bar {
+  background: rgba(4, 28, 51, 0.95);
+  border-radius: 12px;
+  border: 1px solid rgba(90, 179, 220, 0.35);
+  padding: 0 15px;
+  display: flex;
+  align-items: center;
+  gap: 20px;
+  height: 52px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
+}
+
+.wrw-filter-item {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 0 20px;
+  height: 36px;
+  border-radius: 6px;
+  border: 1px solid rgba(90, 179, 220, 0.35);
+  background: rgba(6, 34, 59, 0.84);
+}
+
+.wrw-filter-label {
+  color: #9bc8e2;
+  font-size: 13px;
+  white-space: nowrap;
+}
+
+.wrw-filter-select,
+.wrw-filter-date {
+  width: 160px;
+}
+
+.wrw-tab-bar {
+  display: flex;
+  gap: 12px;
+  margin: 16px 0 8px;
+}
+
+.wrw-tab-btn {
+  padding: 8px 18px;
+  border-radius: 20px;
+  border: 1px solid rgba(111, 239, 252, 0.3);
+  background: rgba(6, 34, 59, 0.65);
+  color: #9bc8e2;
+  font-size: 13px;
+  cursor: pointer;
+  transition: all 0.25s ease;
+}
+
+.wrw-tab-btn:hover {
+  border-color: rgba(111, 239, 252, 0.5);
+  color: #d9efff;
+}
+
+.wrw-tab-btn.active {
+  color: #e6f1ff;
+  border-color: rgba(111, 239, 252, 0.45);
+  background: linear-gradient(
+    180deg,
+    rgba(111, 239, 252, 0.18),
+    rgba(90, 179, 220, 0.08)
+  );
+}
+
+.wrw-section-title {
+  margin: 20px 0 10px;
+  padding-left: 10px;
+  border-left: 4px solid #6feffc;
+  color: #b7dff5;
+  font-size: 14px;
+  font-weight: 600;
+}
+
+.wrw-card {
+  background: linear-gradient(145deg, #0f1b2d, #0b1424);
+  border-radius: 16px;
+  border: 1px solid rgba(111, 239, 252, 0.15);
+  box-shadow: 0 0 20px rgba(111, 239, 252, 0.1);
+  padding: 15px;
+}
+
+.wrw-row {
+  display: flex;
+  gap: 20px;
+  flex-wrap: wrap;
+}
+
+.wrw-col {
+  flex: 1;
+  min-width: 320px;
+}
+
+.wrw-chart {
+  height: 300px;
+  width: 100%;
+}
+
+.wrw-chart--bar {
+  height: 350px;
+}
+
+.wrw-overview-section {
+  margin-top: 18px;
+}
+
+.wrw-overview-section:first-child {
+  margin-top: 8px;
+}
+
+.wrw-overview-section-title {
+  margin: 0 0 10px 4px;
+  padding-left: 10px;
+  border-left: 3px solid #6feffc;
+  color: #b7dff5;
+  font-size: 14px;
+  font-weight: 600;
+  letter-spacing: 0.5px;
+}
+
+.wrw-overview-section-desc {
+  margin: -6px 0 10px 14px;
+  color: #6a8299;
+  font-size: 11px;
+}
+
+.wrw-overview-card {
+  padding: 12px 12px 8px;
+  overflow: hidden;
+  background: linear-gradient(
+    160deg,
+    rgba(15, 27, 45, 0.95),
+    rgba(8, 18, 32, 0.98)
+  );
+  border: 1px solid rgba(111, 239, 252, 0.12);
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
+}
+
+.wrw-overview-chart {
+  width: 100%;
+  height: 400px;
+}
+
+.wrw-overview-chart-large {
+  height: 920px;
+  padding-bottom: 4px;
+}
+
+.wrw-wind-rose-grid {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  gap: 12px 16px;
+}
+
+.wrw-wind-rose-cell {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  min-width: 0;
+}
+
+.wrw-wind-rose-cell-title {
+  margin-bottom: 6px;
+  padding: 3px 12px;
+  border-radius: 10px;
+  background: rgba(111, 239, 252, 0.12);
+  color: #e6f1ff;
+  font-size: 11px;
+  font-weight: 600;
+  line-height: 1.4;
+  text-align: center;
+}
+
+.wrw-wind-rose-cell-chart {
+  width: 100%;
+  height: 260px;
+}
+
+.wrw-wind-rose-legend {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: center;
+  gap: 12px 20px;
+  margin-top: 8px;
+  padding: 8px 12px 4px;
+}
+
+.wrw-wind-rose-legend-item {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  color: #8fa3c8;
+  font-size: 11px;
+
+  i {
+    display: inline-block;
+    width: 22px;
+    height: 10px;
+    border-radius: 2px;
+  }
+}
+
+.wrw-table-wrapper {
+  overflow-x: auto;
+  border-radius: 8px;
+  box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.3);
+  margin-top: 20px;
+}
+
+.wrw-table {
+  width: 100%;
+  border-collapse: separate;
+  border-spacing: 0;
+  background: rgba(6, 34, 59, 0.4);
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+.wrw-table thead {
+  background: linear-gradient(
+    180deg,
+    rgba(111, 239, 252, 0.15),
+    rgba(90, 179, 220, 0.1)
+  );
+}
+
+.wrw-table th {
+  color: #6feffc;
+  font-weight: 600;
+  font-size: 13px;
+  padding: 14px 12px;
+  text-align: center;
+  border-bottom: 2px solid rgba(111, 239, 252, 0.3);
+  letter-spacing: 0.5px;
+  position: relative;
+}
+
+.wrw-table th::after {
+  content: "";
+  position: absolute;
+  bottom: -2px;
+  left: 0;
+  right: 0;
+  height: 2px;
+  background: linear-gradient(
+    90deg,
+    transparent,
+    rgba(111, 239, 252, 0.6),
+    transparent
+  );
+}
+
+.wrw-table tbody tr {
+  transition: all 0.3s ease;
+}
+
+.wrw-table tbody tr:hover {
+  background: rgba(111, 239, 252, 0.08);
+}
+
+.wrw-table td {
+  padding: 12px;
+  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
+  text-align: center;
+  color: #e6f1ff;
+  font-size: 13px;
+}
+
+.wrw-table tbody tr:last-child td {
+  border-bottom: none;
+}
+
+.wrw-table tbody tr td:first-child {
+  border-left: 2px solid transparent;
+  transition: border-color 0.3s ease;
+}
+
+.wrw-table tbody tr:hover td:first-child {
+  border-left-color: #6feffc;
+}
+
+.wrw-status-yes {
+  display: inline-block;
+  padding: 4px 12px;
+  border-radius: 12px;
+  background: rgba(76, 175, 80, 0.2);
+  color: #4caf50;
+  font-weight: 500;
+  font-size: 12px;
+  border: 1px solid rgba(76, 175, 80, 0.3);
+}
+
+.wrw-status-no {
+  display: inline-block;
+  padding: 4px 12px;
+  border-radius: 12px;
+  background: rgba(255, 152, 0, 0.15);
+  color: #ff9800;
+  font-weight: 500;
+  font-size: 12px;
+  border: 1px solid rgba(255, 152, 0, 0.3);
+}
+
+.wrw-value-high {
+  color: #ff5252;
+  font-weight: 600;
+}
+
+.wrw-value-medium {
+  color: #ff9800;
+  font-weight: 600;
+}
+
+.wrw-value-low {
+  color: #4caf50;
+  font-weight: 600;
+}
+
+.wrw-image-container {
+  display: inline-block;
+  padding: 4px;
+  border: 1px solid rgba(111, 239, 252, 0.2);
+  border-radius: 6px;
+  background: rgba(6, 34, 59, 0.6);
+  transition: all 0.3s ease;
+  cursor: pointer;
+}
+
+.wrw-image-container:hover {
+  border-color: #6feffc;
+  box-shadow: 0 0 12px rgba(111, 239, 252, 0.3);
+  transform: scale(1.1);
+}
+
+.wrw-image-container img,
+.wrw-table-thumb {
+  width: 45px;
+  height: 45px;
+  border-radius: 4px;
+  display: block;
+}
+
+.wrw-table-thumb-error {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 45px;
+  height: 45px;
+  border-radius: 4px;
+  background: rgba(6, 34, 59, 0.8);
+  color: #6a8299;
+  font-size: 18px;
+}
+
+.wrw-pagination {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 8px;
+  margin-top: 16px;
+  padding: 12px;
+  background: rgba(6, 34, 59, 0.4);
+  border-radius: 8px;
+  border: 1px solid rgba(111, 239, 252, 0.15);
+}
+
+.wrw-pagination button {
+  min-width: 36px;
+  height: 36px;
+  padding: 0 12px;
+  border: none;
+  border-radius: 6px;
+  background: rgba(111, 239, 252, 0.1);
+  color: #8fa3c8;
+  font-size: 13px;
+  cursor: pointer;
+  transition: all 0.3s ease;
+}
+
+.wrw-pagination button:hover:not(:disabled) {
+  background: rgba(111, 239, 252, 0.25);
+  color: #6feffc;
+}
+
+.wrw-pagination button:disabled {
+  opacity: 0.4;
+  cursor: not-allowed;
+}
+
+.wrw-page-info {
+  color: #8fa3c8;
+  font-size: 12px;
+  padding: 0 8px;
+}
+
+.wrw-page-size-select {
+  background: rgba(6, 34, 59, 0.8);
+  border: 1px solid rgba(111, 239, 252, 0.2);
+  border-radius: 4px;
+  color: #e6f1ff;
+  padding: 6px 10px;
+  font-size: 12px;
+  cursor: pointer;
+  outline: none;
+  margin-left: 8px;
+}
+
+.wrw-table-wrapper::-webkit-scrollbar {
+  width: 6px;
+  height: 6px;
+}
+
+.wrw-table-wrapper::-webkit-scrollbar-track {
+  background: rgba(6, 34, 59, 0.3);
+  border-radius: 3px;
+}
+
+.wrw-table-wrapper::-webkit-scrollbar-thumb {
+  background: rgba(111, 239, 252, 0.4);
+  border-radius: 3px;
+}
+
+@media (max-width: 900px) {
+  .wrw-wind-rose-grid {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+
+  .wrw-wind-rose-cell-chart {
+    height: 220px;
+  }
+}

+ 320 - 0
src/views/windResourceWake/utils/overviewChartOptions.js

@@ -0,0 +1,320 @@
+import { turbineNames } from "../data/mockData";
+
+const overviewTheme = {
+  title: "#b7dff5",
+  text: "#8FA3C8",
+  axis: "rgba(111, 239, 252, 0.35)",
+  split: "rgba(255, 255, 255, 0.07)",
+  polarSplit: "rgba(111, 239, 252, 0.1)",
+  tooltipBg: "rgba(6, 22, 42, 0.96)",
+  tooltipBorder: "rgba(111, 239, 252, 0.4)",
+};
+
+const overviewTooltip = {
+  backgroundColor: overviewTheme.tooltipBg,
+  borderColor: overviewTheme.tooltipBorder,
+  borderWidth: 1,
+  padding: [10, 14],
+  textStyle: { color: "#e6f1ff", fontSize: 12 },
+  extraCssText:
+    "box-shadow: 0 8px 24px rgba(0,0,0,0.45); border-radius: 8px;",
+};
+
+const colorList = [
+  "#6feffc",
+  "#4D8BFF",
+  "#E85D5D",
+  "#66BB6A",
+  "#E8966A",
+  "#C0A882",
+  "#9a7ce6",
+  "#8FA3C8",
+  "#D4B86A",
+];
+
+function buildDistSeries() {
+  const speedBins = Array.from({ length: 52 }, (_, i) =>
+    Number((i * 0.5).toFixed(1))
+  );
+  const baseDist = speedBins.map((x) => {
+    const narrowPeak = 0.135 * Math.exp(-Math.pow((x - 3.15) / 0.34, 2));
+    const shoulder = 0.115 * Math.exp(-Math.pow((x - 5.5) / 2.35, 2));
+    const lowWind = 0.035 * Math.exp(-Math.pow((x - 1.7) / 0.72, 2));
+    const tail = x > 7 ? 0.022 * Math.exp(-(x - 7) / 1.9) : 0;
+    return Number(
+      Math.min(0.24, narrowPeak + shoulder + lowWind + tail).toFixed(4)
+    );
+  });
+
+  return turbineNames.map((name, idx) => {
+    const peakLift = [0.012, 0.006, 0.032, -0.005, 0.0, 0.018, 0.006, 0.012, -0.008][idx];
+    const scale = 0.92 + idx * 0.018;
+    const data = speedBins.map((x, i) => {
+      const localPeak = Math.abs(x - 3.1) < 0.35 ? peakLift : 0;
+      const phase = Math.sin((i + idx) * 0.7) * 0.003;
+      return [
+        x,
+        Number(
+          Math.max(0, baseDist[i] * scale + localPeak + phase).toFixed(4)
+        ),
+      ];
+    });
+    return {
+      name,
+      type: "line",
+      step: "middle",
+      symbol: "none",
+      lineStyle: {
+        width: idx === 8 ? 2.2 : 1.8,
+        color: colorList[idx],
+        cap: "round",
+        join: "round",
+      },
+      emphasis: {
+        focus: "series",
+        lineStyle: {
+          width: 2.8,
+          shadowBlur: 12,
+          shadowColor: `${colorList[idx]}66`,
+        },
+      },
+      blur: { lineStyle: { opacity: 0.2 } },
+      data,
+    };
+  });
+}
+
+export function getWindDistOption() {
+  return {
+    animationDuration: 800,
+    animationEasing: "cubicOut",
+    color: colorList,
+    legend: {
+      type: "scroll",
+      bottom: 8,
+      left: "center",
+      orient: "horizontal",
+      itemWidth: 18,
+      itemHeight: 8,
+      itemGap: 16,
+      pageIconColor: overviewTheme.text,
+      pageTextStyle: { color: overviewTheme.text },
+      textStyle: { color: overviewTheme.text, fontSize: 11 },
+    },
+    tooltip: {
+      ...overviewTooltip,
+      trigger: "axis",
+      axisPointer: {
+        type: "cross",
+        crossStyle: { color: "rgba(111, 239, 252, 0.5)" },
+        lineStyle: {
+          color: "rgba(111, 239, 252, 0.35)",
+          type: "dashed",
+        },
+        label: {
+          backgroundColor: overviewTheme.tooltipBg,
+          borderColor: overviewTheme.tooltipBorder,
+          color: overviewTheme.text,
+        },
+      },
+    },
+    grid: {
+      left: 56,
+      right: 24,
+      top: 28,
+      bottom: 72,
+      show: true,
+      borderColor: "rgba(111, 239, 252, 0.12)",
+      backgroundColor: "rgba(6, 28, 48, 0.4)",
+    },
+    xAxis: {
+      type: "value",
+      min: 0,
+      max: 25,
+      interval: 5,
+      name: "风速 (m/s)",
+      nameLocation: "middle",
+      nameGap: 32,
+      nameTextStyle: { color: overviewTheme.text, fontSize: 12 },
+      axisLine: { lineStyle: { color: overviewTheme.axis } },
+      axisTick: { show: true, lineStyle: { color: overviewTheme.axis } },
+      axisLabel: { color: overviewTheme.text, fontSize: 10 },
+      splitLine: {
+        lineStyle: { color: overviewTheme.split, type: "dashed" },
+      },
+    },
+    yAxis: {
+      type: "value",
+      name: "概率密度",
+      nameTextStyle: { color: overviewTheme.text, fontSize: 12 },
+      min: 0,
+      max: 0.24,
+      interval: 0.05,
+      axisLine: { show: true, lineStyle: { color: overviewTheme.axis } },
+      axisTick: { show: true, lineStyle: { color: overviewTheme.axis } },
+      axisLabel: {
+        color: overviewTheme.text,
+        fontSize: 10,
+        formatter: "{value}",
+      },
+      splitLine: {
+        lineStyle: { color: overviewTheme.split, type: "dashed" },
+      },
+    },
+    series: buildDistSeries(),
+  };
+}
+
+const ROSE_DIRECTIONS = [
+  "N",
+  "",
+  "NE",
+  "",
+  "E",
+  "",
+  "SE",
+  "",
+  "S",
+  "",
+  "SW",
+  "",
+  "W",
+  "",
+  "NW",
+  "",
+];
+
+export const ROSE_SPEED_BANDS = [
+  "0~3 m/s",
+  "3~6 m/s",
+  "6~9 m/s",
+  "9~12 m/s",
+  ">12 m/s",
+];
+
+export const ROSE_COLORS = [
+  "#4A72B0",
+  "#92C1E9",
+  "#FFDF92",
+  "#F58D66",
+  "#E86B45",
+];
+
+const ROSE_PREFERRED_DIR = [2, 2, 2, 2, 4, 12, 2, 2, 2];
+
+function buildRoseBandData(turbineIndex, bandIdx) {
+  const dirPeak = ROSE_PREFERRED_DIR[turbineIndex];
+  const base =
+    bandIdx === 0
+      ? 2.35
+      : bandIdx === 1
+      ? 2.05
+      : bandIdx === 2
+      ? 1.55
+      : bandIdx === 3
+      ? 0.78
+      : 0.26;
+
+  return ROSE_DIRECTIONS.map((_, dIdx) => {
+    const distance = Math.min(
+      Math.abs(dIdx - dirPeak),
+      16 - Math.abs(dIdx - dirPeak)
+    );
+    const radial = Math.max(0, 1 - distance / 6);
+    const jitter =
+      (((dIdx + turbineIndex * 3 + bandIdx * 5) % 7) - 3) * 0.06;
+    const sectorBoost = dIdx === dirPeak ? 0.8 + (turbineIndex % 3) * 0.25 : 0;
+    return Number(
+      Math.max(0.05, base + radial * base * 1.85 + sectorBoost + jitter).toFixed(
+        2
+      )
+    );
+  });
+}
+
+export function getSingleWindRoseOption(turbineIndex) {
+  const series = ROSE_SPEED_BANDS.map((band, bandIdx) => ({
+    name: band,
+    type: "bar",
+    coordinateSystem: "polar",
+    stack: "wind",
+    barWidth: "78%",
+    itemStyle: {
+      color: ROSE_COLORS[bandIdx],
+      opacity: 0.96,
+      borderColor: "rgba(255, 255, 255, 0.32)",
+      borderWidth: 0.4,
+      shadowBlur: 2,
+      shadowColor: "rgba(0, 0, 0, 0.2)",
+    },
+    emphasis: {
+      focus: "self",
+      itemStyle: {
+        opacity: 1,
+        shadowBlur: 10,
+        shadowColor: "rgba(146, 193, 233, 0.5)",
+      },
+    },
+    data: buildRoseBandData(turbineIndex, bandIdx),
+  }));
+
+  return {
+    animationDuration: 600,
+    animationEasing: "cubicOut",
+    color: ROSE_COLORS,
+    polar: {
+      center: ["50%", "54%"],
+      radius: "72%",
+    },
+    angleAxis: {
+      type: "category",
+      data: ROSE_DIRECTIONS,
+      boundaryGap: false,
+      clockwise: true,
+      startAngle: 90,
+      axisLine: { lineStyle: { color: overviewTheme.axis } },
+      axisTick: { show: false },
+      axisLabel: {
+        color: overviewTheme.text,
+        fontSize: 9,
+        interval: 0,
+        margin: 4,
+        formatter: (v) => v || "",
+      },
+      splitLine: {
+        show: true,
+        lineStyle: { color: overviewTheme.polarSplit },
+      },
+    },
+    radiusAxis: {
+      min: 0,
+      max: 14,
+      interval: 3.5,
+      axisLabel: {
+        color: overviewTheme.text,
+        fontSize: 8,
+        showMinLabel: false,
+      },
+      axisLine: { show: false },
+      axisTick: { show: false },
+      splitLine: {
+        lineStyle: { color: overviewTheme.polarSplit, type: "dashed" },
+      },
+      splitArea: {
+        show: true,
+        areaStyle: {
+          color: ["rgba(111, 239, 252, 0.04)", "transparent"],
+        },
+      },
+    },
+    tooltip: {
+      ...overviewTooltip,
+      trigger: "item",
+      formatter: (p) => {
+        const dir = p.name || "—";
+        return `${p.marker}${p.seriesName}<br/>方向 ${dir} · 频次 ${p.value}`;
+      },
+    },
+    series,
+  };
+}

+ 274 - 0
src/views/windResourceWake/utils/wakeChartOptions.js

@@ -0,0 +1,274 @@
+const scatterData = [
+  { name: "L9Xcm4u0", value: [333330, 4173350, 7.8] },
+  { name: "o23d6LBB", value: [333320, 4173300, 7.5] },
+  { name: "0DdPWnck", value: [333310, 4173380, 4.8] },
+  { name: "CZbp7no4", value: [333290, 4174350, 8.1] },
+  { name: "4dCWnzLK", value: [333300, 4174380, 2.5] },
+  { name: "3dORuyAV", value: [333240, 4173720, 3.4] },
+  { name: "SPlyMKa9", value: [333270, 4173320, 4.5] },
+  { name: "86n2qgiM", value: [333230, 4172950, 4.5] },
+  { name: "BCKL9lkw", value: [333420, 4173200, 34.6] },
+  { name: "qAqnO0YH", value: [333280, 4172580, 0.2] },
+  { name: "I7EJ4nPt", value: [333360, 4173450, 59.6] },
+  { name: "RQov6gm3", value: [333315, 4172900, 4.1] },
+  { name: "7xKAWgZW", value: [333410, 4172980, 84.7] },
+  { name: "olFRqfSf", value: [333400, 4173380, 12.8] },
+  { name: "NSfopOW3", value: [333370, 4173750, 2.4] },
+  { name: "3jyH154U", value: [333270, 4172520, 0.4] },
+];
+
+const scatter2Data = [
+  { name: "L9Xcm4u0", value: [7.8, 92], itemStyle: { color: "#C0C0C0" } },
+  { name: "o23d6LBB", value: [7.5, 78], itemStyle: { color: "#D4C4B0" } },
+  { name: "0DdPWnck", value: [4.8, 85], itemStyle: { color: "#B0B0B0" } },
+  { name: "CZbp7no4", value: [8.1, 88], itemStyle: { color: "#A0A0A0" } },
+  { name: "4dCWnzLK", value: [2.5, 79], itemStyle: { color: "#C8C8C8" } },
+  { name: "3dORuyAV", value: [3.4, 95], itemStyle: { color: "#4CAF50" } },
+  { name: "SPlyMKa9", value: [4.5, 79], itemStyle: { color: "#C0C0C0" } },
+  { name: "86n2qgiM", value: [4.5, 91], itemStyle: { color: "#66BB6A" } },
+  { name: "BCKL9lkw", value: [34.6, 78], itemStyle: { color: "#D4C4B0" } },
+  { name: "qAqnO0YH", value: [0.2, 93], itemStyle: { color: "#4CAF50" } },
+  { name: "I7EJ4nPt", value: [59.6, 82], itemStyle: { color: "#CD853F" } },
+  { name: "RQov6gm3", value: [4.1, 94], itemStyle: { color: "#4CAF50" } },
+  { name: "7xKAWgZW", value: [84.7, 87], itemStyle: { color: "#E85D5D" } },
+  { name: "olFRqfSf", value: [12.8, 88], itemStyle: { color: "#66BB6A" } },
+  { name: "NSfopOW3", value: [2.4, 92], itemStyle: { color: "#4CAF50" } },
+  { name: "3jyH154U", value: [0.4, 67], itemStyle: { color: "#E85D5D" } },
+];
+
+const barCategories = scatterData.map((item) => item.name);
+const lossData = scatterData.map((item) => item.value[2]);
+const delData = [
+  50.8, 54.6, 19.9, 17.2, 6.6, 20.7, 36.9, 24.6, 72.9, 3.5, 348.4, 13.5, 324.4,
+  14.1, 34.9, 3.6,
+];
+
+export function getScatter1Option() {
+  return {
+    backgroundColor: "transparent",
+    title: {
+      text: "风电场布局与机组日尺度发电量损失图(2025-10-24)",
+      left: "center",
+      top: 10,
+      textStyle: { color: "#8FA3C8", fontSize: 14, fontWeight: "bold" },
+    },
+    tooltip: {
+      formatter(params) {
+        const val = params.value || params.data;
+        return `${params.name}<br/>损失: ${Array.isArray(val) ? val[2] : val}%`;
+      },
+    },
+    grid: { left: "12%", right: "15%", bottom: "13%", top: "15%" },
+    xAxis: {
+      name: "东西方向坐标(米)",
+      nameTextStyle: { color: "#8FA3C8", fontSize: 12, padding: [10, 0, 0, 0] },
+      nameLocation: "middle",
+      min: 333200,
+      max: 333450,
+      axisLabel: {
+        color: "#8FA3C8",
+        formatter(value) {
+          return (value / 1000000).toFixed(4);
+        },
+      },
+      axisLine: { lineStyle: { color: "rgba(111, 239, 252, 0.3)" } },
+      axisTick: { show: true },
+      splitLine: { lineStyle: { color: "rgba(255, 255, 255, 0.05)" } },
+    },
+    yAxis: {
+      name: "南北方向坐标(米)",
+      nameTextStyle: { color: "#8FA3C8", fontSize: 12, padding: [-50, 0, 35, 0] },
+      nameLocation: "middle",
+      min: 4172500,
+      max: 4174000,
+      axisLabel: {
+        color: "#8FA3C8",
+        formatter(value) {
+          return (value / 1000000).toFixed(4);
+        },
+      },
+      axisLine: { lineStyle: { color: "rgba(111, 239, 252, 0.3)" } },
+      axisTick: { show: true },
+      splitLine: { lineStyle: { color: "rgba(255, 255, 255, 0.05)" } },
+    },
+    visualMap: {
+      type: "continuous",
+      orient: "vertical",
+      right: 0,
+      top: "center",
+      min: 0,
+      max: 85,
+      inRange: {
+        color: ["#E8DFD9", "#DCB699", "#E8966A", "#DC4E4E", "#B82E2E"],
+      },
+      textStyle: { color: "#8FA3C8", fontSize: 11 },
+      text: ["发电量损失(%)", "0%"],
+      calculable: true,
+      dimension: 2,
+      itemWidth: 15,
+    },
+    series: [
+      {
+        type: "scatter",
+        symbolSize: 20,
+        label: {
+          show: true,
+          position: "top",
+          color: "#8FA3C8",
+          fontSize: 9,
+          formatter: "{b}",
+        },
+        data: scatterData,
+        itemStyle: { borderColor: "#999", borderWidth: 1 },
+      },
+    ],
+  };
+}
+
+export function getScatter2Option() {
+  return {
+    title: {
+      text: "日尺度损失与可信度散点图(2025-10-24)",
+      left: "center",
+      top: 10,
+      textStyle: { color: "#8FA3C8", fontSize: 14 },
+    },
+    grid: { left: "8%", right: "8%", bottom: "13%", top: "15%" },
+    xAxis: {
+      name: "发电量损失(%)",
+      nameTextStyle: { color: "#8FA3C8", fontSize: 12, padding: [10, 0, 0, 0] },
+      nameLocation: "middle",
+      min: 0,
+      max: 90,
+      axisLabel: { color: "#8FA3C8" },
+      splitLine: { lineStyle: { color: "rgba(255,255,255,0.05)" } },
+      axisLine: { lineStyle: { color: "rgba(255,255,255,0.2)" } },
+    },
+    yAxis: {
+      name: "结果可信度(%)",
+      nameTextStyle: { color: "#8FA3C8", fontSize: 12 },
+      min: 65,
+      max: 98,
+      axisLabel: { color: "#8FA3C8" },
+      splitLine: { lineStyle: { color: "rgba(255,255,255,0.05)" } },
+      axisLine: { lineStyle: { color: "rgba(255,255,255,0.2)" } },
+    },
+    graphic: [
+      {
+        type: "text",
+        left: "70%",
+        top: "25%",
+        style: {
+          text: "高损失 高可信度",
+          fill: "#4CAF50",
+          fontSize: 14,
+          fontWeight: "bold",
+        },
+      },
+      {
+        type: "text",
+        left: "65%",
+        top: "75%",
+        style: {
+          text: "高损失 低可信度",
+          fill: "#E85D5D",
+          fontSize: 14,
+          fontWeight: "bold",
+        },
+      },
+    ],
+    series: [
+      {
+        type: "scatter",
+        symbolSize: 15,
+        label: {
+          show: true,
+          position: "top",
+          color: "#8FA3C8",
+          fontSize: 10,
+          formatter: "{b}",
+        },
+        data: scatter2Data,
+        itemStyle: { shadowBlur: 8, shadowColor: "rgba(0, 0, 0, 0.3)" },
+      },
+    ],
+  };
+}
+
+export function getBarOption() {
+  return {
+    title: {
+      text: "各机组日尺度发电量损失与相对DEL指标(2025-10-24)",
+      left: "center",
+      top: 10,
+      textStyle: { color: "#8FA3C8", fontSize: 16, fontWeight: "bold" },
+    },
+    legend: {
+      data: ["发电量损失(%)", "相对DEL指标(%)"],
+      top: 40,
+      textStyle: { color: "#8FA3C8" },
+    },
+    grid: {
+      left: "3%",
+      right: "4%",
+      bottom: "15%",
+      top: "20%",
+      containLabel: true,
+    },
+    xAxis: {
+      type: "category",
+      data: barCategories,
+      axisLabel: { color: "#8FA3C8", rotate: 45, fontSize: 10 },
+      axisLine: { lineStyle: { color: "rgba(255,255,255,0.2)" } },
+    },
+    yAxis: [
+      {
+        type: "value",
+        name: "发电量损失(%)",
+        nameTextStyle: { color: "#E85D5D", fontSize: 12 },
+        min: 0,
+        max: 100,
+        axisLabel: { color: "#E85D5D", formatter: "{value}%" },
+        splitLine: { lineStyle: { color: "rgba(255,255,255,0.05)" } },
+      },
+      {
+        type: "value",
+        name: "相对DEL指标(%)",
+        nameTextStyle: { color: "#4D8BFF", fontSize: 12 },
+        min: 0,
+        max: 400,
+        axisLabel: { color: "#4D8BFF", formatter: "{value}%" },
+        splitLine: { show: false },
+      },
+    ],
+    series: [
+      {
+        name: "发电量损失(%)",
+        type: "bar",
+        barWidth: "30%",
+        data: lossData,
+        itemStyle: { color: "#E85D5D" },
+        label: {
+          show: true,
+          position: "top",
+          color: "#E85D5D",
+          fontSize: 10,
+        },
+      },
+      {
+        name: "相对DEL指标(%)",
+        type: "bar",
+        barWidth: "30%",
+        yAxisIndex: 1,
+        data: delData,
+        itemStyle: { color: "#4D8BFF" },
+        label: {
+          show: true,
+          position: "top",
+          color: "#4D8BFF",
+          fontSize: 10,
+        },
+      },
+    ],
+  };
+}