|
|
@@ -1,5 +1,8 @@
|
|
|
<template>
|
|
|
<div ref="mapContainer" class="map-container"></div>
|
|
|
+ <div class="map-reset-btn">
|
|
|
+ <Button type="primary" @click="reset"> 重置 </Button>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
@@ -7,17 +10,58 @@
|
|
|
// 引入 Leaflet
|
|
|
import L from 'leaflet';
|
|
|
import 'leaflet/dist/leaflet.css';
|
|
|
- import { list, getGoafAlarmLevel } from '/@/api/sys/map';
|
|
|
+ import { getGoafAlarmLevel, getMarkers } from '/@/api/sys/map';
|
|
|
import { StatusColorEnum } from '/@/enums/jeecgEnum';
|
|
|
+ import { Button } from 'ant-design-vue';
|
|
|
// Ant Design Vue 图标
|
|
|
-
|
|
|
// --- 1. 组件引用和状态定义 ---
|
|
|
const mapContainer = ref(null);
|
|
|
+
|
|
|
+ const DEFAULT_LAYER_ID = 'default';
|
|
|
+ // 邮政编码,GeoJSON中存有完整编码,下面是去掉了后两位00的编码
|
|
|
+ const layerMap = new Map<string, L.Layer>([
|
|
|
+ [DEFAULT_LAYER_ID, L.featureGroup()],
|
|
|
+ // 西安
|
|
|
+ ['1566101', L.featureGroup()],
|
|
|
+ // 铜川
|
|
|
+ ['1566102', L.featureGroup()],
|
|
|
+ // 宝鸡
|
|
|
+ ['1566103', L.featureGroup()],
|
|
|
+ // 咸阳
|
|
|
+ ['1566104', L.featureGroup()],
|
|
|
+ // 渭南
|
|
|
+ ['1566105', L.featureGroup()],
|
|
|
+ // 延安
|
|
|
+ ['1566106', L.featureGroup()],
|
|
|
+ // 汉中
|
|
|
+ ['1566107', L.featureGroup()],
|
|
|
+ // 榆林
|
|
|
+ ['1566108', L.featureGroup()],
|
|
|
+ // 安康
|
|
|
+ ['1566109', L.featureGroup()],
|
|
|
+ // 商洛
|
|
|
+ ['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 = {
|
|
|
- custom: {
|
|
|
+ // --- 2. 配置项声明 ---
|
|
|
+ const tileLayers: any[] = [
|
|
|
+ {
|
|
|
name: '基准瓦片',
|
|
|
layer: null,
|
|
|
url: 'https://shaanxizhxx.chinamine-safety.gov.cn/zh1/8/33/17.png',
|
|
|
@@ -30,7 +74,7 @@
|
|
|
// crossOrigin: true,
|
|
|
},
|
|
|
},
|
|
|
- satellite: {
|
|
|
+ {
|
|
|
name: '卫星图',
|
|
|
layer: null,
|
|
|
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
|
|
|
@@ -40,7 +84,7 @@
|
|
|
attribution: '© Esri',
|
|
|
},
|
|
|
},
|
|
|
- gaode: {
|
|
|
+ {
|
|
|
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}',
|
|
|
@@ -52,24 +96,13 @@
|
|
|
attribution: '© 高德地图', // 版权声明(必填,遵守使用规范)
|
|
|
},
|
|
|
},
|
|
|
- };
|
|
|
-
|
|
|
- // 添加一个风险点示例(类似第一张图的红色气泡)
|
|
|
+ ];
|
|
|
|
|
|
// --- 3. 地图初始化 ---
|
|
|
onMounted(() => {
|
|
|
- initMap();
|
|
|
- });
|
|
|
-
|
|
|
- onUnmounted(() => {
|
|
|
- map && map.remove();
|
|
|
- });
|
|
|
-
|
|
|
- // 初始化地图
|
|
|
- function initMap() {
|
|
|
if (!mapContainer.value) return;
|
|
|
|
|
|
- // 创建地图实例,设置太原市中心和初始缩放级别
|
|
|
+ // 创建地图实例,设置中心和初始缩放级别
|
|
|
map = L.map(mapContainer.value, {
|
|
|
center: [35.841, 108.94], // 西安市中心坐标 [纬度, 经度]
|
|
|
zoom: 7, // 初始缩放级别,适合城市级别查看
|
|
|
@@ -83,12 +116,17 @@
|
|
|
initMarkers();
|
|
|
// 初始化陕西省GeoJSON图层
|
|
|
initGeoJSON();
|
|
|
- }
|
|
|
+
|
|
|
+ layerMap.get(DEFAULT_LAYER_ID).addTo(map);
|
|
|
+ });
|
|
|
+
|
|
|
+ onUnmounted(() => {
|
|
|
+ map && map.remove();
|
|
|
+ });
|
|
|
|
|
|
// 初始化瓦片图层
|
|
|
function initTileLayers() {
|
|
|
- Object.keys(tileLayers).forEach((key) => {
|
|
|
- const config = tileLayers[key];
|
|
|
+ tileLayers.forEach((config) => {
|
|
|
config.layer = L.tileLayer(config.url, config.options);
|
|
|
|
|
|
if (config.addByDefault) {
|
|
|
@@ -101,67 +139,46 @@
|
|
|
const response = await fetch('/js/shanxi.geo.json');
|
|
|
const ShanXiGeoJSON = await response.json();
|
|
|
|
|
|
- const getFeatureStyle = (feature) => {
|
|
|
- const getFeatureColor = (feature) => {
|
|
|
- const gb = feature.properties.gb;
|
|
|
-
|
|
|
- const map = {
|
|
|
- 1566101: '#14baff',
|
|
|
- 1566102: '#14baff',
|
|
|
- 1566103: '#ffff12',
|
|
|
- 1566104: '#ffff12',
|
|
|
- 1566105: '#ffab12',
|
|
|
- 1566106: '#ffab12',
|
|
|
- 1566107: '#fe1414',
|
|
|
- };
|
|
|
-
|
|
|
- for (const key in map) {
|
|
|
- if (gb && gb.startsWith(key)) {
|
|
|
- return map[key];
|
|
|
- }
|
|
|
- }
|
|
|
+ layerMap.forEach((group, code) => {
|
|
|
+ const features = ShanXiGeoJSON.features.filter((feature) => {
|
|
|
+ if (code === DEFAULT_LAYER_ID) return true;
|
|
|
+ return feature.properties.gb.startsWith(code);
|
|
|
+ });
|
|
|
|
|
|
- return '#14baff';
|
|
|
- };
|
|
|
-
|
|
|
- return {
|
|
|
- fillColor: getFeatureColor(feature),
|
|
|
- fillOpacity: 0.2,
|
|
|
- weight: 2,
|
|
|
- opacity: 1,
|
|
|
- color: '#ff9100',
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
- const getFeatureEventHandlers = (feature, layer) => {
|
|
|
- const highlightFeature = (e) => {
|
|
|
- // console.log('debug sss', e);
|
|
|
- // if (feature.properties && feature.properties.name === '陕西省') return;
|
|
|
- const layer = e.target;
|
|
|
-
|
|
|
- layer.setStyle({
|
|
|
- weight: 2,
|
|
|
- fillOpacity: 0.6,
|
|
|
- });
|
|
|
-
|
|
|
- layer.bringToFront();
|
|
|
- };
|
|
|
-
|
|
|
- const resetHighlight = (e) => {
|
|
|
- geoJSON.resetStyle(e.target);
|
|
|
- };
|
|
|
-
|
|
|
- layer.on({
|
|
|
- mouseover: highlightFeature,
|
|
|
- mouseout: resetHighlight,
|
|
|
- // click: zoomToFeature,
|
|
|
+ const GeoJSON = L.geoJSON(features, {
|
|
|
+ style(feature) {
|
|
|
+ const gb = feature.properties.gb.slice(0, -2);
|
|
|
+ return {
|
|
|
+ fillColor: colorMap.get(gb) || '#14baff',
|
|
|
+ fillOpacity: 0.2,
|
|
|
+ weight: 2,
|
|
|
+ opacity: 1,
|
|
|
+ color: '#ff9100',
|
|
|
+ };
|
|
|
+ },
|
|
|
+ onEachFeature(__, layer) {
|
|
|
+ layer.on({
|
|
|
+ mouseover: (e) => {
|
|
|
+ const layer = e.target;
|
|
|
+
|
|
|
+ layer.setStyle({
|
|
|
+ weight: 2,
|
|
|
+ fillOpacity: 0.6,
|
|
|
+ });
|
|
|
+
|
|
|
+ layer.bringToFront();
|
|
|
+ },
|
|
|
+ mouseout: (e) => {
|
|
|
+ GeoJSON.resetStyle(e.target);
|
|
|
+ },
|
|
|
+ // click: zoomToFeature,
|
|
|
+ });
|
|
|
+ },
|
|
|
});
|
|
|
- };
|
|
|
|
|
|
- const geoJSON = L.geoJSON(ShanXiGeoJSON, {
|
|
|
- style: getFeatureStyle,
|
|
|
- onEachFeature: getFeatureEventHandlers,
|
|
|
- }).addTo(map);
|
|
|
+ // layerMap.set(code, GeoJSON);
|
|
|
+ GeoJSON.addTo(group);
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
/** 生成一个标准的标记点,通过传入的数据,请求详情信息并生成监测详情框 */
|
|
|
@@ -219,29 +236,91 @@
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+ 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 list({ pageNo: 0, paseSize: 9999 });
|
|
|
- records.forEach((item, index) => {
|
|
|
- const presets = [
|
|
|
- [36.644181586865905, 109.48980355362514],
|
|
|
- [33.063924198120645, 107.01782226562501],
|
|
|
- [34.34116826510752, 108.93468369998695],
|
|
|
- [34.361576287484176, 107.23571011212084],
|
|
|
- ];
|
|
|
-
|
|
|
- generateStandardMarker(
|
|
|
- {
|
|
|
- name: item.mineName,
|
|
|
- lat: presets[index][0],
|
|
|
- lng: presets[index][1],
|
|
|
- // color: 'red',
|
|
|
- radius: 10,
|
|
|
- type: 'marker',
|
|
|
- ...item,
|
|
|
- },
|
|
|
- {}
|
|
|
- ).then((r) => r.addTo(map));
|
|
|
+ const records = await getMarkers({ pageNo: 0, paseSize: 9999 });
|
|
|
+ const details: Promise<any>[] = [];
|
|
|
+ records.forEach((item) => {
|
|
|
+ generateRootMarker(item, {
|
|
|
+ riseOnHover: true,
|
|
|
+ }).addTo(layerMap.get(DEFAULT_LAYER_ID));
|
|
|
+
|
|
|
+ if (!item.children.length) return;
|
|
|
+
|
|
|
+ item.children.forEach((child) => {
|
|
|
+ details.push(
|
|
|
+ generateStandardMarker(child, {
|
|
|
+ riseOnHover: false,
|
|
|
+ }).then((marker) => {
|
|
|
+ if (layerMap.has(child.gb)) {
|
|
|
+ marker.addTo(layerMap.get(child.gb));
|
|
|
+ }
|
|
|
+ })
|
|
|
+ );
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ await Promise.all(details);
|
|
|
+ }
|
|
|
+
|
|
|
+ function reset() {
|
|
|
+ layerMap.forEach((layer, code) => {
|
|
|
+ map.removeLayer(layer);
|
|
|
+
|
|
|
+ if (code === DEFAULT_LAYER_ID) {
|
|
|
+ map.addLayer(layer);
|
|
|
+
|
|
|
+ map.flyToBounds(layer.getBounds());
|
|
|
+ }
|
|
|
});
|
|
|
}
|
|
|
</script>
|
|
|
@@ -319,4 +398,13 @@
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ .map-reset-btn {
|
|
|
+ position: absolute;
|
|
|
+ right: 26%; /* 距离右边 20px */
|
|
|
+ z-index: 1000; /* 确保在地图控件之上 */
|
|
|
+ top: @header-height;
|
|
|
+ // padding: 10px 15px;
|
|
|
+ margin: 10px 0;
|
|
|
+ }
|
|
|
</style>
|