|
|
@@ -1,25 +1,25 @@
|
|
|
<template>
|
|
|
<div ref="mapContainer" class="map-container"></div>
|
|
|
- <div class="map-reset-btn">
|
|
|
- <Button type="primary" @click="reset"> 重置 </Button>
|
|
|
+ <div v-if="appStore.getSimpleMapParams.mineCode" class="map-reset-btn">
|
|
|
+ <Button type="primary" @click="reset">重置</Button>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
import { ref, onMounted, onUnmounted } from 'vue';
|
|
|
- // 引入 Leaflet
|
|
|
import L from 'leaflet';
|
|
|
import 'leaflet/dist/leaflet.css';
|
|
|
- import { getGoafAlarmLevel, getMarkers } from '/@/api/sys/map';
|
|
|
- import { StatusColorEnum } from '/@/enums/jeecgEnum';
|
|
|
import { Button } from 'ant-design-vue';
|
|
|
- // Ant Design Vue 图标
|
|
|
- // --- 1. 组件引用和状态定义 ---
|
|
|
+ import { getGoafAlarmLevel, getMarkers } from '/@/api/sys/map';
|
|
|
+ import { DEFAULT_LAYER_ID, regionColorMap, tileLayerConfigs, generatePopupContent, getRootMarkerIcon } from './simpleMap.data';
|
|
|
+ import { useAppStore } from '/@/store/modules/app';
|
|
|
+
|
|
|
+ const appStore = useAppStore();
|
|
|
const mapContainer = ref(null);
|
|
|
+ let map: L.Map | null = null;
|
|
|
|
|
|
- const DEFAULT_LAYER_ID = 'default';
|
|
|
- // 邮政编码,GeoJSON中存有完整编码,下面是去掉了后两位00的编码
|
|
|
- const layerMap = new Map<string, L.Layer>([
|
|
|
+ // 图层组 Map(基于所有区域编码初始化)
|
|
|
+ const layerMap = new Map<string, L.LayerGroup>([
|
|
|
[DEFAULT_LAYER_ID, L.featureGroup()],
|
|
|
// 西安
|
|
|
['1566101', L.featureGroup()],
|
|
|
@@ -42,289 +42,131 @@
|
|
|
// 商洛
|
|
|
['1566111', L.featureGroup()],
|
|
|
]);
|
|
|
- const colorMap = new Map<string, string>([
|
|
|
- ['1566101', '#14baff'],
|
|
|
- ['1566102', '#14baff'],
|
|
|
- ['1566103', '#ffff12'],
|
|
|
- ['1566104', '#ffff12'],
|
|
|
- ['1566105', '#ffab12'],
|
|
|
- ['1566106', '#ffab12'],
|
|
|
- ['1566107', '#fe1414'],
|
|
|
- ['1566108', '#fe1414'],
|
|
|
- ['1566109', '#fe1414'],
|
|
|
- ['1566111', '#fe1414'],
|
|
|
- ['810321182c6a40fa92d5acccdba34ce3', '#ffff12'],
|
|
|
- ['63afd272d0e24c7e9c1475c6498f0c80', '#ffab12'],
|
|
|
- ]);
|
|
|
-
|
|
|
- let map: typeof L = null; // Leaflet 地图实例
|
|
|
|
|
|
- // --- 2. 配置项声明 ---
|
|
|
- const tileLayers: any[] = [
|
|
|
- {
|
|
|
- name: '基准瓦片',
|
|
|
- layer: null,
|
|
|
- url: 'https://shaanxizhxx.chinamine-safety.gov.cn/zh1/8/33/17.png',
|
|
|
- // url: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png',
|
|
|
- addByDefault: false,
|
|
|
- options: {
|
|
|
- maxZoom: 18,
|
|
|
- // attribution: '',
|
|
|
- attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
|
|
- // crossOrigin: true,
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- name: '卫星图',
|
|
|
- layer: null,
|
|
|
- url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
|
|
|
- addByDefault: false,
|
|
|
- options: {
|
|
|
- maxZoom: 18,
|
|
|
- attribution: '© Esri',
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- name: '高德地图',
|
|
|
- layer: null,
|
|
|
- url: 'https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
|
|
|
- addByDefault: true,
|
|
|
- options: {
|
|
|
- className: 'light-blue-theme',
|
|
|
- maxZoom: 18,
|
|
|
- subdomains: ['1', '2', '3', '4'], // 高德底图服务器集群
|
|
|
- attribution: '© 高德地图', // 版权声明(必填,遵守使用规范)
|
|
|
- },
|
|
|
- },
|
|
|
- ];
|
|
|
-
|
|
|
- // --- 3. 地图初始化 ---
|
|
|
- onMounted(() => {
|
|
|
+ onMounted(async () => {
|
|
|
if (!mapContainer.value) return;
|
|
|
|
|
|
- // 创建地图实例,设置中心和初始缩放级别
|
|
|
+ reset();
|
|
|
+
|
|
|
map = L.map(mapContainer.value, {
|
|
|
- center: [35.841, 108.94], // 西安市中心坐标 [纬度, 经度]
|
|
|
- zoom: 7, // 初始缩放级别,适合城市级别查看
|
|
|
- zoomControl: false, // 显示缩放控件
|
|
|
- attributionControl: false, // 显示属性控件
|
|
|
+ center: [35.841, 108.94],
|
|
|
+ zoom: 7,
|
|
|
+ zoomControl: false,
|
|
|
+ attributionControl: false,
|
|
|
});
|
|
|
|
|
|
- // 初始化所有瓦片图层
|
|
|
initTileLayers();
|
|
|
- // 请求矿区数据并初始化标记点图层
|
|
|
- initMarkers();
|
|
|
- // 初始化陕西省GeoJSON图层
|
|
|
- initGeoJSON();
|
|
|
+ await initMarkers();
|
|
|
+ await initGeoJSON();
|
|
|
|
|
|
- layerMap.get(DEFAULT_LAYER_ID).addTo(map);
|
|
|
+ layerMap.get(DEFAULT_LAYER_ID)?.addTo(map);
|
|
|
});
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
- map && map.remove();
|
|
|
+ map?.remove();
|
|
|
});
|
|
|
|
|
|
- // 初始化瓦片图层
|
|
|
+ /** 初始化瓦片图层 */
|
|
|
function initTileLayers() {
|
|
|
- tileLayers.forEach((config) => {
|
|
|
- config.layer = L.tileLayer(config.url, config.options);
|
|
|
-
|
|
|
+ tileLayerConfigs.forEach((config) => {
|
|
|
+ const layer = L.tileLayer(config.url, config.options);
|
|
|
if (config.addByDefault) {
|
|
|
- config.layer.addTo(map);
|
|
|
+ layer.addTo(map!);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ /** 加载 GeoJSON 并添加到对应图层组 */
|
|
|
async function initGeoJSON() {
|
|
|
const response = await fetch('/js/shanxi.geo.json');
|
|
|
const ShanXiGeoJSON = await response.json();
|
|
|
|
|
|
layerMap.forEach((group, code) => {
|
|
|
- const features = ShanXiGeoJSON.features.filter((feature) => {
|
|
|
+ const features = ShanXiGeoJSON.features.filter((feature: any) => {
|
|
|
if (code === DEFAULT_LAYER_ID) return true;
|
|
|
return feature.properties.gb.startsWith(code);
|
|
|
});
|
|
|
|
|
|
- const GeoJSON = L.geoJSON(features, {
|
|
|
+ const geoJsonLayer = L.geoJSON(features, {
|
|
|
style(feature) {
|
|
|
const gb = feature.properties.gb.slice(0, -2);
|
|
|
return {
|
|
|
- fillColor: colorMap.get(gb) || '#14baff',
|
|
|
+ fillColor: regionColorMap[gb] || '#14baff',
|
|
|
fillOpacity: 0.2,
|
|
|
weight: 2,
|
|
|
opacity: 1,
|
|
|
color: '#ff9100',
|
|
|
};
|
|
|
},
|
|
|
- onEachFeature(__, layer) {
|
|
|
+ onEachFeature(_, layer) {
|
|
|
layer.on({
|
|
|
mouseover: (e) => {
|
|
|
- const layer = e.target;
|
|
|
-
|
|
|
- layer.setStyle({
|
|
|
- weight: 2,
|
|
|
- fillOpacity: 0.6,
|
|
|
- });
|
|
|
-
|
|
|
- layer.bringToFront();
|
|
|
+ e.target.setStyle({ weight: 2, fillOpacity: 0.6 });
|
|
|
+ e.target.bringToFront();
|
|
|
},
|
|
|
mouseout: (e) => {
|
|
|
- GeoJSON.resetStyle(e.target);
|
|
|
+ geoJsonLayer.resetStyle(e.target);
|
|
|
},
|
|
|
- // click: zoomToFeature,
|
|
|
});
|
|
|
},
|
|
|
});
|
|
|
|
|
|
- // layerMap.set(code, GeoJSON);
|
|
|
- GeoJSON.addTo(group);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- /** 生成一个标准的标记点,通过传入的数据,请求详情信息并生成监测详情框 */
|
|
|
- async function generateStandardMarker(marker: any, options: any) {
|
|
|
- const linkArr = ['断线', '正常', '标校'];
|
|
|
- const statusArr = ['暂无信息', '低风险', '一般风险', '较高风险', '高风险'];
|
|
|
- const colorArr = [StatusColorEnum.red, StatusColorEnum.blue, StatusColorEnum.gold, StatusColorEnum.purple, StatusColorEnum.red];
|
|
|
-
|
|
|
- const tbodyInnerHtml = await getGoafAlarmLevel({ mineCode: marker.mineCode }).then(({ goafDataList, alarmLevel, goafNum }) => {
|
|
|
- marker.alarmLevel = alarmLevel;
|
|
|
- marker.goafNum = goafNum;
|
|
|
-
|
|
|
- return goafDataList.reduce((htmlstr, item, index) => {
|
|
|
- htmlstr += `
|
|
|
- <tr>
|
|
|
- <td>${index + 1}</td>
|
|
|
- <td>${item.devicePos}</td>
|
|
|
- <td style="color: ${colorArr[item.alarmLevel || 0]}">${statusArr[item.alarmLevel || 0]}</td>
|
|
|
- <td style="color: ${colorArr[item.linkStatus || 0]}">${linkArr[item.linkStatus || 0]}</td>
|
|
|
- </tr>
|
|
|
- `;
|
|
|
-
|
|
|
- return htmlstr;
|
|
|
- }, ``);
|
|
|
+ geoJsonLayer.addTo(group);
|
|
|
});
|
|
|
-
|
|
|
- return L.marker([marker.lat, marker.lng], options).bindPopup(
|
|
|
- `
|
|
|
- <div class="leaflet-popup-content__title">${marker.mineName}</div>
|
|
|
- <div class="leaflet-popup-content__divider"></div>
|
|
|
- <div class="leaflet-popup-content__board mb-5px">
|
|
|
- <div class="mr-5px">所属执法处</div>
|
|
|
- <div>${marker.managementName}</div>
|
|
|
- </div>
|
|
|
- <div class="leaflet-popup-content__board mb-5px">
|
|
|
- <div class="mr-5px">采空区密闭数量</div>
|
|
|
- <div>${marker.goafNum}</div>
|
|
|
- </div>
|
|
|
- <div class="leaflet-popup-content__board">
|
|
|
- <div class="mr-5px">密闭整体风险等级</div>
|
|
|
- <div style="color: ${colorArr[marker.alarmLevel]}">${statusArr[marker.alarmLevel]}</div>
|
|
|
- </div>
|
|
|
- <div class="leaflet-popup-content__divider"></div>
|
|
|
- <table id="${marker.id}" class="leaflet-popup-content__table w-full mt-10px">
|
|
|
- <thead>
|
|
|
- <tr>
|
|
|
- <th>序号</th>
|
|
|
- <th>采空区名称</th>
|
|
|
- <th>风险等级</th>
|
|
|
- <th>通讯状态</th>
|
|
|
- </tr>
|
|
|
- </thead>
|
|
|
- <tbody>
|
|
|
- ${tbodyInnerHtml.length ? tbodyInnerHtml : `<tr><td colspan="4">暂无数据</td></tr>`}
|
|
|
- </tbody>
|
|
|
- </table>
|
|
|
- `
|
|
|
- );
|
|
|
}
|
|
|
|
|
|
- function generateRootMarker(marker: any, options: any) {
|
|
|
- const size = 40;
|
|
|
- const icon = L.divIcon({
|
|
|
- className: 'numbered-marker',
|
|
|
- html: `
|
|
|
- <svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" style="overflow: visible;">
|
|
|
- <!-- 圆形背景 -->
|
|
|
- <circle
|
|
|
- cx="${size / 2}"
|
|
|
- cy="${size / 2}"
|
|
|
- r="${size / 2 - 2}"
|
|
|
- fill="${colorMap.get(marker.gb)}"
|
|
|
- />
|
|
|
- <!-- 数字文本 -->
|
|
|
- <text
|
|
|
- x="${size / 2}"
|
|
|
- y="${size / 2}"
|
|
|
- text-anchor="middle"
|
|
|
- dominant-baseline="middle"
|
|
|
- fill="gray"
|
|
|
- font-size="16"
|
|
|
- font-weight="bold"
|
|
|
- font-family="Arial, sans-serif"
|
|
|
- >${marker.children.length}</text>
|
|
|
- </svg>
|
|
|
- `,
|
|
|
- iconSize: [size, size],
|
|
|
- iconAnchor: [size / 2, size / 2],
|
|
|
- popupAnchor: [0, -size / 2],
|
|
|
- });
|
|
|
-
|
|
|
- return L.marker([marker.lat, marker.lng], {
|
|
|
- icon,
|
|
|
- color: colorMap.get(marker.gb),
|
|
|
- fillColor: colorMap.get(marker.gb),
|
|
|
- ...options,
|
|
|
- }).on('click', () => {
|
|
|
- layerMap.forEach((group, code) => {
|
|
|
- map.removeLayer(group);
|
|
|
-
|
|
|
- if (code === marker.gb) {
|
|
|
- map.addLayer(group);
|
|
|
- console.log('debug bounds', group, marker);
|
|
|
- map.flyToBounds(group.getBounds());
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- /** 请求矿区数据并初始化标记点 */
|
|
|
+ /** 初始化所有标记点 */
|
|
|
async function initMarkers() {
|
|
|
const records = await getMarkers({ pageNo: 0, paseSize: 9999 });
|
|
|
- const details: Promise<any>[] = [];
|
|
|
+ const promises: Promise<void>[] = [];
|
|
|
+
|
|
|
records.forEach((item) => {
|
|
|
- generateRootMarker(item, {
|
|
|
+ // 创建根标记点
|
|
|
+ const iconConfig = getRootMarkerIcon(40, item.children.length, regionColorMap[item.gb]);
|
|
|
+ const marker = L.marker([item.lat, item.lng], {
|
|
|
+ icon: L.divIcon(iconConfig),
|
|
|
riseOnHover: true,
|
|
|
- }).addTo(layerMap.get(DEFAULT_LAYER_ID));
|
|
|
+ }).on('click', () => {
|
|
|
+ layerMap.forEach((group, code) => {
|
|
|
+ map?.removeLayer(group);
|
|
|
+ if (code === item.gb) {
|
|
|
+ map?.addLayer(group);
|
|
|
+ map?.flyToBounds(group.getBounds());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ appStore.setSimpleMapParams({ mineCode: item.mineCode });
|
|
|
+ });
|
|
|
+ marker.addTo(layerMap.get(DEFAULT_LAYER_ID)!);
|
|
|
|
|
|
+ // 创建子标记点
|
|
|
if (!item.children.length) return;
|
|
|
-
|
|
|
- item.children.forEach((child) => {
|
|
|
- details.push(
|
|
|
- generateStandardMarker(child, {
|
|
|
- riseOnHover: false,
|
|
|
- }).then((marker) => {
|
|
|
+ item.children.forEach((child: any) => {
|
|
|
+ promises.push(
|
|
|
+ getGoafAlarmLevel({ mineCode: child.mineCode }).then((result) => {
|
|
|
+ const popupContent = generatePopupContent(result);
|
|
|
+ const childMarker = L.marker([child.lat, child.lng], { riseOnHover: false }).bindPopup(popupContent);
|
|
|
if (layerMap.has(child.gb)) {
|
|
|
- marker.addTo(layerMap.get(child.gb));
|
|
|
+ childMarker.addTo(layerMap.get(child.gb)!);
|
|
|
}
|
|
|
})
|
|
|
);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
- await Promise.all(details);
|
|
|
+ await Promise.all(promises);
|
|
|
}
|
|
|
|
|
|
+ /** 重置视图到默认图层 */
|
|
|
function reset() {
|
|
|
- layerMap.forEach((layer, code) => {
|
|
|
- map.removeLayer(layer);
|
|
|
-
|
|
|
+ layerMap.forEach((group, code) => {
|
|
|
+ map?.removeLayer(group);
|
|
|
if (code === DEFAULT_LAYER_ID) {
|
|
|
- map.addLayer(layer);
|
|
|
-
|
|
|
- map.flyToBounds(layer.getBounds());
|
|
|
+ map?.addLayer(group);
|
|
|
+ map?.flyToBounds(group.getBounds());
|
|
|
}
|
|
|
});
|
|
|
+
|
|
|
+ appStore.setSimpleMapParams({});
|
|
|
}
|
|
|
</script>
|
|
|
|