From 2d3d56ff801b73afdb779267004d740f9beafe57 Mon Sep 17 00:00:00 2001
From: riku <risaku@163.com>
Date: 星期二, 31 十月 2023 16:09:44 +0800
Subject: [PATCH] 2023.10.31

---
 src/assets/shortcut.css                                           |   16 
 src/components/button/CloseButton.vue                             |   36 +
 src/model/clueQuestion.js                                         |    2 
 src/api/clue/clueConclusionApi.js                                 |    2 
 src/components.d.ts                                               |   10 
 src/components/search-option/OptionTime.vue                       |    4 
 src/views/HomePage.vue                                            |   12 
 src/views/overlay-clue/report/ClueReport.vue                      |  107 +++
 src/composables/formConfirm.js                                    |   21 
 src/views/overlay-clue/list/ClueManage.vue                        |   80 ++
 src/views/overlay-grid/components/ListGridDetail.vue              |    6 
 src/api/clue/clueQuestionApi.js                                   |    2 
 src/views/overlay-clue/report/components/ClueReportClue.vue       |   51 +
 src/api/index.js                                                  |   33 
 src/api/grid/gridInfoApi.js                                       |   31 
 src/components/map/baseMapUtil.js                                 |   55 +
 src/assets/text.css                                               |    4 
 src/views/overlay-grid/components/GridCreate.vue                  |   96 +-
 vite.config.js                                                    |    3 
 src/api/config.js                                                 |   82 ++
 src/api/clue/clueApi.js                                           |   30 
 src/views/overlay-clue/list/components/ClueList.vue               |  138 ++++
 src/assets/main.css                                               |   28 
 src/views/overlay-grid/components/OptionGridRecord.vue            |   48 
 src/model/gridRecord.js                                           |   28 
 src/components/list/DescriptionsList.vue                          |   31 
 src/views/overlay-grid/components/SchemeCreate.vue                |  101 ++
 src/components/list/DescriptionsListItem.vue                      |   36 +
 src/components/map/mapGrid.js                                     |    6 
 src/utils/textFormat.js                                           |   19 
 src/views/overlay-clue/report/components/ClueReportQuestion.vue   |   76 -
 /dev/null                                                         |  104 ---
 src/stores/grid.js                                                |   37 
 src/views/overlay-clue/report/components/ClueReportConclusion.vue |   58 
 src/assets/base.css                                               |   70 -
 src/main.js                                                       |    5 
 src/views/overlay-grid/components/GridEditing.vue                 |   94 ++
 src/composables/messageBox.js                                     |   48 
 src/views/overlay-clue/report/components/QuestionDetail.vue       |   84 +
 src/components/core/CoreHeader.vue                                |   44 +
 src/api/grid/gridSchemeApi.js                                     |   18 
 src/assets/layout.css                                             |   56 +
 src/views/overlay-grid/GridLayout.vue                             |   99 +-
 src/views/overlay-clue/ClueLayout.vue                             |  115 --
 44 files changed, 1,432 insertions(+), 594 deletions(-)

diff --git a/src/api/clue/clueApi.js b/src/api/clue/clueApi.js
index 0ce8f48..82e565a 100644
--- a/src/api/clue/clueApi.js
+++ b/src/api/clue/clueApi.js
@@ -1,23 +1,43 @@
-import { $clue } from './index';
+import { $clue } from '../index';
 
 export default {
   /**
    * 鏌ヨ绾跨储娓呭崟
-   * @param {object} param0 
-   * @returns 
+   * @param {object} param0
+   * @returns
    */
   getClue({ sTime, eTime, pageNum = 1, pageSize = 30 }) {
+    let url = 'clue/fetch?';
+    if (sTime) {
+      url += `sTime=${sTime}&`;
+    }
+    if (eTime) {
+      url += `eTime=${eTime}&`;
+    }
     return $clue.get(
-      `clue/fetch?sTime=${sTime}&eTime=${eTime}&pageNum=${pageNum}&pageSize=${pageSize}`
+      `${url}pageNum=${pageNum}&pageSize=${pageSize}`
     );
   },
 
   /**
    * 浠庣涓夋柟杩滅▼鎷夊彇绾跨储娓呭崟
    * @param {string} updateTime 鏇存柊鏃堕棿锛岃幏鍙栬鏃堕棿涔嬪悗鐨勭嚎绱�
-   * @returns 
+   * @returns
    */
   fetchRemoteClue(updateTime) {
     return $clue.get(`clue/fetch/remote?updateTime=${updateTime}`);
+  },
+
+  fetchRemoteClueFileUrl(clueId) {
+    return `${$clue.defaults.baseURL}clue/fetch/remote/file?clueId=${clueId}`;
+  },
+
+  /**
+   * 鎺ㄩ�佺嚎绱㈢殑缁撹涓庨棶棰�
+   * @param {string} clueId 
+   * @returns 
+   */
+  pushClue(clueId) {
+    return $clue.post(`clue/push?clueId=${clueId}`);
   }
 };
