Newer
Older
safe_production_front / src / views / map / map-bak.vue
dutingting on 13 Nov 12 KB 优化
<script lang="ts" setup name="Map">
import { ElMessage } from 'element-plus'
import type { Ref } from 'vue'
import { getCurrentInstance, ref } from 'vue'
import dayjs from 'dayjs'
import L from 'leaflet'
import 'leaflet/dist/leaflet.css'
// import aMap from '@/components/aMap/aMap.vue'
import { getDataHisList, getDevInfo, getDevMap } from '@/api/ptz/dev'
import useWebsocketStore from '@/store/modules/websocket'
import type { lineDataI } from '@/components/Echart/echart-interface'
import { videoTree } from '@/api/ycjg/aqbb'
import 'leaflet.label'
import 'proj4'
import 'proj4leaflet'
import { getDevListPage } from '@/api/ycjg/dev'
import './L.TileLayer.NoGap.js'
const { proxy } = getCurrentInstance() as any
const treeRef = ref()
const toParentsMap = ref({
  lat: '',
  lng: '',
})

const filterText = ref('')
const defaultProps = ref({
  children: 'children',
  label: 'name',
  isDisabled: 'disabled',
})

const treeData = ref([]) as any // 树数据
const map: any = ref(null)
const baseLayer = ref([])
const keyword = ref('')
const isRightShow = ref(false)
let treeClickCount = 0
const popupOpen = ref(false)
// 筛选树节点
function filterNode(value: any, data: { name: string | any[] }) {
  if (value === '' || value === null) {
    return true
  }
  return data.name.includes(value)
}

/**
 * 制作icon以及popup的显示内容
 * @param deviceStatus 设备状态
 * @param monitorName
 * @param deviceStatusName 设备状态名称
 * @param secretLevelName 密级
 * @param location 详细位置
 */
const makePopupMessage = (
  deviceStatus: number,
  monitorName = '',
  deviceStatusName = '',
  secretLevelName = '',
  location = '',
) => {
  let icon
  let color
  switch (deviceStatus) {
    case 0:
      icon = './marker/gray.png'
      color = 'gray'
      break
    case 1:
      icon = './marker/green.png'
      color = 'green'
      break
  }
  const message
    = `<div style="padding: 10px;width: 230px;font-size: 16px;">
          <div style="font-weight: bold;margin-bottom: 10px">${monitorName || ''}</div>
          <div style="color: ` + `${color}` + `"><span style="color: black">设备状态:</span>${deviceStatusName || ''}</div>
          <div style="word-break: break-all; "><span>密级:</span>${secretLevelName || ''}</div>
          <div style="word-break: break-all; "><span>详细位置:</span>${location || ''}</div>
        </div>`
  return {
    message,
    icon,
  }
}

const currentNode = ref(null)

// 显示marker信息
function handleClosePopup() {
  console.log('关闭popup')
  map.value.closePopup()
}

// 双击树节点
function handleNodeClick(data: any, node: any, self: any) {
  console.log('获取设备信息', data)
  currentNode.value = data
  const now = new Date().getTime()
  if (now - treeClickCount < 300) { // 双击事件的判断,300毫秒内重复点击
    if (data.children.length !== 0) { // 点击父亲
      return
    }
    if (data.pid === 0) {
      return
    }
    if (data && data.device && data.device.latitude && data.device.longitude) {
      const { message } = makePopupMessage(data.device.deviceStatus, data.device.monitorName, data.device.deviceStatusName, data.device.secretLevelName, data.device.location)
      map.value.panTo([data.device.latitude, data.device.longitude])
      const popup = L.popup({ closeButton: false }).setLatLng([data.device.latitude, data.device.longitude]).setContent(message)
      map.value.openPopup(popup)
      popupOpen.value = true
      map.value.setView([data.device.latitude, data.device.longitude], 18)
    }
    else {
      ElMessage.warning('未获取到此设备的经纬度,请在设备管理中查看此设备的详细信息')
    }
  }
  treeClickCount = now
}

