生产管理系统前端 - 更新瓦力提交的产品原型到参考目录
This commit is contained in:
526
src/lib/gisMapEngine.ts
Normal file
526
src/lib/gisMapEngine.ts
Normal file
@@ -0,0 +1,526 @@
|
||||
/**
|
||||
* 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.warn('高德地图SDK未加载,切换到占位模式');
|
||||
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 {
|
||||
// 动态加载Leaflet
|
||||
if (!window.L) {
|
||||
await this.loadLeaflet();
|
||||
}
|
||||
|
||||
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地图初始化成功');
|
||||
} catch (error) {
|
||||
console.error('Leaflet地图初始化失败:', error);
|
||||
this.provider = 'placeholder';
|
||||
this.initPlaceholder(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Leaflet库
|
||||
*/
|
||||
private async loadLeaflet(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 加载CSS
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
|
||||
document.head.appendChild(link);
|
||||
|
||||
// 加载JS
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
|
||||
script.onload = () => resolve();
|
||||
script.onerror = () => reject(new Error('Leaflet加载失败'));
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化占位地图
|
||||
*/
|
||||
private initPlaceholder(config: MapConfig) {
|
||||
if (!this.container) return;
|
||||
|
||||
this.container.innerHTML = `
|
||||
<div class="gis-placeholder-map" style="
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #f0fdf4 0%, #dbeafe 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
">
|
||||
<div style="
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(0,0,0,0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(0,0,0,0.03) 1px, transparent 1px);
|
||||
background-size: 50px 50px;
|
||||
"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
console.log('✅ 占位地图初始化成功(功能完整)');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置地图图层
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user