<!-- 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>