<!-- Description: 泄漏监测-地图 Author: 李亚光 Date: 2025-03-25 --> <script lang="ts" setup name="LeakageMap"> import AMap from '@/components/map/index.vue' import { toTreeList } from '@/utils/structure' import wellInfo from '@/views/home/well/components/detailInfoDialog.vue' import stationInfo from '@/views/home/station/station/components/detailInfoDialog.vue' import piepleInfo from '@/views/home/pipeline/components/detailInfoDialog.vue' 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] // console.log(publicPath, 'publicPath') // 展示图例数据 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', '3', '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' }, { id: '4', value: '3', 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 pageIsFirst = ref(true) // 绘制标记点 const drawMarker = (legend?: boolean) => { const style = [ { url: `${publicPath}/image/well/well-normal.png`, // 图标地址 // anchor: new mapRef.value.AMap.Pixel(0, 0), // 图标显示位置偏移量,基准点为图标左上角 // size: new mapRef.value.AMap.Size(25, 25), // 图标的尺寸 zIndex: 99, // 每种样式图标的叠加顺序,数字越大越靠前 }, { url: `${publicPath}/image/well/well-stop.png`, // 图标地址 // anchor: new mapRef.value.AMap.Pixel(0, 0), // 图标显示位置偏移量,基准点为图标左上角 // size: new mapRef.value.AMap.Size(25, 25), // 图标的尺寸 zIndex: 99, // 每种样式图标的叠加顺序,数字越大越靠前 }, { url: `${publicPath}/image/well/well-error.png`, // 图标地址 // anchor: new mapRef.value.AMap.Pixel(0, 0), // 图标显示位置偏移量,基准点为图标左上角 // size: new mapRef.value.AMap.Size(25, 25), // 图标的尺寸 zIndex: 99, // 每种样式图标的叠加顺序,数字越大越靠前 }, { url: `${publicPath}/image/station/station-normal.png`, // 图标地址 // anchor: new mapRef.value.AMap.Pixel(0, 0), // 图标显示位置偏移量,基准点为图标左上角 // size: new mapRef.value.AMap.Size(25, 25), // 图标的尺寸 zIndex: 99, // 每种样式图标的叠加顺序,数字越大越靠前 }, { url: `${publicPath}/image/station/station-stop.png`, // 图标地址 // anchor: new mapRef.value.AMap.Pixel(0, 0), // 图标显示位置偏移量,基准点为图标左上角 // size: new mapRef.value.AMap.Size(25, 25), // 图标的尺寸 zIndex: 99, // 每种样式图标的叠加顺序,数字越大越靠前 }, { url: `${publicPath}/image/station/station-error.png`, // 图标地址 // anchor: new mapRef.value.AMap.Pixel(0, 0), // 图标显示位置偏移量,基准点为图标左上角 // size: new mapRef.value.AMap.Size(25, 25), // 图标的尺寸 zIndex: 99, // 每种样式图标的叠加顺序,数字越大越靠前 }, { url: `${publicPath}/image/pipeline/pieple-normal.png`, // 图标地址 // anchor: new mapRef.value.AMap.Pixel(0, 0), // 图标显示位置偏移量,基准点为图标左上角 // size: new mapRef.value.AMap.Size(25, 25), // 图标的尺寸 zIndex: 99, // 每种样式图标的叠加顺序,数字越大越靠前 }, { url: `${publicPath}/image/pipeline/pieple-stop.png`, // 图标地址 // anchor: new mapRef.value.AMap.Pixel(0, 0), // 图标显示位置偏移量,基准点为图标左上角 // size: new mapRef.value.AMap.Size(25, 25), // 图标的尺寸 zIndex: 99, // 每种样式图标的叠加顺序,数字越大越靠前 }, { url: `${publicPath}/image/pipeline/pieple-error.png`, // 图标地址 // anchor: new mapRef.value.AMap.Pixel(0, 0), // 图标显示位置偏移量,基准点为图标左上角 // size: new mapRef.value.AMap.Size(25, 25), // 图标的尺寸 zIndex: 99, // 每种样式图标的叠加顺序,数字越大越靠前 }, ] // // (0:未监控,1:正常,2:异常,3:离线) const styleDict = { '1-1': 0, '1-2': 1, '1-3': 2, '2-1': 3, '2-2': 4, '2-3': 5, '3-1': 6, '3-2': 7, '3-3': 8, } 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.type && item.lngGaode && item.latGaode) .map((item:any) => ({ ...item, onlineState: item.onlineState === '0' ? '3' : item.onlineState })) .filter((item: any) => legendShowData.value.includes(item.onlineState)) .filter((item: any) => treeData.includes(item.type)) .map((item: any) => ({ lnglat: [item.lngGaode, item.latGaode], name: item.ledgerName, style: styleDict[`${item.type}-${item.onlineState}`], onlineState: item.onlineState, id: item.id, row: item, type: item.type })) console.log(data, 'data') const typeList = ['1', '2', '3'] typeList.forEach((item: string) => { const list = data.filter((citem: any) => citem.type === item) mapRef.value.addCluster(list, style) }) // mapRef.value.addCluster(data, 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 wellRef = ref() const stationRef = ref() const piepleRef = ref() const massMarksClick = (data: any) => { $emits('clickMarker', data.data.row) const infoDict = { 1: wellRef.value, 2: stationRef.value, 3: piepleRef.value } as { [key: string]: any } const dialog = infoDict[data.data.row.type] if (!dialog) { return } mapRef.value.map.setZoom(10.5) // 定义弹窗 detail.value = new mapRef.value.AMap.InfoWindow({ closeWhenClickMap: true, // 是否在鼠标点击地图后关闭信息窗体 autoMove: true, // 是否自动调整窗体到视野内 isCustom: true, // 自定义信息窗体 content: dialog.$el, // 窗体内容(vue组件) offset: new mapRef.value.AMap.Pixel(9, -5), // 偏移 }) // 打开信息窗口 detail.value.open(data.map, data.event.data.lnglat) // 初始化信息窗口 dialog.initDialog({ overlay: data.event.target, infoWindow: detail.value, info: data.event.data, map: mapRef.value.map, }) setTimeout(() => { // mapRef.value.map.setZoom(14) // mapRef.value.map.setFitView() // mapRef.value.map.setCenter(data.event.data.lnglat) 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) => { const infoDict = { 1: wellRef.value, 2: stationRef.value, 3: piepleRef.value } as { [key: string]: any } const dialog = infoDict[data.row.type] if (!dialog) { return } mapRef.value.map.setZoom(10.5) // 定义弹窗 detail.value = new mapRef.value.AMap.InfoWindow({ closeWhenClickMap: true, // 是否在鼠标点击地图后关闭信息窗体 autoMove: true, // 是否自动调整窗体到视野内 isCustom: true, // 自定义信息窗体 content: dialog.$el, // 窗体内容(vue组件) offset: new mapRef.value.AMap.Pixel(9, -5), // 偏移 }) // 打开信息窗口 setTimeout(() => { detail.value.open(mapRef.value.map, data.lnglat) // 初始化信息窗口 dialog.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 = () => { // console.log('地图绘制完毕') // 绘制海量点 resetDraw() setTimeout(() => { pageIsFirst.value = false }, 2000) } const setLegend = () => { selectTree.value = ['1', '2', '3', '1-1'] legendShowData.value = ['1', '2', '3'] treeRef.value.setChecked('1', true, true) } defineExpose({ openInfoDetail, drawMarker, resetDraw, mapRef, setLegend }) const showMoreLegend = ref(false) const moreLegendData = ref([ { name: '闸井', url: `${publicPath}/image/well/well-normal.png`, value: '1', }, { name: '场站', url: `${publicPath}/image/station/station-normal.png`, value: '2', }, { name: '管线', url: `${publicPath}/image/pipeline/pieple-normal.png`, value: '2', }, ]) </script> <template> <div :style="`height: ${$props.height}px`" class="map-container"> <!-- 设备信息窗体 --> <well-info ref="wellRef" /> <station-info ref="stationRef" /> <pieple-info ref="piepleRef" /> <!-- 地图 --> <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: 65px; .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>