Newer
Older
BJgas-metering-front / src / views / dashboard / map / index.vue
liyaguang on 19 May 2023 12 KB fix(*): 解决语音通话没声音
<!--
  Description: 高德地图
  Author: 李亚光
  Date: 2023-04-21
 -->
<script lang="ts" setup name="GuadMap">
import AMapLoader from '@amap/amap-jsapi-loader'
import detailInfo from './deviceDetail.vue'
import { getSceneListPage } from '@/api/scene'
import { getAlarmList, getDeviceList, getPersonList } from '@/api/dashboard'
const $router = useRouter()
const loading = ref(true)
// 设置安全密钥
window._AMapSecurityConfig = {
  securityJsCode: '56bf9671d4b3517d294caec4751889a1', // 后期需替换
}
const activeNames = ref(['0'])
// 现场列表
const sceneList = ref<any[]>([])
// 报警列表
const alarmList = ref<any[]>([])
// 设备列表
const deviceList = ref<any[]>([])
// 设备坐标点列表
const deviceMarkerList = ref<any[]>([])
// 安全树设备连点左边点列表
const deviceLineList = ref<any[]>([])
// 人员列表
const personList = ref<any[]>([])
// 人员坐标点列表
const personMarkerList = ref<any[]>([])
// 标记点实例对象列表(用于清除)
const markerList = ref<any[]>([])
// 折线实例对象(用于清除)
const lineList = ref<any[]>([])
// 地图实例
const map = shallowRef()
const AMap1 = ref()
// 设备和人员详情信息窗体
const detailRef = ref()
const detail = ref()
const timer = ref()
const publicPath = import.meta.env.BASE_URL
console.log(publicPath, 'publicPath')
// 初始化地图
const initMap = () => {
  AMapLoader.load({
    key: '40849e82b4e33f5255b17372520c954d', // 后期需替换
    version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
    plugins: ['AMap.Scale', 'AMap.MouseTool'], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
  })
    .then((AMap) => {
      AMap1.value = AMap
      // 初始化地图
      map.value = new AMap.Map('map', {
        viewMode: '2D', //  是否为3D地图模式
        zoom: 17, // 初始化地图级别
        resizeEnable: true,
        center: [116.26759100, 39.91563500],
      })
      // 地图图块加载完成后触发
      map.value.on('complete', () => {
        //  修改loading状态
        console.log('地图加载完成')
        loading.value = false
      })
    })
    .catch((e) => {
      console.log('地图加载失败')
    })
}
// 获取作业现场列表
const fetchSceneList = () => {
  getSceneListPage({ limit: 9999, offset: 1 }).then((res) => {
    sceneList.value = res.data.rows
  })
}
const alarmListRef = ref()
const projectId1 = ref()
fetchSceneList()