// 查询设备列表并往地图上添加marker
function search(init = true) {
  map.value.eachLayer((layer: any) => {
    if (layer !== baseLayer.value[0]) {
      layer.remove()
    }
  })
  getDevListPage({
    keyword: keyword.value,
    devType: '',
    offset: 1,
    limit: 1000,
  }).then((res) => {
    res.data.rows.forEach((item: any) => {
      const { message, icon } = makePopupMessage(item.deviceStatus, item.monitorName, item.deviceStatusName, item.secretLevelName, item.location)
      const popup = L.popup().setContent(message)
      const divIcon = L.divIcon({
        html: `<div>
                <image src="${icon}" width="30" height="30" style="margin-bottom: -3px">
                <div style="margin-left: calc(-50% - 5px);border-radius:5px;display: inline-block;white-space: nowrap;background: rgb(255,255,255);text-align: left;height: 20px;padding: 0 4px">${item.monitorName || ''}</div>
              </div>`,
        className: 'my-div-icon',
        iconSize: [30, 30],
      })
      const marker = L.marker([item.latitude, item.longitude], { icon: divIcon }).addTo(map.value)
      marker.bindPopup(popup, { closeButton: false })

      popup.on('close', () => {
        console.log('监听到popup关闭')
        popupOpen.value = false
      })

      marker.on('click', () => {
        if (popupOpen.value) {
          // 如果弹窗已经打开,再次点击标记将关闭弹窗
          marker.closePopup()
          popupOpen.value = false
        }
        else {
          // 首次点击将打开弹窗
          marker.openPopup()
          popupOpen.value = true
        }
      })
    })
    if (init && res && res.data && res.data.rows) {
      if (!res.data.rows.length) {
        ElMessage.warning('查无结果')
      }
      else {
        let lat = proxy.$position.lat
        let lng = proxy.$position.lng
        console.log('执行定位, 默认北京', lat, lng)
        for (let i = 0; i < res.data.rows.length; i++) {
          if (res.data.rows[i].latitude && res.data.rows[i].longitude) {
            lat = res.data.rows[i].latitude
            lng = res.data.rows[i].longitude
            console.log('找到第一个有明确定位的设备:lat, lng', lat, lng)
            break
          }
        }
        map.value.setView([lat, lng])
      }
    }
    if (currentNode.value) {
      handleNodeClick(currentNode.value, null, null)
    }
  })
}

/**
 * Proj框架:坐标系转换
 * EPSG:900913是一种投影坐标系
 */
L.CRS.Baidu = new L.Proj.CRS(
  'EPSG:900913',
  '+proj=merc +a=6378206 +b=6356584.314245179 +lat_ts=0.0 +lon_0=0.0 +x_0=0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext  +no_defs',
  {
    resolutions: (function () {
      const level = 19
      var res = []
      res[0] = 2 ** 18 // 2的18次幂
      for (var i = 1; i < level; i++) {
        res[i] = 2 ** (18 - i)
      }
      return res
    })(),
    origin: [0, 0],
    // L.bounds 返回数组下标下界
    bounds: L.bounds([20037508.342789244, 0], [0, 20037508.342789244]),
  })

onBeforeMount(() => {
  toParentsMap.value.lat = proxy.$position.lat
  toParentsMap.value.lng = proxy.$position.lng
})

// 处理树的数据
function solveData(data: any) {
  data.forEach((item: any) => {
    if (item.device) {
      item.name = `${item.name} (${item.device.deviceStatusName})`
      console.log('修改后的name', item.name)
    }
    if (item.children && item.children.length) {
      solveData(item.children)
    }
  })
  return data
}

onMounted(() => {
  videoTree().then((response) => {
    if (response.code === 200) {
      treeData.value = response.data
      // treeData.value = solveData(treeData.value)
    }
  })

  map.value = L.map('map', {
    minZoom: 5,
    maxZoom: 18,
    center: [39.91459528, 116.26499505],
    zoom: window.localStorage.getItem('zoom') ? Number(window.localStorage.getItem('zoom')) : 15,
    zoomControl: false,
    attributionControl: false,
    crs: L.CRS.Baidu,
  })
  const str = '/static/tiles/{z}/{x}/{y}.png'
  baseLayer.value.push(L.tileLayer(str, {
    tms: true,
    noWrap: true,
  }).addTo(map.value))
  search(false)

  // 当地图缩放结束后更新标记点位置
  map.value.on('zoomend', () => {
    console.log('监听缩放结束')
    search(false)
  })
})
const unwatch = watch(filterText, (newVal) => {
  treeRef.value.filter(newVal)
})
onBeforeUnmount(() => {
  unwatch()
})
</script>

