riku
2024-07-17 264880703c677d63b7e35b5eb085e6bc3214e3ed
2024.7.17
已修改19个文件
已添加4个文件
971 ■■■■ 文件已修改
src/api/index.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/CardDialog.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/MessageBox.vue 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chart/ProgressLineChart.vue 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/core/CoreMenu.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/mission/MIssionCreate.vue 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/mission/MissionManage.vue 62 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search/OptionLocation2.vue 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search/OptionMission.vue 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search/OptionTime.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/composables/fetchData.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/composables/messageBox.js 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/constant/location.js 257 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/stores/mission.js 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/styles/elementUI.scss 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/3dLayer.js 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/dialog.js 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/marks.js 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/sector.js 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/util.js 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/historymode/HistoryMode.vue 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/index.js
@@ -1,7 +1,7 @@
import axios from 'axios';
import { ElMessage } from 'element-plus';
const debug = true;
const debug = false;
// let ip1 = 'http://114.215.109.124:8805/';
let ip1 = 'http://47.100.191.150:9029/';
src/components.d.ts
@@ -16,6 +16,7 @@
    DataSummary: typeof import('./components/monitor/DataSummary.vue')['default']
    DataTable: typeof import('./components/monitor/DataTable.vue')['default']
    ElButton: typeof import('element-plus/es')['ElButton']
    ElCascader: typeof import('element-plus/es')['ElCascader']
    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
    ElCol: typeof import('element-plus/es')['ElCol']
@@ -51,11 +52,13 @@
    MapLocation: typeof import('./components/map/MapLocation.vue')['default']
    MapScene: typeof import('./components/map/MapScene.vue')['default']
    MapToolbox: typeof import('./components/map/MapToolbox.vue')['default']
    MessageBox: typeof import('./components/MessageBox.vue')['default']
    MissionCreate: typeof import('./components/mission/MissionCreate.vue')['default']
    MissionImport: typeof import('./components/mission/MissionImport.vue')['default']
    MissionManage: typeof import('./components/mission/MissionManage.vue')['default']
    OptionDevice: typeof import('./components/search/OptionDevice.vue')['default']
    OptionLocation: typeof import('./components/search/OptionLocation.vue')['default']
    OptionLocation2: typeof import('./components/search/OptionLocation2.vue')['default']
    OptionMission: typeof import('./components/search/OptionMission.vue')['default']
    OptionTime: typeof import('./components/search/OptionTime.vue')['default']
    OptionType: typeof import('./components/search/OptionType.vue')['default']
src/components/CardDialog.vue
@@ -4,7 +4,9 @@
    @opened="handleChange(true)"
    @closed="handleChange(false)"
    :show-close="false"
    :destroy-on-close="true"
    align-center
    :width="width"
  >
    <template #header="{ close, titleId, titleClass }">
      <BaseCard direction="top-left" borderless="t">
@@ -27,6 +29,9 @@
      <template #content>
        <slot></slot>
      </template>
      <template #footer>
        <slot name="footer"></slot>
      </template>
    </BaseCard>
  </el-dialog>
</template>
@@ -34,7 +39,11 @@
export default {
  props: {
    title: String,
    modelValue: Boolean
    modelValue: Boolean,
    width: {
      type: [String, Number],
      default: '50%'
    }
  },
  emits: ['update:modelValue'],
  methods: {
src/components/MessageBox.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
<template>
  <CardDialog
    :title="title"
    :model-value="modelValue"
    :width="400"
    @handleChange="handleChange"
  >
    <el-row justify="center">
      <div class="m-v-16">{{ msg }}</div>
    </el-row>
    <template #footer>
      <el-row justify="end">
        <el-button type="primary" class="el-button-custom" @click="cancel">
          {{ cancelText }}
        </el-button>
        <el-button
          type="danger"
          class="el-button-custom-light"
          @click="confirm"
        >
          {{ confirmText }}
        </el-button>
      </el-row>
    </template>
  </CardDialog>
</template>
<script>
export default {
  props: {
    title: String,
    msg: String,
    modelValue: Boolean,
    confirmText: {
      type: String,
      default: '确认'
    },
    cancelText: {
      type: String,
      default: '取消'
    },
    onConfirm: Function,
    onCancel: Function
  },
  data() {},
  emits: ['update:modelValue'],
  methods: {
    handleChange(value) {
      this.$emit('update:modelValue', value);
    },
    confirm() {
      if (this.onConfirm) {
        this.onConfirm();
      }
      this.$emit('update:modelValue', false);
    },
    cancel() {
      if (this.onCancel) {
        this.onCancel();
      }
      this.$emit('update:modelValue', false);
    }
  }
};
</script>
src/components/chart/ProgressLineChart.vue
@@ -67,13 +67,6 @@
    },
    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 = {
@@ -92,7 +85,21 @@
          ]
        };
      }
      // è®¡ç®—超出单页数据量的长度
      let len = this.allXAxis.length - this.pageSize;
      len = len < 0 ? 0 : len;
      // å®šä½ç‚¹åº”该展示在趋势图中间,因此定位百分比往前偏移当前_size的一半
      let i = nV - parseInt(this.pageSize / 2);
      // ç¡®ä¿ç´¢å¼•不会超出范围
      i = i < 0 ? 0 : i;
      i = i > len ? len : i;
      // èŽ·å–ç´¢å¼•å¯¹åº”çš„è¿›åº¦ç™¾åˆ†æ¯”
      const _progress = (i / len) * 100;
      if (this.progress != _progress) {
        this.progress = _progress;
      } else {
      this.changeChartRange();
      }
    }
  },
  methods: {
@@ -143,9 +150,7 @@
    },
    getShowSeries(sIndex, eIndex) {
      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);
