Newer
Older
smartwell_front / src / views / home / force / components / map.vue
<!--
  Description: 防外力破坏监测-地图
  Author: 李亚光
  Date: 2025-03-25
 -->
<script lang="ts" setup name="ForceMap">
// import detailInfo from '@/views/home/pipeline/components/detailInfo.vue'
import detailInfo from '@/views/home/pipeline/components/detailInfoDialog.vue'
import AMap from '@/components/map/index.vue'
import { toTreeList } from '@/utils/structure'
const $props = defineProps({
  height: {
    type: Number,
    default: 0,
  },
  data: {
    type: Array,
    default: () => ([]),
  },
})
const $emits = defineEmits(['clickMarker'])
// 地图实例
const mapRef = ref()
const publicPath = window.location.href.split('#')[0]
// 展示图例数据
const legendShowData = ref(['1', '2', '3'])
// 地图图例数据
const legendData = ref([
  {
    name: '正常',
    url: `${publicPath}/image/legend/normal.png`,
    value: '1',
  },
  {
    name: '报警',
    url: `${publicPath}/image/legend/alarm.png`,
    value: '2',
  },
  {
    name: '故障',
    url: `${publicPath}/image/legend/fault.png`,
    value: '3',
  },
])
// 控制图例
const clickLegend = (type: string) => {
  if (legendShowData.value.includes(type)) {
    legendShowData.value = legendShowData.value.filter((item: string) => item !== type)
  }
  else {
    legendShowData.value.push(type)
  }
  resetDraw()
}
// 树形选择数据
const pointTreeData = ref()
const defaultProps = {
  children: 'children',
  label: 'label',
}
const selectTree = ref<string[]>(['1', '2', '1-1'])
const treeRef = ref()
setTimeout(() => {
  const data = [
    { id: '1', value: '1-1', label: '全部', pid: '0' },
    { id: '2', value: '1', label: '智能警示桩', pid: '1' },
    { id: '3', value: '2', label: '管网哨兵', pid: '1' },
  ] as any[]
  pointTreeData.value = toTreeList(data)
}, 1000);
const checkTreeChange = () => {
  const currentChecked = treeRef.value.getCheckedNodes().map((item: any) => item.value)
  selectTree.value = currentChecked
  resetDraw()
}
const resetLegend = () => {
  selectTree.value = ['1', '2', '1-1']
  legendShowData.value = ['1', '2', '3']
  treeRef.value.setChecked('1', true, true)
}
// 初次加载标识
const pageIsFirst = ref(true)
// 绘制标记点
const drawMarker = (legend?: boolean) => {
  const style = [
    {
      url: `${publicPath}/image/pipeline/warning-normal.png`, // 图标地址
      // anchor: new mapRef.value.AMap.Pixel(0, 0), // 图标显示位置偏移量,基准点为图标左上角
      // size: new mapRef.value.AMap.Size(25, 25), // 图标的尺寸
      zIndex: 9,
    },
    {
      url: `${publicPath}/image/pipeline/warning-error.png`, // 图标地址
      // anchor: new mapRef.value.AMap.Pixel(0, 0), // 图标显示位置偏移量,基准点为图标左上角
      // size: new mapRef.value.AMap.Size(25, 25), // 图标的尺寸
      zIndex: 9,
    },
    {
      url: `${publicPath}/image/pipeline/warning-no.png`, // 图标地址
      // anchor: new mapRef.value.AMap.Pixel(0, 0), // 图标显示位置偏移量,基准点为图标左上角
      // size: new mapRef.value.AMap.Size(25, 25), // 图标的尺寸
      zIndex: 9,
    },
    {
      url: `${publicPath}/image/pipeline/underground-normal.png`, // 图标地址
      // anchor: new mapRef.value.AMap.Pixel(0, 0), // 图标显示位置偏移量,基准点为图标左上角
      // size: new mapRef.value.AMap.Size(25, 25), // 图标的尺寸
      zIndex: 9,
    },
    {
      url: `${publicPath}/image/pipeline/underground-error.png`, // 图标地址
      // anchor: new mapRef.value.AMap.Pixel(0, 0), // 图标显示位置偏移量,基准点为图标左上角
      // size: new mapRef.value.AMap.Size(25, 25), // 图标的尺寸
      zIndex: 9,
    },
    {
      url: `${publicPath}/image/pipeline/underground-no.png`, // 图标地址
      // anchor: new mapRef.value.AMap.Pixel(0, 0), // 图标显示位置偏移量,基准点为图标左上角
      // size: new mapRef.value.AMap.Size(25, 25), // 图标的尺寸
      zIndex: 9,
    },
  ]
  // // (1:正常,2:异常,3:离线)
  const styleDict = {
    '1-1': 0,
    '1-2': 1,
    '1-3': 2,
    '2-1': 3,
    '2-2': 4,
    '2-3': 5,
  } as { [key: string]: number }
  const treeData = selectTree.value.filter((item: string) => !item.includes('-'))
  const data = JSON.parse(JSON.stringify($props.data)).filter((item: any) => item.onlineState && item.lngGaode && item.latGaode && item.deviceTypeName)
    .filter((item: any) => legendShowData.value.includes(item.monitorState))
    .map((item: any) => ({
      ...item,
      treeType: item.deviceTypeName.includes('智能警示桩') ? '1' : '2',
      onlineState: item.onlineState === '0' ? '3' : item.onlineState
    }))
    .filter((item: any) => treeData.includes(item.treeType))
    .map((item: any) => ({
      lnglat: [item.lngGaode, item.latGaode],
      name: item.ledgerName,
      style: styleDict[`${item.treeType}-${item.onlineState}`],
      id: item.id,
      row: item,
    }))
  // if (data.length > 1000) {
  //   mapRef.value.addCluster(data)
  // }
  // else {
  mapRef.value.addMassMarks({
    path: data,
    zIndex: 111,
    zooms: [3, 20],
    style,
  })
  // }

  if (!legend) {
    if (pageIsFirst.value) {
      mapRef.value.map.setCenter([116.397428, 39.90923])
    }
    else {
      mapRef.value.map.setFitView()
    }
    mapRef.value.map.setZoom(10.5)
  }
}
// 点标记弹窗
const detail = ref()
const detailRef = ref()
const massMarksClick = (data: any) => {
  // console.log(detailRef.value, data)
  $emits('clickMarker', data.data.row)
  mapRef.value.map.setZoom(10.5)
  // 定义弹窗
  detail.value = new mapRef.value.AMap.InfoWindow({
    closeWhenClickMap: true, // 是否在鼠标点击地图后关闭信息窗体
    autoMove: true, // 是否自动调整窗体到视野内
    isCustom: true, // 自定义信息窗体
    content: detailRef.value.$el, // 窗体内容(vue组件)
    offset: new mapRef.value.AMap.Pixel(9, -5), // 偏移
  })
  // 打开信息窗口
  detail.value.open(data.map, data.event.data.lnglat)
  // 初始化信息窗口
  detailRef.value.initDialog({
    overlay: data.event.target,
    infoWindow: detail.value,
    info: data.event.data,
    map: mapRef.value.map,
  })
  setTimeout(() => {
    const center = JSON.parse(JSON.stringify(data.event.data.lnglat))
    center[1] = Number(center[1]) + 0.0002
    mapRef.value.map.setCenter(center)
  })
}

