feiyu02
2024-05-06 9e0df95ffda0ef9f2339f7caf413b357640aea28
完成历史轨迹展示
已修改17个文件
已添加4个文件
1131 ■■■■ 文件已修改
index.html 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitorDataApi.js 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/3dmap.css 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/base.css 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/map/MapToolbox.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monitor/FactorRadio.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search/OptionMission.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search/OptionTime.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search/OptionType.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search/SearchBar.vue 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/constant/device-type.js 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/constant/radio-options.js 156 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/model/Factor.js 222 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/model/FactorDatas.js 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/model/Legend.js 294 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/styles/elementUI.scss 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/calculate.js 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/historymode/HistoryMode.vue 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.html
@@ -4,7 +4,7 @@
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
    <title>飞羽大气环境智能走航监测系统</title>
  </head>
  <body>
    <div id="app"></div>
package-lock.json
@@ -33,7 +33,7 @@
        "eslint-plugin-vue": "^9.17.0",
        "jsdom": "^24.0.0",
        "prettier": "^3.0.3",
        "sass": "^1.72.0",
        "sass": "^1.76.0",
        "vite": "^5.1.6",
        "vitest": "^1.4.0"
      }
@@ -3514,9 +3514,9 @@
      "dev": true
    },
    "node_modules/sass": {
      "version": "1.72.0",
      "resolved": "https://registry.npmmirror.com/sass/-/sass-1.72.0.tgz",
      "integrity": "sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==",
      "version": "1.76.0",
      "resolved": "https://registry.npmmirror.com/sass/-/sass-1.76.0.tgz",
      "integrity": "sha512-nc3LeqvF2FNW5xGF1zxZifdW3ffIz5aBb7I7tSvOoNu7z1RQ6pFt9MBuiPtjgaI62YWrM/txjWlOCFiGtf2xpw==",
      "dev": true,
      "dependencies": {
        "chokidar": ">=3.0.0 <4.0.0",
@@ -6966,9 +6966,9 @@
      "dev": true
    },
    "sass": {
      "version": "1.72.0",
      "resolved": "https://registry.npmmirror.com/sass/-/sass-1.72.0.tgz",
      "integrity": "sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==",
      "version": "1.76.0",
      "resolved": "https://registry.npmmirror.com/sass/-/sass-1.76.0.tgz",
      "integrity": "sha512-nc3LeqvF2FNW5xGF1zxZifdW3ffIz5aBb7I7tSvOoNu7z1RQ6pFt9MBuiPtjgaI62YWrM/txjWlOCFiGtf2xpw==",
      "dev": true,
      "requires": {
        "chokidar": ">=3.0.0 <4.0.0",
package.json
@@ -37,7 +37,7 @@
    "eslint-plugin-vue": "^9.17.0",
    "jsdom": "^24.0.0",
    "prettier": "^3.0.3",
    "sass": "^1.72.0",
    "sass": "^1.76.0",
    "vite": "^5.1.6",
    "vitest": "^1.4.0"
  }
src/api/monitorDataApi.js
@@ -1,11 +1,26 @@
import { $http } from './index';
/**
 *
 * èµ°èˆªç›‘测数据相关API
 */
export default {
  fethcRealtimeData(deviceCode, type, page, perPage) {
    const params = `deviceCode=${deviceCode}&type=${type}&page=${page}&perPage=${perPage}`;
    $http.get(`air/realtime/sec?${params}`);
  /**
   * èŽ·å–æœ€æ–°æ•°æ®
   * @returns
   */
  fethcRealtimeData({ deviceCode, type, page, perPage }) {
    return this.fetchHistroyData({ deviceCode, type, page, perPage });
  },
  /**
   * èŽ·å–åŽ†å²æ•°æ®
   * @returns
   */
  fetchHistroyData({ deviceCode, startTime, endTime, type, page, perPage }) {
    let params = `deviceCode=${deviceCode}&page=${page}&perPage=${perPage}`;
    params += type ? `&type=${type}` : '';
    params += startTime ? `&startTime=${startTime}` : '';
    params += endTime ? `&type=${endTime}` : '';
    return $http.get(`air/realtime/sec?${params}`).then((res) => res.data);
  }
};
src/assets/3dmap.css
@@ -68,7 +68,7 @@
.map-factor-selector {
    position: absolute;
    left: 1px;
    top: 64px;
    top: 41px;
    z-index: 100px;
}
@@ -96,12 +96,11 @@
}
.map-date-selector {
    position: absolute;
    position: relative;
    left: 0;
    right: 0;
    top: 70px;
    padding: 0 4px;
    /* background-color: #ffffffb0; */
    top: 0px;
    /* padding: 0 4px; */
    /* color: ffffffbd; */
}
@@ -165,7 +164,7 @@
.map-mode-change {
    position: absolute;
    left: 10px;
    top: 80px;
    top: 104px;
    z-index: 1000px;
    padding: 1px;
    font-size: 1rem;
src/assets/base.css
@@ -12,7 +12,7 @@
}
body {
  --screen-min-width: 1200px;
  --screen-min-width: 1440px;
  --screen-min-height: 600px;
  min-height: var(--screen-min-height);
  min-width: var(--screen-min-width);
src/components/map/MapToolbox.vue
@@ -101,17 +101,7 @@
  left: 2px;
}
.el-button-custom {
  --el-button-bg-color: var(--bg-color);
  --el-button-hover-text-color: var(--select_color);
  --el-button-hover-bg-color: var(--bg-color);
  --el-button-border-color: var(--font-color);
  --el-button-active-border-color: transparent;
}
.el-button-custom:focus-visible {
  outline: 0px solid var(--el-button-outline-color);
}
.el-button {
  margin: initial !important;
src/components/monitor/FactorRadio.vue
@@ -13,14 +13,34 @@
<script>
// ç›‘测因子单选框
import { radioOptions } from '@/constant/radio-options';
import { TYPE0 } from '@/constant/device-type';
export default {
  props: {
    deviceType: {
      type: String,
      // type0: è½¦è½½æˆ–无人机; type1:无人船
      default: TYPE0
    }
  },
  emits: ['change'],
  data() {
    return {
      radio: radioOptions.type0[0].value,
      options: radioOptions.type0
      radio: radioOptions(TYPE0)[0].value
    };
  },
  computed: {
    options() {
      return radioOptions(this.deviceType);
    }
  },
  watch: {
    deviceType(nV, oV) {
      if (nV != oV) {
        this.radio = this.options[0].value;
      }
    }
  },
  method: {
    handleChange(value) {
      this.$emit('change', value);
src/components/search/OptionMission.vue
@@ -18,7 +18,7 @@
</template>
<script>
import missionApi from '../../api/missionApi';
import missionApi from '@/api/missionApi';
import { useFetchData } from '@/composables/fetchData';
export default {
@@ -64,7 +64,7 @@
  }
};
</script>
<style scoped>
<style>
/* :deep() .el-form-item__label {
  color: red !important;
} */
src/components/search/OptionTime.vue
@@ -37,4 +37,3 @@
  }
};
</script>
<style scoped></style>
src/components/search/OptionType.vue
@@ -43,6 +43,7 @@
  },
  methods: {
    handleChange(value) {
      // todo æ ¹æ®è®¾å¤‡ç±»åž‹åˆ‡æ¢åœ°å›¾è½½å…·çš„图标、
      this.$emit('update:modelValue', value);
    }
  },
@@ -51,4 +52,3 @@
  }
};
</script>
<style scoped></style>
src/components/search/SearchBar.vue
@@ -1,5 +1,5 @@
<template>
  <BaseCard class="map-date-selector">
  <BaseCard class="map-date-selector flexbox-col align-items">
    <template #content>
      <el-form :inline="true">
        <OptionMission v-model="formSearch.missionCode"></OptionMission>