diff --git a/src/api/clue/clueConclusionApi.js b/src/api/clue/clueConclusionApi.js
index ede0d44..9a4a076 100644
--- a/src/api/clue/clueConclusionApi.js
+++ b/src/api/clue/clueConclusionApi.js
@@ -1,4 +1,4 @@
-import { $clue } from './index';
+import { $clue } from '../index';
 
 export default {
   /**
diff --git a/src/api/clue/clueQuestionApi.js b/src/api/clue/clueQuestionApi.js
index 4f820d5..8d04cf6 100644
--- a/src/api/clue/clueQuestionApi.js
+++ b/src/api/clue/clueQuestionApi.js
@@ -1,4 +1,4 @@
-import { $clue } from './index';
+import { $clue } from '../index';
 import { getClueQuestionList } from '@/model/clueQuestion';
 
 export default {
diff --git a/src/api/clue/index.js b/src/api/clue/index.js
deleted file mode 100644
index 678789c..0000000
--- a/src/api/clue/index.js
+++ /dev/null
@@ -1,105 +0,0 @@
-import axios from 'axios';
-import md5 from 'md5';
-import { ElMessage } from 'element-plus';
-
-const url = 'http://47.100.191.150:9030/';
-// const url = 'http://192.168.1.9:8080/';
-const imgUrl = url + 'images/';
-
-//椋炵窘鐩戠
-const $clue = axios.create({
-  baseURL: url,
-  timeout: 10000
-  // headers: addHeaders()
-});
-// console.log($clue);
-
-function getHeaders() {
-  const token = 'e6dc8bb9e1ff0ce973fb92b4af2e4c3f';
-
-  const date = new Date();
-  const timestamp = parseInt(date.getTime() / 1000) - 200;
-
-  const sign = md5(timestamp + token);
-
-  // headers['JA-TIMESTAMP'] = timestamp;
-  // headers['JA-SIGN'] = sign;
-  // headers['JA-TOKEN'] = token;
-
-  return {
-    'JA-TIMESTAMP': timestamp,
-    'JA-SIGN': sign,
-    'JA-TOKEN': token
-  };
-}
-
-//娣诲姞鎷︽埅鍣�
-[$clue].forEach((i) => {
-  // 娣诲姞璇锋眰鎷︽埅鍣�
-  i.interceptors.request.use(
-    function (config) {
-      // 鍦ㄥ彂閫佽姹備箣鍓�, 娣诲姞璇锋眰澶�
-      // config.headers = addHeaders(config.headers);
-
-      console.log('==>璇锋眰寮�濮�');
-      console.log(`${config.baseURL}${config.url}`);
-      if (config.data) {
-        console.log('==>璇锋眰鏁版嵁', config.data);
-      }
-      return config;
-    },
-    function (error) {
-      // 瀵硅姹傞敊璇仛浜涗粈涔�
-      console.log('==>璇锋眰寮�濮�');
-      console.log(error);
-      ElMessage({
-        message: error,
-        type: 'error'
-      });
-      return Promise.reject(error);
-    }
-  );
-
-  // 娣诲姞鍝嶅簲鎷︽埅鍣�
-  i.interceptors.response.use(
-    function (response) {
-      // 2xx 鑼冨洿鍐呯殑鐘舵�佺爜閮戒細瑙﹀彂璇ュ嚱鏁般��
-      // 瀵瑰搷搴旀暟鎹仛鐐逛粈涔�
-      console.log(response);
-      console.log('==>璇锋眰缁撴潫');
-      if (response.status == 200) {
-        if (
-          response.data.success != undefined &&
-          response.data.success != null
-        ) {
-          if (response.data.success == true) {
-            return response.data.data;
-          } else {
-            ElMessage({
-              message: response.data.message,
-              type: 'error'
-            });
-            return Promise.reject(response.data.message);
-          }
-        } else {
-          return response;
-        }
-      } else {
-        return Promise.reject(response);
-      }
-    },
-    function (error) {
-      // 瓒呭嚭 2xx 鑼冨洿鐨勭姸鎬佺爜閮戒細瑙﹀彂璇ュ嚱鏁般��
-      // 瀵瑰搷搴旈敊璇仛鐐逛粈涔�
-      console.log(error);
-      console.log('==>璇锋眰缁撴潫');
-      ElMessage({
-        message: error,
-        type: 'error'
-      });
-      return Promise.reject(error);
-    }
-  );
-});
-
-export { $clue, imgUrl };
diff --git a/src/api/config.js b/src/api/config.js
new file mode 100644
index 0000000..c322f3f
--- /dev/null
+++ b/src/api/config.js
@@ -0,0 +1,82 @@
+import { ElMessage } from 'element-plus';
+
+/**
+ * 璁剧疆缃戣矾璇锋眰鐩戝惉
+ */
+function setInterceptors(...instance) {
+  instance.forEach((i) => {
+    // 娣诲姞璇锋眰鎷︽埅鍣�
+    i.interceptors.request.use(
+      function (config) {
+        // 鍦ㄥ彂閫佽姹備箣鍓�, 娣诲姞璇锋眰澶�
+        // config.headers = addHeaders(config.headers);
+
+        console.log('==>璇锋眰寮�濮�');
+        console.log(`${config.baseURL}${config.url}`);
+        if (config.data) {
+          console.log('==>璇锋眰鏁版嵁', config.data);
+        }
+        return config;
+      },
+      function (error) {
+        // 瀵硅姹傞敊璇仛浜涗粈涔�
+        console.log('==>璇锋眰寮�濮�');
+        console.log(error);
+        ElMessage({
+          message: error,
+          type: 'error'
+        });
+        return Promise.reject(error);
+      }
+    );
+
+    // 娣诲姞鍝嶅簲鎷︽埅鍣�
+    i.interceptors.response.use(
+      function (response) {
+        // 2xx 鑼冨洿鍐呯殑鐘舵�佺爜閮戒細瑙﹀彂璇ュ嚱鏁般��
+        // 瀵瑰搷搴旀暟鎹仛鐐逛粈涔�
+        console.log(response);
+        console.log('==>璇锋眰缁撴潫');
+        if (response.status == 200) {
+          if (
+            response.data.success != undefined &&
+            response.data.success != null
+          ) {
+            if (response.data.success == true) {
+              // if (response.data.message && response.data.message != '') {
+              //   ElMessage({
+              //     message: response.data.message,
+              //     type: 'success'
+              //   }); 
+              // }
+              return response.data.data;
+            } else {
+              ElMessage({
+                message: response.data.message,
+                type: 'error'
+              });
+              return Promise.reject(response.data.message);
+            }
+          } else {
+            return response;
+          }
+        } else {
+          return Promise.reject(response);
+        }
+      },
+      function (error) {
+        // 瓒呭嚭 2xx 鑼冨洿鐨勭姸鎬佺爜閮戒細瑙﹀彂璇ュ嚱鏁般��
+        // 瀵瑰搷搴旈敊璇仛鐐逛粈涔�
+        console.log(error);
+        console.log('==>璇锋眰缁撴潫');
+        ElMessage({
+          message: error,
+          type: 'error'
+        });
+        return Promise.reject(error);
+      }
+    );
+  });
+}
+
+export { setInterceptors };
diff --git a/src/api/grid/gridInfoApi.js b/src/api/grid/gridInfoApi.js
new file mode 100644
index 0000000..824675e
--- /dev/null
+++ b/src/api/grid/gridInfoApi.js
@@ -0,0 +1,31 @@
+import { $clue } from '../index';
+import { getGridRecord, getGridRecordList } from '@/model/gridRecord';
+
+export default {
+  /**
+   * 鑾峰彇鏂规缃戞牸淇℃伅
+   */
+  fetchGridList(schemeId) {
+    return $clue.get(`grid/info/fetch?id=${schemeId}`).then((res) => {
+      return getGridRecordList(res);
+    });
+  },
+
+  /**
+   * 鏂板缓缃戞牸
+   * @param {Object} grid
+   */
+  createGrid(gridInfo) {
+    return $clue.post(`grid/info/create`, gridInfo).then((res) => {
+      return getGridRecord(res);
+    });
+  },
+
+  /**
+   * 鏇存柊缃戞牸
+   * @param {Object} gridInfo 
+   */
+  updateGrid(gridInfo) {
+    return $clue.post(`grid/info/update`, gridInfo)
+  }
+};
diff --git a/src/api/grid/gridSchemeApi.js b/src/api/grid/gridSchemeApi.js
new file mode 100644
index 0000000..eaee15d
--- /dev/null
+++ b/src/api/grid/gridSchemeApi.js
@@ -0,0 +1,18 @@
+import { $clue } from '../index';
+
+export default {
+  /**
+   * 鑾峰彇鍏ㄩ儴缃戞牸鍖栬鍒掓柟妗�
+   */
+  fetchAllSchemes() {
+    return $clue.get(`grid/scheme/fetch`);
+  },
+
+  /**
+   * 鏂板缓缃戞牸鏂规
+   * @param {Object} scheme
+   */
+  createScheme(scheme) {
+    return $clue.post(`grid/scheme/create`, scheme);
+  }
+};
diff --git a/src/api/gridRecordApi.js b/src/api/gridRecordApi.js
deleted file mode 100644
index 763882f..0000000
--- a/src/api/gridRecordApi.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import { getGridRecordList } from '@/model/gridRecord';
-
-export default {
-  /**
-   * 鑾峰彇鍏ㄩ儴缃戞牸鍖栬鍒掕褰�
-   */
-  getGridRecords() {
-    return new Promise((resolve, reject) => {
-      resolve([
-        {
-          value: '1',
-          label: '缃戞牸瑙勫垝鏂规涓�'
-        },
-        {
-          value: '2',
-          label: '缃戞牸瑙勫垝鏂规浜�'
-        }
-      ]);
-    });
-  },
-
-  /**
-   * 鏍规嵁鏂规璁板綍id锛岃幏鍙栧搴旂殑璁板綍璇︽儏
-   * @param {String} recordId 鏂规璁板綍id
-   */
-  getGridRecordDetail(recordId) {
-    return new Promise((resolve, reject) => {
-      const data = [
-        {
-          gId: '1',
-          gName: '缃戞牸涓�',
-          gSide: [
-            [121.01334, 31.232145],
-            [121.01434, 31.232145],
-            [121.01434, 31.233145],
-            [121.01334, 31.233145]
-          ]
-        }
-      ];
-      for (let i = 0; i < 10; i++) {
-        const l = [];
-        data[i].gSide.forEach((p) => {
-          const lng = p[0] + 0.001;
-          l.push([lng, p[1]]);
-        });
-        data.push({
-          gId: i + 2 + '',
-          gName: '缃戞牸浜�',
-          gSide: l
-        });
-      }
-      resolve(getGridRecordList(data));
-    });
-  }
-};
diff --git a/src/api/index.js b/src/api/index.js
index e69de29..0ff0550 100644
--- a/src/api/index.js
+++ b/src/api/index.js
@@ -0,0 +1,33 @@
+import axios from 'axios';
+import { setInterceptors } from "./config";
+
+const url = 'http://47.100.191.150:9031/';
+// const url = 'http://192.168.1.9:8080/';
+const imgUrl = 'http://47.100.191.150:9031/images/';
+
+//椋炵窘鐩戠
+const $clue = axios.create({
+  baseURL: url,
+  timeout: 10000
+  // headers: addHeaders()
+});
+
+// function getHeaders() {
+//   const token = 'e6dc8bb9e1ff0ce973fb92b4af2e4c3f';
+
+//   const date = new Date();
+//   const timestamp = parseInt(date.getTime() / 1000) - 200;
+
+//   const sign = md5(timestamp + token);
+
+//   return {
+//     'JA-TIMESTAMP': timestamp,
+//     'JA-SIGN': sign,
+//     'JA-TOKEN': token
+//   };
+// }
+
+//娣诲姞鎷︽埅鍣�
+setInterceptors($clue)
+
+export { $clue, imgUrl };
diff --git a/src/assets/base.css b/src/assets/base.css
index b15a559..5ae75b2 100644
--- a/src/assets/base.css
+++ b/src/assets/base.css
@@ -1,55 +1,3 @@
-/* color palette from <https://github.com/vuejs/theme> */
-:root {
-  --vt-c-white: #ffffff;
-  --vt-c-white-soft: #f8f8f8;
-  --vt-c-white-mute: #f2f2f2;
-
-  --vt-c-black: #181818;
-  --vt-c-black-soft: #222222;
-  --vt-c-black-mute: #282828;
-
-  --vt-c-indigo: #2c3e50;
-
-  --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
-  --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
-  --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
-  --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
-
-  --vt-c-text-light-1: var(--vt-c-indigo);
-  --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
-  --vt-c-text-dark-1: var(--vt-c-white);
-  --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
-}
-
-/* semantic color variables for this project */
-:root {
-  --color-background: var(--vt-c-white);
-  --color-background-soft: var(--vt-c-white-soft);
-  --color-background-mute: var(--vt-c-white-mute);
-
-  --color-border: var(--vt-c-divider-light-2);
-  --color-border-hover: var(--vt-c-divider-light-1);
-
-  --color-heading: var(--vt-c-text-light-1);
-  --color-text: var(--vt-c-text-light-1);
-
-  --section-gap: 160px;
-}
-
-@media (prefers-color-scheme: dark) {
-  :root {
-    --color-background: var(--vt-c-black);
-    --color-background-soft: var(--vt-c-black-soft);
-    --color-background-mute: var(--vt-c-black-mute);
-
-    --color-border: var(--vt-c-divider-dark-2);
-    --color-border-hover: var(--vt-c-divider-dark-1);
-
-    --color-heading: var(--vt-c-text-dark-1);
-    --color-text: var(--vt-c-text-dark-2);
-  }
-}
-
 *,
 *::before,
 *::after {
@@ -58,9 +6,23 @@
   font-weight: normal;
 }
 
+:root {
+  --fy-head-height: 50px;
+  --fy-body-height: calc(100% - var(--fy-head-height));
+}
+
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+td, th {
+  padding: 0;
+}
+
 body {
-  --screen-min-width: 1440px;
-  --screen-min-height: 900px;
+  --screen-min-width: 1200px;
+  --screen-min-height: 600px;
   min-height: var(--screen-min-height);
   min-width: var(--screen-min-width);
   /* overflow: scroll; */
diff --git a/src/assets/layout.css b/src/assets/layout.css
new file mode 100644
index 0000000..9736a1a
--- /dev/null
+++ b/src/assets/layout.css
@@ -0,0 +1,56 @@
+.fy-head {
+  height: var(--fy-head-height);
+}
+
+.fy-body {
+  height: var(--fy-body-height);
+}
+
+.fy-overlay-container {
+  pointer-events: none;
+  /* background-color: aqua; */
+}
+
+.fy-card {
+  position: relative;
+  /* height: 700px; */
+  background: white;
+  border-radius: 12px;
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+  pointer-events: auto;
+  box-shadow: var(--el-box-shadow-dark);
+  /* padding: 0 8px; */
+}
+
+.fy-main {
+  /* background-color: aliceblue; */
+  padding: 8px 8px 16px 8px;
+  font-size: var(--el-font-size-base);
+}
+
+.fy-main-border {
+  /* background-color: aliceblue; */
+  padding: 0 8px;
+  font-size: var(--el-font-size-base);
+  border: var(--el-border);
+  border-radius: 6px;
+}
+
+.fy-column-reverse {
+  display: flex;
+  flex-direction: column-reverse;
+  height: 100%;
+}
+
+.fy-flex-row {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  padding: 0 8px;
+}
+
+.fy-flex-row>span{
+  color: var(--el-text-color-regular);
+}
\ No newline at end of file
diff --git a/src/assets/main.css b/src/assets/main.css
index 781f450..8cf314d 100644
--- a/src/assets/main.css
+++ b/src/assets/main.css
@@ -1,34 +1,10 @@
 @import './base.css';
 @import './border.css';
 @import './text.css';
+@import './layout.css';
+@import './shortcut.css';
 
 #app {
   margin: 0 auto;
   font-weight: normal;
-}
-
-/* a,
-.green {
-  text-decoration: none;
-  color: hsla(160, 100%, 37%, 1);
-  transition: 0.4s;
-} */
-
-@media (hover: hover) {
-  /* a:hover {
-    background-color: hsla(160, 100%, 37%, 0.2);
-  } */
-}
-
-@media (min-width: 1024px) {
-  /* body {
-    display: flex;
-    place-items: center;
-  }
-
-  #app {
-    display: grid;
-    grid-template-columns: 1fr 1fr;
-    padding: 0 2rem;
-  } */
 }
\ No newline at end of file
diff --git a/src/assets/shortcut.css b/src/assets/shortcut.css
new file mode 100644
index 0000000..714f8ee
--- /dev/null
+++ b/src/assets/shortcut.css
@@ -0,0 +1,16 @@
+.flex {
+  display: flex;
+}
+
+.flex-col {
+  display: flex;
+  flex-direction: column;
+}
+
+.gap-1 {
+  gap: 4px;
+}
+
+.p-h-1 {
+  padding: 0 8px;
+}
\ No newline at end of file
diff --git a/src/assets/text.css b/src/assets/text.css
index 1dd790d..770facd 100644
--- a/src/assets/text.css
+++ b/src/assets/text.css
@@ -1,14 +1,16 @@
 .fy-h1 {
   padding: 8px 8px 8px 8px;
   font-size: var(--el-font-size-large);
+  font-weight: 600;
 }
 
 .fy-h2 {
-  padding: 16px 0 8px 0;
+  padding: 8px 8px;
   font-size: var(--el-font-size-medium);
   display: flex;
   justify-content: space-between;
   align-items: center;
+  font-weight: 600;
 }
 
 .fy-p1 {
diff --git a/src/components.d.ts b/src/components.d.ts
index 0499a39..925dc48 100644
--- a/src/components.d.ts
+++ b/src/components.d.ts
@@ -8,26 +8,32 @@
 declare module 'vue' {
   export interface GlobalComponents {
     BaseMap: typeof import('./components/map/BaseMap.vue')['default']
+    CloseButton: typeof import('./components/button/CloseButton.vue')['default']
+    CoreHeader: typeof import('./components/core/CoreHeader.vue')['default']
+    DescriptionsList: typeof import('./components/list/DescriptionsList.vue')['default']
+    DescriptionsListItem: typeof import('./components/list/DescriptionsListItem.vue')['default']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
     ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
-    ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
-    ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
     ElDialog: typeof import('element-plus/es')['ElDialog']
     ElDivider: typeof import('element-plus/es')['ElDivider']
     ElEmpty: typeof import('element-plus/es')['ElEmpty']
     ElForm: typeof import('element-plus/es')['ElForm']
     ElFormItem: typeof import('element-plus/es')['ElFormItem']
     ElIcon: typeof import('element-plus/es')['ElIcon']
+    ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
     ElInput: typeof import('element-plus/es')['ElInput']
     ElOption: typeof import('element-plus/es')['ElOption']
     ElRadio: typeof import('element-plus/es')['ElRadio']
+    ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
     ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElSelect: typeof import('element-plus/es')['ElSelect']
+    ElTag: typeof import('element-plus/es')['ElTag']
+    ElText: typeof import('element-plus/es')['ElText']
     ElUpload: typeof import('element-plus/es')['ElUpload']
     MapSearch: typeof import('./components/map/MapSearch.vue')['default']
     OptionTime: typeof import('./components/search-option/OptionTime.vue')['default']
diff --git a/src/components/button/CloseButton.vue b/src/components/button/CloseButton.vue
new file mode 100644
index 0000000..aaf5736
--- /dev/null
+++ b/src/components/button/CloseButton.vue
@@ -0,0 +1,36 @@
+<template>
+  <div class="wrapper">
+    <el-button
+      class="close-btn"
+      type="danger"
+      icon="Close"
+      circle
+      @click="close"
+    />
+    <slot></slot>
+  </div>
+</template>
+
+<script>
+export default {
+  emits: ['close'],
+  methods: {
+    close() {
+      this.$emit('close');
+    }
+  }
+};
+</script>
+<style scoped>
+.wrapper {
+  position: relative;
+  padding-right: 10px;
+  pointer-events: auto;
+}
+.close-btn {
+  position: absolute;
+  right: 2px;
+  top: -10px;
+  z-index: 1;
+}
+</style>
diff --git a/src/components/core/CoreHeader.vue b/src/components/core/CoreHeader.vue
new file mode 100644
index 0000000..7465b24
--- /dev/null
+++ b/src/components/core/CoreHeader.vue
@@ -0,0 +1,44 @@
+<template>
+  <el-row class="fy-head">
+    <div>
+      <el-radio-group
+        class="container"
+        v-model="radio1"
+        size="large"
+        @change="onChange"
+      >
+        <el-radio-button
+          v-for="item in radioOptions"
+          :key="item.label"
+          :label="item.label"
+          >{{ item.name }}</el-radio-button
+        >
+      </el-radio-group>
+    </div>
+  </el-row>
+</template>
+
+<script>
+export default {
+  emits: ['onChange'],
+  data() {
+    return {
+      radioOptions: [
+        { name: '绾跨储绠$悊', label: 0 },
+        { name: '缃戞牸绠$悊', label: 1 }
+      ],
+      radio1: 0
+    };
+  },
+  methods: {
+    onChange(e) {
+      this.$emit('onChange', e);
+    }
+  }
+};
+</script>
+<style scoped>
+.container {
+  pointer-events: auto;
+}
+</style>
diff --git a/src/components/list/DescriptionsList.vue b/src/components/list/DescriptionsList.vue
new file mode 100644
index 0000000..4afb85a
--- /dev/null
+++ b/src/components/list/DescriptionsList.vue
@@ -0,0 +1,31 @@
+<template>
+  <div class="title-wrapper">
+    <div v-if="title" class="fy-h2">{{ title }}</div>
+    <slot name="extra"></slot>
+  </div>
+  <table>
+    <tbody>
+      <slot></slot>
+    </tbody>
+  </table>
+</template>
+
+<script>
+export default {
+  props: {
+    title: String
+  }
+};
+</script>
+
+<style scoped>
+.title-wrapper {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+table {
+  width: 100%;
+}
+</style>
diff --git a/src/components/list/DescriptionsListItem.vue b/src/components/list/DescriptionsListItem.vue
new file mode 100644
index 0000000..65e34c8
--- /dev/null
+++ b/src/components/list/DescriptionsListItem.vue
@@ -0,0 +1,36 @@
+<template>
+  <tr>
+    <td v-if="label" class="td-1">{{ label }}</td>
+    <td v-else class="td-1"><slot name="label"></slot></td>
+    <td v-if="content" class="td-2">{{ content }}</td>
+    <td v-else class="td-2"><slot name="content"></slot></td>
+  </tr>
+</template>
+
+<script>
+export default {
+  props: {
+    label: String,
+    content: String
+  }
+};
+</script>
+
+<style scoped>
+tr {
+  font-size: var(--el-font-size-small);
+}
+td {
+  border: var(--el-border);
+  padding: 2px 6px;
+}
+.td-1 {
+  width: 68px;
+  background-color: var(--el-fill-color-light);
+  color: var(--el-text-color-regular);
+}
+
+.td-2 {
+  color: var(--el-text-color-primary);
+}
+</style>
diff --git a/src/components/map/baseMapUtil.js b/src/components/map/baseMapUtil.js
index 735b58e..02b6601 100644
--- a/src/components/map/baseMapUtil.js
+++ b/src/components/map/baseMapUtil.js
@@ -80,21 +80,31 @@
     });
   },
 
+  addMarker(lnglat) {
+    const marker = new AMap.Marker({
+      position: lnglat
+    });
+    map.add(marker);
+    this.setCenter(lnglat);
+    return marker;
+  },
+
+  setCenter(lnglat) {
+    map.setCenter(lnglat);
+  },
+
   /**
    * 缂╂斁鍦板浘鍒板悎閫傜殑瑙嗛噹绾у埆
    */
-  setFitView(overlays, type = 0) {
+  setFitView(...overlays) {
     const _overlays = toRaw(overlays);
-    switch (type) {
-      case 0:
-        map.setFitView([_overlays]);
-        break;
-      case 1:
-        map.setFitView(_overlays);
-        break;
-      default:
-        map.setFitView([_overlays]);
-        break;
+    map.setFitView(_overlays, true, [60, 60, 500, 60], 14.5);
+  },
+
+  addView(overlays) {
+    if (overlays) {
+      const _overlays = toRaw(overlays);
+      map.add(_overlays);
     }
   },
 
@@ -102,8 +112,10 @@
    * 绉婚櫎瑕嗙洊鐗�
    */
   removeView(overlays) {
-    const _overlays = toRaw(overlays);
-    map.remove(_overlays);
+    if (overlays) {
+      const _overlays = toRaw(overlays);
+      map.remove(_overlays);
+    }
   },
 
   /**
@@ -133,8 +145,10 @@
         (((a * (1 - ee)) / (magic * sqrtmagic)) * PI);
       dlng =
         (dlng * 180.0) / ((a / sqrtmagic) * Math.cos(radlat) * PI);
-      let mglat = Math.round((lat * 2 - lat - dlat) * 1000000) / 1000000;
-      let mglng = Math.round((lng * 2 - lng - dlng) * 1000000) / 1000000;
+      let mglat =
+        Math.round((lat * 2 - lat - dlat) * 1000000) / 1000000;
+      let mglng =
+        Math.round((lng * 2 - lng - dlng) * 1000000) / 1000000;
       return [mglng, mglat];
     }
   },
@@ -164,5 +178,16 @@
       let mglng = Math.round((lng + dlng) * 1000000) / 1000000;
       return [mglng, mglat];
     }
+  },
+  gpsConvert(gps) {
+    return new Promise((reject) => {
+      // 鍙傛暟璇存槑锛氶渶瑕佽浆鎹㈢殑鍧愭爣锛岄渶瑕佽浆鎹㈢殑鍧愭爣绫诲瀷锛岃浆鎹㈡垚鍔熷悗鐨勫洖璋冨嚱鏁�
+      AMap.convertFrom(gps, 'baidu', function (status, result) {
+        if (result.info === 'ok') {
+          var lnglats = result.locations; // 杞崲鍚庣殑楂樺痉鍧愭爣 Array.<LngLat>
+          reject(lnglats[0]);
+        }
+      });
+    });
   }
 };
diff --git a/src/components/map/mapGrid.js b/src/components/map/mapGrid.js
index 9f630ce..d16d8ba 100644
--- a/src/components/map/mapGrid.js
+++ b/src/components/map/mapGrid.js
@@ -37,10 +37,10 @@
     };
     mouseTool.on('draw', lastDrawEvent);
     mouseTool.polygon({
-      strokeColor: '#FF33FF',
+      strokeColor: 'green',
       strokeOpacity: 1,
-      strokeWeight: 6,
-      fillColor: '#1791fc',
+      strokeWeight: 2,
+      fillColor: '#fff',
       fillOpacity: 0.4,
       // 绾挎牱寮忚繕鏀寔 'dashed'
       strokeStyle: 'solid'
diff --git a/src/components/search-option/OptionTime.vue b/src/components/search-option/OptionTime.vue
index 572283e..7c92ea3 100644
--- a/src/components/search-option/OptionTime.vue
+++ b/src/components/search-option/OptionTime.vue
@@ -9,7 +9,7 @@
 <script>
 // 缃戞牸鍖栨柟妗堣褰曢�夐」
 
-import gridRecordApi from '@/api/gridRecordApi';
+import gridSchemeApi from '@/api/grid/gridSchemeApi';
 
 export default {
   props: {
@@ -37,7 +37,7 @@
   },
   methods: {
     getOptions() {
-      gridRecordApi.getGridRecords().then((res) => {
+      gridSchemeApi.getGridRecords().then((res) => {
         this.options = res;
         this.selectedOptions = res[0];
       });
diff --git a/src/composables/formConfirm.js b/src/composables/formConfirm.js
index 43b3d35..f036d0b 100644
--- a/src/composables/formConfirm.js
+++ b/src/composables/formConfirm.js
@@ -75,9 +75,10 @@
 
   // 娓呯┖琛ㄥ崟
   const clear = function () {
+    formRef.value.clearValidate();
     edit.value = false;
     isReset = true;
-    formObj.value = {}
+    formObj.value = {};
   };
 
   // 鎻愪氦鎴愬姛鍚�
@@ -94,9 +95,9 @@
           confirmMsg: submit.msg,
           confirmTitle: submit.title,
           onConfirm: async () => {
-            await submit.do();
-            submited();
-            return;
+            return submit.do().then(() => {
+              submited();
+            });
           }
         });
       }
@@ -116,6 +117,7 @@
         }
       });
     } else {
+      formRef.value.clearValidate();
       cancel.do();
     }
   };
@@ -142,5 +144,14 @@
     }
   };
 
-  return { formObj, formRef, edit, active, onSubmit, onCancel, onReset, clear };
+  return {
+    formObj,
+    formRef,
+    edit,
+    active,
+    onSubmit,
+    onCancel,
+    onReset,
+    clear
+  };
 }
diff --git a/src/composables/messageBox.js b/src/composables/messageBox.js
index d896244..3f43447 100644
--- a/src/composables/messageBox.js
+++ b/src/composables/messageBox.js
@@ -1,31 +1,45 @@
-import { ElMessageBox, ElNotification, ElMessage } from 'element-plus';
+import {
+  ElMessageBox,
+  ElNotification,
+  ElMessage
+} from 'element-plus';
 
 function useMessageBoxTip({
   confirmMsg,
   confirmTitle = '鎻愪氦',
   doneMsg = confirmTitle,
-  onConfirm,
+  onConfirm
 }) {
   ElMessageBox.confirm(confirmMsg, `${confirmTitle}纭`, {
     confirmButtonText: '纭',
     cancelButtonText: '鍙栨秷',
-    type: 'warning',
+    type: 'warning'
   })
     .then(async () => {
-      let msg = `宸�${doneMsg}`
+      let msg = `宸�${doneMsg}`;
       if (typeof onConfirm === 'function') {
-        const str = await onConfirm();
-        if (str && str != '') {
-          msg = `宸�${doneMsg}, ${str}`
-        }
+        onConfirm()
+          .then((res) => {
+            if (res && res != '') {
+              msg = `宸�${doneMsg}, ${res}`;
+            }
+            ElNotification({
+              title: `${confirmTitle}鎴愬姛`,
+              message: msg,
+              type: 'success'
+            });
+          })
+          .catch((err) => {
+            let errStr = `${confirmTitle}鍙栨秷`;
+            if (err != 'cancel') {
+              errStr = `${confirmTitle}澶辫触, ${err}`;
+            }
+            ElMessage({
+              message: errStr,
+              type: 'warning'
+            });
+          });
       }
-      ElNotification({
-        title: `${confirmTitle}鎴愬姛`,
-        message: msg,
-        type: 'success',
-        // offset: 170,
-        position: 'bottom-left',
-      });
     })
     .catch((err) => {
       let errStr = `${confirmTitle}鍙栨秷`;
@@ -34,7 +48,7 @@
       }
       ElMessage({
         message: errStr,
-        type: 'warning',
+        type: 'warning'
       });
     });
 }
@@ -43,7 +57,7 @@
   ElMessageBox.confirm(confirmMsg, confirmTitle, {
     confirmButtonText: '纭',
     cancelButtonText: '鍙栨秷',
-    type: 'warning',
+    type: 'warning'
   })
     .then(async () => {
       if (typeof onConfirm === 'function') {
diff --git a/src/main.js b/src/main.js
index 4ff35b4..24d614c 100644
--- a/src/main.js
+++ b/src/main.js
@@ -4,6 +4,7 @@
 import { createPinia } from 'pinia';
 import App from './App.vue';
 import { createMap } from './components/map/baseMap';
+import { tf, nf } from './utils/textFormat';
 
 import * as ElementPlusIconsVue from '@element-plus/icons-vue';
 import 'element-plus/theme-chalk/src/overlay.scss';
@@ -20,6 +21,8 @@
 
 // 楂樺痉鍦板浘鍒濆鍖�
 createMap('container');
-
+// 鎸傝浇鏃堕棿鏍煎紡鍖栧嚱鏁拌嚦鍏ㄥ眬
+app.config.globalProperties.$tf = tf;
+app.config.globalProperties.$nf = nf;
 
 app.use(createPinia()).mount('#app');
diff --git a/src/model/clueQuestion.js b/src/model/clueQuestion.js
index dc7d77f..87954a4 100644
--- a/src/model/clueQuestion.js
+++ b/src/model/clueQuestion.js
@@ -1,4 +1,4 @@
-import { imgUrl } from '@/api/clue/index';
+import { imgUrl } from '@/api/index';
 
 function getClueQuestion(data) {
   data.cqFilePath = data.cqFilePath.split(';').map((val) => {
diff --git a/src/model/gridRecord.js b/src/model/gridRecord.js
index d991ac8..de996b7 100644
--- a/src/model/gridRecord.js
+++ b/src/model/gridRecord.js
@@ -6,9 +6,29 @@
  * @returns
  */
 function getGridRecord(data) {
-  const path = util.listToLngLat(data.gSide);
-  data.gSide = path;
-  return data;
+  const _sides = data.giSide.split(';').map((value) => {
+    return value.split(',');
+  });
+  const _data = {
+    id: data.giUid,
+    schemeId: data.gsId,
+    name: data.giName,
+    sides: util.listToLngLat(_sides),
+    delete: data.giDelete,
+    createTime: data.giCreateTime
+  };
+  return _data;
+}
+
+function parseToGridInfo(data) {
+  return {
+    giUid: data.id,
+    gsId: data.schemeId,
+    giName: data.name,
+    giSide: data.overlays.getPath().join(';'),
+    giDelete: data.delete,
+    giCreateTime: data.createTime
+  };
 }
 
 function getGridRecordList(dataList) {
@@ -17,4 +37,4 @@
   });
 }
 
-export { getGridRecord, getGridRecordList };
+export { getGridRecord, getGridRecordList, parseToGridInfo };
diff --git a/src/stores/grid.js b/src/stores/grid.js
index 56ea718..23dac47 100644
--- a/src/stores/grid.js
+++ b/src/stores/grid.js
@@ -2,21 +2,27 @@
 import { defineStore } from 'pinia';
 import mapGrid from '@/components/map/mapGrid';
 import baseMapUtil from '@/components/map/baseMapUtil';
+import gridInfoApi from '@/api/grid/gridInfoApi';
+import { parseToGridInfo } from '@/model/gridRecord';
 
 export const useGridStore = defineStore('grid', () => {
   // 褰撳墠鍔犺浇鐨勭綉鏍兼暟鎹泦鍚�
   const gridList = ref([]);
+  // 褰撳墠鏄惁鏈夐�変腑鏂规
+  const hasScheme = ref(false);
   // 褰撳墠閫変腑鎿嶄綔鐨勭綉鏍间俊鎭�
-  const selectedGrid = ref({});
+  const selectedGrid = ref();
   // 褰撳墠閫変腑鎿嶄綔鐨勭綉鏍兼槸鍚﹀浜庡彲缂栬緫鐘舵��
   const isEdit = ref(false);
+  // 璁板綍褰撳墠缃戞牸璺緞淇℃伅
+  var selectedPath = undefined;
 
   /**
    * 妫�鏌ュ綋鍓嶆槸鍚︽湁閫変腑鐨勭綉鏍�
    * @returns {Boolean}
    */
   function _checkGridExist() {
-    return selectedGrid.value.gId != undefined;
+    return selectedGrid.value && selectedGrid.value.id != undefined;
   }
 
   /**鏁版嵁澧炲垹**************************************************************/
@@ -27,7 +33,16 @@
   function setGrid(index) {
     if (index >= 0 && index < gridList.value.length) {
       selectedGrid.value = gridList.value[index];
+      selectedPath = selectedGrid.value.overlays.getPath().join(';');
     }
+  }
+
+  /**
+   * 鍙栨秷閫変腑
+   */
+  function clearSelect() {
+    selectedGrid.value = undefined;
+    selectedPath = undefined;
   }
 
   /**
@@ -36,6 +51,7 @@
    */
   function setGridList(list) {
     gridList.value = list;
+    hasScheme.value = true;
   }
 
   /**
@@ -88,7 +104,14 @@
    */
   function saveSelectedGrid() {
     if (!_checkGridExist()) return;
-    selectedGrid.value.gSide = selectedGrid.value.overlays.getPath();
+    const newPath = selectedGrid.value.overlays.getPath().join(';');
+    if (selectedPath != newPath) {
+      const _data = parseToGridInfo(selectedGrid.value);
+      return gridInfoApi.updateGrid(_data).then(() => {
+        selectedGrid.value.sides =
+          selectedGrid.value.overlays.getPath();
+      });
+    }
   }
 
   /**
@@ -98,7 +121,7 @@
     if (!_checkGridExist()) return;
     baseMapUtil.removeView(selectedGrid.value.overlays);
     selectedGrid.value.overlays = mapGrid.drawPolygon(
-      selectedGrid.value.gSide
+      selectedGrid.value.sides
     );
   }
 
@@ -112,7 +135,7 @@
       showGrid(i);
       grids.push(l.overlays);
     });
-    baseMapUtil.setFitView(grids, 1);
+    baseMapUtil.setFitView(...grids);
   }
 
   /**
@@ -123,15 +146,17 @@
     if (item.overlays) {
       baseMapUtil.setFitView(item.overlays);
     } else {
-      item.overlays = mapGrid.drawPolygon(item.gSide);
+      item.overlays = mapGrid.drawPolygon(item.sides);
     }
   }
 
   return {
+    hasScheme,
     gridList,
     selectedGrid,
     isEdit,
     setGrid,
+    clearSelect,
     setGridList,
     addGrid,
     deleteGrid,
diff --git a/src/utils/textFormat.js b/src/utils/textFormat.js
new file mode 100644
index 0000000..0504a28
--- /dev/null
+++ b/src/utils/textFormat.js
@@ -0,0 +1,19 @@
+/**
+ * 鏂囨湰鏍峰紡鏍煎紡鍖�
+ */
+
+import moment from 'moment';
+
+function tf(timeStr) {
+  return moment(timeStr).format('YYYY-MM-DD HH:mm:ss');
+}
+
+function nf(num, digit = 2) {
+  let numStr = num + '';
+  while (numStr.length < digit) {
+    numStr = '0' + numStr;
+  }
+  return numStr;
+}
+
+export { tf, nf };
diff --git a/src/views/HomePage.vue b/src/views/HomePage.vue
index 8445b25..4ede8f0 100644
--- a/src/views/HomePage.vue
+++ b/src/views/HomePage.vue
@@ -1,16 +1,22 @@
 <template>
   <BaseMap></BaseMap>
   <div class="overlay-container">
+    <CoreHeader @on-change="(e) => (menuIndex = e)"></CoreHeader>
     <!-- <router-view> -->
-    <!-- <GridLayout></GridLayout> -->
-    <ClueLayout></ClueLayout>
+    <ClueLayout v-show="menuIndex == 0"></ClueLayout>
+    <GridLayout v-show="menuIndex == 1"></GridLayout>
     <!-- </router-view> -->
   </div>
 </template>
 
 <script setup>
+import { ref } from 'vue';
+
 import GridLayout from '@/views/overlay-grid/GridLayout.vue';
 import ClueLayout from '@/views/overlay-clue/ClueLayout.vue';
+
+// 椁愬崟绱㈠紩
+const menuIndex = ref(0);
 </script>
 
 <style scoped>
@@ -23,7 +29,7 @@
   height: 100vh;
   top: 0;
   left: 0;
-  padding: 4px;
+  /* padding: 4px; */
   pointer-events: none;
 }
 </style>
diff --git a/src/views/overlay-clue/ClueLayout.vue b/src/views/overlay-clue/ClueLayout.vue
index b47e870..be9e728 100644
--- a/src/views/overlay-clue/ClueLayout.vue
+++ b/src/views/overlay-clue/ClueLayout.vue
@@ -1,119 +1,32 @@
 <template>
-  <el-row class="container" justify="space-between">
-    <el-col :span="6" class="grid-content bg-content">
-      <div class="fy-h1">绾跨储娓呭崟</div>
-      <div class="search-wrap">
-        <span>鏃堕棿</span>
-        <el-date-picker
-          v-model="updateTime"
-          type="datetime"
-          placeholder="閫夋嫨鏃ユ湡鍜屾椂闂�"
-        />
-        <el-button type="primary" @click="getClues">鏌ヨ</el-button>
-        <el-button type="primary" @click="fetchRemoteClue"
-          >鎷夊彇绾跨储</el-button
-        >
-      </div>
-      <ClueList
-        :dataList="clueList"
-        @itemSelected="selectClue"
-      ></ClueList>
+  <el-row class="fy-overlay-container" justify="space-between">
+    <el-col :span="6">
+      <ClueManage @itemSelected="selectClue"></ClueManage>
     </el-col>
-    <el-col :span="6" class="grid-content bg-content-1">
-      <div class="fy-h1">绾跨储鍙嶉</div>
-      <el-scrollbar height="80vh" class="bg-fill">
-        <ClueReport :clueData="selectedClue"></ClueReport>
-      </el-scrollbar>
+    <el-col :span="6">
+      <ClueReport
+        v-model:show="show"
+        :clueData="selectedClue"
+      ></ClueReport>
     </el-col>
   </el-row>
 </template>
 
 <script setup>
-import ClueList from './components/ClueList.vue';
-import ClueReport from './components/ClueReport.vue';
+import ClueManage from './list/ClueManage.vue';
+import ClueReport from './report/ClueReport.vue';
+import { ref } from 'vue';
 
-import clueApi from '@/api/clue/clueApi';
-import { onMapMounted } from '@/components/map/baseMap';
-import moment from 'moment';
-import { ref, watch } from 'vue';
-
-// 涓嬪彂鏃堕棿锛堟瘡娆℃煡璇㈠ぇ浜庢鏃堕棿鐨勬暟鎹級
-const updateTime = ref(new Date());
-// 绾跨储娓呭崟
-const clueList = ref([]);
 const selectedClue = ref();
-
-/**
- * 鏌ヨ宸蹭笅鍙戠殑绾跨储娓呭崟
- */
-const getClues = function () {
-  const now = moment(updateTime.value);
-  const sTime = now.format('YYYY-MM-DD HH:mm:ss');
-  const eTime = now.add(1, 'month').format('YYYY-MM-DD HH:mm:ss');
-  onMapMounted(() => {
-    clueApi.getClue({ sTime, eTime }).then((res) => {
-      clueList.value = res;
-    });
-  });
-};
-
-function fetchRemoteClue() {
-  const time = moment(updateTime.value).format('YYYY-MM-DD HH:mm:ss');
-  onMapMounted(() => {
-    clueApi.fetchRemoteClue(time).then((res) => {
-      clueList.value = res;
-    });
-  });
-}
+const show = ref(false);
 
 /**
  * 閫夋嫨绾跨储浜嬩欢
  */
 const selectClue = function (clue) {
+  show.value = true;
   selectedClue.value = clue;
 };
 </script>
 
-<style scoped>
-.title {
-  font-size: var(--el-font-size-large);
-}
-
-.container {
-  pointer-events: none;
-}
-
-.grid-content {
-  /* min-width: 180px; */
-  border-radius: var(--el-border-radius-round);
-  display: flex;
-  flex-direction: column;
-  gap: 16px;
-  /* padding: 8px 8px; */
-  pointer-events: auto;
-  box-shadow: var(--el-box-shadow-dark);
-}
-
-.bg-content {
-  height: 90vh;
-  background: white;
-  min-width: calc(var(--screen-min-width) / 6);
-}
-
-.bg-content-1 {
-  height: 90vh;
-  background: white;
-}
-
-.search-wrap {
-  padding: 0 8px;
-  display: flex;
-  align-items: center;
-  gap: 4px;
-}
-
-.bg-fill {
-  /* background: var(--el-fill-color-extra-light); */
-  padding: 0 8px;
-}
-</style>
+<style scoped></style>
diff --git a/src/views/overlay-clue/components/ClueList.vue b/src/views/overlay-clue/components/ClueList.vue
deleted file mode 100644
index 2b49cd8..0000000
--- a/src/views/overlay-clue/components/ClueList.vue
+++ /dev/null
@@ -1,92 +0,0 @@
-<template>
-  <ul class="list-container">
-    <el-scrollbar>
-      <template v-for="(item, index) in dataList" :key="index">
-        <li
-          :class="
-            'list-item ' +
-            (item.selected ? 'list-item__selected' : '')
-          "
-          @click="selectItem(item)"
-          v-if="!item.delete"
-        >
-          <div style="display: flex; gap: 8px">
-            <div>{{ item.cid }}</div>
-            <div>{{ item.cclueName }}</div>
-          </div>
-        </li>
-      </template>
-    </el-scrollbar>
-  </ul>
-</template>
-
-<script>
-export default {
-  props: {
-    dataList: Array
-  },
-  emits: ['itemSelected'],
-  data() {
-    return {};
-  },
-  watch: {},
-  methods: {
-    // 鍒楄〃閫夋嫨
-    selectItem(item) {
-      this.clearSelect();
-      item.selected = true;
-      this.$emit('itemSelected', item);
-    },
-    clearSelect() {
-      this.dataList.forEach((e) => {
-        e.selected = false;
-      });
-    }
-  }
-};
-</script>
-<style scoped>
-.list-container {
-  /* background-color: antiquewhite; */
-  padding: initial;
-  height: 50vh;
-  overflow: auto;
-  overflow-x: hidden;
-  border: var(--el-border);
-  font-size: var(--el-font-size-small);
-}
-
-.list-item {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 4px;
-  list-style-type: none;
-  /* background-color: aqua; */
-  /* border: var(--el-border); */
-  border-radius: var(--el-border-radius-base);
-  box-shadow: var(--el-box-shadow-lighter);
-  margin-bottom: 2px;
-  cursor: pointer;
-  line-height: 36px;
-}
-
-.list-item:hover {
-  background-color: var(--el-color-primary-light-9);
-}
-
-.list-item__selected {
-  background-color: var(--el-color-primary-light-9);
-}
-
-.v-enter-from,
-.v-leave-to {
-  opacity: 0;
-  transform: translateX(8px);
-}
-
-.v-enter-active,
-.v-leave-active {
-  transition: all 0.3s ease-out;
-}
-</style>
diff --git a/src/views/overlay-clue/components/ClueReport.vue b/src/views/overlay-clue/components/ClueReport.vue
deleted file mode 100644
index 7ed612e..0000000
--- a/src/views/overlay-clue/components/ClueReport.vue
+++ /dev/null
@@ -1,104 +0,0 @@
-<template>
-  <!-- 娓呭崟璇︽儏 -->
-  <el-descriptions
-    class=""
-    title="绾跨储娓呭崟璇︽儏"
-    :column="1"
-    size="small"
-    border
-  >
-    <template #extra>
-      <el-button
-        type="primary"
-        text
-        size="small"
-        @click="openPDF"
-        >鏌ョ湅PDF</el-button
-      >
-    </template>
-    <el-descriptions-item>
-      <template #label>
-        <div class="cell-item">绾跨储缂栧彿</div>
-      </template>
-      {{ clueData.cid }}
-    </el-descriptions-item>
-    <el-descriptions-item width="65px" min-width="50px">
-      <template #label>
-        <div class="cell-item">绾跨储鍚嶇О</div>
-      </template>
-      {{ clueData.cclueName }}
-    </el-descriptions-item>
-    <!-- <el-descriptions-item>
-      <template #label>
-        <div class="cell-item">鍒涘缓鏃堕棿</div>
-      </template>
-      {{ clueData.ccreateTime }}
-    </el-descriptions-item> -->
-    <el-descriptions-item>
-      <template #label>
-        <div class="cell-item">涓嬪彂鏃堕棿</div>
-      </template>
-      {{ clueData.creleaseTime }}
-    </el-descriptions-item>
-    <el-descriptions-item>
-      <template #label>
-        <div class="cell-item">鎶ヨ绔欑偣</div>
-      </template>
-      {{ clueData.csiteName }}
-    </el-descriptions-item>
-    <el-descriptions-item>
-      <template #label>
-        <div class="cell-item">绔欑偣绫诲瀷</div>
-      </template>
-      {{ clueData.csiteType }}
-    </el-descriptions-item>
-    <el-descriptions-item>
-      <template #label>
-        <div class="cell-item">绾跨储缁撹</div>
-      </template>
-      {{ clueData.cconclusion }}
-    </el-descriptions-item>
-    <!-- <el-descriptions-item>
-      <template #label>
-        <div class="cell-item">绔欑偣绫诲瀷閫夐」</div>
-      </template>
-      {{ clueData.cairCheckedOptions }}
-    </el-descriptions-item> -->
-  </el-descriptions>
-  <ClueReportConclusion :clueId="clueData.cid"></ClueReportConclusion>
-  <ClueReportQuestion :clueId="clueData.cid"></ClueReportQuestion>
-</template>
-
-<script>
-import ClueReportConclusion from './ClueReportConclusion.vue';
-import ClueReportQuestion from './ClueReportQuestion.vue';
-export default {
-  components: { ClueReportConclusion, ClueReportQuestion },
-  props: {
-    clueData: {
-      type: Object,
-      default: () => {
-        return {};
-      }
-    }
-  },
-  name: 'HomePage',
-  data() {
-    return {
-      // 鍥剧墖涓婁紶鐨勫脊鍑烘
-      isShowReportPhoto: false,
-      photoForm: {
-        clueId: 0,
-        questionDescription: ''
-      }
-    };
-  },
-  methods: {
-    openPDF() {
-      
-    }
-  }
-};
-</script>
-
-<style></style>
diff --git a/src/views/overlay-clue/list/ClueManage.vue b/src/views/overlay-clue/list/ClueManage.vue
new file mode 100644
index 0000000..8519370
--- /dev/null
+++ b/src/views/overlay-clue/list/ClueManage.vue
@@ -0,0 +1,80 @@
+<template>
+  <div class="fy-card">
+    <div class="fy-h1">绾跨储娓呭崟</div>
+    <div class="fy-flex-row">
+      <span>鏃堕棿</span>
+      <el-date-picker
+        v-model="updateTime"
+        type="datetime"
+        placeholder="閫夋嫨鏃ユ湡鍜屾椂闂�"
+      />
+      <el-button type="primary" @click="getClues">鏌ヨ</el-button>
+      <el-button type="primary" @click="fetchRemoteClue" plain
+        >鎷夊彇绾跨储</el-button
+      >
+    </div>
+    <el-scrollbar height="70vh" class="p-h-1">
+      <ClueList
+        :dataList="clueList"
+        @itemSelected="selectClue"
+      ></ClueList>
+    </el-scrollbar>
+  </div>
+</template>
+
+<script setup>
+import ClueList from './components/ClueList.vue';
+
+import clueApi from '@/api/clue/clueApi';
+import { onMapMounted } from '@/components/map/baseMap';
+import moment from 'moment';
+import { ref, onMounted } from 'vue';
+
+const emits = defineEmits('itemSelected');
+
+// 涓嬪彂鏃堕棿锛堟瘡娆℃煡璇㈠ぇ浜庢鏃堕棿鐨勬暟鎹級
+const updateTime = ref();
+// 绾跨储娓呭崟
+const clueList = ref([]);
+
+/**
+ * 鏌ヨ宸蹭笅鍙戠殑绾跨储娓呭崟
+ */
+const getClues = function () {
+  let sTime;
+  let eTime;
+  if (updateTime.value) {
+    const now = moment(updateTime.value);
+    sTime = now.format('YYYY-MM-DD HH:mm:ss');
+    eTime = now.add(1, 'month').format('YYYY-MM-DD HH:mm:ss');
+  }
+  onMapMounted(() => {
+    clueApi.getClue({ sTime, eTime }).then((res) => {
+      clueList.value = res;
+    });
+  });
+};
+
+function fetchRemoteClue() {
+  const time = moment(updateTime.value).format('YYYY-MM-DD HH:mm:ss');
+  onMapMounted(() => {
+    clueApi.fetchRemoteClue(time).then((res) => {
+      clueList.value = res;
+    });
+  });
+}
+
+/**
+ * 閫夋嫨绾跨储浜嬩欢
+ */
+const selectClue = function (clue) {
+  emits('itemSelected', clue);
+};
+
+onMounted(() => {
+  getClues();
+});
+</script>
+<style scoped>
+
+</style>
\ No newline at end of file
diff --git a/src/views/overlay-clue/list/components/ClueList.vue b/src/views/overlay-clue/list/components/ClueList.vue
new file mode 100644
index 0000000..9aa4250
--- /dev/null
+++ b/src/views/overlay-clue/list/components/ClueList.vue
@@ -0,0 +1,138 @@
+<template>
+  <ul class="list-container">
+    <template v-for="(item, index) in dataList" :key="index">
+      <li
+        :class="
+          'list-item ' + (item.selected ? 'list-item__selected' : '')
+        "
+        @click="selectItem(item)"
+        v-if="!item.delete"
+      >
+        <div class="clue-item">
+          <div class="flex gap-1">
+            <div class="clue-num">{{ $nf(item.cid) }}</div>
+            <el-text class="fy-h1" truncated>{{ item.cclueName }}</el-text>
+          </div>
+          <div class="flex gap-1">
+            <div class="">
+              <el-text type="info" size="small">缁撹锛�</el-text>
+              <el-text size="small">{{ item.conclusionCount + '/1' }}</el-text>
+            </div>
+            <div class="">
+              <el-text type="info" size="small">闂锛�</el-text>
+              <el-text size="small">{{ item.questionCount }}</el-text>
+            </div>
+            <el-text type="info" size="small">{{item.cuploaded ? '宸叉帹閫�' : '鏈帹閫�'}}</el-text>
+          </div>
+          <el-row justify="space-between">
+            <span class="flex gap-1">
+              <el-tag v-if="item.csiteType" size="default" type="info">{{
+                item.csiteType
+              }}</el-tag>
+              <el-tag v-if="item.cfactor" size="default" effect="" type="danger">{{
+                item.cfactor
+              }}</el-tag>
+            </span>
+            <el-text size="small">涓嬪彂鏃堕棿锛歿{
+              $tf(item.creleaseTime)
+            }}</el-text>
+          </el-row>
+        </div>
+      </li>
+    </template>
+  </ul>
+</template>
+
+<script>
+import baseMapUtil from '@/components/map/baseMapUtil';
+
+var _marker;
+export default {
+  props: {
+    dataList: Array
+  },
+  emits: ['itemSelected'],
+  data() {
+    return {};
+  },
+  watch: {},
+  methods: {
+    // 鍒楄〃閫夋嫨
+    selectItem(item) {
+      this.clearSelect();
+      item.selected = true;
+      // const lnglat = baseMapUtil.wgs84togcj02(
+      //   item.clongitude,
+      //   item.clatitude
+      // );
+      baseMapUtil
+        .gpsConvert([item.clongitude, item.clatitude])
+        .then((lnglat) => {
+          baseMapUtil.removeView(_marker);
+          _marker = baseMapUtil.addMarker(lnglat);
+        });
+      this.$emit('itemSelected', item);
+    },
+    clearSelect() {
+      this.dataList.forEach((e) => {
+        e.selected = false;
+      });
+    }
+  }
+};
+</script>
+<style scoped>
+.list-container {
+  padding: initial;
+  /* border: var(--el-border); */
+  font-size: var(--el-font-size-base);
+}
+
+.list-item {
+  padding: 4px;
+  list-style-type: none;
+  border: var(--el-border);
+  border-radius: var(--el-border-radius-base);
+  box-shadow: var(--el-box-shadow-lighter);
+  margin-bottom: 6px;
+  cursor: pointer;
+}
+
+.list-item:hover {
+  background-color: var(--el-color-primary-light-9);
+}
+
+.list-item__selected {
+  background-color: var(--el-color-primary-light-9);
+}
+
+.v-enter-from,
+.v-leave-to {
+  opacity: 0;
+  transform: translateX(8px);
+}
+
+.v-enter-active,
+.v-leave-active {
+  transition: all 0.3s ease-out;
+}
+
+.clue-item {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.clue-num {
+  font-size: 16px;
+  font-weight: 700;
+  font-style: italic;
+  color: var(--el-color-primary);
+}
+
+.clue-tag {
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-start;
+}
+</style>
diff --git a/src/views/overlay-clue/report/ClueReport.vue b/src/views/overlay-clue/report/ClueReport.vue
new file mode 100644
index 0000000..97f98d4
--- /dev/null
+++ b/src/views/overlay-clue/report/ClueReport.vue
@@ -0,0 +1,107 @@
+<template>
+  <!-- 娓呭崟璇︽儏 -->
+  <CloseButton v-show="show" @close="closeEdit">
+    <el-button
+      class="push-btn"
+      :type="clueData.cuploaded ? 'success' : 'danger'"
+      @click="pushCheck"
+      :disabled="clueData.cuploaded"
+      ><div class="flex-col">
+        <template v-if="clueData.cuploaded">
+          <el-icon><Check /></el-icon>
+          <div>宸�</div>
+          <div>鎺�</div>
+          <div>閫�</div>
+        </template>
+        <template v-else>
+          <el-icon><Upload /></el-icon>
+          <div>鎺�</div>
+          <div>閫�</div>
+          <div>鍙�</div>
+          <div>棣�</div>
+        </template>
+      </div></el-button
+    >
+    <div class="fy-card">
+      <div class="fy-h1">绾跨储鍙嶉</div>
+      <el-scrollbar height="80vh" class="p-h-1">
+        <ClueReportClue :clue="clueData"></ClueReportClue>
+        <ClueReportConclusion
+          :clueId="clueData.cid"
+        ></ClueReportConclusion>
+        <ClueReportQuestion
+          :clueId="clueData.cid"
+        ></ClueReportQuestion>
+      </el-scrollbar>
+    </div>
+  </CloseButton>
+</template>
+
+<script>
+import ClueReportClue from './components/ClueReportClue.vue';
+import ClueReportConclusion from './components/ClueReportConclusion.vue';
+import ClueReportQuestion from './components/ClueReportQuestion.vue';
+import { useMessageBoxTip } from '@/composables/messageBox';
+import clueApi from "@/api/clue/clueApi";
+
+export default {
+  components: {
+    ClueReportClue,
+    ClueReportConclusion,
+    ClueReportQuestion
+  },
+  props: {
+    clueData: {
+      type: Object,
+      default: () => {
+        return {};
+      }
+    },
+    show: Boolean
+  },
+  emits: ['update:show'],
+  data() {
+    return {};
+  },
+  methods: {
+    closeEdit() {
+      this.$emit('update:show', false);
+    },
+    pushCheck() {
+      useMessageBoxTip({
+        confirmMsg: '绾跨储鎺ㄩ�佸悗鏃犳硶鍐嶄慨鏀圭粨璁轰笌闂锛岀‘璁ゆ帹閫侊紵',
+        confirmTitle: '绾跨储鎺ㄩ��',
+        onConfirm: () => {
+          return this.pushClue();
+        }
+      });
+    },
+    pushClue() {
+      return clueApi.pushClue(this.clueData.cid)
+    }
+  }
+};
+</script>
+
+<style scoped>
+.push-btn {
+  position: absolute;
+  z-index: 1;
+  top: 2rem;
+  left: -2.5rem;
+  width: 2.5rem;
+  height: initial;
+  margin: initial;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  /* background-color: white; */
+  /* border-color: white; */
+  /* border-top: 1px solid;
+  border-left: 1px solid;
+  border-bottom: 1px solid; */
+  border-top-right-radius: 0px;
+  border-bottom-right-radius: 0px;
+  /* box-shadow: var(--el-box-shadow-light); */
+}
+</style>
diff --git a/src/views/overlay-clue/report/components/ClueReportClue.vue b/src/views/overlay-clue/report/components/ClueReportClue.vue
new file mode 100644
index 0000000..6010893
--- /dev/null
+++ b/src/views/overlay-clue/report/components/ClueReportClue.vue
@@ -0,0 +1,51 @@
+<template>
+  <!-- 娓呭崟璇︽儏 -->
+  <DescriptionsList title="绾跨储娓呭崟璇︽儏">
+    <template #extra>
+      <el-button type="primary" text size="small" @click="openPDF"
+        >鏌ョ湅PDF</el-button
+      >
+    </template>
+    <DescriptionsListItem label="绾跨储缂栧彿" :content="clue.cid" />
+    <DescriptionsListItem
+      label="绾跨储鍚嶇О"
+      :content="clue.cclueName"
+    />
+    <DescriptionsListItem label="鍒涘缓鏃堕棿" :content="$tf(clue.ccreateTime)" />
+    <DescriptionsListItem label="涓嬪彂鏃堕棿" :content="$tf(clue.creleaseTime)" />
+    <DescriptionsListItem
+      label="鎶ヨ绔欑偣"
+      :content="clue.csiteName"
+    />
+    <DescriptionsListItem
+      label="绔欑偣绫诲瀷"
+      :content="clue.csiteType"
+    />
+    <DescriptionsListItem
+      label="绾跨储缁撹"
+      :content="clue.cconclusion"
+    />
+    <!-- <DescriptionsListItem 
+      label="绔欑偣绫诲瀷閫夐」"
+      :content="clue.cairCheckedOptions"
+    /> -->
+  </DescriptionsList>
+</template>
+
+<script>
+import clueApi from '@/api/clue/clueApi';
+
+export default {
+  props: {
+    clue: Object
+  },
+  methods: {
+    openPDF() {
+      window.open(
+        clueApi.fetchRemoteClueFileUrl(this.clue.cid),
+        '_blank'
+      );
+    }
+  },
+};
+</script>
diff --git a/src/views/overlay-clue/components/ClueReportConclusion.vue b/src/views/overlay-clue/report/components/ClueReportConclusion.vue
similarity index 79%
rename from src/views/overlay-clue/components/ClueReportConclusion.vue
rename to src/views/overlay-clue/report/components/ClueReportConclusion.vue
index a42a427..31634b2 100644
--- a/src/views/overlay-clue/components/ClueReportConclusion.vue
+++ b/src/views/overlay-clue/report/components/ClueReportConclusion.vue
@@ -1,31 +1,31 @@
 <template>
-  <div class="fy-h2">
-    绾跨储缁撹
-    <el-button
-      v-if="conclusion"
-      type="warning"
-      size="small"
-      plain
-      icon="Upload"
-      @click="pushConclusion"
-      :disabled="pushing ? true : conclusion.ccUploaded"
-      >{{ pushing ? '鎺ㄩ�佷腑' : pushText }}</el-button
-    >
-  </div>
-  <el-descriptions v-if="conclusion" :column="1" size="small" border>
-    <el-descriptions-item width="1px" min-width="30px">
-      <template #label>
-        <div class="descriptions-item">闂绫诲瀷</div>
-      </template>
-      {{ conclusion.ccQuestionType }}
-    </el-descriptions-item>
-    <el-descriptions-item label="绾跨储缁撹">
-      {{ conclusion.ccConclusion }}
-    </el-descriptions-item>
-    <el-descriptions-item label="璇︾粏鎻忚堪">
-      {{ conclusion.ccDetails }}
-    </el-descriptions-item>
-  </el-descriptions>
+  <div class="fy-h2">绾跨储缁撹</div>
+  <DescriptionsList v-if="conclusion">
+    <!-- <template #extra>
+      <el-button
+        v-if="conclusion"
+        type="warning"
+        size="small"
+        plain
+        icon="Upload"
+        @click="pushConclusion"
+        :disabled="pushing ? true : conclusion.ccUploaded"
+        >{{ pushing ? '鎺ㄩ�佷腑' : pushText }}</el-button
+      >
+    </template> -->
+    <DescriptionsListItem
+      label="闂绫诲瀷"
+      :content="conclusion.ccQuestionType"
+    />
+    <DescriptionsListItem
+      label="绾跨储缁撹"
+      :content="conclusion.ccConclusion"
+    />
+    <DescriptionsListItem
+      label="璇︾粏鎻忚堪"
+      :content="conclusion.ccDetails"
+    />
+  </DescriptionsList>
   <div v-else class="fy-dashed-border">
     <el-empty :image-size="50" description="绾跨储缁撹鏈笂浼�">
       <el-button type="primary" @click="openDialog"
@@ -187,7 +187,5 @@
 });
 </script>
 <style scoped>
-.descriptions-item {
-  /* background: aqua; */
-}
+
 </style>
diff --git a/src/views/overlay-clue/components/ClueReportQuestion.vue b/src/views/overlay-clue/report/components/ClueReportQuestion.vue
similarity index 61%
rename from src/views/overlay-clue/components/ClueReportQuestion.vue
rename to src/views/overlay-clue/report/components/ClueReportQuestion.vue
index cc68613..bec32fd 100644
--- a/src/views/overlay-clue/components/ClueReportQuestion.vue
+++ b/src/views/overlay-clue/report/components/ClueReportQuestion.vue
@@ -1,17 +1,11 @@
 <template>
   <div class="fy-h2">绾跨储闂</div>
   <template v-if="questionList.length > 0">
-    <el-descriptions
-      v-for="(item, index) in questionList"
-      :title="'闂 ' + item.cqUid"
-      :key="index"
-      :column="2"
-      size="small"
-      border
-    >
-      <template #extra>
-        <el-button-group>
-          <el-button
+    <template v-for="(item, index) in questionList" :key="index">
+      <DescriptionsList :title="item.cqUid">
+        <template #extra>
+          <el-button-group>
+            <!-- <el-button
             type="warning"
             size="small"
             plain
@@ -25,41 +19,30 @@
                 ? '鎺ㄩ�佷腑'
                 : '鎺ㄩ�侀棶棰�'
             }}</el-button