// 获取设备列表
const fetchDeviceList = (id: string) => {
  getDeviceList(id).then((res) => {
    console.log(res.data, '设备列表')
    // 过滤整理数据
    const data = res.data.filter((device: any) => device.lng && device.lat)
    deviceList.value = data
    deviceMarkerList.value = data.map((item: any) => ([Number(item.lng), Number(item.lat)]))
    //  地图AMap参数
    const AMap = AMap1.value
    // 绘制标记点
    for (let i = 0; i < deviceMarkerList.value.length; i++) {
      const alarmFlag = deviceList.value[i].alarmFlag ? 'alarm' : deviceList.value[i].deviceType
      const content = `<img src="${publicPath}icon/${alarmFlag}.png" class="img-map-marker">`
      const marker = new AMap.Marker({
        position: deviceMarkerList.value[i], // 经纬度对象,也可以是经纬度构成的一维数组[116.39, 39.9]
        map: map.value,
        content,
        offset: new AMap.Pixel(-10, -15),
      })
      detail.value = new AMap.InfoWindow({
        isCustom: true, // 自定义信息窗体
        content: detailRef.value.$el, // 窗体内容(vue组件)
        offset: new AMap.Pixel(9, -5), // 偏移
      })
      marker.on('click', (e) => {
        // 打开信息窗口
        detail.value.open(map.value, e.lnglat)
        // 初始化信息窗口
        detailRef.value.initDialog({
          overlay: e.target,
          infoWindow: detail.value,
          info: deviceList.value[i],
          flag: 'device',
        })
      })
      markerList.value.push(marker)
      map.value.add([marker])
      // 自动适应显示想显示的范围区域
      map.value.setFitView()
    }
    // 绘制连线(必须四个安全树设备才绘制连线)
    deviceLineList.value = data.filter((device: any) => device.deviceType === '1').map((item: any) => (new AMap.LngLat(Number(item.lng), Number(item.lat))))
    if (deviceLineList.value.length === 4) {
      deviceLineList.value.push(deviceLineList.value[0])
      const polyline = new AMap.Polyline({
        path: deviceLineList.value,
        borderWeight: 2, // 线条宽度,默认为 1
        strokeColor: '#3969B9', // 线条颜色
        lineJoin: 'round', // 折线拐点连接处样式
        strokeStyle: 'solid',
      })
      lineList.value = polyline
      polyline.setMap(map.value)
      map.value.add(polyline)
    }
  })
}
// 获取人员列表
const fetchPersonList = (id: string) => {
  getPersonList(id).then((res) => {
    // 过滤整理数据
    const data = res.data.filter((person: any) => person.lng && person.lat)
    personList.value = data
    personMarkerList.value = data.map((item: any) => ([Number(item.lng), Number(item.lat)]))
    //  地图AMap参数
    const AMap = AMap1.value
    // 绘制标记点
    for (let i = 0; i < personMarkerList.value.length; i++) {
      const alarmFlag = personMarkerList.value[i].alarmFlag ? 'alarm' : 'person'
      const content = `<img src="${publicPath}icon/${alarmFlag}.png" class="img-map-marker">`
      const marker = new AMap.Marker({
        position: personMarkerList.value[i], // 经纬度对象,也可以是经纬度构成的一维数组[116.39, 39.9]
        map: map.value,
        content,
        offset: new AMap.Pixel(-10, -10),
      })
      detail.value = new AMap.InfoWindow({
        isCustom: true, // 自定义信息窗体
        content: detailRef.value.$el, // 窗体内容(vue组件)
        offset: new AMap.Pixel(9, -5), // 偏移
      })
      marker.on('click', (e) => {
        // 打开信息窗口
        detail.value.open(map.value, e.lnglat)
        // 初始化信息窗口
        detailRef.value.initDialog({
          overlay: e.target,
          infoWindow: detail.value,
          info: personList.value[i],
          flag: 'person',
        })
      })
      markerList.value.push(marker)
      map.value.add([marker])
      // 自动适应显示想显示的范围区域
      // map.value.setFitView()
    }
  })
}
// 开启定时器
const openTimer = () => {
  timer.value = setInterval(() => {
    if (projectId1.value) {
      getAlarmList(projectId1.value).then((res) => {
        if (JSON.stringify(alarmList.value) !== JSON.stringify(res.data)) {
          alarmList.value = res.data
          // 清除覆盖物并重新获取数据
          map.value.clearMap()
          fetchDeviceList(projectId1.value)
        }
      })
    }
  }, 1000 * 60)
}
// 获取报警列表
const fetchAlarmList = (id: string) => {
  getAlarmList(id).then((res) => {
    alarmList.value = res.data
    // 判断有无报警 有则自动展开列表
    if (alarmList.value.length) {
      if (!activeNames.value.includes('2')) {
        activeNames.value.push('2')
      }
    }
  })
}
// 当前索引(用于现场列表高亮显示)
const currenIndex = ref()
// 选择作业现场
const checkScene = (index: number) => {
  currenIndex.value = index
  // 先判断标记点和折线 如果有则清除
  if (markerList.value.length || lineList.value) {
    map.value.clearMap()
  }
  // 发送请求获取对应作业现场的数据
  const projectId = (sceneList.value[index] as any).id
  projectId1.value = projectId
  fetchAlarmList(projectId)
  fetchDeviceList(projectId)
  fetchPersonList(projectId)
}
// 报警详情
const alarmDetail = (type: string) => {
  // 跳转到报警管理页面
  $router.push({
    path: '/alarm/alarmlist',
    query: { type },
  })
}
onMounted(() => {
  loading.value = true
  initMap()
  openTimer()
})
onBeforeUnmount(() => {
  if (map.value) {
    map.value.destroy()
  }
  if (timer.value) {
    clearInterval(timer.value)
    timer.value = null
  }
})
</script>