@@ -9,6 +9,9 @@
          v-model="formSearch.deviceCode"
        ></OptionDevice>
        <OptionTime v-model="formSearch.timeArray"></OptionTime>
        <el-button type="primary" class="el-button-custom" @click="handleClick">
          åˆ†æž
        </el-button>
      </el-form>
    </template>
  </BaseCard>
@@ -17,6 +20,9 @@
<script>
// æœç´¢æ¡†
export default {
  props: {
    searchTime: Array
  },
  data() {
    return {
      formSearch: {
@@ -27,7 +33,22 @@
      }
    };
  },
  method: {}
  emits: ['search'],
  watch: {
    searchTime(nV, oV) {
      if (nV != oV) {
        this.timeArray = this.searchTime;
      }
    }
  },
  method: {
    handleClick() {
      this.$emit('search', this.formSearch);
    }
  }
};
</script>
<style scoped></style>
<style lang="scss">
.fy-container {
}
</style>
src/constant/device-type.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
// è½¦è½½
const TYPE0 = '0a';
// æ— äººæœº
const TYPE1 = '0b';
// æ— äººèˆ¹
const TYPE2 = '0c';
// å®šç‚¹ç›‘测
const TYPE3 = '0d';
// ç”¨ç”µé‡
const TYPE4 = '31';
export { TYPE0, TYPE1, TYPE2, TYPE3, TYPE4 };
src/constant/radio-options.js
@@ -1,74 +1,84 @@
import { TYPE0, TYPE1, TYPE2 } from '@/constant/device-type';
// ç›‘测因子单选框选项
export const radioOptions = {
  type0: [
    //无人机
    {
      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'
    }
  ],
  type1: [
    //无人船
    {
      label: '温度',
      name: '温度',
      value: '1'
    },
    {
      label: '电导率',
      name: '电导率',
      value: '2'
    },
    {
      label: '浊度',
      name: '浊度',
      value: '3'
    },
    {
      label: '溶解氧',
      name: '溶解氧',
      value: '4'
    },
    {
      label: 'PH',
      name: 'PH',
      value: '5'
    }
  ]
};
function radioOptions(deviceType) {
  if ([TYPE0, TYPE1].includes(deviceType)) {
    return option1;
  } else if (deviceType == TYPE2) {
    return option2;
  } else {
    return [];
  }
}
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'
  }
];
const option2 = [
  {
    label: '温度',
    name: '温度',
    value: '1'
  },
  {
    label: '电导率',
    name: '电导率',
    value: '2'
  },
  {
    label: '浊度',
    name: '浊度',
    value: '3'
  },
  {
    label: '溶解氧',
    name: '溶解氧',
    value: '4'
  },
  {
    label: 'PH',
    name: 'PH',
    value: '5'
  }
];
export { radioOptions };
src/model/Factor.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,222 @@
import { Legend } from './Legend';
const _hMap = {
  1: [0, 1000], //NO2
  2: [0, 2000], //CO
  3: [0, 1000], //H2S
  4: [0, 1000], //SO2
  5: [0, 1000], //O3
  6: [0, 1000], //PM2.5
  7: [0, 1000], //PM10
  8: [0, 100], //TEMPERATURE
  9: [0, 100], //HUMIDITY
  10: [0, 500], //VOC
  11: [0, 1000] //NOI
};
const _hRange = [0, 1000];
function getFactorHeight(type, data, _range) {
  var range = _range == undefined ? _hMap.get(type) : _range;
  var min = range[0];
  var max = range[1];
  var scale = max - min == 0 ? 0 : (_hRange[1] - _hRange[0]) / (max - min);
  var offset = min;
  // console.log("height:" + (data - offset) * scale * 10);
  if (data < range[0]) {
    return (range[0] - offset) * scale * 10;
  } else if (data > range[1]) {
    return (range[1] - offset) * scale * 10;
  } else {
    return (data - offset) * scale * 10;
  }
}
/**
 * ç›‘测因子类
 * å­˜å‚¨æŸä¸€ç±»åž‹çš„监测因子数据,提供3d地图绘制高度换算,绘图范围设定等功能
 * ç”¨äºŽ3d地图绘制
 */