-          >
-          <el-button
-            type="primary"
-            size="small"
-            @click="checkQuestion(item)"
-            >闂璇︽儏</el-button
-          >
-        </el-button-group>
-      </template>
-      <el-descriptions-item
-        width="1px"
-        min-width="70px"
-        label="闂缂栧彿"
-      >
-        {{ item.cqUid }}
-      </el-descriptions-item>
-      <el-descriptions-item label="鎵�鍦ㄨ闀�">
-        {{ item.cqStreet }}
-      </el-descriptions-item>
-      <el-descriptions-item label="闂鎻忚堪">
-        {{ item.cqDescription }}
-      </el-descriptions-item>
-      <!-- <el-descriptions-item label="璇︾粏鍦板潃">
-        {{ item.cqAddress }}
-      </el-descriptions-item>
-      <el-descriptions-item label="缁忓害">
-        {{ item.cqLongitude }}
-      </el-descriptions-item>
-      <el-descriptions-item label="绾害">
-        {{ item.cqLatitude }}
-      </el-descriptions-item>
-      <el-descriptions-item label="鍒涘缓鏃堕棿">
-        {{ item.cqCreateTime }}
-      </el-descriptions-item> -->
-    </el-descriptions>
+          > -->
+            <el-button
+              type="primary"
+              size="small"
+              @click="checkQuestion(item)"
+              >闂璇︽儏</el-button
+            >
+          </el-button-group>
+        </template>
+        <DescriptionsListItem
+          label="闂鍚嶇О"
+          :content="item.cqName"
+        />
+        <DescriptionsListItem
+          label="鎵�鍦ㄨ闀�"
+          :content="item.cqStreet"
+        />
+        <DescriptionsListItem
+          label="闂鎻忚堪"
+          :content="item.cqDescription"
+        />
+      </DescriptionsList>
+      <!-- <el-divider /> -->
+    </template>
     <div class="btn-wrap">
       <el-button type="primary" @click="openDialog"
         >娣诲姞闂</el-button
