riku
2024-06-07 0a97c702074830791cb29a158098f05c8033c4b1
原型制作中
已修改12个文件
已添加18个文件
1577 ■■■■■ 文件已修改
package-lock.json 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/fysp/taskApi.js 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/index.js 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icon/scene_1.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/base.scss 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/inspection/TaskNode.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/inspection/TaskSummaryItem.vue 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/map/BaseMap.vue 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search/OptionLocation.vue 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search/OptionSceneType.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search/OptionTime.vue 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/enum/location.js 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/enum/scene.js 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/index.js 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/marks.js 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/time-util.js 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inspection/InspectionView.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inspection/JointEnforcement.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inspection/SelfInspection.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inspection/TaskTrack.vue 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/main/MonitorView.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/management/ManagementView.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/management/TaskStats.vue 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/management/TaskSummary.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/visualization/SubtaskVisual.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/visualization/SupervisionVisual.vue 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/visualization/VisualizationView.vue 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json
@@ -8,6 +8,7 @@
      "name": "fy-report",
      "version": "0.0.0",
      "dependencies": {
        "@amap/amap-jsapi-loader": "^1.0.1",
        "@element-plus/icons-vue": "^2.3.1",
        "@vueuse/core": "^10.10.0",
        "axios": "^1.7.2",
@@ -31,6 +32,11 @@
        "vite": "^5.2.8",
        "vitest": "^1.4.0"
      }
    },
    "node_modules/@amap/amap-jsapi-loader": {
      "version": "1.0.1",
      "resolved": "https://registry.npmmirror.com/@amap/amap-jsapi-loader/-/amap-jsapi-loader-1.0.1.tgz",
      "integrity": "sha512-nPyLKt7Ow/ThHLkSvn2etQlUzqxmTVgK7bIgwdBRTg2HK5668oN7xVxkaiRe3YZEzGzfV2XgH5Jmu2T73ljejw=="
    },
    "node_modules/@antfu/utils": {
      "version": "0.7.8",
@@ -4674,6 +4680,11 @@
    }
  },
  "dependencies": {
    "@amap/amap-jsapi-loader": {
      "version": "1.0.1",
      "resolved": "https://registry.npmmirror.com/@amap/amap-jsapi-loader/-/amap-jsapi-loader-1.0.1.tgz",
      "integrity": "sha512-nPyLKt7Ow/ThHLkSvn2etQlUzqxmTVgK7bIgwdBRTg2HK5668oN7xVxkaiRe3YZEzGzfV2XgH5Jmu2T73ljejw=="
    },
    "@antfu/utils": {
      "version": "0.7.8",
      "resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.8.tgz",
package.json
@@ -12,6 +12,7 @@
    "format": "prettier --write src/"
  },
  "dependencies": {
    "@amap/amap-jsapi-loader": "^1.0.1",
    "@element-plus/icons-vue": "^2.3.1",
    "@vueuse/core": "^10.10.0",
    "axios": "^1.7.2",
src/api/fysp/taskApi.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
import { $fysp } from '../index'
export default {
  /**
   * èŽ·å–é¡¶å±‚ä»»åŠ¡
   */
  getTopTask() {
    return $fysp.get('task/alltask/0').then((res) => res.data)
  },
  /**
   * æŸ¥è¯¢æ€»ä»»åŠ¡
   * @param {Object} area
   * @returns
   */
  fetchTopTasks(area) {
    return $fysp.post('task/find', area).then((res) => res.data)
  },
  /**
   * èŽ·å–å­ä»»åŠ¡ç»Ÿè®¡ä¿¡æ¯
   */
  fetchSubtaskSummary({ topTaskId = undefined, sceneTypeId = undefined }) {
    return $fysp
      .get('subtask/summary', {
        params: {
          topTaskId: topTaskId,
          sceneTypeId: sceneTypeId
        }
      })
      .then((res) => res.data)
  },
  /**
   * èŽ·å–å­ä»»åŠ¡ç»Ÿè®¡ä¿¡æ¯
   */
  fetchSubtaskSummaryArea(area) {
    return $fysp.post('subtask/summary/area', area).then((res) => res.data)
  },
  /**
   * èŽ·å–å­ä»»åŠ¡é—®é¢˜è¯¦æƒ…
   */
  getProBySubtask(id) {
    return $fysp
      .get('problemlist/subtask', {
        params: {
          stGuid: id
        }
      })
      .then((res) => res.data)
  }
}
src/api/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
import axios from 'axios'
import { ElMessage } from 'element-plus'
const debug = true
let ip1 = 'http://47.100.191.150:9005/'
let ip1_file = 'http://47.100.191.150:9005/'
// let ip1 = 'https://fyami.com.cn:447/';
// let ip1_file = 'https://fyami.com.cn:447/';
let ip2 = 'https://fyami.com.cn/'
let ip2_file = 'https://fyami.com.cn/'
if (debug) {
  ip1 = 'http://192.168.0.138:8082/'
  // ip1_file = 'http://47.100.191.150:9005/';
  // ip2 = 'http://192.168.0.138:8080/';
  // ip2_file = 'https://fyami.com.cn/';
}
//飞羽监管
const $fysp = axios.create({
  baseURL: ip1,
  timeout: 20000
})
$fysp.imgUrl = `${ip1_file}images/`
$fysp.downloadUrl = `${ip1_file}files/`
//飞羽环境
const $fytz = axios.create({
  baseURL: ip2,
  timeout: 20000
})
$fytz.imgUrl = `${ip2_file}images/`
//添加拦截器
;[$fysp, $fytz].forEach((i) => {
  // æ·»åŠ è¯·æ±‚æ‹¦æˆªå™¨
  i.interceptors.request.use(
    function (config) {
      // åœ¨å‘送请求之前做些什么
      console.log('==>请求开始')
      console.log(`${config.baseURL}${config.url}`)
      if (config.data) {
        console.log('==>请求数据', config.data)
      }
      return config
    },
    function (error) {
      // å¯¹è¯·æ±‚错误做些什么
      console.log('==>请求开始')
      console.log(error)
      ElMessage({
        message: error,
        type: 'error'
      })
      return Promise.reject(error)
    }
  )
  // æ·»åŠ å“åº”æ‹¦æˆªå™¨
  i.interceptors.response.use(
    function (response) {
      // 2xx èŒƒå›´å†…的状态码都会触发该函数。
      // å¯¹å“åº”数据做点什么
      console.log(response)
      console.log('==>请求结束')
      if (response.status == 200) {
        if (response.data.success != undefined && response.data.success != null) {
          if (response.data.success == true) {
            return response
          } else {
            ElMessage({
              message: response.data.message,
              type: 'error'
            })
            return Promise.reject(response.data.message)
          }
        } else {
          return response
        }
      } else {
        return Promise.reject(response)
      }
    },
    function (error) {
      // è¶…出 2xx èŒƒå›´çš„状态码都会触发该函数。
      // å¯¹å“åº”错误做点什么
      console.log(error)
      console.log('==>请求结束')
      ElMessage({
        message: error,
        type: 'error'
      })
      return Promise.reject(error)
    }
  )
})
export { $fysp, $fytz }
src/assets/icon/scene_1.png
src/assets/styles/base.scss
@@ -115,3 +115,12 @@
  flex-direction: column;
  align-items: center;
}
/************************************** é¼ æ ‡äº‹ä»¶ **************************************/
.p-events-auto {
  pointer-events: auto;
}
.p-events-none {
  pointer-events: none;
}
src/components.d.ts
@@ -7,21 +7,37 @@
/* prettier-ignore */
declare module 'vue' {
  export interface GlobalComponents {
    BaseMap: typeof import('./components/map/BaseMap.vue')['default']
    copy: typeof import('./components/search/OptionLocation copy.vue')['default']
    CoreHeader: typeof import('./components/core/CoreHeader.vue')['default']
    ElButton: typeof import('element-plus/es')['ElButton']
    ElCard: typeof import('element-plus/es')['ElCard']
    ElCascader: typeof import('element-plus/es')['ElCascader']
    ElCol: typeof import('element-plus/es')['ElCol']
    ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
    ElFormItem: typeof import('element-plus/es')['ElFormItem']
    ElOption: typeof import('element-plus/es')['ElOption']
    ElProgress: typeof import('element-plus/es')['ElProgress']
    ElRow: typeof import('element-plus/es')['ElRow']
    ElScorllbar: typeof import('element-plus/es')['ElScorllbar']
    ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
    ElSelect: typeof import('element-plus/es')['ElSelect']
    ElStatistic: typeof import('element-plus/es')['ElStatistic']
    ElTag: typeof import('element-plus/es')['ElTag']
    ElText: typeof import('element-plus/es')['ElText']
    ElTimeline: typeof import('element-plus/es')['ElTimeline']
    ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
    FYOptionTime: typeof import('./components/search/FYOptionTime.vue')['default']
    OptionLocation: typeof import('./components/search/OptionLocation.vue')['default']
    OptionSceneType: typeof import('./components/search/OptionSceneType.vue')['default']
    OptionTime: typeof import('./components/search/OptionTime.vue')['default']
    RouterLink: typeof import('vue-router')['RouterLink']
    RouterView: typeof import('vue-router')['RouterView']
    SubtaskExamineItem: typeof import('./components/inspection/SubtaskExamineItem.vue')['default']
    SubtaskItem: typeof import('./components/inspection/SubtaskItem.vue')['default']
    TaskItem: typeof import('./components/inspection/TaskItem.vue')['default']
    TaskNode: typeof import('./components/inspection/TaskNode.vue')['default']
    TaskSummaryItem: typeof import('./components/inspection/TaskSummaryItem.vue')['default']
  }
}
src/components/inspection/TaskNode.vue
@@ -1,6 +1,9 @@
<template>
  <div>
    <el-row justify="center">{{ title }}</el-row>
    <el-row justify="start">
      <div>{{ title }}</div>
      <el-text type="danger" size="small">{{ warning }}</el-text>
    </el-row>
    <div class="border-r-small f-b">
      <div v-for="(item, i) in items" :key="title + i">
        <slot :item="item" :index="i"></slot>
@@ -16,6 +19,7 @@
export default {
  props: {
    title: String,
    warning: String,
    count: Number,
    items: Array
  },
src/components/inspection/TaskSummaryItem.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
<template>
  <div class="border-r-small p-h-8">
    <el-row justify="space-between">
      <div>{{ title }}</div>
      <el-text size="small">{{ sceneType }}</el-text>
    </el-row>
    <el-row>
      <el-col :span="8">
        <el-statistic title="巡查" :value="10">
          <template #suffix>个</template>
        </el-statistic>
      </el-col>
      <el-col :span="8">
        <el-statistic title="问题" :value="16">
          <template #suffix>个</template>
        </el-statistic>
      </el-col>
      <el-col :span="8">
        <el-statistic title="整改" :value="8">
          <template #suffix>个</template>
        </el-statistic>
      </el-col>
    </el-row>
  </div>
</template>
<script>
export default {
  props: {
    title: String,
    sceneType: {
      type: String,
      default: '全场景'
    }
  }
}
</script>
<style scoped></style>
src/components/map/BaseMap.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
<template>
  <div id="container" :style="height"></div>
</template>
<script setup>
import { onMounted } from 'vue'
import { createMap } from '@/utils/map/index'
// import { createMap } from '@/utils/map/baseMap'
import { inject } from 'vue'
const mapHeight = inject('mapHeight')
const height = 'height:' + mapHeight
onMounted(() => {
  // é«˜å¾·åœ°å›¾åˆå§‹åŒ–
  createMap('container')
})
</script>
<style>
#container {
  position: relative;
  width: 100%;
  /* height: calc(var(--fy-body-height) / 2); */
  /* min-height: var(--screen-min-height);
  min-width: var(--screen-min-width); */
  z-index: 0px;
}
/* åŽ»é™¤é«˜å¾·åœ°å›¾çš„å›ºå®šç‰ˆå·å›¾æ ‡ */
.amap-logo {
  display: none;
  opacity: 0 !important;
}
.amap-copyright {
  display: none;
  opacity: 0 !important;
}
.amap-marker-label {
  font-size: 13px;
  text-align: center;
  color: white;
  background-color: transparent;
  text-shadow: black 2px 2px 2px;
  border-radius: 2px;
  border: 0px;
  padding: 4px;
}
</style>
src/components/search/OptionLocation.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 '@/enum/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/OptionSceneType.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,78 @@
<template>
  <!-- <el-form-item label="场景类型" :prop="prop"> -->
  <el-select
    :model-value="modelValue"
    @change="handleChange"
    placeholder="场景类型"
    size="small"
    :style="'width: ' + width + 'px'"
  >
    <el-option v-for="s in sceneTypes" :key="s.value" :label="s.label" :value="s" />
  </el-select>
  <!-- </el-form-item> -->
</template>
<script>
import { enumScene } from '@/enum/scene'
export default {
  props: {
    // æ˜¯å¦åœ¨é¦–选项处添加“全部”选项
    allOption: {
      type: Boolean,
      default: true
    },
    // 1:飞羽环境系统;2:飞羽监管系统;
    type: {
      type: Number,
      default: 1
    },
    // è¿”回结果
    modelValue: Object,
    // æ˜¯å¦é»˜è®¤è¿”回初始选项
    initValue: {
      type: Boolean,
      default: true
    },
    // form表单绑定属性名
    prop: {
      type: String,
      default: '_scenetype'
    },
    // åˆ‡æ¢ type åŽï¼Œå½“前选项是否清空
    sourceInit: {
      type: Boolean,
      default: true
    },
    width: {
      type: Number,
      default: 220
    }
  },
  emits: ['update:modelValue'],
  data() {
    return {
      // sceneTypes: enumScene(this.type, this.allOption),
    }
  },
  computed: {
    sceneTypes() {
      if (this.sourceInit) {
        // å½“因为type或者allOption参数变化引起选项变更时,清空当前选项
        this.handleChange()
      }
      return enumScene(this.type, this.allOption)
    }
  },
  methods: {
    handleChange(value) {
      this.$emit('update:modelValue', value)
    }
  },
  mounted() {
    if (this.initValue) {
      this.handleChange(this.sceneTypes[0])
    }
  }
}
</script>
src/components/search/OptionTime.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
<template>
  <!-- <el-form-item label="时间" :prop="prop"> -->
  <el-date-picker
    v-model="date"
    @change="handleChange"
    :type="type"
    placeholder="选择时间"
    size="small"
    style="width: 100px"
  />
  <!-- </el-form-item> -->
</template>
<script>
const MONTH = 'month'
export default {
  props: {
    type: {
      type: String,
      default: MONTH
    },
    // è¿”回结果
    modelValue: Date,
    // æ˜¯å¦é»˜è®¤è¿”回初始选项
    initValue: {
      type: Boolean,
      default: true
    },
    prop: String
  },
  emits: ['update:modelValue'],
  data() {
    return {
      date: this.modelValue
    }
  },
  computed: {},
  methods: {
    handleChange(value) {
      this.$emit('update:modelValue', value)
    },
    timeFormat() {
      switch (this.type) {
        case MONTH:
          return 'YYYY-MM'
        default:
          return 'YYYY-MM'
      }
    }
  },
  mounted() {
    if (this.initValue) {
      this.date = new Date()
      this.handleChange(this.date)
    }
  }
}
</script>
src/enum/location.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,252 @@
/**
 * èŽ·å–è¡Œæ”¿åŒºåˆ’
 * @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: []
            }
          ]
        }
      ]
    }
  ]
}
// æŒ‰ç…§éœ€æ±‚的定位精度返回对应数据
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/enum/scene.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,126 @@
/**
 * åœºæ™¯ç±»åž‹æžšä¸¾
 * @param {Number} type 1:飞羽环境系统;2:飞羽监管系统;
 * @param {Boolean} allOption æ˜¯å¦åœ¨å¤´éƒ¨æ·»åŠ â€œå…¨éƒ¨â€é€‰é¡¹
 */
function enumScene(type, allOption = true) {
  let l
  switch (type) {
    case 1:
      l = _enumScene_1()
      break
    case 2:
      l = _enumScene_2()
      break
    default:
      l = _enumScene_1()
      break
  }
  if (!allOption) {
    l.shift()
  }
  return l
}
function getSceneName(value, type = 1) {
  return enumScene(type).find((v) => {
    if (v.value == value) {
      return v
    }
  })
}
// é£žç¾½çŽ¯å¢ƒç³»ç»Ÿ
function _enumScene_1() {
  return [
    {
      label: '全部场景',
      value: null
    },
    {
      label: '餐饮',
      value: '1'
    },
    {
      label: '工地',
      value: '2'
    },
    {
      label: '码头',
      value: '3'
    },
    {
      label: '堆场',
      value: '4'
    },
    {
      label: '搅拌站',
      value: '5'
    },
    {
      label: '工业企业',
      value: '6'
    },
    {
      label: '汽修',
      value: '7'
    },
    {
      label: '实验室',
      value: '8'
    },
    {
      label: '医疗机构',
      value: '9'
    }
  ]
}
// é£žç¾½ç›‘管系统
function _enumScene_2() {
  return [
    {
      label: '全部场景',
      value: null
    },
    {
      label: '工地',
      value: '1'
    },
    {
      label: '码头',
      value: '2'
    },
    {
      label: '搅拌站',
      value: '3'
    },
    {
      label: '工业企业',
      value: '4'
    },
    {
      label: '餐饮',
      value: '5'
    },
    {
      label: '汽修',
      value: '6'
    },
    {
      label: '道路扬尘监测点',
      value: '9'
    },
    {
      label: '道路',
      value: '10'
    },
    {
      label: '堆场',
      value: '14'
    }
  ]
}
export { enumScene, getSceneName }
src/main.js
@@ -4,9 +4,12 @@
import App from './App.vue'
import router from './router'
import timeUtil from './utils/time-util'
const app = createApp(App)
app.config.globalProperties.$fm = timeUtil
// elementUI Icon æ³¨å†Œ
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
src/utils/map/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,110 @@
import AMapLoader from '@amap/amap-jsapi-loader'
var mapInitDone = false
var onMapMountedEvents = []
var AMap
// åœ°å›¾å¯¹è±¡
var map
// å«æ˜Ÿå›¾å±‚
var satellite
// é¼ æ ‡ç»˜å›¾
var mouseTool
// 3D图层
var object3Dlayer
// åœ°å›¾æ‹–动状态
var isDragging = false
// åœ°å›¾åŠ è½½å®Œæˆè§¦å‘
function onMapMounted(...events) {
  if (mapInitDone) {
    events.forEach((e) => {
      e()
    })
  } else {
    onMapMountedEvents = onMapMountedEvents.concat(events)
  }
}
function createMap(id) {
  AMapLoader.load({
    key: 'c55f27799afbfa69dc5a3fad90cafe51', // ç”³è¯·å¥½çš„Web端开发者Key,首次调用 load æ—¶å¿…å¡«
    version: '2.0', // æŒ‡å®šè¦åŠ è½½çš„ JS API çš„版本,缺省时默认为 1.4.15
    plugins: [
      'Map3D',
      'ElasticMarker',
      'AMap.ControlBar',
      'AMap.ToolBar',
      'AMap.Scale',
      'AMap.DragRoute',
      'AMap.MouseTool',
      'AMap.PolygonEditor'
    ] // éœ€è¦ä½¿ç”¨çš„的插件列表,如比例尺'AMap.Scale'等
  })
    .then((_AMap) => {
      AMap = _AMap
      _initMap(id)
      mapInitDone = true
      onMapMountedEvents.forEach((e) => {
        e()
      })
      onMapMountedEvents = []
    })
    .catch((e) => {
      console.log(e)
    })
}
function _initMap(elementId) {
  map = new AMap.Map(elementId, {
    rotateEnable: true,
    pitchEnable: true,
    alwaysRender: false,
    showLabel: true,
    showBuildingBlock: true,
    mapStyle: 'amap://styles/e1e78509de64ddcd2efb4cb34c6fae2a',
    features: ['bg', 'road'],
    pitch: 45, // åœ°å›¾ä¿¯ä»°è§’度,有效范围 0 åº¦- 83 åº¦
    viewMode: '3D', // åœ°å›¾æ¨¡å¼
    resizeEnable: true,
    center: [121.603928, 31.252955],
    zooms: [3, 18],
    zoom: 14
  })
  // æ·»åŠ å«æ˜Ÿåœ°å›¾
  satellite = new AMap.TileLayer.Satellite()
  satellite.hide()
  map.add([satellite])
  _initMouseTool()
  // _init3DLayer();
  _initDragEvent()
}
// é¼ æ ‡ç»˜å›¾åˆå§‹åŒ–
function _initMouseTool() {
  mouseTool = new AMap.MouseTool(map)
}
// 3D图层初始化
// function _init3DLayer() {
//   object3Dlayer = new AMap.Object3DLayer();
//   map.add(object3Dlayer);
// }
// è®¾ç½®åœ°å›¾æ‹–拽监听事件
function _initDragEvent() {
  let dragEndEvent
  map.on('dragstart', () => {
    clearTimeout(dragEndEvent)
    isDragging = true
  })
  map.on('dragend', function () {
    dragEndEvent = setTimeout(() => {
      isDragging = false
    }, 8000)
  })
}
export { createMap, onMapMounted, map, AMap, mouseTool, object3Dlayer, isDragging }
src/utils/map/marks.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,131 @@
/**
 * é«˜å¾·åœ°å›¾ç‚¹æ ‡è®°ç»˜åˆ¶ç›¸å…³
 */
import { map, AMap } from './index'
var _massMarks = undefined
export default {
  /**
   * ç»˜åˆ¶æµ·é‡ç‚¹æ ‡è®°
   * @param fDatas å®Œæ•´ç›‘测数据
   * @param _factor å½“前展示的监测因子对象
   */
  drawMassMarks(fDatas, _factor, onClick) {
    if (_massMarks) {
      map.remove(_massMarks)
      _massMarks = undefined
    }
    const lnglats = fDatas.lnglats_GD
    var data = []
    for (let i = 0; i < lnglats.length; i++) {
      data.push({
        lnglat: lnglats[i], //点标记位置
        name: `${fDatas.times[i]}<br/>${_factor.factorName}: ${_factor.datas[i].factorData} mg/m³`,
        id: i
      })
    }
    // åˆ›å»ºæ ·å¼å¯¹è±¡
    var styleObject = {
      url: 'https://a.amap.com/jsapi_demos/static/images/mass1.png',
      // url: './asset/mipmap/ic_up_white.png', // å›¾æ ‡åœ°å€
      size: new AMap.Size(11, 11), // å›¾æ ‡å¤§å°
      anchor: new AMap.Pixel(5, 5) // å›¾æ ‡æ˜¾ç¤ºä½ç½®åç§»é‡ï¼ŒåŸºå‡†ç‚¹ä¸ºå›¾æ ‡å·¦ä¸Šè§’
    }
    var massMarks = new AMap.MassMarks(data, {
      zIndex: 5, // æµ·é‡ç‚¹å›¾å±‚叠加的顺序
      zooms: [15, 18], // åœ¨æŒ‡å®šåœ°å›¾ç¼©æ”¾çº§åˆ«èŒƒå›´å†…展示海量点图层
      style: styleObject // è®¾ç½®æ ·å¼å¯¹è±¡
    })
    massMarks.on('click', (event) => {
      const i = event.data.id
      // 3. è‡ªå®šä¹‰ç‚¹å‡»äº‹ä»¶
      onClick(i)
    })
    var marker = new AMap.Marker({
      content: ' ',
      map: map,
      offset: new AMap.Pixel(13, 12)
    })
    var timeout
    massMarks.on('mouseover', (e) => {
      if (timeout) {
        clearTimeout(timeout)
      }
      marker.setPosition(e.data.lnglat)
      marker.setLabel({ content: e.data.name })
      map.add(marker)
      timeout = setTimeout(() => {
        map.remove(marker)
      }, 2000)
    })
    _massMarks = massMarks
    map.add(massMarks)
  },
  createLabelMarks(img, dataList) {
    const layer = new AMap.LabelsLayer({
      zooms: [3, 20],
      zIndex: 1000,
      // å¼€å¯æ ‡æ³¨é¿è®©ï¼Œé»˜è®¤ä¸ºå¼€å¯ï¼Œv1.4.15 æ–°å¢žå±žæ€§
      collision: true,
      // å¼€å¯æ ‡æ³¨æ·¡å…¥åŠ¨ç”»ï¼Œé»˜è®¤ä¸ºå¼€å¯ï¼Œv1.4.15 æ–°å¢žå±žæ€§
      animation: true
    })
    map.add(layer)
    // var markers = [];
    for (var i = 0; i < dataList.length; i++) {
      const data = dataList[i]
      var curData = {
        name: data.sceneName,
        position: [data.longitude, data.latitude],
        zooms: [10, 20],
        opacity: 1,
        zIndex: 10,
        icon: {
          type: 'image',
          image: img,
          // clipOrigin: [14, 92],
          // clipSize: [50, 68],
          size: [30, 30],
          anchor: 'bottom-center',
          angel: 0,
          retina: true
        },
        text: {
          content: data.sceneName,
          direction: 'top',
          offset: [0, -5],
          style: {
            fontSize: 16,
            fontWeight: 'normal',
            fillColor: '#fff',
            strokeColor: '#333',
            strokeWidth: 0,
            backgroundColor: '#122b54a9'
          }
        }
      }
      curData.extData = {
        index: i
      }
      var labelMarker = new AMap.LabelMarker(curData)
      // markers.push(labelMarker);
      layer.add(labelMarker)
    }
    return layer
  }
}
src/utils/time-util.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
import dayjs from "dayjs";
export default {
  format(date, template) {
    return dayjs(date).format(template)
  },
  formatH(date){
    if (date) {
      return this.format(date, 'HH:mm:ss')
    } else {
      return '--:--:--'
    }
  },
  formatYM(date){
    if (date) {
      return this.format(date, 'YYYY-MM')
    } else {
      return '----/--'
    }
  },
  formatYMD(date){
    if (date) {
      return this.format(date, 'YYYY-MM-DD')
    } else {
      return '----/--/--'
    }
  },
  formatYMDH(date){
    if (date) {
      return this.format(date, 'YYYY-MM-DD HH:mm:ss')
    } else {
      return '----/--/-- --:--:--'
    }
  },
}
src/views/inspection/InspectionView.vue
@@ -6,6 +6,9 @@
</template>
<script setup>
/**
 * çŽ°åœºå·¡æŸ¥å®žæ—¶è·Ÿè¸ª
 */
import TaskTrack from '@/views/inspection/TaskTrack.vue'
</script>
src/views/inspection/JointEnforcement.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
<template>
  <div class="border-r-small" style="height: 200px">
    <div class="f-l">联合执法</div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      tasks: []
    }
  },
  watch: {},
  methods: {},
  mounted() {}
}
</script>
<style scoped></style>
src/views/inspection/SelfInspection.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
<template>
  <div class="border-r-small" style="height: 200px">
    <div class="f-l">应急自巡查</div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      tasks: []
    }
  },
  watch: {},
  methods: {},
  mounted() {}
}
</script>
<style scoped></style>
src/views/inspection/TaskTrack.vue
@@ -4,27 +4,52 @@
      <!-- <div class="f-l">现场巡查跟踪(现场人员巡查情况实时掌握)</div> -->
      <!-- <SubtaskItem v-for="item in subtaskList" :key="item.guid" v-bind="item"> </SubtaskItem> -->
      <el-col :span="4">
        <TaskNode title="新任务" :items="subtaskList" v-slot="{ item, index }">
        <TaskNode
          title="新任务"
          :items="subtaskList"
          warning="警告:有历史未完成任务"
          v-slot="{ item, index }"
        >
          <SubtaskItem v-bind="item" :index="index"> </SubtaskItem>
        </TaskNode>
      </el-col>
      <el-col :span="4">
        <TaskNode title="正在执行" :items="subtaskList" v-slot="{ item, index }">
        <TaskNode
          title="正在执行"
          :items="subtaskList"
          warning="警告:有执行时间过长的任务"
          v-slot="{ item, index }"
        >
          <SubtaskItem v-bind="item" :index="index"> </SubtaskItem>
        </TaskNode>
      </el-col>
      <el-col :span="4">
        <TaskNode title="待审核" :items="subtaskList" v-slot="{ item, index }">
        <TaskNode
          title="待审核"
          :items="subtaskList"
          warning="警告:有长时间未审核问题"
          v-slot="{ item, index }"
        >
          <SubtaskExamineItem v-bind="item" :index="index" type="问题审核"> </SubtaskExamineItem>
        </TaskNode>
      </el-col>
      <el-col :span="4">
        <TaskNode title="待整改" :items="subtaskList" v-slot="{ item, index }">
        <TaskNode
          title="待整改"
          :items="subtaskList"
          warning="警告:有长时间未整改问题"
          v-slot="{ item, index }"
        >
          <SubtaskExamineItem v-bind="item" :index="index" type="待整改"> </SubtaskExamineItem>
        </TaskNode>
      </el-col>
      <el-col :span="4">
        <TaskNode title="待确认" :items="subtaskList" v-slot="{ item, index }">
        <TaskNode
          title="待确认"
          :items="subtaskList"
          warning="警告:有长时间未审核整改"
          v-slot="{ item, index }"
        >
          <SubtaskExamineItem v-bind="item" :index="index" type="整改审核"> </SubtaskExamineItem>
        </TaskNode>
      </el-col>
