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

修改故障诊断其他模块样式

liujiejie 2 дней назад
Родитель
Сommit
45d6161133

+ 7 - 7
.env.dev

@@ -1,8 +1,8 @@
 ###
  # @Author: your name
  # @Date: 2025-07-17 14:14:27
- # @LastEditTime: 2026-04-08 13:43:11
- # @LastEditors: bogon
+ # @LastEditTime: 2026-04-15 10:57:56
+ # @LastEditors: milo-MacBook-Pro.local
  # @Description: In User Settings Edit
  # @FilePath: /performance-test/.env.dev
 #这里需要在router/index.js 文件中进行判断cockpitComponent;
@@ -14,8 +14,8 @@ VUE_APP_TITLE='机组功率曲线异常检测数据分析系统'
 #外网
 VUE_APP_MAPVIEW= "http://106.120.102.238:18000/tiles/{z}/{x}/{y}.png"
 VUE_APP_UPLOAD="http://106.120.102.238:16700/energy-manage-service/api/check/upload"
-#VUE_APP_APIPROXY='http://106.120.102.238:16700' #生产外网
-VUE_APP_APIPROXY='http://106.120.102.238:16600' #开发外网
+VUE_APP_APIPROXY='http://106.120.102.238:16600' #生产外网
+# VUE_APP_APIPROXY='http://106.120.102.238:16600' #开发外网
 VUE_APP_MAP=http://106.120.102.238:18080
 # VUE_APP_WZLAPIPROXY='http://106.120.102.238:18080/WindTransDev'
 # VUE_APP_ETLAPIPROXY='http://106.120.102.238:18080/WindTransDev'
@@ -25,14 +25,14 @@ VUE_APP_AnalysisMultiAPIPROXY='http://106.120.102.238:28999/AnalysisMulti'
 #自定义算法文佳 目前无法使用,可能是服务未启动
 VUE_APP_sAlgorithmAPIPROXY='http://106.120.102.238:58880'
 VUE_APP_databaseApiAPIPROXY='http://106.120.102.238:58880'
-# #暂时不知下载报告内网dev 环境地址
-# VUE_APP_downLoadChartAPIPROXY='http://106.120.102.238:58880'
+#暂时不知下载报告内网dev 环境地址
+VUE_APP_downLoadChartAPIPROXY='http://106.120.102.238:58880'
 VUE_APP_downLoadChartAPIPROXY='http://0.0.0.0:3001'
 #内网
 # VUE_APP_UPLOAD="http://192.168.50.235/energy-manage-service/api/check/upload"
 # VUE_APP_MAPVIEW=/tiles/{z}/{x}/{y}.png
 # VUE_APP_MAP=http://192.168.50.235
-#http://192.168.50.235:16200 开发内网地址
+# # http://192.168.50.235:16200 开发内网地址
 # VUE_APP_APIPROXY='http://192.168.50.235:16300' # 生产数据库
 # VUE_APP_WZLAPIPROXY='http://192.168.50.241:9002'
 # # VUE_APP_ETLAPIPROXY='http://192.168.50.241:9002'

+ 2 - 2
downLoadServer/.env

@@ -9,8 +9,8 @@ API_BASE_URL=http://0.0.0.0:3001
 
 MINIO_ENDPOINT=192.168.50.234 #minio 生产
 MINIO_PORT=6900 #minio 生产
-MINIO_ACCESS_KEY=6VkF2ul6X7udr7RLsG2W
-MINIO_SECRET_KEY=jtBuqZ80biRWQf6sbwzDQJwHtEBicPjkZBtvjTrA
+MINIO_ACCESS_KEY=iPzAnpgos6YETnZi7rNI
+MINIO_SECRET_KEY=iexajFlKr9tXq7oef0nbTPxsaiIgnB9QndW0wngX
 # MINIO_ACCESS_KEY=haH1vePq7unSp4TG1One
 # MINIO_SECRET_KEY=idxO5SAjboUYERpDICgHgBoHX7bcYv355lMQANt6
 CHROME_PATH=/Applications/Google Chrome.app/Contents/MacOS/Google Chrome

+ 550 - 280
src/views/health/components/malfunction/bearing.vue

@@ -1,142 +1,129 @@
 <template>
-  <div>
-    <div class="TopBox">
-      <div class="rightdiv">
-        <el-table
-          ref="multipleTable"
-          :data="tableData"
-          tooltip-effect="dark"
-          style="width: 100%"
-          height="250"
-          stripe
-          border
-        >
-          <!-- <el-table-column fixed type="selection" width="55"> </el-table-column> -->
-          <el-table-column
-            type="index"
-            label="序号"
-            align="center"
-            width="100"
-            :index="bearingTableIndex"
-          >
-          </el-table-column>
-          <el-table-column prop="brandType" label="型号" align="left">
-          </el-table-column>
-          <el-table-column prop="detectionPointCn" label="名称" align="left">
-          </el-table-column>
-          <el-table-column label="状态" width="150" align="center">
-            <template slot-scope="scope">
-              <el-tag
-                size="mini"
-                :type="statusTagType(scope.row.status)"
-                effect="dark"
-              >
-                {{ formatStatusText(scope.row.status) }}
-              </el-tag>
-            </template>
-          </el-table-column>
-          <!-- <el-table-column prop="timeStamp" label="时间" align="center">
-          </el-table-column> -->
-          <el-table-column prop="brand" width="150" label="品牌" align="left">
-          </el-table-column>
-
-          <!-- <el-table-column prop="" label="操作" align="center">
-            <template slot-scope="scope">
-              <el-button
-                type="text"
-                size="small"
-                @click="handleDetail(scope.row)"
-                >查看振动分析</el-button
-              >
-            </template>
-          </el-table-column> -->
-        </el-table>
-        <div class="fenye">
-          <p></p>
-          <!-- <p><span>状态码说明:</span>0正常,1报警,2危险</p> -->
-          <el-pagination
-            @current-change="handleCurrentChange"
-            :current-page="currentPage"
-            layout="total, prev, pager, next"
-            :total="totalCount"
-            :page-size="pageSize"
-            small
-          ></el-pagination>
-        </div>
+  <div class="gear-container">
+    <div class="gear-header">
+      <div class="header-title">
+        <p class="title">轴承诊断状态总览</p>
+        <p class="sub-title">共 {{ statusSummary.total }} 个监测点</p>
       </div>
-      <!-- <div class="leftdiv">
-        <div class="stateBox">
-          <h4>轴承状态</h4>
-          <div class="state">
-            <p :style="{ backgroundColor: bearingStateColors.innerRing }">
-              轴承内圈状态
-            </p>
-            <p :style="{ backgroundColor: bearingStateColors.outerRing }">
-              轴承外圈状态
-            </p>
-            <p :style="{ backgroundColor: bearingStateColors.rollingElement }">
-              轴承滚动体状态
-            </p>
-            <p :style="{ backgroundColor: bearingStateColors.cage }">
-              轴承保持架状态
-            </p>
+      <div class="header-stat">
+        <div
+          v-for="item in statusCards"
+          :key="item.key"
+          class="stat-item"
+          :class="`is-${item.key}`"
+        >
+          <el-divider direction="vertical" class="header-divider"></el-divider>
+          <div class="stat-item-content">
+            <div class="icon-wrap">
+              <i :class="item.icon"></i>
+            </div>
+            <div class="stat-content">
+              <p class="label">{{ item.label }}</p>
+              <p class="count">{{ item.count }}</p>
+              <p class="ratio">占比 {{ item.ratio }}</p>
+            </div>
           </div>
-          <el-button
-            type="primary"
-            size="small"
-            @click="automaticDiagnosis"
-            class="btn-auto"
-            :loading="loading"
-            >诊断</el-button
-          >
+          <div class="stat-nobox"></div>
         </div>
-
-        <div class="Btn">
-          <div style="height: 200px">
-            <bingTwo :statistics="statistics"> </bingTwo>
+      </div>
+    </div>
+    <div class="gear">
+      <div class="TopBox">
+        <div class="rightdiv">
+          <div class="monitor-panel">
+            <h4>轴承监测点列表</h4>
+            <div v-if="!tableData.length" class="monitor-empty">
+              <el-empty description="暂无监测点数据"></el-empty>
+            </div>
+            <div v-else class="monitor-list">
+              <div
+                v-for="(row, index) in tableData"
+                :key="row.id || `${row.functionTypeName}-${index}`"
+                class="monitor-item"
+                :class="{
+                  'is-active':
+                    selectedFunctionTypeName &&
+                    selectedFunctionTypeName === rowFunctionTypeName(row),
+                }"
+                @click="selectMonitorPoint(row)"
+              >
+                <div
+                  class="index-badge"
+                  :class="`is-${statusTagType(row.status)}`"
+                >
+                  {{ bearingTableIndex(index) }}
+                </div>
+                <div class="item-main">
+                  <p class="item-name">
+                    {{ monitorDisplayName(row) }}
+                  </p>
+                  <p class="item-line">监测类型:{{ monitorTypeText(row) }}</p>
+                  <p class="item-line">型号:{{ row.brandType }}</p>
+                  <p class="item-line">品牌:{{ row.brand }}</p>
+                </div>
+                <div class="item-right">
+                  <span
+                    class="status-pill"
+                    :class="`is-${statusTagType(row.status)}`"
+                  >
+                    <i class="status-dot"></i>
+                    {{ formatStatusText(row.status) }}
+                  </span>
+                </div>
+              </div>
+            </div>
           </div>
-
-          <div class="minp">
-            <p class="PText">
-              <span><i class="color1"></i>正常</span
-              ><span><i class="color2"></i>报警</span
-              ><span><i class="color3"></i>危险</span>
-            </p>
-
-            <h4>诊断步骤:</h4>
-            <p>1、选择振动测点与起止时间,点击“查询”;</p>
-
-            <p>2、点击“诊断”输出最终轴承状态结果。</p>
+          <div class="fenye">
+            <p></p>
+            <el-pagination
+              @current-change="handleCurrentChange"
+              :current-page="currentPage"
+              layout="total, prev, pager, next"
+              :total="totalCount"
+              :page-size="pageSize"
+              small
+            ></el-pagination>
           </div>
         </div>