@@ -128,7 +111,6 @@
     item.pushing = true;
   });
 }
-
 </script>
 <style scoped>
 .btn-wrap {
diff --git a/src/views/overlay-clue/components/QuestionDetail.vue b/src/views/overlay-clue/report/components/QuestionDetail.vue
similarity index 76%
rename from src/views/overlay-clue/components/QuestionDetail.vue
rename to src/views/overlay-clue/report/components/QuestionDetail.vue
index ac67c93..81c524a 100644
--- a/src/views/overlay-clue/components/QuestionDetail.vue
+++ b/src/views/overlay-clue/report/components/QuestionDetail.vue
@@ -7,7 +7,7 @@
     destroy-on-close
   >
     <template #header>
-      <span> 鍙嶉闂</span>
+      <span> 娣诲姞闂</span>
     </template>
     <el-form
       label-width="90px"
@@ -16,11 +16,17 @@
       :model="formObj"
       ref="formRef"
     >
+      <el-form-item label="闂鍚嶇О" prop="cqName">
+        <el-input
+          v-model="formObj.cqName"
+          placeholder="璇疯緭鍏ラ棶棰樺悕绉�"
+        ></el-input>
+      </el-form-item>
       <el-form-item label="闂鎻忚堪" prop="cqDescription">
         <el-input
           v-model="formObj.cqDescription"
           type="textarea"
-          placeholder="璇疯緭鍏ヨ鎯�"
+          placeholder="璇疯緭鍏ラ棶棰樻弿杩�"
         ></el-input>
       </el-form-item>
       <el-form-item label="鎵�鍦ㄨ闀�" prop="cqStreet">
@@ -34,7 +40,10 @@
         </el-select>
       </el-form-item>
       <el-form-item label="璇︾粏鍦板潃" prop="cqAddress">
-        <el-input v-model="formObj.cqAddress"></el-input>
+        <el-input
+          v-model="formObj.cqAddress"
+          placeholder="璇疯緭鍏ュ湴鍧�鎴栬�呴�氳繃鈥滃潗鏍囨嬀鍙栤�濊嚜鍔ㄨ幏寰�"
+        ></el-input>
       </el-form-item>
       <el-form-item label="鍧愭爣" prop="coordinate">
         <el-input
@@ -57,6 +66,7 @@
           accept="image/png, image/jpeg"
           :limit="3"
           multiple
+          :on-preview="handleFilePreview"
           :on-remove="handleFileRemove"
           :on-change="handleFileChange"
         >
@@ -80,6 +90,13 @@
       >
     </template>
   </el-dialog>
+  <el-image-viewer
+    v-if="previewShow"
+    :url-list="urlList"
+    :initial-index="initialIndex"
+    :infinite="false"
+    @close="closePreview"
+  ></el-image-viewer>
   <MapSearch
     v-model:show="mapDialogShow"
     @on-submit="selectAddress"
@@ -99,13 +116,30 @@
   question: Object
 });
 
