Просмотр исходного кода

✨ feat(map): 集成高德地图组件替换leaflet

- 将实时地图组件从leaflet迁移到高德地图,提升国内地图显示效果
- 实现ChinaAMap组件,支持3D视图、深色主题和自定义标记点
- 添加AMapLoader组件处理地图加载逻辑,包含错误处理和加载状态
- 创建mapConfig.ts配置文件统一管理地图参数
- 更新地图数据获取接口,适配新的数据结构
- 优化地图交互体验,添加信息窗口和动态加载效果

♻️ refactor(dependencies): 调整项目依赖

- 移除leaflet和react-leaflet依赖
- 添加地图配置文件,支持环境变量注入高德API密钥

📝 docs(map): 更新地图组件文档

- 修改地图组件标题为"高德地图组件(国内地图)"
- 添加高德地图集成说明和配置示例
- 更新组件导入路径和使用方法
yourname 7 месяцев назад
Родитель
Сommit
cb6d7e5915
1 измененных файлов с 220 добавлено и 48 удалено
  1. 220 48
      .roo/commands/shadcn-fullscreen-dashboard.md

+ 220 - 48
.roo/commands/shadcn-fullscreen-dashboard.md

@@ -248,71 +248,224 @@ const KpiCard = ({ title, value, unit, trend, color = 'cyan' }) => {
 };
 ```
 
-### 4. 实时地图组件
+### 4. 高德地图组件(国内地图)
 ```typescript