// 打开信息窗体
const openInfoDetail = (data: any) => {
  mapRef.value.map.setZoom(10.5)
  // 定义弹窗
  detail.value = new mapRef.value.AMap.InfoWindow({
    closeWhenClickMap: true, // 是否在鼠标点击地图后关闭信息窗体
    autoMove: true, // 是否自动调整窗体到视野内
    isCustom: true, // 自定义信息窗体
    content: detailRef.value.$el, // 窗体内容(vue组件)
    offset: new mapRef.value.AMap.Pixel(9, -5), // 偏移
  })
  // 打开信息窗口
  setTimeout(() => {
    detail.value.open(mapRef.value.map, data.lnglat)
    // 初始化信息窗口
    detailRef.value.initDialog({
      infoWindow: detail.value,
      info: data,
      map: mapRef.value.map,
    })
    const center = JSON.parse(JSON.stringify(data.lnglat))
    center[1] = Number(center[1]) + 0.0002
    mapRef.value.map.setCenter(center)
  })
}

function resetDraw() {
  mapRef.value.removeCluster()
  mapRef.value.removeMassMarks()
  if (detail.value) {
    detail.value.close()
  }
  if ($props.data) {
    // 清空标记点重新绘制
    drawMarker(true)
  }
}
// 地图绘制完毕
const completeMap = () => {
  // 绘制海量点
  resetDraw()
  setTimeout(() => {
    pageIsFirst.value = false
  }, 2000)
}
defineExpose({ openInfoDetail, drawMarker, resetDraw, mapRef, resetLegend })