function Factor(options) {
  /**
    * {factorData: 43.209
        factorId: "1"
        factorName: "NO2"
        physicalQuantity: 211.1
        sensorId: null
        statusList: null}
    */
  this.datas = []; // åŽŸå§‹æ•°æ®
  // this.lnglats = [] //3d地图当前展示坐标点数组
  this.factorName;
  this.factorId;
  this.heights = []; //3d地图当前展示坐标点对应的高度数组
  this.colors = []; // 3d地图当前展示坐标点对应的颜色数组
  this.bottomColor; //最小值对应的图例色
  this.min = -1; // å½“前显示的最小值
  this.max = -1; // å½“前显示的最大值
  this.originMin = -1; // åŽŸå§‹æ•°æ®ä¸­çš„æœ€å°å€¼
  this.originMax = -1; // åŽŸå§‹æ•°æ®ä¸­çš„æœ€å¤§å€¼
  this.standardMin = -1; //监测因子类型对应的标准最小值
  this.standardMax = -1; //监测因子类型对应的标准最大值
  this.legendType = Legend.S_TYPE; //图例模式
  if (options != undefined) {
    this.datas = options.datas;
    this.heights = options.heights;
    this.min = options.min;
    this.max = options.max;
    this.originMin = options.originMin;
    this.originMax = options.originMax;
    this.factorName = options.factorName;
    this.factorId = options.factorId;
    this.colors = options.colors;
    this.bottomColor = options.bottomColor;
    this.standardMin = options.standardMin;
    this.standardMax = options.standardMax;
  }
}
Factor.prototype = {
  // drawMode: ç»˜åˆ¶æ¨¡å¼ï¼Œ0:自动模式,自动计算当前数据的范围,绘制合适的比例;1:手动模式,根据页面设置的绘图范围进行绘制
  pushData: function (data, drawMode) {
    if (this.factorName == undefined) {
      this.factorName = data.factorName;
      this.factorId = data.factorId;
    } else {
      if (this.factorName != data.factorName) {
        console.log(
          '错误: Factor中插入的数据前后名称不一致,原因子:' +
            this.factorName +
            ',新因子:' +
            data.factorName
        );
      }
    }
    this.datas.push(data);
    this.getRange(data, drawMode);
    if (this.standardMin == -1) {
      var range = Legend.getStandardRange(this.factorName);
      this.standardMin = range[0];
      this.standardMax = range[1];
    }
  },
  getRange: function (data, drawMode) {
    if (this.min == -1) {
      this.min = data.factorData;
      this.max = data.factorData;
    }
    if (this.originMin == -1) {
      this.originMin = data.factorData;
      this.originMax = data.factorData;
    }
    if (drawMode == 0) {
      this.min = Math.min(this.min, data.factorData);
      this.max = Math.max(this.max, data.factorData);
      // this.min = this.standardMin
      // this.max = this.standardMax
    }
    this.originMin = Math.min(this.originMin, data.factorData);
    this.originMax = Math.max(this.originMax, data.factorData);
  },
  getHeight: function () {
    this.heights = [];
    this.colors = [];
    this.datas.forEach((d) => {
      var h = getFactorHeight(d.factorId, d.factorData, [this.min, this.max]);
      if (d.factorData == -1) {
        h = -1;
      }
      this.heights.push(h);
      var c = Legend.getColor(
        this.factorName,
        this.legendType,
        d.factorData,
        this.min,
        this.max
      );
      this.colors.push(c);
      // this.heights.push(d.factorData)
    });
    this.bottomColor = Legend.getColor(
      this.factorName,
      this.legendType,
      this.standardMin,
      this.min,
      this.max
    );
    // console.log(this.factorName + ':' + this.bottomColor);
  },
  setRange: function (range) {
    this.min = range[0];
    this.max = range[1];
    this.legendType = Legend.C_TYPE;
    this.getHeight();
  },
  clearRange: function () {
    this.min = this.originMin;
    this.max = this.originMax;
    this.legendType = Legend.D_TYPE;
    this.getHeight();
  },
  standardRange: function () {
    this.min = this.originMin;
    this.max = this.originMax;
    // this.min = this.standardMin
    // this.max = this.standardMax
    this.legendType = Legend.S_TYPE;
    this.getHeight();
  },
  // æ ¹æ®å¼€å§‹å’Œç»“束下标获取对应位置数据集
  getByIndex: function (s, e) {
    var d = this.datas.slice(s, e);
    var h = this.heights.slice(s, e);
    return new Factor({
      datas: d,
      heights: h,
      min: this.min,
      max: this.max,
      originMin: this.originMin,
      originMax: this.originMax,
      factorName: this.factorName,
      colors: this.colors,
      bottomColor: this.bottomColor,
      standardMin: this.standardMin,
      standardMax: this.standardMax
    });
  },
  // æ–°å¢žæ•°æ®åŒæ—¶æ’帧
  insertFrame: function (factor, count, isDraw) {
    var d1 = this.datas[this.datas.length - 1];
    var d2 = factor.datas[0];
    if (d1 == undefined || d2 == undefined) {
      return;
    }
    // å•帧数据值的差值
    var dValue = {
      factorData: (d2.factorData - d1.factorData) / count,
      physicalQuantity: (d2.physicalQuantity - d2.physicalQuantity) / count
    };
    for (let i = 0; i < count - 1; i++) {
      var _data = {
        factorData: d1.factorData + dValue.factorData * (i + 1),
        factorId: d1.factorId,
        factorName: d1.factorName,
        physicalQuantity:
          d1.physicalQuantity + dValue.physicalQuantity * (i + 1),
        sensorId: d1.sensorId,
        statusList: d1.statusList
      };
      if (!isDraw) {
        _data.factorData = -1;
        _data.physicalQuantity = -1;
      }
      this.datas.push(_data);
    }
    // this.datas.push(d2)
  }
};
export { Factor };
src/model/FactorDatas.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,168 @@
import { Factor } from "./Factor";
import calculate from "@/utils/map/calculate";
/**
 *
 */
