From 1534aee0339dee8000cdd26c21797cf3ad391f7a Mon Sep 17 00:00:00 2001
From: riku <risaku@163.com>
Date: 星期一, 06 五月 2024 17:33:23 +0800
Subject: [PATCH] 新增折线图模块功能

---
 src/utils/chart/chart-option.js                   |  100 +++++
 src/assets/main.css                               |    6 
 src/components/SliderBar.vue                      |   51 ++
 src/views/historymode/HistoryMode.vue             |   29 +
 package-lock.json                                 |   59 +++
 src/assets/border.css                             |   11 
 src/components/monitor/FactorCheckbox.vue         |   64 +++
 src/components.d.ts                               |    8 
 src/components/search/SearchBar.vue               |   17 
 src/assets/3dmap.css                              |   89 ----
 src/components/map/MapToolbox.vue                 |   16 
 src/components/monitor/FactorLegend.vue           |  123 ++++++
 src/components/core/CoreMenu.vue                  |   63 +++
 src/model/Legend.js                               |   73 ---
 src/components/monitor/FactorRadio.vue            |    4 
 src/constant/factor-name.js                       |    2 
 package.json                                      |    1 
 src/components/core/CoreHeader.vue                |    2 
 src/constant/checkbox-options.js                  |  186 +++++++++
 src/styles/base.scss                              |    4 
 src/components/BaseCard.vue                       |   20 
 src/views/historymode/component/TrendAnalysis.vue |   41 ++
 src/components/monitor/LineChart.vue              |  115 ++++++
 23 files changed, 889 insertions(+), 195 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index e5db76b..e21b6be 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,7 @@
         "@fortawesome/vue-fontawesome": "^3.0.6",
         "@vueuse/core": "^10.9.0",
         "axios": "^1.6.8",
+        "echarts": "^5.5.0",
         "element-plus": "^2.6.2",
         "moment": "^2.30.1",
         "pinia": "^2.1.7",
@@ -1712,6 +1713,20 @@
       "resolved": "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
       "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
       "dev": true
+    },
+    "node_modules/echarts": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.5.0.tgz",
+      "integrity": "sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==",
+      "dependencies": {
+        "tslib": "2.3.0",
+        "zrender": "5.5.0"
+      }
+    },
+    "node_modules/echarts/node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
     },
     "node_modules/editorconfig": {
       "version": "1.0.4",
@@ -4424,6 +4439,19 @@
       "engines": {
         "node": ">=10"
       }
+    },
+    "node_modules/zrender": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.5.0.tgz",
+      "integrity": "sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==",
+      "dependencies": {
+        "tslib": "2.3.0"
+      }
+    },
+    "node_modules/zrender/node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
     }
   },
   "dependencies": {
@@ -5582,6 +5610,22 @@
       "resolved": "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
       "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
       "dev": true
+    },
+    "echarts": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.5.0.tgz",
+      "integrity": "sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==",
+      "requires": {
+        "tslib": "2.3.0",
+        "zrender": "5.5.0"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+          "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
+        }
+      }
     },
     "editorconfig": {
       "version": "1.0.4",
@@ -7604,6 +7648,21 @@
       "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz",
       "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
       "dev": true
+    },
+    "zrender": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.5.0.tgz",
+      "integrity": "sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==",
+      "requires": {
+        "tslib": "2.3.0"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+          "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
+        }
+      }
     }
   }
 }
diff --git a/package.json b/package.json
index cac16c5..b754274 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
     "@fortawesome/vue-fontawesome": "^3.0.6",
     "@vueuse/core": "^10.9.0",
     "axios": "^1.6.8",
+    "echarts": "^5.5.0",
     "element-plus": "^2.6.2",
     "moment": "^2.30.1",
     "pinia": "^2.1.7",
diff --git a/src/assets/3dmap.css b/src/assets/3dmap.css
index 489d0bf..8f4779b 100644
--- a/src/assets/3dmap.css
+++ b/src/assets/3dmap.css
@@ -95,14 +95,7 @@
     /* color: #009414; */
 }
 