const showMoreLegend = ref(false)
const moreLegendData = ref([
  {
    name: '智能警示桩',
    url: `${publicPath}/image/pipeline/warning-normal.png`,
    value: '1',
  },
  {
    name: '管网哨兵',
    url: `${publicPath}/image/pipeline/underground-normal.png`,
    value: '2',
  },
  {
    name: '',
    url: ``,
    value: '',
  },
])
</script>

<template>
  <div :style="`height: ${$props.height}px`" class="map-container">
    <!-- 设备信息窗体 -->
    <detail-info ref="detailRef" />
    <!-- 地图 -->
    <a-map ref="mapRef" :show-pieple-layer="true" @complete="completeMap" @massMarksClick="massMarksClick" />
    <!-- 图例 -->
    <div class="legend">
      <div v-for="item in legendData" :key="item.value" class="legend-item" @click="clickLegend(item.value)">
        <img v-show="legendShowData.includes(item.value)" class="img" :src="item.url">
        <img v-show="!legendShowData.includes(item.value)" class="img transparent" :src="item.url">
        <span style="margin-left: 5px;">{{ item.name }}</span>
      </div>
      <!-- 展开图标 -->
      <div v-if="!showMoreLegend" @click="showMoreLegend = true" class="expand-icon"><el-icon>
          <ArrowRightBold />
        </el-icon></div>
      <!-- 更多图标 -->
      <div v-if="showMoreLegend" class="more-legend-data">
        <div v-for="item in moreLegendData" :key="item.id" class="more-item">
          <img class="img" :src="item.url" v-show="item.url">
          <span style="margin-left: 5px;">{{ item.name }}</span>
        </div>
        <!-- 收起图标 -->
        <div v-if="showMoreLegend" @click="showMoreLegend = false" class="retract-icon"><el-icon>
            <ArrowLeftBold />
          </el-icon></div>
      </div>
    </div>
    <!-- 树形图例 -->
    <div class="point-info">
      <el-tree ref="treeRef" :data="pointTreeData" show-checkbox node-key="id" default-expand-all
        :default-checked-keys="[1, 2, 3]" :props="defaultProps" @check-change="checkTreeChange" />
    </div>
  </div>
</template>

<style lang="scss" scoped>
::v-deep(.el-tree) {
  background: rgb(255 255 255 / 80%);
}
.map-container {
  width: 100%;
  position: relative;
}

.point-info {
  position: absolute;
  width: 160px;
  top: 5px;
  left: 15px;
  z-index: 9;
}

.legend {
  background: rgb(255 255 255 / 80%);
  position: absolute;
  z-index: 1;
  left: 15px;
  bottom: 10px;
  padding: 0 10px;
  border-radius: 3px;
  padding-right: 20px;

  .expand-icon {
    position: absolute;
    top: 50%;
    right: 0;
    transform: translateY(-50%);
    font-size: 20px;
    font-weight: 700;
    margin-top: 3px;

    &:hover {
      cursor: pointer;
      color: #0d76d4;
    }
  }

  .retract-icon {
    position: absolute;
    // transform: translateY(-50%);
    top: 0;
    font-size: 20px;
    font-weight: 700;
    // margin-top: 3px;
    // top: 50%;
    right: -20px;
    // margin-top: 3px;
    background: rgb(255 255 255 / 80%);
    height: 103px;
    line-height: 108px;

    &:hover {
      cursor: pointer;
      color: #0d76d4;
    }
  }

  .more-legend-data {
    padding: 4px 0;
    display: flex;
    position: absolute;
    top: 0px;
    left: 80px;
    height: 103px;
    flex-wrap: wrap;
    flex-direction: column;
    justify-content: space-evenly;

    background: rgb(255 255 255 / 80%);
    width: 105px;

    .more-item {
      align-items: flex-start;
      // height: 33px;
      display: flex;
      align-items: center;
      color: #000;
      // margin: 10px 0;
      height: 30px;

      .img {
        width: 20px;
        height: 20px;
      }

      span {
        display: block;
        white-space: nowrap;
        margin-right: 5px;
      }

    }
  }
  .legend-item {
    margin: 10px 0;
    // font-weight: 700;
    display: flex;
    align-items: center;
    color: #000;

    .img {
      width: 20px;
      height: 20px;
    }

    .transparent {
      opacity: 0.25;
    }

    &:hover {
      cursor: pointer;
    }
  }
}
</style>