function FactorDatas(options) {
  // æ—¶é—´
  this.times = [];
  // åŽŸå§‹ç»çº¬åº¦ï¼ˆGPS)
  this.lnglats_GPS = [];
  // é«˜å¾·ç»çº¬åº¦
  this.lnglats_GD = [];
  // ï¼ˆé«˜å¾·åœ°å›¾ï¼‰è½¬æ¢åŽçš„3d地图绘制坐标
  this.coors_GD = [];
  // ç›‘测因子数据,Map<String, Factor>
  this.factor = new Map();
  if (options != undefined) {
    this.times = options.times;
    this.lnglats_GPS = options.lnglats_GPS;
    this.lnglats_GD = options.lnglats_GD;
    this.coors_GD = options.coors_GD;
    this.factor = options.factor;
  }
}
FactorDatas.prototype = {
  /**
     * @param drawMode ç»˜åˆ¶æ¨¡å¼ï¼Œ0:自动模式,自动计算当前数据的范围,绘制合适的比例;1:手动模式,根据页面设置的绘图范围进行绘制
     * @param  dataList ç»“构如下
     * [{
        "time": "2021-04-21 21:11:59",
        "deviceCode": "0a",
        "lng": 121.229385,
        "lat": 30.828487
        "values": [
            {
            "factorId": "1",
            "factorName": "NO2",
            "sensorId": null,
            "factorData": 65.4,
            "physicalQuantity": 215.39,
            "statusList": null
            },
        ],
        }]
        */
  setData: function (dataList, drawMode, callback) {
    this.clearData();
    dataList.forEach((d) => {
      this.times.push(d.time);
      this.lnglats_GPS.push([d.lng, d.lat]);
      d.values.forEach((v) => {
        var f = this.factor[v.factorId];
        if (f == undefined) {
          f = new Factor();
          this.factor[v.factorId] = f;
        }
        f.pushData(v, drawMode == undefined ? 0 : drawMode);
      });
    });
    this.convertGPS(this.lnglats_GPS, callback);
  },
  // æ–°å¢žä¸€ä¸ªæ–°æ•°æ®
  addData: function (dataList, drawMode, callback) {
    var newGps = [];
    dataList.forEach((data) => {
      this.times.push(data.time);
      this.lnglats_GPS.push([data.lng, data.lat]);
      newGps.push([data.lng, data.lat]);
      // this.coors_GD: 3d地图的坐标通过第一次绘制之后获得
      data.values.forEach((d) => {
        var f = this.factor[d.factorId];
        if (f == undefined) {
          f = new Factor();
          this.factor[d.factorId] = f;
        }
        f.pushData(d, drawMode == undefined ? 0 : drawMode);
      });
    });
    this.convertGPS(newGps, callback);
  },
  convertGPS: function (gpsList, callback) {
    var that = this;
    calculate.convertFromGPS(gpsList, function (result) {
      var gd = that.lnglats_GD;
      gd.push.apply(gd, result);
      var coor_GD = calculate.lngLatToGeodeticCoord(result);
      var coor = that.coors_GD;
      coor.push.apply(coor, coor_GD);
      if (typeof callback === 'function') {
        callback();
      }
    });
  },
  clearData: function () {
    this.times = [];
    this.lnglats_GPS = [];
    this.lnglats_GD = [];
    this.coors_GD = [];
    this.factor = new Map();
  },
  // è®¾ç½®ç»˜å›¾èŒƒå›´
  setRange: function (key, range) {
    this.factor[key].setRange(range);
  },
  // é‡ç½®ç»˜å›¾èŒƒå›´
  resetRange: function (key) {
    this.factor[key].clearRange();
  },
  // è®¾ç½®ä¸ºæ ‡å‡†ç»˜å›¾èŒƒå›´
  standardRange: function (key) {
    this.factor[key].standardRange();
  },
  // æ ¹æ®å½“前绘图范围重新计算绘图高度
  refreshHeight: function (key) {
    if (key != undefined) {
      this.factor[key].getHeight();
    } else {
      for (const k in this.factor) {
        this.factor[k].getHeight();
      }
    }
  },
  // æ ¹æ®å¼€å§‹å’Œç»“束下标获取对应位置数据集
  getByIndex: function (s, e) {
    var t = this.times.slice(s, e);
    var l = this.lnglats_GPS.slice(s, e);
    var l_GD = this.lnglats_GD.slice(s, e);
    var c = this.coors_GD.slice(s, e);
    var f = new Map();
    for (const key in this.factor) {
      if (Object.hasOwnProperty.call(this.factor, key)) {
        const element = this.factor[key];
        f[key] = element.getByIndex(s, e);
      }
    }
    return new FactorDatas({
      times: t,
      lnglats_GPS: l,
      lnglats_GD: l_GD,
      coors_GD: c,
      factor: f
    });
  },
  // èŽ·å–æ•°æ®é•¿åº¦
  length: function () {
    return this.lnglats_GD.length;
  },
};
export { FactorDatas };
src/model/Legend.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,294 @@
/**
 * ç›‘测因子图例
 */