-const emit = defineEmits(['update:show', 'onSubmit'], ['onClose']);
+const emit = defineEmits(['update:show', 'onSubmit', 'onClose']);
 
 // 涓婃姤寮瑰嚭妗�
 const dialogShow = ref(false);
 const mapDialogShow = ref(false);
 const uploadRef = ref();
 const fileList = ref([]);
+
+const previewShow = ref(false);
+const initialIndex = ref(0);
+const urlList = computed(() =>
+  fileList.value.map((value) => {
+    return value.url;
+  })
+);
+
+function handleFilePreview(file) {
+  initialIndex.value = fileList.value.indexOf(file);
+  previewShow.value = true;
+}
+
+function closePreview() {
+  previewShow.value = false;
+}
 
 function handleFileRemove(file, fileList) {
   formObj.value.files = fileList;
@@ -127,6 +161,13 @@
 const loading = ref(false);
 // 琛ㄥ崟妫�鏌ヨ鍒�
 const rules = reactive({
+  cqName: [
+    {
+      required: true,
+      message: '闂鍚嶇О涓嶈兘涓虹┖',
+      trigger: 'blur'
+    }
+  ],
   cqDescription: [
     {
       required: true,
@@ -182,6 +223,7 @@
   const coor = formObj.value.coordinate.split(',');
   const q = {
     cId: parseInt(props.clueId),
+    cqName: formObj.value.cqName,
     cqDescription: formObj.value.cqDescription,
     cqStreet: formObj.value.cqStreet,
     cqAddress: formObj.value.cqAddress,
@@ -238,26 +280,34 @@
       url: f
     });
   });
-  return question;
+  return { ...question };
 }
 
 watch(
-  () => props.show,
+  () => [props.show, props.question],
   (val) => {
-    dialogShow.value = val;
-  }
-);
-watch(
-  () => props.question,
-  (val) => {
-    fileList.value = [];
-    if (val) {
-      formObj.value = parseFormObj(val);
-    } else {
-      formObj.value = {};
+    dialogShow.value = val[0];
+    if (val[0]) {
+      fileList.value = [];
+      if (val[1]) {
+        formObj.value = parseFormObj(val[1]);        
+      } else {
+        formObj.value = {};
+      }
     }
   }
 );
+// watch(
+//   () => props.question,
+//   (val) => {
+//     fileList.value = [];
+//     if (val) {
+//       formObj.value = parseFormObj(val);
+//     } else {
+//       formObj.value = {};
+//     }
+//   }
+// );
 watch(dialogShow, (val) => {
   emit('update:show', val);
 });
diff --git a/src/views/overlay-grid/GridLayout.vue b/src/views/overlay-grid/GridLayout.vue
index aeb080d..8a65679 100644
--- a/src/views/overlay-grid/GridLayout.vue
+++ b/src/views/overlay-grid/GridLayout.vue
@@ -1,16 +1,36 @@
 <template>
-  <el-row class="container">
-    <el-col :span="4" class="grid-content bg-content">
-      <div class="title">缃戞牸鍖栫鐞�</div>
-      <OptionGridRecord v-model:value="gridRecord"></OptionGridRecord>
-      <ListGridDetail></ListGridDetail>
-      <GridCreate
-        :is-active="dialogVisible"
-        :is-create="true"
-      ></GridCreate>
+  <el-row class="fy-body fy-overlay-container" gutter="0">
+    <el-col :span="6">
+      <div class="fy-card fy-main">
+        <div class="fy-h1">缃戞牸鍖栫鐞�</div>
+        <div class="fy-flex-row">
+          <span>鏂规</span>
+          <OptionGridRecord
+            :refresh="newScheme"
+            v-model:value="gridSchemeId"
+          ></OptionGridRecord>
+          <el-button
+            :disabled="!gridSchemeId"
+            icon="Search"
+            type="primary"
+            @click="getSchemeList"
+            >鏌ヨ</el-button
+          >
+          <SchemeCreate @created="newScheme = !newScheme"></SchemeCreate>
+        </div>
+        <ListGridDetail></ListGridDetail>
+        <GridCreate
+          :schemeId="gridSchemeId"
+          :is-active="dialogVisible"
+        ></GridCreate>
+      </div>
     </el-col>
-    <el-col v-if="gridStore.selectedGrid" :span="4" class="grid-content bg-content-1">
-      <GridEditing></GridEditing>
+    <el-col v-if="gridStore.selectedGrid" :span="4" :offset="14">
+      <div class="fy-column-reverse">
+        <!-- <div class="fy-card"> -->
+        <GridEditing></GridEditing>
+        <!-- </div> -->
+      </div>
     </el-col>
   </el-row>
 </template>
@@ -20,60 +40,31 @@
 import OptionGridRecord from './components/OptionGridRecord.vue';
 import GridCreate from './components/GridCreate.vue';
 import GridEditing from './components/GridEditing.vue';
+import SchemeCreate from './components/SchemeCreate.vue';
 
 import { ref, watch } from 'vue';
 import baseMapUtil from '@/components/map/baseMapUtil';
 import { onMapMounted } from '@/components/map/baseMap';
-import gridRecordApi from '@/api/gridRecordApi';
+import gridInfoApi from '@/api/grid/gridInfoApi';
 import { useGridStore } from '@/stores/grid';
 
 const gridStore = useGridStore();
 
 // 缃戞牸瑙勫垝鏂规id
-const gridRecord = ref({ value: '' });
-// 鏂规鍏蜂綋缃戞牸淇℃伅闆嗗悎
+const gridSchemeId = ref();
 
-const getList = function () {
+const newScheme = ref(false)
+
+// 鑾峰彇鏂规璁板綍
+function getSchemeList() {
   onMapMounted(() => {
-    gridRecordApi
-      .getGridRecordDetail(gridRecord.value)
-      .then((res) => {
-        baseMapUtil.clearMap();
-        gridStore.setGridList(res);
-      });
+    gridInfoApi.fetchGridList(gridSchemeId.value).then((res) => {
+      baseMapUtil.clearMap();
+      gridStore.setGridList(res);
+    });
   });
-};
+}
 
-watch(gridRecord, getList);
+// watch(gridSchemeId, getList);
 </script>
-<style scoped>
-.container {
-  pointer-events: none;
-}
-
-.title {
-  font-size: var(--el-font-size-large);
-}
-
-.grid-content {
-  min-width: calc(var(--screen-min-width) / 6);
-  /* min-width: 180px; */
-  border-radius: 4px;
-  display: flex;
-  flex-direction: column;
-  gap: 16px;
-  padding: 8px 8px;
-  pointer-events: auto;
-}
-
-.bg-content {
-  height: 90vh;
-  background: white;
-}
-
-.bg-content-1 {
-  background: antiquewhite;
-  height: 40vh;
-  margin-left: 8px;
-}
-</style>
+<style scoped></style>
diff --git a/src/views/overlay-grid/components/GridCreate.vue b/src/views/overlay-grid/components/GridCreate.vue
index 45f2caa..6152df0 100644
--- a/src/views/overlay-grid/components/GridCreate.vue
+++ b/src/views/overlay-grid/components/GridCreate.vue
@@ -1,8 +1,19 @@
 <template>
   <div>
-    <el-button type="primary" @click="createGrid">鏂板缓缃戞牸</el-button>
+    <el-button
+      :disabled="!gridStore.hasScheme"
+      type="primary"
+      @click="createGrid"
+      >鏂板缓缃戞牸</el-button
+    >
   </div>
-  <el-dialog v-model="dialogVisible" title="Tips" width="30%">
+  <el-dialog
+    v-model="dialogVisible"
+    title="鏂板缓缃戞牸"
+    width="600px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+  >
     <el-form
       :inline="false"
       :model="formObj"
@@ -39,6 +50,8 @@
 import { useGridStore } from '@/stores/grid';
 import mapGrid from '@/components/map/mapGrid';
 import baseMapUtil from '@/components/map/baseMapUtil';
+import gridInfoApi from '@/api/grid/gridInfoApi';
+import { getGridRecord } from '@/model/gridRecord';
 
 const gridStore = useGridStore();
 
@@ -56,15 +69,11 @@
 };
 
 const props = defineProps({
-  //鍩烘湰淇℃伅
-  formInfo: Object,
-  //鏄垱寤烘垨鑰呮洿鏂�
-  isCreate: Boolean,
+  //缃戞牸鏂规id
+  schemeId: String,
   //
   isActive: Boolean
 });
-
-const emit = defineEmits(['onSubmit', 'onCancel']);
 
 const { formObj, formRef, edit, active, onSubmit, onCancel } =
   useFormConfirm({
@@ -88,60 +97,43 @@
   ]
 });
 
