Sfoglia il codice sorgente

绘制首页样式重组

liujiejie 2 mesi fa
parent
commit
d6159c3af7

+ 25 - 2
src/components/map/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="map-container">
-    <div id="map" class="map"></div>
+    <div ref="mapRoot" class="map"></div>
     <!-- 以下为悬浮信息等 -->
     <div
       v-if="hoverInfo"
@@ -211,6 +211,7 @@ export default {
   name: "T-map",
   data() {
     return {
+      map: null,
       dialogVisible: false,
       tableData: [],
       hoverInfo: null,
@@ -243,7 +244,7 @@ export default {
   },
   mounted() {
     this.map = new Map({
-      target: "map",
+      target: this.$refs.mapRoot,
       view: new View({
         projection: "EPSG:4326",
         center: fromLonLat([116.389, 39.903]),
@@ -311,6 +312,28 @@ export default {
     // }
     // 这个放在外面
     this.initEvent();
+    this.$nextTick(() => {
+      if (this.map) {
+        this.map.updateSize();
+      }
+    });
+  },
+  activated() {
+    if (!this.map) return;
+    this.map.setTarget(this.$refs.mapRoot || null);
+    this.$nextTick(() => {
+      this.map && this.map.updateSize();
+    });
+  },
+  deactivated() {
+    if (this.map) {
+      this.map.setTarget(null);
+    }
+  },
+  beforeDestroy() {
+    if (this.map) {
+      this.map.setTarget(null);
+    }
   },
   methods: {
     onHoverLeave() {

+ 109 - 195
src/views/admin/cockpitManage/Index.vue

@@ -1,75 +1,89 @@
 <template>
-  <div class="map-ditu">
-    <Tmap ref="map" :maplist="maplist" @feature-click="featureClick"></Tmap>
-    <div class="statistics">
-      <div class="ledata">
-        <selecttree
-          placeholder="请选择所属公司"
-          :list="parentOpt"
-          type="1"
-          v-model="companyCode"
-          @change="parentChange"
-          :defaultParentProps="{
-            children: 'children',
-            label: 'companyName',
-            value: 'codeNumber',
-          }"
-        >
-        </selecttree>
+  <div class="cockpit-dashboard-page map-ditu">
+    <Tmap
+      ref="map"
+      class="cockpit-dashboard__map"
+      :maplist="maplist"
+      @feature-click="featureClick"
+    />
 
-        <p>
-          <span class="SpText">已接入风场数:</span>
-          {{ totalList?.fieldSumNumber ? totalList.fieldSumNumber : 0 }}
-          座
-        </p>
-        <!-- <p>
-          <span class="SpText">已完成分析风场:</span>
-          {{
-            totalList?.analysisFinishNumber ? totalList.analysisFinishNumber : 0
-          }}
-          个
-        </p> -->
-        <!-- <p>
-          <span class="SpText">未完成分析风场:</span>
-          {{
-            totalList?.analysisUnFinishedNumber
-              ? totalList.analysisUnFinishedNumber
-              : 0
-          }}
-          个
-        </p> -->
-        <p>
-          <span class="SpText">已接入风机数:</span>
-          {{ totalList?.engineGroupNumber ? totalList.engineGroupNumber : 0 }}
-          台
-        </p>
-        <!-- <p>
-          <span class="SpText">风场状态:</span>
-          <span class="red"></span>
-          <span class="yellow"></span>
-          <span class="green"></span>
-        </p> -->
-        <el-switch
-          v-model="value1"
-          active-text="显示"
-          inactive-text="隐藏"
-          @change="handleSwitchChange"
+    <div class="cockpit-dashboard__overlay">
+      <!-- 左侧:统计面板始终显示;其余面板由 switch 控制 -->
+      <aside class="cockpit-dashboard__left">
+        <section
+          class="cockpit-panel cockpit-panel--stats cockpit-panel--compact"
         >
-        </el-switch>
-      </div>
-      <leftdata v-show="ShowRi" class="task"></leftdata>
+          <selecttree
+            placeholder="请选择所属公司"
+            :list="parentOpt"
+            type="1"
+            v-model="companyCode"
+            @change="parentChange"
+            :defaultParentProps="{
+              children: 'children',
+              label: 'companyName',
+              value: 'codeNumber',
+            }"
+          />
+          <p class="cockpit-stats__row">
+            <span class="SpText">已接入风场数:</span>
+            <span>{{ totalList?.fieldSumNumber || 0 }} 座</span>
+          </p>
+          <p class="cockpit-stats__row">
+            <span class="SpText">已接入风机数:</span>
+            <span>{{ totalList?.engineGroupNumber || 0 }} 台</span>
+          </p>
+
+          <div class="cockpit-stats__switch">
+            <el-switch
+              v-model="value1"
+              active-text="显示"
+              inactive-text="隐藏"
+              @change="handleSwitchChange"
+            />
+          </div>
+        </section>
+
+        <template v-if="ShowRi">
+          <section class="cockpit-panel cockpit-panel--analysis-history">
+            <Rightdata
+              panel="analysis-history"
+              :maplistArr="maplistArr"
+              :defaultdata="defaultdata"
+            />
+          </section>
+
+          <section
+            class="cockpit-panel cockpit-panel--task cockpit-panel--compact"
+            v-hasPermi="['home:performance:performanceData']"
+          >
+            <Leftdata panel="task" />
+          </section>
+        </template>
+      </aside>
+
+      <div v-show="ShowRi" class="cockpit-dashboard__center-spacer" />
+
+      <!-- 右侧:健康 / 健康数据与业务 Tab -->
+      <aside
+        v-show="ShowRi"
+        class="cockpit-dashboard__right"
+        v-hasPermi="['home:performance:performanceData']"
+      >
+        <section class="cockpit-panel cockpit-panel--health">
+          <CockpitHealthPanel
+            :maplistArr="maplistArr"
+            :defaultdata="defaultdata"
+          />
+        </section>
+
+        <section class="cockpit-panel cockpit-panel--inbound">
+          <Leftdata panel="inbound" />
+        </section>
+      </aside>
     </div>
 
-    <Rightdata
-      v-show="ShowRi"
-      v-hasPermi="['home:performance:performanceData']"
-      ref="childRef"
-      :maplistArr="maplistArr"
-      :defaultdata="defaultdata"
-      class="ridata"
-    >
-    </Rightdata>
-    <router-view></router-view>
+    <router-view />
   </div>
 </template>
 
@@ -82,6 +96,7 @@ import {
 import Tmap from "@/components/map";
 import Rightdata from "./component/rightdata.vue";
 import Leftdata from "./component/leftdata.vue";
+import CockpitHealthPanel from "./component/CockpitHealthPanel.vue";
 
 export default {
   name: "Index",
@@ -90,15 +105,12 @@ export default {
     Rightdata,
     selecttree,
     Leftdata,
+    CockpitHealthPanel,
   },
   data() {
     return {
       ShowRi: true,
-      treeval: "",
       value1: true,
-      treeval: "", // 绑定的输入框值
-      showTree: false, // 控制树列表显示
-      treeData: [],
       maplist: [],
       maplistArr: {},
       totalList: [],
@@ -112,34 +124,30 @@ export default {
     this.GETtotal();
   },
   mounted() {
-    //模拟地图上的点位
-    const data = this.maplist;
-    // data.forEach((element) => {
-    //   console.log(element);
-    //   // this.$refs.map.addMarker({ point: [116.40740,39.90420], val: "1" });
-    // });
+    this.restoreMapMarkers();
+  },
+  activated() {
+    this.restoreMapMarkers();
   },
   methods: {
-    // 获取总数
+    restoreMapMarkers() {
+      if (!this.maplistArr || !Object.keys(this.maplistArr).length) return;
+      this.$nextTick(() => {
+        this.parentChange(this.maplistArr);
+      });
+    },
     GETtotal() {
       getCompanyFieldNumberVo().then((res) => {
         this.totalList = res.data;
       });
     },
-    // 获取企业数
     async GETtree() {
       const res = await getSysOrganizationAuthTreeByRoleId();
-      const treedata = res.data;
-      const processedData = this.processTreeData(treedata);
+      const processedData = this.processTreeData(res.data);
       this.parentOpt = processedData;
       this.defaultdata = res.data[0];
       this.parentChange(this.defaultdata);
-      // const firstLayer = this.findFirstFieldLayer(processedData);
-      // if (firstLayer) {
-      //   this.parentChange(  this.defaultdata);
-      // }
     },
-    //过滤数据
     processTreeData(treeData) {
       const processedData = [];
       function processNode(node) {
@@ -147,9 +155,7 @@ export default {
           node.companyName = node.fieldName;
         }
         if (node.children && node.children.length > 0) {
-          node.children.forEach((child) => {
-            processNode(child);
-          });
+          node.children.forEach((child) => processNode(child));
         }
       }
       treeData.forEach((root) => {
@@ -158,73 +164,52 @@ export default {
       });
       return processedData;
     },
-    handleTreeData() {
-      this.treeData = this.processTreeData(this.treeData);
-    },
     parseCoordinates(input) {
       if (input && typeof input === "string") {
         return input.split(",").map(Number);
       }
-      // debugger;
       return [];
     },
     parentChange(data) {
-      //data为当前选中对象
-      this.$refs.map.clearMarkers();
       this.maplist = data;
       this.maplistArr = data;
+      const mapRef = this.$refs.map;
+      if (!mapRef || typeof mapRef.clearMarkers !== "function") {
+        return;
+      }
+      mapRef.clearMarkers();
       if (data.codeType === "field") {
         if (this.parseCoordinates(data.longitudeAndLatitudeString).length > 0) {
-          this.$refs.map.addMarker({
+          mapRef.addMarker({
             point: this.parseCoordinates(data.longitudeAndLatitudeString),
             val: data.analysisState,
             ...data,
           });
           return;
         }
-        this.$refs.map.clearMarkers();
+        mapRef.clearMarkers();
       } else {
-        const dataMapList = data.children;
-        dataMapList.forEach((element) => {
+        (data.children || []).forEach((element) => {
           if (
             this.parseCoordinates(element.longitudeAndLatitudeString).length >
               0 &&
             element.codeType === "field"
           ) {
-            this.$refs.map.addMarker({
+            mapRef.addMarker({
               point: this.parseCoordinates(element.longitudeAndLatitudeString),
               val: element.analysisState,
               ...element,
             });
-            return;
           }
-          this.$refs.map.clearMarkers();
         });
       }
     },
-    findFirstFieldLayer(data) {
-      for (const item of data) {
-        if (item.codeType === "field") {
-          return item;
-        }
-        if (item.children && item.children.length > 0) {
-          const childResult = this.findFirstFieldLayer(item.children);
-          if (childResult) {
-            return childResult;
-          }
-        }
-      }
-      return null;
-    },
     featureClick(data) {
       const val = data.val;
       if (val === "-1" || val === -1 || val === 10) {
         this.$message.error("当前风场未完成分析");
       } else if (val === "0" || val === 0) {
-        this.$message({
-          message: "当前风场正在分析中",
-          type: "warning",
-        });
+        this.$message({ message: "当前风场正在分析中", type: "warning" });
       } else if (val === "1" || val === 1 || val === "30" || val === 30) {
         this.$router.push({
           path: "cockpitManage/electronic-map",
@@ -235,84 +220,13 @@ export default {
         });
       }
     },
-    handleSwitchChange() {
-      this.ShowRi = !this.ShowRi;
+    handleSwitchChange(val) {
+      this.ShowRi = val;
     },
   },
 };
 </script>
 
-<style lang="scss" scoped>
-.map-ditu {
-  //  min-width: 86.9vw;
-  //  max-width: 94.9vw;
-  height: calc(100vh - 60px);
-  // height: 100vh;
-  position: relative;
-}
-
-.statistics {
-  position: absolute;
-  top: 20px; /* 距离页面顶部20px */
-  left: 60px;
-  display: grid;
-  grid-template-rows: auto auto; /* 为 ledata 和 task 分配行 */
-  grid-gap: 20px; /* 在 ledata 和 task 之间设置间距 */
-
-  .ledata {
-    font-size: 14px;
-    width: 300px;
-    background: rgba(0, 128, 128, 0.8);
-    padding: 10px;
-    color: #fff;
-    border-radius: 5px;
-    p {
-      line-height: 24px;
-
-      .SpText {
-        display: inline-block;
-        width: 120px;
-        text-align: right;
-      }
-
-      .red,
-      .yellow,
-      .green {
-        display: inline-block;
-        width: 30px;
-        height: 12px;
-        margin-right: 5px;
-      }
-
-      .red {
-        background-color: #e16757;
-      }
-
-      .yellow {
-        background-color: #eecb5f;
-      }
-
-      .green {
-        background-color: #7ecf51;
-      }
-    }
-  }
-
-  .task {
-    // background: #f4f4f4;
-    // padding: 10px;
-    color: #333;
-    border-radius: 5px;
-  }
-}
-
-.ridata {
-  position: absolute;
-  top: 20px;
-  right: 20px;
-}
-
-::v-deep .el-switch__label.is-active {
-  color: #fff;
-}
+<style lang="scss">
+@import "./cockpit-dashboard.scss";
 </style>

+ 1482 - 0
src/views/admin/cockpitManage/cockpit-dashboard.scss

@@ -0,0 +1,1482 @@
+/* 驾驶舱:布局 + 主题(全局,勿 scoped,以便作用于子组件) */
+.cockpit-dashboard-page {
+  --cockpit-th-height: 32px;
+  --cockpit-tr-height: 40px;
+  --cockpit-td-padding-x: 8px;
+  --cockpit-table-stripe-odd: #1a2a4f;
+  --cockpit-table-stripe-even: #0f1f3d;
+  --cockpit-table-visible-rows: 4;
+  --cockpit-table-body-h: calc(
+    var(--cockpit-tr-height) * var(--cockpit-table-visible-rows)
+  );
+  --cockpit-table-block-h: calc(
+    var(--cockpit-th-height) + var(--cockpit-table-body-h)
+  );
+  /* 健康 Tab:环图 + 表格标题 + 4 行表,避免固定过高留白 */
+  --cockpit-health-chart-h: 150px;
+  --cockpit-health-abnormal-kpi-h: 76px;
+  --cockpit-health-abnormal-pane-h: calc(
+    32px + var(--cockpit-health-abnormal-kpi-h) + 12px + 34px +
+      var(--cockpit-table-block-h)
+  );
+  --cockpit-health-pane-h: max(
+    calc(
+      32px + var(--cockpit-health-chart-h) + 8px + 30px +
+        var(--cockpit-table-block-h)
+    ),
+    var(--cockpit-health-abnormal-pane-h)
+  );
+  /* 入库 Tab:副标题 + 表头 + 4 行表(缩小底部冗余预留) */
+  --cockpit-inbound-pane-h: calc(32px + var(--cockpit-table-block-h));
+
+  position: relative;
+  width: 100%;
+  height: calc(100vh - 60px);
+  overflow: hidden;
+  background: var(--cockpit-page-bg, #f0f2f5);
+  box-sizing: border-box;
+
+  *,
+  *::before,
+  *::after {
+    box-sizing: border-box;
+  }
+
+  .cockpit-dashboard__map {
+    position: absolute;
+    inset: 0;
+    z-index: 0;
+  }
+
+  .cockpit-dashboard__overlay {
+    position: relative;
+    z-index: 2;
+    display: flex;
+    align-items: flex-start;
+    justify-content: space-between;
+    height: 100%;
+    padding: 10px 12px;
+    gap: 10px;
+    pointer-events: none;
+  }
+
+  .cockpit-dashboard__left,
+  .cockpit-dashboard__right {
+    flex: 0 0 308px;
+    width: 308px;
+    max-height: 100%;
+    overflow-x: hidden;
+    overflow-y: auto;
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+    pointer-events: auto;
+    scrollbar-width: thin;
+    scrollbar-color: rgba(0, 140, 200, 0.45) transparent;
+  }
+
+  .cockpit-dashboard__left::-webkit-scrollbar,
+  .cockpit-dashboard__right::-webkit-scrollbar {
+    width: 4px;
+  }
+
+  .cockpit-dashboard__left::-webkit-scrollbar-thumb,
+  .cockpit-dashboard__right::-webkit-scrollbar-thumb {
+    border-radius: 4px;
+    background: rgba(0, 150, 220, 0.45);
+  }
+
+  .cockpit-dashboard__center-spacer {
+    flex: 1 1 auto;
+    min-width: 0;
+    pointer-events: none;
+  }
+
+  /* —— 统一面板容器 —— */
+  .cockpit-panel {
+    flex-shrink: 0;
+    border-radius: var(--cockpit-panel-radius, 8px);
+    padding: 10px 12px 12px;
+    background: var(--cockpit-panel-bg, #ffffff);
+    border: 1px solid var(--cockpit-panel-border, #ebeef5);
+    color: var(--cockpit-panel-text, #303133);
+    box-shadow: var(--cockpit-panel-shadow, 0 2px 8px rgba(0, 0, 0, 0.06));
+
+    &::after {
+      display: none !important;
+      content: none !important;
+    }
+  }
+
+  .cockpit-panel--compact {
+    padding: 10px 12px;
+  }
+
+  /* 面板内标题(含子组件 h3) */
+  .cockpit-panel__title,
+  .cockpit-panel h3,
+  .cockpit-panel .backbone h3,
+  .cockpit-panel .BOXYI > h3,
+  .cockpit-panel .centeredh3 {
+    margin: 0 0 10px;
+    padding: 0 0 8px;
+    font-size: 14px;
+    font-weight: 600;
+    line-height: 1.4;
+    color: var(--cockpit-panel-title, #303133);
+    border-bottom: 1px solid var(--cockpit-panel-divider, #ebeef5);
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    width: 100%;
+  }
+
+  .cockpit-panel .centeredh3 span {
+    font-size: 12px;
+    font-weight: 400;
+    color: var(--cockpit-accent, var(--primary-color, #82780c));
+  }
+
+  .cockpit-panel--stats {
+    .cockpit-stats__row {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      font-size: 13px;
+      line-height: 28px;
+      color: var(--cockpit-panel-text, #303133);
+
+      .SpText {
+        color: var(--cockpit-panel-muted, #909399);
+      }
+    }
+
+    .cockpit-stats__switch {
+      margin-top: 6px;
+      display: flex;
+      justify-content: flex-end;
+    }
+
+    .el-select,
+    .el-input__inner {
+      width: 100%;
+    }
+  }
+
+  /* 子组件根:透明,由面板承载外观 */
+  .cockpit-panel .backbone,
+  .cockpit-panel .BOXYI,
+  .cockpit-panel .BOX {
+    width: 100% !important;
+    background: transparent !important;
+    padding: 0 !important;
+    margin: 0 !important;
+    border: none !important;
+    border-radius: 0 !important;
+    color: inherit !important;
+    box-shadow: none !important;
+  }
+
+  .cockpit-panel--data-tabs .cockpit-inbound-wrap {
+    margin-top: 12px;
+    padding-top: 12px;
+    border-top: 1px solid var(--cockpit-panel-divider, #ebeef5);
+  }
+
+  /* 表头条(与 el-table thead 同高) */
+  .cockpit-panel .titleJQ,
+  .cockpit-panel .titleYC,
+  .cockpit-panel .cockpit-inbound-table-head {
+    margin: 0 0 6px;
+    padding: 0 8px;
+    min-height: var(--cockpit-th-height);
+    height: var(--cockpit-th-height);
+    line-height: var(--cockpit-th-height);
+    font-size: 12px;
+    border-radius: 2px;
+    background: var(--cockpit-table-header-bg, #f5f7fa) !important;
+    color: var(--cockpit-panel-muted, #909399) !important;
+    box-sizing: border-box;
+  }
+
+  .cockpit-panel .cockpit-inbound-table-head {
+    line-height: normal;
+    display: grid;
+    align-items: center;
+  }
+
+  /* el-table 表头:含 has-gutter 时高度一致 */
+  .cockpit-panel .el-table thead th.el-table__cell,
+  .cockpit-panel .el-table thead.has-gutter th.el-table__cell,
+  .cockpit-panel .el-table thead th.gutter,
+  .cockpit-panel .el-table thead.has-gutter th.gutter {
+    height: var(--cockpit-th-height) !important;
+    padding: 0 4px !important;
+    box-sizing: border-box;
+  }
+
+  .cockpit-panel .el-table thead.has-gutter tr {
+    height: var(--cockpit-th-height) !important;
+  }
+
+  .cockpit-panel .el-table thead th.gutter,
+  .cockpit-panel .el-table thead.has-gutter th.gutter {
+    display: table-cell !important;
+    width: 6px !important;
+    padding: 0 !important;
+    vertical-align: middle;
+  }
+
+  .cockpit-panel .el-table thead th .cell {
+    line-height: var(--cockpit-th-height);
+    height: var(--cockpit-th-height);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 0 4px;
+    box-sizing: border-box;
+  }
+
+  .cockpit-panel .el-table thead th.is-left .cell,
+  .cockpit-panel .el-table thead th.cockpit-health-col-name .cell {
+    justify-content: flex-start;
+  }
+
+  /* 表体:固定 4 行高度,两表一致 */
+  .cockpit-panel .cockpit-table-block .el-table__body-wrapper {
+    max-height: var(--cockpit-table-body-h) !important;
+    min-height: var(--cockpit-table-body-h) !important;
+    height: var(--cockpit-table-body-h) !important;
+    overflow-y: auto !important;
+  }
+
+  /* 自动滚动列表:隐藏滚动条,保留 programmatic scroll */
+  .cockpit-panel .cockpit-table-block .el-table__body-wrapper,
+  .cockpit-panel #Tabtab .el-table__body-wrapper,
+  .cockpit-panel .cockpit-unified-table .el-table__body-wrapper {
+    scrollbar-width: none !important;
+    -ms-overflow-style: none !important;
+  }
+
+  .cockpit-panel
+    .cockpit-table-block
+    .el-table__body-wrapper::-webkit-scrollbar,
+  .cockpit-panel #Tabtab .el-table__body-wrapper::-webkit-scrollbar,
+  .cockpit-panel
+    .cockpit-unified-table
+    .el-table__body-wrapper::-webkit-scrollbar {
+    width: 0 !important;
+    height: 0 !important;
+    display: none !important;
+  }
+
+  .cockpit-panel .cockpit-table-block {
+    min-height: var(--cockpit-table-block-h);
+  }
+
+  .cockpit-panel .cockpit-table-block > .titleYC,
+  .cockpit-panel .cockpit-table-block > .cockpit-inbound-table-head {
+    margin-bottom: 0 !important;
+  }
+
+  .cockpit-panel .cockpit-table-block .cockpit-tabroll-table {
+    flex: 1;
+    min-height: 0;
+  }
+
+  .cockpit-panel .cockpit-table-block .el-table__body tr {
+    height: var(--cockpit-tr-height) !important;
+  }
+
+  .cockpit-panel .cockpit-table-block .el-table__body td.el-table__cell {
+    height: var(--cockpit-tr-height) !important;
+    padding: 0 !important;
+    box-sizing: border-box;
+    border-bottom: none !important;
+  }
+
+  .cockpit-inbound-table-wrap.cockpit-table-block {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .cockpit-panel .cockpit-table-block .el-table__body td.el-table__cell .cell {
+    line-height: var(--cockpit-tr-height);
+    height: var(--cockpit-tr-height);
+    display: flex;
+    align-items: center;
+    padding: 0 var(--cockpit-td-padding-x) !important;
+    box-sizing: border-box;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  /* 滚动列表表格 */
+  .cockpit-panel #Tabtab.el-table,
+  .cockpit-panel #Tabtab .el-table__cell {
+    background: transparent !important;
+    color: var(--cockpit-panel-text, #606266) !important;
+    font-size: 12px;
+    border-color: var(--cockpit-panel-divider, #ebeef5) !important;
+  }
+
+  .cockpit-panel #Tabtab th.el-table__cell {
+    background: var(--cockpit-table-header-bg, #f5f7fa) !important;
+    color: var(--cockpit-panel-muted, #909399) !important;
+  }
+
+  .cockpit-panel #Tabtab .el-table__row:nth-child(2n) td {
+    background: var(--cockpit-table-stripe, #fafafa) !important;
+  }
+
+  .cockpit-panel .cockpit-analysis-chart {
+    width: 100%;
+    height: 160px;
+  }
+
+  .cockpit-panel--analysis-history .cockpit-analysis-chart {
+    height: 132px;
+    margin-top: 4px;
+  }
+
+  .cockpit-panel--analysis-history .cockpit-history-block {
+    margin-top: 10px;
+    padding-top: 10px;
+    border-top: 1px solid var(--cockpit-panel-divider, #ebeef5);
+  }
+
+  .cockpit-panel--analysis-history .cockpit-section-title {
+    margin-bottom: 8px;
+  }
+
+  .cockpit-scroll-table {
+    border-radius: 2px;
+    overflow: hidden;
+  }
+
+  /* 统一列表表(历史 / 健康 / 入库 / 异常) */
+  .cockpit-unified-table {
+    background: transparent;
+  }
+
+  .cockpit-unified-table-head {
+    display: grid !important;
+    align-items: center;
+    margin: 0 !important;
+    padding: 0 var(--cockpit-td-padding-x) !important;
+    min-height: var(--cockpit-th-height);
+    height: var(--cockpit-th-height);
+    line-height: var(--cockpit-th-height);
+    font-size: 12px;
+    font-weight: 500;
+    border: none !important;
+    border-radius: 0;
+    box-sizing: border-box;
+
+    span {
+      margin: 0 !important;
+      padding: 0;
+      text-align: center;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+
+    span:first-child {
+      text-align: left !important;
+    }
+
+    &--history {
+      grid-template-columns: 28% 38% 34%;
+    }
+
+    &--health {
+      grid-template-columns: 38% 15% 15% 15% 17%;
+    }
+
+    &--inbound-import {
+      grid-template-columns: 42% 58%;
+    }
+
+    &--inbound-two,
+    &--abnormal-two {
+      grid-template-columns: 50% 50%;
+    }
+  }
+
+  .cockpit-unified-table-head--inbound-import .cockpit-inbound-col-data {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 8px;
+    padding-right: 0;
+  }
+
+  .cockpit-unified-table .cockpit-tabroll-table,
+  .cockpit-unified-table #Tabtab {
+    width: 100% !important;
+  }
+
+  .cockpit-unified-table #Tabtab.el-table,
+  .cockpit-unified-table #Tabtab::before {
+    background: transparent !important;
+  }
+
+  .cockpit-unified-table #Tabtab .el-table__body,
+  .cockpit-unified-table #Tabtab .el-table__body-wrapper table {
+    width: 100% !important;
+    table-layout: fixed !important;
+  }
+
+  .cockpit-unified-table #Tabtab .el-table__body td.el-table__cell {
+    border: none !important;
+    color: #ffffff;
+    vertical-align: middle;
+  }
+
+  .cockpit-unified-table
+    #Tabtab
+    .el-table__body
+    tr:nth-child(odd)
+    td.el-table__cell {
+    background: var(--cockpit-table-stripe-odd) !important;
+    color: #ffffff !important;
+  }
+
+  .cockpit-unified-table
+    #Tabtab
+    .el-table__body
+    tr:nth-child(even)
+    td.el-table__cell {
+    background: var(--cockpit-table-stripe-even) !important;
+    color: #ffffff !important;
+  }
+
+  .cockpit-unified-table #Tabtab .el-table__body tr:hover > td.el-table__cell {
+    background: #182544 !important; /* 稍微加深的蓝色 */
+    color: rgba(255, 255, 255, 0.9) !important;
+  }
+
+  .cockpit-unified-table #Tabtab .el-table__body td.el-table__cell .cell {
+    display: flex !important;
+    align-items: center !important;
+    width: 100%;
+    box-sizing: border-box;
+  }
+
+  /* 表体除第一列外居中 */
+  .cockpit-unified-table
+    #Tabtab
+    .el-table__body
+    tr
+    td.el-table__cell:not(:first-child)
+    .cell {
+    justify-content: center !important;
+    text-align: center !important;
+  }
+
+  .cockpit-unified-table
+    #Tabtab
+    .el-table__body
+    tr
+    td.el-table__cell:nth-child(n + 2)
+    .cell {
+    justify-content: center !important;
+    text-align: center !important;
+  }
+
+  /* 第一列数据强制左对齐 */
+  .cockpit-unified-table #Tabtab .el-table__body td.el-table__cell:first-child .cell,
+  .cockpit-unified-table #Tabtab .el-table__body tr td:first-child .cell {
+    justify-content: flex-start !important;
+    text-align: left !important;
+  }
+
+  /* 表头与表体列宽对齐 */
+  .cockpit-unified-table--history #Tabtab colgroup col:nth-child(1) {
+    width: 28% !important;
+  }
+
+  .cockpit-unified-table--history #Tabtab colgroup col:nth-child(2) {
+    width: 38% !important;
+  }
+
+  .cockpit-unified-table--history #Tabtab colgroup col:nth-child(3) {
+    width: 34% !important;
+  }
+
+  .cockpit-unified-table--health #Tabtab colgroup col:nth-child(1) {
+    width: 38% !important;
+  }
+
+  .cockpit-unified-table--health #Tabtab colgroup col:nth-child(2),
+  .cockpit-unified-table--health #Tabtab colgroup col:nth-child(3),
+  .cockpit-unified-table--health #Tabtab colgroup col:nth-child(4) {
+    width: 15% !important;
+  }
+
+  .cockpit-unified-table--health #Tabtab colgroup col:nth-child(5) {
+    width: 17% !important;
+  }
+
+  .cockpit-unified-table--inbound-import #Tabtab colgroup col:nth-child(1) {
+    width: 42% !important;
+  }
+
+  .cockpit-unified-table--inbound-import #Tabtab colgroup col:nth-child(2) {
+    width: 58% !important;
+  }
+
+  .cockpit-unified-table--inbound-two #Tabtab colgroup col:nth-child(1),
+  .cockpit-unified-table--abnormal-two #Tabtab colgroup col:nth-child(1) {
+    width: 50% !important;
+  }
+
+  .cockpit-unified-table--inbound-two #Tabtab colgroup col:nth-child(2),
+  .cockpit-unified-table--abnormal-two #Tabtab colgroup col:nth-child(2) {
+    width: 50% !important;
+  }
+
+  .cockpit-section-title {
+    margin: 0 0 8px;
+    padding: 0;
+    border-bottom: none;
+    font-size: 14px;
+    font-weight: 600;
+    line-height: 1.4;
+  }
+
+  .cockpit-panel--stats .cockpit-stats__row {
+    line-height: 26px;
+  }
+
+  .cockpit-panel--stats .cockpit-stats__switch {
+    margin-top: 4px;
+  }
+
+  /* 任务统计数字块 */
+  .cockpit-panel .BOXsan {
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+    gap: 8px;
+  }
+
+  .cockpit-panel .YC01,
+  .cockpit-panel .YC02,
+  .cockpit-panel .YC03 {
+    flex: 1;
+    min-width: 0;
+  }
+
+  /* Tab 统一样式 */
+  .cockpit-panel .el-tabs__header {
+    margin: 0 0 10px;
+  }
+
+  .cockpit-panel .el-tabs__nav-wrap::after {
+    background-color: var(--cockpit-panel-divider, #ebeef5);
+  }
+
+  .cockpit-panel .el-tabs__item {
+    height: 32px;
+    line-height: 32px;
+    font-size: 13px;
+    color: var(--cockpit-panel-muted, #909399);
+    padding: 0 14px;
+  }
+
+  .cockpit-panel .el-tabs__item.is-active {
+    color: var(--cockpit-accent, var(--primary-color, #82780c));
+    font-weight: 600;
+  }
+
+  .cockpit-panel .el-tabs__active-bar {
+    background-color: var(--cockpit-accent, var(--primary-color, #82780c));
+  }
+
+  /* 表单控件 */
+  .cockpit-panel .el-input__inner,
+  .cockpit-panel .el-range-editor.el-input__inner,
+  .cockpit-panel .el-range-input {
+    background-color: var(--cockpit-input-bg, #ffffff) !important;
+    border-color: var(--cockpit-input-border, #dcdfe6) !important;
+    color: var(--cockpit-panel-text, #606266) !important;
+  }
+
+  .cockpit-panel .el-date-editor .el-range-separator {
+    color: var(--cockpit-panel-muted, #909399) !important;
+  }
+
+  .cockpit-panel .el-switch__label {
+    color: var(--cockpit-panel-muted, #909399);
+  }
+
+  .cockpit-panel .el-switch__label.is-active {
+    color: var(--cockpit-accent, var(--primary-color, #82780c)) !important;
+  }
+
+  .cockpit-panel--health .cockpit-health-table__head {
+    border-bottom: none;
+    margin-bottom: 8px;
+    padding-bottom: 0;
+  }
+
+  .cockpit-inbound-panel {
+    width: 100%;
+  }
+
+  .cockpit-inbound-body {
+    position: relative;
+    height: var(--cockpit-inbound-pane-h);
+    min-height: var(--cockpit-inbound-pane-h);
+    overflow: hidden;
+  }
+
+  .cockpit-inbound-pane {
+    position: absolute;
+    inset: 0;
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .cockpit-panel--inbound .cockpit-inbound-pane-header {
+    margin: 2px 0 6px;
+  }
+
+  .cockpit-panel--inbound .cockpit-inbound-subtitle {
+    margin: 0;
+  }
+
+  .cockpit-inbound-pane .cockpit-inbound-table-wrap {
+    flex: 1;
+    min-height: 0;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .cockpit-inbound-pane .cockpit-tabroll-table {
+    flex: 1;
+    min-height: 0;
+  }
+  .cockpit-inbound-pane-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    .cockpit-inbound-col-data {
+      font-size: 12px;
+      color: var(--cockpit-panel-muted, #909399);
+      cursor: pointer;
+      &:hover {
+        color: var(--cockpit-accent, #2979ff) !important;
+      }
+    }
+  }
+
+  .cockpit-inbound-subtitle {
+    margin: 8px 0 10px;
+    font-size: 14px;
+    font-weight: 600;
+    line-height: 1.3;
+    color: var(--cockpit-panel-title, #303133);
+  }
+
+  .cockpit-inbound-table-wrap {
+    border-radius: 6px;
+    overflow: hidden;
+  }
+
+  .cockpit-inbound-table-head {
+    display: grid !important;
+    align-items: center;
+    margin: 0 !important;
+    padding: 0 10px !important;
+    min-height: var(--cockpit-th-height);
+    height: var(--cockpit-th-height);
+    line-height: normal;
+    width: 100%;
+    box-sizing: border-box;
+
+    span {
+      margin: 0 !important;
+      font-size: 12px;
+    }
+
+    &--import {
+      grid-template-columns: 42% 58%;
+    }
+
+    .cockpit-inbound-col-name {
+      text-align: left;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+
+    .cockpit-inbound-col-data {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      gap: 8px;
+      padding-left: 6px;
+      box-sizing: border-box;
+
+      > span:first-child {
+        flex: 1;
+        text-align: right;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
+    }
+
+    &--two {
+      grid-template-columns: 42% 58%;
+    }
+
+    .cockpit-inbound-col-extra {
+      text-align: right;
+      padding-right: 4px;
+    }
+  }
+
+  .cockpit-inbound-more-link {
+    flex-shrink: 0;
+    color: var(--cockpit-accent, #2979ff) !important;
+    cursor: pointer;
+    text-align: right !important;
+  }
+
+  .cockpit-inbound-table-wrap .tabroll-wrap,
+  .cockpit-inbound-table-wrap #Tabtab,
+  .cockpit-inbound-table-wrap #Tabtab .el-table__body,
+  .cockpit-inbound-table-wrap #Tabtab .el-table__body-wrapper,
+  .cockpit-inbound-table-wrap #Tabtab table {
+    width: 100% !important;
+  }
+
+  .cockpit-inbound-table-wrap #Tabtab table {
+    table-layout: fixed !important;
+  }
+
+  .cockpit-inbound-table-wrap #Tabtab colgroup col:first-child {
+    width: 42% !important;
+  }
+
+  .cockpit-inbound-table-wrap #Tabtab colgroup col:last-child {
+    width: 58% !important;
+  }
+
+  .cockpit-panel--inbound .centeredh3 {
+    display: none;
+  }
+
+  .cockpit-health-table .el-table th.el-table__cell {
+    background: var(--cockpit-table-header-bg, #f5f7fa) !important;
+    color: var(--cockpit-panel-muted, #909399) !important;
+    font-size: 12px;
+  }
+
+  .cockpit-health-table .el-table td.el-table__cell {
+    background: transparent !important;
+    color: var(--cockpit-panel-text, #606266) !important;
+    font-size: 12px;
+  }
+}
+
+/* —— 科技蓝大屏(参考设计稿) —— */
+html[data-theme="tech-blue"] .cockpit-dashboard-page {
+  --cockpit-page-bg: #020a16;
+  --cockpit-panel-border: rgba(0, 90, 140, 0.72);
+  --cockpit-panel-divider: rgba(0, 120, 200, 0.22);
+  --cockpit-panel-text: #c8e8f8;
+  --cockpit-panel-title: #ffffff;
+  --cockpit-panel-muted: rgba(168, 210, 235, 0.72);
+  --cockpit-panel-radius: 8px;
+  --cockpit-table-header-bg: rgba(0, 36, 64, 0.92);
+  --cockpit-table-row-bg: rgba(0, 18, 36, 0.55);
+  --cockpit-table-row-bg-alt: rgba(0, 24, 46, 0.72);
+  --cockpit-input-bg: rgba(0, 16, 32, 0.92);
+  --cockpit-input-border: rgba(0, 140, 210, 0.45);
+  --cockpit-accent: #00c8ff;
+  --cockpit-status-ok: #7ecf51;
+  --cockpit-status-warn: #eecb5f;
+  --cockpit-status-danger: #e16757;
+  --cockpit-kpi-cyan: #5ce1ff;
+
+  background-color: #020a16;
+  background-image: radial-gradient(
+      ellipse 80% 55% at 50% 38%,
+      rgba(12, 40, 88, 0.45) 0%,
+      transparent 68%
+    ),
+    linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
+    linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px);
+  background-size: 100% 100%, 24px 24px, 24px 24px;
+
+  .cockpit-dashboard__left,
+  .cockpit-dashboard__right {
+    scrollbar-width: none;
+    -ms-overflow-style: none;
+
+    &::-webkit-scrollbar {
+      display: none;
+    }
+  }
+
+  .cockpit-panel {
+    position: relative;
+    overflow: hidden;
+    background: linear-gradient(
+      180deg,
+      rgba(6, 22, 45, 0.97) 0%,
+      rgba(4, 16, 32, 0.98) 52%,
+      rgba(2, 10, 22, 0.99) 100%
+    ) !important;
+    border: 1px solid var(--cockpit-panel-border) !important;
+    border-radius: var(--cockpit-panel-radius) !important;
+    box-shadow: inset 0 0 28px rgba(0, 130, 220, 0.1),
+      inset 0 1px 0 rgba(0, 210, 255, 0.12), 0 6px 28px rgba(0, 0, 0, 0.42) !important;
+    backdrop-filter: blur(12px);
+
+    &::before {
+      content: "";
+      position: absolute;
+      top: 0;
+      left: 6%;
+      right: 6%;
+      height: 1px;
+      background: linear-gradient(
+        90deg,
+        transparent,
+        rgba(0, 242, 255, 0.65),
+        transparent
+      );
+      pointer-events: none;
+      z-index: 1;
+    }
+  }
+
+  .cockpit-panel__title,
+  .cockpit-panel h3,
+  .cockpit-panel .backbone h3,
+  .cockpit-panel .BOXYI > h3,
+  .cockpit-panel .centeredh3 {
+    color: #ffffff;
+    font-size: 14px;
+    font-weight: 600;
+    letter-spacing: 0.02em;
+    border-bottom: 1px solid var(--cockpit-panel-divider);
+    padding: 0 0 8px;
+    margin-bottom: 10px;
+  }
+
+  .cockpit-panel .centeredh3 span {
+    color: #5ec8ff;
+  }
+
+  .cockpit-panel:not(.cockpit-panel--health):not(.cockpit-panel--inbound)
+    .el-tabs__item.is-active {
+    color: #00d4ff !important;
+  }
+
+  .cockpit-panel .el-tabs__active-bar {
+    background-color: #00aaff !important;
+  }
+
+  /* 统一列表表(科技蓝) */
+  .cockpit-unified-table-head {
+    background: rgba(0, 24, 44, 0.55) !important;
+    color: #ffffff !important;
+
+    span {
+      color: #ffffff !important;
+      font-weight: 400;
+    }
+  }
+
+  .cockpit-unified-table #Tabtab .el-table__empty-text {
+    color: rgba(200, 228, 245, 0.65) !important;
+  }
+
+  .cockpit-panel .cockpit-section-title {
+    color: #ffffff;
+    text-shadow: none;
+  }
+
+  /* 接入统计 */
+  .cockpit-panel--stats {
+    .cockpit-stats__row {
+      color: var(--cockpit-panel-text);
+      font-size: 13px;
+
+      .SpText {
+        color: var(--cockpit-panel-muted);
+      }
+
+      > span:last-child {
+        color: #fff;
+        font-weight: 500;
+      }
+    }
+
+    .cockpit-stats__legend {
+      display: flex;
+      gap: 5px;
+      margin: 8px 0 6px;
+      height: 5px;
+    }
+
+    .cockpit-stats__legend-seg {
+      flex: 1;
+      border-radius: 2px;
+      opacity: 0.92;
+
+      &--ok {
+        background: var(--cockpit-status-ok);
+        box-shadow: 0 0 8px rgba(126, 207, 81, 0.45);
+      }
+
+      &--warn {
+        background: var(--cockpit-status-warn);
+        box-shadow: 0 0 8px rgba(238, 203, 95, 0.4);
+      }
+
+      &--danger {
+        background: var(--cockpit-status-danger);
+        box-shadow: 0 0 8px rgba(225, 103, 87, 0.4);
+      }
+    }
+
+    .el-select .el-input__inner {
+      height: 34px;
+      line-height: 34px;
+      font-weight: 600;
+      color: #fff !important;
+      background: var(--cockpit-input-bg) !important;
+      border-color: var(--cockpit-input-border) !important;
+    }
+
+    .el-switch__label {
+      color: var(--cockpit-panel-muted);
+      font-size: 12px;
+
+      &.is-active {
+        color: var(--cockpit-kpi-cyan) !important;
+      }
+    }
+
+    .el-switch__core {
+      background: rgba(0, 35, 60, 0.95);
+      border-color: rgba(0, 150, 220, 0.5);
+    }
+
+    .el-switch.is-checked .el-switch__core {
+      border-color: var(--cockpit-accent);
+      background-color: rgba(0, 120, 200, 0.85);
+    }
+  }
+
+  /* 分析 + 历史 */
+  .cockpit-panel--analysis-history {
+    .cockpit-analysis-chart {
+      height: 128px;
+    }
+
+    .cockpit-history-block {
+      border-top-color: var(--cockpit-panel-divider);
+    }
+
+    .el-date-editor.el-input__inner {
+      width: 100%;
+      max-width: none;
+      height: 30px;
+      font-size: 11px;
+      color: var(--cockpit-panel-text) !important;
+      background: var(--cockpit-input-bg) !important;
+      border-color: var(--cockpit-input-border) !important;
+    }
+
+    .el-range-separator,
+    .el-range-input {
+      color: var(--cockpit-panel-muted) !important;
+    }
+
+    .el-input__icon {
+      color: var(--cockpit-kpi-cyan) !important;
+    }
+  }
+
+  /* 任务统计 KPI(L 角描边) */
+  .cockpit-panel .cockpit-task-kpi {
+    gap: 8px;
+    margin-top: 2px;
+  }
+
+  .cockpit-panel .cockpit-task-kpi .YC02,
+  .cockpit-panel .cockpit-task-kpi .YC03 {
+    background-image: none !important;
+    width: auto !important;
+    flex: 1;
+    min-width: 0;
+    min-height: 76px;
+    margin-top: 0 !important;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    gap: 6px;
+    cursor: pointer;
+    position: relative;
+    background: rgba(0, 22, 42, 0.65) !important;
+    border: none !important;
+    border-radius: 2px;
+    box-shadow: inset 0 0 0 1px rgba(0, 100, 170, 0.35);
+    transition: box-shadow 0.2s;
+
+    &::before,
+    &::after {
+      content: "";
+      position: absolute;
+      width: 10px;
+      height: 10px;
+      border-color: rgba(0, 210, 255, 0.8);
+      border-style: solid;
+      pointer-events: none;
+    }
+
+    &::before {
+      top: 0;
+      left: 0;
+      border-width: 1px 0 0 1px;
+    }
+
+    &::after {
+      bottom: 0;
+      right: 0;
+      border-width: 0 1px 1px 0;
+    }
+
+    &:hover {
+      box-shadow: inset 0 0 0 1px rgba(0, 200, 255, 0.5),
+        0 0 14px rgba(0, 160, 255, 0.18);
+    }
+
+    strong {
+      font-size: 26px;
+      line-height: 1;
+      color: var(--cockpit-kpi-cyan);
+      text-shadow: 0 0 12px rgba(0, 200, 255, 0.35);
+    }
+
+    span {
+      font-size: 12px;
+      color: var(--cockpit-panel-muted);
+    }
+
+    .texyellow {
+      color: var(--cockpit-status-warn) !important;
+      text-shadow: 0 0 10px rgba(238, 203, 95, 0.35);
+    }
+
+    .textred {
+      color: var(--cockpit-status-danger) !important;
+      text-shadow: 0 0 10px rgba(225, 103, 87, 0.35);
+    }
+  }
+
+  .cockpit-panel .YC01 strong,
+  .cockpit-panel .YC02 strong {
+    color: #4bffff;
+  }
+
+  .cockpit-panel .YC02 span,
+  .cockpit-panel .YC01 span {
+    color: rgba(183, 223, 245, 0.85);
+  }
+
+  .cockpit-panel--health,
+  .cockpit-panel--inbound {
+    overflow: visible;
+  }
+
+  /* 自定义科技蓝 Tab(CockpitTechTabs) */
+  .cockpit-tech-tabs {
+    margin-bottom: 10px;
+  }
+
+  .cockpit-tech-tabs__track {
+    display: flex;
+    align-items: flex-end;
+    gap: 0;
+    min-height: 42px;
+    padding: 5px 10px 0;
+    background: rgba(0, 4, 12, 0.98);
+    border: 1px solid rgba(0, 95, 165, 0.45);
+    border-radius: 22px;
+    box-shadow: inset 0 1px 0 rgba(0, 70, 120, 0.2),
+      inset 0 -12px 20px rgba(0, 0, 0, 0.4);
+    position: relative;
+    overflow: hidden;
+  }
+
+  .cockpit-tech-tabs__list {
+    flex: 1;
+    display: flex;
+    align-items: flex-end;
+    min-width: 0;
+    min-height: 36px;
+    position: relative;
+    z-index: 1;
+  }
+
+  .cockpit-tech-tabs__item {
+    flex: 1;
+    min-width: 72px;
+    height: 34px;
+    margin: 0;
+    padding: 0 4px;
+    border: none;
+    background: transparent;
+    cursor: pointer;
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    outline: none;
+
+    .cockpit-tech-tabs__label {
+      position: relative;
+      z-index: 3;
+      font-size: 13px;
+      line-height: 1;
+      color: rgba(170, 205, 230, 0.72);
+      transition: color 0.2s;
+      pointer-events: none;
+      user-select: none;
+    }
+
+    &:hover:not(.is-active) .cockpit-tech-tabs__label {
+      color: rgba(210, 235, 255, 0.95);
+    }
+
+    &.is-active {
+      flex: 1.12;
+      height: 36px;
+      margin-bottom: -1px;
+
+      .cockpit-tech-tabs__label {
+        color: #ffffff;
+        font-weight: 600;
+        text-shadow: 0 0 10px rgba(0, 220, 255, 0.35);
+      }
+    }
+  }
+
+  .cockpit-tech-tabs__shape {
+    position: absolute;
+    left: 4%;
+    right: 4%;
+    bottom: 0;
+    top: 0;
+    width: 92%;
+    height: 100%;
+    z-index: 1;
+    pointer-events: none;
+    filter: drop-shadow(0 2px 10px rgba(0, 160, 255, 0.55));
+  }
+
+  .cockpit-tech-tabs__baseline {
+    position: absolute;
+    left: 10px;
+    right: 10px;
+    bottom: 0;
+    height: 2px;
+    z-index: 0;
+    background: linear-gradient(
+      90deg,
+      #00f0ff 0%,
+      rgba(0, 190, 255, 0.85) 28%,
+      rgba(0, 140, 220, 0.5) 70%,
+      rgba(0, 90, 160, 0.25) 100%
+    );
+    box-shadow: 0 0 12px rgba(0, 230, 255, 0.75), 0 0 4px rgba(0, 255, 255, 0.5);
+  }
+
+  .cockpit-tech-tabs__extra {
+    flex-shrink: 0;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 0 4px 8px 8px;
+    position: relative;
+    z-index: 2;
+  }
+
+  .cockpit-tech-tabs__bolt {
+    display: none !important;
+  }
+
+  .cockpit-tech-tabs__chevron {
+    font-size: 14px;
+    color: #5ec8ff;
+    cursor: pointer;
+    text-shadow: 0 0 8px rgba(0, 200, 255, 0.6);
+
+    &:hover {
+      color: #9ef8ff;
+    }
+  }
+
+  /* 右侧统一 Tab:贴顶左对齐(健康 / 入库,图二) */
+  .cockpit-panel--health .cockpit-tech-tabs,
+  .cockpit-panel--inbound .cockpit-tech-tabs,
+  .cockpit-tech-tabs--panel {
+    margin-bottom: 6px;
+  }
+
+  .cockpit-panel--health .cockpit-tech-tabs--panel,
+  .cockpit-panel--inbound .cockpit-tech-tabs--panel,
+  .cockpit-tech-tabs--panel {
+    .cockpit-tech-tabs__track {
+      position: relative;
+      min-height: 36px;
+      padding: 0 28px 0 0;
+      background: transparent;
+      border: none;
+      border-radius: 0;
+      box-shadow: none;
+      overflow: visible;
+    }
+
+    .cockpit-tech-tabs__list {
+      flex: 1;
+      justify-content: flex-start;
+      gap: 6px;
+      min-height: 34px;
+    }
+
+    .cockpit-tech-tabs__item {
+      flex: 0 0 auto;
+      min-width: 64px;
+      height: 32px;
+      padding: 0 20px;
+
+      &.is-active {
+        flex: 0 0 auto;
+        height: 34px;
+        margin-bottom: 0;
+
+        &::after {
+          content: "";
+          position: absolute;
+          left: 8%;
+          right: 8%;
+          bottom: 0;
+          height: 2px;
+          background: #00f0ff;
+          box-shadow: 0 0 10px rgba(0, 240, 255, 0.85);
+          z-index: 4;
+          pointer-events: none;
+        }
+      }
+
+      &:not(.is-active) .cockpit-tech-tabs__label {
+        color: rgba(183, 223, 245, 0.5);
+        font-weight: 400;
+      }
+    }
+
+    .cockpit-tech-tabs__shape {
+      left: 0;
+      right: 0;
+      width: 100%;
+      filter: drop-shadow(0 2px 12px rgba(0, 170, 255, 0.5));
+    }
+
+    .cockpit-tech-tabs__baseline {
+      display: none;
+    }
+
+    .cockpit-tech-tabs__extra {
+      flex-shrink: 0;
+      margin-left: auto;
+      padding: 0 0 8px 8px;
+      position: static;
+      transform: none;
+    }
+  }
+
+  /* 入库表格:图二深色圆角容器 + 轻斑马纹 */
+  .cockpit-panel--inbound .cockpit-inbound-table-wrap {
+    border: 1px solid rgba(0, 110, 190, 0.4);
+    background: rgba(0, 16, 32, 0.75);
+    box-shadow: inset 0 0 16px rgba(0, 40, 80, 0.25);
+  }
+
+  .cockpit-panel--inbound .cockpit-inbound-more-link {
+    color: rgba(126, 212, 255, 0.95) !important;
+    cursor: pointer;
+  }
+
+  .cockpit-panel--health .cockpit-health-panel__tab-body {
+    height: var(--cockpit-health-pane-h);
+    min-height: var(--cockpit-health-pane-h);
+  }
+
+  .cockpit-panel--health .cockpit-health-panel__pane--abnormal {
+    justify-content: flex-start;
+  }
+
+  .cockpit-panel--health
+    .cockpit-health-panel__pane--abnormal
+    .cockpit-abnormal-panel {
+    height: auto;
+    flex: 0 0 auto;
+  }
+
+  .cockpit-panel--health .cockpit-abnormal-panel__subtitle,
+  .cockpit-panel--health .cockpit-abnormal-table__section-head {
+    color: #ffffff;
+    text-shadow: 0 0 8px rgba(0, 170, 255, 0.2);
+  }
+
+  .cockpit-panel--health .cockpit-abnormal-table__section-head {
+    background: rgba(0, 52, 96, 0.85);
+    border: 1px solid rgba(0, 120, 200, 0.35);
+  }
+
+  .cockpit-panel--health .cockpit-abnormal-kpi__card {
+    --cockpit-abnormal-kpi-bg: rgba(0, 28, 52, 0.75);
+    --cockpit-abnormal-kpi-corner: rgba(0, 210, 255, 0.85);
+  }
+
+  .cockpit-panel--health .cockpit-abnormal-kpi__value {
+    color: #5ce1ff;
+    text-shadow: 0 0 12px rgba(0, 200, 255, 0.35);
+  }
+
+  .cockpit-panel--health .cockpit-abnormal-kpi__label {
+    color: rgba(190, 215, 235, 0.88);
+  }
+
+  .cockpit-health-panel__subtitle,
+  .cockpit-inbound-subtitle {
+    color: #ffffff;
+    font-size: 14px;
+    text-shadow: 0 0 8px rgba(0, 170, 255, 0.2);
+  }
+
+  .cockpit-panel--health,
+  .cockpit-panel--inbound {
+    padding-top: 8px;
+  }
+
+  .cockpit-panel--health .cockpit-health-panel__subtitle {
+    margin-top: 4px;
+    padding-right: 4px;
+    border-bottom: none;
+  }
+
+  .cockpit-panel--inbound .cockpit-inbound-subtitle {
+    margin: 6px 0 8px;
+    border-bottom: none;
+  }
+}
+
+/* —— 非科技蓝:浅色统一 —— */
+html:not([data-theme="tech-blue"]) .cockpit-dashboard-page {
+  --cockpit-page-bg: #eef1f6;
+  --cockpit-panel-bg: #ffffff;
+  --cockpit-panel-border: #e4e7ed;
+  --cockpit-panel-divider: #ebeef5;
+  --cockpit-panel-text: #303133;
+  --cockpit-panel-title: #303133;
+  --cockpit-panel-muted: #909399;
+  --cockpit-panel-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
+  --cockpit-panel-radius: 8px;
+  --cockpit-table-header-bg: #f5f7fa;
+  --cockpit-table-stripe: #fafafa;
+  --cockpit-input-bg: #ffffff;
+  --cockpit-input-border: #dcdfe6;
+  --cockpit-accent: var(--primary-color, #82780c);
+
+  .cockpit-stats__legend {
+    display: none;
+  }
+
+  .cockpit-panel__title,
+  .cockpit-panel h3 {
+    border-bottom-color: #ebeef5;
+  }
+
+  .cockpit-tech-tabs__track {
+    background: #f0f2f5;
+    border: 1px solid #dcdfe6;
+    border-radius: 8px;
+    box-shadow: none;
+    padding: 4px;
+    min-height: 36px;
+  }
+
+  .cockpit-tech-tabs__baseline,
+  .cockpit-tech-tabs__shape {
+    display: none;
+  }
+
+  .cockpit-tech-tabs__bolt {
+    display: none !important;
+  }
+
+  .cockpit-tech-tabs__item {
+    height: 32px;
+    border-radius: 4px;
+
+    .cockpit-tech-tabs__label {
+      color: #909399;
+    }
+
+    &.is-active {
+      background: #ffffff;
+      box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
+
+      .cockpit-tech-tabs__label {
+        color: var(--primary-color, #82780c);
+        font-weight: 600;
+        text-shadow: none;
+      }
+    }
+  }
+
+  .cockpit-tech-tabs__chevron {
+    color: var(--primary-color, #82780c);
+    text-shadow: none;
+  }
+
+  .cockpit-panel .cockpit-task-kpi .YC02,
+  .cockpit-panel .cockpit-task-kpi .YC03 {
+    background-image: none !important;
+    width: auto !important;
+    flex: 1;
+    min-height: 64px;
+    margin-top: 0 !important;
+    border: 1px solid #e4e7ed;
+    border-radius: 6px;
+    background: #f8fafc !important;
+  }
+
+  .cockpit-unified-table-head {
+    background: var(--cockpit-table-header-bg) !important;
+    color: var(--cockpit-panel-title) !important;
+
+    span {
+      color: var(--cockpit-panel-muted) !important;
+    }
+  }
+
+  .cockpit-unified-table #Tabtab .el-table__body tr:hover > td.el-table__cell {
+    background: var(--cockpit-table-stripe) !important;
+  }
+}

+ 235 - 0
src/views/admin/cockpitManage/component/CockpitAbnormalPanel.vue

@@ -0,0 +1,235 @@
+<template>
+  <div class="cockpit-abnormal-panel">
+    <div class="cockpit-abnormal-panel__subtitle">异常设备概览</div>
+
+    <div class="cockpit-abnormal-kpi">
+      <div class="cockpit-abnormal-kpi__card">
+        <span class="cockpit-abnormal-kpi__corner is-tl" aria-hidden="true" />
+        <span class="cockpit-abnormal-kpi__corner is-tr" aria-hidden="true" />
+        <span class="cockpit-abnormal-kpi__corner is-bl" aria-hidden="true" />
+        <span class="cockpit-abnormal-kpi__corner is-br" aria-hidden="true" />
+        <strong class="cockpit-abnormal-kpi__value">{{ normalTotal }}</strong>
+        <span class="cockpit-abnormal-kpi__label">正常总数</span>
+      </div>
+      <div
+        class="cockpit-abnormal-kpi__card cockpit-abnormal-kpi__card--danger"
+      >
+        <span class="cockpit-abnormal-kpi__corner is-tl" aria-hidden="true" />
+        <span class="cockpit-abnormal-kpi__corner is-tr" aria-hidden="true" />
+        <span class="cockpit-abnormal-kpi__corner is-bl" aria-hidden="true" />
+        <span class="cockpit-abnormal-kpi__corner is-br" aria-hidden="true" />
+        <strong class="cockpit-abnormal-kpi__value">{{ abnormalTotal }}</strong>
+        <span class="cockpit-abnormal-kpi__label">异常总数</span>
+      </div>
+    </div>
+
+    <div class="cockpit-abnormal-table">
+      <div class="cockpit-abnormal-table__section-head">异常数据信息</div>
+      <div
+        class="cockpit-abnormal-table-wrap cockpit-table-block cockpit-unified-table cockpit-unified-table--abnormal-two"
+      >
+        <p
+          class="titleYC cockpit-unified-table-head cockpit-unified-table-head--abnormal-two"
+        >
+          <span class="cockpit-inbound-col-name">风场名称</span>
+          <span class="cockpit-inbound-col-extra">异常风机数</span>
+        </p>
+        <Tabroll
+          :table-data="tableData"
+          :shujuloading="loading"
+          :columns="tableColumns"
+          :column-widths="columnWidths"
+          :max-height="tableBodyHeight"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { queryStatistics } from "@/api/ledger.js";
+import Tabroll from "./tabroll.vue";
+import { COCKPIT_TABLE_BODY_HEIGHT } from "./cockpitTableMetrics";
+
+export default {
+  name: "CockpitAbnormalPanel",
+  components: { Tabroll },
+  props: {
+    maplistArr: { type: Object, default: () => ({}) },
+    defaultdata: { type: Object, default: () => ({}) },
+  },
+  data() {
+    return {
+      loading: false,
+      equipment: {},
+      tableData: [],
+      tableColumns: [{ prop: "fieldName" }, { prop: "errCount" }],
+      columnWidths: {
+        fieldName: "50%",
+        errCount: "50%",
+      },
+      tableBodyHeight: COCKPIT_TABLE_BODY_HEIGHT,
+    };
+  },
+  computed: {
+    abnormalTotal() {
+      const e = this.equipment;
+      return e.errDeviceCount ?? e.errCounrt ?? e.errCount ?? 0;
+    },
+    normalTotal() {
+      const e = this.equipment;
+      if (e.normalCount != null) return e.normalCount;
+      if (e.engineNormalCount != null) return e.engineNormalCount;
+      if (e.totalCount != null && this.abnormalTotal != null) {
+        return Math.max(0, Number(e.totalCount) - Number(this.abnormalTotal));
+      }
+      return 0;
+    },
+  },
+  watch: {
+    maplistArr: {
+      handler(val) {
+        if (val && Object.keys(val).length > 0) {
+          this.fetchStatistics();
+        }
+      },
+      immediate: true,
+      deep: true,
+    },
+  },
+  methods: {
+    fetchStatistics() {
+      if (!this.maplistArr?.codeNumber) return;
+      this.loading = true;
+      const end = new Date();
+      const start = new Date();
+      start.setMonth(start.getMonth() - 6);
+      queryStatistics({
+        codeNumber: this.maplistArr.codeNumber,
+        codeType: this.maplistArr.codeType,
+        startTime: this.$formatDate(start),
+        endTime: this.$formatDate(end),
+      })
+        .then((res) => {
+          const data = res.data || {};
+          this.equipment = data;
+          this.tableData = (data.errList || []).map((row) => ({
+            fieldName: row.fieldName ?? row.windFarmName ?? "—",
+            errCount:
+              row.errCount ??
+              row.abnormalEngineCount ??
+              row.engineErrCount ??
+              0,
+          }));
+        })
+        .finally(() => {
+          this.loading = false;
+        });
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.cockpit-abnormal-panel {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+}
+
+.cockpit-abnormal-panel__subtitle {
+  flex-shrink: 0;
+  margin: 4px 0 10px;
+  font-size: 14px;
+  font-weight: 600;
+  color: var(--cockpit-panel-title, #303133);
+}
+
+.cockpit-abnormal-kpi {
+  flex-shrink: 0;
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 10px;
+  margin-bottom: 12px;
+}
+
+.cockpit-abnormal-kpi__card {
+  position: relative;
+  min-height: 76px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 4px;
+  background: var(--cockpit-abnormal-kpi-bg, rgba(0, 36, 64, 0.45));
+}
+
+.cockpit-abnormal-kpi__corner {
+  position: absolute;
+  width: 10px;
+  height: 10px;
+  border-color: var(--cockpit-abnormal-kpi-corner, rgba(0, 200, 255, 0.75));
+  border-style: solid;
+  pointer-events: none;
+
+  &.is-tl {
+    top: 0;
+    left: 0;
+    border-width: 1px 0 0 1px;
+  }
+
+  &.is-tr {
+    top: 0;
+    right: 0;
+    border-width: 1px 1px 0 0;
+  }
+
+  &.is-bl {
+    bottom: 0;
+    left: 0;
+    border-width: 0 0 1px 1px;
+  }
+
+  &.is-br {
+    bottom: 0;
+    right: 0;
+    border-width: 0 1px 1px 0;
+  }
+}
+
+.cockpit-abnormal-kpi__value {
+  font-size: 28px;
+  font-weight: 600;
+  line-height: 1.1;
+  color: var(--cockpit-abnormal-kpi-value, #4bffff);
+}
+
+.cockpit-abnormal-kpi__card--danger .cockpit-abnormal-kpi__value {
+  color: #ff4d4f;
+}
+
+.cockpit-abnormal-kpi__label {
+  font-size: 12px;
+  color: var(--cockpit-panel-muted, #909399);
+}
+
+.cockpit-abnormal-table {
+  flex: 0 0 auto;
+  min-height: 0;
+}
+
+.cockpit-abnormal-table__section-head {
+  margin-bottom: 8px;
+  padding: 6px 10px;
+  font-size: 14px;
+  font-weight: 600;
+  color: var(--cockpit-panel-title, #303133);
+  background: var(--cockpit-abnormal-section-head-bg, rgba(0, 52, 96, 0.35));
+  border-radius: 2px;
+}
+
+.cockpit-abnormal-table-wrap {
+  border-radius: 4px;
+  overflow: hidden;
+}
+</style>

+ 207 - 0
src/views/admin/cockpitManage/component/CockpitHealthPanel.vue

@@ -0,0 +1,207 @@
+<template>
+  <div class="cockpit-health-panel">
+    <CockpitTechTabs
+      v-model="activeTab"
+      :tabs="healthTabs"
+      variant="panel"
+    />
+
+    <div class="cockpit-health-panel__tab-body">
+      <div
+        v-show="activeTab === 'healthy'"
+        class="cockpit-health-panel__pane cockpit-health-panel__pane--healthy"
+      >
+        <div class="cockpit-health-panel__subtitle">综合健康评分</div>
+        <div class="cockpit-health-panel__body">
+          <ul class="cockpit-health-panel__legend">
+            <li
+              v-for="item in legendItems"
+              :key="item.name"
+              class="cockpit-health-panel__legend-item"
+            >
+              <i :style="{ background: item.color }"></i>
+              <span>{{ item.name }}</span>
+            </li>
+          </ul>
+          <div ref="chartRef" class="cockpit-health-panel__chart"></div>
+        </div>
+        <CockpitHealthTable ref="healthTable" class="cockpit-health-panel__table" />
+      </div>
+
+      <div
+        v-show="activeTab === 'abnormal'"
+        class="cockpit-health-panel__pane cockpit-health-panel__pane--abnormal"
+      >
+        <CockpitAbnormalPanel
+          :maplistArr="maplistArr"
+          :defaultdata="defaultdata"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import * as echarts from "echarts";
+import { healthScoreDistribution } from "./cockpitHealthData";
+import CockpitTechTabs from "./CockpitTechTabs.vue";
+import CockpitHealthTable from "./CockpitHealthTable.vue";
+import CockpitAbnormalPanel from "./CockpitAbnormalPanel.vue";
+
+export default {
+  name: "CockpitHealthPanel",
+  components: { CockpitTechTabs, CockpitHealthTable, CockpitAbnormalPanel },
+  props: {
+    maplistArr: { type: Object, default: () => ({}) },
+    defaultdata: { type: Object, default: () => ({}) },
+  },
+  data() {
+    return {
+      activeTab: "healthy",
+      healthTabs: [
+        { label: "健康", value: "healthy" },
+        { label: "异常", value: "abnormal" },
+      ],
+      chart: null,
+      legendItems: [
+        { name: "优", color: "#7ECF51" },
+        { name: "良", color: "#2979ff" },
+        { name: "中", color: "#EECB5F" },
+        { name: "差", color: "#E16757" },
+      ],
+    };
+  },
+  mounted() {
+    this.$nextTick(() => this.initChart());
+    window.addEventListener("resize", this.handleResize);
+  },
+  watch: {
+    activeTab(val) {
+      if (val === "healthy") {
+        this.$nextTick(() => {
+          if (this.chart) {
+            this.chart.resize();
+          } else {
+            this.initChart();
+          }
+        });
+      }
+    },
+  },
+  beforeDestroy() {
+    window.removeEventListener("resize", this.handleResize);
+    if (this.chart) {
+      this.chart.dispose();
+      this.chart = null;
+    }
+  },
+  methods: {
+    initChart() {
+      if (!this.$refs.chartRef) return;
+      const textColor =
+        getComputedStyle(document.documentElement)
+          .getPropertyValue("--cockpit-panel-muted")
+          .trim() || "#909399";
+      this.chart = echarts.init(this.$refs.chartRef);
+      this.chart.setOption({
+        tooltip: {
+          trigger: "item",
+          formatter: "{b}: {c} ({d}%)",
+        },
+        series: [
+          {
+            type: "pie",
+            radius: ["48%", "78%"],
+            center: ["50%", "50%"],
+            avoidLabelOverlap: true,
+            label: {
+              show: true,
+              formatter: "{b}\n{d}%",
+              color: textColor,
+              fontSize: 11,
+            },
+            labelLine: {
+              show: true,
+              length: 8,
+              length2: 6,
+              lineStyle: { color: "rgba(183, 223, 245, 0.45)" },
+            },
+            data: healthScoreDistribution,
+          },
+        ],
+      });
+    },
+    handleResize() {
+      if (this.chart) this.chart.resize();
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.cockpit-health-panel__subtitle {
+  margin: 4px 0 10px;
+  font-size: 13px;
+  font-weight: 600;
+  color: var(--cockpit-panel-title, #303133);
+}
+
+.cockpit-health-panel__tab-body {
+  position: relative;
+  height: var(--cockpit-health-pane-h);
+  min-height: var(--cockpit-health-pane-h);
+  overflow: hidden;
+}
+
+.cockpit-health-panel__pane {
+  position: absolute;
+  inset: 0;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-start;
+}
+
+.cockpit-health-panel__body {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  flex-shrink: 0;
+  height: var(--cockpit-health-chart-h, 150px);
+  min-height: var(--cockpit-health-chart-h, 150px);
+}
+
+.cockpit-health-panel__legend {
+  flex: 0 0 52px;
+  margin: 0;
+  padding: 0;
+  list-style: none;
+}
+
+.cockpit-health-panel__legend-item {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  margin-bottom: 10px;
+  font-size: 12px;
+  color: var(--cockpit-panel-muted, #909399);
+
+  i {
+    flex-shrink: 0;
+    width: 10px;
+    height: 10px;
+    border-radius: 1px;
+  }
+}
+
+.cockpit-health-panel__chart {
+  flex: 1;
+  min-width: 0;
+  height: var(--cockpit-health-chart-h, 150px);
+}
+
+.cockpit-health-panel__table {
+  flex: 0 0 auto;
+  margin-top: 8px;
+}
+</style>

+ 65 - 0
src/views/admin/cockpitManage/component/CockpitHealthTable.vue

@@ -0,0 +1,65 @@
+<template>
+  <div class="cockpit-health-table">
+    <h3 class="cockpit-section-title">健康数据信息</h3>
+    <div
+      class="cockpit-scroll-table cockpit-table-block cockpit-unified-table cockpit-unified-table--health"
+    >
+      <p
+        class="titleYC cockpit-unified-table-head cockpit-unified-table-head--health"
+      >
+        <span>风场名称</span>
+        <span>优</span>
+        <span>良</span>
+        <span>中</span>
+        <span>差</span>
+      </p>
+      <Tabroll
+        :table-data="tableData"
+        :columns="columns"
+        :column-widths="columnWidths"
+        :max-height="bodyHeight"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+import { healthTableColumns, healthTableData } from "./cockpitHealthData";
+import { COCKPIT_TABLE_BODY_HEIGHT } from "./cockpitTableMetrics";
+import Tabroll from "./tabroll.vue";
+
+export default {
+  name: "CockpitHealthTable",
+  components: { Tabroll },
+  props: {
+    bodyHeight: { type: Number, default: COCKPIT_TABLE_BODY_HEIGHT },
+  },
+  data() {
+    return {
+      columns: healthTableColumns.map(({ prop }) => ({ prop })),
+      columnWidths: {
+        fieldName: "38%",
+        excellent: "15%",
+        good: "15%",
+        medium: "16%",
+        poor: "16%",
+      },
+      tableData: healthTableData,
+    };
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.cockpit-health-table {
+  width: 100%;
+}
+
+.cockpit-health-table .cockpit-section-title {
+  margin: 0 0 8px;
+  padding: 0;
+  border-bottom: none;
+  font-size: 14px;
+  font-weight: 600;
+}
+</style>

+ 96 - 0
src/views/admin/cockpitManage/component/CockpitTechTabs.vue

@@ -0,0 +1,96 @@
+<template>
+  <div class="cockpit-tech-tabs" :class="`cockpit-tech-tabs--${variant}`">
+    <div class="cockpit-tech-tabs__track">
+      <div class="cockpit-tech-tabs__list" role="tablist">
+        <button
+          v-for="tab in tabs"
+          :key="tab.value"
+          type="button"
+          role="tab"
+          class="cockpit-tech-tabs__item"
+          :class="{ 'is-active': value === tab.value }"
+          :aria-selected="value === tab.value"
+          @click="onSelect(tab.value)"
+        >
+          <svg
+            v-if="value === tab.value"
+            class="cockpit-tech-tabs__shape"
+            viewBox="0 0 160 40"
+            preserveAspectRatio="none"
+            aria-hidden="true"
+          >
+            <defs>
+              <linearGradient :id="gradId" x1="0%" y1="0%" x2="0%" y2="100%">
+                <stop offset="0%" stop-color="#4dd4ff" />
+                <stop offset="38%" stop-color="#1a9ee8" />
+                <stop offset="100%" stop-color="#0a4d8c" />
+              </linearGradient>
+              <linearGradient :id="strokeId" x1="0%" y1="0%" x2="100%" y2="0%">
+                <stop offset="0%" stop-color="rgba(100, 230, 255, 0.15)" />
+                <stop offset="25%" stop-color="#9ef8ff" />
+                <stop offset="75%" stop-color="#9ef8ff" />
+                <stop offset="100%" stop-color="rgba(100, 230, 255, 0.15)" />
+              </linearGradient>
+            </defs>
+            <path
+              class="cockpit-tech-tabs__shape-fill"
+              :fill="`url(#${gradId})`"
+              d="M14 3 L146 3 Q152 3 155 9 L160 40 L0 40 L5 9 Q8 3 14 3 Z"
+            />
+            <path
+              class="cockpit-tech-tabs__shape-stroke"
+              fill="none"
+              :stroke="`url(#${strokeId})`"
+              stroke-width="1.2"
+              d="M14 3 L146 3 Q152 3 155 9 L160 40 L0 40 L5 9 Q8 3 14 3 Z"
+            />
+          </svg>
+          <span class="cockpit-tech-tabs__label">{{ tab.label }}</span>
+        </button>
+      </div>
+      <!-- <div v-if="$slots.extra" class="cockpit-tech-tabs__extra">
+        <slot name="extra" />
+      </div> -->
+      <div class="cockpit-tech-tabs__baseline" aria-hidden="true" />
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "CockpitTechTabs",
+  model: {
+    prop: "value",
+    event: "input",
+  },
+  props: {
+    value: { type: String, required: true },
+    tabs: {
+      type: Array,
+      required: true,
+      // [{ label: '入库', value: 'import' }]
+    },
+    /** pill:胶囊底托(健康等);panel:贴面板顶左对齐(入库/接入,图二) */
+    variant: {
+      type: String,
+      default: "pill",
+      validator: (v) => ["pill", "panel"].includes(v),
+    },
+  },
+  computed: {
+    gradId() {
+      return `cockpit-tab-grad-${this._uid}`;
+    },
+    strokeId() {
+      return `cockpit-tab-stroke-${this._uid}`;
+    },
+  },
+  methods: {
+    onSelect(val) {
+      if (val !== this.value) {
+        this.$emit("input", val);
+      }
+    },
+  },
+};
+</script>

+ 23 - 0
src/views/admin/cockpitManage/component/cockpitHealthData.js

@@ -0,0 +1,23 @@
+/** 驾驶舱健康展示 mock(布局占位,不影响原有业务接口) */
+export const healthScoreDistribution = [
+  { value: 40, name: "优", itemStyle: { color: "#7ECF51" } },
+  { value: 10, name: "良", itemStyle: { color: "#2979ff" } },
+  { value: 45, name: "中", itemStyle: { color: "#EECB5F" } },
+  { value: 5, name: "差", itemStyle: { color: "#E16757" } },
+];
+
+export const healthTableColumns = [
+  { prop: "fieldName", label: "风场名称", minWidth: 108 },
+  { prop: "excellent", label: "优", width: 40 },
+  { prop: "good", label: "良", width: 40 },
+  { prop: "medium", label: "中", width: 40 },
+  { prop: "poor", label: "差", width: 40 },
+];
+
+export const healthTableData = [
+  { fieldName: "马宗山风电场", excellent: 3, good: 10, medium: 10, poor: 9 },
+  { fieldName: "桥西第一风电场", excellent: 5, good: 8, medium: 6, poor: 2 },
+  { fieldName: "桥西第二风电场", excellent: 4, good: 9, medium: 7, poor: 3 },
+  { fieldName: "王团风电场", excellent: 6, good: 7, medium: 5, poor: 1 },
+  { fieldName: "示范风电场", excellent: 50, good: 200, medium: 100, poor: 100 },
+];

+ 6 - 0
src/views/admin/cockpitManage/component/cockpitTableMetrics.js

@@ -0,0 +1,6 @@
+/** 驾驶舱列表表格统一尺寸(与 cockpit-dashboard.scss 变量保持一致) */
+export const COCKPIT_TR_HEIGHT = 40;
+export const COCKPIT_TABLE_VISIBLE_ROWS = 4;
+export const COCKPIT_TH_HEIGHT = 32;
+export const COCKPIT_TABLE_BODY_HEIGHT =
+  COCKPIT_TR_HEIGHT * COCKPIT_TABLE_VISIBLE_ROWS;

+ 238 - 108
src/views/admin/cockpitManage/component/leftdata.vue

@@ -1,12 +1,14 @@
 <template>
-  <div class="backbone">
-    <div class="BOXYI" v-hasPermi="['home:performance:performanceData']">
+  <div class="backbone" :class="{ 'backbone--flat': panel !== 'all' }">
+    <div
+      v-if="showTaskBlock"
+      class="BOXYI"
+      v-hasPermi="['home:performance:performanceData']"
+    >
       <h3>任务统计</h3>
-      <div class="BOXsan">
+      <div class="BOXsan cockpit-task-kpi">
         <div class="YC02" @click="getwgl">
-          <strong class="texyellow">
-            {{ onrelevance ? onrelevance : 0 }}</strong
-          >
+          <strong> {{ onrelevance ? onrelevance : 0 }}</strong>
           <span>待分析</span>
         </div>
         <div class="YC02" @click="getwfx">
@@ -15,7 +17,9 @@
         </div>
 
         <div class="YC03" @click="getpd">
-          <strong class="textred"> {{ queueNumber ? queueNumber : 0 }}</strong>
+          <strong class="texyellow">
+            {{ queueNumber ? queueNumber : 0 }}</strong
+          >
           <span>排队中</span>
         </div>
       </div>
@@ -96,46 +100,130 @@
         </div>
       </el-dialog>
     </div>
-    <!-- 入库信息 -->
-    <!-- 离线 -->
-    <div class="BOX" v-hasPermi="['home:offlinedata']">
-      <h3 class="centeredh3">
-        入库数据
-        <span>
-          <span style="cursor: pointer" @click="importdata">导入数据</span>
-          <span style="cursor: pointer" @click="more">更多</span>
-        </span>
-      </h3>
-      <div>
-        <p class="titleYC"><span>风场名称</span><span>入库时间</span></p>
-        <tabroll
-          :table-data="tableData"
-          :shujuloading="shujuloading"
-          :columns="YCtableColumns"
-          :column-widths="YCcolumnWidths"
-          :max-height="140"
-        ></tabroll>
-      </div>
-    </div>
-    <!-- 在线 -->
-    <div class="BOX" v-hasPermi="['home:onlinData']">
-      <h3 class="centeredh3">
-        接入数据
-        <span>
-          <span style="cursor: pointer" @click="onlinedata">更多</span>
-        </span>
-      </h3>
-      <div>
-        <p class="titleYC"><span>风场名称</span><span>采集状态</span></p>
-        <tabroll
-          :table-data="hometopdata"
-          :hometoploading="hometoploading"
-          :columns="hometopColumns"
-          :column-widths="hometopcolumnWidths"
-          :max-height="140"
-        ></tabroll>
+    <template v-if="showInboundBlock">
+      <div v-if="panel === 'inbound'" class="cockpit-inbound-panel">
+        <CockpitTechTabs
+          v-model="inboundTab"
+          :tabs="inboundTabs"
+          variant="panel"
+        >
+          <template #extra>
+            <i
+              class="el-icon-arrow-right cockpit-tech-tabs__chevron"
+              title="更多"
+              @click="onInboundChevronClick"
+            />
+          </template>
+        </CockpitTechTabs>
+
+        <div class="cockpit-inbound-body">
+          <div v-show="inboundTab === 'import'" class="cockpit-inbound-pane">
+            <div class="cockpit-inbound-pane-header">
+              <div
+                class="cockpit-inbound-subtitle"
+                v-hasPermi="['home:offlinedata']"
+              >
+                入库数据
+              </div>
+              <span class="cockpit-inbound-col-data">
+                <span class="cockpit-inbound-more-link" @click="more"
+                  >更多</span
+                >
+              </span>
+            </div>
+
+            <div
+              class="cockpit-inbound-table-wrap cockpit-table-block cockpit-unified-table cockpit-unified-table--inbound-import BOX BOX--flat"
+              v-hasPermi="['home:offlinedata']"
+            >
+              <p
+                class="titleYC cockpit-unified-table-head cockpit-unified-table-head--inbound-import"
+              >
+                <span class="cockpit-inbound-col-name">风场名称</span>
+                <span class="cockpit-inbound-col-extra"> 入库时间 </span>
+              </p>
+              <tabroll
+                :table-data="tableData"
+                :shujuloading="shujuloading"
+                :columns="inboundImportColumns"
+                :column-widths="inboundImportColumnWidths"
+                :max-height="140"
+              />
+            </div>
+          </div>
+
+          <div v-show="inboundTab === 'access'" class="cockpit-inbound-pane">
+            <div
+              class="cockpit-inbound-subtitle"
+              v-hasPermi="['home:onlinData']"
+            >
+              接入数据
+            </div>
+            <div
+              class="cockpit-inbound-table-wrap cockpit-table-block cockpit-unified-table cockpit-unified-table--inbound-two BOX BOX--flat"
+              v-hasPermi="['home:onlinData']"
+            >
+              <p
+                class="titleYC cockpit-unified-table-head cockpit-unified-table-head--inbound-two"
+              >
+                <span class="cockpit-inbound-col-name">风场名称</span>
+                <span class="cockpit-inbound-col-extra">采集状态</span>
+              </p>
+              <tabroll
+                :table-data="hometopdata"
+                :hometoploading="hometoploading"
+                :columns="hometopColumns"
+                :column-widths="hometopcolumnWidths"
+                :max-height="cockpitTableBodyHeight"
+              />
+            </div>
+          </div>
+        </div>
       </div>
-    </div>
+
+      <template v-else>
+        <!-- 入库信息 -->
+        <!-- 离线 -->
+        <div class="BOX" v-hasPermi="['home:offlinedata']">
+          <h3 class="centeredh3">
+            入库数据
+            <span>
+              <span style="cursor: pointer" @click="importdata">导入数据</span>
+              <span style="cursor: pointer" @click="more">更多</span>
+            </span>
+          </h3>
+          <div>
+            <p class="titleYC"><span>风场名称</span><span>入库时间</span></p>
+            <tabroll
+              :table-data="tableData"
+              :shujuloading="shujuloading"
+              :columns="YCtableColumns"
+              :column-widths="YCcolumnWidths"
+              :max-height="140"
+            ></tabroll>
+          </div>
+        </div>
+        <!-- 在线 -->
+        <div class="BOX" v-hasPermi="['home:onlinData']">
+          <h3 class="centeredh3">
+            接入数据
+            <span>
+              <span style="cursor: pointer" @click="onlinedata">更多</span>
+            </span>
+          </h3>
+          <div>
+            <p class="titleYC"><span>风场名称</span><span>采集状态</span></p>
+            <tabroll
+              :table-data="hometopdata"
+              :hometoploading="hometoploading"
+              :columns="hometopColumns"
+              :column-widths="hometopcolumnWidths"
+              :max-height="140"
+            ></tabroll>
+          </div>
+        </div>
+      </template>
+    </template>
     <el-dialog
       title="实时数据采集统计"
       :visible.sync="lineShow"
@@ -304,22 +392,58 @@ import {
 } from "@/api/ledger.js";
 import axios from "axios";
 import Tabroll from "./tabroll.vue";
+import CockpitTechTabs from "./CockpitTechTabs.vue";
+import { COCKPIT_TABLE_BODY_HEIGHT } from "./cockpitTableMetrics";
+
 export default {
-  // props: {
-  //   maplistArr: {
-  //     type: Object,
-  //     default: () => ({}),
-  //   },
-  //   defaultdata: {
-  //     type: Object,
-  //     default: () => ({}),
-  //   },
-  // },
+  props: {
+    /** task | inbound | all */
+    panel: { type: String, default: "all" },
+  },
   components: {
     Tabroll,
+    CockpitTechTabs,
+  },
+  computed: {
+    showTaskBlock() {
+      return this.panel === "all" || this.panel === "task";
+    },
+    showInboundBlock() {
+      return this.panel === "all" || this.panel === "inbound";
+    },
+    shouldPollInbound() {
+      return this.panel === "inbound" || this.panel === "all";
+    },
+    filteredWindList() {
+      return this.searchvalue
+        ? this.windList.filter((item) =>
+            item.windFarmName.includes(this.searchvalue),
+          )
+        : this.windList;
+    },
+    inboundImportColumns() {
+      return [
+        { prop: "windFarmName", label: "风场名称" },
+        { prop: "createTime", label: "导入数据" },
+      ];
+    },
+    inboundImportColumnWidths() {
+      return {
+        windFarmName: "42%",
+        createTime: "auto",
+      };
+    },
+    inboundTabs() {
+      return [
+        { label: "入库", value: "import" },
+        { label: "接入", value: "access" },
+      ];
+    },
   },
   data() {
     return {
+      cockpitTableBodyHeight: COCKPIT_TABLE_BODY_HEIGHT,
+      inboundTab: "import",
       onrelevance: "",
       abnormal: "",
       queueNumber: "",
@@ -398,8 +522,8 @@ export default {
         },
       ],
       hometopcolumnWidths: {
-        windFarmName: 130,
-        canAnalysis: 150,
+        windFarmName: "42%",
+        canAnalysis: "auto",
       },
       ListtableData: [],
       startDate: "",
@@ -410,27 +534,22 @@ export default {
     };
   },
   created() {
-    this.getbacklog();
-    this.ruku();
-    // this.hometop();
-  },
-  computed: {
-    filteredWindList() {
-      return this.searchvalue
-        ? this.windList.filter((item) =>
-            item.windFarmName.includes(this.searchvalue),
-          )
-        : this.windList;
-    },
+    if (this.showTaskBlock) {
+      this.getbacklog();
+    }
+    if (this.showInboundBlock) {
+      this.ruku();
+    }
   },
-
-  // 在线版本 离线需要注释
   mounted() {
-    this.startPolling();
+    if (this.shouldPollInbound) {
+      this.startPolling();
+    }
   },
-  // 在线版本 离线需要注释
   beforeDestroy() {
-    this.stopPolling();
+    if (this.shouldPollInbound) {
+      this.stopPolling();
+    }
   },
   methods: {
     getbacklog() {
@@ -560,6 +679,13 @@ export default {
           console.error("Error fetching data:", error);
         });
     },
+    onInboundChevronClick() {
+      if (this.inboundTab === "import") {
+        this.more();
+      } else {
+        this.onlinedata();
+      }
+    },
     importdata() {
       window.open(this.$router.resolve({ path: "/transition" }).href, "_blank");
     },
@@ -670,22 +796,28 @@ export default {
 
 <style lang="scss" scoped>
 .backbone {
-  width: 300px;
-  color: #fff;
-  border-radius: 5px;
-  //   padding: 20px;
+  width: 100%;
+  color: inherit;
+  border-radius: 0;
+}
+
+.backbone--flat .BOXYI,
+.backbone--flat .BOX {
+  background: transparent !important;
+  padding-left: 0 !important;
+  padding-right: 0 !important;
+  margin-bottom: 0 !important;
+  color: inherit !important;
 }
 
 .BOXYI {
-  background: rgba(0, 79, 95, 0.8);
+  background: transparent;
   display: flex;
   flex-wrap: wrap;
-  padding-left: 10px;
-  padding-right: 10px;
-  padding-bottom: 15px;
-  color: #fff;
-  border-radius: 5px;
-  margin-bottom: 10px;
+  padding: 0;
+  color: inherit;
+  border-radius: 0;
+  margin-bottom: 0;
 
   .BOXsan {
     width: 100%;
@@ -787,24 +919,22 @@ export default {
 }
 
 .BOX {
-  background: rgba(0, 79, 95, 0.8);
+  background: transparent;
   display: flex;
-  padding-left: 10px;
+  padding-left: 0;
   flex-wrap: wrap;
-  padding-bottom: 15px;
-  color: #fff;
-  border-radius: 5px;
-  margin-bottom: 10px;
-
-  h3 {
-    margin-top: 5px;
-  }
+  padding-bottom: 8px;
+  color: inherit;
+  border-radius: 0;
+  margin-bottom: 0;
 
   .centeredh3 {
     width: 100%;
     display: flex;
     justify-content: space-between;
     margin-bottom: 5px;
+    border-bottom: none;
+    padding-bottom: 0;
 
     span {
       font-size: 12px;
@@ -817,26 +947,26 @@ export default {
   font-size: 12px;
 }
 
-::v-deep.el-table__cell {
-  padding: 2px 0 !important;
+.cockpit-inbound-panel ::v-deep .el-table__body .el-table__cell {
+  padding: 0 !important;
 }
 
-::v-deep.el-table .el-table__cell {
-  padding: 2px 0 !important;
+.cockpit-inbound-panel ::v-deep .el-table__body .el-table__cell .cell {
+  padding-top: 0 !important;
+  padding-bottom: 0 !important;
+  padding-left: var(--cockpit-td-padding-x, 8px) !important;
+  padding-right: var(--cockpit-td-padding-x, 8px) !important;
 }
 
 ::v-deep.el-table--scrollable-y .el-table__body-wrapper {
   overflow-y: clip !important;
 }
 
-.titleYC {
-  font-size: 14px;
-  background: #214e5d;
-  padding: 5px 0;
-
-  span:nth-child(1) {
-    margin: 0 70px 0 10px;
-  }
+.titleYC:not(.cockpit-unified-table-head) span:nth-child(1) {
+  margin: 0 70px 0 10px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
 }
 
 .titleJQ {

+ 206 - 167
src/views/admin/cockpitManage/component/rightdata.vue

@@ -1,6 +1,6 @@
 <template>
-  <div class="backbone">
-    <div>
+  <div class="backbone" :class="{ 'backbone--flat': panel !== 'all' }">
+    <div v-if="showAnalysis">
       <h3>风场分析完成率</h3>
       <el-date-picker
         size="mini"
@@ -11,60 +11,60 @@
         start-placeholder="开始日期"
         end-placeholder="结束日期"
         @change="onDateChange"
-      >
-      </el-date-picker>
-      <div id="main" style="width: 260px; height: 160px"></div>
+      />
+      <div ref="chartRef" class="cockpit-analysis-chart"></div>
     </div>
 
-    <div>
-      <h3 style="margin-bottom: 8px">历史操作记录</h3>
-      <p class="titleJQ">
-        <span>名字</span><span>日期</span><span>操作记录</span>
-      </p>
-      <Tabroll
-        :table-data="tableData"
-        :shujuloading="shujuloading"
-        :columns="tableColumns"
-        :max-height="120"
-        :column-widths="columnWidths"
-      ></Tabroll>
+    <div v-if="showHistory" class="cockpit-history-block">
+      <h3 class="cockpit-section-title">历史操作记录</h3>
+      <div
+        class="cockpit-scroll-table cockpit-table-block cockpit-unified-table cockpit-unified-table--history"
+      >
+        <p
+          class="titleJQ cockpit-unified-table-head cockpit-unified-table-head--history"
+        >
+          <span>名字</span><span>日期</span><span>操作记录</span>
+        </p>
+        <Tabroll
+          :table-data="tableData"
+          :shujuloading="shujuloading"
+          :columns="tableColumns"
+          :max-height="tableBodyHeight"
+          :column-widths="historyColumnWidths"
+        />
+      </div>
     </div>
-    <div>
+
+    <div v-if="showEquipment">
       <h3>风机异常概览</h3>
       <div class="BOX">
         <div style="display: flex">
           <div class="YC01">
-            <strong>
-              {{ equipment?.errCounrt ? equipment.errCounrt : 0 }}</strong
-            >
+            <strong>{{
+              equipment?.errCounrt ? equipment.errCounrt : 0
+            }}</strong>
             <span>异常总数</span>
           </div>
           <div class="YC02">
-            <strong>
-              {{
-                equipment?.errCountInCurrentMonth
-                  ? equipment.errCountInCurrentMonth
-                  : 0
-              }}</strong
-            >
+            <strong>{{
+              equipment?.errCountInCurrentMonth
+                ? equipment.errCountInCurrentMonth
+                : 0
+            }}</strong>
             <span>本月异常</span>
           </div>
         </div>
         <div style="display: flex">
           <div class="YC02">
-            <strong class="textred">
-              {{
-                equipment?.errCountNotDeal ? equipment.errCountNotDeal : 0
-              }}</strong
-            >
+            <strong class="textred">{{
+              equipment?.errCountNotDeal ? equipment.errCountNotDeal : 0
+            }}</strong>
             <span>未处理异常</span>
           </div>
           <div class="YC02">
-            <strong class="texyellow">
-              {{
-                equipment?.errDeviceCount ? equipment.errDeviceCount : 0
-              }}</strong
-            >
+            <strong class="texyellow">{{
+              equipment?.errDeviceCount ? equipment.errDeviceCount : 0
+            }}</strong>
             <span>异常设备数</span>
           </div>
           <div class="YC03"></div>
@@ -72,8 +72,15 @@
       </div>
     </div>
 
-    <div>
-      <h3 style="margin: 8px 0">异常数据信息</h3>
+    <div
+      v-if="showEquipmentTable"
+      class="cockpit-equipment-table-block"
+      :class="{
+        'cockpit-equipment-table-block--stacked':
+          panel === 'equipment-abnormal',
+      }"
+    >
+      <h3 class="cockpit-section-title">异常数据信息</h3>
       <p class="titleYC"><span>风场名称</span><span>风机信息</span></p>
       <Tabroll
         :table-data="YCtableData"
@@ -81,7 +88,7 @@
         :columns="YCtableColumns"
         :column-widths="YCcolumnWidths"
         :max-height="120"
-      ></Tabroll>
+      />
     </div>
   </div>
 </template>
@@ -90,54 +97,36 @@
 import { queryStatistics, queryOptionRecords } from "@/api/ledger.js";
 import * as echarts from "echarts";
 import Tabroll from "./tabroll.vue";
+import { COCKPIT_TABLE_BODY_HEIGHT } from "./cockpitTableMetrics";
 
 export default {
   props: {
-    maplistArr: {
-      type: Object,
-      default: () => ({}),
-    },
-    defaultdata: {
-      type: Object,
-      default: () => ({}),
-    },
-  },
-
-  watch: {
-    maplistArr: {
-      handler(newVal) {
-        if (Object.keys(newVal).length > 0) {
-          // 检查maplistArr是否有值
-          this.getStatistics();
-          this.getOptionRecords();
-        }
-      },
-      immediate: true, // 立即执行一次handler
-      deep: true, // 深度监听对象内部变化
-    },
-  },
-  components: {
-    Tabroll,
+    maplistArr: { type: Object, default: () => ({}) },
+    defaultdata: { type: Object, default: () => ({}) },
+    /** analysis | history | analysis-history | equipment | equipment-table | equipment-abnormal | all */
+    panel: { type: String, default: "all" },
   },
+  components: { Tabroll },
   data() {
     return {
       myChart: null,
       YCtableData: [],
       shujuloading: false,
       YCtableColumns: [{ prop: "fieldName" }, { prop: "engineName" }],
-      YCcolumnWidths: {
-        fieldName: 120,
-      },
+      YCcolumnWidths: { fieldName: 120 },
       tableData: [],
       tableColumns: [
         { prop: "optionByName" },
         { prop: "createTime" },
         { prop: "optionContent" },
       ],
-      columnWidths: {
-        optionByName: 60,
-        createTime: 100,
+      columnWidths: { optionByName: 60, createTime: 100 },
+      historyColumnWidths: {
+        optionByName: "28%",
+        createTime: "38%",
+        optionContent: "34%",
       },
+      tableBodyHeight: COCKPIT_TABLE_BODY_HEIGHT,
       equipment: {
         analysisedCount: 0,
         analysisingCount: 0,
@@ -148,50 +137,108 @@ export default {
       endTime: "",
     };
   },
-  created() {},
+  computed: {
+    showAnalysis() {
+      return (
+        this.panel === "all" ||
+        this.panel === "analysis" ||
+        this.panel === "analysis-history"
+      );
+    },
+    showHistory() {
+      return (
+        this.panel === "all" ||
+        this.panel === "history" ||
+        this.panel === "analysis-history"
+      );
+    },
+    showEquipment() {
+      return (
+        this.panel === "all" ||
+        this.panel === "equipment" ||
+        this.panel === "equipment-abnormal"
+      );
+    },
+    showEquipmentTable() {
+      return (
+        this.panel === "all" ||
+        this.panel === "equipment-table" ||
+        this.panel === "equipment-abnormal"
+      );
+    },
+  },
+  watch: {
+    maplistArr: {
+      handler(newVal) {
+        if (!newVal?.codeNumber) return;
+        if (
+          this.showAnalysis ||
+          this.showEquipment ||
+          this.showEquipmentTable
+        ) {
+          this.getStatistics();
+        }
+        if (this.showHistory) {
+          this.getOptionRecords();
+        }
+      },
+      immediate: true,
+      deep: true,
+    },
+  },
   mounted() {
-    this.initChart();
-    const currentTime = new Date();
-    const halfYearAgo = new Date();
-    halfYearAgo.setMonth(currentTime.getMonth() - 6);
-    this.value1 = [halfYearAgo, currentTime];
-    // 同时更新 startTime 和 endTime
-    this.startTime = this.$formatDate(halfYearAgo);
-    this.endTime = this.$formatDate(currentTime);
+    if (this.showAnalysis) {
+      const currentTime = new Date();
+      const halfYearAgo = new Date();
+      halfYearAgo.setMonth(currentTime.getMonth() - 6);
+      this.value1 = [halfYearAgo, currentTime];
+      this.startTime = this.$formatDate(halfYearAgo);
+      this.endTime = this.$formatDate(currentTime);
+      this.$nextTick(() => this.initChart());
+    }
+  },
+  beforeDestroy() {
+    if (this.myChart) {
+      this.myChart.dispose();
+      this.myChart = null;
+    }
   },
   methods: {
     initChart() {
-      var chartDom = document.getElementById("main");
-      this.myChart = echarts.init(chartDom);
+      if (!this.$refs.chartRef) return;
+      this.myChart = echarts.init(this.$refs.chartRef);
       this.updateChart();
     },
     updateChart() {
-      var option = {
+      if (!this.myChart) return;
+      this.myChart.setOption({
         tooltip: {
           trigger: "item",
-          formatter: function (params) {
-            var percent = params.percent.toFixed(2) + "%";
+          formatter(params) {
+            const percent = params.percent.toFixed(2) + "%";
             return `${params.name}: ${params.value} (${percent})`;
           },
         },
         legend: {
           orient: "vertical",
-          left: "left",
-          top: "30%",
+          left: 4,
+          top: "middle",
+          itemWidth: 10,
+          itemHeight: 10,
+          itemGap: 10,
+          textStyle: {
+            color: "rgba(183, 223, 245, 0.85)",
+            fontSize: 11,
+          },
         },
         series: [
           {
             type: "pie",
-            radius: ["40%", "80%"],
+            radius: ["42%", "72%"],
             avoidLabelOverlap: false,
-            label: {
-              show: false,
-              position: "center",
-            },
-            labelLine: {
-              show: false,
-            },
-            center: ["60%", "50%"],
+            label: { show: false, position: "center" },
+            labelLine: { show: false },
+            center: ["62%", "50%"],
             data: [
               {
                 value: this.equipment.analysisedCount,
@@ -211,41 +258,51 @@ export default {
             ],
           },
         ],
-      };
-      this.myChart.setOption(option);
+      });
     },
-
     getStatistics() {
+      if (!this.maplistArr?.codeNumber) return;
       const startTime =
         this.startTime ||
         this.$formatDate(
-          new Date(new Date().setMonth(new Date().getMonth() - 6))
-        ); // 半年前的时间
-      const endTime = this.endTime || this.$formatDate(new Date()); // 当前时间
-      // halfYearAgo.setMonth(currentTime.getMonth() - 6)
-
-      let DataObject = {
+          new Date(new Date().setMonth(new Date().getMonth() - 6)),
+        );
+      const endTime = this.endTime || this.$formatDate(new Date());
+      queryStatistics({
         codeNumber: this.maplistArr.codeNumber,
         codeType: this.maplistArr.codeType,
-        startTime: startTime,
-        endTime: endTime,
-      };
-      queryStatistics(DataObject).then((res) => {
-        this.YCtableData = res.data.errList || [];
-        this.equipment = res.data || {};
-        this.updateChart(); // Update chart after data is received
+        startTime,
+        endTime,
+      }).then((res) => {
+        const data = res.data || {};
+        this.YCtableData = data.errList || [];
+        this.equipment = data;
+        this.$nextTick(() => {
+          if (this.showAnalysis) {
+            if (!this.myChart && this.$refs.chartRef) {
+              this.initChart();
+            } else {
+              this.updateChart();
+            }
+          }
+        });
       });
     },
     getOptionRecords() {
-      let DataObject = {
+      if (!this.maplistArr?.codeNumber) return;
+      this.shujuloading = true;
+      queryOptionRecords({
         codeNumber: this.maplistArr.codeNumber,
         codeType: this.maplistArr.codeType,
-      };
-      queryOptionRecords(DataObject).then((res) => {
-        this.tableData = res.data || [];
-      });
+      })
+        .then((res) => {
+          const list = res.data;
+          this.tableData = Array.isArray(list) ? list : list?.list || [];
+        })
+        .finally(() => {
+          this.shujuloading = false;
+        });
     },
-
     onDateChange(date) {
       if (Array.isArray(date)) {
         this.startTime = this.$formatDate(date[0]);
@@ -265,21 +322,29 @@ export default {
 
 <style lang="scss" scoped>
 .backbone {
-  width: 300px;
-  color: #fff;
-  background: rgba(0, 128, 128, 0.8);
-  // height: 100%;
-  // height: 95%;
-  border-radius: 5px;
-  padding: 20px;
+  width: 100%;
+  color: inherit;
+  background: transparent;
+  padding: 0;
+  border-radius: 0;
+}
+
+.backbone--flat .BOX {
+  background: transparent;
+  padding-left: 0;
+}
+
+.cockpit-analysis-chart {
+  width: 100%;
+  height: 160px;
 }
 
 .BOX {
-  background: rgba(0, 79, 95, 0.6);
+  background: transparent;
   display: flex;
-  padding-left: 10px;
+  padding-left: 0;
   flex-wrap: wrap;
-  padding-bottom: 15px;
+  padding-bottom: 8px;
 }
 
 .YC01 {
@@ -290,7 +355,7 @@ export default {
   background-repeat: no-repeat;
   background-position: center;
   margin-right: 15px;
-  margin-top: 15px;
+  margin-top: 8px;
   text-align: center;
 
   strong {
@@ -313,7 +378,7 @@ export default {
   background-repeat: no-repeat;
   background-position: center;
   margin-right: 15px;
-  margin-top: 15px;
+  margin-top: 8px;
   text-align: center;
 
   .textred {
@@ -341,47 +406,21 @@ export default {
   height: 60px;
 }
 
-.Tabtab {
-  font-size: 12px;
-}
-
-::v-deep.el-table__cell {
-  padding: 2px 0 !important;
-}
-
-::v-deep.el-table .el-table__cell {
-  padding: 2px 0 !important;
+.titleYC span:nth-child(1) {
+  margin: 0 70px 0 10px;
 }
 
-::v-deep.el-table--scrollable-y .el-table__body-wrapper {
-  overflow-y: clip !important;
+.titleJQ:not(.cockpit-history-table-head) span:nth-child(1) {
+  margin: 0 15px 0 10px;
 }
 
-.titleYC {
-  font-size: 14px;
-  background: #214e5d;
-  padding: 5px 0;
-
-  span:nth-child(1) {
-    margin: 0 70px 0 10px;
-  }
+.titleJQ:not(.cockpit-history-table-head) span:nth-child(2) {
+  margin: 0 45px 0 30px;
 }
 
-.titleJQ {
-  font-size: 14px;
-  background: #214e5d;
-  padding: 5px 0;
-
-  span:nth-child(1) {
-    margin: 0 15px 0 10px;
-  }
-
-  span:nth-child(2) {
-    margin: 0 45px 0 30px;
-  }
-}
-.el-date-editor--datetimerange.el-input,
-.el-date-editor--datetimerange.el-input__inner {
-  width: 250px;
+::v-deep .el-date-editor--datetimerange.el-input,
+::v-deep .el-date-editor--datetimerange.el-input__inner {
+  width: 100%;
+  max-width: 260px;
 }
 </style>

+ 80 - 34
src/views/admin/cockpitManage/component/tabroll.vue

@@ -1,16 +1,16 @@
 <template>
-  <div>
+  <div class="tabroll-wrap cockpit-tabroll-table">
     <el-table
-    :show-header="false"
+      :show-header="false"
       v-loading="shujuloading"
       :data="tableData"
       :max-height="maxHeight"
       ref="shujuscroll_Table"
       strip
+      style="width: 100%"
       @mouseenter.native="shujuautoScroll(true)"
       @mouseleave.native="shujuautoScroll(false)"
       id="Tabtab"
-      :header-cell-style="{ backgroundColor: 'red' }"
     >
       <el-table-column
         v-for="(column, index) in columns"
@@ -18,13 +18,16 @@
         :prop="column.prop"
         :label="column.label"
         show-overflow-tooltip
-        :width="getColumnWidth(column.prop)" 
-      ></el-table-column>
+        :width="getColumnWidth(column.prop)"
+        :min-width="getColumnMinWidth(column.prop)"
+      />
     </el-table>
   </div>
 </template>
 
 <script>
+import { COCKPIT_TABLE_BODY_HEIGHT } from "./cockpitTableMetrics";
+
 export default {
   props: {
     tableData: {
@@ -45,7 +48,7 @@ export default {
     },
     maxHeight: {
       type: Number,
-      default: 150,
+      default: COCKPIT_TABLE_BODY_HEIGHT,
     },
     columnWidths: {
       type: Object,
@@ -57,32 +60,60 @@ export default {
     },
   },
   mounted() {
-    this.shujuautoScroll();
+    this.$nextTick(() => this.shujuautoScroll());
+  },
+  watch: {
+    tableData() {
+      this.$nextTick(() => {
+        this.shujuautoScroll(true);
+        this.shujuautoScroll();
+      });
+    },
   },
   beforeDestroy() {
     this.shujuautoScroll(true);
   },
   methods: {
     shujuautoScroll(stop) {
-      const table = this.$refs.shujuscroll_Table;
-      const divData = table.$refs.bodyWrapper;
       if (stop) {
         window.clearInterval(this.scrolltimer);
-      } else {
-        this.scrolltimer = window.setInterval(() => {
-          divData.scrollTop += 1;
-          if (
-            divData.clientHeight + divData.scrollTop ==
-            divData.scrollHeight
-          ) {
-            divData.scrollTop = 0;
-          }
-        }, 150);
+        this.scrolltimer = null;
+        return;
       }
+      const table = this.$refs.shujuscroll_Table;
+      const divData = table && table.$refs.bodyWrapper;
+      if (!divData || !this.tableData || this.tableData.length === 0) {
+        return;
+      }
+      if (divData.scrollHeight <= divData.clientHeight) {
+        return;
+      }
+      window.clearInterval(this.scrolltimer);
+      this.scrolltimer = window.setInterval(() => {
+        divData.scrollTop += 1;
+        if (
+          divData.clientHeight + divData.scrollTop >=
+          divData.scrollHeight
+        ) {
+          divData.scrollTop = 0;
+        }
+      }, 150);
     },
     getColumnWidth(prop) {
-      return this.columnWidths[prop] + 'px'; // 根据传入的属性(prop)获取对应的列宽度
-      return this.YCcolumnWidths[prop] + 'px'; // 根据传入的属性(prop)获取对应的列宽度
+      const w =
+        this.columnWidths[prop] ?? this.YCcolumnWidths[prop];
+      if (w == null || w === "auto") return undefined;
+      if (typeof w === "string" && w.endsWith("%")) return undefined;
+      if (typeof w === "string") return w;
+      return `${w}px`;
+    },
+    getColumnMinWidth(prop) {
+      const w =
+        this.columnWidths[prop] ?? this.YCcolumnWidths[prop];
+      if (typeof w === "string" && w.endsWith("%")) {
+        return w;
+      }
+      return undefined;
     },
     tableRowClassName({ row, rowIndex }) {
       if (rowIndex % 2 === 1) {
@@ -96,12 +127,30 @@ export default {
 </script>
 
 <style lang="scss" scoped>
+.tabroll-wrap {
+  width: 100%;
+}
+
 #Tabtab {
+  width: 100% !important;
   font-size: 12px;
 }
 
-::v-deep.el-table .el-table__cell {
-  padding: 2px 0 !important;
+::v-deep #Tabtab .el-table__body,
+::v-deep #Tabtab .el-table__body-wrapper table {
+  width: 100% !important;
+  table-layout: fixed;
+}
+
+::v-deep .cockpit-tabroll-table .el-table__body .el-table__cell {
+  padding: 0 !important;
+}
+
+::v-deep .cockpit-tabroll-table .el-table__body .el-table__cell .cell {
+  padding-top: 0 !important;
+  padding-bottom: 0 !important;
+  padding-left: var(--cockpit-td-padding-x, 8px) !important;
+  padding-right: var(--cockpit-td-padding-x, 8px) !important;
 }
 
 /* 去掉最下面的那一条线 */
@@ -109,18 +158,15 @@ export default {
   height: 0px;
 }
 
-/* 滚动条样式 */
-::v-deep .el-table__body-wrapper::-webkit-scrollbar-track {
-  background-color: #004f5f;
-}
-
-::v-deep .el-table__body-wrapper::-webkit-scrollbar {
-  width: 0px;
-  opacity: 0.5;
+/* 自动滚动:隐藏滚动条 */
+::v-deep .cockpit-tabroll-table .el-table__body-wrapper {
+  scrollbar-width: none;
+  -ms-overflow-style: none;
 }
 
-::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb {
-  border-radius: 15px;
-  // background-color: rgba(0, 79, 95, 0.75);
+::v-deep .cockpit-tabroll-table .el-table__body-wrapper::-webkit-scrollbar {
+  width: 0 !important;
+  height: 0 !important;
+  display: none !important;
 }
 </style>