-.map-date-selector {
-    position: relative;
-    left: 0;
-    right: 0;
-    top: 0px;
-    /* padding: 0 4px; */
-    /* color: ffffffbd; */
-}
+
 
 .map-animation-status {
     position: absolute;
@@ -159,86 +152,6 @@
     right: 0;
     top: 0;
     z-index: 1000px;
-}
-
-.map-mode-change {
-    position: absolute;
-    left: 10px;
-    top: 104px;
-    z-index: 1000px;
-    padding: 1px;
-    font-size: 1rem;
-    font-weight: 900;
-    /* background-color: #ffffffad; */
-}
-
-.map-mode-change a:hover {
-    color: antiquewhite;
-}
-
-.map-mode-change .mode-btn {
-    white-space: nowrap;
-}
-
-.map-mode-change .btn-selected {
-    color: #ffffff;
-    padding: 4px 16px;
-    cursor: pointer;
-    border: 2px solid var(--border-color);
-    background-color: #23dad1;
-    transform: skew(-30deg);
-    text-decoration: none;
-    /* -moz-border-radius: 6px; Old Firefox */
-    /* box-shadow: 5px 0px 2.5px #888888; */
-}
-
-.map-mode-change .btn-selected div {
-    transform: skew(30deg);
-}
-
-.map-mode-change .btn-unselected {
-    color: #5555557e;
-    padding: 4px 16px;
-    cursor: pointer;
-    background-color: #ffffff;
-    border: 2px solid #00000015;
-    transform: skew(-30deg);
-    text-decoration: none;
-    /* -moz-border-radius: 6px; Old Firefox */
-    /* box-shadow: 5px 0px 2.5px #888888; */
-}
-
-.map-mode-change .btn-unselected div {
-    transform: skew(30deg);
-}
-
-.map-date-selector .label-date {
-    display: box;
-    display: -webkit-box;
-    /* OLD - iOS 6-, Safari 3.1-6 */
-    display: -moz-box;
-    /* OLD - Firefox 19- (buggy but mostly works) */
-    display: -ms-flexbox;
-    /* TWEENER - IE 10 */
-    display: -webkit-flex;
-    /* NEW - Chrome */
-    display: flex;
-    align-items: center;
-    -webkit-align-items: center;
-    box-align: center;
-    -moz-box-align: center;
-    -webkit-box-align: center;
-    /* margin-left: 0.5rem; */
-    /* margin-top: 0.8rem; */
-    /* background: #d14646; */
-    height: auto;
-}
-
-.map-date-selector .label-date .label-date-title {
-    /* margin-right: 15px; */
-    /* color: #000000; */
-    font-size: 1rem;
-    line-height: 1.11rem;
 }
 
 .map-legend {
diff --git a/src/assets/border.css b/src/assets/border.css
index a8a5118..533bde8 100644
--- a/src/assets/border.css
+++ b/src/assets/border.css
@@ -41,7 +41,7 @@
     width: 100%;
     height: 100%;
     background-color: var(--border-color);
-    z-index: 1000px;
+    /* z-index: 1000px; */
 }
 
 /* 鑳屾櫙 */
@@ -49,13 +49,13 @@
     position: relative;
     color: var(--font-color);
     background-color: var(--bg-color);
-    z-index: 1000px;
+    /* z-index: 1000; */
 }
 
 .ff-border-top img.ff-img {
     width: 16px;
     height: 16px;
-    margin-bottom: 6px;
+    /* margin-bottom: 6px; */
 }
 
 /* 涓夎鏍峰紡 */
@@ -97,8 +97,9 @@
     position: absolute;
     color: var(--font-color);
     width: calc(100% - var(--border-width) * 2);
-    min-height: calc((var(--bevel-length-2) - var(--border-width)) * 1);
+    /* min-height: calc((var(--bevel-length-2) - var(--border-width)) * 1); */
     /* background-color: brown; */
+    z-index: 10;
 }
 
 /*****************杈规鍩烘湰灞炴�� - end **************************/
@@ -366,7 +367,7 @@
 /* 鍐呭 */
 .ff-content {
     position: relative;
-    display: inline-block;
+    display: block;
 }
 
 /* .ff-content .ff-border-top {
diff --git a/src/assets/main.css b/src/assets/main.css
index 6cae802..36e347e 100644
--- a/src/assets/main.css
+++ b/src/assets/main.css
@@ -8,6 +8,10 @@
   font-weight: normal;
 }
 
-.fy-container {
+.p-events-auto {
   pointer-events: auto;
+}
+
+.p-events-none {
+  pointer-events: none;
 }
\ No newline at end of file
diff --git a/src/components.d.ts b/src/components.d.ts
index f936b4c..1d653d4 100644
--- a/src/components.d.ts
+++ b/src/components.d.ts
@@ -12,6 +12,9 @@
     CoreHeader: typeof import('./components/core/CoreHeader.vue')['default']
     CoreMenu: typeof import('./components/core/CoreMenu.vue')['default']
     ElButton: typeof import('element-plus/es')['ElButton']
+    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
+    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
+    ElCol: typeof import('element-plus/es')['ElCol']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
     ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
     ElDropdown: typeof import('element-plus/es')['ElDropdown']
@@ -25,7 +28,11 @@
     ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElSelect: typeof import('element-plus/es')['ElSelect']
+    ElSlider: typeof import('element-plus/es')['ElSlider']
+    FactorCheckbox: typeof import('./components/monitor/FactorCheckbox.vue')['default']
+    FactorLegend: typeof import('./components/monitor/FactorLegend.vue')['default']
     FactorRadio: typeof import('./components/monitor/FactorRadio.vue')['default']
+    LineChart: typeof import('./components/monitor/LineChart.vue')['default']
     MapToolbox: typeof import('./components/map/MapToolbox.vue')['default']
     OptionDevice: typeof import('./components/search/OptionDevice.vue')['default']
     OptionMission: typeof import('./components/search/OptionMission.vue')['default']
@@ -34,5 +41,6 @@
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     SearchBar: typeof import('./components/search/SearchBar.vue')['default']
+    SliderBar: typeof import('./components/SliderBar.vue')['default']
   }
 }
diff --git a/src/components/BaseCard.vue b/src/components/BaseCard.vue
index a24f197..a01f0a0 100644
--- a/src/components/BaseCard.vue
+++ b/src/components/BaseCard.vue
@@ -6,6 +6,12 @@
         <slot name="content"></slot>
       </div>
     </div>
+    <div class="ff-footer">
+      <slot name="footer"></slot>
+    </div>
+    <div v-if="size != 'small'" class="ff-triangle">
+      <div class="ff-triangle-border"></div>
+    </div>
   </div>
 </template>
 
@@ -23,18 +29,26 @@
     },
     /**
      * 鏍峰紡鏈濆悜
-     * left | right
+     * left | right | top-left
      */
     direction: {
       type: String,
       default: 'left'
+    },
+    /**
+     * 閫夋嫨鏃犺竟妗嗘柟鍚�
+     * r锛堝彸渚ф棤杈规锛� | t锛堥《閮ㄦ棤杈规锛�
+     */
+    borderless: {
+      type: String
     }
   },
   computed: {
     wrapClz() {
-      let clz = 'ff-content fy-container';
-      clz += ` ff-content-${this.size}`;
+      let clz = 'ff-content p-events-auto';
       clz += ` ff-content-${this.direction}`;
+      clz += ` ff-content-${this.size}`;
+      clz += `${this.borderless ? '-borderless-' + this.borderless : ''}`;
       return clz;
     }
   }