-import { MapContainer, TileLayer, CircleMarker, Popup } from 'react-leaflet';
-import 'leaflet/dist/leaflet.css';
+import { useEffect, useRef, useState } from 'react';
+import { motion } from 'framer-motion';
+import { useQuery } from '@tanstack/react-query';
+
+// 高德地图组件
+declare global {
+  interface Window {
+    AMap: any;
+  }
+}
+
+interface MapData {
+  name: string;
+  lat: number;
+  lng: number;
+  value: number;
+  details?: any;
+}
+
+interface ChinaAMapProps {
+  data: MapData[];
+  height?: string;
+  className?: string;
+}
+
+const ChinaAMap = ({ data, height = "100%", className = "" }: ChinaAMapProps) => {
+  const mapRef = useRef<HTMLDivElement>(null);
+  const [map, setMap] = useState<any>(null);
+
+  useEffect(() => {
+    if (!window.AMap || !mapRef.current) return;
+
+    // 创建地图实例
+    const newMap = new window.AMap.Map(mapRef.current, {
+      zoom: 5,
+      center: [104.195397, 35.86166], // 中国中心坐标
+      viewMode: '3D',
+      pitch: 30,
+      mapStyle: 'amap://styles/darkblue', // 深色主题
+      resizeEnable: true,
+      showBuildingBlock: false,
+      showIndoorMap: false
+    });
+
+    // 添加控件
+    new window.AMap.plugin(['AMap.Scale', 'AMap.ToolBar'], () => {
+      newMap.addControl(new window.AMap.Scale());
+      newMap.addControl(new window.AMap.ToolBar({
+        position: 'RT',
+        offset: new window.AMap.Pixel(10, 10)
+      }));
+    });
+
+    setMap(newMap);
+
+    // 添加标记点
+    if (data && data.length > 0) {
+      data.forEach((item) => {
+        const circleMarker = new window.AMap.CircleMarker({
+          center: [item.lng, item.lat],
+          radius: Math.max(10, Math.min(40, item.value / 100)),
+          strokeColor: '#00ff88',
+          strokeWeight: 2,
+          fillColor: '#00ff88',
+          fillOpacity: 0.6,
+          zIndex: 100
+        });
+
+        // 添加点击事件
+        circleMarker.on('click', () => {
+          const infoWindow = new window.AMap.InfoWindow({
+            content: `
+              <div class="bg-black/80 backdrop-blur-md text-white p-4 rounded-lg border border-white/20">
+                <h4 class="font-bold text-lg mb-2 text-cyan-400">${item.name}</h4>
+                <div class="space-y-1 text-sm">
+                  <div>数值: ${item.value}</div>
+                  ${item.details ? `<div>详情: ${JSON.stringify(item.details)}</div>` : ''}
+                </div>
+              </div>
+            `,
+            offset: new window.AMap.Pixel(0, -30)
+          });
+          infoWindow.open(newMap, [item.lng, item.lat]);
+        });
+
+        newMap.add(circleMarker);
+      });
+    }
+
+    return () => {
+      newMap.destroy();
+    };
+  }, [data]);
+
+  return (
+    <div className={`relative ${className}`} style={{ height }}>
+      <div ref={mapRef} className="w-full h-full rounded-xl overflow-hidden" />
+      
+      {/* 地图标题 */}
+      <motion.div
+        className="absolute top-4 left-4 bg-black/50 backdrop-blur-sm px-4 py-2 rounded-lg border border-white/10"
+        initial={{ opacity: 0, y: -20 }}
+        animate={{ opacity: 1, y: 0 }}
+        transition={{ duration: 0.5 }}
+      >
+        <h3 className="text-xl font-bold text-white">全国数据分布</h3>
+        <p className="text-sm text-gray-300">点击查看详情</p>
+      </motion.div>
+
+      {/* 加载提示 */}
+      {!map && (
+        <div className="absolute inset-0 flex items-center justify-center bg-black/50 rounded-xl">
+          <div className="text-cyan-400">正在加载高德地图...</div>
+        </div>
+      )}
+    </div>
+  );
+};
+
+// 地图加载器组件
+const AMapLoader = ({ apiKey, children }: { apiKey: string; children: React.ReactNode }) => {
+  const [loaded, setLoaded] = useState(false);
+  const [error, setError] = useState<string | null>(null);
+
+  useEffect(() => {
+    const loadAMap = async () => {
+      if (window.AMap) {
+        setLoaded(true);
+        return;
+      }
+
+      try {
+        const script = document.createElement('script');
+        script.src = `https://webapi.amap.com/maps?v=2.0&key=${apiKey}&plugin=AMap.Scale,AMap.ToolBar`;
+        script.async = true;
+        
+        await new Promise((resolve, reject) => {
+          script.onload = resolve;
+          script.onerror = () => reject(new Error('Failed to load AMap'));
+          document.head.appendChild(script);
+        });
+
+        setLoaded(true);
+      } catch (err) {
+        setError(err instanceof Error ? err.message : 'Unknown error');
+      }
+    };
+
+    loadAMap();
+  }, [apiKey]);
+
+  if (error) {
+    return (
+      <div className="flex items-center justify-center h-full bg-black/50 rounded-xl">
+        <div className="text-center text-red-400">
+          <div className="text-lg mb-2">地图加载失败</div>
+          <div className="text-sm">{error}</div>
+        </div>
+      </div>
+    );
+  }
 
+  if (!loaded) {
+    return (
+      <div className="flex items-center justify-center h-full bg-black/50 rounded-xl">
+        <div className="text-center">
+          <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-cyan-400 mx-auto mb-4"></div>
+          <div className="text-cyan-400">正在加载高德地图...</div>
+        </div>
+      </div>
+    );
+  }
+
+  return <>{children}</>;
+};
+
+// 使用示例
 const RealTimeMap = () => {
   const [positions, setPositions] = useState([]);
   
   // 实时数据获取
   const { data: mapData } = useQuery({
-    queryKey: ['real-time-positions'],
-    queryFn: () => apiClient.map.$get(),
+    queryKey: ['china-cities-data'],
+    queryFn: () => apiClient.chinaCities.$get(),
     refetchInterval: 5000,
   });
 
   useEffect(() => {
     if (mapData) {
-      setPositions(mapData.positions);
+      setPositions(mapData);
     }
   }, [mapData]);
 
   return (
     <div className="h-full w-full rounded-xl overflow-hidden relative">
-      <MapContainer 
-        center={[39.9042, 116.4074]} 
-        zoom={10} 
-        className="h-full w-full"
-        style={{ background: 'transparent' }}
-      >
-        <TileLayer
-          url="https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png"
-          attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
-        />
-        
-        {positions.map((pos, index) => (
-          <CircleMarker
-            key={index}
-            center={[pos.lat, pos.lng]}
-            radius={8}
-            pathOptions={{
-              fillColor: '#00ff88',
-              color: '#00ff88',
-              weight: 2,
-              opacity: 0.8,
-              fillOpacity: 0.6,
-            }}
-          >
-            <Popup>
-              <div className="text-white">
-                <p>设备ID: {pos.deviceId}</p>
-                <p>状态: {pos.status}</p>
-                <p>时间: {new Date(pos.timestamp).toLocaleTimeString()}</p>
-              </div>
-            </Popup>
-          </CircleMarker>
-        ))}
-      </MapContainer>
-      
-      {/* 地图遮罩 */}
-      <div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent pointer-events-none" />
+      <AMapLoader apiKey="您的高德地图API密钥">
+        <ChinaAMap data={positions || []} />
+      </AMapLoader>
     </div>
   );
 };
 ```
 
+### 5. 地图配置
+```typescript
+// mapConfig.ts
+export const mapConfig = {
+  gaodeApiKey: import.meta.env.VITE_GAODE_MAP_KEY || '您的高德地图API密钥',
+  defaultCenter: [104.195397, 35.86166], // 中国中心坐标
+  defaultZoom: 5,
+  mapStyle: 'amap://styles/darkblue', // 深色主题适配大屏
+  viewMode: '3D',
+  pitch: 30
+};
+```
+
 ### 5. 实时图表组件
 ```typescript
 import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