const Legend = {
  S_TYPE: 0, //标准图例
  D_TYPE: 1, //动态图例
  C_TYPE: 2, //自定义范围图例
  //各监测因子数据分级(标准)
  _legend_r: {
    NO2: [0, 100, 200, 700, 1200, 2340],
    CO: [0, 5, 10, 35, 60, 90],
    H2S: [0, 150, 500, 650, 800, 1600],
    SO2: [0, 150, 500, 650, 800, 1600],
    O3: [0, 160, 200, 300, 400, 800],
    PM25: [0, 35, 75, 115, 150, 250],
    PM10: [0, 50, 150, 250, 350, 420],
    VOC: [0, 200, 400, 600, 1000, 2000, 4000],
    TMP: [-10, 0, 10, 20, 30, 40],
    spC: [0, 200, 400, 600, 800, 1000],
    tur: [0, 50, 100, 150, 200, 250],
    DO: [0, 3, 5, 7, 9, 11],
    PH: [1, 3, 5, 7, 9, 11, 13]
  },
  //各监测因子数据分级(标准)对应颜色
  _legend_c: {
    NO2: [
      [0, 0.89, 0, 0.75],
      [1, 1, 0, 0.75],
      [1, 0.49, 0, 0.75],
      [1, 0, 0, 0.75],
      [0.6, 0, 0.3, 0.75],
      [0.49, 0, 0.14, 0.75]
    ],
    CO: [
      [0, 0.89, 0, 0.75],
      [1, 1, 0, 0.75],
      [1, 0.49, 0, 0.75],
      [1, 0, 0, 0.75],
      [0.6, 0, 0.3, 0.75],
      [0.49, 0, 0.14, 0.75]
    ],
    H2S: [
      [0, 0.89, 0, 0.75],
      [1, 1, 0, 0.75],
      [1, 0.49, 0, 0.75],
      [1, 0, 0, 0.75],
      [0.6, 0, 0.3, 0.75],
      [0.49, 0, 0.14, 0.75]
    ],
    SO2: [
      [0, 0.89, 0, 0.75],
      [1, 1, 0, 0.75],
      [1, 0.49, 0, 0.75],
      [1, 0, 0, 0.75],
      [0.6, 0, 0.3, 0.75],
      [0.49, 0, 0.14, 0.75]
    ],
    O3: [
      [0, 0.89, 0, 0.75],
      [1, 1, 0, 0.75],
      [1, 0.49, 0, 0.75],
      [1, 0, 0, 0.75],
      [0.6, 0, 0.3, 0.75],
      [0.49, 0, 0.14, 0.75]
    ],
    PM25: [
      [0, 0.89, 0, 0.75],
      [1, 1, 0, 0.75],
      [1, 0.49, 0, 0.75],
      [1, 0, 0, 0.75],
      [0.6, 0, 0.3, 0.75],
      [0.49, 0, 0.14, 0.75]
    ],
    PM10: [
      [0, 0.89, 0, 0.75],
      [1, 1, 0, 0.75],
      [1, 0.49, 0, 0.75],
      [1, 0, 0, 0.75],
      [0.6, 0, 0.3, 0.75],
      [0.49, 0, 0.14, 0.75]
    ],
    VOC: [
      [0, 0.89, 0, 0.75],
      [0, 0.7, 0, 0.75],
      [0.8, 1, 0, 0.75],
      [1, 0.49, 0, 0.75],
      [1, 0, 0, 0.75],
      [0.4, 0, 0.3, 0.75],
      [0.6, 0, 0.3, 0.75]
    ],
    TMP: [
      [0, 0.89, 0, 0.75],
      [1, 1, 0, 0.75],
      [1, 0.49, 0, 0.75],
      [1, 0, 0, 0.75],
      [0.6, 0, 0.3, 0.75],
      [0.49, 0, 0.14, 0.75]
    ],
    spC: [
      [0, 0.89, 0, 0.75],
      [1, 1, 0, 0.75],
      [1, 0.49, 0, 0.75],
      [1, 0, 0, 0.75],
      [0.6, 0, 0.3, 0.75],
      [0.49, 0, 0.14, 0.75]
    ],
    tur: [
      [0, 0.89, 0, 0.75],
      [1, 1, 0, 0.75],
      [1, 0.49, 0, 0.75],
      [1, 0, 0, 0.75],
      [0.6, 0, 0.3, 0.75],
      [0.49, 0, 0.14, 0.75]
    ],
    DO: [
      [0.49, 0, 0.14, 0.75],
      [0.6, 0, 0.3, 0.75],
      [1, 0, 0, 0.75],
      [1, 0.49, 0, 0.75],
      [1, 1, 0, 0.75],
      [0, 0.89, 0, 0.75]
    ],
    PH: [
      [0, 0.89, 0, 0.75],
      [0, 0.7, 0, 0.75],
      [0.8, 1, 0, 0.75],
      [1, 0.49, 0, 0.75],
      [1, 0, 0, 0.75],
      [0.4, 0, 0.3, 0.75],
      [0.6, 0, 0.3, 0.75]
    ]
  },
  _custom: [
    [0.05, 0.9, 0.03, 0.75],
    [0.3, 0.65, 0.02, 0.75],
    [0.87, 0.92, 0.03, 0.75],
    [0.8, 0.67, 0.04, 0.75],
    [0.92, 0.28, 0.07, 0.75],
    [0.6, 0.05, 0.05, 0.75]
  ],
  getStandardRange: function (name) {
    var range = this._legend_r[name];
    if (range == undefined) {
      range = this._legend_r['PM25'];
    }
    var min = range[0];
    var max = range[range.length - 1];
    // for (const key in range) {
    //   if (min == undefined) {
    //     min = parseInt(key)
    //   }
    //   max = parseInt(key)
    // }
    if (name == 'CO') {
      min *= 1000;
      max *= 1000;
    }
    return [min, max];
  },
  getColor: function (name, type, data, min, max) {
    if (type == this.S_TYPE) {
      return this.getStandardColor(name, data);
    } else {
      return this.getCustomColor(data, min, max);
    }
  },
  /**
   * èŽ·å–ç›‘æµ‹å› å­å½“å‰æµ“åº¦å¯¹åº”çš„é¢œè‰²
   * @param name ç›‘测因子名称
   * @param data ç›‘测因子浓度
   */
  getStandardColor: function (name, data) {
    var range = this._legend_r[name];
    var colors = this._legend_c[name];
    if (range == undefined) {
      range = this._legend_r['PM25'];
      colors = this._legend_c['PM25'];
    }
    // return colors[0]
    var selected = undefined;
    for (let i = 0; i < range.length; i++) {
      const d = range[i];
      var d1 = d;
      if (name == 'CO') {
        d1 *= 1000;
      }
      if (data >= d1) {
        selected = i;
      } else {
        break;
      }
    }
    // é¿å…ä¸‹æ ‡è¶Šç•Œ
    if (selected >= colors.length) {
      selected = colors.length - 1;
    }
    // console.log(name + ': ' + selected);
    return colors[selected];
  },
  getCustomColor: function (data, min, max) {
    var per = (max - min) / this._custom.length;
    var i = parseInt(data / per);
    if (i >= this._custom.length) {
      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);
    });
  }
};
export { Legend };
src/styles/elementUI.scss
@@ -13,3 +13,15 @@
// .el-radio {
//   --el-radio-text-color: white;
// }
.el-button-custom {
  --el-button-bg-color: var(--bg-color);
  --el-button-hover-text-color: var(--select_color);
  --el-button-hover-bg-color: var(--bg-color);
  --el-button-border-color: var(--font-color);
  --el-button-active-border-color: transparent;
}
.el-button-custom:focus-visible {
  outline: 0px solid var(--el-button-outline-color);
}
src/utils/map/calculate.js
@@ -4,6 +4,52 @@
import { map } from './index_old';
function _prepare4convert(lnglats) {
  var coor = [];
  var maxLength = 1000;
  var start = 0;
  var end = start + maxLength;
  while (end <= lnglats.length) {
    coor.push(lnglats.slice(start, end));
    start += maxLength;
    end += maxLength;
  }
  if (start < lnglats.length) {
    coor.push(lnglats.slice(start));
  }
  return coor;
}
function parse2LngLat(lnglats) {
  // åˆ›å»ºåŒ…含4个节点的折线及文字标注
  var path = [];
  lnglats.forEach(function (value) {
    // eslint-disable-next-line no-undef
    path.push(new AMap.LngLat(value[0], value[1]));
  });
  return path;
}
/**
 * å°†gps经纬度转换为高德地图经纬度
 * @param {*} lnglats
 * @param {*} callback
 */