diff --git a/src/components/SliderBar.vue b/src/components/SliderBar.vue
new file mode 100644
index 0000000..4bb44f7
--- /dev/null
+++ b/src/components/SliderBar.vue
@@ -0,0 +1,51 @@
+<template>
+  <el-row justify="center" align="middle" class="wrap">
+    <el-form-item label="鏁版嵁閲�">
+      <el-select
+        v-model="pageSize"
+        placeholder="鏁版嵁閲�"
+        size="small"
+        class="w-60"
+      >
+        <el-option label="200" :value="200" />
+        <el-option label="500" :value="500" />
+      </el-select>
+    </el-form-item>
+    <div class="slider-wrap m-l-16">
+      <el-slider v-model="progress" :marks="marks" />
+    </div>
+  </el-row>
+</template>
+<script>
+export default {
+  data() {
+    return {
+      pageSize: 200,
+      progress: 0,
+      marks: {
+        0: {
+          style: {
+            color: 'white'
+          },
+          label: '0%'
+        },
+        100: {
+          style: {
+            color: 'white'
+          },
+          label: '100%'
+        }
+      }
+    };
+  }
+};
+</script>
+<style scoped>
+.wrap {
+  background-color: transparent;
+  height: 60px;
+}
+.slider-wrap {
+  min-width: 400px;
+}
+</style>
diff --git a/src/components/core/CoreHeader.vue b/src/components/core/CoreHeader.vue
index 2c2eff7..381807a 100644
--- a/src/components/core/CoreHeader.vue
+++ b/src/components/core/CoreHeader.vue
@@ -1,5 +1,5 @@
 <template>
-  <div class="map-title ff-title flexbox-col align-items fy-container">
+  <div class="map-title ff-title flexbox-col align-items p-events-auto">
     <div class="map-title-content">
       <div class="ff-border-bottom"></div>
       <div class="ff-border-top">
diff --git a/src/components/core/CoreMenu.vue b/src/components/core/CoreMenu.vue
index f11b751..920f38f 100644
--- a/src/components/core/CoreMenu.vue
+++ b/src/components/core/CoreMenu.vue
@@ -1,9 +1,12 @@
 <template>
-  <div class="map-mode-change flexbox flex-space-between fy-container">
+  <div class="map-mode-change p-events-auto">
     <template v-for="(item, index) in menu" :key="index">
       <a :class="btnClz(item.selected)" @click="navTo(index)">
         <div>{{ item.name }}</div>
       </a>
+      <!-- <a class="btn-selected mode-btn m-r-8" @click="navTo(index)">
+        <div>{{ item.name }}</div>
+      </a> -->
     </template>
     <!-- <a class="mode-btn btn-selected">
       <div>姹℃煋婧簮</div>
@@ -69,7 +72,9 @@
   computed: {},
   methods: {
     btnClz(selected) {
-      return 'mode-btn ' + (selected ? 'btn-selected ' : 'btn-unselected ') + 'm-r-8';
+      return (
+        'mode-btn ' + (selected ? 'btn-selected ' : 'btn-unselected ') + 'm-r-8'
+      );
     },
     navTo(index) {
       const m = this.menu;
@@ -82,4 +87,56 @@
   }
 };
 </script>
-<style scoped></style>
+<style scoped>
+.map-mode-change {
+  display: inline-flex;
+  position: relative;
+  /* left: 10px; */
+  /* top: 104px; */
+  padding: 0 10px;
+  font-size: 1rem;
+  font-weight: 900;
+  /* background-color: #ffffffad; */
+}
+
+.map-mode-change a:hover {
+  color: antiquewhite;
+}
+
+.mode-btn {
+  white-space: nowrap;
+  background-color: green;
+}
+
+.map-mode-change .btn-selected {
+  color: #ffffff;
+  padding: 4px 16px;
+  cursor: pointer;
+  border: 2px solid var(--border-color);
+  background-color: #23dad1;
+  transform: skew(-30deg);
+  text-decoration: none;
+  /* -moz-border-radius: 6px; Old Firefox */
+  /* box-shadow: 5px 0px 2.5px #888888; */
+}
+
+.map-mode-change .btn-selected div {
+  transform: skew(30deg);
+}
+
+.map-mode-change .btn-unselected {
+  color: #5555557e;
+  padding: 4px 16px;
+  cursor: pointer;
+  background-color: #ffffff;
+  border: 2px solid #00000015;
+  transform: skew(-30deg);
+  text-decoration: none;
+  /* -moz-border-radius: 6px; Old Firefox */
+  /* box-shadow: 5px 0px 2.5px #888888; */
+}
+
+.map-mode-change .btn-unselected div {
+  transform: skew(30deg);
+}
+</style>
diff --git a/src/components/map/MapToolbox.vue b/src/components/map/MapToolbox.vue
index ba324db..cabd44c 100644
--- a/src/components/map/MapToolbox.vue
+++ b/src/components/map/MapToolbox.vue
@@ -1,6 +1,6 @@
 <template>
   <el-dropdown