-// 鍒涘缓
-async function create() {
-  // loading.value = true;
-  // return sceneApi
-  //   .createScene(formObj.value)
-  //   .then((res) => {
-  //     console.log(res);
-  //   })
-  //   .finally(() => {
-  //     loading.value = false;
-  //   });
-}
-
-// 鏇存柊
-async function update() {
-  // loading.value = true;
-  // return sceneApi
-  //   .updateScene(formObj.value)
-  //   .then((res) => {
-  //     console.log(res);
-  //   })
-  //   .finally(() => {
-  //     loading.value = false;
-  //   });
-}
-
 function submit() {
-  const newRecord = {
-    gId: '1',
-    gType: 0,
-    gName: formObj.value.gName,
-    gSide: tempOverlays.getPath(),
-    overlays: tempOverlays
+  const gridInfo = {
+    gsId: props.schemeId,
+    giName: formObj.value.gName,
+    giSide: tempOverlays.getPath().join(';')
   };
 
-  gridStore.addGrid(newRecord);
-  emit('onSubmit', newRecord);
-  dialogVisible.value = false;
+  loading.value = true;
+  return gridInfoApi
+    .createGrid(gridInfo)
+    .then((res) => {
+      const newRecord = {
+        ...res,
+        overlays: tempOverlays
+      };
 
-  return props.isCreate ? create() : update();
+      gridStore.addGrid(getGridRecord(newRecord));
+      dialogVisible.value = false;
+    })
+    // .catch((err) => {
+    //   return err;
+    // })
+    .finally(() => (loading.value = false));
 }
+
 function cancel() {
-  emit('onCancel');
   baseMapUtil.removeView(tempOverlays);
   dialogVisible.value = false;
 }
 
-watch(
-  () => props.formInfo,
-  (nValue) => {
-    formObj.value = nValue;
-  },
-  { immediate: false }
-);
+// watch(
+//   () => props.formInfo,
+//   (nValue) => {
+//     formObj.value = nValue;
+//   },
+//   { immediate: false }
+// );
 
 watch(dialogVisible, (nValue) => {
   active.value = nValue;
diff --git a/src/views/overlay-grid/components/GridEditing.vue b/src/views/overlay-grid/components/GridEditing.vue
index 3d7d5d1..619aae4 100644
--- a/src/views/overlay-grid/components/GridEditing.vue
+++ b/src/views/overlay-grid/components/GridEditing.vue
@@ -1,18 +1,53 @@
 <template>
-  <div>褰撳墠鎿嶄綔缃戞牸: {{ selectedGrid.gName }}</div>
-  <div>缃戞牸缂栧彿锛歿{ selectedGrid.gId }}</div>
-  <div>缃戞牸椤剁偣锛�</div>
-  <div v-for="(item, index) in selectedGrid.gSide" :key="index">
-    {{ item }}
-  </div>
-  <div class="btn-group">
-    <el-button @click="startOrCompleteEdit">{{
-      isEdit ? '淇濆瓨缂栬緫' : '寮�濮嬬紪杈�'
-    }}</el-button>
-    <el-button :disabled="!isEdit" @click="cancelEdit"
-      >鍙栨秷缂栬緫</el-button
-    >
-  </div>
+  <CloseButton @close="closeEdit">
+    <div class="fy-card">
+      <div class="fy-h2">姝e湪缂栬緫缃戞牸</div>
+      <div class="fy-main">
+        <el-form
+          label-position="left"
+          :rules="rules"
+          :model="selectedGrid"
+          ref="formRef"
+        >
+          <!-- <el-form-item label="缂栧彿" prop="id">
+          <div>{{ selectedGrid.id }}</div>
+        </el-form-item> -->
+          <el-form-item label="鍚嶇О" prop="name">
+            <el-input
+              v-model="selectedGrid.name"
+              placeholder="璇疯緭鍏ョ綉鏍煎悕绉�"
+              clearable
+              disabled
+            ></el-input>
+          </el-form-item>
+          <el-form-item label="椤剁偣" prop="sides">
+            <el-scrollbar height="100px" class="fy-main-border">
+              <div
+                class="list-wrapper"
+                v-for="(item, index) in selectedGrid.sides"
+                :key="index"
+              >
+                {{ `${index + 1}. ${item}` }}
+              </div>
+            </el-scrollbar>
+          </el-form-item>
+        </el-form>
+        <div class="btn-group">
+          <el-button
+            @click="startOrCompleteEdit"
+            :loading="loading"
+            >{{ isEdit ? '淇濆瓨缂栬緫' : '寮�濮嬬紪杈�' }}</el-button
+          >
+          <el-button
+            type="danger"
+            :disabled="!isEdit"
+            @click="cancelEdit"
+            >鍙栨秷缂栬緫</el-button
+          >
+        </div>
+      </div>
+    </div>
+  </CloseButton>
 </template>
 
 <script>
@@ -30,7 +65,8 @@
   },
   data() {
     return {
-      editing: false
+      editing: false,
+      loading: false
     };
   },
   computed: {
@@ -41,13 +77,18 @@
       'openGridEdit',
       'closeGridEdit',
       'saveSelectedGrid',
-      'quitSelectedGrid'
+      'quitSelectedGrid',
+      'clearSelect'
     ]),
     // 寮�濮嬫垨瀹屾垚淇濆瓨缂栬緫缃戞牸澶氳竟褰�
     startOrCompleteEdit() {
       if (this.isEdit) {
-        this.saveSelectedGrid();
-        this.closeGridEdit();
+        this.loading = true;
+        this.saveSelectedGrid()
+          .then(() => {
+            this.closeGridEdit();
+          })
+          .finally(() => (this.loading = false));
       } else {
         this.openGridEdit();
       }
@@ -56,12 +97,27 @@
     cancelEdit() {
       this.quitSelectedGrid();
       this.closeGridEdit();
+    },
+    // 鍏抽棴缂栬緫鐣岄潰
+    closeEdit() {
+      this.cancelEdit();
+      this.clearSelect();
     }
   }
 };
 </script>
 <style scoped>