src/components/core/CoreMenu.vue
@@ -56,12 +56,12 @@
        {
          name: '走航监测',
          path: 'rmode'
        },
        {
          name: '污染溯源2',
          path: 'hmode2'
        }
        // {
        //   name: '污染溯源2',
        //   path: 'hmode2'
        // }
        // {
        //   name: '网格化监测',
        //   path: 'gridmonitor'
        // }
src/components/mission/MIssionCreate.vue
@@ -15,6 +15,15 @@
      label-position="right"
      label-width="100px"
    >
      <el-form-item label="区县" prop="location">
        <OptionLocation2
          :level="3"
          :initValue="false"
          :checkStrictly="false"
          :allOption="false"
          v-model="formObj.location"
        ></OptionLocation2>
      </el-form-item>
      <el-form-item label="任务编号" prop="missionCode">
        <el-input
          size="small"
@@ -24,14 +33,18 @@
        />
      </el-form-item>
      <OptionType v-model="formObj.deviceType"></OptionType>
      <el-form-item label="设备编号" prop="acountname">
      <!-- <el-form-item label="设备编号" prop="acountname">
        <el-input
          size="small"
          clearable
          v-model="formObj.deviceCode"
          placeholder="设备编号"
        />
      </el-form-item>
      </el-form-item> -->
      <OptionDevice
        :type="formObj.deviceType"
        v-model="formObj.deviceCode"
      ></OptionDevice>
      <OptionTime v-model="formObj.timeArray"></OptionTime>
      <el-form-item>
        <el-button
