1. 完成表格模块的功能迁移;
2. 完成表格、折线图、地图标记三者点击联动
已修改16个文件
已添加3个文件
1300 ■■■■ 文件已修改
src/assets/border.css 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/CardButton.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SliderBar.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/core/CoreMenu.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/map/MapToolbox.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monitor/DataSummary.vue 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monitor/DataTable.vue 161 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monitor/FactorCheckbox.vue 66 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monitor/LineChart.vue 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/model/FrameAnimation.js 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/3dLayer.js 165 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/animation.js 389 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/dialog.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/marks.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/sector.js 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/historymode/HistoryMode.vue 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/historymode/component/DataSheet.vue 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/historymode/component/TrendAnalysis.vue 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/border.css
@@ -565,6 +565,7 @@
/* æŒ‰é’® */
.ff-toggle-btn {
    position: relative;
}
.ff-toggle-btn .ff-border-top {
src/components.d.ts
@@ -12,6 +12,8 @@
    CardButton: typeof import('./components/CardButton.vue')['default']
    CoreHeader: typeof import('./components/core/CoreHeader.vue')['default']
    CoreMenu: typeof import('./components/core/CoreMenu.vue')['default']
    DataStatistic: typeof import('./components/monitor/DataStatistic.vue')['default']
    DataSummary: typeof import('./components/monitor/DataSummary.vue')['default']
    DataTable: typeof import('./components/monitor/DataTable.vue')['default']
    ElButton: typeof import('element-plus/es')['ElButton']
    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
@@ -26,11 +28,15 @@
    ElFormItem: typeof import('element-plus/es')['ElFormItem']
    ElIcon: typeof import('element-plus/es')['ElIcon']
    ElOption: typeof import('element-plus/es')['ElOption']
    ElPagination: typeof import('element-plus/es')['ElPagination']
    ElRadio: typeof import('element-plus/es')['ElRadio']
    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']
    ElTable: typeof import('element-plus/es')['ElTable']
    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
    ElText: typeof import('element-plus/es')['ElText']
    FactorCheckbox: typeof import('./components/monitor/FactorCheckbox.vue')['default']
    FactorLegend: typeof import('./components/monitor/FactorLegend.vue')['default']
    FactorRadio: typeof import('./components/monitor/FactorRadio.vue')['default']
@@ -45,4 +51,7 @@
    SearchBar: typeof import('./components/search/SearchBar.vue')['default']
    SliderBar: typeof import('./components/SliderBar.vue')['default']
  }
  export interface ComponentCustomProperties {
    vLoading: typeof import('element-plus/es')['ElLoadingDirective']
  }
}
src/components/CardButton.vue
@@ -1,9 +1,14 @@
<template>
  <BaseCard type="btn" size="medium" direction="right" @click="handleClick">
  <BaseCard
    type="btn"
    size="medium"
    :direction="direction"
    @click="handleClick"
  >
    <template #content>
      <div class="card-btn">
        <img :src="src" class="ff-img m-b-8" />
        <span v-for="(item, index) in name" :key="index"> {{ item }}</span>
        <span v-for="item in name" :key="item"> {{ item }}</span>
      </div>
    </template>
  </BaseCard>
@@ -18,13 +23,13 @@
    name: String,
    direction: {
      type: String,
      default: 'left'
      default: 'right'
    }
  },
  emits: ['click'],
  data() {
    return {
      src: this.direction ? shrinkLeft : shrinkRight
      src: this.direction == 'right' ? shrinkRight : shrinkLeft
    };
  },
  computed: {},
src/components/SliderBar.vue
@@ -13,17 +13,22 @@
      </el-select>
    </el-form-item>
    <div class="slider-wrap m-l-16">
      <el-slider v-model="progress" :marks="marks" @input="handleInput" />
      <el-slider :model-value="progress" :marks="marks" @input="handleInput" />
    </div>
  </el-row>