<template>
  <div class="container">
    <!-- 地图 -->
    <div id="map" v-loading="loading" />
    <!-- 报警列表下拉 -->
    <div class="collapse">
      <el-collapse v-model="activeNames">
        <el-collapse-item ref="alarmListRef" title="设备告警" :class="{ alarm: alarmList.length ? true : false }" name="2">
          <div v-if="!alarmList.length" class="message-content" style="text-align: center;">
            暂无数据或未选择作业现场
          </div>
          <div v-else class="alarm-container">
            <div
              v-for="(alarm, index) in alarmList" :key="index" class="alarm-item"
              @click="alarmDetail(alarm.alarmType)"
            >
              <div class="item-count">
                {{ alarm.alarmCount }}
              </div>
              <div class="item-type" :title="alarm.alarmTypeName">
                {{ alarm.alarmTypeName }}
              </div>
            </div>
          </div>
        </el-collapse-item>
      </el-collapse>
    </div>
    <!-- 现场列表 -->
    <div class="collapse1">
      <el-collapse v-model="activeNames">
        <el-collapse-item title="作业现场列表" name="1">
          <div v-if="!sceneList.length" class="message-content" style="text-align: center;">
            暂无数据
          </div>
          <el-scrollbar v-else max-height="400px">
            <div
              v-for="(message, index) of sceneList" :key="index" class="message-item"
              :class="{ current: currenIndex === index ? true : false }"
            >
              <div class="message-content" @click.stop.capture="checkScene(index)">
                <div class="message">
                  {{ index + 1 }}.{{ message.workTitle }}
                </div>
              </div>
            </div>
          </el-scrollbar>
        </el-collapse-item>
      </el-collapse>
    </div>
    <!-- 设备信息窗体 -->
    <detail-info ref="detailRef" />
  </div>
</template>

<style lang="scss" scoped>
.container {
  height: 100%;
  width: 100%;

  .current {
    background-color: rgba($color: #ccc, $alpha: 50%);
  }

  .alarm {
    :deep(.el-collapse-item__header) {
      color: red;
    }
  }

  .alarm-container {
    background-color: transparent;
    display: flex;
    // justify-content: space-around;
    flex-wrap: wrap;
    padding: 5px 0;

    .alarm-item {
      width: 33%;
      border: 2px solid #3969b9;
      margin-top: 5px;
      margin-left: 8px;
      text-align: center;

      &:hover {
        cursor: pointer;
        background-color: rgba($color: #ccc, $alpha: 50%);
      }

      .item-count,
      .item-type {
        font-weight: 700;
      }

      .item-count {
        color: #ff001e;
      }

      .item-type {
        // width: 100%;
        display: block;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }
    }
  }

  .collapse {
    position: absolute;
    top: 15px;
    right: 5px;
  }

  .collapse1 {
    position: absolute;
    top: 15px;
    left: 0;
  }

  :deep(.el-collapse-item__wrap) {
    background-color: rgba($color: #fff, $alpha: 100%);
  }

  :deep(.el-collapse-item) {
    width: 260px;
  }

  :deep(.el-collapse-item__header) {
    font-size: 16px;
    font-weight: 700;
    text-align: center !important;
    // padding-left: 70px;
    // background-color: transparent;
  }

  :deep(.el-collapse-item__content) {
    background-color: transparent !important;
    padding-bottom: 0;
  }

  .message-item {
    display: flex;
    align-items: center;
    border-bottom: 1px solid #ddd;
    padding: 8px 5px;
    cursor: pointer;
    position: relative;

    &:hover {
      background-color: rgba($color: #ccc, $alpha: 50%);
    }
  }

  .message-content {
    flex: 1;
    font-size: 14px;
    font-weight: 500;

    .time {
      margin-top: 3px;
      font-size: 12px;
      color: #888;
    }

    .message {
      font-weight: 700;
    }
  }
}

#map {
  overflow: hidden;
  width: 100%;
  height: 100%;
  margin: 0;
  font-family: "微软雅黑";
}
</style>

<style>
/* 隐藏高德logo  */
.amap-logo {
  display: none !important;
}

/* 隐藏高德版权  */
.amap-copyright {
  display: none !important;
}

.img-map-marker {
  width: 20px;
  height: 20px;
}
</style>