index.vue 9.83 KB
<template>
  <div @mouseenter="handleMouseenter" @mouseleave="handleMouseleave" class="chart-amap" ref="vChartRef">
    <div v-show="showSearchBox" @click.stop="handleOpenSearchBox" class="search-box"></div>
    <search-box :modelShow="modelShow" @searchParams="handleSearchParams" @closeDrawer="handleCloseDrawer"></search-box>
  </div>
</template>

<script setup lang="ts">
import { ref, PropType, toRefs, watch, render, h } from 'vue'
import AMapLoader from '@amap/amap-jsapi-loader'
import { CreateComponentType } from '@/packages/index.d'
import { useChartDataFetch } from '@/hooks'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { MarkerEnum, ThemeEnum, dataExtraInfoType, dataJsonType, dataJsonMarkersType, filterDevice } from './config'
import { isArray } from '@/utils'
import djh from './images/djh.png'
import online from './images/online.png'
import lx1 from './images/lx1.png'
import positionImg from './images/marker/position1.png'
import { getDeviceActiveTime, getDeviceList } from '@/api/external/common/index'
import dayjs from 'dayjs'
import SearchBox from './components/SearchBox.vue'

const props = defineProps({
  chartConfig: {
    type: Object as PropType<CreateComponentType>,
    required: true
  }
})

const modelShow = ref(false)

const showSearchBox = ref(false)

let {
  amapKey,
  amapStyleKey,
  amapLon,
  amapLat,
  amapZindex,
  mapMarkerType,
  lang,
  amapStyleKeyCustom,
  features,
  viewMode,
  pitch,
  skyColor,
  marker,
  typeMarker,
  mpBorderConfig
} = toRefs(props.chartConfig.option.mapOptions)

//官方没有高德地图api的ts,所以类型全用的any
let mapIns: any = null
let markers: any = []
let AMapIns: any = null
const vChartRef = ref<HTMLElement>()

const initMap = (newData: any) => {
  // 初始化
  AMapLoader.load({
    key: amapKey.value, //api服务key--另外需要在public中使用安全密钥!!!
    version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
    plugins: ['AMap.PlaceSearch', 'AMap.AutoComplete'] // 需要使用的的插件列表
  })
    .then(AMap => {
      AMapIns = AMap
      mapIns = new AMap.Map(vChartRef.value, {
        resizeEnable: true,
        zoom: amapZindex.value, // 地图显示的缩放级别
        center: [amapLon.value, amapLat.value],
        lang: lang.value,
        features: features.value,
        pitch: pitch.value, // 地图俯仰角度,有效范围 0 度- 83 度
        skyColor: skyColor.value,
        viewMode: viewMode.value, // 地图模式
        willReadFrequently: true
      })
      dataHandle(props.chartConfig.option.dataset) //处理地图标点
      let satellite = new AMap.TileLayer.Satellite()
      let roadNet = new AMap.TileLayer.RoadNet()
      if (newData.amapStyleKey === ThemeEnum.WEIXIN) {
        mapIns.add([satellite, roadNet])
      } else {
        mapIns.remove([satellite, roadNet])
        mapIns.setMapStyle(
          `amap://styles/${amapStyleKeyCustom.value !== '' ? amapStyleKeyCustom.value : amapStyleKey.value}`
        )
      }
      mapIns.on('mouseout', () => {
        mapIns.clearInfoWindow()
      })
    })
    .catch(e => {
      console.error(e)
    })
}

//创建信息弹窗
const createInfoWindow = async (extraInfo: dataExtraInfoType) => {
  try {
    const { name, alias, organizationDTO, deviceState, deviceProfile, deviceInfo, tbDeviceId } = extraInfo
    if (tbDeviceId.startsWith('@')) return //假的模拟数据则终止
    const res = await getDeviceActiveTime(tbDeviceId) //查询设备最后离线时间
    let { lastUpdateTs } = res[0]
    const lastUpdateFormatTs = dayjs(lastUpdateTs).format('YYYY-MM-DD HH:mm:ss')
    //render方式渲染小组件里的边框组件
    const BorderInstance = await import(`../../../../Decorates/Borders/${mpBorderConfig.value.value}/index.vue`)
    const config = await import(`../../../../Decorates/Borders/${mpBorderConfig.value.value}/config.ts`)
    const BorderConfigInstance = new config.default()
    const id = Date.now().toString()
    setTimeout(() => {
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      render(h(BorderInstance.default, { chartConfig: BorderConfigInstance }), document.getElementById(id)!)
    }, 100)
    //
    return `
        <div id="${id}" style="width:25vw;height:29vh;position:relative;top:30px;">
         <div style="position: absolute;top: 52px;left: 105px;">
          <div style="display:flex;justify-content:space-between; margin:20px 0px;">
            <div style="width:12vw;text-overflow: ellipsis;overflow: hidden; word-break: break-all;white-space: nowrap;font-size:18px;font-weight:bold;color:white">${
              alias || name
            }</div>
            ${
              deviceState === 'INACTIVE'
                ? `<div style="display:flex;align-items:center;color:#999999"><img style="width:15px;height:15px;margin-right:5px" src="${djh}">待激活</div>`
                : deviceState === 'ONLINE'
                ? `<div style="display:flex;align-items:center;color:green">
                <img style="width:15px;height:15px;margin-right:5px"  src="${online}"  class="mr-1">在线</div>`
                : `<div style="display:flex;align-items:center;color:#F5222D"><img style="width:15px;height:15px;margin-right:5px" src="${lx1}">离线</div>`
            }
          </div>
          <div style="color:white;">所属组织:${organizationDTO.name}</div>
          <div style="margin-top:6px;color:white">接入协议:${deviceProfile.transportType}</div>
          <div style="margin-top:6px;color:white">
            设备位置:${!deviceInfo.address ? '该设备暂无地理位置' : deviceInfo.address}
          </div>
          <div style="margin-top:6px;color:white">
            ${
              deviceState === 'ONLINE' ? '在线' : deviceState === 'INACTIVE' ? '创建' : '离线'
            }时间:${lastUpdateFormatTs}
            </div>
            </div>
          </div>
          `
  } catch (e) {
    console.error(e)
  }
}