function _convertLatlng(index, coor, lnglats, callback) {
  if (index < coor.length) {
    var path = parse2LngLat(coor[index]);
    // eslint-disable-next-line no-undef
    AMap.convertFrom(path, 'gps', function (status, result) {
      if (result.info === 'ok') {
        lnglats.push.apply(lnglats, result.locations);
        _convertLatlng(index + 1, coor, lnglats, callback);
      }
    });
  } else {
    callback(lnglats);
  }
}
export default {
  /**
   * æ ¹æ®åæ ‡ç‚¹ã€è·ç¦»å’Œè§’度,得到另一个坐标点
@@ -55,5 +101,16 @@
      coors_GD.push(p);
    }
    return coors_GD;
  },
  convertFromGPS: function (gps, callback) {
    var coor = _prepare4convert(gps);
    _convertLatlng(0, coor, [], function (result) {
      var gd = [];
      result.forEach((r) => {
        gd.push([r.lng, r.lat]);
      });
      callback(gd);
    });
  }
};
src/views/historymode/HistoryMode.vue
@@ -1,22 +1,44 @@
<template>
  <div class="fy-container">
    <FactorRadio @change="(e) => (factorType = e)"></FactorRadio>
    <SearchBar></SearchBar>
    <el-row justify="center">
      <SearchBar search-time="" @search="fetchHistroyData"></SearchBar>
    </el-row>
    <FactorRadio
      :device-type="deviceType"
      @change="(e) => (factorType = e)"
    ></FactorRadio>
  </div>
</template>
<script>
import Layer from '@/utils/map/3dLayer';
import marks from '@/utils/map/marks';
import monitorDataApi from '@/api/monitorDataApi';
import { useFetchData } from '@/composables/fetchData';
import moment from 'moment';
import { TYPE0 } from '@/constant/device-type';
import { FactorDatas } from '@/model/FactorDatas';
export default {
  name: 'HistoryPage',
  setup() {
    const { loading, fetchData } = useFetchData(10000);
    return { loading, fetchData };
  },
  data() {
    return {
      factorType: '',
      factorDatas: [],
      // ç›‘测设备类型
      deviceType: TYPE0,
      // ç›‘测因子的类型编号
      factorType: '1',
      // ç›‘测数据
      factorDatas: new FactorDatas(),
      // å†³å®šç»˜åˆ¶3D图形时是否与原图像合并
      merge: false,
      setCenter: true
      // å†³å®šç»˜åˆ¶å®Œ3D图形后地图视角是否自动回中
      setCenter: true,
      // ç»˜åˆ¶æ¨¡å¼ï¼Œ0:自动模式,自动计算当前数据的范围,绘制合适的比例;1:手动模式,根据页面设置的绘图范围进行绘制
      drawMode: 0,
      searchTime: []
    };
  },
  watch: {
@@ -28,6 +50,7 @@
  },
  methods: {
    draw() {
      // todo åˆ·æ–°å›¾ä¾‹
      const factor = this.factorDatas.factor[this.factorType];
      this.drawRoadMap(factor);
      this.drawMassMarks(factor);
@@ -54,12 +77,62 @@
        // 4. è¡¨æ ¼æ•°æ®è·³è½¬å®šä½
        // Table.locate(i);
      });
    },
    onFetchData(type, data) {
      // todo æ ¹æ®è®¾å¤‡ç±»åž‹åˆ‡æ¢åœ°å›¾ç›‘测因子展示单选框、折线图复选框、数据表格复选框的因子类型
      this.deviceType = type;
      this.factorDatas.setData(data, this.drawMode, () => {
        this.factorDatas.refreshHeight(this.factorType);
        this.draw();
      });
    },
    fetchHistroyData(option) {
      const { deviceCode, type, timeArray } = option;
      let startTime, endTime;
      if (timeArray && timeArray.length == 2) {
        startTime = moment(timeArray[0]).format('YYYY-MM-DD HH:mm:ss');
        endTime = moment(timeArray[1]).format('YYYY-MM-DD HH:mm:ss');
      }
      this.fetchData((page, pageSize) => {
        return monitorDataApi
          .fetchHistroyData({
            deviceCode,
            startTime,
            endTime,
            type,
            page,
            perPage: pageSize
          })
          .then((res) => this.onFetchData(type, res.data));
      });
    },
    fetchRealTimeData() {
      // fixme 2024.5.3 æ­¤å¤„初始获取的数据,参数应该由searchbar决定,后续修改
      this.fetchData((page, pageSize) => {
        return monitorDataApi
          .fetchHistroyData({
            deviceCode: '0a0000000001',
            // type: TYPE0,
            page,
            perPage: 100
          })
          .then((res) => {
            if (res.data.length > 0) {
              const s = new Date(res.data[0].time);
              const e = new Date(res.data[res.data.length - 1].time);
              this.searchTime = [s, e];
            }
            this.onFetchData(TYPE0, res.data);
          });
      });
    }
  },
  mounted() {
    this.fetchRealTimeData();
  }
};
</script>
<style scoped>
.fy-container {
  background-color: antiquewhite;
}
</style>
vite.config.js
@@ -34,6 +34,7 @@
    preprocessorOptions: {
      scss: {
        additionalData: `@use "@/styles/index.scss" as *;`
        // additionalData: '@import   "@/styles/index.scss";'
      }
    }
  },