src/views/main/MonitorView.vue
@@ -20,16 +20,20 @@
import InspectionView from '@/views/inspection/InspectionView.vue'
import ManagementView from '@/views/management/ManagementView.vue'
import VisualizationView from '@/views/visualization/VisualizationView.vue'
import { provide } from 'vue'
provide('mapHeight', 'calc(var(--fy-body-height) / 3 * 2)')
</script>
<style scoped>
.page-left-top {
  height: calc(var(--fy-body-height) / 2);
  background-color: aquamarine;
  height: calc(var(--fy-body-height) / 3 * 2);
  /* background-color: aquamarine; */
}
.page-left-bottom {
  height: calc(var(--fy-body-height) / 2);
  background-color: bisque;
  height: calc(var(--fy-body-height) / 3 * 1);
  /* background-color: bisque; */
}
.page-right {
src/views/management/ManagementView.vue
@@ -1,10 +1,12 @@
<template>
  <el-row> ç»Ÿè®¡ç®¡ç† </el-row>
  <TaskStats></TaskStats>
  <TaskSummary></TaskSummary>
</template>
<script setup>
import TaskStats from '@/views/management/TaskStats.vue'
import TaskSummary from '@/views/management/TaskSummary.vue'
</script>
<style scoped></style>
src/views/management/TaskStats.vue
@@ -4,11 +4,26 @@
    <el-row>
      <TaskItem v-for="item in tasks" :key="item.guid" v-bind="item"></TaskItem>
    </el-row>
    <el-row>
      <el-col :span="12">
        <SelfInspection></SelfInspection>
      </el-col>
      <el-col :span="12">
        <JointEnforcement></JointEnforcement>
      </el-col>
    </el-row>
  </div>
</template>
<script>
import SelfInspection from '@/views/inspection/SelfInspection.vue'
import JointEnforcement from '@/views/inspection/JointEnforcement.vue'
/**
 * ä»»åŠ¡å®Œæˆæƒ…å†µ
 */
export default {
  components: { SelfInspection, JointEnforcement },
  data() {
    return {
      tasks: []
src/views/management/TaskSummary.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
<template>
  <el-row> å·¡æŸ¥æ±‡æ€» </el-row>
  <el-row>
    <el-col :span="8">
      <TaskSummaryItem title="今日汇总"></TaskSummaryItem>
    </el-col>
    <el-col :span="8">
      <TaskSummaryItem title="周度汇总"></TaskSummaryItem>
    </el-col>
    <el-col :span="8">
      <TaskSummaryItem title="月度汇总"></TaskSummaryItem>
    </el-col>
  </el-row>
  <el-row>
    <el-col :span="12">
      <TaskSummaryItem title="季度汇总"></TaskSummaryItem>
    </el-col>
    <el-col :span="12">
      <TaskSummaryItem title="年度汇总"></TaskSummaryItem>
    </el-col>
  </el-row>
</template>
<script setup></script>
<style scoped></style>
src/views/visualization/SubtaskVisual.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,95 @@
<template>
  <el-scrollbar :height="mapHeight">
    <el-card class="p-events-auto wrapper">
      <div>{{ subtask.name }}</div>
      <el-timeline style="max-width: 600px">
        <el-timeline-item
          v-for="(activity, index) in activities"
          :key="index"
          :timestamp="activity.timestamp"
          :hide-timestamp="activity.running"
          :type="activity.running ? 'danger' : 'success'"
          :size="activity.running ? 'large' : 'normal'"
          :hollow="false"
        >
          {{ activity.content }}
        </el-timeline-item>
      </el-timeline>
    </el-card>
  </el-scrollbar>
</template>
<script>
import { inject } from 'vue'
/**
 * å…·ä½“巡查任务可视化
 * åŒ…括地图定位信息展示、巡查任务全流程平铺展示
 */
export default {
  setup() {
    const mapHeight = inject('mapHeight')
    const height = 'height:' + mapHeight
    return { height, mapHeight }
  },
  props: {
    subtask: {
      type: Object,
      default: () => {
        return {
          guid: 'SMuheEkjswioSn7A',
          name: '中科生态数字港项目巡查中科生态数字港项目巡查',
          district: '金山区',
          planTime: '2024-06-04',
          startTime: '2024-06-04 13:31:26',
          endTime: '2024-06-04 13:33:37',
          userName: '朱正强',
          status: '已结束',
          total: 4,
          checked: 2
        }
      }
    }
  },
  data() {
    return {
      activities: [
        {
          content: '任务创建',
          timestamp: '2024-06-04 08:00',
          running: false
        },
        {
          content: '开始巡查',
          timestamp: '2024-06-04 09:00',
          running: false
        },
        {
          content: '结束巡查',
          timestamp: '2024-06-04 09:15',
          running: false
        },
        {
          content: '完成问题审核',
          timestamp: '2024-06-04 10:15',
          running: false
        },
        {
          content: '问题整改中...',
          timestamp: '2024-06-04 10:15',
          running: true
        }
      ]
    }
  }
}
</script>
<style scoped>
.wrapper {
  /* position: absolute; */
  top: 0;
  right: 0;
  /* background-color: wheat; */
}
</style>
src/views/visualization/SupervisionVisual.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
<template>
  <el-row>
    <div class="p-events-auto">
      <OptionLocation :level="3" :width="170" v-model="locations"></OptionLocation>
      <OptionSceneType :type="2" :width="120" v-model="sceneType"></OptionSceneType>
      <OptionTime v-model="time"></OptionTime>
    </div>
  </el-row>
</template>
<script>
import { inject } from 'vue'
import taskApi from '@/api/fysp/taskApi.js'
import marks from '@/utils/map/marks.js'
import scene_1 from '@/assets/icon/scene_1.png'
/**
 * ç›‘管可视化
 * å±•现区域整体监管状态
 */
export default {
  setup() {
    const mapHeight = inject('mapHeight')
    const height = 'height:' + mapHeight
    return { height, mapHeight }
  },
  props: {},
  data() {
    return {
      locations: {},
      sceneType: {},
      time: ''
    }
  },
  computed: {
    area() {
      return {
        provincecode: this.locations.pCode,
        provincename: this.locations.pName,
        citycode: this.locations.cCode,
        cityname: this.locations.cName,
        districtcode: this.locations.dCode,
        districtname: this.locations.dName,
        starttime: this.$fm.formatYMDH(this.time),
        scensetypeid: this.sceneType.value
      }
    }
  },
  methods: {
    // æŸ¥è¯¢
    fetchSubtaskSummaryArea() {
      return taskApi.fetchSubtaskSummaryArea(this.area).then((res) => {
        let list = []
        res.data.forEach((e) => {
          list = list.concat(e.subTaskSummary)
        })
        this.newLabelMasks(list)
      })
    },
    newLabelMasks(data) {
      marks.createLabelMarks(scene_1, data)
    }
  },
  mounted() {
    this.fetchSubtaskSummaryArea()
  }
}
</script>
<style scoped></style>
src/views/visualization/VisualizationView.vue
@@ -1,11 +1,35 @@
<template>
  <el-row class="wrapper"> å¯è§†åŒ– </el-row>
  <!-- <el-row class="wrapper"> å¯è§†åŒ– </el-row> -->
  <BaseMap></BaseMap>
  <el-row class="overlay-container" :style="height">
    <el-col :span="18">
      <SupervisionVisual></SupervisionVisual>
    </el-col>
    <el-col :span="6">
      <SubtaskVisual></SubtaskVisual>
    </el-col>
  </el-row>
</template>
<script setup></script>
<script setup>
import SubtaskVisual from './SubtaskVisual.vue'
import SupervisionVisual from './SupervisionVisual.vue'
import { inject } from 'vue'
const mapHeight = inject('mapHeight')
const height = 'height:' + mapHeight
</script>
<style scoped>
.wrapper {
  /* background-color: aquamarine; */
.overlay-container {
  /* background: aliceblue; */
  position: absolute;
  width: 100%;
  /* height: 100vh; */
  top: 0;
  left: 0;
  /* padding: 4px; */
  pointer-events: none;
}
</style>