@@ -51,34 +64,37 @@
import missionApi from '@/api/missionApi';
import { useFormConfirm } from '@/composables/formConfirm';
import { useFetchData } from '@/composables/fetchData';
import { useMissionStore } from '@/stores/mission';
const missionStore = useMissionStore();
const dialogVisible = ref(false);
const { loading, fetchData } = useFetchData();
const baseRules = reactive({
  _usertype: [
const rules = reactive({
  location: [
    {
      required: true,
      message: '用户类型不能为空',
      message: '区县不能为空',
      trigger: 'change'
    }
  ],
  _locations: [
  missionCode: [
    {
      required: true,
      message: '行政区划不能为空',
      trigger: 'change'
      message: '任务编号不能为空',
      trigger: 'blur'
    }
  ],
  _scenetype: [
  timeArray: [
    {
      required: true,
      message: '场景类型不能为空',
      message: '时间不能为空',
      trigger: 'change'
    }
  ]
});
const param = computed(() => {
  return {
    districtName: formObj.value.location.dName,
    missionCode: formObj.value.missionCode,
    deviceType: formObj.value.deviceType,
    deviceCode: formObj.value.deviceCode,
@@ -91,6 +107,7 @@
  fetchData((page, pageSize) => {
    return missionApi.putNewMission(param.value).then((res) => {
      dialogVisible.value = false;
      missionStore.fetchMission();
    });
  });
}
src/components/mission/MissionManage.vue
@@ -7,34 +7,16 @@
  >
    ä»»åŠ¡ç®¡ç†
  </el-button>
  <el-dialog v-model="dialogVisible" :show-close="false" align-center>
    <template #header="{ close, titleId, titleClass }">
      <BaseCard direction="top-left" borderless="t">
        <template #content>
          <el-row justify="space-between" align="middle">
            <el-row align="middle">
              <font-awesome-icon icon="fa fa-list" class="m-r-4" />
              <span :id="titleId" :class="titleClass">走航任务管理</span>
            </el-row>
            <font-awesome-icon
              icon="fa fa-times"
              class="cursor-p m-r-4"
              @click="close"
            />
          </el-row>
        </template>
      </BaseCard>
    </template>
    <BaseCard size="medium">
      <template #content>
  <CardDialog v-model="dialogVisible" title="走航任务管理">
        <el-row class="mission-table">
          <el-col :span="20">
            <el-table
              :data="missionList"
          :data="missionStore.missionList"
              table-layout="fixed"
              size="small"
              :show-overflow-tooltip="true"
              border
          height="64vh"
              row-class-name="t-row"
              cell-class-name="t-cell"
              header-row-class-name="t-header-row"
@@ -46,11 +28,7 @@
                align="center"
                width="50"
              />
              <el-table-column
                prop="missionCode"
                label="任务编号"
                align="center"
              />
          <el-table-column prop="missionCode" label="任务编号" align="center" />
              <el-table-column
                prop="startTime"
                label="开始时间"
@@ -95,28 +73,44 @@
            </div>
          </el-col>
        </el-row>
      </template>
    </BaseCard>
  </el-dialog>
  </CardDialog>
  <MessageBox
    v-model="msgBoxVisible"
    :on-confirm="onConfirm"
    title="删除走航任务"
    msg="确认是否删除该走航任务"
    confirmText="删除"
  ></MessageBox>
</template>
<script>
import moment from 'moment';
import { mapState } from 'pinia';
import { mapStores } from 'pinia';
import { useMissionStore } from '@/stores/mission';
import { useFetchData } from '@/composables/fetchData';
export default {
  setup() {
    const { loading, fetchData } = useFetchData();
    return { loading, fetchData };
  },
  props: {},
  data() {
    return {
      dialogVisible: false
      dialogVisible: false,
      msgBoxVisible: false,
      onConfirm: undefined
    };
  },
  computed: {
    ...mapState(useMissionStore, ['missionList'])
    ...mapStores(useMissionStore)
  },
  methods: {
    createMission() {},
    deleteMission(row) {},
    deleteMission(row) {
      this.onConfirm = () => {
        this.missionStore.deleteMission(row.missionCode);
      };
      this.msgBoxVisible = true;
    },
    timeFormatter(row, col, cellValue, index) {
      return moment(cellValue).format('YYYY-MM-DD HH:mm:ss');
    }
src/components/search/OptionLocation2.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,165 @@
<template>
  <!-- <el-form-item :label="placeholder" :prop="prop"> -->
  <el-cascader
    :model-value="formatedValue"
    @change="handleChange"
    :options="locations"
    :placeholder="placeholder"
    :props="optionProps"
    size="small"
    :style="'width: ' + width + 'px'"
  />
  <!-- </el-form-item> -->
</template>
<script>
import { enumLocation } from '@/constant/location';
export default {
  props: {
    // æ˜¯å¦åœ¨é¦–选项处添加“全部”选项
    allOption: {
      type: Boolean,
      default: true
    },
    // æŸ¥è¯¢çš„行政级别,取值1,2,3,4, 5, 6
    level: {
      type: Number,
      default: 4
    },
    // ç»“果返回
    modelValue: Object,
    // æ˜¯å¦é»˜è®¤è¿”回初始选项
    initValue: {
      type: Boolean,
      default: true
    },
    // èƒ½å¦é€‰æ‹©ä»»æ„ä¸€çº§é€‰é¡¹
    checkStrictly: {
      type: Boolean,
      default: true
    },
    prop: {
      type: String,
      default: '_locations'
    },
    width: {
      type: Number,
      default: 220
    }
  },
  emits: ['update:modelValue'],
  data() {
    return {
      locations: enumLocation(this.allOption, this.level),
      optionProps: {
        checkStrictly: this.checkStrictly
      }
    };
  },
  computed: {
    placeholder() {
      const list = '省/市/区/镇/集/物'.split('/');
      const p = [];
      for (let i = 0; i < this.level; i++) {
        p.push(list[i]);
      }
      return p.join('/');
    },
    formatedValue() {
      return this.optionFormatReverse(this.modelValue);
    }
  },
  methods: {
    handleChange(value) {
      this.$emit('update:modelValue', this.optionFormat(value));
    },
    /**
     * åœ°åŒºé€‰é¡¹ç»“果格式化
     */
    optionFormat(val) {
      const res = {
        pCode: null,
        pName: null,
        cCode: null,
        cName: null,
        dCode: null,
        dName: null,
        tCode: null,
        tName: null,
        aCode: null,
        aName: null,
        mCode: null,
        mName: null
      };
      if (val.length > 0) {
        res.pCode = val[0][0];
        res.pName = val[0][1];
      }
      if (val.length > 1) {
        res.cCode = val[1][0];
        res.cName = val[1][1];
      }
      if (val.length > 2) {
        res.dCode = val[2][0];
        res.dName = val[2][1];
      }
      if (val.length > 3) {
        res.tCode = val[3][0];
        res.tName = val[3][1];
      }
      if (val.length > 4) {
        res.aCode = val[4][0];
        res.aName = val[4][1];
      }
      if (val.length > 5) {
        res.mCode = val[5][0];
        res.mName = val[5][1];
      }
      return res;
    },
    optionFormatReverse(val) {
      const res = [];
      if (val) {
        if (val.pName) {
          res.push([val.pCode, val.pName]);
        }
        if (val.cName) {
          res.push([val.cCode, val.cName]);
        }
        if (val.dName) {
          res.push([val.dCode, val.dName]);
        }
        if (val.tName) {
          res.push([val.tCode, val.tName]);
        }
        if (val.aName) {
          res.push([val.aCode, val.aName]);
        }
        if (val.mName) {
          res.push([val.mCode, val.mName]);
        }
      }
      return res;
    }
  },
  mounted() {
    if (this.initValue) {
      if (this.checkStrictly) {
        this.handleChange([this.locations[0].value]);
      } else {
        const f = (location) => {
          if (location.children && location.children.length > 0) {
            const r = f(location.children[0]);
            r.unshift(location.value);
            return r;
          } else {
            return [location.value];
          }
        };
        this.handleChange(f(this.locations[0]));
      }
    }
  }
};
</script>
src/components/search/OptionMission.vue
@@ -8,7 +8,7 @@
      class="w-150"
    >
      <el-option
        v-for="(s, i) in missionList"
        v-for="(s, i) in missionStore.missionList"
        :key="i"
        :label="s.missionCode"
        :value="i"
@@ -35,7 +35,6 @@
  emits: ['update:modelValue', 'change'],
  data() {
    return {
      missionList: [],
      index: undefined
    };
  },
@@ -44,21 +43,22 @@
  },
  methods: {
    fetchMission() {
      this.fetchData((page, pageSize) => {
        return missionApi
          .fethchMission({ type: this.type, page, pageSize })
          .then((res) => {
            this.missionList = res.data;
            this.missionStore.missionList = res.data;
            // if (this.missionList.length > 0) {
            //   this.handleChange(0);
            // }
            return res.head;
          });
      });
      // this.fetchData((page, pageSize) => {
      //   return missionApi
      //     .fethchMission({ type: this.type, page, pageSize })
      //     .then((res) => {
      //       this.missionList = res.data;
      //       this.missionStore.missionList = res.data;
      //       // if (this.missionList.length > 0) {
      //       //   this.handleChange(0);
      //       // }
      //       return res.head;
      //     });
      // });
      this.missionStore.fetchMission(this.type);
    },
    handleChange(value) {
      this.$emit('update:modelValue', this.missionList[value]);
      this.$emit('update:modelValue', this.missionStore.missionList[value]);
      // this.$emit('change', this.missionList[value]);
    }
  },
src/components/search/OptionTime.vue
@@ -1,5 +1,5 @@
<template>
  <el-form-item label="时间">
  <el-form-item label="时间" prop="timeArray">
    <el-date-picker
      :model-value="modelValue"
      @update:model-value="handleChange"
src/composables/fetchData.js
@@ -28,7 +28,7 @@
  // æ•°æ®èŽ·å–
  function fetchData(fetch) {
    loadStatus.value = 1;
    fetch(page.value, pageSize.value)
    return fetch(page.value, pageSize.value)
      .then((pageInfo) => {
        if (pageInfo) {
          page.value = pageInfo.page ? pageInfo.page : 1;
src/composables/messageBox.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
import { ElMessageBox, ElNotification, ElMessage } from 'element-plus';
function useMessageBoxTip({
  confirmMsg,
  confirmTitle = '提交',
  doneMsg = confirmTitle,
  onConfirm,
}) {
  ElMessageBox.confirm(confirmMsg, `${confirmTitle}确认`, {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(async () => {
      let msg = `å·²${doneMsg}`
      if (typeof onConfirm === 'function') {
        const str = await onConfirm();
        if (typeof str === 'string' && str != '') {
          msg = `å·²${doneMsg}, ${str}`
        }
      }
      ElNotification({
        title: `${confirmTitle}成功`,
        message: msg,
        type: 'success',
        // offset: 170,
        position: 'bottom-left',
      });
    })
    .catch((err) => {
      let errStr = `${confirmTitle}取消`;
      if (err != 'cancel') {
        errStr = `${confirmTitle}失败, ${err}`;
      }
      ElMessage({
        message: errStr,
        type: 'warning',
      });
    });
}
function useMessageBox({ confirmMsg, confirmTitle, onConfirm }) {
  ElMessageBox.confirm(confirmMsg, confirmTitle, {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(async () => {
      if (typeof onConfirm === 'function') {
        onConfirm();
      }
    })
    .catch(() => {});
}
export { useMessageBoxTip, useMessageBox };
src/constant/location.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,257 @@
/**
 * èŽ·å–è¡Œæ”¿åŒºåˆ’
 * @param {Boolean} allOption æ˜¯å¦åœ¨å¤´éƒ¨æ·»åŠ â€œå…¨éƒ¨â€é€‰é¡¹
 * @param {Number} level èŽ·å–çš„åˆ†ç±»æ·±åº¦ï¼ŒèŒƒå›´ 1 - 4
 * @returns
 */
function enumLocation(allOption = true, level = 4) {
  const l = _enumLocation();
  if (!allOption) {
    l.shift();
  }
  _deleteByLevel(l, level, 1);
  return l;
}
function _enumLocation() {
  return [
    {
      label: '全市',
      value: [null, '全市']
    },
    {
      label: '上海市',
      value: ['31', '上海市'],
      children: [
        {
          label: '上海市',
          value: ['3100', '上海市'],
          children: [
            {
              label: '金山区',
              value: ['310116', '金山区'],
              children: [
                { label: '张堰镇', value: ['310116103', '张堰镇'] },
                { label: '亭林镇', value: ['310116104', '亭林镇'] },
                { label: '吕巷镇', value: ['310116105', '吕巷镇'] },
                { label: '廊下镇', value: ['310116107', '廊下镇'] },
                { label: '高新区', value: ['310116503', '高新区'] },
                { label: '金山卫镇', value: ['310116109', '金山卫镇'] },
                { label: '漕泾镇', value: ['310116112', '漕泾镇'] },
                {
                  label: '山阳镇',
                  value: ['310116113', '山阳镇'],
                  children: [
                    {
                      label: '万达广场',
                      value: ['31011611301', '万达广场']
                    }
                  ]
                },
                { label: '石化街道', value: ['310116001', '石化街道'] },
                { label: '朱泾镇', value: ['310116101', '朱泾镇'] },
                { label: '枫泾镇', value: ['310116102', '枫泾镇'] },
                { label: '碳谷绿湾', value: ['9000', '碳谷绿湾'] }
              ]
            },
            {
              label: '徐汇区',
              value: ['310104', '徐汇区'],
              children: [
                {
                  label: '漕河泾新兴技术开发区',
                  value: ['310104501', '漕河泾新兴技术开发区']
                },
                { label: '湖南路街道', value: ['310104004', '湖南路街道'] },
                { label: '天平路街道', value: ['310104003', '天平路街道'] },
                { label: '虹梅路街道', value: ['310104012', '虹梅路街道'] },
                { label: '枫林路街道', value: ['310104008', '枫林路街道'] },
                { label: '斜土路街道', value: ['310104007', '斜土路街道'] },
                { label: '长桥街道', value: ['310104010', '长桥街道'] },
                {
                  label: '田林街道',
                  value: ['310104011', '田林街道'],
                  children: [
                    {
                      label: '田尚坊',
                      value: ['31010401101', '田尚坊']
                    }
                  ]
                },
                { label: '康健新村街道', value: ['310104013', '康健新村街道'] },
                {
                  label: '徐家汇街道',
                  value: ['310104014', '徐家汇街道'],
                  children: [
                    {
                      label: '天钥桥',
                      value: ['31010401401', '天钥桥']
                    }
                  ]
                },
                { label: '凌云路街道', value: ['310104015', '凌云路街道'] },
                { label: '龙华街道', value: ['310104016', '龙华街道'] },
                { label: '漕河泾街道', value: ['310104017', '漕河泾街道'] },
                { label: '华泾镇', value: ['310104103', '华泾镇'] }
              ]
            },
            {
              label: '静安区',
              value: ['310106', '静安区'],
              children: [
                {
                  label: '大宁路街道',
                  value: ['310106019', '大宁路街道'],
                  children: [
                    {
                      label: '久光中心',
                      value: ['31010601901', '久光中心']
                    }
                  ]
                },
                { label: '彭浦新村街道', value: ['310106020', '彭浦新村街道'] },
                { label: '临汾路街道', value: ['310106021', '临汾路街道'] },
                { label: '芷江西路街道', value: ['310106022', '芷江西路街道'] },
                {
                  label: '彭浦镇',
                  value: ['310106101', '彭浦镇'],
                  children: [
                    {
                      label: '大融城',
                      value: ['31010610101', '大融城']
                    }
                  ]
                },
                { label: '江宁路街道', value: ['310106006', '江宁路街道'] },
                { label: '石门二路街道', value: ['310106011', '石门二路街道'] },
                {
                  label: '南京西路街道',
                  value: ['310106012', '南京西路街道'],
                  children: [
                    {
                      label: 'X88',
                      value: ['31010601201', 'X88']
                    }
                  ]
                },
                { label: '静安寺街道', value: ['310106013', '静安寺街道'] },
                {
                  label: '曹家渡街道',
                  value: ['310106014', '曹家渡街道'],
                  children: [
                    {
                      label: '889',
                      value: ['31010601401', '889']
                    }
                  ]
                },
                { label: '天目西路街道', value: ['310106015', '天目西路街道'] },
                {
                  label: '北站街道',
                  value: ['310106016', '北站街道'],
                  children: [
                    {
                      label: '大悦城',
                      value: ['31010601601', '大悦城']
                    }
                  ]
                },
                { label: '宝山路街道', value: ['310106017', '宝山路街道'] },
                { label: '共和新路街道', value: ['310106018', '共和新路街道'] }
              ]
            },
            {
              label: '普陀区',
              value: ['310107', '普陀区'],
              children: [
                { label: '曹杨新村街道', value: ['310107005', '曹杨新村街道'] },
                { label: '万里街道', value: ['310107021', '万里街道'] },
                { label: '真如镇街道', value: ['310107022', '真如镇街道'] },
                { label: '长征镇', value: ['310107102', '长征镇'] },
                { label: '桃浦镇', value: ['310107103', '桃浦镇'] },
                { label: '石泉路街道', value: ['310107017', '石泉路街道'] },
                { label: '甘泉路街道', value: ['310107016', '甘泉路街道'] },
                { label: '长寿路街道', value: ['310107015', '长寿路街道'] },
                { label: '长风新村街道', value: ['310107014', '长风新村街道'] },
                { label: '宜川路街道', value: ['310107020', '宜川路街道'] }
              ]
            },
            {
              label: '闵行区',
              value: ['310112', '闵行区'],
              children: [
                { label: '江川路街道', value: ['310112001', '江川路街道'] },
                { label: '古美街道', value: ['310112006', '古美街道'] },
                { label: '新虹街道', value: ['310112008', '新虹街道'] },
                { label: '浦锦街道', value: ['310112009', '浦锦街道'] },
                { label: '莘庄镇', value: ['310112101', '莘庄镇'] },
                { label: '七宝镇', value: ['310112102', '七宝镇'] },
                { label: '颛桥镇', value: ['310112103', '颛桥镇'] },
                { label: '华漕镇', value: ['310112106', '华漕镇'] },
                { label: '虹桥镇', value: ['310112107', '虹桥镇'] },
                { label: '梅陇镇', value: ['310112108', '梅陇镇'] },
                { label: '吴泾镇', value: ['310112110', '吴泾镇'] },
                { label: '马桥镇', value: ['310112112', '马桥镇'] },
                { label: '浦江镇', value: ['310112114', '浦江镇'] },
                { label: '莘庄工业区', value: ['310112501', '莘庄工业区'] }
              ]
            },
            {
              label: '长宁区',
              value: ['310105', '长宁区'],
              children: [
                { label: '华阳路街道', value: ['310105001', '华阳路街道'] },
                { label: '江苏路街道', value: ['310105002', '江苏路街道'] },
                { label: '新华路街道', value: ['310105004', '新华路街道'] },
                { label: '周家桥街道', value: ['310105005', '周家桥街道'] },
                { label: '天山路街道', value: ['310105006', '天山路街道'] },
                { label: '仙霞新村街道', value: ['310105008', '仙霞新村街道'] },
                { label: '虹桥街道', value: ['310105009', '虹桥街道'] },
                { label: '程家桥街道', value: ['310105010', '程家桥街道'] },
                { label: '北新泾街道', value: ['310105011', '北新泾街道'] },
                { label: '新泾镇', value: ['310105102', '新泾镇'] }
              ]
            },
            {
              label: '宝山区',
              value: ['310113', '宝山区'],
              children: []
            },
            {
              label: '嘉定区',
              value: ['310114', '嘉定区'],
              children: []
            },
            {
              label: '浦东新区',
              value: ['310115', '浦东新区'],
              children: []
            }
          ]
        }
      ]
    }
  ];
}
// æŒ‰ç…§éœ€æ±‚的定位精度返回对应数据
function _deleteByLevel(locations, level, step) {
  if (step == level) {
    locations.forEach((l) => {
      if (l.children) {
        l.children = undefined;
      }
    });
    return;
  } else {
    step++;
    locations.forEach((l) => {
      if (l.children) {
        _deleteByLevel(l.children, level, step);
      }
    });
  }
}
export { enumLocation };
src/main.js
@@ -3,6 +3,10 @@
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
import 'element-plus/theme-chalk/src/overlay.scss';
import 'element-plus/theme-chalk/src/message.scss';
import 'element-plus/theme-chalk/src/message-box.scss';
import 'element-plus/theme-chalk/src/notification.scss';
import App from './App.vue';
import router from './router';
src/stores/mission.js
@@ -1,9 +1,42 @@
import { ref } from 'vue';
import { defineStore } from 'pinia';
import missionApi from '@/api/missionApi';
import { useFetchData } from '@/composables/fetchData';
// èµ°èˆªä»»åŠ¡
export const useMissionStore = defineStore('mission', () => {
  const missionList = ref([]);
  const { loading, fetchData } = useFetchData();
  return { missionList };
  function fetchMission(type) {
    return fetchData((page, pageSize) => {
      return missionApi
        .fethchMission({ type: type, page, pageSize })
        .then((res) => {
          missionList.value = res.data;
          return res;
        });
    });
  }
  function deleteMission(missionCode) {
    return fetchData(() => {
      return missionApi.deleteMission(missionCode).then((res) => {
        let index = -1;
        for (let i = 0; i < missionList.value.length; i++) {
          const e = missionList.value[i];
          if (e.missionCode == missionCode) {
            index = i;
            break;
          }
        }
        if (index != -1) {
          missionList.value.splice(index, 1);
        }
        return res;
      });
    });
  }
  return { missionList, loading, fetchMission, deleteMission };
});
src/styles/elementUI.scss
@@ -23,6 +23,15 @@
  --el-button-active-border-color: transparent;
}
.el-button-custom-light {
  --el-button-bg-color: var(--font-color);
  --el-button-text-color: var(--bg-color);
  --el-button-hover-text-color: var(--bg-color);
  --el-button-hover-bg-color: var(--font-color);
  --el-button-border-color: var(--bg-color);
  --el-button-active-border-color: transparent;
}
.el-button-custom:focus-visible {
  outline: 0px solid var(--el-button-outline-color);
}
src/utils/map/3dLayer.js
@@ -30,6 +30,7 @@
  const fDatas = _factorDatas;
  const factor = _factor;
  drawMesh(fDatas, factor);
  // console.log(map.getZoom());
}
var _maxHeight = 1000,
@@ -90,15 +91,12 @@
/**
 * ç»˜å›¾
 */
function drawMesh(fDatas, factor, center, merge) {
function drawMesh(fDatas, factor, 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();
@@ -198,23 +196,8 @@
      _maxH = maxH;
    }
    // 3.确定定位坐标点
    var center;
    if (setCenter && lnglats_GD.length > 0) {
      var p = lnglats_GD[0];
      for (let i = 0; i < lnglats_GD.length; i++) {
        const e = lnglats_GD[i];
        if (e[0] != 0) {
          p = e;
          break;
        }
      }
      // eslint-disable-next-line no-undef
      center = new AMap.LngLat(...p);
    }
    // 5.绘制3D图形
    this.drawMesh(fDatas, factor, center, merge);
    this.drawMesh(fDatas, factor, merge);
    // ç¼©æ”¾åœ°å›¾åˆ°åˆé€‚的视野级别
    // map.setFitView()
src/utils/map/dialog.js
@@ -33,7 +33,8 @@
      isCustom: true, //使用自定义窗体
      content: this.createWindowContent(m),
      // eslint-disable-next-line no-undef
      offset: new AMap.Pixel(16, -45)
      offset: new AMap.Pixel(16, -45),
      autoMove: false
    });
    return m.window;
  },
src/utils/map/marks.js
@@ -3,8 +3,6 @@
 */
import { map } from './index_old';
import sector from './sector';
import { DialogUtil } from './dialog';
import { useToolboxStore } from '@/stores/toolbox';
const toolboxStore = useToolboxStore();
@@ -21,10 +19,7 @@
    if (!toolboxStore.dataMarkerStatus) {
      return;
    }
    if (_massMarks) {
      map.remove(_massMarks);
      _massMarks = undefined;
    }
    this.clearMassMarks();
    const lnglats = fDatas.lnglats_GD;
    var data = [];
    for (let i = 0; i < lnglats.length; i++) {
@@ -77,6 +72,12 @@
    _massMarks = massMarks;
    map.add(massMarks);
  },
  clearMassMarks() {
    if (_massMarks) {
      map.remove(_massMarks);
      _massMarks = undefined;
    }
  },
  createLabelMarks(img, dataList) {
    // eslint-disable-next-line no-undef
src/utils/map/sector.js
@@ -1,6 +1,7 @@
import { map, object3Dlayer } from './index_old';
import calculate from './calculate';
import imgLocation from '@/assets/mipmap/location.png';
import { FactorDatas } from '@/model/FactorDatas';
var _defaultDeg = 30,
  _sector = undefined,
@@ -76,7 +77,7 @@
          content: content,
          offset: [-35, 0],
          position: 'BM',
          minZoom: 15
          minZoom: 10
        }
      }
    ],
@@ -107,6 +108,9 @@
}
function drawSectorMesh(sDeg, eDeg, lnglat, distance, distance2) {
  if (distance == 0 || distance2 == 0) {
    return;
  }
  // eslint-disable-next-line no-undef
  var sector = new AMap.Object3D.Mesh();
  sector.transparent = true;
@@ -124,16 +128,16 @@
    var angle1 = sDeg + unitDeg * i;
    var angle2 = sDeg + unitDeg * (i + 1);
    var l1 = calculate.getLatLon(lnglat, distance, angle1);
    var l2 = calculate.getLatLon(lnglat, distance, angle2);
    var l3 = calculate.getLatLon(lnglat, distance2, angle1);
    var l4 = calculate.getLatLon(lnglat, distance2, angle2);
    var p1 = calculate.getLatLon(lnglat, distance, angle1);
    var p2 = calculate.getLatLon(lnglat, distance, angle2);
    var p3 = calculate.getLatLon(lnglat, distance2, angle1);
    var p4 = calculate.getLatLon(lnglat, distance2, angle2);
    var coors = calculate.lngLatToGeodeticCoord([l1, l2, l3, l4]);
    l1 = coors[0];
    l2 = coors[1];
    l3 = coors[2];
    l4 = coors[3];
    var coors = calculate.lngLatToGeodeticCoord([p1, p2, p3, p4]);
    const l1 = coors[0];
    const l2 = coors[1];
    const l3 = coors[2];
    const l4 = coors[3];
    // å†…测扇形
    geometry.vertices.push(p0.x, p0.y, 0);
@@ -211,6 +215,9 @@
}
export default {
  /**
   * æ¸…空扇形和文本标记
   */
  clearSector() {
    var list = [];
    for (const iterator of _sectorViews) {
@@ -225,11 +232,20 @@
    }
    this.clearSectorMesh();
  },
  /**
   * åªæ¸…空扇形
   */
  clearSectorMesh() {
    if (_sector) {
      object3Dlayer.remove(_sector);
    }
  },
  /**
   * ç»˜åˆ¶æ‰‡å½¢
   * @param {FactorDatas} fDatas
   * @param {number} i
   * @returns æ•°æ®åæ ‡ç‚¹å’Œæ‰‡å½¢æœ€å¤§åŠå¾„
   */
  drawSector(fDatas, i) {
    if (_sector != undefined) {
      this.clearSector();
@@ -238,8 +254,14 @@
      sectorParams(fDatas, i);
    drawSectorMesh(sDeg, eDeg, lnglat, distance, distance2);
    drawElasticMarker(list, list2, distance, distance2);
    return { p: lnglat, r: distance };
  },
  /**
   * ç»˜åˆ¶è½¨è¿¹åŠ¨ç”»ä¸­çš„æ‰‡å½¢
   * @param {FactorDatas} fDatas
   * @param {number} i
   */
  drawSectorAna(fDatas, i) {
    this.clearSectorMesh();
    const { sDeg, eDeg, lnglat, distance, distance2, list, list2 } =
src/utils/map/util.js
@@ -1,4 +1,65 @@
import { map, isDragging } from '@/utils/map/index_old';
import marks from '@/utils/map/marks';
/**
 * åæ ‡é›†åˆçš„æœ€è¥¿å—角和最东北角
 * @param {*} list
 *  list æ˜¯æŽ¥å£èŽ·å–çš„ç‚¹ çš„æ•°ç»„
 */
const getBound = (list) => {
  const offset = 0.005;
  let south = null;
  let west = null;
  let north = null;
  let east = null;
  for (let item of list) {
    // æŽ’除无效经纬度
    if (item[0] == 0 && item[1] == 0) {
      continue;
    }
    if ((west && item[0] < west) || !west) {
      west = item[0] - offset;
    }
    if ((south && item[1] < south) || !south) {
      south = item[1] - offset;
    }
    if ((east && item[0] > east) || !east) {
      east = item[0] + offset;
    }
    if ((north && item[1] > north) || !north) {
      north = item[1] + offset;
    }
  }
  if (!south || !west || !north || !east) {
    return { sw: null, ne: null };
  } else {
    return { sw: [west, south], ne: [east, north] };
  }
};
/**
 * æ ¹æ®ä¸­å¿ƒç‚¹å‡ºå‘的半径,得到合适的地图缩放系数
 * é«˜å¾·åœ°å›¾ç¼©æ”¾ç³»æ•°æ¯å‡å°‘1,则地图展示的实际距离放大1倍
 * é«˜å¾·åœ°å›¾ç¼©æ”¾ç³»æ•°èŒƒå›´[3, 18]
 * @param {*} d
 */
const distanceToZoom = (d) => {
  let baseDis = 250,
    z = 0;
  while (baseDis < d) {
    baseDis *= 2;
    z++;
  }
  // å¤šä½™çš„地图缩放系数
  const x = (baseDis - d) / (baseDis / 2);
  z -= x;
  z = z < 0 ? 0 : z;
  z = 18 - z;
  z = z < 3 ? 3 : z;
  return z;
};
export default {
  setCenter(lnglat) {
@@ -20,7 +81,29 @@
  removeViews(view) {
    map.remove(view);
  },
  setFitView() {
  clearMap() {
    marks.clearMassMarks();
    map.clearMap();
  },
  setFitView(views) {
    if (views) {
      map.setFitView(views);
    } else {
    map.setFitView();
  }
  },
  setFitSector({ p, r }) {
    this.setCenter(p);
    const z = distanceToZoom(r);
    map.setZoom(z);
  },
  setBound(lnglats_GD) {
    const { sw, ne } = getBound(lnglats_GD);
    if (!sw || !ne) {
      return;
    }
    // eslint-disable-next-line no-undef
    var mybounds = new AMap.Bounds(sw, ne); // sw, ne > [xxx,xxx], [xxx,xxx]
    map.setBounds(mybounds);
  }
};
src/views/historymode/HistoryMode.vue
@@ -50,6 +50,7 @@
import Layer from '@/utils/map/3dLayer';
import marks from '@/utils/map/marks';
import sector from '@/utils/map/sector';
import mapUtil from '@/utils/map/util';
import { DialogUtil } from '@/utils/map/dialog';
import monitorDataApi from '@/api/monitorDataApi';
import { useFetchData } from '@/composables/fetchData';
@@ -58,6 +59,7 @@
import { FactorDatas } from '@/model/FactorDatas';
import TrendAnalysis from './component/TrendAnalysis.vue';
import DataSheet from './component/DataSheet.vue';
import { ElMessageBox, ElNotification, ElMessage } from 'element-plus';
export default {
  components: { TrendAnalysis, DataSheet },
@@ -94,14 +96,31 @@
    }
  },
  methods: {
    // æ£€æŸ¥æ•°æ®ç»çº¬åº¦æ˜¯å¦åˆæ³•
    checkDataIsValid(index) {
      const lnglats_GD = this.factorDatas.lnglats_GD[index];
      const time = this.factorDatas.times[index];
      if (lnglats_GD[0] == 0 && lnglats_GD[1] == 0) {
        ElMessage({
          message: `${time}的数据经纬度无效`,
          type: 'warning'
        });
        return false;
      } else {
        this.locateIndex = index;
        return true;
      }
    },
    // ç›‘听折线图和表格的点击事件
    handelIndexChange(index) {
      this.locateIndex = index;
      if (this.checkDataIsValid(index)) {
      this.drawSector(index);
      }
    },
    draw() {
      // todo åˆ·æ–°å›¾ä¾‹
      // åˆ·æ–°å›¾ä¾‹
      const factor = this.factorDatas.factor[this.factorType];
      sector.clearSector();
      this.drawRoadMap(factor);
      this.drawMassMarks(factor);
    },
@@ -116,13 +135,18 @@
      marks.drawMassMarks(this.factorDatas, e, (index) => {
        // æŸ¥è¯¢èŒƒå›´å†…的监测站点
        // SceneUtil.searchByCoordinate(lnglat[0], lnglat[1], distance);
        if (this.checkDataIsValid(index)) {
        this.drawSector(index);
        this.locateIndex = index;
        }
      });
      // è°ƒæ•´åœ°å›¾è§†è§’
      mapUtil.setBound(this.factorDatas.lnglats_GD);
    },
    drawSector(index) {
      // 1. ç»˜åˆ¶æ–°æ‰‡å½¢åŒºåŸŸ
      sector.drawSector(this.factorDatas, index);
      const pr = sector.drawSector(this.factorDatas, index);
      // è°ƒæ•´è§†è§’居中显示
      mapUtil.setFitSector(pr);
      // 2. ç»˜åˆ¶å¯¹è¯æ¡†
      DialogUtil.openNewWindow(this.factorDatas, index, () => {
        // ç§»é™¤æ‰‡å½¢åŒºåŸŸ
@@ -181,6 +205,9 @@
  },
  mounted() {
    this.fetchRealTimeData();
  },
  unmounted() {
    mapUtil.clearMap();
  }
};
</script>