From 9ca85dc3bd39864daf9528d746f4bc6a0963a4c0 Mon Sep 17 00:00:00 2001
From: riku <risaku@163.com>
Date: 星期四, 17 四月 2025 14:05:44 +0800
Subject: [PATCH] 完成走航融合模块

---
 src/model/SatelliteGrid.js                            |  237 +++++-----
 src/views/underwaymix/component/UnderwayMixEdit.vue   |  197 ++++++++
 src/components/search/OptionPollutionDegree.vue       |   40 +
 src/api/gridApi.js                                    |   36 +
 src/components.d.ts                                   |    1 
 src/views/underwaymix/component/GridStyleTool.vue     |  401 ++++++++++-------
 src/styles/elementUI.scss                             |    8 
 src/constant/pollution-degree.js                      |   30 +
 src/views/underwaymix/component/UnderwayMixManage.vue |  214 +++++++++
 src/components/monitor/DataTable.vue                  |    3 
 src/api/index.js                                      |    2 
 src/views/underwaymix/UnderwayMixMode.vue             |   87 +-
 src/components/grid/GridSearch.vue                    |   19 
 src/constant/device-type.js                           |   28 -
 src/stores/fusion-data.js                             |   60 ++
 15 files changed, 984 insertions(+), 379 deletions(-)

diff --git a/src/api/gridApi.js b/src/api/gridApi.js
index f9f4dff..4d971b1 100644
--- a/src/api/gridApi.js
+++ b/src/api/gridApi.js
@@ -46,6 +46,29 @@
       .then((res) => res.data);
   },
 
+  /**
+   * 鑾峰彇缃戞牸缁勪笅鐨勯仴娴嬫暟鎹�
+   * @param {*} gridData 缃戞牸鏁版嵁
+   * @returns
+   */
+  fetchGridData2(gridData) {
+    return $http
+      .post(`air/satellite/grid/data2`, gridData)
+      .then((res) => res.data);
+  },
+
+  /**
+   * 鍒犻櫎缃戞牸鏁版嵁
+   * @param {Number} dataId 鏁版嵁id
+   */
+  deleteGridData(dataId) {
+    return $http
+      .delete(`air/satellite/grid/data/delete`, {
+        params: { dataId }
+      })
+      .then((res) => res.data);
+  },
+
   // /**
   //  * 鑾峰彇缃戞牸缁勪笅鐨勯仴娴媋od
   //  * @param {*} groupId
@@ -144,10 +167,15 @@
       .then((res) => res.data);
   },
 