-      </div> -->
-    </div>
-    <div class="bottomBox">
-      <div class="BtLeft">
-        <h4>轴承状态趋势图</h4>
-        <p style="font-size: 12px; color: #999">
-          <span>状态码说明:</span>0正常,1报警,2危险
-        </p>
-        <el-empty
-          v-if="!trendChartList.length"
-          description="暂无数据"
-          style="padding: 46px 0"
-        ></el-empty>
-        <div v-else class="mpoint-charts">
-          <div
-            v-for="(chart, index) in trendChartList"
-            :key="`${chart.title}-${index}`"
-            class="mpoint-chart-item"
-          >
-            <!-- <p class="mpoint-chart-title">{{ chart.title }}</p> -->
-            <Eecharts
-              style="height: 260px"
-              :xData="chart.xData"
-              :yData="chart.yData"
-              :yNames="chart.yNames"
-              yAxisName="故障状态"
-              :jumpContext="chart.jumpContext"
-            ></Eecharts>
+      </div>
+      <div class="bottomBox">
+        <div class="BtLeft">
+          <h4>轴承状态趋势图:</h4>
+          <p class="status-legend">
+            <span class="legend-label">状态码说明:</span>
+            <span class="legend-item is-normal"
+              ><i class="legend-dot"></i>0 正常</span
+            >
+            <span class="legend-item is-warning"
+              ><i class="legend-dot"></i>1 报警</span
+            >
+            <span class="legend-item is-danger"
+              ><i class="legend-dot"></i>2 危险</span
+            >
+          </p>
+          <el-empty
+            v-if="!filteredTrendChartList.length"
+            description="暂无数据"
+            style="padding: 46px 0"
+          ></el-empty>
+          <div v-else class="mpoint-charts">
+            <el-carousel :key="trendCarouselKey">
+              <el-carousel-item
+                v-for="(chart, index) in filteredTrendChartList"
+                :key="`${chart.title}-${index}`"
+              >
+                <div class="mpoint-chart-item">
+                  <Eecharts
+                    style="height: 260px"
+                    :xData="chart.xData"
+                    :yData="chart.yData"
+                    :yNames="chart.yNames"
+                    yAxisName="故障状态"
+                    :jumpContext="chart.jumpContext"
+                  ></Eecharts>
+                </div>
+              </el-carousel-item>
+            </el-carousel>
           </div>
         </div>
       </div>
@@ -146,9 +133,8 @@
 
 <script>
 import Eecharts from "./mpointEecharts.vue";
-import bingTwo from "./bingTwo.vue";
 export default {
-  components: { Eecharts, bingTwo },
+  components: { Eecharts },
   props: {
     codedata: {
       type: Array,
@@ -188,10 +174,9 @@ export default {
       vibrationSpeedDangerThreshold: "",
       envelopeTotalAlarmThreshold: "",
       envelopeTotalDangerThreshold: "",
-      // 分页
-      pageSize: 10,
       currentPage: 1,
       total: 1,
+      pageSize: 10,
       xData: [],
       yData: [],
       // 颜色判断
@@ -207,6 +192,8 @@ export default {
       loading: false, // 控制加载状态
       statistics: {},
       trendChartList: [],
+      selectedFunctionTypeName: "",
+      selectedMonitorId: "",
     };
   },
   created() {},
@@ -214,6 +201,24 @@ export default {
     codedata: {
       handler(newVal) {
         this.tableData = newVal;
+        const exists = this.tableData.some(
+          (row) =>
+            this.resolveFunctionTypeNameByRow(row) ===
+            this.selectedFunctionTypeName,
+        );
+        if (!exists) {
+          if (this.tableData.length) {
+            this.selectedFunctionTypeName = this.resolveFunctionTypeNameByRow(
+              this.tableData[0],
+            );
+            this.selectedMonitorId = this.tableData[0]?.id
+              ? String(this.tableData[0].id)
+              : "";
+          } else {
+            this.selectedFunctionTypeName = "";
+            this.selectedMonitorId = "";
+          }
+        }
       },
       immediate: true, // 组件创建时立刻执行一次
       deep: true, // 如果 codedata 是复杂对象,建议加上
@@ -235,16 +240,34 @@ export default {
                 : Number(v);
             });
             const pointIds = inner.map((d) => d?.id);
+            const pointRows = inner.map((d) => {
+              const pid = d?.id;
+              return (
+                this.tableData.find((r) => String(r?.id) === String(pid)) ||
+                null
+              );
+            });
+            const normalizedFunctionTypeName = this.normalizeFunctionTypeName(
+              item?.functionTypeName ||
+                inner.find((d) => d?.functionTypeName)?.functionTypeName ||
+                pointRows.find((r) => r?.functionTypeName)?.functionTypeName ||
+                item?.functionTypeNameCn ||
+                item?.pointNameCn ||
+                item?.mesurePointName ||
+                "",
+            );
             return {
               title,
               xData,
               yData: [seriesY],
               yNames: [title],
+              functionTypeName: normalizedFunctionTypeName,
               jumpContext: {
                 companyCode: this.fieldCode,
                 windCode: this.fieldCode,
                 windTurbineNumber: this.windTurbineNumber,
                 itemKey: item?.itemKey ?? item?.item_key,
+                functionTypeName: normalizedFunctionTypeName,
                 id: item?.id,
                 mesurePointName: item?.mesurePointName || title,
                 detectionPointCn: item?.pointNameCn || title,
@@ -255,17 +278,111 @@ export default {
                     ? { ...item.otherData }
                     : undefined,
                 pointIds,
+                pointRows,
               },
             };
           })
           .filter((it) => it.xData.length);
+        if (!this.selectedFunctionTypeName && this.tableData.length) {
+          this.selectedFunctionTypeName = this.resolveFunctionTypeNameByRow(
+            this.tableData[0],
+          );
+          this.selectedMonitorId = this.tableData[0]?.id
+            ? String(this.tableData[0].id)
+            : "";
+        }
       },
       immediate: true,
       deep: true,
     },
   },