-    class="fy-container dropdown-wrap"
+    class="p-events-auto dropdown-wrap"
     trigger="click"
     size="small"
     @command="handleCommand"
@@ -12,8 +12,16 @@
     </el-button>
     <template #dropdown>
       <el-dropdown-menu>
-        <el-dropdown-item v-for="(item, index) in toolItem" :key="index" :command="index">
-          <el-button :type="item.value ? 'primary' : 'info'" plain size="default">
+        <el-dropdown-item
+          v-for="(item, index) in toolItem"
+          :key="index"
+          :command="index"
+        >
+          <el-button
+            :type="item.value ? 'primary' : 'info'"
+            plain
+            size="default"
+          >
             <font-awesome-icon :icon="item.icon" class="m-r-4" />
             {{ item.label + ': ' + (item.value ? '寮�' : '鍏�') }}
           </el-button>
@@ -100,8 +108,6 @@
   top: 10px;
   left: 2px;
 }
-
-
 
 .el-button {
   margin: initial !important;
diff --git a/src/components/monitor/FactorCheckbox.vue b/src/components/monitor/FactorCheckbox.vue
new file mode 100644
index 0000000..43c1b93
--- /dev/null
+++ b/src/components/monitor/FactorCheckbox.vue
@@ -0,0 +1,64 @@
+<template>
+  <BaseCard direction="top-left" borderless="t">
+    <template #content>
+      <el-checkbox-group
+        v-model="checkbox"
+        size="default"
+        @change="handleChange"
+      >
+        <el-checkbox
+          v-for="(item, i) in options"
+          :key="i"
+          :value="item.value"
+          >{{ item.label }}</el-checkbox
+        >
+      </el-checkbox-group>
+    </template>
+  </BaseCard>
+</template>
+
+<script>
+// 鐩戞祴鍥犲瓙鍗曢�夋
+import { checkboxOptions } from '@/constant/checkbox-options';
+import { TYPE0 } from '@/constant/device-type';
+
+export default {
+  props: {
+    deviceType: {
+      type: String,
+      // type0: 杞﹁浇鎴栨棤浜烘満; type1:鏃犱汉鑸�
+      default: TYPE0
+    }
+  },
+  emits: ['change'],
+  data() {
+    return {
+      checkbox: [checkboxOptions(TYPE0)[0].value]
+    };
+  },
+  computed: {
+    options() {
+      return checkboxOptions(this.deviceType);
+    }
+  },
+  watch: {
+    deviceType(nV, oV) {
+      if (nV != oV) {
+        this.checkbox = this.options[0].value;
+      }
+    }
+  },
+  methods: {
+    handleChange(value) {
+      this.$emit('change', value);
+    }
+  }
+};
+</script>
+<style scoped>
+.el-checkbox {
+  --el-checkbox-text-color: white;
+  margin-right: 6px;
+  /* height: initial; */
+}
+</style>
diff --git a/src/components/monitor/FactorLegend.vue b/src/components/monitor/FactorLegend.vue
new file mode 100644
index 0000000..23e576f
--- /dev/null
+++ b/src/components/monitor/FactorLegend.vue
@@ -0,0 +1,123 @@
+<template>
+  <BaseCard>
+    <template #content>
+      <el-row justify="space-between" align="middle">
+        <el-row align="middle">
+          <img src="@/assets/mipmap/data_chart.png" class="ff-img m-r-4" />
+          <span>璧拌埅鍥句緥</span>
+        </el-row>
+        <span>{{ factor.factorName }}</span>
+      </el-row>
+      <div
+        v-for="(item, index) in legends"
+        :key="index"
+        class="flexbox align-items margin-top"
+      >
+        <div class="rectangle" :style="'background-color: ' + item.color"></div>
+        <el-row v-if="item.max">
+          <span class="w-40 text-right">{{ item.min }}</span>
+          <span class="w-20 text-center">~</span>
+          <span class="w-40 text-right">{{ item.max }}</span>
+          <span class="w-50 m-l-8">{{ item.unit }}</span>
+        </el-row>
+        <el-row v-else>
+          <span class="w-40 text-right"></span>
+          <span class="w-20 text-center">></span>
+          <span class="w-40 text-right">{{ item.min }}</span>
+          <span class="w-50 m-l-8">{{ item.unit }}</span>
+        </el-row>
+      </div>
+    </template>
+  </BaseCard>
+</template>
+
+<script>
+import { Legend } from '@/model/Legend';
+import { factorUnit } from '../../constant/factor-unit';
+import { Factor } from '@/model/Factor';
+
+export default {
+  props: {
+    factor: {
+      type: Factor,
+      default: () => new Factor()
+    }
+  },
+  data() {
+    return {};
+  },
+  computed: {
+    legends() {
+      const res = this.factor
+        ? this.refreshLegend(
+            this.factor.factorName,
+            this.factor.legendType,
+            this.factor.min,
+            this.factor.max
+          )
+        : [];
+      return res;
+    }
+  },
+  methods: {
+    /**
+     * 鑾峰彇鍒嗘瀽鍥句緥
+     */
+    refreshLegend(name, type, min, max) {
+      var r = Legend._legend_r[name];
+      var c = Legend._legend_c[name];
+      // 娌℃湁鎵惧埌鏍囧噯鍥句緥鐨勫洜瀛愶紝榛樿浣跨敤鑷畾涔夎寖鍥村浘渚�
+      if (r == undefined) {
+        type = Legend.C_TYPE;
+      }
+      var range = [];
+      if (type != Legend.S_TYPE && min != undefined && max != undefined) {
+        var count = Legend._custom.length;
+        var per = (max - min) / count;
+        for (let i = 0; i < count; i++) {
+          range.push([(min + per * i).toFixed(1), Legend._custom[i]]);
+        }
+      } else {
+        for (let i = 0; i < r.length; i++) {
+          range.push([r[i], c[i]]);
+        }
+      }
+
+      const legendList = [];
+      for (let i = 0; i < range.length; i++) {
+        const r = range[i];
+        const nextR = range[i + 1];
+
+        var color = r[1];
+        var bgColor =
+          'rgba(' +
+          color[0] * 255 +
+          ', ' +
+          color[1] * 255 +
+          ', ' +
+          color[2] * 255 +
+          ', ' +
+          color[3] +
+          ')';
+        legendList.push({
+          color: bgColor,
+          min: r[0],
+          max: nextR ? nextR[0] : undefined,
+          unit: factorUnit[name]
+        });
+      }
+
+      return legendList;
+    }
+  }
+};
+</script>
+<style scoped>
+.text-right {
+  text-align: right;
+}
+
+.text-center {
+  text-align: center;
+}
+</style>
diff --git a/src/components/monitor/FactorRadio.vue b/src/components/monitor/FactorRadio.vue
index 5282125..afdd4d5 100644
--- a/src/components/monitor/FactorRadio.vue
+++ b/src/components/monitor/FactorRadio.vue
@@ -1,5 +1,5 @@
 <template>
-  <BaseCard class="map-factor-selector">
+  <BaseCard>
     <template #content>
       <el-radio-group v-model="radio" size="default" @change="handleChange">
         <el-radio v-for="(item, i) in options" :key="i" :value="item.value">{{
@@ -41,7 +41,7 @@
       }
     }
   },
