riku
2024-05-06 1534aee0339dee8000cdd26c21797cf3ad391f7a
新增折线图模块功能
已修改16个文件
已添加7个文件
1076 ■■■■ 文件已修改
package-lock.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/3dmap.css 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/border.css 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/main.css 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/BaseCard.vue 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SliderBar.vue 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/core/CoreHeader.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/core/CoreMenu.vue 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/map/MapToolbox.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monitor/FactorCheckbox.vue 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monitor/FactorLegend.vue 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monitor/FactorRadio.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monitor/LineChart.vue 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search/SearchBar.vue 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/constant/checkbox-options.js 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/constant/factor-name.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/model/Legend.js 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/styles/base.scss 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/chart/chart-option.js 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/historymode/HistoryMode.vue 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/historymode/component/TrendAnalysis.vue 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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=="
        }
      }
    }
  }
}
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",
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 {
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 {
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;
}
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']
  }
}
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;
    }
  }
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>
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">
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>
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;
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>
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>
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图像切换展示监测因子
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>
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>
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 };
src/constant/factor-name.js
@@ -12,7 +12,7 @@
  TEMPERATURE: '温度',
  HUMIDITY: '湿度',
  VOC: 'TVOC', //5
  // 'NOI': 'NOI', //9
  NOI: 'NOI', //9
  LNG: '经度',
  LAT: '纬度',
  VELOCITY: '车速',
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);
    });
  }
};
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};
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 };
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>
    <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 æ­¤å¤„初始获取的数据,参数应该由searchbar决定,后续修改
      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>
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>