</template>
<script>
export default {
  emits: ['input', 'sizeChange'],
  props: {
    progress: {
      type: Number,
      default: 0
    }
  },
  emits: ['update:progress', 'sizeChange'],
  data() {
    return {
      pageSize: 200,
      progress: 0,
      marks: {
        0: {
          style: {
@@ -43,7 +48,7 @@
  methods: {
    handleInput(e) {
      // console.log(e);
      this.$emit('input', e);
      this.$emit('update:progress', e);
    },
    handleSizeChange(e) {
      this.$emit('sizeChange', e);
src/components/core/CoreMenu.vue
@@ -1,6 +1,6 @@
<template>
  <div class="map-mode-change p-events-auto">
    <template v-for="(item, index) in menu" :key="index">
    <template v-for="(item, index) in menu" :key="item.path">
      <a :class="btnClz(item.selected)" @click="navTo(index)">
        <div>{{ item.name }}</div>
      </a>
src/components/map/MapToolbox.vue
@@ -14,7 +14,7 @@
      <el-dropdown-menu>
        <el-dropdown-item
          v-for="(item, index) in toolItem"
          :key="index"
          :key="item.label"
          :command="index"
        >
          <el-button
src/components/monitor/DataSummary.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,91 @@
<template>
  <el-row justify="end">
    <el-col span="4">
      <CardButton
        name="数据统计"
        direction="left"
        @click="() => (show = !show)"
      ></CardButton>
    </el-col>
    <el-col span="20">
      <BaseCard v-show="show" direction="rigtht" borderless="r">
        <template #content>
          <!-- <el-row>
            <el-text style="color: white">数据统计</el-text>
          </el-row> -->
          <el-table
            :data="showSummary"
            v-loading="loading"
            table-layout="auto"
            size="small"
            :show-overflow-tooltip="true"
            border
            row-class-name="t-row"
            cell-class-name="t-cell"
            header-row-class-name="t-header-row"
            header-cell-class-name="t-header-cell"
          >
            <el-table-column prop="factor" label="因子" width="66" />
            <el-table-column prop="avg" label="均值" width="66" />
            <el-table-column prop="min" label="最小值" width="66" />
            <el-table-column prop="max" label="最大值" width="66" />
          </el-table>
        </template>
      </BaseCard>
    </el-col>
  </el-row>
</template>
<script>
import { FactorDatas } from '@/model/FactorDatas';
import { factorName } from '@/constant/factor-name';
export default {
  props: {
    loading: Boolean,
    factorDatas: FactorDatas,
    selectFactorType: Array
  },
  data() {
    return {
      show: true
    };
  },
  computed: {
    summary() {
      const list = [];
      for (const key in this.factorDatas.factor) {
        if (Object.hasOwnProperty.call(this.factorDatas.factor, key)) {
          const f = this.factorDatas.factor[key];
          let min,
            max,
            total = 0,
            count = 0;
          f.datas.forEach((v) => {
            if (!min || v.factorData < min) {
              min = v.factorData;
            }
            if (!max || v.factorData > max) {
              max = v.factorData;
            }
            total += v.factorData;
            count++;
          });
          list.push({
            factorId: f.factorId,
            factor: factorName[f.factorName],
            min,
            max,
            avg: count == 0 ? 0 : Math.round((total / count) * 100) / 100
          });
        }
      }
      return list;
    },
    showSummary() {
      return this.summary.filter((v) => {
        return this.selectFactorType.includes(v.factorId);
      });
    }
  }
};
</script>
src/components/monitor/DataTable.vue
@@ -1,61 +1,109 @@
<template>
  <BaseCard>
  <BaseCard size="medium" direction="right">
    <template #content>
      <el-table
        :data="tableData"
        ref="tableRef"
        :data="showData"
        v-loading="loading"
        table-layout="fixed"
        :row-class-name="tableRowClassName"
        :height="tableHeight"
        table-layout="auto"
        height="calc(94vh - var(--bevel-length-2))"
        size="small"
        :show-overflow-tooltip="true"
        border
        row-class-name="t-row"
        cell-class-name="t-cell"
        header-row-class-name="t-header-row"
        header-cell-class-name="t-header-cell"
        :show-summary="false"
        :highlight-current-row="true"
        @row-click="handleRowClick"
      >
        <el-table-column prop="TIME" label="时间" />
        <el-table-column
          v-for="(item, index) in tableColumn"
          :key="index"
          :prop="item.name"
          :label="item.label"
        />
          :fixed="true"
          prop="TIME"
          label="时间"
          :formatter="timeFormatter"
          align="center"
          width="66"
        >
        </el-table-column>
        <template v-for="item in tableColumn" :key="item.name">
          <el-table-column
            v-if="selectFactorType.includes(item.value)"
            :prop="item.name"
            :label="item.label"
            align="center"
            width="64"
          />
        </template>
      </el-table>
      <el-pagination
        v-if="pagination"
        ref="paginationRef"
        class="el-pagination"
        small
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :page-sizes="[10, 20, 50, 100]"
        :background="true"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
        :page-sizes="[200, 500]"
        :hide-on-single-page="false"
        layout="prev, pager, next"
        :total="tableData.length"
      />
    </template>
    <template #footer> </template>
    <template #footer>
      <el-row justify="end">
        <el-text size="small" type="warning"
          >共 {{ tableData.length }} æ¡ï¼Œ{{ pageSize }}条/页</el-text
        >
      </el-row>
    </template>
  </BaseCard>
</template>
<script>
import moment from 'moment';
import { FactorDatas } from '@/model/FactorDatas';
import { checkboxOptions } from '@/constant/checkbox-options';
import { TYPE0 } from '@/constant/device-type';
export default {
  props: {
    loading: Boolean,
    factorDatas: FactorDatas,
    deviceType: {
      type: String,
      // type0: è½¦è½½æˆ–无人机; type1:无人船
      default: TYPE0
    }
    },
    selectFactorType: {
      type: Array,
      default: () => ['1', '2', '3']
    },
    // å½“前选中高亮的数据点索引
    locateIndex: Number
  },
  data() {
    return {
      tableHeight: '500',
      total: 0,
      currentPage: 1,
      pageSize: 20,
      loading: false
      pageSize: 200,
      rowHeight: undefined
    };
  },
  emits: ['tableClick'],
  watch: {
    locateIndex(nV, oV) {
      if (nV == oV) return;
      this.$refs.tableRef.setCurrentRow(this.tableData[nV]);
      // è®¡ç®—分页
      this.currentPage = parseInt(nV / this.pageSize) + 1;
      // è®¡ç®—对应分页中的索引
      const index = nV % this.pageSize;
      const h = this.getRowHeight();
      this.$refs.tableRef.setScrollTop(h * index - 350);
    }
  },
  computed: {
    tableData() {
@@ -65,18 +113,87 @@
          const f = this.factorDatas.factor[key];
          f.datas.forEach((v, i) => {
            if (list.length <= i) {
              list.push({ [f.factorName]: v });
              list.push({
                index: i,
                [f.factorName]: v.factorData
              });
            } else {
              list[i][f.factorName] = v;
              list[i][f.factorName] = v.factorData;
            }
          });
        }
      }
      return list;
    },
    showData() {
      const sIndex = (this.currentPage - 1) * this.pageSize;
      const eIndex = sIndex + this.pageSize;
      return this.tableData.slice(sIndex, eIndex);
    },
    tableColumn() {
      return checkboxOptions(this.deviceType);
    }
  },
  methods: {
    // èŽ·å–è¡¨æ ¼ç¬¬ä¸€è¡Œé«˜åº¦
    getRowHeight() {
      if (!this.rowHeight) {
        const rowList = document.getElementsByClassName('t-row');
        if (rowList.length != 0) {
          const row = rowList[0];
          this.rowHeight = row.getBoundingClientRect().height;
        } else {
          this.rowHeight = 0;
        }
      }
      return this.rowHeight;
    },
    timeFormatter(row, col, cellValue, index) {
      return moment(cellValue).format('HH:mm:ss');
    },
    handleRowClick(row, col, event) {
      this.$emit('tableClick', row.index);
      // console.log(row);
      // console.log(col);
      // console.log(event.target.getBoundingClientRect().height);
    }
  }
};
</script>
<style>
.el-table {
  --el-table-bg-color: transparent;
  --el-table-row-hover-bg-color: #23dad0a2;
  --el-table-current-row-bg-color: #7dff5d96;
  --el-table-text-color: var(--font-color);
}
.t-row {
  cursor: pointer;
  background-color: transparent !important;
}
.t-cell {
  /* background: red !important; */
  /* height: 40px;
  border: 1px solid black; */
}
.t-header-row {
}
.t-header-cell {
  background-color: var(--bg-color-2) !important;
  text-align: center !important;
  color: white !important;
}
.el-pagination {
  --el-pagination-bg-color: transparent;
  --el-pagination-button-bg-color: transparent;
  --el-pagination-button-color: transparent;
  --el-pagination-button-disabled-color: white;
  --el-pagination-button-disabled-bg-color: transparent;
  --el-pagination-text-color: white;
  --el-pagination-button-color: white;
}
</style>
src/components/monitor/FactorCheckbox.vue
@@ -1,17 +1,20 @@
<template>
  <BaseCard direction="top-left" borderless="t">
  <BaseCard :direction="direction" :borderless="borderlessDirection">
    <template #content>
      <el-checkbox-group
        v-model="checkbox"
        :model-value="modelValue"
        size="default"
        @change="handleChange"
        :min="1"
        @update:model-value="handleChange"
      >
        <el-checkbox
          v-for="(item, i) in options"
          :key="i"
          :value="item.value"
          >{{ item.label }}</el-checkbox
        >
        <div :class="vertical ? 'vertical-class' : ''">
          <el-checkbox
            v-for="item in options"
            :key="item.label"
            :value="item.value"
            >{{ item.label }}</el-checkbox
          >
        </div>
      </el-checkbox-group>
    </template>
  </BaseCard>
@@ -24,13 +27,41 @@
export default {
  props: {
    // å¤é€‰æ¡†å€¼
    modelValue: Array,
    deviceType: {
      type: String,
      // type0: è½¦è½½æˆ–无人机; type1:无人船
      default: TYPE0
    },
    /**
     *
     * æ ·å¼æœå‘
     * top-left | left
     */
    direction: {
      type: String,
      default: 'top-left'
    },
    /**
     * æ— è¾¹æ¡†çš„æ–¹å‘
     * t | r
     */
    borderlessDirection: {
      type: String,
      default: 't'
    },
    /**
     * å†…容是否垂直排布
     */
    vertical: Boolean,
    // é»˜è®¤é€‰ä¸­çš„个数(从第一个选项开始)
    defaultNum: {
      type: Number,
      default: 1
    }
  },
  emits: ['change'],
  emits: ['update:modelValue'],
  data() {
    return {
      checkbox: [checkboxOptions(TYPE0)[0].value]
@@ -44,13 +75,19 @@
  watch: {
    deviceType(nV, oV) {
      if (nV != oV) {
        this.checkbox = this.options[0].value;
        const res = [];
        const array = checkboxOptions(nV);
        for (let i = 0; i < this.defaultNum; i++) {
          const e = array[i];
          res.push(e.value);
        }
        this.$emit('update:modelValue', res);
      }
    }
  },
  methods: {
    handleChange(value) {
      this.$emit('change', value);
      this.$emit('update:modelValue', value);
    }
  }
};
@@ -61,4 +98,9 @@
  margin-right: 6px;
  /* height: initial; */
}
.vertical-class {
  display: flex;
  flex-direction: column;
}
</style>
src/components/monitor/LineChart.vue
@@ -6,7 +6,7 @@
    <template #footer>
      <!-- å•页数据量-->
      <SliderBar
        @input="(e) => (progress = e)"
        v-model:progress="progress"
        @size-change="(e) => (pageSize = e)"
      ></SliderBar>
    </template>
@@ -21,6 +21,7 @@
export default {
  props: {
    loading: Boolean,
    factorDatas: {
      type: FactorDatas
      // default: () => new FactorDatas()
@@ -28,7 +29,9 @@
    selectFactorType: {
      type: Array,
      default: () => ['1']
    }
    },
    // å½“前选中高亮的数据点索引
    locateIndex: Number
  },
  data() {
    return {
@@ -39,6 +42,7 @@
      progress: 0
    };
  },
  emits: ['chartClick'],
  watch: {
    factorDatas: {
      handler() {
@@ -56,6 +60,35 @@
      this.changeChartRange();
    },
    pageSize() {
      this.changeChartRange();
    },
    locateIndex(nV, oV) {
      if (nV == oV) return;
      // 1. å®šä½ç‚¹åº”该展示在趋势图中间,因此定位百分比往前偏移当前_size的一半
      let i = nV - parseInt(this.pageSize / 2);
      // 2. ç¡®ä¿ç´¢å¼•不会超出范围
      i = i < 0 ? 0 : i;
      // 3. èŽ·å–ç´¢å¼•å¯¹åº”çš„è¿›åº¦ç™¾åˆ†æ¯”
      this.progress = (i / (this.allXAxis.length - this.pageSize)) * 100;
      for (const iterator of this.allSeries) {
        // if (iterator.name == factorName || (iterator.name == 'TVOC' || factorName == 'VOC')) {
        iterator.markLine = {
          symbol: 'none',
          data: [
            {
              name: '选中',
              xAxis: this.allXAxis[nV],
              label: {
                color: 'white'
              },
              lineStyle: {
                color: 'yellow'
              }
            }
          ]
        };
      }
      this.changeChartRange();
    }
  },
@@ -103,16 +136,18 @@
      }
      // this.option.dataZoom[0].start = startPer;
      // this.option.dataZoom[0].end = endPer;
      this.lineChart.setOption(this.option);
      this.lineChart.setOption(this.option, { notMerge: true });
    },
    getShowXAxis(sIndex, eIndex) {
      return this.allXAxis.slice(sIndex, eIndex);
    },
    getShowSeries(sIndex, eIndex) {
      const res = this.allSeries.filter((s) => {
      this.allSeries.forEach((s) => {
        if (sIndex && eIndex) {
          s.data = s.allData.slice(sIndex, eIndex);
        }
      });
      const res = this.allSeries.filter((s) => {
        return this.selectFactorType.includes(s.key);
      });
      return res;
@@ -137,6 +172,9 @@
  },
  mounted() {
    this.lineChart = echarts.init(this.$refs.lineChart);
    this.lineChart.on('click', (e) => {
      this.$emit('chartClick', e.dataIndex);
    });
  }
};
</script>
src/model/FrameAnimation.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,143 @@
import moment from 'moment';
/**
 * å¸§åŠ¨ç”»æŽ§åˆ¶å·¥å…·
 *
 */
function FrameAnimation() {
  // åŠ¨ç”»å¸§æ•°
  this.fps = 12;
  // å¸§é—´éš”(ms)
  this.timeout = 1000 / this.fps;
  this.speed = 1;
  this.animationTask = [];
  // å•次插入最多任务数
  this.maxTasks = 10;
  this.onNextTask;
  this.isPause = false;
  this.isRunning = false;
  // æ˜¯å¦å¼€å¯åŠ¨æ€ç»˜åˆ¶é€Ÿåº¦
  this.dynamicSpeed = false;
  // æ¯ä¸ªä»»åŠ¡è€—æ—¶ï¼ˆç§’ï¼‰
  this.taskPeriod = 4;
  // æ•°æ®æœ€æ–°æ—¶é—´
  this.lastestTime;
}
FrameAnimation.prototype = {
  start: function () {
    if (this.intervalFlag != undefined) {
      this.isPause = false;
    } else {
      this.isRunning = true;
      this._doTask();
    }
  },
  /**
   * åŠ¨æ€åŠ¨ç”»é€Ÿåº¦
   * å½“页面失去焦点时,循环任务js会暂停,此时后续的轨迹动画就会被延后;
   * åŒæ—¶ï¼Œç”±äºŽæµè§ˆå™¨æ€§èƒ½é—®é¢˜ï¼Œå½“运行时间逐渐变长时,轨迹动画与实际时间的差值也会逐渐拉大;
   * å› æ­¤éœ€è¦åŠ¨æ€è°ƒæ•´åŠ¨ç”»è¿è¡Œå€é€Ÿï¼Œä¸Žå®žé™…æ—¶é—´ä¿æŒç›¸è¿‘
   */
  dynamicAdjustment: function (time) {
    if (this.dynamicSpeed && this.lastestTime) {
      // ä»¥ä¸¤ä¸ªåæ ‡ç‚¹ï¼ˆä¸€æ¬¡task)为最小单位,根据当前绘制数据时间和最新数据时间的差值,调整绘制速度
      var t1 = moment(time);
      var t2 = moment(this.lastestTime);
      var sec = Math.abs(t1.diff(t2, 'seconds')); // èŽ·å–æ—¶é—´å·®
      // å½“时间差超过单任务耗时时,需要适当加快动画速度
      if (sec > this.taskPeriod) {
        this.speed = Math.round(sec / this.taskPeriod);
        if (this.speed > 16) {
          // this.speed = 16
        }
      } else {
        this.speed = 1;
      }
    }
  },
  changeSpeed: function (speed) {
    if (typeof speed === 'number') {
      this.speed = speed;
    }
  },
  pause: function () {
    this.isPause = true;
  },
  stop: function () {
    if (this.intervalFlag != undefined) {
      clearInterval(this.intervalFlag);
      this.intervalFlag = undefined;
      this.animationTask = [];
    }
    // å½“前任务列表结束后,首先判断是否有下一段任务
    if (this.onNextTask) {
      this.onNextTask();
      return;
    }
    if (typeof this.onStopCallback === 'function') {
      this.onStopCallback();
    }
    this.isRunning = false;
    this.isPause = false;
    this.lastestTime = undefined;
  },
  setOnStopCallback: function (callback) {
    this.onStopCallback = callback;
  },
  addTask: function (count, data, callback) {
    var task = {
      data: data,
      count: count,
      task: callback
    };
    this.animationTask.push(task);
    // console.log('FrameAnimation: addTask ' + count);
  },
  // ä»»åŠ¡é‡‡å–åˆ†æ®µæ‰§è¡Œæ–¹å¼ï¼Œå³å½“å½“å‰ä»»åŠ¡åˆ—è¡¨æ‰§è¡Œå®ŒæˆåŽï¼Œç»§ç»­å¼€å§‹å¤„ç†ä¸‹ä¸€æ®µä»»åŠ¡
  addOnNextTasks: function (callback) {
    this.onNextTask = callback;
  },
  runStatus: function () {
    return this.isRunning;
  },
  _doTask: function () {
    if (this.animationTask.length > 0) {
      var t = this.animationTask[0];
      // åŠ¨æ€è°ƒæ•´é€Ÿåº¦
      this.dynamicAdjustment(t.data.times[t.data.times.length - 1]);
      var index = 0;
      this.intervalFlag = setInterval(() => {
        if (this.isPause) {
          return;
        }
        if (index >= t.count) {
          this._endTask(this.intervalFlag);
          return;
        }
        t.task(t.data, index, t.count);
        index += this.speed;
        // console.log("_doTask:" + index);
      }, this.timeout);
    }
  },
  _endTask: function (intervalFlag) {
    clearInterval(intervalFlag);
    this.animationTask.splice(0, 1);
    if (this.animationTask.length > 0) {
      this._doTask();
    } else {
      this.stop();
    }
  }
};
export { FrameAnimation };
src/utils/map/3dLayer.js
@@ -87,89 +87,88 @@
  return scale;
}
/**
 * ç»˜å›¾
 */
function drawMesh(fDatas, factor, center, merge) {
  const lnglats_GD = fDatas.lnglats_GD;
  const coors = fDatas.coors_GD;
  const heights = factor.heights;
  const colors = factor.colors;
  const bColor = factor.bottomColor;
  if (center) {
    map.setZoomAndCenter(16, center);
  }
  // eslint-disable-next-line no-undef
  var cylinder = new AMap.Object3D.Mesh();
  cylinder.backOrFront = 'both';
  cylinder.transparent = true;
  var geometry = cylinder.geometry;
  const scale = _getScale(_minH, _maxH);
  for (let i = 0; i < coors.length; i++) {
    var r = lnglats_GD[i];
    var lastP = lnglats_GD[i - 1];
    var p = coors[i];
    var h = (heights[i] - _minH) * scale + _minHeight;
    if (heights[i] == -1) {
      h = -1;
    }
    geometry.vertices.push(p.x, p.y, 0); //底部顶点
    geometry.vertices.push(p.x, p.y, 0 - h); //顶部顶点
    if (i > 0) {
      // eslint-disable-next-line no-undef
      var distance = AMap.GeometryUtil.distance(r, lastP);
      //两个数据点最小间隔时间为4s,假设车速按照120km/h计算,4s行驶最大距离作为132米,
      //设定超过1分钟的数据绘制特殊的连线
      if (distance <= 500 && h != -1) {
        var bottomIndex = i * 2;
        var topIndex = bottomIndex + 1;
        var lastBottomIndex = bottomIndex - 2;
        var lastTopIndex = bottomIndex - 1;
        geometry.faces.push(bottomIndex, topIndex, lastTopIndex);
        geometry.faces.push(bottomIndex, lastBottomIndex, lastTopIndex);
      }
    }
    // var bColor = bColor
    var tColor = colors[i];
    geometry.vertexColors.push.apply(geometry.vertexColors, bColor); //底部顶点颜色
    geometry.vertexColors.push.apply(geometry.vertexColors, tColor); //顶部顶点颜色
  }
  // 7.根据合并选项重置或新增当前缓存数据
  if (merge != true) {
    _factorDatas = fDatas;
    _factor = factor;
    if (_cylinder != undefined) {
      object3Dlayer.remove(_cylinder);
    }
  } else {
    // _factorDatas.lnglats.push.apply(
    //   _factorDatas.lnglats,
    //   lnglats_GD
    // );
    // _factorDatas.coors.push.apply(_factorDatas.coors, coors);
    // _factorDatas.heights.push.apply(_factorDatas.heights, heights);
    // _factorDatas.colors.push.apply(_factorDatas.colors, colors);
    // _factorDatas.bottomColor = bColor;
  }
  object3Dlayer.add(cylinder);
  /**************test ****************/
  // object3Dlayer.on('mouseover', function (e) {
  //   console.log(
  //     `鼠标移入覆盖物! [${e.lnglat.getlng()}, ${e.lnglat.getLat()}]`
  //   );
  // });
  /**************test ****************/
  _cylinder = cylinder;
}
export default {
  /**
   * ç»˜å›¾
   */
  drawMesh(fDatas, factor, center, merge) {
    const lnglats_GD = fDatas.lnglats_GD;
    const coors = fDatas.coors_GD;
    const heights = factor.heights;
    const colors = factor.colors;
    const bColor = factor.bottomColor;
    if (center) {
      map.setZoomAndCenter(16, center);
    }
    // eslint-disable-next-line no-undef
    var cylinder = new AMap.Object3D.Mesh();
    cylinder.backOrFront = 'both';
    cylinder.transparent = true;
    var geometry = cylinder.geometry;
    const scale = _getScale(_minH, _maxH);
    for (let i = 0; i < coors.length; i++) {
      var r = lnglats_GD[i];
      var lastP = lnglats_GD[i - 1];
      var p = coors[i];
      var h = (heights[i] - _minH) * scale + _minHeight;
      if (heights[i] == -1) {
        h = -1;
      }
      geometry.vertices.push(p.x, p.y, 0); //底部顶点
      geometry.vertices.push(p.x, p.y, 0 - h); //顶部顶点
      if (i > 0) {
        // eslint-disable-next-line no-undef
        var distance = AMap.GeometryUtil.distance(r, lastP);
        //两个数据点最小间隔时间为4s,假设车速按照120km/h计算,4s行驶最大距离作为132米,
        //设定超过1分钟的数据绘制特殊的连线
        if (distance <= 500 && h != -1) {
          var bottomIndex = i * 2;
          var topIndex = bottomIndex + 1;
          var lastBottomIndex = bottomIndex - 2;
          var lastTopIndex = bottomIndex - 1;
          geometry.faces.push(bottomIndex, topIndex, lastTopIndex);
          geometry.faces.push(bottomIndex, lastBottomIndex, lastTopIndex);
        }
      }
      // var bColor = bColor
      var tColor = colors[i];
      geometry.vertexColors.push.apply(geometry.vertexColors, bColor); //底部顶点颜色
      geometry.vertexColors.push.apply(geometry.vertexColors, tColor); //顶部顶点颜色
    }
    // 7.根据合并选项重置或新增当前缓存数据
    if (merge != true) {
      _factorDatas = fDatas;
      _factor = factor;
      if (_cylinder != undefined) {
        object3Dlayer.remove(_cylinder);
      }
    } else {
      // _factorDatas.lnglats.push.apply(
      //   _factorDatas.lnglats,
      //   lnglats_GD
      // );
      // _factorDatas.coors.push.apply(_factorDatas.coors, coors);
      // _factorDatas.heights.push.apply(_factorDatas.heights, heights);
      // _factorDatas.colors.push.apply(_factorDatas.colors, colors);
      // _factorDatas.bottomColor = bColor;
    }
    object3Dlayer.add(cylinder);
    /**************test ****************/
    // object3Dlayer.on('mouseover', function (e) {
    //   console.log(
    //     `鼠标移入覆盖物! [${e.lnglat.getlng()}, ${e.lnglat.getLat()}]`
    //   );
    // });
    /**************test ****************/
    _cylinder = cylinder;
  },
  /**
   * ç»˜åˆ¶3D走行路线图
   * @param fDatas å®Œæ•´ç›‘测数据
@@ -213,7 +212,7 @@
    }
    // 5.绘制3D图形
    drawMesh(fDatas, factor, center, merge);
    this.drawMesh(fDatas, factor, center, merge);
    // ç¼©æ”¾åœ°å›¾åˆ°åˆé€‚的视野级别
    // map.setFitView()
src/utils/map/animation.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,389 @@
import { FrameAnimation } from '@/model/FrameAnimation';
import calculate from '@/utils/map/calculate';
import Layer from '@/utils/map/3dLayer';
import sector from '@/utils/map/sector';
import map from '@/utils/map/index_old';
function MapAnimation() {
  // éžè¿žç»­åæ ‡ç‚¹æœ€å¤§è·ç¦»(ç±³)
  this.maxD = 500;
  // å½“前绘制的监测因子类型
  this.factorType = 0;
  this.factorDatas;
  // è½½å…·ç±»åž‹
  this.vehicleType = 0; // 0: è½¦è¾†ï¼›1:无人机:2:无人船
  // å¸§åŠ¨ç”»æŽ§åˆ¶å·¥å…·
  this.frameAnimation = new FrameAnimation();
  // åŠ¨ç”»å¸§æ•°
  this._fps = this.frameAnimation.fps;
  // ç»˜åˆ¶å›¾å½¢ç¼“å­˜
  this.moveViews = {};
}
MapAnimation.prototype = {
  setFactorType: function (factorType) {
    this.factorType = factorType;
  },
  /**
   * è®¾ç½®è½½å…·ç±»åž‹ï¼Œå¯¹åº”显示不同的图标
   */
  setVehicleType: function (t) {
    this.vehicleType = t;
    this.moveViews = {};
  },
  /**
   * è½¦è¾†ç§»åŠ¨åŠ¨ç”»
   * @param {*} factorDatas
   * @param {*} factorType
   */
  moveAnimation: function (factorDatas, factorType, startIndex) {
    // MapUtil.drawLine(factorDatas.lnglats_GD)
    // è®¾ç½®å½“前数据的最新时间
    this.frameAnimation.lastestTime =
      factorDatas.times[factorDatas.times.length - 1];
    // å½“数据缓存不为空时,说明上一次的绘制还未结束,所以只更新数据源即可
    if (this.factorDatas != undefined) {
      this.factorType = factorType;
      this.factorDatas = factorDatas;
      return;
    }
    this.factorType = factorType;
    this.factorDatas = factorDatas;
    this.startIndex = startIndex == undefined ? 0 : startIndex;
    // ä¸€æ¬¡æ€§æœ€å¤šè®¡ç®—[this.frameAnimation.maxTasks]个坐标点的绘制轨迹,绘制完成后再次计算,提升前端网页响应速度
    this.endIndex = this.startIndex + this.frameAnimation.maxTasks;
    if (this.endIndex >= factorDatas.length()) {
      this.endIndex = factorDatas.length() - 1;
    }
    // console.log(new Date());
    this.nextAnimation(this.startIndex, factorDatas, this.endIndex);
    // console.log(new Date());
  },
  nextAnimation: function (start, factorDatas, endIndex) {
    if (factorDatas == undefined) {
      return;
    }
    var that = this;
    // æ•°æ®æ ¼å¼è½¬æ¢
    for (let i = start; i < factorDatas.length(); i++) {
      // ç»ˆç‚¹
      if (i == endIndex || i == factorDatas.length() - 1) {
        break;
      }
      // åŠ¨ç”»è½¨è¿¹
      var animationData = factorDatas.getByIndex(0, i + 1);
      // è·¯å¾„点
      var sPos = factorDatas.lnglats_GD[i];
      var ePos = factorDatas.lnglats_GD[i + 1];
      // ç»ˆç‚¹ç›¸å¯¹èµ·ç‚¹çš„角度
      var angle = this._getAngle(sPos, ePos);
      // è·¯å¾„总长度
      // eslint-disable-next-line no-undef
      var distance = AMap.GeometryUtil.distance(sPos, ePos);
      // æ—¶é—´å·®
      var d1 = new Date(factorDatas.times[i]);
      var d2 = new Date(factorDatas.times[i + 1]);
      var duration = (d2 - d1) / 1000;
      // æ€»å¸§æ•°
      var count = duration * this._fps;
      // å•帧距离
      var d = distance / count;
      // æ¯ä¸ªfps对应的实际时长(默认1秒)
      var t = 1;
      // ä¸¤ç‚¹é—´ä¸è¿žç»­æ—¶
      if (distance > this.maxD) {
        count = 4 * this._fps;
        d = distance / count;
        t = duration / (count / this._fps);
      }
      // ç›‘测数据
      // var fData1 = factorDatas.getByIndex(i, i + 1)
      var fData2 = factorDatas.getByIndex(i + 1, i + 2);
      // è®¡ç®—动画轨迹
      for (let i = 0; i < count - 1; i++) {
        // path
        var length = d * (i + 1);
        if (isNaN(angle)) {
          animationData.lnglats_GD.push(sPos);
        } else {
          var point = this._getLatLon(sPos, length, angle);
          animationData.lnglats_GD.push(point);
        }
        // time
        var time = d1.format('yyyy-MM-dd hh:mm:ss');
        if ((i + 1) % this._fps == 0) {
          var t1 = d1.getTime();
          d1.setTime(t1 + t * 1000);
          time = d1.format('yyyy-MM-dd hh:mm:ss');
        }
        animationData.times.push(time);
      }
      // factor
      for (const key in animationData.factor) {
        var factor = animationData.factor[key];
        factor.insertFrame(fData2.factor[key], count, distance <= this.maxD);
      }
      animationData.refreshHeight();
      var coor_GD = calculate.lngLatToGeodeticCoord(animationData.lnglats_GD);
      animationData.coors_GD = coor_GD;
      this.frameAnimation.addTask(
        count,
        animationData,
        function (data, index, count) {
          var length = data.length();
          var start = length - count + 1;
          // 1.获取数据
          var d = data.getByIndex(0, start + index + 1);
          var f = d.factor[that.factorType + 1 + ''];
          // 2.绘制图形
          if (length > count || index > 0) {
            // 3d图形
            // var lnglat = d.lnglats_GD[d.lnglats_GD.length - 1];
            Layer.drawMesh(d, f);
            // MapUtil.drawLine(lnglat)
            // é£Žå‘风速
            sector.drawSector(d, start + index);
            // if (d.factor['17'] != undefined && d.factor['16'] != undefined) {
            //   var windDir = d.factor['17'].datas;
            //   windDir = windDir[windDir.length - 1].factorData;
            //   var windSpeed = d.factor['16'].datas;
            //   windSpeed = windSpeed[windSpeed.length - 1].factorData;
            //   MapUtil.drawSector4(lnglat, windDir, windSpeed);
            // }
          }
          var pos = d.lnglats_GD[d.lnglats_GD.length - 1];
          var time = d.times[d.times.length - 1].slice(10);
          // ç»˜åˆ¶è½¦è¾†å›¾æ ‡å’Œæ—¶é—´æ–‡æœ¬
          var status = 0;
          // åœæ­¢
          if (index + 1 == count) {
            status = 1;
          }
          // è¡Œé©¶ä¸­
          else {
            status = 2;
          }
          that._setPosition(pos, time, status);
          // console.log("animation");
        }
      );
    }
    // å½“endIndex已经是最后一个坐标点时,路径绘制结束,否则开始下一段路径绘制
    if (that.endIndex < that.factorDatas.length() - 1) {
      that.startIndex = that.endIndex;
      that.endIndex = that.startIndex + that.frameAnimation.maxTasks;
      if (that.endIndex >= that.factorDatas.length) {
        that.endIndex = that.factorDatas.length - 1;
      }
      var cb = function () {
        that.nextAnimation(that.startIndex, that.factorDatas, that.endIndex);
      };
      that.frameAnimation.addOnNextTasks(cb);
    }
    // å½“全部数据绘制完成时,清空数据
    else {
      that.factorDatas = undefined;
      that.frameAnimation.addOnNextTasks(undefined);
    }
    this.start();
  },
  /*******************************动画任务逻辑 -start ******************************/
  start: function () {
    sector.clearSector();
    this.frameAnimation.start();
  },
  changeSpeed: function (speed) {
    this.frameAnimation.changeSpeed(speed);
  },
  pause: function () {
    this.frameAnimation.pause();
  },
  stop: function () {
    this.factorDatas = undefined;
    this.frameAnimation.addOnNextTasks(undefined);
    this.frameAnimation.stop();
  },
  setOnStopCallback: function (callback) {
    this.frameAnimation.setOnStopCallback(
      function () {
        this._clearMap();
        callback();
      }.bind(this)
    );
  },
  runStatus: function () {
    return this.frameAnimation.runStatus();
  },
  /**
   * è®¾ç½®æ˜¯å¦å¼€å¯åŠ¨æ€ç»˜åˆ¶é€Ÿåº¦
   */
  setDynamicSpeed: function (b, sec) {
    if (b) {
      this.frameAnimation.dynamicSpeed = true;
      this.frameAnimation.taskPeriod = sec;
    } else {
      this.frameAnimation.dynamicSpeed = false;
      this.frameAnimation.taskPeriod = sec;
    }
  },
  /*******************************动画任务逻辑 -end ******************************/
  _clearMap: function () {
    var list = [];
    for (const key in this.moveViews) {
      list.push(this.moveViews[key]);
    }
    map.remove(list);
    this.moveViews = {};
    sector.clearSector();
  },
  _setPosition: function (pos, time, status) {
    // eslint-disable-next-line no-undef
    var lnglat = new AMap.LngLat(...pos);
    if (this.moveViews['text'] == undefined) {
      var url;
      switch (this.vehicleType) {
        case 0:
          url = './asset/mipmap/car_driving.png';
          break;
        case 1:
          url = './asset/mipmap/car_driving.png';
          break;
        case 2:
          url = './asset/mipmap/boat_driving.png';
          break;
        default:
          url = './asset/mipmap/car_driving.png';
          break;
      }
      // var url = "./asset/mipmap/car_offline.png";
      // switch (status) {
      //   case 0:
      //     url = "./asset/mipmap/car_offline.png";
      //     break;
      //   case 1:
      //     url = "./asset/mipmap/car_stop.png";
      //     break;
      //   case 2:
      //     url = "./asset/mipmap/car_driving.png";
      //     break;
      //   default:
      //     url = "./asset/mipmap/car_stop.png";
      //     break;
      // }
      var icon = new AMap.Icon({
        size: new AMap.Size(60, 30),
        imageSize: new AMap.Size(54, 21),
        image: url
        // imageOffset: new AMap.Pixel(-16, -16) // ç›¸å¯¹äºŽåŸºç‚¹çš„偏移位置
      });
      var car = new AMap.Marker({
        icon: icon,
        position: lnglat, // åŸºç‚¹ä½ç½®
        offset: new AMap.Pixel(-20, -15) // ç›¸å¯¹äºŽåŸºç‚¹çš„偏移位置
      });
      MapUtil._map.add(car);
      this.moveViews['car'] = car;
      // æ—¶é—´
      var text = new AMap.Text({
        text: time,
        position: lnglat,
        offset: new AMap.Pixel(5, 20),
        style: {
          'font-size': '13px',
          'text-align': 'center',
          color: 'black',
          'background-color': 'white',
          // 'text-shadow': 'black 2px 2px 2px',
          'border-radius': '2px',
          border: '0px',
          padding: '0 4px'
        }
      });
      MapUtil._map.add(text);
      this.moveViews['text'] = text;
    } else {
      MapUtil.setCenter(lnglat);
      this.moveViews['car'].setPosition(lnglat);
      this.moveViews['text'].setPosition(lnglat);
      this.moveViews['text'].setText(time);
    }
  },
  _setStatus: function (status) {},
  /**
   * æ ¹æ®åæ ‡ç‚¹ã€è·ç¦»å’Œè§’度,得到另一个坐标点
   * @param {*} pos
   * @param {*} distance ç±³
   * @param {*} angle
   */
  _getLatLon: function (pos, distance, angle) {
    var Ea = 6378137; //   èµ¤é“半径
    var Eb = 6356725; //   æžåŠå¾„
    var dx = distance * Math.sin((angle * Math.PI) / 180);
    var dy = distance * Math.cos((angle * Math.PI) / 180);
    var ec = Eb + ((Ea - Eb) * (90.0 - pos[1])) / 90.0;
    var ed = ec * Math.cos((pos[1] * Math.PI) / 180);
    var lng = ((dx / ed + (pos[0] * Math.PI) / 180.0) * 180.0) / Math.PI;
    var lat = ((dy / ec + (pos[1] * Math.PI) / 180.0) * 180.0) / Math.PI;
    return [lng, lat];
  },
  /**
   * æ ¹æ®ä¸¤ä¸ªåæ ‡ç‚¹èŽ·å¾—ç›¸å¯¹è§’åº¦
   * @param {*} sPos
   * @param {*} ePos
   */
  _getAngle: function (sPos, ePos) {
    var lng_a = sPos[0];
    var lat_a = sPos[1];
    var lng_b = ePos[0];
    var lat_b = ePos[1];
    var a = ((90 - lat_b) * Math.PI) / 180;
    var b = ((90 - lat_a) * Math.PI) / 180;
    var AOC_BOC = ((lng_b - lng_a) * Math.PI) / 180;
    var cosc =
      Math.cos(a) * Math.cos(b) + Math.sin(a) * Math.sin(b) * Math.cos(AOC_BOC);
    var sinc = Math.sqrt(1 - cosc * cosc);
    var sinA = (Math.sin(a) * Math.sin(AOC_BOC)) / sinc;
    var A = (Math.asin(sinA) * 180) / Math.PI;
    var res = 0;
    if (lng_b > lng_a && lat_b > lat_a) res = A;
    else if (lng_b > lng_a && lat_b < lat_a) res = 180 - A;
    else if (lng_b < lng_a && lat_b < lat_a) res = 180 - A;
    else if (lng_b < lng_a && lat_b > lat_a) res = 360 + A;
    else if (lng_b > lng_a && lat_b == lat_a) res = 90;
    else if (lng_b < lng_a && lat_b == lat_a) res = 270;
    else if (lng_b == lng_a && lat_b > lat_a) res = 0;
    else if (lng_b == lng_a && lat_b < lat_a) res = 180;
    return res;
  }
};
export { MapAnimation };
src/utils/map/dialog.js
@@ -1,6 +1,7 @@
import { factorName } from '../../constant/factor-name';
import { factorUnit } from '../../constant/factor-unit';
import { windDir } from '../../constant/wind-dir';
import { factorName } from '@/constant/factor-name';
import { factorUnit } from '@/constant/factor-unit';
import { windDir } from '@/constant/wind-dir';
import { map } from './index_old';
export const DialogUtil = {
  show: true,
@@ -152,7 +153,8 @@
    // å®šä¹‰é¡¶éƒ¨æ ‡é¢˜
    var top = document.createElement('div');
    // top.className = "info-top";
    top.className = 'ff-content ff-content-top-left ff-content-small-borderless-t info-top';
    top.className =
      'ff-content ff-content-top-left ff-content-small-borderless-t info-top';
    var top_b = document.createElement('div');
    top_b.className = 'ff-border-bottom';
    var top_t = document.createElement('div');
@@ -246,13 +248,13 @@
    return info;
  },
  openNewWindow(factorDatas, i, map, position, onClose) {
  openNewWindow(factorDatas, i, onClose) {
    if (!this.show) return;
    const window = this.createInfoWindow(factorDatas, i, onClose);
    window.open(map, position);
    window.open(map, factorDatas.lnglats_GD[i]);
  },
  openNewWindow2(factorData, map, position, onClose) {
  openNewWindow2(factorData, position, onClose) {
    if (!this.show) return;
    const window = this.createInfoWindow2(factorData, onClose);
    window.open(map, position);
src/utils/map/marks.js
@@ -52,17 +52,8 @@
    });
    massMarks.on('click', (event) => {
      const i = event.data.id;
      // 1. ç»˜åˆ¶æ‰‡å½¢åŒºåŸŸ
      sector.drawSector(fDatas, i);
      // 2. ç»˜åˆ¶å¯¹è¯æ¡†
      DialogUtil.openNewWindow(fDatas, i, map, lnglats[i], () => {
        // ç§»é™¤æ‰‡å½¢åŒºåŸŸ
        // clearSector3();
      });
      // 3. è‡ªå®šä¹‰ç‚¹å‡»äº‹ä»¶
      onClick();
      onClick(i);
    });
    // eslint-disable-next-line no-undef
    var marker = new AMap.Marker({
src/utils/map/sector.js
@@ -1,25 +1,25 @@
import { map, object3Dlayer } from './index_old';
import calculate from './calculate';
import imgLocation from '@/assets/mipmap/location.png';
var _defaultDeg = 30,
  _sector = undefined,
  _sectorViews = {};
function clearSector() {
  var list = [];
  for (const key in _sectorViews) {
    list.push(_sectorViews[key]);
  }
  if (list.length > 0) {
    map.remove(list);
    _sectorViews = {};
  }
  if (_sector) {
    object3Dlayer.remove(_sector);
  }
}
export default {
  clearSector() {
    var list = [];
    for (const key in _sectorViews) {
      list.push(_sectorViews[key]);
    }
    if (list.length > 0) {
      map.remove(list);
      _sectorViews = {};
    }
    if (_sector) {
      object3Dlayer.remove(_sector);
    }
  },
  drawSector(fDatas, i) {
    const lnglat = fDatas.lnglats_GD[i];
    let windDir = fDatas.factor[17].datas[i].factorData;
@@ -31,7 +31,7 @@
      return;
    }
    if (_sector != undefined) {
      clearSector();
      this.clearSector();
    }
    // eslint-disable-next-line no-undef
@@ -126,7 +126,7 @@
      styles: [
        {
          icon: {
            img: './asset/mipmap/location.png',
            img: imgLocation,
            size: [16, 16], //可见区域的大小
            ancher: [8, 16], //锚点
            fitZoom: 18, //最合适的级别
@@ -152,7 +152,7 @@
      styles: [
        {
          icon: {
            img: './asset/mipmap/location.png',
            img: imgLocation,
            size: [16, 16], //可见区域的大小
            ancher: [8, 16], //锚点
            fitZoom: 18, //最合适的级别
@@ -182,7 +182,7 @@
      styles: [
        {
          icon: {
            img: './asset/mipmap/location.png',
            img: imgLocation,
            size: [16, 16], //可见区域的大小
            ancher: [8, 16], //锚点
            fitZoom: 18, //最合适的级别
@@ -207,7 +207,7 @@
      styles: [
        {
          icon: {
            img: './asset/mipmap/location.png',
            img: imgLocation,
            size: [16, 16], //可见区域的大小
            ancher: [8, 16], //锚点
            fitZoom: 18, //最合适的级别
src/views/historymode/HistoryMode.vue
@@ -20,23 +20,36 @@
    </el-row>
    <TrendAnalysis
      class="trend-analysis"
      :locate-index="locateIndex"
      @chart-click="handelIndexChange"
      :factor-datas="factorDatas"
      :device-type="deviceType"
    ></TrendAnalysis>
    <DataSheet
      class="data-sheet"
      :locate-index="locateIndex"
      @table-click="handelIndexChange"
      :factor-datas="factorDatas"
      :device-type="deviceType"
    ></DataSheet>
  </div>
</template>
<script>
import Layer from '@/utils/map/3dLayer';
import marks from '@/utils/map/marks';
import sector from '@/utils/map/sector';
import { DialogUtil } from '@/utils/map/dialog';
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';
import TrendAnalysis from './component/TrendAnalysis.vue';
import DataSheet from './component/DataSheet.vue';
export default {
  components: { TrendAnalysis },
  components: { TrendAnalysis, DataSheet },
  setup() {
    const { loading, fetchData } = useFetchData(10000);
    return { loading, fetchData };
@@ -55,7 +68,9 @@
      setCenter: true,
      // ç»˜åˆ¶æ¨¡å¼ï¼Œ0:自动模式,自动计算当前数据的范围,绘制合适的比例;1:手动模式,根据页面设置的绘图范围进行绘制
      drawMode: 0,
      searchTime: []
      searchTime: [],
      // å½“前选中高亮的数据点索引
      locateIndex: undefined
    };
  },
  watch: {
@@ -66,6 +81,11 @@
    }
  },
  methods: {
    // ç›‘听折线图和表格的点击事件
    handelIndexChange(index) {
      this.locateIndex = index;
      this.drawSector(index);
    },
    draw() {
      // todo åˆ·æ–°å›¾ä¾‹
      const factor = this.factorDatas.factor[this.factorType];
@@ -85,13 +105,24 @@
      // }
    },
    drawMassMarks(e) {
      marks.drawMassMarks(this.factorDatas, e, () => {
      marks.drawMassMarks(this.factorDatas, e, (index) => {
        // æŸ¥è¯¢èŒƒå›´å†…的监测站点
        // SceneUtil.searchByCoordinate(lnglat[0], lnglat[1], distance);
        // 3. è¶‹åŠ¿å›¾è·³è½¬å®šä½
        // const progress = FChart.locate(lineChart.chart, lineChart.option, i, _factor.factorName);
        // 4. è¡¨æ ¼æ•°æ®è·³è½¬å®šä½
        // Table.locate(i);
        this.drawSector(index);
        this.locateIndex = index;
      });
    },
    drawSector(index) {
      // 1. ç»˜åˆ¶æ‰‡å½¢åŒºåŸŸ
      sector.drawSector(this.factorDatas, index);
      // 2. ç»˜åˆ¶å¯¹è¯æ¡†
      DialogUtil.openNewWindow(this.factorDatas, index, () => {
        // ç§»é™¤æ‰‡å½¢åŒºåŸŸ
        // clearSector3();
      });
    },
    onFetchData(type, data) {
@@ -155,4 +186,10 @@
  left: 0;
  bottom: 2px;
}
.data-sheet {
  position: absolute;
  right: 0;
  top: 0;
}
</style>
src/views/historymode/component/DataSheet.vue
@@ -0,0 +1,77 @@
<template>
  <el-row class="wrap">
    <el-col span="2" class="flex-col">
      <el-row justify="end">
        <CardButton
          name="走航数据详情"
          direction="left"
          @click="() => (show = !show)"
        ></CardButton>
      </el-row>
      <el-row class="flex-col">
        <DataSummary
          v-show="show"
          :loading="loading"
          :factor-datas="factorDatas"
          :select-factor-type="selectFactorType"
        ></DataSummary>
      </el-row>
    </el-col>
    <el-col v-show="show" span="10">
      <el-row align="bottom">
        <DataTable
          :loading="loading"
          :select-factor-type="selectFactorType"
          :factor-datas="factorDatas"
          :device-type="deviceType"
          :locate-index="locateIndex"
          @table-click="handleTableClick"
        ></DataTable>
        <FactorCheckbox
          direction="left"
          vertical
          borderless-direction="r"
          v-model="selectFactorType"
          :device-type="deviceType"
        ></FactorCheckbox>
      </el-row>
    </el-col>
  </el-row>
</template>
<script>
import { TYPE0 } from '@/constant/device-type';
import { FactorDatas } from '@/model/FactorDatas';
export default {
  props: {
    loading: Boolean,
    factorDatas: FactorDatas,
    deviceType: {
      type: String,
      // type0: è½¦è½½æˆ–无人机; type1:无人船
      default: TYPE0
    },
    // å½“前选中高亮的数据点索引
    locateIndex: Number
  },
  data() {
    return {
      selectFactorType: ['1', '2', '3'],
      show: false
    };
  },
  emits: ['tableClick'],
  methods: {
    handleTableClick(index) {
      this.$emit('tableClick', index);
    }
  }
};
</script>
<style scoped>
.flex-col {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
</style>
src/views/historymode/component/TrendAnalysis.vue
@@ -3,10 +3,12 @@
    <Transition name="">
      <el-col v-show="show" span="10">
        <FactorCheckbox
          v-model="selectFactorType"
          :device-type="deviceType"
          @change="(e) => (selectFactorType = e)"
        ></FactorCheckbox>
        <LineChart
          :locate-index="locateIndex"
          @chart-click="handleChartClick"
          :factor-datas="factorDatas"
          :select-factor-type="selectFactorType"
        ></LineChart>
@@ -32,13 +34,21 @@
    deviceType: {
      type: String
    },
    factorDatas: FactorDatas
    factorDatas: FactorDatas,
    // å½“前选中高亮的数据点索引
    locateIndex: Number
  },
  data() {
    return {
      selectFactorType: ['1'],
      show: true
    };
  },
  emits: ['chartClick'],
  methods: {
    handleChartClick(index) {
      this.$emit('chartClick', index);
    }
  }
};
</script>