-  method: {
+  methods: {
     handleChange(value) {
       this.$emit('change', value);
       // todo 鍦板浘3d鍥惧儚鍒囨崲灞曠ず鐩戞祴鍥犲瓙
diff --git a/src/components/monitor/LineChart.vue b/src/components/monitor/LineChart.vue
new file mode 100644
index 0000000..ccaa3e2
--- /dev/null
+++ b/src/components/monitor/LineChart.vue
@@ -0,0 +1,115 @@
+<template>
+  <BaseCard size="medium">
+    <template #content>
+      <div ref="lineChart" class="line-chart"></div>
+    </template>
+    <template #footer>
+      <!-- 鍗曢〉鏁版嵁閲�-->
+      <SliderBar></SliderBar>
+    </template>
+  </BaseCard>
+</template>
+
+<script>
+import * as echarts from 'echarts';
+import { FactorDatas } from '@/model/FactorDatas';
+import { factorName } from '@/constant/factor-name';
+import { factorLineOption } from '@/utils/chart/chart-option';
+
+export default {
+  props: {
+    factorDatas: {
+      type: FactorDatas
+      // default: () => new FactorDatas()
+    },
+    selectFactorType: {
+      type: Array,
+      default: () => ['1']
+    }
+  },
+  data() {
+    return {
+      lineChart: null,
+      option: null
+    };
+  },
+  watch: {
+    factorDatas: {
+      handler() {
+        this.refreshChart();
+      },
+      deep: true
+    },
+    selectFactorType: {
+      handler() {
+        this.refreshChart();
+      },
+      deep: true
+    }
+  },
+  computed: {
+    /**
+     * 鑾峰彇妯潗鏍�
+     */
+    xAxis() {
+      return this.factorDatas.times.map((v) => {
+        return v.split(' ')[1];
+      });
+    },
+    /**
+     * 鑾峰彇鐩戞祴鏁版嵁绾靛潗鏍�
+     */
+    allSeries() {
+      const res = [];
+      for (const key in this.factorDatas.factor) {
+        if (Object.hasOwnProperty.call(this.factorDatas.factor, key)) {
+          const e = this.factorDatas.factor[key];
+          res.push({
+            key: key,
+            name: factorName[e.factorName],
+            type: 'line',
+            data: e.datas.map((v) => v.factorData),
+            showAllSymbol: true,
+            animationDelay: function (idx) {
+              return idx * 10;
+            }
+          });
+        }
+      }
+      return res;
+    },
+    showSeries() {
+      return this.allSeries.filter((s) => {
+        return this.selectFactorType.includes(s.key);
+      });
+    },
+    legends() {
+      return this.showSeries.map((s) => {
+        return s.name;
+      });
+    }
+  },
+  methods: {
+    initChart() {
+      this.lineChart = echarts.init(this.$refs.lineChart);
+    },
+    refreshChart() {
+      const option = factorLineOption(
+        this.xAxis,
+        this.showSeries,
+        this.legends
+      );
+      this.lineChart.setOption(option, { notMerge: true });
+    }
+  },
+  mounted() {
+    this.initChart();
+  }
+};
+</script>
+<style scoped>
+.line-chart {
+  /* width: 200px; */
+  height: 280px;
+}
+</style>
diff --git a/src/components/search/SearchBar.vue b/src/components/search/SearchBar.vue
index 003c1a0..68ee8ed 100644
--- a/src/components/search/SearchBar.vue
+++ b/src/components/search/SearchBar.vue
@@ -1,5 +1,5 @@
 <template>
-  <BaseCard class="map-date-selector flexbox-col align-items">
+  <BaseCard class="">
     <template #content>
       <el-form :inline="true">
         <OptionMission v-model="formSearch.missionCode"></OptionMission>
@@ -41,7 +41,7 @@
       }
     }
   },