<template>
  <div id="map" style="height: calc(100vh - 110px);" />
  <!-- <div class="input">
    <el-input v-model="keyword" type="text" placeholder="设备名称关键字" clearable style="width: 200px;" />
    <el-button type="primary" style="margin-left: 5px;" @click="search">
      查询
    </el-button>
  </div> -->
  <el-card class="tree-area">
    <el-input
      v-model="filterText"
      placeholder="设备名称过滤"
      style="width: 240px;"
    />
    <el-tree
      ref="treeRef"
      class="filter-tree"
      style="width: 100%;height: 100%;"
      :data="treeData"
      :filter-node-method="filterNode"
      node-key="id"
      :default-expand-all="true"
      :props="defaultProps"
      @node-click="handleNodeClick"
    >
      <template #default="{ node, data }">
        <span style="display: flex;align-items: center;">
          <el-icon v-if="data.device.deviceStatusName === '在线'" style="margin-right: 5px;">
            <svg-icon name="icon-online" />
          </el-icon>
          <el-icon v-if="data.device.deviceStatusName === '离线'" style="margin-right: 5px;">
            <svg-icon name="icon-offline" />
          </el-icon>
          <el-tooltip
            class="box-item"
            effect="dark"
            :content="node.label"
            placement="right"
          >
            <template #content>
              <span>{{ node.label }}</span>
              <span v-if="data.device.deviceTypeName">({{ data.device.deviceTypeName }})</span>
            </template>
            <span v-if="data.device.deviceTypeName" :style="{ 'color': data.device.deviceStatusName === '在线' ? '#0e932e' : '#606266', 'font-weight': data.device.deviceStatusName === '在线' ? 600 : 500 }">{{ node.label }}</span>
            <span v-else>{{ node.label }}</span>
          </el-tooltip>
          <el-icon v-if="data.device.deviceType === '0'" style="margin-left: 5px;">
            <svg-icon name="icon-qiang" :color="data.device.deviceStatusName === '在线' ? '#0e932e' : '#979797'" />
          </el-icon>
          <el-icon v-if="data.device.deviceType === '1' || data.device.deviceType === '2'" style="margin-left: 5px;">
            <svg-icon name="icon-ball" :color="data.device.deviceStatusName === '在线' ? '#0e932e' : '#979797'" />
          </el-icon>
          <el-icon v-if="data.device.deviceType === '3'" style="margin-left: 5px;">
            <svg-icon name="icon-ball" :color="data.device.deviceStatusName === '在线' ? '#0e932e' : '#979797'" />
          </el-icon>
          <el-icon v-if="data.device.deviceType === '4'" style="margin-left: 5px;">
            <svg-icon name="icon-ytqiang" :color="data.device.deviceStatusName === '在线' ? '#0e932e' : '#979797'" />
          </el-icon>
        </span>
      </template>
    </el-tree>
  </el-card>
  <div class="cover">
    <div class="green" />
    <div class="gray" />
    <div class="item">
      正常
    </div>
    <div class="item">
      离线
    </div>
  </div>
  <div v-if="isRightShow" class="right" />
</template>

<style lang="scss">
.leaflet-popup {
  margin-bottom: 35px !important;
}
</style>

<style lang="scss" scoped>
.map {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
}

.right {
  z-index: 1000 !important;
  position: absolute;
  right: 20px;
  top: 10px;
  width: 350px;
  height: calc(100vh - var(--g-topbar-height) - var(--g-header-height) - 20px);
  background: #faefe0;
  border-radius: 10px;
  padding: 20px 0;

  .devName {
    padding-left: 10px;
    margin-bottom: 10px;
    font-size: 19px;
    font-weight: bold;
  }

  .title {
    font-size: 17px;
    font-weight: bold;
    margin: 10px 0;
    color: #464646;
  }

  .close {
    width: 20px;
    height: 20px;
    position: absolute;
    right: 10px;
    top: 20px;
  }
}

.input {
  position: absolute;
  top: 20px;
  left: 20px;
  width: 280px;
  height: 34px;
  display: flex;
  z-index: 1001;
}

.tree-area {
  position: absolute;
  top: 10px;
  left: 10px;
  width: 280px;
  // height: 200px;
  height: calc(100% - 160px);
  display: flex;
  z-index: 1000;
  overflow: auto;
}

.cover {
  position: absolute;
  bottom: 25px;
  left: 5px;
  width: 120px;
  height: 120px;
  border-radius: 10px;
  background: #faefe0;
  z-index: 1111;
  display: flex;
  flex-flow: column wrap;
  justify-content: space-evenly;
  align-items: center;

  .item {
    width: 50px;
    height: 45px;
    padding-top: 10px;
    font-weight: bold;
    color: #5f5f5f;
  }

  .green {
    background: url("../../assets/images/marker/green.png") no-repeat center / cover;
    width: 45px;
    height: 45px;
  }

  .red {
    background: url("../../assets/images/marker/red.png") no-repeat center / cover;
    width: 45px;
    height: 45px;
  }

  .gray {
    background: url("../../assets/images/marker/gray.png") no-repeat center / cover;
    width: 45px;
    height: 45px;
  }
}
</style>

<style>
.my-div-icon {
  color: black;
  font-size: 14px;
  text-align: center;
  pointer-events: none; /* Prevent mouse events from firing */
}
</style>