+.wrapper {
+  /* position: relative;
+  background: antiquewhite;
+  padding-right: 10px; */
+}
 .btn-group {
   display: flex;
 }
-</style>
\ No newline at end of file
+
+.list-wrapper {
+  width: 100%;
+  /* background-color: var(--el-bg-color-page); */
+}
+</style>
diff --git a/src/views/overlay-grid/components/ListGridDetail.vue b/src/views/overlay-grid/components/ListGridDetail.vue
index 1ac26cf..d08b3d9 100644
--- a/src/views/overlay-grid/components/ListGridDetail.vue
+++ b/src/views/overlay-grid/components/ListGridDetail.vue
@@ -14,8 +14,8 @@
             v-if="!item.delete"
           >
             <div style="display: flex; gap: 8px">
-              <div>{{ item.gName }}</div>
-              <div>{{ item.gSide.length + '/4' }}</div>
+              <div>{{ item.name }}</div>
+              <div>{{ item.sides.length + '/4' }}</div>
             </div>
             <Transition>
               <el-button
@@ -83,7 +83,7 @@
 .list-container {
   /* background-color: antiquewhite; */
   padding: initial;
-  height: 50vh;
+  height: 60vh;
   overflow: auto;
   overflow-x: hidden;
   border: var(--el-border);
diff --git a/src/views/overlay-grid/components/OptionGridRecord.vue b/src/views/overlay-grid/components/OptionGridRecord.vue
index ce725c9..a37eef8 100644
--- a/src/views/overlay-grid/components/OptionGridRecord.vue
+++ b/src/views/overlay-grid/components/OptionGridRecord.vue
@@ -2,13 +2,13 @@
   <el-select
     v-model="selectedOptions"
     placeholder="缃戞牸鍖栬褰�"
-    style="max-width: 150px"
+    style="width: 150px"
   >
     <el-option
       v-for="s in options"
-      :key="s.value"
-      :label="s.label"
-      :value="s"
+      :key="s.gsId"
+      :label="s.gsName"
+      :value="s.gsId"
     />
   </el-select>
 </template>
@@ -16,17 +16,14 @@
 <script>
 // 缃戞牸鍖栨柟妗堣褰曢�夐」
 
-import gridRecordApi from '@/api/gridRecordApi';
+import gridSchemeApi from '@/api/grid/gridSchemeApi';
 
 export default {
   props: {
-    // 鏄惁鍦ㄩ閫夐」澶勬坊鍔犫�滃叏閮ㄢ�濋�夐」
-    // allOption: {
-    //   type: Boolean,
-    //   default: true,
-    // },
+    // 鍒锋柊涓嬫媺鍒楄〃
+    refresh: Boolean,
     // 杩斿洖缁撴灉
-    value: Object,
+    value: Number
   },
   data() {
     return {
@@ -35,18 +32,29 @@
     };
   },
   watch: {
-    selectedOptions: {
-      handler(val) {
-        this.$emit('update:value', val);
-      },
-      deep: true,
+    // selectedOptions: {
+    //   handler(val) {
+    //     this.$emit('update:value', val);
+    //   },
+    //   deep: true
+    // },
+    selectedOptions(nVal) {
+      this.$emit('update:value', nVal);
     },
+    refresh() {
+      this.getOptions(true);
+    }
   },
   methods: {
-    getOptions() {
-      gridRecordApi.getGridRecords().then((res) => {
-        this.options = res;
-        this.selectedOptions = res[0];
+    getOptions(newScheme) {
+      gridSchemeApi.fetchAllSchemes().then((res) => {
+        if (res.length > 0) {
+          this.options = res;
+          this.selectedOptions = newScheme
+            ? res[res.length - 1].gsId
+            : res[0].gsId;
+          // this.selectedOptions = res[0].gsId;
+        }
       });
     }
   },
diff --git a/src/views/overlay-grid/components/SchemeCreate.vue b/src/views/overlay-grid/components/SchemeCreate.vue
new file mode 100644
index 0000000..a6da646
--- /dev/null
+++ b/src/views/overlay-grid/components/SchemeCreate.vue
@@ -0,0 +1,101 @@
+<template>
+  <el-button icon="Plus" type="success" plain @click="openDialog"
+    >鏂板缓鏂规</el-button
+  >
+  <el-dialog
+    v-model="dialogShow"
+    width="600px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    destroy-on-close
+  >
+    <template #header>
+      <span> 鏂板缓鏂规</span>
+    </template>
+    <el-form
+      label-width="90px"
+      label-position="left"
+      :rules="rules"
+      :model="formObj"
+      ref="formRef"
+    >
+      <el-form-item label="鏂规鍚嶇О" prop="gsName">
+        <el-input
+          style="width: 400px"
+          v-model="formObj.gsName"
+          placeholder="璇疯緭鍏ユ柟妗堝悕绉�"
+        ></el-input>
+      </el-form-item>
+      <el-form-item label="鏂规鎻忚堪" prop="gsDescription">
+        <el-input
+          v-model="formObj.gsDescription"
+          type="textarea"
+          placeholder="璇疯緭鍏ユ柟妗堟弿杩�"
+        ></el-input>
+      </el-form-item>
+      <el-form-item label="鍒涘缓浜�" prop="gsCreatorName">
+        <el-input
+          style="width: 200px"
+          v-model="formObj.gsCreatorName"
+          placeholder="璇疯緭鍏ュ垱寤轰汉"
+        ></el-input>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="onCancel">鍙栨秷</el-button>
+      <el-button
+        :disabled="!edit"
+        type="primary"
+        :loading="loading"
+        @click="onSubmit"
+        >纭畾</el-button
+      >
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { reactive, ref, watch } from 'vue';
+import { useFormConfirm } from '@/composables/formConfirm';
+import gridSchemeApi from '@/api/grid/gridSchemeApi';
+
+const emit = defineEmits(['created']);
+
+const rules = reactive({
+  gsName: [
+    {
+      required: true,
+      message: '鏂规鍚嶇О涓嶈兘涓虹┖',
+      trigger: 'blur'
+    }
+  ]
+});
+
+const { formObj, formRef, edit, onSubmit, onCancel } = useFormConfirm(
+  {
+    submit: {
+      do: submit
+    },
+    cancel: {
+      do: cancel
+    }
+  }
+);
+
+const dialogShow = ref(false);
+function openDialog() {
+  dialogShow.value = true;
+}
+
+function submit() {
+  return gridSchemeApi.createScheme(formObj.value).then(() => {
+    // clear()
+    emit('created')
+    dialogShow.value = false;
+  });
+}
+
+function cancel() {
+  dialogShow.value = false;
+}
+</script>
diff --git a/vite.config.js b/vite.config.js
index ea3fe35..56719e5 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -26,5 +26,8 @@
     alias: {
       '@': fileURLToPath(new URL('./src', import.meta.url))
     }
+  },
+  server: {
+    host: '0.0.0.0'
   }
 });

--
Gitblit v1.9.3