-  method: {
+  methods: {
     handleClick() {
       this.$emit('search', this.formSearch);
     }
@@ -49,6 +49,17 @@
 };
 </script>
 <style lang="scss">
-.fy-container {
+.map-date-selector {
+  display: inline-block;
+  position: relative;
+  /* left: 0;
+    right: 0;
+    top: 0px; */
+  /* padding: 0 4px; */
+  /* color: ffffffbd; */
+  /* background-color: antiquewhite; */
+}
+
+.p-events-auto {
 }
 </style>
diff --git a/src/constant/checkbox-options.js b/src/constant/checkbox-options.js
new file mode 100644
index 0000000..86b1a78
--- /dev/null
+++ b/src/constant/checkbox-options.js
@@ -0,0 +1,186 @@
+import { TYPE0, TYPE1, TYPE2, TYPE4 } from '@/constant/device-type';
+
+// 鐩戞祴鍥犲瓙鍗曢�夋閫夐」
+function checkboxOptions(deviceType) {
+  switch (deviceType) {
+    case TYPE0:
+      return option1;
+    case TYPE1:
+      return option3;
+    case TYPE2:
+      return option2;
+    case TYPE4:
+      return option4;
+    default:
+      return option1;
+  }
+}
+
+const option1 = [
+  {
+    label: 'NO2',
+    name: 'NO2',
+    value: '1'
+  },
+  {
+    label: 'CO',
+    name: 'CO',
+    value: '2'
+  },
+  {
+    label: 'H2S',
+    name: 'H2S',
+    value: '3'
+  },
+  {
+    label: 'SO2',
+    name: 'SO2',
+    value: '4'
+  },
+  {
+    label: 'O3',
+    name: 'NO2',
+    value: '5'
+  },
+  {
+    label: 'PM2.5',
+    name: 'PM25',
+    value: '6'
+  },
+  {
+    label: 'PM10',
+    name: 'PM10',
+    value: '7'
+  },
+  {
+    label: 'TVOC',
+    name: 'VOC',
+    value: '10'
+  },
+
+  // {
+  //     label: "NOI",
+  //     name: "NOI",
+  //     value: "11"
+  // },
+  {
+    label: '杞﹂��',
+    name: 'VELOCITY',
+    value: '14'
+  },
+  {
+    label: '椋庨��',
+    name: 'WIND_SPEED',
+    value: '16'
+  },
+  {
+    label: '椋庡悜',
+    name: 'WIND_DIRECTION',
+    value: '17'
+  },
+  {
+    label: '楂樺害',
+    name: 'HEIGHT',
+    value: '18'
+  }
+];
+
+const option2 = [
+  {
+    label: '娓╁害',
+    name: '娓╁害',
+    value: '1'
+  },
+  {
+    label: '鐢靛鐜�',
+    name: '鐢靛鐜�',
+    value: '2'
+  },
+  {
+    label: '娴婂害',
+    name: '娴婂害',
+    value: '3'
+  },
+  {
+    label: '婧惰В姘�',
+    name: '婧惰В姘�',
+    value: '4'
+  },
+  {
+    label: 'PH',
+    name: 'PH',
+    value: '5'
+  }
+];
+
+const option3 = [
+  {
+    label: 'NO2',
+    name: 'NO2',
+    value: '1'
+  },
+  {
+    label: 'CO',
+    name: 'CO',
+    value: '2'
+  },
+  {
+    label: 'H2S',
+    name: 'H2S',
+    value: '3'
+  },
+  {
+    label: 'SO2',
+    name: 'SO2',
+    value: '4'
+  },
+  {
+    label: 'O3',
+    name: 'NO2',
+    value: '5'
+  },
+  {
+    label: 'PM2.5',
+    name: 'PM25',
+    value: '6'
+  },
+  {
+    label: 'PM10',
+    name: 'PM10',
+    value: '7'
+  },
+  {
+    label: 'TVOC',
+    name: 'VOC',
+    value: '10'
+  },
+  {
+    label: '椋庨��',
+    name: 'WIND_SPEED',
+    value: '16'
+  },
+  {
+    label: '椋庡悜',
+    name: 'WIND_DIRECTION',
+    value: '17'
+  }
+];
+
+const option4 = [
+  {
+    label: 'A鐩哥數娴�',
+    name: 'EA',
+    value: '1'
+  },
+  {
+    label: 'B鐩哥數娴�',
+    name: 'EB',
+    value: '2'
+  },
+  {
+    label: 'C鐩哥數娴�',
+    name: 'EC',
+    value: '3'
+  }
+];
+export { checkboxOptions };
diff --git a/src/constant/factor-name.js b/src/constant/factor-name.js
index d8b551a..360e5f0 100644
--- a/src/constant/factor-name.js
+++ b/src/constant/factor-name.js
@@ -12,7 +12,7 @@
   TEMPERATURE: '娓╁害',
   HUMIDITY: '婀垮害',
   VOC: 'TVOC', //5
-  // 'NOI': 'NOI', //9
+  NOI: 'NOI', //9
   LNG: '缁忓害',
   LAT: '绾害',
   VELOCITY: '杞﹂��',
diff --git a/src/model/Legend.js b/src/model/Legend.js
index 054ad4b..304934e 100644
--- a/src/model/Legend.js
+++ b/src/model/Legend.js
@@ -215,79 +215,6 @@
       i = this._custom.length - 1;
     }
     return this._custom[i];
-  },
-
-  /**
-   * 鑾峰彇鍒嗘瀽鍥句緥
-   */
-  refreshLegend: function (eId, name, animation, type, min, max) {
-    var legend = $('#' + eId);
-    legend.empty();
-
-    var r = this._legend_r[name];
-    var c = this._legend_c[name];
-    // 娌℃湁鎵惧埌鏍囧噯鍥句緥鐨勫洜瀛愶紝榛樿浣跨敤鑷畾涔夎寖鍥村浘渚�
-    if (r == undefined) {
-      type = this.C_TYPE;
-    }
-    var range = [];
-    if (type != this.S_TYPE && min != undefined && max != undefined) {
-      var count = this._custom.length;
-      var per = (max - min) / count;
-      for (let i = 0; i < count; i++) {
-        range.push([(min + per * i).toFixed(1), this._custom[i]]);
-      }
-    } else {
-      for (let i = 0; i < r.length; i++) {
-        range.push([r[i], c[i]]);
-      }
-    }
-
-    for (let i = 0; i < range.length; i++) {
-      const r = range[i];
-      const nextR = range[i + 1];
-      var div1 = $('<div></div>');
-      div1.addClass('flexbox align-items margin-top');
-      var div2 = $('<div></div>');
-      div2.addClass('rectangle');
-
-      var color = r[1];
-      var bgcolor =
-        'rgba(' +
-        color[0] * 255 +
-        ', ' +
-        color[1] * 255 +
-        ', ' +
-        color[2] * 255 +
-        ', ' +
-        color[3] +
-        ')';
-      div2.css('background-color', bgcolor);
-      var div3 = $('<div></div>');
-      var d;
-      if (nextR != undefined) {
-        d = r[0] + '&nbsp;~' + nextR[0] + '&nbsp;' + Util.factorUnit2[name];
-      } else {
-        d =
-          '&nbsp;&nbsp;&nbsp;&nbsp; > &nbsp;' +
-          r[0] +
-          ' &nbsp;' +
-          Util.factorUnit2[name];
-      }
-      div3.append(d);
-      div1.append(div2);
-      div1.append(div3);
-      legend.append(div1);
-    }
-
-    if (animation == false) {
-      return;
-    }
-    legend.hide('fast', function () {
-      setTimeout(() => {
-        legend.show('fast');
-      }, 500);
-    });
   }
 };
 