-  buildUnderwayProduct(missionCode, groupId) {
-    return $http.get(`air/satellite/import/grid/aod/download/template`, {
-      responseType: 'blob'
-    });
+  /**
+   * 鐢熸垚璧拌埅缃戞牸铻嶅悎璁板綍
+   * @param {*} gridData
+   * @returns
+   */
+  buildUnderwayProduct(gridData) {
+    return $http
+      .post(`air/satellite/product/underway/build`, gridData)
+      .then((res) => res.data);
   },
 
   mixUnderwayGridData(groupId, dataIdList) {
diff --git a/src/api/index.js b/src/api/index.js
index 559cc74..11a5208 100644
--- a/src/api/index.js
+++ b/src/api/index.js
@@ -1,7 +1,7 @@
 import axios from 'axios';
 import { ElMessage } from 'element-plus';
 
-const debug = true;
+const debug = false;
 
 let ip1 = 'http://47.100.191.150:9029/';
 // console.log(import.meta.env);
diff --git a/src/components.d.ts b/src/components.d.ts
index 77f6091..12e94ad 100644
--- a/src/components.d.ts
+++ b/src/components.d.ts
@@ -74,6 +74,7 @@
     OptionLocation: typeof import('./components/search/OptionLocation.vue')['default']
     OptionLocation2: typeof import('./components/search/OptionLocation2.vue')['default']
     OptionMission: typeof import('./components/search/OptionMission.vue')['default']
+    OptionPollutionDegree: typeof import('./components/search/OptionPollutionDegree.vue')['default']
     OptionTime: typeof import('./components/search/OptionTime.vue')['default']
     OptionType: typeof import('./components/search/OptionType.vue')['default']
     ProgressLineChart: typeof import('./components/chart/ProgressLineChart.vue')['default']
diff --git a/src/components/grid/GridSearch.vue b/src/components/grid/GridSearch.vue
index cbb977a..406818d 100644
--- a/src/components/grid/GridSearch.vue
+++ b/src/components/grid/GridSearch.vue
@@ -17,10 +17,10 @@
             label="缁忕含搴�"
             :content="data.gridCell.longitude + ', ' + data.gridCell.latitude"
           />
-          <DescriptionsListItem label="鍥涜嚦鑼冨洿" content="/" />
+          <!-- <DescriptionsListItem label="鍥涜嚦鑼冨洿" content="/" /> -->
         </DescriptionsList>
 
-        <el-tabs v-model="activeName" >
+        <el-tabs v-model="activeName">
           <el-tab-pane label="缃戞牸鏁版嵁" name="first">
             <DescriptionsList>
               <DescriptionsListItem
@@ -134,21 +134,11 @@
 
 const data = computed(() => {
   if (gridStore.selectedGridCellAndDataDetail) {
-    // const { gridCell, gridDataDetail } =
-    //   gridStore.selectedGridCellAndDataDetail;
-    // const res = [];
-    // // 濡傛灉缃戞牸鏁版嵁涓鸿瀺鍚堟暟鎹紝鍒欓渶瑕佸悓姝ュ睍绀哄嚭鍘熷鏁版嵁
-    // if (gridDataDetail.mixData) {
-    // }
     return {
       gridCell: gridStore.selectedGridCellAndDataDetail.gridCell,
       gridDataDetail: gridStore.selectedGridCellAndDataDetail.gridDataDetail,
       extData: gridStore.selectedGridCellAndDataDetail.extData
     };
-    // console.log(gridStore.selectedGridCellAndDataDetail);
-    
-    // return undefined;
-    // return gridStore.selectedGridCellAndDataDetail;
   } else {
     return undefined;
   }
@@ -158,6 +148,7 @@
   () => gridStore.selectedGridCellAndDataDetail,
   (nv, ov) => {
     if (nv != ov) {
+      activeName.value = 'first';
       dialogVisible.value = true;
     }
   },
@@ -165,10 +156,10 @@
 );
 </script>
 <style scoped>
-:deep(.el-tabs__item){
+:deep(.el-tabs__item) {
   color: rgba(221, 221, 221, 0.806);
 }
-:deep(.is-active){
+:deep(.is-active) {
   color: #f0ff1d;
 }
 </style>
diff --git a/src/components/monitor/DataTable.vue b/src/components/monitor/DataTable.vue
index c918ade..e7ad20d 100644
--- a/src/components/monitor/DataTable.vue
+++ b/src/components/monitor/DataTable.vue
@@ -268,7 +268,8 @@
 <style>
 .el-table {
   --el-table-bg-color: transparent;
-  --el-table-row-hover-bg-color: var(--select_color);
+  --el-table-row-hover-bg-color: #bffff454;
+  /* --el-table-row-hover-bg-color: transparent; */
   --el-table-current-row-bg-color: var(--select_color);
   /* --el-table-current-row-bg-color: #7dff5d96; */
   --el-table-text-color: var(--font-color);
diff --git a/src/components/search/OptionPollutionDegree.vue b/src/components/search/OptionPollutionDegree.vue
new file mode 100644
index 0000000..f7a6dac
--- /dev/null
+++ b/src/components/search/OptionPollutionDegree.vue
@@ -0,0 +1,40 @@
+<template>
+  <el-select
+    :model-value="modelValue"
+    @update:model-value="handleChange"
+    placeholder="姹℃煋鑳屾櫙"
+    size="small"
+    class="w-120"
+  >
+    <el-option
+      v-for="(s, i) in pollutionList"
+      :key="i"
+      :label="s.label"
+      :value="s.value"
+    />
+  </el-select>
+</template>
+
+<script>
+import { pollutionList } from '@/constant/pollution-degree';
+export default {
+  props: {
+    modelValue: String
+  },
+  emits: ['update:modelValue', 'initOver'],
+  data() {
+    return {
+      pollutionList: pollutionList()
+    };
+  },
+  methods: {
+    handleChange(value) {
+      this.$emit('update:modelValue', value);
+    }
+  },
+  mounted() {
+    this.$emit('initOver');
+    // this.handleChange(this.pollutionList[0].value);
+  }
+};
+</script>
diff --git a/src/constant/device-type.js b/src/constant/device-type.js
index 1768fb0..350c85b 100644
--- a/src/constant/device-type.js
+++ b/src/constant/device-type.js
@@ -39,34 +39,8 @@
   }
 }
 
-// fixeme 2024.8.19 鍚庣画璁惧缂栧彿搴旇浠庢湇鍔″櫒鍔ㄦ�佽幏鍙栵紝鍚屾椂鏈夎澶囧彿鐨勫湪绾跨紪杈戝姛鑳�
-function deviceList(type) {
-  if (import.meta.env.VITE_DATA_MODE == 'jingan') {
-    return [
-      {
-        label: '杞﹁浇璁惧1鍙�',
-        value: 'TX105'
-      }
-    ];
-  } else {
-    const t = type ? type : '0a';
-    const _typeList = typeList();
-    const typeStr = _typeList.find((v) => {
-      return v.value == t;
-    });
-    return [1, 2, 3].map((v) => {
-      const label = `${typeStr.label}璁惧${v}鍙穈;
-      const value = `${t}000000000${v}`;
-      return {
-        label: label,
-        value: value
-      };
-    });
-  }
-}
-
 function typeName(type) {
   return typeList().find((v) => v.value == type).label;
 }
 
-export { TYPE0, TYPE1, TYPE2, TYPE3, TYPE4, typeList, typeName, deviceList };
+export { TYPE0, TYPE1, TYPE2, TYPE3, TYPE4, typeList, typeName };
diff --git a/src/constant/pollution-degree.js b/src/constant/pollution-degree.js
new file mode 100644
index 0000000..d5d6ead
--- /dev/null
+++ b/src/constant/pollution-degree.js
@@ -0,0 +1,30 @@
+function pollutionList() {
+  return [
+    {
+      label: '浼�',
+      value: 1
+    },
+    {
+      label: '鑹�',
+      value: 2
+    },
+    {
+      label: '杞诲害姹℃煋',
+      value: 3
+    },
+    {
+      label: '涓害姹℃煋',
+      value: 4
+    },
+    {
+      label: '閲嶅害姹℃煋',
+      value: 5
+    }
+  ];
+}
+
+function pollutionName(type) {
+  return pollutionList().find((v) => v.value == type).label;
+}
+
+export { pollutionList, pollutionName };
diff --git a/src/model/SatelliteGrid.js b/src/model/SatelliteGrid.js
index e2ef06d..af3f0f7 100644
--- a/src/model/SatelliteGrid.js
+++ b/src/model/SatelliteGrid.js
@@ -1,3 +1,4 @@
+import { reactive } from 'vue';
 import calculate from '@/utils/map/calculate';
 import gridMapUtil from '@/utils/map/grid';
 import { map, onMapMounted } from '@/utils/map/index_old';
@@ -18,14 +19,13 @@
 
   // 榛樿鍦板浘缃戞牸鐩稿叧瀵硅薄
   mapViews;
-
+  gridState;
   gridDataDetail;
 
   // 鍦板浘缃戞牸瀵硅薄Map缁撴瀯锛屽瓨鍌ㄥ搴攌ey涓嬬殑缃戞牸瀵硅薄銆佺綉鏍煎潗鏍囦俊鎭�
   mapViewsMap = new Map();
-
+  // 鍦板浘缃戞牸瀵硅薄鐨勫弬鏁扮姸鎬�
   gridStateMap = new Map();
-
   // 缃戞牸鏁版嵁Map缁撴瀯锛屽瓨鍌ㄥ搴攌ey涓嬬殑缃戞牸鐩戞祴鏁版嵁淇℃伅
   gridDataDetailMap = new Map();
 
@@ -273,7 +273,7 @@
       map.remove(lastGridViews);
     }
     map.add(resGridViews);
-    // map.setFitView(resGridViews);
+    map.setFitView(resGridViews);
 
     return { resGridViews, pointsRes };
   }
@@ -288,13 +288,20 @@
   // 缁樺埗缃戞牸閬ユ劅鏁版嵁鍊煎拰缃戞牸棰滆壊
   drawGrid({
     mapViews,
+    gridState,
     data,
-    grid = { useCustomColor: false, style: { opacity: 1, zIndex: 11 } },
+    grid = {
+      useCustomColor: false,
+      style: { opacity: 1, zIndex: 11, isMixGridHighlight: false }
+    },
     dataTxt = { isShow: false, useCustomColor: false, useColor: false },
     rankTxt = { isShow: false }
   }) {
+    // 纭畾缁樺埗鐨勭綉鏍肩粍鍙婂叾鍙傛暟瀵硅薄
     const _mapViews = mapViews ? mapViews : this.mapViews;
-    this.gridDataDetail = data;
+    const _gridState = gridState ? gridState : this.gridState;
+
+    // 鏍规嵁缃戞牸鏁版嵁缁樺埗瀵瑰簲鐨勭綉鏍�
     const { resGridViews, pointsRes } = this.drawColor({
       gridViews: _mapViews.gridViews,
       points: _mapViews.points,
@@ -303,10 +310,17 @@
       customColor: grid.useCustomColor,
       style: grid.style
     });
+    // 淇濆瓨鍦板浘缃戞牸鍥惧舰瀵硅薄鍜岀粡绾害瀵硅薄
     _mapViews.lastGridViews = resGridViews;
     _mapViews.lastPoints = pointsRes;
+    // 鏇存柊缃戞牸鐘舵�佸弬鏁�
+    _gridState.showGrid = true;
+    _gridState.showGridCustomColor = false;
+    _gridState.gridOpacityValue = grid.style.opacity;
+    _gridState.gridZIndex = grid.style.zIndex;
+    _gridState.highlightFusionGrid = grid.style.isMixGridHighlight;
 
-    // 鏁版嵁鏍囪
+    // 缁樺埗鏁版嵁鏂囨湰
     const { textViews: dtv } = this.drawDataText(
       _mapViews.lastPoints,
       data,
@@ -315,7 +329,12 @@
       dataTxt.useColor
     );
     _mapViews.dataTxt = dtv;
+    // 鏇存柊鏁版嵁鏂囨湰鐘舵�佸弬鏁�
+    _gridState.showData = dataTxt.isShow;
+    _gridState.showDataColor = dataTxt.useColor;
+    _gridState.showDataCustomColor = dataTxt.useCustomColor;
 
+    // 缁樺埗鎺掑悕鏂囨湰
     const { textViews: rtv } = this.drawRankText(
       _mapViews.lastPoints,
       data,
@@ -323,6 +342,8 @@
       _mapViews.rankLayer
     );
     _mapViews.rankTxt = rtv;
+    // 鏇存柊鎺掑悕鏂囨湰鐘舵�佸弬鏁�
+    _gridState.showRank = rankTxt.isShow;
 
     if (dataTxt.isShow) {
       map.add(_mapViews.dataTxt);
@@ -350,13 +371,16 @@
     if (!this.mapViewsMap.has(tag)) {
       const newMapViews = this._createNewMapViews({ extData });
       const newGridState = this._createNewGridState({ extData });
-      this.gridStateMap.set(tag, newGridState);
       this.mapViewsMap.set(tag, newMapViews);
+      map.on('zoomend', newMapViews.mapZoomEvent);
+      this.gridStateMap.set(tag, newGridState);
       this.gridDataDetailMap.set(tag, data);
     }
     const _mapViews = this.mapViewsMap.get(tag);
+    const _gridState = this.gridStateMap.get(tag);
     this.drawGrid({
       mapViews: _mapViews,
+      gridState: _gridState,
       data,
       grid,
       dataTxt,
@@ -364,6 +388,10 @@
     });
   }
 
+  /**
+   * 鏍规嵁鏍囩鍒犻櫎瀵瑰簲鐨勭綉鏍硷紝鍚屾椂绉婚櫎鍦板浘缂╂斁浜嬩欢绛�
+   * @param {*} tags 缃戞牸鏍囩鏁扮粍
+   */
   deleteTagGrid(tags) {
     this.changeVisibility({
       tags,
@@ -372,6 +400,8 @@
       showRankTxt: false
     });
     tags.forEach((t) => {
+      const { mapZoomEvent } = this.mapViewsMap.get(t);
+      map.off('zoomend', mapZoomEvent);
       this.mapViewsMap.delete(t);
       this.gridDataDetailMap.delete(t);
       this.gridStateMap.delete(t);
@@ -380,61 +410,56 @@
 
   // 璋冩暣鍚勭被鍦板浘瑕嗙洊鐗╃殑鍙鎬�
   changeVisibility({ tags = [], showGridViews, showDataTxt, showRankTxt }) {
-    let { _mapViewsList } = this._getMapViews(...tags);
+    let { _mapViewsList, _gridStateList } = this._getMapViews(...tags);
 
     if (showGridViews != undefined) {
       if (showGridViews) {
-        // map.add(this.mapViews.lastGridViews);
-        _mapViewsList.forEach((v) => {
+        _mapViewsList.forEach((v, i) => {
           if (v.lastGridViews) {
             map.add(v.lastGridViews);
-            v.show = true;
+            _gridStateList[i].showGrid = true;
           }
         });
       } else {
-        // map.remove(this.mapViews.lastGridViews);
-        _mapViewsList.forEach((v) => {
+        _mapViewsList.forEach((v, i) => {
           if (v.lastGridViews) {
             map.remove(v.lastGridViews);
-            v.show = false;
+            _gridStateList[i].showGrid = false;
+            _gridStateList[i].showHeatMap = false;
           }
         });
       }
     }
     if (showDataTxt != undefined) {
       if (showDataTxt) {
-        // map.add(this.mapViews.dataTxt);
-        _mapViewsList.forEach((v) => {
+        _mapViewsList.forEach((v, i) => {
           if (v.dataTxt) {
             map.add(v.dataTxt);
-            v.showData = true;
+            _gridStateList[i].showData = true;
           }
         });
       } else {
-        // map.remove(this.mapViews.dataTxt);
-        _mapViewsList.forEach((v) => {
+        _mapViewsList.forEach((v, i) => {
           if (v.dataTxt) {
             map.remove(v.dataTxt);
-            v.showData = false;
+            _gridStateList[i].showData = false;
           }
         });
       }
     }
     if (showRankTxt != undefined) {
       if (showRankTxt) {
-        // map.add(this.mapViews.rankTxt);
-        _mapViewsList.forEach((v) => {
+        _mapViewsList.forEach((v, i) => {
           if (v.rankTxt) {
             map.add(v.rankTxt);
-            v.showRank = true;
+            _gridStateList[i].showRank = true;
           }
         });
       } else {
-        // map.remove(this.mapViews.rankTxt);
-        _mapViewsList.forEach((v) => {
+        _mapViewsList.forEach((v, i) => {
           if (v.rankTxt) {
             map.remove(v.rankTxt);
-            v.showRank = false;
+            _gridStateList[i].showRank = false;
           }
         });
       }
@@ -442,9 +467,11 @@
   }
 
   changeGridOpacity({ tag, isOpacity, opacityValue }) {
-    let { _mapViewsList } = tag ? this._getMapViews(tag) : this._getMapViews();
+    let { _mapViewsList, _gridStateList } = tag
+      ? this._getMapViews(tag)
+      : this._getMapViews();
 
-    _mapViewsList.forEach((v) => {
+    _mapViewsList.forEach((v, i) => {
       if (v.lastGridViews) {
         v.lastGridViews.forEach((e) => {
           e.setOptions({
@@ -456,6 +483,7 @@
                   : 0.7
           });
         });
+        _gridStateList[i].gridOpacityValue = opacityValue;
       }
     });
   }
@@ -468,19 +496,26 @@
     zIndex,
     isMixGridHighlight
   }) {
-    let { _mapViewsList, _gridDataDetailList } = this._getMapViews(...tags);
+    let { _mapViewsList, _gridDataDetailList, _gridStateList } =
+      this._getMapViews(...tags);
     if (_mapViewsList.length == _gridDataDetailList.length) {
       _mapViewsList.forEach((v, i) => {
         if (v.lastGridViews) {
-          if (useCustomColor != undefined) v.showCustomColor = useCustomColor;
           const lastGridDataDetail = _gridDataDetailList[i];
-          if (v.showCustomColor) {
+          const _gridState = _gridStateList[i];
+          if (useCustomColor != undefined)
+            _gridState.showGridCustomColor = useCustomColor;
+          if (useCustomColor) {
             gridMapUtil.drawGridColorCustom(
               v.lastGridViews,
               lastGridDataDetail,
               factorName ? factorName : this.selectedFactorType.name
             );
           } else {
+            if (opacity != undefined) _gridState.gridOpacityValue = opacity;
+            if (zIndex != undefined) _gridState.gridZIndex = zIndex;
+            if (isMixGridHighlight != undefined)
+              _gridState.highlightFusionGrid = isMixGridHighlight;
             gridMapUtil.drawGridColor(
               v.lastGridViews,
               lastGridDataDetail,
@@ -578,83 +613,6 @@
    * 閲嶅彔鐨勭綉鏍艰繘琛岀洃娴嬫暟鎹潎鍊艰绠楀苟閲嶆柊璁$畻瀵瑰簲棰滆壊锛屽舰鎴愭柊鐨勪竴缁勮瀺鍚堢綉鏍�
    * @param {Array} tags 闇�瑕佽瀺鍚堢殑缃戞牸鏍囩锛屽綋涓虹┖鏃讹紝榛樿铻嶅悎鎵�鏈夌綉鏍�
    */
-  mixGrid({ tags, isMixGridHighlight = true }) {
-    tags.sort((a, b) => {
-      return a < b ? -1 : 1;
-    });
-    const mixTag = tags.join('-');
-    if (this.mapViewsMap.has(mixTag)) {
-      this.changeVisibility({
-        tags: [mixTag],
-        showGridViews: true
-      });
-      this.changeGridColor({ tags: [mixTag], isMixGridHighlight });
-    } else {
-      // 鏍规嵁鏍囩tag锛岃幏鍙栧搴斿缁勭綉鏍兼暟鎹�
-      let { _gridDataDetailList } = this._getMapViews(...tags);
-      const _dataMap = new Map();
-      // 灏嗘瘡缁勬瘡涓綉鏍兼暟鎹寜鐓х綉鏍肩紪鍙疯繘琛屽垎绫伙紝鐩稿悓缃戞牸鐨勬暟缁勫綊闆嗚嚦涓�璧�
-      _gridDataDetailList.forEach((list) => {
-        list.forEach((gdd) => {
-          if (!_dataMap.has(gdd.cellId)) {
-            _dataMap.set(gdd.cellId, {
-              source: [],
-              res: {}
-            });
-          }
-          _dataMap.get(gdd.cellId).source.push(gdd);
-        });
-      });
-      // 璁$畻姣忎釜缃戞牸涓嬬殑鏁版嵁鍧囧��
-      const resGridDataDetail = [];
-      _dataMap.forEach((v, k) => {
-        let total = 0,
-          count = v.source.length;
-        v.source.forEach((s) => {
-          total += s.pm25;
-        });
-        v.res = {
-          isMixData: true,
-          groupId: v.source[0].groupId,
-          cellId: v.source[0].cellId,
-          pm25: count == 0 ? null : Math.round((total / count) * 10) / 10,
-          originData: v.source
-        };
-        // 鏁版嵁閲忚秴杩�1涓椂锛岃〃鏄庤缃戞牸鏁版嵁鏄瀺鍚堢殑锛屽睍绀洪珮浜殑鏍峰紡
-        if (count > 1) {
-          v.res.gridStyle = {
-            strokeWeight: 2,
-            strokeColor: '#23dad1'
-          };
-        }
-        resGridDataDetail.push(v.res);
-      });
-      // 閲嶆柊鎸夌収鐩戞祴鏁版嵁鎺掑簭骞舵爣璁版帓鍚�
-      resGridDataDetail.sort((a, b) => {
-        return b.pm25 - a.pm25;
-      });
-      resGridDataDetail.forEach((gdd, i) => {
-        gdd.rank = i + 1;
-      });
-
-      this.drawTagGrid({
-        tag: mixTag,
-        data: resGridDataDetail,
-        grid: {
-          style: {
-            isMixGridHighlight
-          }
-        },
-        extData: {
-          name: `璧拌埅铻嶅悎 - ${mixTag}`,
-          type: 1
-        }
-      });
-    }
-
-    return mixTag;
-  }
-
   mixGrid2({ tags, isMixGridHighlight = true, gridDataDetailList }) {
     tags.sort((a, b) => {
       return a < b ? -1 : 1;
@@ -696,6 +654,9 @@
 
   drawHeatGrid2(tag, headGridDataDetailList) {
     const heatTag = `heat-${tag}`;
+    if (this.gridStateMap.has(tag)) {
+      this.gridStateMap.get(tag).showHeatMap = true;
+    }
     if (this.mapViewsMap.has(heatTag)) {
       this.changeVisibility({
         tags: [heatTag],
@@ -834,12 +795,14 @@
 
   _getMapViews(...tags) {
     let _mapViewsList = [],
-      _gridDataDetailList = [];
+      _gridDataDetailList = [],
+      _gridStateList = [];
     if (tags.length > 0) {
       tags.forEach((tag) => {
         if (this.mapViewsMap.has(tag) && this.gridDataDetailMap.has(tag)) {
           _mapViewsList.push(this.mapViewsMap.get(tag));
           _gridDataDetailList.push(this.gridDataDetailMap.get(tag));
+          _gridStateList.push(this.gridStateMap.get(tag));
         }
       });
     } else {
@@ -849,16 +812,20 @@
       this.gridDataDetailMap.forEach((v) => {
         _gridDataDetailList.push(v);
       });
-      if (this.mapViews && this.gridDataDetail) {
+      this.gridStateMap.forEach((v) => {
+        _gridStateList.push(v);
+      });
+      if (this.mapViews && this.gridDataDetail && this.gridState) {
         _mapViewsList.push(this.mapViews);
         _gridDataDetailList.push(this.gridDataDetail);
+        _gridStateList.push(this.gridState);
       }
     }
 
-    return { _mapViewsList, _gridDataDetailList };
+    return { _mapViewsList, _gridDataDetailList, _gridStateList };
   }
 
-  _createNewMapViews({ extData }) {
+  _createNewMapViews({ tag, extData }) {
     return {
       gridViews: gridMapUtil.drawPolylines({
         points: this.mapViews.gridPoints,
@@ -867,22 +834,50 @@
       gridPoints: JSON.parse(JSON.stringify(this.mapViews.gridPoints)),
       points: JSON.parse(JSON.stringify(this.mapViews.points)),
       extData,
-      show: true
+      show: true,
+      mapZoomEvent: () => {
+        const zoomLevel = map.getZoom();
+        if (zoomLevel >= 16.5) {
+          this.changeGridOpacity({ tag, opacityValue: 0.1 });
+        } else {
+          this.changeGridOpacity({ tag, opacityValue: 1 });
+        }
+        // console.log('mapZoomEvent', map.getZoom());
+      }
     };
   }
 
   _createNewGridState({ extData }) {
     return {
-      type: undefined,
-      name: '',
-      showGrid: true,
-      showRank: false,
-      showData: false,
-      showCustomColor: false,
+      // 缃戞牸鍚嶇О
+      name: extData.name,
+      // 缃戞牸绫诲瀷锛�0锛氳蛋鑸綉鏍硷紱1锛氳蛋鑸瀺鍚堢綉鏍硷紱2锛氳蛋鑸儹鍔涘浘
+      type: extData.type,
+      // 缃戞牸鐩稿叧鍙傛暟
+      showGrid: true, // 鏄惁鏄剧ず缃戞牸
+      showGridCustomColor: false, // 鏄惁鏄剧ず瀵规瘮鑹�
+      gridOpacityValue: 1, // 缃戞牸閫忔槑搴�
+      gridZIndex: 11, // 缃戞牸z杞撮珮搴�
+
+      // 鎺掑悕鏂囨湰鐩稿叧鍙傛暟
+      showRank: false, // 鎺掑悕鏂囨湰鏄惁鏄剧ず
+
+      // 鏁版嵁鏂囨湰鐩稿叧鍙傛暟
+      showData: false, // 鏁版嵁鏂囨湰鏄惁鏄剧ず
+      showDataColor: false, // 鏁版嵁鏂囨湰鏄惁浣跨敤瀵瑰簲鐩戞祴鍥犲瓙鐨勬爣鍑嗛鑹�
+      showDataCustomColor: false, // 鏁版嵁鏂囨湰鏄惁浣跨敤瀵瑰簲鐩戞祴鍥犲瓙鐨勫姣旈鑹�
+
+      // 鏄惁鏄剧ず瀵瑰簲鐑姏鍥�
       showHeatMap: false,
+
+      // 濡傛灉鏄瀺鍚堢綉鏍硷紝鏄惁楂樹寒鏄剧ず閲嶅彔缃戞牸
       highlightFusionGrid: false,
+
+      // 濡傛灉鏄櫘閫氱殑璧拌埅缃戞牸锛屾槸鍚︽樉绀哄搴旂殑璧拌埅杞ㄨ抗
       showUnderway: false,
-      opacityValue: 1
+
+      // 鍙兘鐨勮嚜瀹氫箟棰濆鏁版嵁
+      extData
     };
   }
 }
diff --git a/src/stores/fusion-data.js b/src/stores/fusion-data.js
new file mode 100644
index 0000000..ac4d502
--- /dev/null
+++ b/src/stores/fusion-data.js
@@ -0,0 +1,60 @@
+// import { ref } from 'vue';
+// import { defineStore } from 'pinia';
+// import gridApi from '@/api/gridApi';
+// import { useFetchData } from '@/composables/fetchData';
+
+// // 璧拌埅浠诲姟
+// export const useMissionStore = defineStore('mission', () => {
+//   const missionList = ref([]);
+//   const { loading, fetchData } = useFetchData(1000);
+
+//   function fetchMission(type) {
+//     return fetchData((page, pageSize) => {
+//       return missionApi
+//         .fethchMission({ type: type, page, pageSize })
+//         .then((res) => {
+//           missionList.value = res.data;
+//           return res;
+//         });
+//     });
+//   }
+
+//   function fetchFusionData() {
+//     return fetchData((page, pageSize) => {
+//        missionApi
+//         .fethchMission({ type: type, page, pageSize })
+//         .then((res) => {
+//           missionList.value = res.data;
+//           return res;
+//         });
+
+//         return  gridApi
+//       .fetchGridData(props.groupId, undefined, 3)
+//       .then((res) => {
+//         fusionDataList.value = res.data;
+//       })
+//       .finally(() => (fusionLoading.value = false));
+//     });
+//   }
+
+//   function deleteMission(missionCode) {
+//     return fetchData(() => {
+//       return missionApi.deleteMission(missionCode).then((res) => {
+//         let index = -1;
+//         for (let i = 0; i < missionList.value.length; i++) {
+//           const e = missionList.value[i];
+//           if (e.missionCode == missionCode) {
+//             index = i;
+//             break;
+//           }
+//         }
+//         if (index != -1) {
+//           missionList.value.splice(index, 1);
+//         }
+//         return res;
+//       });
+//     });
+//   }
+
+//   return { missionList, loading, fetchMission, deleteMission };
+// });
diff --git a/src/styles/elementUI.scss b/src/styles/elementUI.scss
index e1d1401..96cfa18 100644
--- a/src/styles/elementUI.scss
+++ b/src/styles/elementUI.scss
@@ -20,12 +20,12 @@
 }
 
 .el-button-custom-light {
-  --el-button-bg-color: var(--font-color);
-  --el-button-text-color: var(--bg-color);
+  --el-button-bg-color: var(--select_color);
+  // --el-button-text-color: var(--bg-color);
   // --el-button-hover-text-color: var(--bg-color);
   // --el-button-hover-bg-color: var(--font-color);
-  --el-button-hover-bg-color: var(--select_color);
-  --el-button-border-color: var(--bg-color);
+  --el-button-hover-bg-color: transparent;
+  --el-button-border-color: var(--font-color);
   --el-button-active-border-color: transparent;
 }
 
diff --git a/src/views/underwaymix/UnderwayMixMode.vue b/src/views/underwaymix/UnderwayMixMode.vue
index fed5590..0fae038 100644
--- a/src/views/underwaymix/UnderwayMixMode.vue
+++ b/src/views/underwaymix/UnderwayMixMode.vue
@@ -158,6 +158,14 @@
               >
                 {{ '铻嶅悎鍒嗘瀽' }}
               </el-button>
+              <el-button
+                type="primary"
+                class="el-button-custom"
+                size="small"
+                @click="underwayMixDialogVisible = true"
+              >
+                {{ '铻嶅悎绠$悊' }}
+              </el-button>
               <!-- <CheckButton
                 active-text="铻嶅悎鍒嗘瀽"
                 :default-value="false"
@@ -180,12 +188,21 @@
         <!-- </el-row> -->
       </el-col>
     </el-row>
-    <GridStyleTool
-      :gridCtrls="gridCtrls"
-      @show-underway="handleUnderwayClick"
-      @on-delete="handleFusionDelete"
-    ></GridStyleTool>
   </el-row>
+  <GridStyleTool
+    class="style-tool"
+    :gridCtrls="gridCtrls"
+    @show-underway="handleUnderwayClick"
+    @on-delete="handleFusionDelete"
+  ></GridStyleTool>
+  <!-- <el-row class="m-t-2">
+    <FactorLegend class="m-t-2" @change="handleLegendTypeChange"></FactorLegend>
+  </el-row> -->
+  <UnderwayMixManage
+    :groupId="groupId"
+    v-model="underwayMixDialogVisible"
+    @onUpdated="fetchFusionData"
+  ></UnderwayMixManage>
   <!-- </div> -->
 </template>
 
@@ -194,12 +211,14 @@
 import moment from 'moment';
 import gridApi from '@/api/gridApi';
 import { SatelliteGrid } from '@/model/SatelliteGrid';
-import GridStyleTool from './component/GridStyleTool.vue';
 import { useGridStore } from '@/stores/grid-info';
 import { TYPE0 } from '@/constant/device-type';
 import { defaultOptions } from '@/constant/radio-options';
 import { useMessageBox } from '@/composables/messageBox';
 import { useCloned } from '@vueuse/core';
+
+import GridStyleTool from './component/GridStyleTool.vue';
+import UnderwayMixManage from './component/UnderwayMixManage.vue';
 
 const gridStore = useGridStore();
 
@@ -210,7 +229,7 @@
 const props = defineProps({
   groupId: {
     type: Number,
-    default: 3
+    default: 2
   }
 });
 const show = ref(true);
@@ -218,14 +237,18 @@
 // 鐩戞祴鍥犲瓙鐨勭被鍨嬬紪鍙�
 const factorType = ref(defaultOptions(TYPE0));
 satelliteGrid.setShowFactorType(toRaw(factorType.value));
+// const factor = computed(()=>{
+
+// })
 
 function handleFactorTypeChange(e, item) {
   factorType.value = item;
-  console.log(toRaw(factorType.value));
+  // console.log(toRaw(factorType.value));
   satelliteGrid.setShowFactorType(toRaw(factorType.value));
 }
 
 const mission = ref(undefined);
+const underwayMixDialogVisible = ref(false);
 
 const gridCellList = ref(undefined);
 const fusionData = ref(undefined);
@@ -393,38 +416,8 @@
     .finally(() => (fusionLoading.value = false));
 }
 
-// 妫�鏌ヨ蛋鑸暟鎹槸鍚﹀拰100绫崇綉鏍煎凡铻嶅悎
-// function checkUnderwayFusionResult() {
-//   const time = moment(mission.value.startTime).format('YYYY-MM-DD HH:mm:ss');
-//   gridApi.fetchGridData(props.groupId, time, 3).then((res) => {
-//     if (res.data.length > 0) {
-//       fusionData.value = res.data[0];
-//     } else {
-//       fusionData.value = undefined;
-//     }
-//   });
-// }
-
 function prepareGrid(gridInfo) {
-  satelliteGrid.gridPrepare(gridInfo, function (polygon) {
-    // const originOption = polygon.getOptions();
-    // //榧犳爣绉诲叆浜嬩欢
-    // polygon.on('mouseover', () => {
-    //   polygon.setOptions({
-    //     //淇敼澶氳竟褰㈠睘鎬х殑鏂规硶
-    //     strokeWeight: 2,
-    //     strokeColor: 'red'
-    //   });
-    // });
-    // //榧犳爣绉诲嚭浜嬩欢
-    // polygon.on('mouseout', () => {
-    //   // polygon.setOptions({
-    //   //   strokeWeight: originOption.strokeWeight,
-    //   //   strokeColor: originOption.strokeColor
-    //   // });
-    //   polygon.setOptions(originOption);
-    // });
-  });
+  satelliteGrid.gridPrepare(gridInfo);
 }
 
 watch(
@@ -462,7 +455,7 @@
           tag: d.id,
           data: gdd,
           extData: {
-            name: `璧拌埅缃戞牸 - ${d.mixDataId}`,
+            name: `璧拌埅缃戞牸 - ${d.missionCode}`,
             type: 0
           }
         });
@@ -523,7 +516,7 @@
   } else {
     const d = fusionDataList.value.find((v) => v.id == dataId);
     const mission = missionStore.missionList.find((v) => {
-      return v.missionCode == d.mixDataId;
+      return v.missionCode == d.missionCode;
     });
     mapLine.hideLine(mission.missionCode);
     done();
@@ -578,7 +571,7 @@
         tag: d.id,
         data: gdd,
         extData: {
-          name: `璧拌埅缃戞牸 - ${d.mixDataId}`,
+          name: `璧拌埅缃戞牸 - ${d.missionCode}`,
           type: 0
         }
       });
@@ -602,7 +595,7 @@
           tag: d.id,
           data: gdd,
           extData: {
-            name: `璧拌埅缃戞牸 - ${d.mixDataId}`,
+            name: `璧拌埅缃戞牸 - ${d.missionCode}`,
             type: 0
           }
         });
@@ -679,7 +672,7 @@
   if (isUnmounted.value) return Promise.resolve();
   const d = fusionDataList.value.find((v) => v.id == dataId);
   const mission = missionStore.missionList.find((v) => {
-    return v.missionCode == d.mixDataId;
+    return v.missionCode == d.missionCode;
   });
 
   if (factorDataMap.has(mission.missionCode)) {
@@ -707,6 +700,12 @@
 }
 </script>
 <style scoped>
+.style-tool {
+  position: absolute;
+  top: 0;
+  right: 0;
+}
+
 :deep(.el-table) {
   --el-table-bg-color: transparent;
   --el-table-row-hover-bg-color: var(--select_color);
diff --git a/src/views/underwaymix/component/GridStyleTool.vue b/src/views/underwaymix/component/GridStyleTool.vue
index 0c5b79d..bfa9951 100644
--- a/src/views/underwaymix/component/GridStyleTool.vue
+++ b/src/views/underwaymix/component/GridStyleTool.vue
@@ -9,135 +9,142 @@
         ></CardButton>
       </el-row>
     </el-col>
+
     <el-col span="2">
       <BaseCard v-show="show" direction="right" borderless="r">
         <template #content>
           <el-scrollbar class="content-wrap">
-            <div v-for="(g, i) in gridCtrlList" :key="i">
-              <span>{{ g.name }}</span>
-              <div v-for="(value, t) in g.views" :key="t">
-                <el-row justify="space-between" align="middle">
-                  <div>
-                    <span v-if="value.extData.type == 0">{{
-                      value.tag + '.'
-                    }}</span>
-                    {{ value.extData.name }}
-                  </div>
-                  <el-button
-                    class="el-button-custom"
-                    type="primary"
-                    icon="Close"
-                    circle
-                    size="small"
-                    @click="handleCloseClick(i, t, value)"
-                  />
-                  <!-- <el-icon><Close /></el-icon> -->
-                </el-row>
+            <el-tabs
+              class="grid-ctrl-card"
+              v-model="activeGridCtrl"
+              type="border-card"
+            >
+              <el-tab-pane
+                v-for="(g, i) in gridCtrlList"
+                :key="g.name"
+                :label="g.name"
+                :name="i"
+              >
+                <el-tabs v-model="activeGrid">
+                  <el-tab-pane
+                    v-for="(grid, y) in g.views"
+                    :key="y"
+                    :label="grid.name"
+                    :name="y"
+                  >
+                    <div v-for="(value, t) in grid.views" :key="t">
+                      <el-row justify="space-between" align="middle">
+                        <div>
+                          <span v-if="value.type == 0">{{
+                            value.tag + '.'
+                          }}</span>
+                          {{ value.name }}
+                        </div>
+                        <el-button
+                          class="el-button-custom"
+                          type="primary"
+                          icon="Close"
+                          circle
+                          size="small"
+                          @click="handleCloseClick(i, y, t, value)"
+                        />
+                      </el-row>
 
-                <!-- {{ key }} -->
-                <!-- <el-text>{{ g.name }}</el-text> -->
-                <!-- <div class="m-t-8">缃戞牸瑕佺礌</div> -->
-                <el-row class="m-t-8" justify="space-between">
-                  <!-- <el-button
-                    type="primary"
-                    class="el-button-custom"
-                    size="small"
-                    @click="gridLoading = !gridLoading"
-                  >
-                    {{ '铻嶅悎鍒嗘瀽' }}
-                  </el-button> -->
-                  <CheckButton
-                    :loading="gridLoading"
-                    v-model="value.show"
-                    active-text="鏄剧ず缃戞牸"
-                    inactive-text="闅愯棌缃戞牸"
-                    @change="(e) => handleGridClick(e, i, value)"
-                  >
-                  </CheckButton>
-                  <CheckButton
-                    :loading="rankLoading"
-                    v-model="value.showRank"
-                    active-text="鏄剧ず鎺掑悕"
-                    inactive-text="闅愯棌鎺掑悕"
-                    :default-value="false"
-                    @change="(e) => handleRankClick(e, i, value)"
-                  >
-                  </CheckButton>
-                  <CheckButton
-                    :loading="dataLoading"
-                    v-model="value.showData"
-                    active-text="鏄剧ず鏁版嵁"
-                    inactive-text="闅愯棌鏁版嵁"
-                    :default-value="false"
-                    @change="(e) => handleDataClick(e, i, value)"
-                  >
-                  </CheckButton>
-                </el-row>
-                <el-row class="m-t-8" justify="space-between">
-                  <CheckButton
-                    :loading="colorLoading"
-                    v-model="value.showCustomColor"
-                    active-text="缁樺埗瀵规瘮鑹�"
-                    inactive-text="缁樺埗鏍囧噯鑹�"
-                    :default-value="false"
-                    @change="(e) => handleColorClick(e, i, value)"
-                  >
-                  </CheckButton>
-                  <CheckButton
-                    :loading="heatMapLoading"
-                    v-model="value.showHeatMap"
-                    active-text="椋庨櫓鐑姏鍥�"
-                    inactive-text="椋庨櫓鐑姏鍥�"
-                    :default-value="false"
-                    @change="(e) => handleHeatMapClick(e, i, value)"
-                  >
-                  </CheckButton>
-                  <CheckButton
-                    :loading="underwayLoading"
-                    v-if="value.extData.type == 0"
-                    v-model="value.showUnderway"
-                    active-text="鏄剧ず璧拌埅杞ㄨ抗"
-                    inactive-text="闅愯棌璧拌埅杞ㄨ抗"
-                    :default-value="false"
-                    @change="(e) => handleUnderwayClick(e, i, value)"
-                  >
-                  </CheckButton>
-                  <CheckButton
-                    :loading="highlightLoading"
-                    v-if="value.extData.type == 1"
-                    v-model="value.highlightFusionGrid"
-                    active-text="楂樹寒铻嶅悎缃戞牸"
-                    :default-value="true"
-                    @change="(e) => handleHighlightGridClick(e, i, value)"
-                  >
-                  </CheckButton>
-                </el-row>
-                <!-- <el-row class="m-b-8" align="middle"> -->
-                <el-form-item label="閫忔槑搴�">
-                  <!-- <div class="m-t-8">缃戞牸閫忔槑搴�</div> -->
-                  <el-slider
-                    v-model="value.opacityValue"
-                    :min="0"
-                    :max="1"
-                    :step="0.1"
-                    show-stops
-                    @change="(e) => handleOpacityChange(e, i, value)"
-                    style="width: 150px"
-                  />
-                  <el-input-number
-                    class="m-l-16"
-                    size="small"
-                    v-model="value.opacityValue"
-                    :min="0"
-                    :max="1"
-                    :step="0.1"
-                    @change="(e) => handleOpacityChange(e, i, value)"
-                  />
-                </el-form-item>
-                <!-- </el-row> -->
-                <el-divider />
-              </div>
-            </div>
+                      <el-row class="m-t-8" justify="space-between">
+                        <CheckButton
+                          :loading="gridLoading"
+                          v-model="value.showGrid"
+                          active-text="鏄剧ず缃戞牸"
+                          inactive-text="闅愯棌缃戞牸"
+                          @change="(e) => handleGridClick(e, i, value)"
+                        >
+                        </CheckButton>
+                        <CheckButton
+                          :loading="rankLoading"
+                          v-if="value.type != 2"
+                          v-model="value.showRank"
+                          active-text="鏄剧ず鎺掑悕"
+                          inactive-text="闅愯棌鎺掑悕"
+                          :default-value="false"
+                          @change="(e) => handleRankClick(e, i, value)"
+                        >
+                        </CheckButton>
+                        <CheckButton
+                          :loading="dataLoading"
+                          v-model="value.showData"
+                          active-text="鏄剧ず鏁版嵁"
+                          inactive-text="闅愯棌鏁版嵁"
+                          :default-value="false"
+                          @change="(e) => handleDataClick(e, i, value)"
+                        >
+                        </CheckButton>
+                      </el-row>
+                      <el-row class="m-t-8" justify="space-between">
+                        <CheckButton
+                          :loading="colorLoading"
+                          v-model="value.showGridCustomColor"
+                          active-text="缁樺埗瀵规瘮鑹�"
+                          inactive-text="缁樺埗鏍囧噯鑹�"
+                          :default-value="false"
+                          @change="(e) => handleColorClick(e, i, value)"
+                        >
+                        </CheckButton>
+                        <CheckButton
+                          :loading="heatMapLoading"
+                          v-if="value.type != 2"
+                          v-model="value.showHeatMap"
+                          active-text="椋庨櫓鐑姏鍥�"
+                          inactive-text="椋庨櫓鐑姏鍥�"
+                          :default-value="false"
+                          @change="(e) => handleHeatMapClick(e, i, value)"
+                        >
+                        </CheckButton>
+                        <CheckButton
+                          :loading="underwayLoading"
+                          v-if="value.type == 0"
+                          v-model="value.showUnderway"
+                          active-text="鏄剧ず璧拌埅杞ㄨ抗"
+                          inactive-text="闅愯棌璧拌埅杞ㄨ抗"
+                          :default-value="false"
+                          @change="(e) => handleUnderwayClick(e, i, value)"
+                        >
+                        </CheckButton>
+                        <CheckButton
+                          :loading="highlightLoading"
+                          v-if="value.type == 1"
+                          v-model="value.highlightFusionGrid"
+                          active-text="楂樹寒铻嶅悎缃戞牸"
+                          :default-value="true"
+                          @change="(e) => handleHighlightGridClick(e, i, value)"
+                        >
+                        </CheckButton>
+                      </el-row>
+                      <el-form-item label="閫忔槑搴�">
+                        <el-slider
+                          v-model="value.gridOpacityValue"
+                          :min="0"
+                          :max="1"
+                          :step="0.1"
+                          show-stops
+                          @change="(e) => handleOpacityChange(e, i, value)"
+                          style="width: 150px"
+                        />
+                        <el-input-number
+                          class="m-l-16"
+                          size="small"
+                          v-model="value.gridOpacityValue"
+                          :min="0"
+                          :max="1"
+                          :step="0.1"
+                          @change="(e) => handleOpacityChange(e, i, value)"
+                        />
+                      </el-form-item>
+                      <el-divider />
+                    </div>
+                  </el-tab-pane>
+                </el-tabs>
+              </el-tab-pane>
+            </el-tabs>
           </el-scrollbar>
         </template>
       </BaseCard>
@@ -148,7 +155,15 @@
 /**
  * 缃戞牸鏍峰紡鎺у埗宸ュ叿
  */
-import { ref, reactive, onMounted, onUnmounted, computed, toRaw } from 'vue';
+import {
+  ref,
+  reactive,
+  onMounted,
+  onUnmounted,
+  watch,
+  computed,
+  toRaw
+} from 'vue';
 import gridApi from '@/api/gridApi';
 import { useGridStore } from '@/stores/grid-info';
 
@@ -162,6 +177,9 @@
   }
 });
 
+const activeGridCtrl = ref(0);
+const activeGrid = ref(0);
+
 const gridLoading = ref(false);
 const rankLoading = ref(false);
 const dataLoading = ref(false);
@@ -172,34 +190,66 @@
 
 const emits = defineEmits(['showUnderway', 'onDelete']);
 
-const gridCtrlList = computed(() => {
-  return props.gridCtrls.map((g) => {
-    return reactive({
+const gridCtrlList = ref([]);
+
+const show = ref(false);
+
+watch(
+  () => props.gridCtrls,
+  (nV, oV) => {
+    if (nV) {
+      show.value = true;
+      refreshValue();
+    }
+  }
+);
+
+function refreshValue() {
+  gridCtrlList.value = props.gridCtrls.map((g) => {
+    const _tempMap = new Map();
+    if (g.gridState) {
+      _tempMap.set('鍗槦缃戞牸', [g.gridState]);
+    }
+    Array.from(g.gridStateMap).map((v) => {
+      const key = girdTypeToName(v[1].type);
+      if (!_tempMap.has(key)) {
+        _tempMap.set(key, []);
+      }
+      _tempMap.get(key).push({
+        tag: v[0],
+        ...v[1]
+      });
+    });
+    const res = {
       name: g.name,
-      views: Array.from(g.mapViewsMap).map((v) => {
+      views: Array.from(_tempMap).map((v) => {
         return {
-          tag: v[0],
-          extData: v[1].extData,
-          show: v[1].show,
-          showRank: v[1].showRank,
-          showData: v[1].showData,
-          showCustomColor: v[1].showCustomColor,
-          showHeatMap: v[1].showHeatMap,
-          highlightFusionGrid: true,
-          // ...v[1],
-          opacityValue: 1
+          name: v[0],
+          views: reactive(v[1])
         };
       })
-    });
+    };
+    console.log(res);
+
+    return res;
   });
-});
+}
 
-const show = ref(true);
+function girdTypeToName(type) {
+  switch (type) {
+    case 0:
+      return '璧拌埅缃戞牸';
+    case 1:
+      return '铻嶅悎缃戞牸';
+    case 2:
+      return '鐑姏鍥�';
+  }
+}
 
-function handleCloseClick(index, t, value) {
+function handleCloseClick(index, y, t, value) {
   const key = value.tag;
   toRaw(props.gridCtrls[index]).deleteTagGrid([key]);
-  gridCtrlList.value[index].views.splice(t, 1);
+  gridCtrlList.value[index].views[y].views.splice(t, 1);
   emits('onDelete', index, key);
 }
 
@@ -248,7 +298,7 @@
   // value.opacityValue = e;
   toRaw(props.gridCtrls[index]).changeGridOpacity({
     tag: key,
-    opacityValue: toRaw(value.opacityValue)
+    opacityValue: toRaw(value.gridOpacityValue)
   });
 }
 
@@ -263,7 +313,7 @@
 
 function handleHighlightGridClick(e, index, value) {
   highlightLoading.value = true;
-  toRaw(props.gridCtrls[index]).mixGrid({
+  toRaw(props.gridCtrls[index]).mixGrid2({
     tags: value.tag.split('-'),
     isMixGridHighlight: e
   });
@@ -280,13 +330,6 @@
     showDataTxt: false,
     showRankTxt: false
   });
-  // gridCtrlList.value.forEach((v) => {
-  //   v.views.forEach((view) => {
-  //     view.show = false;
-  //     view.showData = false;
-  //     view.showRank = false;
-  //   });
-  // });
   if (e) {
     const data = _satelliteGrid.gridDataDetailMap.get(value.tag);
     gridApi
@@ -298,16 +341,7 @@
       .then((res) => {
         heatTag = _satelliteGrid.drawHeatGrid2(value.tag, res.data);
         _satelliteGrid.setDefaultGridClickEvent([heatTag]);
-        // _satelliteGrid.setGridEvent(
-        //   [heatTag],
-        //   'click',
-        //   (gridCell, gridDataDetail) => {
-        //     gridStore.selectedGridCellAndDataDetail = {
-        //       gridCell,
-        //       gridDataDetail
-        //     };
-        //   }
-        // );
+        refreshValue();
       })
       .finally(() => (heatMapLoading.value = false));
   } else {
@@ -316,6 +350,7 @@
       showGridViews: true
     });
     heatMapLoading.value = false;
+    refreshValue();
   }
 }
 </script>
@@ -331,4 +366,44 @@
 :deep(.el-input-number) {
   width: 80px;
 }
+
+:deep(.el-tabs__item) {
+  color: rgba(221, 221, 221, 0.806);
+}
+:deep(.el-tabs__nav > .is-active) {
+  color: #f0ff1d;
+}
+:deep(.el-tabs--border-card) {
+  background: transparent;
+  border: 0px;
+}
+
+:deep(.grid-ctrl-card > .el-tabs__content) {
+  padding: 0;
+}
+:deep(.grid-ctrl-card > .el-tabs__header) {
+  background: transparent;
+  border-bottom: 0px solid var(--el-border-color-light);
+}
+:deep(
+    .grid-ctrl-card
+      > .el-tabs__header
+      .el-tabs__nav-wrap
+      .el-tabs__nav-scroll
+      .el-tabs__nav
+  ) {
+  border: 1px solid var(--el-border-color-light);
+}
+:deep(.grid-ctrl-card > .el-tabs__header .el-tabs__item.is-active) {
+  color: var(--font-color);
+  background-color: transparent;
+  border-right-color: transparent;
+  border-left-color: transparent;
+}
+:deep(.el-tabs--border-card > .el-tabs__header .el-tabs__item) {
+  margin-top: 0px;
+}
+:deep(.el-tabs--border-card > .el-tabs__header .el-tabs__item:first-child) {
+  margin-left: 0px;
+}
 </style>
diff --git a/src/views/underwaymix/component/UnderwayMixEdit.vue b/src/views/underwaymix/component/UnderwayMixEdit.vue
new file mode 100644
index 0000000..3867e31
--- /dev/null
+++ b/src/views/underwaymix/component/UnderwayMixEdit.vue
@@ -0,0 +1,197 @@
+<template>
+  <CardDialog
+    :title="dialogTitle"
+    :close-on-click-modal="!loading"
+    :model-value="modelValue"
+    @update:modelValue="(e) => $emit('update:modelValue', e)"
+  >
+    <el-form
+      :inline="false"
+      :model="formObj"
+      ref="formRef"
+      :rules="rules"
+      label-position="right"
+      label-width="100px"
+    >
+      <el-form-item label="鍖哄幙" prop="location">
+        <OptionLocation2
+          :disabled="true"
+          :level="3"
+          :initValue="false"
+          :checkStrictly="false"
+          :allOption="false"
+          v-model="formObj.location"
+        ></OptionLocation2>
+      </el-form-item>
+      <el-form-item label="浠诲姟缂栧彿" prop="missionCode">
+        <el-input
+          :disabled="true"
+          size="small"
+          clearable
+          v-model="formObj.missionCode"
+          placeholder="浠诲姟缂栧彿"
+          class="w-150"
+        />
+      </el-form-item>
+      <el-form-item label="鍖哄煙" prop="zone">
+        <el-input
+          size="small"
+          clearable
+          v-model="formObj.zone"
+          placeholder="鍖哄煙"
+          class="w-150"
+        />
+      </el-form-item>
+      <el-form-item label="姹℃煋鑳屾櫙" prop="pollutionDegreeIndex">
+        <OptionPollutionDegree
+          v-model="formObj.pollutionDegreeIndex"
+        ></OptionPollutionDegree>
+      </el-form-item>
+      <el-form-item>
+        <el-button
+          :disabled="!edit"
+          type="primary"
+          @click="onSubmit"
+          :loading="loading"
+          >鎻愪氦</el-button
+        >
+        <el-button @click="onCancel" :disabled="loading">鍙栨秷</el-button>
+      </el-form-item>
+    </el-form>
+  </CardDialog>
+</template>
+<script setup>
+import { ref, onMounted, reactive, computed, watch } from 'vue';
+import { useFormConfirm } from '@/composables/formConfirm';
+import { useFetchData } from '@/composables/fetchData';
+import gridApi from '@/api/gridApi';
+import { pollutionName } from '@/constant/pollution-degree';
+
+const props = defineProps({
+  modelValue: Boolean,
+  // 璧拌埅铻嶅悎缂栬緫妯″紡锛屾柊寤烘垨鏇存柊
+  mode: {
+    type: String,
+    default: 'create'
+  },
+  record: {
+    type: Object
+  },
+  groupId: {
+    type: Number,
+    default: 2
+  }
+});
+const emits = defineEmits(['update:modelValue', 'onSubmit']);
+
+const dialogTitle = computed(() => {
+  return `${props.mode == 'create' ? '鐢熸垚' : '淇敼'}璧拌埅铻嶅悎璁板綍`;
+});
+
+const { loading, fetchData } = useFetchData();
+const { formObj, formRef, edit, onSubmit, onCancel } = useFormConfirm({
+  submit: {
+    do: submitFusionRecord
+  },
+  cancel: {
+    do: () => {
+      emits('update:modelValue', false);
+    }
+  }
+});
+const rules = reactive({
+  location: [
+    {
+      required: true,
+      message: '鍖哄幙涓嶈兘涓虹┖',
+      trigger: 'change'
+    }
+  ],
+  missionCode: [
+    {
+      required: true,
+      message: '浠诲姟缂栧彿涓嶈兘涓虹┖',
+      trigger: 'blur'
+    }
+  ],
+  zone: [
+    {
+      required: true,
+      message: '鍖哄煙涓嶈兘涓虹┖',
+      trigger: 'change'
+    }
+  ],
+  pollutionDegreeIndex: [
+    {
+      required: true,
+      message: '姹℃煋鑳屾櫙涓嶈兘涓虹┖',
+      trigger: 'change'
+    }
+  ]
+});
+
+const param = computed(() => {
+  return {
+    provinceCode: formObj.value.location.pCode,
+    provinceName: formObj.value.location.pName,
+    cityCode: formObj.value.location.cCode,
+    cityName: formObj.value.location.cName,
+    districtCode: formObj.value.location.dCode,
+    districtName: formObj.value.location.dName,
+    townCode: formObj.value.location.tCode,
+    townName: formObj.value.location.tName,
+    missionCode: formObj.value.missionCode,
+    // Fixme 2025.4.15: 缃戞牸缁刬d闇�瑕侀�氳繃鏈嶅姟鍣ㄥ姩鎬佽幏鍙�
+    groupId: props.groupId,
+    zone: formObj.value.zone,
+    pollutionDegreeIndex: formObj.value.pollutionDegreeIndex,
+    pollutionDegree: pollutionName(formObj.value.pollutionDegreeIndex)
+  };
+});
+
+function submitFusionRecord() {
+  fetchData((page, pageSize) => {
+    return gridApi.buildUnderwayProduct(param.value).then((res) => {
+      emits('onSubmit', param.value.missionCode);
+      emits('update:modelValue', false);
+    });
+  });
+}
+
+// 鐩戝惉浼犲叆鐨勮蛋鑸瀺鍚堜俊鎭紝鍦ㄦ洿鏂版ā寮忎笅锛屽皢铻嶅悎淇℃伅鏄犲皠鍒拌〃鍗曚笂
+watch(
+  () => props.record,
+  (nV, oV) => {
+    if (nV && nV != oV) {
+      const r = nV;
+      // 鍏抽棴瀵硅瘽妗嗘椂锛岀洿鎺ヨ繑鍥�
+      if (!props.modelValue) {
+        return;
+      }
+      formObj.value.location = {
+        pCode: r.provinceCode,
+        pName: r.provinceName,
+        cCode: r.cityCode,
+        cName: r.cityName,
+        dCode: r.districtCode,
+        dName: r.districtName,
+        tCode: r.townCode,
+        tName: r.townName
+      };
+      formObj.value.missionCode = r.missionCode;
+      formObj.value.groupId = props.groupId;
+      formObj.value.zone = r.districtName;
+      formObj.value.pollutionDegreeIndex = undefined;
+      formObj.value.pollutionDegree = undefined;
+
+      if (r.fusionData && props.mode == 'update') {
+        const f = r.fusionData;
+        formObj.value.groupId = f.groupId;
+        formObj.value.zone = f.zone;
+        formObj.value.pollutionDegreeIndex = f.pollutionDegreeIndex;
+        formObj.value.pollutionDegree = f.pollutionDegree;
+      }
+    }
+  }
+);
+</script>
diff --git a/src/views/underwaymix/component/UnderwayMixManage.vue b/src/views/underwaymix/component/UnderwayMixManage.vue
new file mode 100644
index 0000000..b77c4b8
--- /dev/null
+++ b/src/views/underwaymix/component/UnderwayMixManage.vue
@@ -0,0 +1,214 @@
+<template>
+  <CardDialog
+    v-bind="$attrs"
+    title="璧拌埅铻嶅悎绠$悊"
+    :model-value="modelValue"
+    @update:modelValue="handleDialogChange"
+  >
+    <el-row class="mission-table">
+      <el-col :span="24">
+        <el-table
+          :data="missionStore.missionList"
+          table-layout="fixed"
+          size="small"
+          :show-overflow-tooltip="true"
+          border
+          height="64vh"
+          row-class-name="t-row-normal"
+          cell-class-name="t-cell"
+          header-row-class-name="t-header-row"
+          header-cell-class-name="t-header-cell"
+        >
+          <el-table-column
+            type="index"
+            label="搴忓彿"
+            align="center"
+            width="50"
+          />
+          <el-table-column prop="missionCode" label="浠诲姟缂栧彿" align="center" />
+          <el-table-column
+            prop="startTime"
+            label="寮�濮嬫椂闂�"
+            align="center"
+            :formatter="timeFormatter"
+            width="150"
+          />
+          <el-table-column
+            prop="endTime"
+            label="缁撴潫鏃堕棿"
+            align="center"
+            :formatter="timeFormatter"
+            width="150"
+          />
+          <el-table-column label="铻嶅悎璁板綍" align="center" width="50">
+            <template #default="{ row }">
+              {{ row.fusionData ? '鏈�' : '鏃�' }}
+            </template>
+          </el-table-column>
+          <el-table-column label="绠$悊" width="160" align="center">
+            <template #default="{ row }">
+              <el-button
+                v-if="row.fusionData"
+                type="primary"
+                size="small"
+                icon="EditPen"
+                class="el-button-custom-light"
+                :loading="row.loading"
+                @click="updateFusionRecord(row)"
+                >鏇存柊璁板綍</el-button
+              >
+              <el-button
+                v-else
+                type="primary"
+                size="small"
+                icon="Plus"
+                class="el-button-custom"
+                :loading="row.loading"
+                @click="createFusionRecord(row)"
+                >铻嶅悎鐢熸垚</el-button
+              >
+              <el-button
+                v-if="row.fusionData"
+                type="primary"
+                size="small"
+                icon="Delete"
+                class="el-button-custom"
+                :disabled="row.loading"
+                @click="deleteFusionRecord(row)"
+              ></el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-col>
+    </el-row>
+  </CardDialog>
+  <MessageBox
+    v-model="msgBoxVisible"
+    :on-confirm="onConfirm"
+    title="鍒犻櫎璧拌埅铻嶅悎"
+    msg="纭鏄惁鍒犻櫎璇ヨ蛋鑸瀺鍚�"
+    confirmText="鍒犻櫎"
+  ></MessageBox>
+  <UnderwayMixEdit
+    v-model="dialogVisible"
+    width="30%"
+    :group-id="groupId"
+    :mode="editMode"
+    :record="selectedFusionRecord"
+    @on-submit="refreshFusionRecord"
+  ></UnderwayMixEdit>
+</template>
+<script setup>
+import { ref, watch, onMounted } from 'vue';
+import moment from 'moment';
+import gridApi from '@/api/gridApi';
+import { useMissionStore } from '@/stores/mission';
+import { useFetchData } from '@/composables/fetchData';
+
+import UnderwayMixEdit from './UnderwayMixEdit.vue';
+
+const props = defineProps({
+  modelValue: Boolean,
+  groupId: {
+    type: Number,
+    default: 2
+  }
+});
+
+const emits = defineEmits(['update:modelValue', 'onUpdated']);
+
+const { loading, fetchData } = useFetchData();
+const missionStore = useMissionStore();
+
+const isEdited = ref(false);
+const editMode = ref('create');
+const selectedFusionRecord = ref(undefined);
+const dialogVisible = ref(false);
+const msgBoxVisible = ref(false);
+const onConfirm = ref(undefined);
+
+// eslint-disable-next-line no-unused-vars
+function timeFormatter(row, col, cellValue, index) {
+  return moment(cellValue).format('YYYY-MM-DD HH:mm:ss');
+}
+
+// 鏌ヨ璧拌埅铻嶅悎璁板綍
+function fetchFusionRecord(row) {
+  row.loading = true;
+  gridApi
+    .fetchGridData2({ type: 3, missionCode: row.missionCode })
+    .then((res) => {
+      // 璧拌埅缃戞牸铻嶅悎锛屽崟涓蛋鑸湪鍦ㄥ崟涓綉鏍肩粍涓彧鏈夊敮涓�璁板綍
+      if (res.data.length > 0) {
+        row.fusionData = res.data[0];
+      } else {
+        row.fusionData = undefined;
+      }
+    })
+    .finally(() => (row.loading = false));
+}
+
+function createFusionRecord(row) {
+  editMode.value = 'create';
+  selectedFusionRecord.value = row;
+  dialogVisible.value = true;
+  // row.loading = true;
+  // setTimeout(() => {
+  //   row.loading = false;
+  // }, 1000);
+}
+
+function updateFusionRecord(row) {
+  editMode.value = 'update';
+  selectedFusionRecord.value = row;
+  dialogVisible.value = true;
+}
+
+function deleteFusionRecord(row) {
+  this.onConfirm = () => {
+    gridApi.deleteGridData(row.fusionData.id).then((res) => {
+      if (res.data) {
+        refreshFusionRecord(row.missionCode);
+      }
+    });
+  };
+  this.msgBoxVisible = true;
+}
+
+function refreshFusionRecord(missionCode) {
+  const m = missionStore.missionList.find((v) => v.missionCode == missionCode);
+  fetchFusionRecord(m);
+  isEdited.value = true;
+}
+
+function handleDialogChange(e) {
+  emits('update:modelValue', e);
+  if (isEdited.value) {
+    emits('onUpdated');
+  }
+}
+
+watch(
+  () => props.modelValue,
+  (nV, oV) => {
+    if (nV) {
+      isEdited.value = false;
+      missionStore.missionList.forEach((v) => {
+        fetchFusionRecord(v);
+      });
+    }
+  }
+);
+</script>
+<style scoped>
+.flex-col {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  align-items: flex-end;
+}
+
+:deep(.t-row-normal) {
+  background-color: transparent !important;
+}
+</style>

--
Gitblit v1.9.3