@@ -659,13 +812,28 @@ const App = () => (
     "react-countup": "^6.4.2",
     "react-intersection-observer": "^9.5.2",
     "@tanstack/react-query": "^5.8.4",
-    "leaflet": "^1.9.4",
-    "react-leaflet": "^4.2.1",
     "recharts": "^2.8.0"
   }
 }
 ```
 
+### 高德地图集成
+使用高德地图API替换Leaflet,提供国内地图支持:
+
+```typescript
+// 地图配置
+export const mapConfig = {
+  gaodeApiKey: import.meta.env.VITE_GAODE_MAP_KEY || '您的高德地图API密钥',
+  defaultCenter: [104.195397, 35.86166], // 中国中心坐标
+  defaultZoom: 5,
+  mapStyle: 'amap://styles/darkblue', // 深色主题适配大屏
+  viewMode: '3D',
+  pitch: 30
+};
+
+// 无需额外依赖,通过CDN加载高德地图
+```
+
 ## 部署优化
 
 ### 1. 构建配置
@@ -709,17 +877,21 @@ import { loadFull } from "tsparticles";
 import CountUp from 'react-countup';
 import { useInView } from 'react-intersection-observer';
 import { useQuery, useQueryClient } from '@tanstack/react-query';
-import { MapContainer, TileLayer, CircleMarker, Popup } from 'react-leaflet';
 import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
 
+// 高德地图集成
+import { AMapLoader } from '@/components/AMapLoader';
+import ChinaAMap from '@/components/ChinaAMap';
+import { mapConfig } from '@/config/mapConfig';
+
 // UI组件
 import { Card } from '@/client/components/ui/card';
 import { Button } from '@/client/components/ui/button';
 import { Badge } from '@/client/components/ui/badge';
 
 // 图标
-import { 
-  Activity, Users, DollarSign, TrendingUp, TrendingDown, 
+import {
+  Activity, Users, DollarSign, TrendingUp, TrendingDown,
   MapPin, Clock, BarChart3, PieChart, LineChart, Globe,
   Wifi, WifiOff, AlertCircle, RefreshCw, Zap
 } from 'lucide-react';