const handleMouseenter = () => (showSearchBox.value = true)

const handleMouseleave = () => (showSearchBox.value = false)

const handleOpenSearchBox = () => (modelShow.value = true)

const handleCloseDrawer = () => (modelShow.value = false)

const handleSearchParams = async (searchPage: any, params: any) => {
  try {
    const { items } = await getDeviceList(searchPage, params)
    const values = filterDevice(items)
    if (!values) return
    dataHandle(values)
  } finally {
    handleCloseDrawer()
  }
}

//地图鼠标hover
const mapClick = (markerInstance: any, markerItem: dataJsonMarkersType) => {
  markerInstance.setExtData({
    extraInfo: markerItem.extraInfo
  })
  markerInstance.on('mouseover', async (e: any) => {
    const { extraInfo } = e.target.getExtData()
    if (extraInfo.tbDeviceId.startsWith('@')) return //假的模拟数据则终止弹窗
    let infoWindow = new AMapIns.InfoWindow({
      content: await createInfoWindow(extraInfo),
      offset: new AMapIns.Pixel(3, -30)
    })
    infoWindow.open(mapIns, e.target.getPosition())
  })
  markerInstance.on('mouseout', () => {
    mapIns.clearInfoWindow()
  })
}

const dataHandle = (newData: dataJsonType) => {
  if (!mapIns && !AMapIns) {
    initMap(props.chartConfig.option)
    return
  }
  if (isArray(newData.markers)) {
    // 先清除旧标记
    mapIns.remove(markers)
    markers = []
    // 记录新标记
    if (mapMarkerType.value === MarkerEnum.MARKER) {
      newData.markers.forEach((markerItem: dataJsonMarkersType) => {
        const markerInstance = new AMapIns.Marker({
          position: [markerItem.position[0], markerItem.position[1]],
          offset: new AMapIns.Pixel(-13, -30),
          icon: typeMarker.value || positionImg
        })
        // markers.push(markerInstance) 原作者这种方式添加,属于JS API 1.4.8版本的
        // markerInstance.setMap(mapIns)
        mapIns.add(markerInstance)
        mapClick(markerInstance, markerItem)
      })
    } else if (mapMarkerType.value === MarkerEnum.CIRCLE_MARKER) {
      newData.markers.forEach((markerItem: dataJsonMarkersType) => {
        const markerInstance = new AMapIns.CircleMarker({
          center: [
            !markerItem.position[0] ? 0 : markerItem.position[0],
            !markerItem.position[1] ? 0 : markerItem.position[1]
          ],
          radius: markerItem.value, //圆圈半径大小
          ...marker.value
        })
        // markers.push(markerInstance) //原作者这种方式添加,属于JS API 1.4.8版本的
        // markerInstance.setMap(mapIns)
        mapIns.add(markerInstance)
        mapClick(markerInstance, markerItem) // circle点击无效
      })
    }
  }
}

const stopWatch = watch(
  () => props.chartConfig.option.mapOptions,
  option => {
    initMap(option)
  },
  {
    immediate: true,
    deep: true
  }
)

watch(
  () => props.chartConfig.option.dataset,
  newData => {
    try {
      dataHandle(newData)
    } catch (error) {
      console.log(error)
    }
  },
  {
    deep: false
  }
)

// 预览
useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
  stopWatch()
  dataHandle(newData)
})
</script>

<style>
.amap-info-outer {
  background-color: rgba(0, 0, 0, 0) !important;
}
.amap-info-close {
  display: none !important;
}
.amap-info-content {
  overflow: hidden !important;
}
.amap-info-content .go-border-box {
  position: absolute;
  transform: scale(0.7);
  top: -10px;
  /* opacity: 0,8; */
  /* background-color: rgba(0, 0, 0, 0.1); */
  background-color:rgba(255,255,255,0.1)
}
.amap-info-content .go-border-box svg {
  /* background-color: rgba(0, 0, 0, 0.1); */
  /* background-color: rgba(255, 255, 255, 0.8); */
}
</style>
<style lang="scss" scoped>
.chart-amap {
  position: relative;
  .search-box {
    cursor: pointer;
    position: absolute;
    right: -25px;
    top: 15px;
    z-index: 9999;
    width: 50px;
    height: 50px;
    border-radius: 50%;
    background-color: rgba(255, 255, 255, 0.2);
  }
}
</style>