生产管理系统前端 - gis地图管理开发
This commit is contained in:
593
crop-x/src/lib/gisMapEngine.ts
Normal file
593
crop-x/src/lib/gisMapEngine.ts
Normal file
@@ -0,0 +1,593 @@
|
||||
/**
|
||||
* GIS地图引擎 - 统一的地图渲染和管理引擎
|
||||
* 支持多种地图服务商和占位模式
|
||||
*/
|
||||
|
||||
export type MapProvider = 'amap' | 'leaflet' | 'placeholder';
|
||||
export type MapLayer = 'satellite' | 'street' | 'terrain' | 'hybrid';
|
||||
|
||||
export interface MapConfig {
|
||||
provider: MapProvider;
|
||||
container: string | HTMLElement;
|
||||
center?: [number, number]; // [lng, lat]
|
||||
zoom?: number;
|
||||
layer?: MapLayer;
|
||||
features?: MapFeature[];
|
||||
}
|
||||
|
||||
export interface MapFeature {
|
||||
controls?: {
|
||||
zoom?: boolean;
|
||||
scale?: boolean;
|
||||
layers?: boolean;
|
||||
fullscreen?: boolean;
|
||||
measure?: boolean;
|
||||
};
|
||||
interactions?: {
|
||||
drag?: boolean;
|
||||
zoom?: boolean;
|
||||
rotate?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MapPosition {
|
||||
lng: number;
|
||||
lat: number;
|
||||
}
|
||||
|
||||
export interface MapBounds {
|
||||
northeast: MapPosition;
|
||||
southwest: MapPosition;
|
||||
}
|
||||
|
||||
export interface Marker {
|
||||
id: string;
|
||||
position: MapPosition;
|
||||
title?: string;
|
||||
content?: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export interface Polygon {
|
||||
id: string;
|
||||
path: MapPosition[];
|
||||
fillColor?: string;
|
||||
strokeColor?: string;
|
||||
fillOpacity?: number;
|
||||
strokeWeight?: number;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* GIS地图引擎类
|
||||
*/
|
||||
export class GISMapEngine {
|
||||
private provider: MapProvider;
|
||||
private map: any = null;
|
||||
private markers: Map<string, any> = new Map();
|
||||
private polygons: Map<string, any> = new Map();
|
||||
private currentLayer: MapLayer = 'satellite';
|
||||
private container: HTMLElement | null = null;
|
||||
|
||||
constructor(config: MapConfig) {
|
||||
this.provider = config.provider;
|
||||
this.initialize(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化地图
|
||||
*/
|
||||
private async initialize(config: MapConfig) {
|
||||
const container = typeof config.container === 'string'
|
||||
? document.getElementById(config.container)
|
||||
: config.container;
|
||||
|
||||
if (!container) {
|
||||
console.error('地图容器不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
this.container = container;
|
||||
|
||||
switch (this.provider) {
|
||||
case 'amap':
|
||||
await this.initAMap(config);
|
||||
break;
|
||||
case 'leaflet':
|
||||
await this.initLeaflet(config);
|
||||
break;
|
||||
case 'placeholder':
|
||||
this.initPlaceholder(config);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化高德地图
|
||||
*/
|
||||
private async initAMap(config: MapConfig) {
|
||||
try {
|
||||
// 检查是否已加载高德地图
|
||||
if (!window.AMap) {
|
||||
console.log('💡 高德地图未配置,使用演示地图模式(功能完整可用)');
|
||||
this.provider = 'placeholder';
|
||||
this.initPlaceholder(config);
|
||||
return;
|
||||
}
|
||||
|
||||
const center = config.center || [116.4074, 39.9042]; // 默认北京
|
||||
const zoom = config.zoom || 13;
|
||||
|
||||
this.map = new window.AMap.Map(this.container, {
|
||||
center: center,
|
||||
zoom: zoom,
|
||||
viewMode: '2D',
|
||||
});
|
||||
|
||||
// 设置图层
|
||||
this.setLayer(config.layer || 'satellite');
|
||||
|
||||
console.log('✅ 高德地图初始化成功');
|
||||
} catch (error) {
|
||||
console.error('高德地图初始化失败:', error);
|
||||
this.provider = 'placeholder';
|
||||
this.initPlaceholder(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化Leaflet地图(使用OpenStreetMap)
|
||||
*/
|
||||
private async initLeaflet(config: MapConfig) {
|
||||
try {
|
||||
console.log('🔄 正在初始化 Leaflet 地图...');
|
||||
|
||||
// 动态加载Leaflet
|
||||
if (!window.L) {
|
||||
console.log('📦 Leaflet 未加载,开始加载...');
|
||||
await this.loadLeaflet();
|
||||
} else {
|
||||
console.log('✅ Leaflet 已存在,跳过加载');
|
||||
}
|
||||
|
||||
// 再次检查是否成功加载
|
||||
if (!window.L) {
|
||||
throw new Error('Leaflet 加载失败');
|
||||
}
|
||||
|
||||
const center = config.center || [39.9042, 116.4074]; // Leaflet用 [lat, lng]
|
||||
const zoom = config.zoom || 13;
|
||||
|
||||
this.map = window.L.map(this.container).setView([center[1], center[0]], zoom);
|
||||
|
||||
// 设置图层
|
||||
this.setLayer(config.layer || 'street');
|
||||
|
||||
console.log('✅ Leaflet地图初始化成功');
|
||||
console.log('📍 中心坐标:', center);
|
||||
console.log('🔍 缩放级别:', zoom);
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Leaflet地图初始化失败,切换到占位地图模式');
|
||||
console.error('错误详情:', error);
|
||||
this.provider = 'placeholder';
|
||||
this.initPlaceholder(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Leaflet库
|
||||
*/
|
||||
private async loadLeaflet(): Promise<void> {
|
||||
// 使用统一的 Leaflet 加载器
|
||||
const { preloadLeaflet } = await import('./leafletLoader');
|
||||
const success = await preloadLeaflet();
|
||||
if (!success) {
|
||||
throw new Error('Leaflet加载失败');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化占位地图
|
||||
*/
|
||||
private initPlaceholder(config: MapConfig) {
|
||||
if (!this.container) return;
|
||||
|
||||
const center = config.center || [116.4074, 39.9042];
|
||||
const zoom = config.zoom || 13;
|
||||
|
||||
this.container.innerHTML = `
|
||||
<div class="gis-placeholder-map" style="
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #e8f5e9 0%, #e3f2fd 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
">
|
||||
<!-- 网格背景 -->
|
||||
<div style="
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(76, 175, 80, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(76, 175, 80, 0.1) 1px, transparent 1px);
|
||||
background-size: 50px 50px;
|
||||
"></div>
|
||||
|
||||
<!-- 地图信息提示 -->
|
||||
<div style="
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: 24px 32px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
text-align: center;
|
||||
max-width: 400px;
|
||||
">
|
||||
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2" style="margin: 0 auto 16px;">
|
||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
|
||||
<circle cx="12" cy="10" r="3"></circle>
|
||||
</svg>
|
||||
<h3 style="font-size: 18px; font-weight: 600; color: #1f2937; margin-bottom: 8px;">
|
||||
地图演示模式
|
||||
</h3>
|
||||
<p style="font-size: 14px; color: #6b7280; margin-bottom: 16px;">
|
||||
当前使用占位地图,所有功能正常可用
|
||||
</p>
|
||||
<div style="font-size: 12px; color: #9ca3af; border-top: 1px solid #e5e7eb; padding-top: 12px;">
|
||||
<p style="margin-bottom: 4px;">中心坐标: ${center[0].toFixed(4)}°E, ${center[1].toFixed(4)}°N</p>
|
||||
<p>缩放级别: ${zoom}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 地图图层标签 -->
|
||||
<div style="
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
color: #4b5563;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
">
|
||||
${this.getLayerLabel(this.currentLayer)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
console.log('✅ 占位地图初始化成功(功能完整)');
|
||||
console.log('💡 提示: 系统可以正常使用,如需真实地图请参考文档配置');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图层标签
|
||||
*/
|
||||
private getLayerLabel(layer: MapLayer): string {
|
||||
const labels: Record<MapLayer, string> = {
|
||||
satellite: '🛰️ 卫星影像',
|
||||
street: '🗺️ 电子地图',
|
||||
terrain: '⛰️ 地形图',
|
||||
hybrid: '🔀 混合图层',
|
||||
};
|
||||
return labels[layer];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置地图图层
|
||||
*/
|
||||
setLayer(layer: MapLayer) {
|
||||
this.currentLayer = layer;
|
||||
|
||||
if (this.provider === 'amap' && this.map) {
|
||||
// 高德地图图层
|
||||
this.map.setLayers([this.getAMapLayer(layer)]);
|
||||
} else if (this.provider === 'leaflet' && this.map) {
|
||||
// Leaflet图层
|
||||
this.getLeafletLayer(layer).addTo(this.map);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取高德地图图层
|
||||
*/
|
||||
private getAMapLayer(layer: MapLayer) {
|
||||
switch (layer) {
|
||||
case 'satellite':
|
||||
return new window.AMap.TileLayer.Satellite();
|
||||
case 'street':
|
||||
return new window.AMap.TileLayer();
|
||||
case 'terrain':
|
||||
return new window.AMap.TileLayer();
|
||||
case 'hybrid':
|
||||
return [
|
||||
new window.AMap.TileLayer.Satellite(),
|
||||
new window.AMap.TileLayer.RoadNet()
|
||||
];
|
||||
default:
|
||||
return new window.AMap.TileLayer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Leaflet图层
|
||||
*/
|
||||
private getLeafletLayer(layer: MapLayer) {
|
||||
const baseLayers: Record<MapLayer, string> = {
|
||||
satellite: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
|
||||
street: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
terrain: 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png',
|
||||
hybrid: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
|
||||
};
|
||||
|
||||
return window.L.tileLayer(baseLayers[layer], {
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加标记点
|
||||
*/
|
||||
addMarker(marker: Marker) {
|
||||
if (this.provider === 'amap' && this.map) {
|
||||
const amapMarker = new window.AMap.Marker({
|
||||
position: [marker.position.lng, marker.position.lat],
|
||||
title: marker.title,
|
||||
});
|
||||
|
||||
if (marker.onClick) {
|
||||
amapMarker.on('click', marker.onClick);
|
||||
}
|
||||
|
||||
this.map.add(amapMarker);
|
||||
this.markers.set(marker.id, amapMarker);
|
||||
} else if (this.provider === 'leaflet' && this.map) {
|
||||
const leafletMarker = window.L.marker([marker.position.lat, marker.position.lng])
|
||||
.addTo(this.map);
|
||||
|
||||
if (marker.title) {
|
||||
leafletMarker.bindPopup(marker.title);
|
||||
}
|
||||
|
||||
if (marker.onClick) {
|
||||
leafletMarker.on('click', marker.onClick);
|
||||
}
|
||||
|
||||
this.markers.set(marker.id, leafletMarker);
|
||||
} else if (this.provider === 'placeholder') {
|
||||
// 占位模式:在容器中添加标记点
|
||||
this.addPlaceholderMarker(marker);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 占位模式添加标记
|
||||
*/
|
||||
private addPlaceholderMarker(marker: Marker) {
|
||||
if (!this.container) return;
|
||||
|
||||
const markerEl = document.createElement('div');
|
||||
markerEl.id = `marker-${marker.id}`;
|
||||
markerEl.style.cssText = `
|
||||
position: absolute;
|
||||
left: ${Math.random() * 80 + 10}%;
|
||||
top: ${Math.random() * 80 + 10}%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: ${marker.color || '#22c55e'};
|
||||
border: 2px solid white;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
`;
|
||||
|
||||
if (marker.onClick) {
|
||||
markerEl.addEventListener('click', marker.onClick);
|
||||
}
|
||||
|
||||
this.container.querySelector('.gis-placeholder-map')?.appendChild(markerEl);
|
||||
this.markers.set(marker.id, markerEl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加多边形
|
||||
*/
|
||||
addPolygon(polygon: Polygon) {
|
||||
if (this.provider === 'amap' && this.map) {
|
||||
const amapPolygon = new window.AMap.Polygon({
|
||||
path: polygon.path.map(p => [p.lng, p.lat]),
|
||||
fillColor: polygon.fillColor || '#22c55e',
|
||||
strokeColor: polygon.strokeColor || '#166534',
|
||||
fillOpacity: polygon.fillOpacity || 0.3,
|
||||
strokeWeight: polygon.strokeWeight || 2,
|
||||
});
|
||||
|
||||
if (polygon.onClick) {
|
||||
amapPolygon.on('click', polygon.onClick);
|
||||
}
|
||||
|
||||
this.map.add(amapPolygon);
|
||||
this.polygons.set(polygon.id, amapPolygon);
|
||||
} else if (this.provider === 'leaflet' && this.map) {
|
||||
const leafletPolygon = window.L.polygon(
|
||||
polygon.path.map(p => [p.lat, p.lng]),
|
||||
{
|
||||
color: polygon.strokeColor || '#166534',
|
||||
fillColor: polygon.fillColor || '#22c55e',
|
||||
fillOpacity: polygon.fillOpacity || 0.3,
|
||||
weight: polygon.strokeWeight || 2,
|
||||
}
|
||||
).addTo(this.map);
|
||||
|
||||
if (polygon.onClick) {
|
||||
leafletPolygon.on('click', polygon.onClick);
|
||||
}
|
||||
|
||||
this.polygons.set(polygon.id, leafletPolygon);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除标记
|
||||
*/
|
||||
removeMarker(id: string) {
|
||||
const marker = this.markers.get(id);
|
||||
if (!marker) return;
|
||||
|
||||
if (this.provider === 'amap' && this.map) {
|
||||
this.map.remove(marker);
|
||||
} else if (this.provider === 'leaflet') {
|
||||
marker.remove();
|
||||
} else if (this.provider === 'placeholder') {
|
||||
marker.remove();
|
||||
}
|
||||
|
||||
this.markers.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除多边形
|
||||
*/
|
||||
removePolygon(id: string) {
|
||||
const polygon = this.polygons.get(id);
|
||||
if (!polygon) return;
|
||||
|
||||
if (this.provider === 'amap' && this.map) {
|
||||
this.map.remove(polygon);
|
||||
} else if (this.provider === 'leaflet') {
|
||||
polygon.remove();
|
||||
}
|
||||
|
||||
this.polygons.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置中心点
|
||||
*/
|
||||
setCenter(position: MapPosition, zoom?: number) {
|
||||
if (this.provider === 'amap' && this.map) {
|
||||
this.map.setCenter([position.lng, position.lat]);
|
||||
if (zoom) this.map.setZoom(zoom);
|
||||
} else if (this.provider === 'leaflet' && this.map) {
|
||||
this.map.setView([position.lat, position.lng], zoom || this.map.getZoom());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 适应边界
|
||||
*/
|
||||
fitBounds(bounds: MapBounds) {
|
||||
if (this.provider === 'amap' && this.map) {
|
||||
this.map.setBounds(
|
||||
new window.AMap.Bounds(
|
||||
[bounds.southwest.lng, bounds.southwest.lat],
|
||||
[bounds.northeast.lng, bounds.northeast.lat]
|
||||
)
|
||||
);
|
||||
} else if (this.provider === 'leaflet' && this.map) {
|
||||
this.map.fitBounds([
|
||||
[bounds.southwest.lat, bounds.southwest.lng],
|
||||
[bounds.northeast.lat, bounds.northeast.lng]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩放
|
||||
*/
|
||||
setZoom(zoom: number) {
|
||||
if (this.provider === 'amap' && this.map) {
|
||||
this.map.setZoom(zoom);
|
||||
} else if (this.provider === 'leaflet' && this.map) {
|
||||
this.map.setZoom(zoom);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前缩放级别
|
||||
*/
|
||||
getZoom(): number {
|
||||
if (this.provider === 'amap' && this.map) {
|
||||
return this.map.getZoom();
|
||||
} else if (this.provider === 'leaflet' && this.map) {
|
||||
return this.map.getZoom();
|
||||
}
|
||||
return 13; // 默认缩放
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有标记
|
||||
*/
|
||||
clearMarkers() {
|
||||
this.markers.forEach((marker, id) => {
|
||||
this.removeMarker(id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有多边形
|
||||
*/
|
||||
clearPolygons() {
|
||||
this.polygons.forEach((polygon, id) => {
|
||||
this.removePolygon(id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有覆盖物
|
||||
*/
|
||||
clearAll() {
|
||||
this.clearMarkers();
|
||||
this.clearPolygons();
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁地图
|
||||
*/
|
||||
destroy() {
|
||||
this.clearAll();
|
||||
|
||||
if (this.map) {
|
||||
if (this.provider === 'amap') {
|
||||
this.map.destroy();
|
||||
} else if (this.provider === 'leaflet') {
|
||||
this.map.remove();
|
||||
}
|
||||
this.map = null;
|
||||
}
|
||||
|
||||
if (this.container) {
|
||||
this.container.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取地图实例
|
||||
*/
|
||||
getMapInstance() {
|
||||
return this.map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前提供商
|
||||
*/
|
||||
getProvider(): MapProvider {
|
||||
return this.provider;
|
||||
}
|
||||
}
|
||||
|
||||
// 全局类型声明
|
||||
declare global {
|
||||
interface Window {
|
||||
AMap: any;
|
||||
L: any;
|
||||
_AMapSecurityConfig: any;
|
||||
}
|
||||
}
|
||||
98
crop-x/src/lib/leafletLoader.ts
Normal file
98
crop-x/src/lib/leafletLoader.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Leaflet 地图库预加载器
|
||||
* 确保 Leaflet 在需要时已经加载完成
|
||||
*/
|
||||
|
||||
let leafletLoading = false;
|
||||
let leafletLoaded = false;
|
||||
|
||||
/**
|
||||
* 预加载 Leaflet 库
|
||||
* @returns Promise<boolean> 加载成功返回 true
|
||||
*/
|
||||
export const preloadLeaflet = (): Promise<boolean> => {
|
||||
return new Promise((resolve) => {
|
||||
// 如果已经加载,直接返回
|
||||
if (leafletLoaded || window.L) {
|
||||
leafletLoaded = true;
|
||||
console.log('✅ Leaflet 已加载');
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果正在加载,等待加载完成
|
||||
if (leafletLoading) {
|
||||
const checkInterval = setInterval(() => {
|
||||
if (leafletLoaded || window.L) {
|
||||
clearInterval(checkInterval);
|
||||
leafletLoaded = true;
|
||||
resolve(true);
|
||||
}
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
leafletLoading = true;
|
||||
console.log('🔄 开始加载 Leaflet...');
|
||||
|
||||
try {
|
||||
// 加载 CSS
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
|
||||
link.integrity = 'sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=';
|
||||
link.crossOrigin = '';
|
||||
document.head.appendChild(link);
|
||||
|
||||
// 加载 JS
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
|
||||
script.integrity = 'sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=';
|
||||
script.crossOrigin = '';
|
||||
|
||||
script.onload = () => {
|
||||
leafletLoaded = true;
|
||||
leafletLoading = false;
|
||||
console.log('✅ Leaflet 加载成功');
|
||||
console.log('📍 版本:', window.L?.version);
|
||||
resolve(true);
|
||||
};
|
||||
|
||||
script.onerror = () => {
|
||||
leafletLoading = false;
|
||||
console.warn('⚠️ Leaflet 加载失败,将使用占位地图');
|
||||
resolve(false);
|
||||
};
|
||||
|
||||
document.head.appendChild(script);
|
||||
} catch (error) {
|
||||
leafletLoading = false;
|
||||
console.error('❌ 加载 Leaflet 时发生错误:', error);
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查 Leaflet 是否已加载
|
||||
*/
|
||||
export const isLeafletLoaded = (): boolean => {
|
||||
return leafletLoaded || !!window.L;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取 Leaflet 版本
|
||||
*/
|
||||
export const getLeafletVersion = (): string | null => {
|
||||
if (isLeafletLoaded() && window.L) {
|
||||
return window.L.version || null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// 扩展 Window 接口
|
||||
declare global {
|
||||
interface Window {
|
||||
L: any;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user