-  mounted() {},
+  computed: {
+    statusSummary() {
+      const rows = Array.isArray(this.tableData) ? this.tableData : [];
+      const total = rows.length;
+      const summary = { total, normal: 0, warning: 0, danger: 0 };
+      rows.forEach((row) => {
+        const code = Number(row?.status);
+        if (code === 0) summary.normal += 1;
+        else if (code === 1) summary.warning += 1;
+        else if (code === 2) summary.danger += 1;
+      });
+      return summary;
+    },
+    statusCards() {
+      const { total, normal, warning, danger } = this.statusSummary;
+      const toRatio = (count) =>
+        total > 0 ? `${((count / total) * 100).toFixed(0)}%` : "0%";
+      return [
+        {
+          key: "normal",
+          label: "正常",
+          count: normal,
+          ratio: toRatio(normal),
+          icon: "el-icon-check",
+        },
+        {
+          key: "warning",
+          label: "报警",
+          count: warning,
+          ratio: toRatio(warning),
+          icon: "el-icon-warning-outline",
+        },
+        {
+          key: "danger",
+          label: "危险",
+          count: danger,
+          ratio: toRatio(danger),
+          icon: "el-icon-warning",
+        },
+      ];
+    },
+    filteredTrendChartList() {
+      const selected = this.selectedFunctionTypeName;
+      if (!selected) return this.trendChartList;
+      return this.trendChartList.filter(
+        (chart) => this.chartFunctionTypeName(chart) === selected,
+      );
+    },
+    trendCarouselKey() {
+      return `trend-${this.selectedFunctionTypeName || "all"}`;
+    },
+  },
   methods: {
+    normalizeFunctionTypeName(name) {
+      return typeof name === "string" ? name.trim() : "";
+    },
+    rowFunctionTypeName(row) {
+      return this.normalizeFunctionTypeName(
+        row?.functionTypeName ? String(row.functionTypeName) : "",
+      );
+    },
+    resolveFunctionTypeNameByRow(row) {
+      const direct = this.rowFunctionTypeName(row);
+      if (direct) return direct;
+      const rowId =
+        row?.id !== undefined && row?.id !== null ? String(row.id) : "";
+      if (!rowId) return "";
+      const chart = this.trendChartList.find((item) => {
+        const rows = item?.jumpContext?.pointRows;
+        if (!Array.isArray(rows)) return false;
+        return rows.some(
+          (r) =>
+            r?.id !== undefined && r?.id !== null && String(r.id) === rowId,
+        );
+      });
+      return chart ? this.chartFunctionTypeName(chart) : "";
+    },
+    monitorDisplayName(row) {
+      return row?.functionTypeName || row?.detectionPointCn || "-";
+    },
+    chartFunctionTypeName(chart) {
+      return this.normalizeFunctionTypeName(
+        chart?.jumpContext?.functionTypeName
+          ? String(chart.jumpContext.functionTypeName)
+          : "",
+      );
+    },
     statusTagType(status) {
       const code = Number(status);
       if (code === 0) return "success";
@@ -282,6 +399,14 @@ export default {
       if (code === -1) return "未定义";
       return String(status);
     },
+    monitorTypeText(row) {
+      return "振动-加速度包络";
+    },
+    selectMonitorPoint(row) {
+      this.selectedMonitorId =
+        row?.id !== undefined && row?.id !== null ? String(row.id) : "";
+      this.selectedFunctionTypeName = this.resolveFunctionTypeNameByRow(row);
+    },
     handleDetail(row) {
       const itemKey = row.itemKey ?? row.item_key;
       const payload = {
@@ -336,126 +461,285 @@ export default {
       this.$emit("updatePage", this.currentPage); // 通知父组件,把当前页传出去
     },
     reset() {
-      // 重置状态
+      this.tableData = [];
       this.xData = [];
       this.yData = [];
       this.statistics = {};
+      this.result = {};
       this.trendChartList = [];
+      this.selectedFunctionTypeName = "";
+      this.selectedMonitorId = "";
       this.bearingStateColors = {
         innerRing: "#80808057",
         outerRing: "#80808057",
         rollingElement: "#80808057",
         cage: "#80808057",
       };
-      this.tableData = [];
     },
   },
 };
 </script>
 
 <style lang="scss" scoped>
-h4 {
-  margin-bottom: 5px;
-  font-weight: 600;
+.gear-container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  margin-top: -10px;
 }
 
-.TopBox {
-  // height: 280px;
+.gear-header {
   display: flex;
-  justify-content: space-around;
-
-  .leftdiv {
-    width: 50%;
+  align-items: stretch;
+  border-radius: 12px;
+  background: #f5f7fa5e;
+  overflow: hidden;
+  box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
+
+  .header-title {
+    width: 230px;
+    padding: 16px 18px;
     display: flex;
-    justify-content: space-around;
+    flex-direction: column;
+    justify-content: center;
+    .title {
+      margin: 0;
+      font-size: 18px;
+      font-weight: 700;
+      color: #303133;
+    }
+    .sub-title {
+      margin: 6px 0 0;
+      font-size: 13px;
+      color: #909399;
+    }
+  }
 
-    .stateBox {
-      .state {
+  .header-stat {
+    flex: 1;
+    display: grid;
+    grid-template-columns: repeat(3, minmax(0, 1fr));
+    ::v-deep .header-divider.el-divider--vertical {
+      height: 48px;
+      margin: 0 8px 0 0;
+      width: 1px;
+      background-color: #dcdfe65e;
+    }
+    .stat-item {
+      padding: 10px 18px;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      gap: 10px;
+      &:last-child {
+        border-right: 0;
+      }
+      .icon-wrap {
+        width: 42px;
+        height: 42px;
+        border-radius: 50%;
+        color: #fff;
         display: flex;
-        flex-direction: column;
+        align-items: center;
         justify-content: center;
-        /* 整体居中 */
+        font-size: 22px;
+      }
+      .stat-item-content {
+        display: flex;
         align-items: center;
-        height: 200px;
-        gap: 20px;
-
-        /* 控制间距 */
-        p {
-          width: 150px;
-          height: 40px;
-          background: rgb(227, 227, 227);
-          color: rgb(50, 50, 50);
-          text-align: center;
-          align-content: center;
-          font-weight: 600;
+        gap: 10px;
+      }
+      .stat-content {
+        .label,
+        .count,
+        .ratio {
+          margin: 0;
+          line-height: 1.2;
+        }
+        .label {
+          font-size: 14px;
+          color: #303133;
+          font-weight: 500;
+        }
+        .count {
+          margin-top: 2px;
+          font-size: 30px;
+          font-weight: 700;
+          color: #303133;
+        }
+        .ratio {
+          margin-top: 2px;
+          font-size: 12px;
+          color: #909399;
         }
       }
-
-      .btn-auto {
-        margin-top: 10px;
-        width: 100%;
+      &.is-normal .icon-wrap {
+        background: #008080;
       }
-    }
-
-    .Btn {
-      width: 50%;
-      display: flex;
-      flex-direction: column;
-      /* 垂直排列 */
-      justify-content: space-between;
-      /* 顶部和底部对齐 */
-      min-height: 100px;
-      /* 确保容器有足够高度 */
-      margin: 10px 0;
-
-      .PText {
-        display: flex;
-        justify-content: space-around;
-
-        span {
-          display: flex;
-          align-items: center;
-
-          i {
-            width: 30px;
-            height: 15px;
-            margin-right: 5px;
-          }
-
-          .color1 {
-            background-color: #8ae359;
-          }
-
-          .color2 {
-            background-color: #eecb5f;
-          }
-
-          .color3 {
-            background-color: #f7715f;
-          }
-        }
+      &.is-warning .icon-wrap {
+        background: #e6a23c;
       }
-
-      margin: 10px 0;
-
-      .minp {
-        font-size: 12px;
-        margin-top: auto;
-        /* 自动推到最底部 */
+      &.is-danger .icon-wrap {
+        background: #f56c6c;
       }
     }
   }
+}
+
+.gear {
+  height: 100%;
+  display: flex;
+  justify-content: space-between;
+  gap: 10px;
+  .TopBox {
+    width: 40%;
+  }
+  .bottomBox {
+    width: 59%;
+    height: 360px;
+  }
+}
+h4 {
+  margin-bottom: 5px;
+  font-weight: 600;
+}
 
+.TopBox {
+  display: flex;
+  justify-content: space-around;
   .rightdiv {
     width: 100%;
-
+    .monitor-panel {
+      border-radius: 14px;
+      padding: 14px;
+      min-height: 360px;
+      box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
+      height: 360px;
+      overflow-y: auto;
+    }
+    .monitor-empty {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      min-height: 260px;
+      background: #fff;
+      border-radius: 10px;
+    }
+    .monitor-list {
+      display: flex;
+      flex-direction: column;
+      gap: 10px;
+    }
+    .monitor-item {
+      position: relative;
+      display: flex;
+      align-items: center;
+      gap: 14px;
+      border: 1px solid #e4e7ed;
+      border-radius: 10px;
+      background: #fff;
+      padding: 14px 16px;
+      cursor: pointer;
+      transition: all 0.2s ease;
+      &.is-active {
+        background: #f0f5f7;
+      }
+      &.is-active::before {
+        content: "";
+        position: absolute;
+        left: 0;
+        top: 10px;
+        bottom: 10px;
+        width: 4px;
+        background: #74727224;
+        box-shadow: 0 0 0 2px #24a16600 inset;
+        border-radius: 0 4px 4px 0;
+      }
+    }
+    .index-badge {
+      width: 30px;
+      height: 30px;
+      border-radius: 50%;
+      color: #fff;
+      font-size: 16px;
+      font-weight: 700;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      flex-shrink: 0;
+      &.is-success {
+        background: #008080;
+      }
+      &.is-warning {
+        background: #e6a23c;
+      }
+      &.is-danger {
+        background: #f56c6c;
+      }
+      &.is-info {
+        background: #909399;
+      }
+    }
+    .item-main {
+      flex: 1;
+      min-width: 0;
+      .item-name {
+        margin: 0 0 8px;
+        font-size: 16px;
+        font-weight: 700;
+        color: #1f2d3d;
+      }
+      .item-line {
+        margin: 0;
+        font-size: 14px;
+        line-height: 1.5;
+        color: #6b7280;
+      }
+    }
+    .item-right {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+    }
+    .status-pill {
+      display: inline-flex;
+      align-items: center;
+      gap: 6px;
+      border-radius: 12px;
+      padding: 10px 16px;
+      font-size: 16px;
+      font-weight: 700;
+      line-height: 1;
+      .status-dot {
+        width: 10px;
+        height: 10px;
+        border-radius: 50%;
+        background: currentColor;
+      }
+      &.is-success {
+        color: #21a366;
+        background: #e7f6ee;
+      }
+      &.is-warning {
+        color: #d48806;
+        background: #fff6e5;
+      }
+      &.is-danger {
+        color: #f04438;
+        background: #ffebee;
+      }
+      &.is-info {
+        color: #606266;
+        background: #f4f4f5;
+      }
+    }
     .fenye {
       display: flex;
-      justify-content: space-between;
-      margin: 5px 0;
-      font-size: 11px;
+      justify-content: flex-end;
+      margin: 10px 0 0;
+      font-size: 12px;
       line-height: 30px;
-
       span {
         font-weight: 600;
       }
@@ -466,63 +750,16 @@ h4 {
 .bottomBox {
   display: flex;
   justify-content: space-around;
-  margin-top: 10px;
-
+  box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
+  border-radius: 12px;
   .BtLeft {
-    border: 1px solid rgb(231, 231, 231);
     width: 100%;
-    padding: 10px;
-  }
-
-  .BtRight {
-    border: 1px solid rgb(231, 231, 231);
-    padding: 10px;
-
-    .BtRightDiv {
-      padding: 0 10px;
-
-      h4 {
-        font-size: 14px;
-      }
-
-      .BtRightP {
-        line-height: 30px;
-        font-size: 12px;
-        display: flex;
-        margin-bottom: 5px;
-        width: 50%;
-
-        span {
-          margin-left: 10px;
-
-          .el-input {
-            width: 100px;
-          }
-        }
-
-        .label-text {
-          width: 140px;
-        }
-      }
-
-      .canshu {
-        display: flex;
-        flex-wrap: wrap;
-        justify-content: space-between;
-      }
-    }
-  }
-
-  .dialog-footer {
-    margin-top: 20px;
-    text-align: right;
+    padding: 10px 20px;
   }
 }
 
 .mpoint-charts {
-  display: grid;
-  grid-template-columns: repeat(2, minmax(0, 1fr));
-  gap: 16px;
+  display: block;
 }
 
 .mpoint-chart-item {
@@ -531,18 +768,51 @@ h4 {
   padding: 8px 10px;
 }
 
-.mpoint-chart-title {
-  margin: 0 0 6px;
-  font-size: 13px;
-  color: #303133;
-}
-
-@media (max-width: 1200px) {
-  .mpoint-charts {
-    grid-template-columns: 1fr;
+.status-legend {
+  margin: 4px 0 8px;
+  font-size: 12px;
+  color: #909399;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  .legend-label {
+    color: #606266;
+  }
+  .legend-item {
+    display: inline-flex;
+    align-items: center;
+    gap: 4px;
+    .legend-dot {
+      width: 8px;
+      height: 8px;
+      border-radius: 50%;
+      background: currentColor;
+    }
+    &.is-normal {
+      color: #008080;
+    }
+    &.is-warning {
+      color: #e6a23c;
+    }
+    &.is-danger {
+      color: #f56c6c;
+    }
   }
 }
 
+::v-deep .el-carousel__indicator .el-carousel__button {
+  background-color: rgba(144, 147, 153, 0.45);
+  opacity: 1;
+}
+::v-deep .el-carousel__indicator.is-active .el-carousel__button {
+  background-color: #24a166;
+}
+::v-deep .el-carousel__indicator:hover .el-carousel__button {
+  background-color: #24a166aa;
+}
+::v-deep .el-carousel__indicators--horizontal {
+  bottom: -10px !important;
+}
 ::v-deep .el-table__cell {
   padding: 2px 0;
   font-size: 12px;

+ 544 - 235
src/views/health/components/malfunction/dissymmetry.vue

@@ -1,128 +1,127 @@
 <template>
-  <div>
-    <div class="TopBox">
-      <div class="rightdiv">
-        <el-table
-          ref="multipleTable"
-          :data="tableData"
-          tooltip-effect="dark"
-          style="width: 100%"
-          height="250"
-          stripe
-          border
-        >
-          <el-table-column
-            type="index"
-            label="序号"
-            align="center"
-            width="100"
-            :index="bearingTableIndex"
-          >
-          </el-table-column>
-          <!-- <el-table-column fixed type="selection" width="55"> </el-table-column> -->
-          <el-table-column prop="detectionPointCn" label="名称" align="left">
-          </el-table-column>
-          <!-- <el-table-column prop="timeStamp" label="时间" align="center">
-          </el-table-column> -->
-          <el-table-column label="状态" align="center" width="150">
-            <template slot-scope="scope">
-              <el-tag
-                size="mini"
-                :type="statusTagType(scope.row.status)"
-                effect="dark"
-              >
-                {{ formatStatusText(scope.row.status) }}
-              </el-tag>
-            </template>
-          </el-table-column>
-          <!-- <el-table-column prop="" label="操作" align="center">
-            <template slot-scope="scope">
-              <el-button
-                type="text"
-                size="small"
-                @click="handleDetail(scope.row)"
-                >查看振动分析</el-button
-              >
-            </template>
-          </el-table-column> -->
-        </el-table>
-        <div class="fenye">
-          <p></p>
-          <!-- <p><span>状态码说明:</span>0正常,1报警,2危险</p> -->
-          <el-pagination
-            small
-            @current-change="handleCurrentChange"
-            :current-page="currentPage"
-            layout="total, prev, pager, next"
-            :total="totalCount"
-            :page-size="pageSize"
-          ></el-pagination>
-        </div>
+  <div class="gear-container">
+    <div class="gear-header">
+      <div class="header-title">
+        <p class="title">不对中诊断状态总览</p>
+        <p class="sub-title">共 {{ statusSummary.total }} 个监测点</p>
       </div>
-
-      <!-- <div class="leftdiv">
-        <div class="stateBox">
-          <h4>不对中状态</h4>
-          <div class="state">
-            <p :style="{ backgroundColor: bearingStateColors.innerRing }">
-              不对中
-            </p>
+      <div class="header-stat">
+        <div
+          v-for="item in statusCards"
+          :key="item.key"
+          class="stat-item"
+          :class="`is-${item.key}`"
+        >
+          <el-divider direction="vertical" class="header-divider"></el-divider>
+          <div class="stat-item-content">
+            <div class="icon-wrap">
+              <i :class="item.icon"></i>
+            </div>
+            <div class="stat-content">
+              <p class="label">{{ item.label }}</p>
+              <p class="count">{{ item.count }}</p>
+              <p class="ratio">占比 {{ item.ratio }}</p>
+            </div>
           </div>
-          <el-button
-            type="primary"
-            size="small"
-            @click="automaticDiagnosis"
-            class="btn-auto"
-            :loading="loading"
-            >诊断</el-button
-          >
+          <div class="stat-nobox"></div>
         </div>
-
-        <div class="Btn">
-          <div style="height: 200px">
-            <bingTwo :statistics="statistics"> </bingTwo>
+      </div>
+    </div>
+    <div class="gear">
+      <div class="TopBox">
+        <div class="rightdiv">
+          <div class="monitor-panel">
+            <h4>不对中监测点列表</h4>
+            <div v-if="!tableData.length" class="monitor-empty">
+              <el-empty description="暂无监测点数据"></el-empty>
+            </div>
+            <div v-else class="monitor-list">
+              <div
+                v-for="(row, index) in tableData"
+                :key="row.id || `${row.functionTypeName}-${index}`"
+                class="monitor-item"
+                :class="{
+                  'is-active':
+                    selectedFunctionTypeName &&
+                    selectedFunctionTypeName === rowFunctionTypeName(row),
+                }"
+                @click="selectMonitorPoint(row)"
+              >
+                <div
+                  class="index-badge"
+                  :class="`is-${statusTagType(row.status)}`"
+                >
+                  {{ bearingTableIndex(index) }}
+                </div>
+                <div class="item-main">
+                  <p class="item-name">
+                    {{ monitorDisplayName(row) }}
+                  </p>
+                  <p class="item-line">监测类型:{{ monitorTypeText(row) }}</p>
+                </div>
+                <div class="item-right">
+                  <span
+                    class="status-pill"
+                    :class="`is-${statusTagType(row.status)}`"
+                  >
+                    <i class="status-dot"></i>
+                    {{ formatStatusText(row.status) }}
+                  </span>
+                </div>
+              </div>
+            </div>
           </div>
-
-          <div class="minp">
-            <p class="PText">
-              <span><i class="color1"></i>正常</span
-              ><span><i class="color2"></i>报警</span
-              ><span><i class="color3"></i>危险</span>
-            </p>
-
-            <h4>诊断步骤:</h4>
-            <p>1、选择振动测点与起止时间,点击“查询;</p>
-            <p>2、点击“诊断”输出不对中状态结果。</p>
+          <div class="fenye">
+            <p></p>
+            <el-pagination
+              small
+              @current-change="handleCurrentChange"
+              :current-page="currentPage"
+              layout="total, prev, pager, next"
+              :total="totalCount"
+              :page-size="pageSize"
+            ></el-pagination>
           </div>
         </div>
-      </div> -->
-    </div>
-    <div class="bottomBox">
-      <div class="BtLeft">
-        <h4>不对中故障状态趋势图</h4>
-        <p style="font-size: 12px; color: #999">
-          <span>状态码说明:</span>0正常,1报警,2危险
-        </p>
-        <el-empty
-          v-if="!trendChartList.length"
-          description="暂无数据"
-          style="padding: 48px 0"
-        ></el-empty>
-        <div v-else class="mpoint-charts">
-          <div
-            v-for="(chart, index) in trendChartList"
-            :key="`${chart.title}-${index}`"
-            class="mpoint-chart-item"
-          >
-            <!-- <p class="mpoint-chart-title">{{ chart.title }}</p> -->
-            <Eecharts
-              style="height: 260px"
-              :xData="chart.xData"
-              :yData="chart.yData"
-              :yNames="chart.yNames"
-              yAxisName="故障状态"
-              :jumpContext="chart.jumpContext"
-            ></Eecharts>
+      </div>
+      <div class="bottomBox">
+        <div class="BtLeft">
+          <h4>不对中故障状态趋势图:</h4>
+          <p class="status-legend">
+            <span class="legend-label">状态码说明:</span>
+            <span class="legend-item is-normal"
+              ><i class="legend-dot"></i>0 正常</span
+            >
+            <span class="legend-item is-warning"
+              ><i class="legend-dot"></i>1 报警</span
+            >
+            <span class="legend-item is-danger"
+              ><i class="legend-dot"></i>2 危险</span
+            >
+          </p>
+          <el-empty
+            v-if="!filteredTrendChartList.length"
+            description="暂无数据"
+            style="padding: 48px 0"
+          ></el-empty>
+          <div v-else class="mpoint-charts">
+            <el-carousel :key="trendCarouselKey">
+              <el-carousel-item
+                v-for="(chart, index) in filteredTrendChartList"
+                :key="`${chart.title}-${index}`"
+              >
+                <div class="mpoint-chart-item">
+                  <Eecharts
+                    style="height: 260px"
+                    :xData="chart.xData"
+                    :yData="chart.yData"
+                    :yNames="chart.yNames"
+                    yAxisName="故障状态"
+                    :jumpContext="chart.jumpContext"
+                  ></Eecharts>
+                </div>
+              </el-carousel-item>
+            </el-carousel>
           </div>
         </div>
       </div>
@@ -131,12 +130,9 @@
 </template>
 
 <script>
-import axios from "axios";
 import Eecharts from "./mpointEecharts.vue";
-import bingTwo from "./bingTwo.vue";
-import { cacheEntryPossiblyAdded, log10, promisify } from "plotly.js-dist";
 export default {
-  components: { Eecharts, bingTwo },
+  components: { Eecharts },
   props: {
     codedata: {
       type: Array,
@@ -197,6 +193,8 @@ export default {
       results: [],
       loading: false, // 控制加载状态
       trendChartList: [],
+      selectedFunctionTypeName: "",
+      selectedMonitorId: "",
     };
   },
   created() {},
@@ -204,6 +202,24 @@ export default {
     codedata: {
       handler(newVal) {
         this.tableData = newVal;
+        const exists = this.tableData.some(
+          (row) =>
+            this.resolveFunctionTypeNameByRow(row) ===
+            this.selectedFunctionTypeName,
+        );
+        if (!exists) {
+          if (this.tableData.length) {
+            this.selectedFunctionTypeName = this.resolveFunctionTypeNameByRow(
+              this.tableData[0],
+            );
+            this.selectedMonitorId = this.tableData[0]?.id
+              ? String(this.tableData[0].id)
+              : "";
+          } else {
+            this.selectedFunctionTypeName = "";
+            this.selectedMonitorId = "";
+          }
+        }
       },
       immediate: true, // 组件创建时立刻执行一次
       deep: true, // 如果 codedata 是复杂对象,建议加上
@@ -232,16 +248,27 @@ export default {
                 null
               );
             });
+            const normalizedFunctionTypeName = this.normalizeFunctionTypeName(
+              item?.functionTypeName ||
+                inner.find((d) => d?.functionTypeName)?.functionTypeName ||
+                pointRows.find((r) => r?.functionTypeName)?.functionTypeName ||
+                item?.functionTypeNameCn ||
+                item?.pointNameCn ||
+                item?.mesurePointName ||
+                "",
+            );
             return {
               title,
               xData,
               yData: [seriesY],
               yNames: [title],
+              functionTypeName: normalizedFunctionTypeName,
               jumpContext: {
                 companyCode: this.fieldCode,
                 windCode: this.fieldCode,
                 windTurbineNumber: this.windTurbineNumber,
                 itemKey: item?.itemKey ?? item?.item_key,
+                functionTypeName: normalizedFunctionTypeName,
                 id: item?.id,
                 mesurePointName: item?.mesurePointName || title,
                 detectionPointCn: item?.pointNameCn || title,
@@ -257,13 +284,107 @@ export default {
             };
           })
           .filter((it) => it.xData.length);
+        if (!this.selectedFunctionTypeName && this.tableData.length) {
+          this.selectedFunctionTypeName = this.resolveFunctionTypeNameByRow(
+            this.tableData[0],
+          );
+          this.selectedMonitorId = this.tableData[0]?.id
+            ? String(this.tableData[0].id)
+            : "";
+        }
       },
       immediate: true,
       deep: true,
     },
   },
 
+  computed: {
+    statusSummary() {
+      const rows = Array.isArray(this.tableData) ? this.tableData : [];
+      const total = rows.length;
+      const summary = { total, normal: 0, warning: 0, danger: 0 };
+      rows.forEach((row) => {
+        const code = Number(row?.status);
+        if (code === 0) summary.normal += 1;
+        else if (code === 1) summary.warning += 1;
+        else if (code === 2) summary.danger += 1;
+      });
+      return summary;
+    },
+    statusCards() {
+      const { total, normal, warning, danger } = this.statusSummary;
+      const toRatio = (count) =>
+        total > 0 ? `${((count / total) * 100).toFixed(0)}%` : "0%";
+      return [
+        {
+          key: "normal",
+          label: "正常",
+          count: normal,
+          ratio: toRatio(normal),
+          icon: "el-icon-check",
+        },
+        {
+          key: "warning",
+          label: "报警",
+          count: warning,
+          ratio: toRatio(warning),
+          icon: "el-icon-warning-outline",
+        },
+        {
+          key: "danger",
+          label: "危险",
+          count: danger,
+          ratio: toRatio(danger),
+          icon: "el-icon-warning",
+        },
+      ];
+    },
+    filteredTrendChartList() {
+      const selected = this.selectedFunctionTypeName;
+      if (!selected) return this.trendChartList;
+      return this.trendChartList.filter(
+        (chart) => this.chartFunctionTypeName(chart) === selected,
+      );
+    },
+    trendCarouselKey() {
+      return `trend-${this.selectedFunctionTypeName || "all"}`;
+    },
+  },
   methods: {
+    normalizeFunctionTypeName(name) {
+      return typeof name === "string" ? name.trim() : "";
+    },
+    rowFunctionTypeName(row) {
+      return this.normalizeFunctionTypeName(
+        row?.functionTypeName ? String(row.functionTypeName) : "",
+      );
+    },
+    resolveFunctionTypeNameByRow(row) {
+      const direct = this.rowFunctionTypeName(row);
+      if (direct) return direct;
+      const rowId =
+        row?.id !== undefined && row?.id !== null ? String(row.id) : "";
+      if (!rowId) return "";
+      const chart = this.trendChartList.find((item) => {
+        const rows = item?.jumpContext?.pointRows;
+        if (!Array.isArray(rows)) return false;
+        return rows.some(
+          (r) =>
+            r?.id !== undefined && r?.id !== null && String(r.id) === rowId,
+        );
+      });
+      return chart ? this.chartFunctionTypeName(chart) : "";
+    },
+    monitorDisplayName(row) {
+      return row?.functionTypeName || row?.detectionPointCn || "-";
+    },
+    chartFunctionTypeName(chart) {
+      return this.normalizeFunctionTypeName(
+        chart?.jumpContext?.functionTypeName
+          ? String(chart.jumpContext.functionTypeName)
+          : "",
+      );
+    },
     statusTagType(status) {
       const code = Number(status);
       if (code === 0) return "success";
@@ -280,6 +401,19 @@ export default {
       if (code === -1) return "未定义";
       return String(status);
     },
+    monitorTypeText(row) {
+      return (
+        row?.measureTypeName ||
+        row?.monitorType ||
+        row?.monitorTypeName ||
+        "振动-速度"
+      );
+    },
+    selectMonitorPoint(row) {
+      this.selectedMonitorId =
+        row?.id !== undefined && row?.id !== null ? String(row.id) : "";
+      this.selectedFunctionTypeName = this.resolveFunctionTypeNameByRow(row);
+    },
     handleDetail(row) {
       const itemKey = row.itemKey ?? row.item_key;
       const payload = {
@@ -341,6 +475,8 @@ export default {
       this.yData = [];
       this.statistics = {};
       this.trendChartList = [];
+      this.selectedFunctionTypeName = "";
+      this.selectedMonitorId = "";
       this.bearingStateColors = {
         innerRing: "#80808057",
         outerRing: "#80808057",
@@ -353,85 +489,262 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-h4 {
-  margin-bottom: 5px;
-  font-weight: 600;
+.gear-container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  margin-top: -10px;
 }
-.TopBox {
-  height: 280px;
+
+.gear-header {
   display: flex;
-  justify-content: space-around;
-  .leftdiv {
-    width: 50%;
+  align-items: stretch;
+  border-radius: 12px;
+  background: #f5f7fa5e;
+  overflow: hidden;
+  box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
+
+  .header-title {
+    width: 230px;
+    padding: 16px 18px;
     display: flex;
-    justify-content: space-around;
-    .stateBox {
-      .state {
+    flex-direction: column;
+    justify-content: center;
+    .title {
+      margin: 0;
+      font-size: 18px;
+      font-weight: 700;
+      color: #303133;
+    }
+    .sub-title {
+      margin: 6px 0 0;
+      font-size: 13px;
+      color: #909399;
+    }
+  }
+
+  .header-stat {
+    flex: 1;
+    display: grid;
+    grid-template-columns: repeat(3, minmax(0, 1fr));
+    ::v-deep .header-divider.el-divider--vertical {
+      height: 48px;
+      margin: 0 8px 0 0;
+      width: 1px;
+      background-color: #dcdfe65e;
+    }
+    .stat-item {
+      padding: 10px 18px;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      gap: 10px;
+      &:last-child {
+        border-right: 0;
+      }
+      .icon-wrap {
+        width: 42px;
+        height: 42px;
+        border-radius: 50%;
+        color: #fff;
         display: flex;
-        flex-direction: column;
-        justify-content: center; /* 整体居中 */
         align-items: center;
-        height: 200px;
-        gap: 20px; /* 控制间距 */
-        p {
-          width: 150px;
-          height: 40px;
-          background: rgb(227, 227, 227);
-          color: rgb(50, 50, 50);
-          text-align: center;
-          align-content: center;
-          font-weight: 600;
-        }
+        justify-content: center;
+        font-size: 22px;
       }
-      .btn-auto {
-        margin-top: 10px;
-        width: 100%;
-      }
-    }
-    .Btn {
-      width: 50%;
-      display: flex;
-      flex-direction: column; /* 垂直排列 */
-      justify-content: space-between; /* 顶部和底部对齐 */
-      min-height: 100px; /* 确保容器有足够高度 */
-      margin: 10px 0;
-      .PText {
+      .stat-item-content {
         display: flex;
-        justify-content: space-around;
-
-        span {
-          display: flex;
-          align-items: center;
-          i {
-            width: 30px;
-            height: 15px;
-            margin-right: 5px;
-          }
-          .color1 {
-            background-color: #8ae359;
-          }
-          .color2 {
-            background-color: #eecb5f;
-          }
-          .color3 {
-            background-color: #f7715f;
-          }
+        align-items: center;
+        gap: 10px;
+      }
+      .stat-content {
+        .label,
+        .count,
+        .ratio {
+          margin: 0;
+          line-height: 1.2;
+        }
+        .label {
+          font-size: 14px;
+          color: #303133;
+          font-weight: 500;
+        }
+        .count {
+          margin-top: 2px;
+          font-size: 30px;
+          font-weight: 700;
+          color: #303133;
         }
+        .ratio {
+          margin-top: 2px;
+          font-size: 12px;
+          color: #909399;
+        }
+      }
+      &.is-normal .icon-wrap {
+        background: #008080;
       }
-      margin: 10px 0;
-      .minp {
-        font-size: 12px;
-        margin-top: auto; /* 自动推到最底部 */
+      &.is-warning .icon-wrap {
+        background: #e6a23c;
+      }
+      &.is-danger .icon-wrap {
+        background: #f56c6c;
       }
     }
   }
+}
+
+.gear {
+  height: 100%;
+  display: flex;
+  justify-content: space-between;
+  gap: 10px;
+  .TopBox {
+    width: 40%;
+  }
+  .bottomBox {
+    width: 59%;
+    height: 360px;
+  }
+}
+h4 {
+  margin-bottom: 5px;
+  font-weight: 600;
+}
+
+.TopBox {
+  display: flex;
+  justify-content: space-around;
   .rightdiv {
     width: 100%;
+    .monitor-panel {
+      border-radius: 14px;
+      padding: 14px;
+      min-height: 360px;
+      box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
+    }
+    .monitor-empty {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      min-height: 260px;
+      background: #fff;
+      border-radius: 10px;
+    }
+    .monitor-list {
+      display: flex;
+      flex-direction: column;
+      gap: 10px;
+    }
+    .monitor-item {
+      position: relative;
+      display: flex;
+      align-items: center;
+      gap: 14px;
+      border: 1px solid #e4e7ed;
+      border-radius: 10px;
+      background: #fff;
+      padding: 14px 16px;
+      cursor: pointer;
+      transition: all 0.2s ease;
+      &.is-active {
+        background: #f0f5f7;
+      }
+      &.is-active::before {
+        content: "";
+        position: absolute;
+        left: 0;
+        top: 10px;
+        bottom: 10px;
+        width: 4px;
+        background: #74727224;
+        box-shadow: 0 0 0 2px #24a16600 inset;
+        border-radius: 0 4px 4px 0;
+      }
+    }
+    .index-badge {
+      width: 30px;
+      height: 30px;
+      border-radius: 50%;
+      color: #fff;
+      font-size: 16px;
+      font-weight: 700;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      flex-shrink: 0;
+      &.is-success {
+        background: #008080;
+      }
+      &.is-warning {
+        background: #e6a23c;
+      }
+      &.is-danger {
+        background: #f56c6c;
+      }
+      &.is-info {
+        background: #909399;
+      }
+    }
+    .item-main {
+      flex: 1;
+      min-width: 0;
+      .item-name {
+        margin: 0 0 8px;
+        font-size: 16px;
+        font-weight: 700;
+        color: #1f2d3d;
+      }
+      .item-line {
+        margin: 0;
+        font-size: 14px;
+        line-height: 1.5;
+        color: #6b7280;
+      }
+    }
+    .item-right {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+    }
+    .status-pill {
+      display: inline-flex;
+      align-items: center;
+      gap: 6px;
+      border-radius: 12px;
+      padding: 10px 16px;
+      font-size: 16px;
+      font-weight: 700;
+      line-height: 1;
+      .status-dot {
+        width: 10px;
+        height: 10px;
+        border-radius: 50%;
+        background: currentColor;
+      }
+      &.is-success {
+        color: #21a366;
+        background: #e7f6ee;
+      }
+      &.is-warning {
+        color: #d48806;
+        background: #fff6e5;
+      }
+      &.is-danger {
+        color: #f04438;
+        background: #ffebee;
+      }
+      &.is-info {
+        color: #606266;
+        background: #f4f4f5;
+      }
+    }
     .fenye {
       display: flex;
-      justify-content: space-between;
-      margin: 5px 0;
-      font-size: 11px;
+      justify-content: flex-end;
+      margin: 10px 0 0;
+      font-size: 12px;
       line-height: 30px;
       span {
         font-weight: 600;
@@ -443,54 +756,16 @@ h4 {
 .bottomBox {
   display: flex;
   justify-content: space-around;
-  margin-top: 10px;
+  box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
+  border-radius: 12px;
   .BtLeft {
-    border: 1px solid rgb(231, 231, 231);
     width: 100%;
-    padding: 10px;
-  }
-  .BtRight {
-    // width: 49%;
-    border: 1px solid rgb(231, 231, 231);
-    padding: 10px;
-    .BtRightDiv {
-      padding: 0 10px;
-      h4 {
-        font-size: 14px;
-      }
-      .BtRightP {
-        line-height: 30px;
-        font-size: 12px;
-        display: flex;
-        margin-bottom: 5px;
-        width: 50%;
-        span {
-          margin-left: 10px;
-          .el-input {
-            width: 100px;
-          }
-        }
-        .label-text {
-          width: 140px;
-        }
-      }
-      .canshu {
-        display: flex;
-        flex-wrap: wrap;
-        justify-content: space-between;
-      }
-    }
-  }
-  .dialog-footer {
-    margin-top: 20px;
-    text-align: right;
+    padding: 10px 20px;
   }
 }
 
 .mpoint-charts {
-  display: grid;
-  grid-template-columns: repeat(2, minmax(0, 1fr));
-  gap: 16px;
+  display: block;
 }
 
 .mpoint-chart-item {
@@ -499,16 +774,50 @@ h4 {
   padding: 8px 10px;
 }
 
-.mpoint-chart-title {
-  margin: 0 0 6px;
-  font-size: 13px;
-  color: #303133;
+.status-legend {
+  margin: 4px 0 8px;
+  font-size: 12px;
+  color: #909399;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  .legend-label {
+    color: #606266;
+  }
+  .legend-item {
+    display: inline-flex;
+    align-items: center;
+    gap: 4px;
+    .legend-dot {
+      width: 8px;
+      height: 8px;
+      border-radius: 50%;
+      background: currentColor;
+    }
+    &.is-normal {
+      color: #008080;
+    }
+    &.is-warning {
+      color: #e6a23c;
+    }
+    &.is-danger {
+      color: #f56c6c;
+    }
+  }
 }
 
-@media (max-width: 1200px) {
-  .mpoint-charts {
-    grid-template-columns: 1fr;
-  }
+::v-deep .el-carousel__indicator .el-carousel__button {
+  background-color: rgba(144, 147, 153, 0.45);
+  opacity: 1;
+}
+::v-deep .el-carousel__indicator.is-active .el-carousel__button {
+  background-color: #24a166;
+}
+::v-deep .el-carousel__indicator:hover .el-carousel__button {
+  background-color: #24a166aa;
+}
+::v-deep .el-carousel__indicators--horizontal {
+  bottom: -10px !important;
 }
 ::v-deep .el-table__cell {
   padding: 2px 0;

+ 579 - 132
src/views/health/components/malfunction/gear.vue

@@ -1,131 +1,137 @@
 <template>
-  <div>
-    <div class="TopBox">
-      <div class="rightdiv">
-        <el-table
-          ref="multipleTable"
-          :data="tableData"
-          tooltip-effect="dark"
-          style="width: 100%"
-          height="250"
-          stripe
-          border
-        >
-          <!-- <el-table-column fixed type="selection" width="55"> </el-table-column> -->
-          <el-table-column
-            type="index"
-            label="序号"
-            align="center"
-            width="100"
-            :index="bearingTableIndex"
-          >
-          </el-table-column>
-          <el-table-column prop="detectionPointCn" label="名称" align="left">
-          </el-table-column>
-          <!-- <el-table-column prop="timeStamp" label="时间" align="center">
-          </el-table-column> -->
-
-          <el-table-column label="状态" align="center" width="150">
-            <template slot-scope="scope">
-              <el-tag
-                size="mini"
-                type="warning"
-                :type="`${statusTagType(scope.row.status)}`"
-              >
-                {{ formatStatusText(scope.row.status) }}
-              </el-tag>
-            </template>
-          </el-table-column>
-          <!-- <el-table-column prop="" label="操作" align="center">
-            <template slot-scope="scope">
-              <el-tag type="warning">标签四</el-tag>
-              <el-button
-                type="text"
-                size="small"
-                @click="handleDetail(scope.row)"
-                >查看振动分析</el-button
-              >
-            </template>
-          </el-table-column> -->
-        </el-table>
-        <div class="fenye">
-          <p></p>
-          <el-pagination
-            @current-change="handleCurrentChange"
-            :current-page="currentPage"
-            layout="total, prev, pager, next"
-            :total="totalCount"
-            :page-size="pageSize"
-            small
-          ></el-pagination>
-        </div>
+  <div class="gear-container">
+    <div class="gear-header">
+      <div class="header-title">
+        <p class="title">齿轮诊断状态总览</p>
+        <p class="sub-title">共 {{ statusSummary.total }} 个监测点</p>
       </div>
-      <!-- <div class="leftdiv">
-        <div class="stateBox">
-          <h4>齿轮状态</h4>
-          <div class="state">
-            <p :style="{ backgroundColor: bearingStateColors.innerRing }">
-              齿轮磨损
-            </p>
-            <p :style="{ backgroundColor: bearingStateColors.outerRing }">
-              齿轮断齿
-            </p>
+      <div class="header-stat">
+        <div
+          v-for="item in statusCards"
+          :key="item.key"
+          class="stat-item"
+          :class="`is-${item.key}`"
+        >
+          <el-divider direction="vertical" class="header-divider"></el-divider>
+          <div class="stat-item-content">
+            <div class="icon-wrap">
+              <i :class="item.icon"></i>
+            </div>
+            <div class="stat-content">
+              <p class="label">{{ item.label }}</p>
+              <p class="count">{{ item.count }}</p>
+              <p class="ratio">占比 {{ item.ratio }}</p>
+            </div>
           </div>
-          <el-button
-            type="primary"
-            size="small"
-            @click="automaticDiagnosis"
-            class="btn-auto"
-            :loading="loading"
-            >诊断</el-button
-          >
+          <div class="stat-nobox"></div>
         </div>
-
-        <div class="Btn">
-          <div style="height: 200px">
-            <bingTwo :statistics="statistics"> </bingTwo>
+      </div>
+    </div>
+    <div class="gear">
+      <div class="TopBox">
+        <div class="rightdiv">
+          <div class="monitor-panel">
+            <h4>齿轮监测点列表</h4>
+            <div v-if="!tableData.length" class="monitor-empty">
+              <el-empty description="暂无监测点数据"></el-empty>
+            </div>
+            <div v-else class="monitor-list">
+              <div
+                v-for="(row, index) in tableData"
+                :key="row.id || `${row.functionTypeName}-${index}`"
+                class="monitor-item"
+                :class="{
+                  'is-active':
+                    selectedFunctionTypeName &&
+                    selectedFunctionTypeName === rowFunctionTypeName(row),
+                }"
+                @click="selectMonitorPoint(row)"
+              >
+                <div
+                  class="index-badge"
+                  :class="`is-${statusTagType(row.status)}`"
+                >
+                  {{ bearingTableIndex(index) }}
+                </div>
+                <div class="item-main">
+                  <p class="item-name">
+                    {{ monitorDisplayName(row) }}
+                  </p>
+                  <p class="item-line">监测类型:{{ monitorTypeText(row) }}</p>
+                  <!-- <p class="item-line">型号:{{ row.brandType }}</p>
+                  <p class="item-line">品牌:{{ row.brand }}</p> -->
+                </div>
+                <div class="item-right">
+                  <span
+                    class="status-pill"
+                    :class="`is-${statusTagType(row.status)}`"
+                  >
+                    <i class="status-dot"></i>
+                    {{ formatStatusText(row.status) }}
+                  </span>
+                  <!-- <el-button
+                    type="text"
+                    class="detail-btn"
+                    @click.stop="handleDetail(row)"
+                  >
+                    <i class="el-icon-arrow-right"></i>
+                  </el-button> -->
+                </div>
+              </div>
+            </div>
           </div>
-
-          <div class="minp">
-            <p class="PText">
-              <span><i class="color1"></i>正常</span
-              ><span><i class="color2"></i>报警</span
-              ><span><i class="color3"></i>危险</span>
-            </p>
-
-            <h4>诊断步骤:</h4>
-            <p>1、选择振动测点与起止时间,点击“查询”;</p>
-            <p>2、点击“诊断”输出最终齿轮状态结果。</p>
+          <div class="fenye">
+            <p></p>
+            <el-pagination
+              @current-change="handleCurrentChange"
+              :current-page="currentPage"
+              layout="total, prev, pager, next"
+              :total="totalCount"
+              :page-size="pageSize"
+              small
+            ></el-pagination>
           </div>
         </div>
-      </div> -->
-    </div>
-    <div class="bottomBox">
-      <div class="BtLeft">
-        <h4>齿轮状态趋势图:</h4>
-        <p style="font-size: 12px; color: #999">
-          <span>状态码说明:</span>0正常,1报警,2危险
-        </p>
-        <el-empty
-          v-if="!trendChartList.length"
-          description="暂无数据"
-          style="padding: 46px 0"
-        ></el-empty>
-        <div v-else class="mpoint-charts">
-          <div
-            v-for="(chart, index) in trendChartList"
-            :key="`${chart.title}-${index}`"
-            class="mpoint-chart-item"
-          >
-            <!-- <p class="mpoint-chart-title">{{ chart.title }}</p> -->
-            <Eecharts
-              style="height: 260px"
-              :xData="chart.xData"
-              :yData="chart.yData"
-              :yNames="chart.yNames"
-              yAxisName="故障状态"
-              :jumpContext="chart.jumpContext"
-            ></Eecharts>
+      </div>
+      <div class="bottomBox">
+        <div class="BtLeft">
+          <h4>齿轮状态趋势图:</h4>
+          <p class="status-legend">
+            <span class="legend-label">状态码说明:</span>
+            <span class="legend-item is-normal"
+              ><i class="legend-dot"></i>0 正常</span
+            >
+            <span class="legend-item is-warning"
+              ><i class="legend-dot"></i>1 报警</span
+            >
+            <span class="legend-item is-danger"
+              ><i class="legend-dot"></i>2 危险</span
+            >
+          </p>
+          <el-empty
+            v-if="!filteredTrendChartList.length"
+            description="暂无数据"
+            style="padding: 46px 0"
+          ></el-empty>
+          <div v-else class="mpoint-charts">
+            <el-carousel :key="trendCarouselKey">
+              <el-carousel-item
+                v-for="(chart, index) in filteredTrendChartList"
+                :key="`${chart.title}-${index}`"
+              >
+                <div class="mpoint-chart-item">
+                  <!-- <p class="mpoint-chart-title">{{ chart.title }}</p> -->
+                  <Eecharts
+                    style="height: 260px"
+                    :xData="chart.xData"
+                    :yData="chart.yData"
+                    :yNames="chart.yNames"
+                    yAxisName="故障状态"
+                    :jumpContext="chart.jumpContext"
+                  ></Eecharts>
+                </div>
+              </el-carousel-item>
+            </el-carousel>
           </div>
         </div>
       </div>
@@ -137,7 +143,6 @@
 import axios from "axios";
 import Eecharts from "./mpointEecharts.vue";
 import bingTwo from "./bingTwo.vue";
-import { cacheEntryPossiblyAdded } from "plotly.js-dist";
 export default {
   components: { Eecharts, bingTwo },
   props: {
@@ -198,6 +203,8 @@ export default {
       loading: false, // 控制加载状态
       statistics: {},
       trendChartList: [],
+      selectedFunctionTypeName: "",
+      selectedMonitorId: "",
     };
   },
   created() {},
@@ -205,6 +212,24 @@ export default {
     codedata: {
       handler(newVal) {
         this.tableData = newVal;
+        const exists = this.tableData.some(
+          (row) =>
+            this.resolveFunctionTypeNameByRow(row) ===
+            this.selectedFunctionTypeName,
+        );
+        if (!exists) {
+          if (this.tableData.length) {
+            this.selectedFunctionTypeName = this.resolveFunctionTypeNameByRow(
+              this.tableData[0],
+            );
+            this.selectedMonitorId = this.tableData[0]?.id
+              ? String(this.tableData[0].id)
+              : "";
+          } else {
+            this.selectedFunctionTypeName = "";
+            this.selectedMonitorId = "";
+          }
+        }
       },
       immediate: true, // 组件创建时立刻执行一次
       deep: true, // 如果 codedata 是复杂对象,建议加上
@@ -233,16 +258,27 @@ export default {
                 null
               );
             });
+            const normalizedFunctionTypeName = this.normalizeFunctionTypeName(
+              item?.functionTypeName ||
+                inner.find((d) => d?.functionTypeName)?.functionTypeName ||
+                pointRows.find((r) => r?.functionTypeName)?.functionTypeName ||
+                item?.functionTypeNameCn ||
+                item?.pointNameCn ||
+                item?.mesurePointName ||
+                "",
+            );
             return {
               title,
               xData,
               yData: [seriesY],
               yNames: [title],
+              functionTypeName: normalizedFunctionTypeName,
               jumpContext: {
                 companyCode: this.fieldCode,
                 windCode: this.fieldCode,
                 windTurbineNumber: this.windTurbineNumber,
                 itemKey: item?.itemKey ?? item?.item_key,
+                functionTypeName: normalizedFunctionTypeName,
                 id: item?.id,
                 mesurePointName: item?.mesurePointName || title,
                 detectionPointCn: item?.pointNameCn || title,
@@ -258,13 +294,107 @@ export default {
             };
           })
           .filter((it) => it.xData.length);
+        if (!this.selectedFunctionTypeName && this.tableData.length) {
+          this.selectedFunctionTypeName = this.resolveFunctionTypeNameByRow(
+            this.tableData[0],
+          );
+          this.selectedMonitorId = this.tableData[0]?.id
+            ? String(this.tableData[0].id)
+            : "";
+        }
       },
       immediate: true,
       deep: true,
     },
   },
+  computed: {
+    statusSummary() {
+      const rows = Array.isArray(this.tableData) ? this.tableData : [];
+      const total = rows.length;
+      const summary = { total, normal: 0, warning: 0, danger: 0 };
+      rows.forEach((row) => {
+        const code = Number(row?.status);
+        if (code === 0) summary.normal += 1;
+        else if (code === 1) summary.warning += 1;
+        else if (code === 2) summary.danger += 1;
+      });
+      return summary;
+    },
+    statusCards() {
+      const { total, normal, warning, danger } = this.statusSummary;
+      const toRatio = (count) =>
+        total > 0 ? `${((count / total) * 100).toFixed(0)}%` : "0%";
+      return [
+        {
+          key: "normal",
+          label: "正常",
+          count: normal,
+          ratio: toRatio(normal),
+          icon: "el-icon-check",
+        },
+        {
+          key: "warning",
+          label: "报警",
+          count: warning,
+          ratio: toRatio(warning),
+          icon: "el-icon-warning-outline",
+        },
+        {
+          key: "danger",
+          label: "危险",
+          count: danger,
+          ratio: toRatio(danger),
+          icon: "el-icon-warning",
+        },
+      ];
+    },
+    filteredTrendChartList() {
+      const selected = this.selectedFunctionTypeName;
+      if (!selected) return this.trendChartList;
+      return this.trendChartList.filter(
+        (chart) => this.chartFunctionTypeName(chart) === selected,
+      );
+    },
+    trendCarouselKey() {
+      return `trend-${this.selectedFunctionTypeName || "all"}`;
+    },
+  },
 
   methods: {
+    normalizeFunctionTypeName(name) {
+      return typeof name === "string" ? name.trim() : "";
+    },
+    rowFunctionTypeName(row) {
+      return this.normalizeFunctionTypeName(
+        row?.functionTypeName ? String(row.functionTypeName) : "",
+      );
+    },
+    resolveFunctionTypeNameByRow(row) {
+      const direct = this.rowFunctionTypeName(row);
+      if (direct) return direct;
+      const rowId =
+        row?.id !== undefined && row?.id !== null ? String(row.id) : "";
+      if (!rowId) return "";
+      const chart = this.trendChartList.find((item) => {
+        const rows = item?.jumpContext?.pointRows;
+        if (!Array.isArray(rows)) return false;
+        return rows.some(
+          (r) =>
+            r?.id !== undefined && r?.id !== null && String(r.id) === rowId,
+        );
+      });
+      return chart ? this.chartFunctionTypeName(chart) : "";
+    },
+    monitorDisplayName(row) {
+      return row?.functionTypeName || row?.detectionPointCn || "-";
+    },
+    chartFunctionTypeName(chart) {
+      return this.normalizeFunctionTypeName(
+        chart?.jumpContext?.functionTypeName
+          ? String(chart.jumpContext.functionTypeName)
+          : "",
+      );
+    },
     statusTagType(status) {
       const code = Number(status);
       if (code === 0) return "success";
@@ -281,6 +411,14 @@ export default {
       if (code === -1) return "未定义";
       return String(status);
     },
+    monitorTypeText(row) {
+      return "振动-加速度";
+    },
+    selectMonitorPoint(row) {
+      this.selectedMonitorId =
+        row?.id !== undefined && row?.id !== null ? String(row.id) : "";
+      this.selectedFunctionTypeName = this.resolveFunctionTypeNameByRow(row);
+    },
     handleDetail(row) {
       const itemKey = row.itemKey ?? row.item_key;
       const payload = {
@@ -351,6 +489,8 @@ export default {
       this.statistics = {};
       this.result = {};
       this.trendChartList = [];
+      this.selectedFunctionTypeName = "";
+      this.selectedMonitorId = "";
       this.bearingStateColors = {
         innerRing: "#80808057",
         outerRing: "#80808057",
@@ -363,6 +503,129 @@ export default {
 </script>
 
 <style lang="scss" scoped>
+.gear-container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  margin-top: -10px;
+}
+
+.gear-header {
+  display: flex;
+  align-items: stretch;
+  // border: 1px solid #ebeef5;
+  border-radius: 12px;
+  background: #f5f7fa5e;
+  overflow: hidden;
+  box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
+
+  .header-title {
+    width: 230px;
+    padding: 16px 18px;
+    // border-right: 1px solid #ebeef5;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    .title {
+      margin: 0;
+      font-size: 18px;
+      font-weight: 700;
+      color: #303133;
+    }
+    .sub-title {
+      margin: 6px 0 0;
+      font-size: 13px;
+      color: #909399;
+    }
+  }
+
+  .header-stat {
+    flex: 1;
+    display: grid;
+    grid-template-columns: repeat(3, minmax(0, 1fr));
+    ::v-deep .header-divider.el-divider--vertical {
+      height: 48px; /* 分割线长度 */
+      margin: 0 8px 0 0; /* 与图标间距 */
+      width: 1px; /* 粗细 */
+      background-color: #dcdfe65e; /* 颜色 */
+    }
+    .stat-item {
+      padding: 10px 18px;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      gap: 10px;
+      // border-right: 1px solid #ebeef5;
+      &:last-child {
+        border-right: 0;
+      }
+      .icon-wrap {
+        width: 42px;
+        height: 42px;
+        border-radius: 50%;
+        color: #fff;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 22px;
+      }
+      .stat-item-content {
+        display: flex;
+        align-items: center;
+        gap: 10px;
+      }
+      .stat-content {
+        .label,
+        .count,
+        .ratio {
+          margin: 0;
+          line-height: 1.2;
+        }
+        .label {
+          font-size: 14px;
+          color: #303133;
+          font-weight: 500;
+        }
+        .count {
+          margin-top: 2px;
+          font-size: 30px;
+          font-weight: 700;
+          color: #303133;
+        }
+        .ratio {
+          margin-top: 2px;
+          font-size: 12px;
+          color: #909399;
+        }
+      }
+      &.is-normal .icon-wrap {
+        background: #008080;
+      }
+      &.is-warning .icon-wrap {
+        background: #e6a23c;
+      }
+      &.is-danger .icon-wrap {
+        background: #f56c6c;
+      }
+    }
+  }
+}
+
+.gear {
+  height: 100%;
+  display: flex;
+  justify-content: space-between;
+  // flex-direction: column;
+  gap: 10px;
+  .TopBox {
+    width: 40%;
+  }
+  .bottomBox {
+    width: 59%;
+    height: 360px;
+  }
+}
 h4 {
   margin-bottom: 5px;
   font-weight: 600;
@@ -457,12 +720,149 @@ h4 {
 
   .rightdiv {
     width: 100%;
+    .monitor-panel {
+      // border: 1px solid #e4e7ed;
+      border-radius: 14px;
+      // background: #f5f7fa;
+      padding: 14px;
+      // min-height: 360px;
+      height: 360px;
+      overflow-y: auto;
+      box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
+    }
+    .panel-title {
+      margin: 0 0 12px;
+      font-size: 18px;
+      font-weight: 700;
+      color: #1f2d3d;
+    }
+    .monitor-empty {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      min-height: 260px;
+      background: #fff;
+      border-radius: 10px;
+    }
+    .monitor-list {
+      display: flex;
+      flex-direction: column;
+      gap: 10px;
+    }
+    .monitor-item {
+      position: relative;
+      display: flex;
+      align-items: center;
+      gap: 14px;
+      border: 1px solid #e4e7ed;
+      border-radius: 10px;
+      background: #fff;
+      padding: 14px 16px;
+      cursor: pointer;
+      transition: all 0.2s ease;
+      &.is-active {
+        background: #f0f5f7;
+      }
+      &.is-active::before {
+        content: "";
+        position: absolute;
+        left: 0;
+        top: 10px;
+        bottom: 10px;
+        width: 4px;
+        background: #74727224;
+        box-shadow: 0 0 0 2px #24a16600 inset;
+        border-radius: 0 4px 4px 0;
+      }
+    }
+    .index-badge {
+      width: 30px;
+      height: 30px;
+      border-radius: 50%;
+      color: #fff;
+      font-size: 16px;
+      font-weight: 700;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      flex-shrink: 0;
+      &.is-success {
+        background: #008080;
+      }
+      &.is-warning {
+        background: #e6a23c;
+      }
+      &.is-danger {
+        background: #f56c6c;
+      }
+      &.is-info {
+        background: #909399;
+      }
+    }
+    .item-main {
+      flex: 1;
+      min-width: 0;
+      .item-name {
+        margin: 0 0 8px;
+        font-size: 16px;
+        font-weight: 700;
+        color: #1f2d3d;
+      }
+      .item-line {
+        margin: 0;
+        font-size: 14px;
+        line-height: 1.5;
+        color: #6b7280;
+      }
+    }
+    .item-right {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      .detail-btn {
+        margin-left: 2px;
+        font-size: 22px;
+        color: #909399;
+      }
+    }
+    .status-pill {
+      display: inline-flex;
+      align-items: center;
+      gap: 6px;
+      border-radius: 12px;
+      padding: 10px 16px;
+      font-size: 16px;
+      font-weight: 700;
+      line-height: 1;
+      .status-dot {
+        width: 10px;
+        height: 10px;
+        border-radius: 50%;
+        background: currentColor;
+      }
+      &.is-success {
+        color: #21a366;
+        background: #e7f6ee;
+      }
+      &.is-warning {
+        color: #d48806;
+        background: #fff6e5;
+      }
+      &.is-danger {
+        color: #f04438;
+        background: #ffebee;
+      }
+      &.is-info {
+        color: #606266;
+        background: #f4f4f5;
+      }
+    }
 
     .fenye {
       display: flex;
-      justify-content: space-between;
-      margin: 5px 0;
-      font-size: 11px;
+      justify-content: flex-end;
+      margin: 10px 0 0;
+      font-size: 12px;
       line-height: 30px;
 
       span {
@@ -475,12 +875,12 @@ h4 {
 .bottomBox {
   display: flex;
   justify-content: space-around;
-  margin-top: 10px;
-
+  box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
+  border-radius: 12px;
   .BtLeft {
-    border: 1px solid rgb(231, 231, 231);
+    // border: 1px solid rgb(231, 231, 231);
     width: 100%;
-    padding: 10px;
+    padding: 10px 20px;
   }
 
   .BtRight {
@@ -529,9 +929,10 @@ h4 {
 }
 
 .mpoint-charts {
-  display: grid;
-  grid-template-columns: repeat(2, minmax(0, 1fr));
-  gap: 16px;
+  // display: grid;
+  // grid-template-columns: repeat(2, minmax(0, 1fr));
+  // gap: 16px;
+  display: block;
 }
 
 .mpoint-chart-item {
@@ -546,6 +947,52 @@ h4 {
   color: #303133;
 }
 
+.status-legend {
+  margin: 4px 0 8px;
+  font-size: 12px;
+  color: #909399;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  .legend-label {
+    color: #606266;
+  }
+  .legend-item {
+    display: inline-flex;
+    align-items: center;
+    gap: 4px;
+    .legend-dot {
+      width: 8px;
+      height: 8px;
+      border-radius: 50%;
+      background: currentColor;
+    }
+    &.is-normal {
+      color: #008080;
+    }
+    &.is-warning {
+      color: #e6a23c;
+    }
+    &.is-danger {
+      color: #f56c6c;
+    }
+  }
+}
+
+/* el-carousel 指示器颜色(Element UI 2.x) */
+::v-deep .el-carousel__indicator .el-carousel__button {
+  background-color: rgba(144, 147, 153, 0.45); /* 默认灰 */
+  opacity: 1; /* Element UI 默认会用 opacity 控制,这里统一用背景色 */
+}
+::v-deep .el-carousel__indicator.is-active .el-carousel__button {
+  background-color: #24a166; /* 激活:主色蓝(可改) */
+}
+::v-deep .el-carousel__indicator:hover .el-carousel__button {
+  background-color: #24a166aa; /* hover */
+}
+::v-deep .el-carousel__indicators--horizontal {
+  bottom: -10px !important;
+}
 @media (max-width: 1200px) {
   .mpoint-charts {
     grid-template-columns: 1fr;

+ 45 - 5
src/views/health/components/malfunction/loose.vue

@@ -7,7 +7,6 @@
           :data="tableData"
           tooltip-effect="dark"
           style="width: 100%"
-          height="250"
           stripe
           border
         >
@@ -20,7 +19,7 @@
           >
           </el-table-column>
           <!-- <el-table-column fixed type="selection" width="55"> </el-table-column> -->
-          <el-table-column prop="detectionPointCn" label="名称" align="left">
+          <el-table-column prop="functionTypeName" label="名称" align="left">
           </el-table-column>
           <!-- <el-table-column prop="timeStamp" label="时间" align="center">
           </el-table-column> -->
@@ -101,8 +100,17 @@
     <div class="bottomBox">
       <div class="BtLeft">
         <h4>松动故障状态趋势图</h4>
-        <p style="font-size: 12px; color: #999">
-          <span>状态码说明:</span>0正常,1报警,2危险
+        <p class="status-legend">
+          <span class="legend-label">状态码说明:</span>
+          <span class="legend-item is-normal"
+            ><i class="legend-dot"></i>0 正常</span
+          >
+          <span class="legend-item is-warning"
+            ><i class="legend-dot"></i>1 报警</span
+          >
+          <span class="legend-item is-danger"
+            ><i class="legend-dot"></i>2 危险</span
+          >
         </p>
         <el-empty
           v-if="!trendChartList.length"
@@ -357,7 +365,7 @@ h4 {
   font-weight: 600;
 }
 .TopBox {
-  height: 280px;
+  // height: 280px;
   display: flex;
   justify-content: space-around;
   .leftdiv {
@@ -443,6 +451,38 @@ h4 {
   display: flex;
   justify-content: space-around;
   margin-top: 10px;
+  .status-legend {
+    margin: 4px 0 8px;
+    font-size: 12px;
+    color: #909399;
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    .legend-label {
+      color: #606266;
+    }
+    .legend-item {
+      display: inline-flex;
+      align-items: center;
+      gap: 4px;
+      .legend-dot {
+        width: 8px;
+        height: 8px;
+        border-radius: 50%;
+        background: currentColor;
+      }
+      &.is-normal {
+        color: #008080;
+      }
+      &.is-warning {
+        color: #e6a23c;
+      }
+      &.is-danger {
+        color: #f56c6c;
+      }
+    }
+  }
+
   .BtLeft {
     border: 1px solid rgb(231, 231, 231);
     width: 100%;

+ 44 - 5
src/views/health/components/malfunction/misalignment.vue

@@ -7,7 +7,6 @@
           :data="tableData"
           tooltip-effect="dark"
           style="width: 100%"
-          height="250"
           stripe
           border
         >
@@ -20,7 +19,7 @@
           >
           </el-table-column>
           <!-- <el-table-column fixed type="selection" width="55"> </el-table-column> -->
-          <el-table-column prop="detectionPointCn" label="名称" align="left">
+          <el-table-column prop="functionTypeName" label="名称" align="left">
           </el-table-column>
           <!-- <el-table-column prop="timeStamp" label="时间" align="center">
           </el-table-column> -->
@@ -100,8 +99,17 @@
     <div class="bottomBox">
       <div class="BtLeft">
         <h4>不平衡故障状态趋势图</h4>
-        <p style="font-size: 12px; color: #999">
-          <span>状态码说明:</span>0正常,1报警,2危险
+        <p class="status-legend">
+          <span class="legend-label">状态码说明:</span>
+          <span class="legend-item is-normal"
+            ><i class="legend-dot"></i>0 正常</span
+          >
+          <span class="legend-item is-warning"
+            ><i class="legend-dot"></i>1 报警</span
+          >
+          <span class="legend-item is-danger"
+            ><i class="legend-dot"></i>2 危险</span
+          >
         </p>
         <el-empty
           v-if="!trendChartList.length"
@@ -356,7 +364,7 @@ h4 {
   font-weight: 600;
 }
 .TopBox {
-  height: 280px;
+  // height: 280px;
   display: flex;
   justify-content: space-around;
   .leftdiv {
@@ -442,6 +450,37 @@ h4 {
   display: flex;
   justify-content: space-around;
   margin-top: 10px;
+  .status-legend {
+    margin: 4px 0 8px;
+    font-size: 12px;
+    color: #909399;
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    .legend-label {
+      color: #606266;
+    }
+    .legend-item {
+      display: inline-flex;
+      align-items: center;
+      gap: 4px;
+      .legend-dot {
+        width: 8px;
+        height: 8px;
+        border-radius: 50%;
+        background: currentColor;
+      }
+      &.is-normal {
+        color: #008080;
+      }
+      &.is-warning {
+        color: #e6a23c;
+      }
+      &.is-danger {
+        color: #f56c6c;
+      }
+    }
+  }
   .BtLeft {
     border: 1px solid rgb(231, 231, 231);
     width: 100%;

+ 92 - 13
src/views/health/components/malfunction/mpointEecharts.vue

@@ -1,7 +1,7 @@
 <!--
  * @Author: your name
  * @Date: 2026-04-09 16:13:52
- * @LastEditTime: 2026-04-10 16:56:18
+ * @LastEditTime: 2026-04-13 17:10:19
  * @LastEditors: MacBookPro
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/health/components/malfunction/cdEecharts.vue
@@ -41,18 +41,42 @@ export default {
       type: Boolean,
       default: true,
     },
+    /** 为 true 时:折线仍为各系列原色,仅数据点按 Y 状态码着色(0 绿 / 1 黄 / 2 红) */
+    enableStatusPointColors: {
+      type: Boolean,
+      default: true,
+    },
   },
   data() {
     return {
       myChart: null,
+      _resizeObserver: null,
     };
   },
   mounted() {
+    window.addEventListener("resize", this.onWindowResize);
     this.initChart();
+    this.$nextTick(() => {
+      this.safeResize();
+      this.setupResizeObserver();
+    });
+  },
+  activated() {
+    // keep-alive 从其它 Tab 切回时,容器由隐藏变为可见,需重新测量宽高
+    this.$nextTick(() => {
+      this.safeResize();
+    });
   },
   beforeDestroy() {
+    window.removeEventListener("resize", this.onWindowResize);
+    if (this._resizeObserver) {
+      this._resizeObserver.disconnect();
+      this._resizeObserver = null;
+    }
     if (this.myChart) {
       this.myChart.off("click", this.handlePointClick);
+      this.myChart.dispose();
+      this.myChart = null;
     }
   },
   watch: {
@@ -74,9 +98,50 @@ export default {
       handler: "initChart",
       immediate: true,
     },
+    enableStatusPointColors: {
+      handler: "initChart",
+      immediate: true,
+    },
   },
 
   methods: {
+    onWindowResize() {
+      this.safeResize();
+    },
+    safeResize() {
+      if (!this.myChart || !this.$refs.chartDom) return;
+      try {
+        this.myChart.resize();
+      } catch (e) {
+        console.error(e);
+      }
+    },
+    setupResizeObserver() {
+      if (typeof ResizeObserver === "undefined" || !this.$refs.chartDom) return;
+      if (this._resizeObserver) {
+        this._resizeObserver.disconnect();
+      }
+      this._resizeObserver = new ResizeObserver(() => {
+        this.$nextTick(() => this.safeResize());
+      });
+      this._resizeObserver.observe(this.$refs.chartDom);
+    },
+    /** 按状态码(Y 轴离散值)取数据点颜色;连接线仍用系列色,不调用本方法 */
+    getStatusPointColor(raw) {
+      const n = Number(raw);
+      if (Number.isNaN(n)) return "#909399";
+      const code = Math.round(n);
+      if (code === 0) return "#008080";
+      if (code === 1) return "#E6A23C";
+      if (code === 2) return "#F56C6C";
+      return "#909399";
+    },
+    pointYFromParams(params) {
+      const v = params?.value;
+      if (v == null) return NaN;
+      if (Array.isArray(v)) return Number(v[v.length - 1]);
+      return Number(v);
+    },
     initChart() {
       if (!this.$refs.chartDom) return;
 
@@ -85,19 +150,31 @@ export default {
         this.myChart.on("click", this.handlePointClick);
       }
 
-      const colors = ["#02aae9", "#5470C6", "#3CB9B9", "#9966CC"]; // 颜色列表可以根据需要扩展
+      const colors = ["#008080", "#5470C6", "#3CB9B9", "#9966CC"]; // 颜色列表可以根据需要扩展
 
-      const series = this.yData.map((data, index) => ({
-        name: this.yNames[index] || `系列${index + 1}`,
-        type: "line",
-        data: data,
-        lineStyle: {
-          color: colors[index % colors.length],
-        },
-        itemStyle: {
-          color: colors[index % colors.length],
-        },
-      }));
+      const series = this.yData.map((data, index) => {
+        const lineColor = colors[index % colors.length];
+        const base = {
+          name: this.yNames[index] || `系列${index + 1}`,
+          type: "line",
+          data,
+          lineStyle: { color: lineColor },
+          showSymbol: true,
+          symbol: "circle",
+          symbolSize: 6,
+        };
+        if (this.enableStatusPointColors) {
+          const pointColor = (params) =>
+            this.getStatusPointColor(this.pointYFromParams(params));
+          base.itemStyle = { color: pointColor, borderWidth: 0 };
+          base.emphasis = {
+            itemStyle: { color: pointColor, borderWidth: 0 },
+          };
+        } else {
+          base.itemStyle = { color: lineColor, borderWidth: 0 };
+        }
+        return base;
+      });
 
       const option = {
         tooltip: {
@@ -134,6 +211,8 @@ export default {
 
       this.myChart.clear(); // ✅ 清除旧配置
       this.myChart.setOption(option);
+      // 数据或布局变化后重算画布尺寸(避免 Tab/路由切换后 width 为 0)
+      this.$nextTick(() => this.safeResize());
     },
     handlePointClick(params) {
       if (!this.enablePointJump) return;