diff --git a/src/styles/base.scss b/src/styles/base.scss
index d993f48..8e29e01 100644
--- a/src/styles/base.scss
+++ b/src/styles/base.scss
@@ -38,7 +38,7 @@
   r: 'right',
   b: 'bottom'
 );
-$size: (4, 8, 10, 16);
+$size: (2, 4, 8, 10, 16);
 @each $dName, $dValue in $direction {
   @each $i in $size {
     .p-#{$dName}-#{$i} {
@@ -71,7 +71,7 @@
   default: var(--el-component-size-default),
   large: var(--el-component-size-large)
 );
-$ws: (20, 40, 60, 80, 100, 120, 150, 300);
+$ws: (20, 40, 50, 60, 80, 100, 120, 150, 300);
 @each $name, $value in $csize {
   .w-#{$name} {
     width: #{$value};
diff --git a/src/utils/chart/chart-option.js b/src/utils/chart/chart-option.js
new file mode 100644
index 0000000..bd9de70
--- /dev/null
+++ b/src/utils/chart/chart-option.js
@@ -0,0 +1,100 @@
+/**
+ * 鑾峰彇鍚堥�傜殑瀛椾綋澶у皬
+ */
+function fGetChartFontSize() {
+  const dpr = window.devicePixelRatio;
+  let fontSize = 12;
+  if (dpr == 2) {
+    fontSize = 17;
+  } else if (dpr == 3) {
+    fontSize = 24;
+  } else if (dpr > 3) {
+    fontSize = 24;
+  }
+  return fontSize;
+}
+
+function factorLineOption(_xAxis, _series, legends) {
+  var fontSize = fGetChartFontSize();
+  return {
+    animationEasing: 'elasticOut',
+    animationDelayUpdate: function (idx) {
+      return idx * 5;
+    },
+    // toolbox: {
+    //   bottom: 0,
+    //   feature: {
+    //     dataZoom: {},
+    //     magicType: {
+    //       type: ['line', 'bar']
+    //     },
+    //     restore: {}
+    //   }
+    // },
+    tooltip: {
+      textStyle: {
+        fontSize: fontSize
+      }
+    },
+    legend: {
+      type: 'scroll',
+      data: legends,
+      left: 0,
+      textStyle: {
+        fontSize: fontSize,
+        color: 'white'
+      }
+    },
+    xAxis: {
+      name: '鏃堕棿',
+      data: _xAxis,
+      axisLabel: {
+        textStyle: {
+          fontSize: fontSize
+        },
+        color: '#ffffff',
+        textBorderColor: '#fff'
+      },
+      axisTick: {
+        lineStyle: {
+          color: 'white'
+        },
+        intervel: 0,
+        inside: false
+      },
+
+      nameTextStyle: {
+        color: '#ffffff'
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#ffffff'
+        }
+      }
+    },
+    yAxis: {
+      name: '娴撳害(渭g/m鲁)',
+      axisLabel: {
+        textStyle: {
+          fontSize: fontSize
+        }
+      },
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: 'white'
+        }
+      },
+      axisTick: {
+        show: true,
+        lineStyle: {
+          color: 'white'
+        }
+      },
+      minInterval: 1
+    },
+    series: _series
+  };
+}
+
+export { factorLineOption };
diff --git a/src/views/historymode/HistoryMode.vue b/src/views/historymode/HistoryMode.vue
index 50dc1f0..179e2cd 100644
--- a/src/views/historymode/HistoryMode.vue
+++ b/src/views/historymode/HistoryMode.vue
@@ -1,12 +1,21 @@
 <template>
-  <div class="fy-container">
+  <div class="p-events-none m-t-2">
     <el-row justify="center">
       <SearchBar search-time="" @search="fetchHistroyData"></SearchBar>
     </el-row>
-    <FactorRadio
-      :device-type="deviceType"
-      @change="(e) => (factorType = e)"
-    ></FactorRadio>
+    <el-row class="m-t-2">
+      <FactorRadio
+        :device-type="deviceType"
+        @change="(e) => (factorType = e)"
+      ></FactorRadio>
+    </el-row>
+    <el-row class="m-t-2">
+      <FactorLegend
+        class="m-t-2"
+        :factor="factorDatas.factor[factorType]"
+      ></FactorLegend>
+    </el-row>
+    <TrendAnalysis :factor-datas="factorDatas"></TrendAnalysis>
   </div>
 </template>
 
@@ -18,8 +27,10 @@
 import moment from 'moment';
 import { TYPE0 } from '@/constant/device-type';
 import { FactorDatas } from '@/model/FactorDatas';
+import TrendAnalysis from './component/TrendAnalysis.vue';
 
 export default {
+  components: { TrendAnalysis },
   setup() {
     const { loading, fetchData } = useFetchData(10000);
     return { loading, fetchData };
@@ -60,7 +71,7 @@
       //   this.factorMode = factorMode;
       // this.factorType = factorType;
       // this.factorName = factorName;
-      // this.factorDatas.refreshHeight(this.factorType + 1 + '');
+      this.factorDatas.refreshHeight(this.factorType);
       // this.refreshLegend(this.factorDatas);
       // this.mapMaker.setFactorType(factorType);
       // if (!this.mapMaker.runStatus()) {
@@ -108,7 +119,7 @@
     },
     fetchRealTimeData() {
       // fixme 2024.5.3 姝ゅ鍒濆鑾峰彇鐨勬暟鎹紝鍙傛暟搴旇鐢眘earchbar鍐冲畾锛屽悗缁慨鏀�
-      this.fetchData((page, pageSize) => {
+      this.fetchData((page) => {
         return monitorDataApi
           .fetchHistroyData({
             deviceCode: '0a0000000001',
@@ -133,6 +144,8 @@
 };
 </script>
 <style scoped>
-.fy-container {
+.p-events-auto {
+  /* background-color: antiquewhite; */
+  /* padding-top: 1px; */
 }
 </style>
diff --git a/src/views/historymode/component/TrendAnalysis.vue b/src/views/historymode/component/TrendAnalysis.vue
new file mode 100644
index 0000000..d5de2d0
--- /dev/null
+++ b/src/views/historymode/component/TrendAnalysis.vue
@@ -0,0 +1,41 @@
+<template>
+  <el-row class="wrap">
+    <el-col span="10">
+      <FactorCheckbox
+        :device-type="deviceType"
+        @change="(e) => (selectFactorType = e)"
+      ></FactorCheckbox>
+      <LineChart
+        :factor-datas="factorDatas"
+        :select-factor-type="selectFactorType"
+      ></LineChart>
+    </el-col>
+  </el-row>
+</template>
+
+<script>
+/**
+ * 鐩戞祴瑕佺礌瓒嬪娍鍒嗘瀽
+ */
+import { FactorDatas } from '@/model/FactorDatas';
+
+export default {
+  props: {
+    deviceType: {
+      type: String
+    },
+    factorDatas: FactorDatas
+  },
+  data() {
+    return {
+      selectFactorType: ['1']
+    };
+  }
+};
+</script>
+<style scoped>
+.wrap {
+  /* display: flex;
+  flex-direction: column; */
+}
+</style>

--
Gitblit v1.9.3