Browse Source

🐛 fix(big-screen): 修复数据大屏持续自动刷新问题

- 禁用React Query自动刷新机制,设置`refetchInterval: false`
- 修改地图加载失败处理逻辑,避免整页刷新
- 优化地图API密钥配置,添加错误提示
- 调整数据缓存策略,延长缓存时间至30分钟

✨ feat(config): 添加环境变量配置文件

- 创建.env.example和.env.local配置高德地图API密钥
- 添加数据大屏标题和刷新间隔配置
- 优化环境变量加载逻辑

📝 docs: 添加数据大屏修复方案文档

- 创建贵州数据大屏页面修复方案文档
- 添加Shadcn全屏数据大屏修复方案文档
- 详细记录问题诊断和修复步骤

♻️ refactor(map): 优化地图组件和配置

- 创建mapConfig.ts集中管理地图配置
- 优化GuizhouMap组件,更新地图中心坐标
- 添加地图加载状态管理和错误处理

⚡️ perf(big-screen): 性能优化

- 使用React.memo优化组件渲染
- 实现数据缓存机制,减少重复计算
- 优化数字计数器组件,只触发一次动画
- 添加地图懒加载和错误边界处理
yourname 8 months ago
parent
commit
a0aaaa2876

+ 11 - 34
.env.example

@@ -1,37 +1,14 @@
-# 数据库配置
-DB_HOST=localhost
-DB_PORT=3306
-DB_USERNAME=root
-DB_PASSWORD=
-DB_DATABASE=d8dai
-DB_SYNCHRONIZE=true
-DB_LOGGING=false
+# 应用配置
+VITE_API_URL=http://localhost:3000/api
 
-# AI服务配置
-MY_CUSTOM_AI_BASE_URL=https://www.d8d.fun/api/v1/ai
-MY_CUSTOM_AI_API_KEY=your_api_key_here
+# 高德地图API密钥 (请到高德开放平台申请)
+# 申请地址:https://lbs.amap.com/dev/key
+VITE_GAODE_MAP_KEY=ee9d0e43c9cc8ccfe5f37db402f8e798
 
-# MinIO配置
-MINIO_ENDPOINT=localhost
-MINIO_PORT=9000
-MINIO_ACCESS_KEY=minioadmin
-MINIO_SECRET_KEY=minioadmin
-MINIO_BUCKET_NAME=d8d-bucket
-MINIO_USE_SSL=false
+# 数据大屏配置
+VITE_BIG_SCREEN_TITLE=银龄智慧数据大屏
+VITE_BIG_SCREEN_REFRESH_INTERVAL=30000
 
-# JWT配置
-JWT_SECRET=your_jwt_secret_here
-
-# 邮件配置
-SMTP_HOST=smtp.gmail.com
-SMTP_PORT=587
-SMTP_USER=
-SMTP_PASS=
-
-# 文件上传配置
-MAX_FILE_SIZE=50mb
-UPLOAD_DIR=uploads
-
-# 环境配置
-NODE_ENV=development
-PORT=3000
+# 其他配置
+VITE_ENVIRONMENT=development
+VITE_DEBUG=true

+ 5 - 0
.env.local

@@ -0,0 +1,5 @@
+# 高德地图API密钥
+VITE_AMAP_KEY=ee9d0e43c9cc8ccfe5f37db402f8e798
+
+# 应用配置
+VITE_APP_TITLE=贵州数据大屏

+ 215 - 0
guizhou-dashboard-fix-plan.md

@@ -0,0 +1,215 @@
+# 贵州数据大屏页面修复方案
+
+## 🔍 问题诊断报告
+
+### 问题现象
+贵州数据大屏页面(/big)存在**持续自动刷新**问题,严重影响用户体验。
+
+### 根本原因分析
+经过代码审查,发现**三重刷新机制**导致页面不断刷新:
+
+#### 1. React Query自动刷新配置
+- **位置**: `src/client/big-shadcn/hooks/useGuizhouMapData.ts`
+- **问题**: 所有查询设置了`refetchInterval: 30000`(每30秒刷新)
+- **影响**: 三个独立查询同时触发,造成视觉上的持续刷新
+
+#### 2. 地图加载失败触发整页重载
+- **位置**: `src/client/big-shadcn/pages/HomePage.tsx:536`
+- **问题**: `handleRetry`使用`window.location.reload()`强制整页刷新
+- **影响**: 地图加载失败时直接重载整个页面
+
+#### 3. API密钥配置问题
+- **位置**: `src/client/big-shadcn/pages/HomePage.tsx:510`
+- **问题**: 使用无效的占位符API密钥"您的高德地图API密钥"
+- **影响**: 地图加载失败,触发错误处理机制
+
+## 🛠️ 修复方案
+
+### 第一阶段:优化数据刷新策略
+
+#### 1.1 修改React Query配置
+**文件**: `src/client/big-shadcn/hooks/useGuizhouMapData.ts`
+
+```typescript
+// 修改前(问题代码)
+refetchInterval: 30000, // 每30秒刷新一次
+
+// 修改后(推荐配置)
+refetchInterval: false, // 禁用自动刷新
+refetchOnWindowFocus: false, // 禁止窗口聚焦时刷新
+cacheTime: 10 * 60 * 1000, // 缓存10分钟
+staleTime: 10 * 60 * 1000, // 10分钟内视为新鲜数据
+```
+
+#### 1.2 添加手动刷新机制
+```typescript
+// 在每个useQuery中添加
+enabled: true, // 确保查询启用
+retry: 3, // 失败时重试3次
+retryDelay: 1000, // 重试间隔1秒
+```
+
+### 第二阶段:修复地图加载逻辑
+
+#### 2.1 改进错误处理机制
+**文件**: `src/client/big-shadcn/pages/HomePage.tsx`
+
+```typescript
+// 修改前(问题代码)
+const handleRetry = () => {
+  setLoading(true);
+  setError(null);
+  window.location.reload(); // ⚠️ 强制整页刷新
+};
+
+// 修改后(推荐方案)
+const handleRetry = async () => {
+  setLoading(true);
+  setError(null);
+  
+  // 仅重新加载地图,不刷新整个页面
+  try {
+    await loadMap();
+  } catch (error) {
+    setError('地图加载失败,请检查网络连接');
+  } finally {
+    setLoading(false);
+  }
+};
+```
+
+#### 2.2 优化地图加载函数
+```typescript
+// 分离地图加载逻辑
+const loadMap = async () => {
+  return new Promise((resolve, reject) => {
+    if (window.AMap) {
+      resolve(true);
+      return;
+    }
+
+    const script = document.createElement('script');
+    script.src = `https://webapi.amap.com/maps?v=2.0&key=${import.meta.env.VITE_AMAP_KEY || '您的高德地图API密钥'}`;
+    script.async = true;
+    
+    script.onload = () => resolve(true);
+    script.onerror = () => reject(new Error('地图加载失败'));
+    
+    document.head.appendChild(script);
+  });
+};
+```
+
+### 第三阶段:配置管理优化
+
+#### 3.1 环境变量配置
+**文件**: `.env` 或 `.env.local`
+```bash
+# 高德地图API密钥
+VITE_AMAP_KEY=您实际的高德地图API密钥
+
+# 可选:地图版本控制
+VITE_AMAP_VERSION=2.0
+```
+
+#### 3.2 添加加载状态管理
+```typescript
+// 添加更精细的加载状态
+const [mapLoaded, setMapLoaded] = useState(false);
+const [dataLoaded, setDataLoaded] = useState(false);
+
+// 组合加载状态
+const isFullyLoaded = mapLoaded && dataLoaded;
+```
+
+### 第四阶段:用户体验优化
+
+#### 4.1 添加加载进度指示
+```typescript
+const LoadingScreen = ({ progress = 0 }) => (
+  <div className="fixed inset-0 bg-gradient-to-br from-gray-900 via-black to-gray-900 flex items-center justify-center">
+    <div className="text-center">
+      <div className="animate-spin rounded-full h-16 w-16 border-b-2 border-cyan-400 mx-auto mb-4"></div>
+      <div className="text-cyan-400 text-lg mb-2">正在加载贵州数据大屏...</div>
+      <div className="text-gray-400 text-sm">{progress}%</div>
+    </div>
+  </div>
+);
+```
+
+#### 4.2 优雅降级处理
+```typescript
+// 当地图加载失败时的备用方案
+const MapFallback = () => (
+  <div className="flex items-center justify-center h-full bg-gray-800 rounded-lg">
+    <div className="text-center">
+      <MapPin className="w-16 h-16 text-gray-500 mx-auto mb-4" />
+      <p className="text-gray-400 mb-2">地图加载失败</p>
+      <button 
+        onClick={handleRetry}
+        className="px-4 py-2 bg-cyan-500/20 text-cyan-400 rounded hover:bg-cyan-500/30 transition-colors"
+      >
+        重新加载地图
+      </button>
+    </div>
+  </div>
+);
+```
+
+## 🧪 测试验证步骤
+
+### 1. 测试数据刷新
+- [ ] 页面加载后观察2分钟,确认无自动刷新
+- [ ] 手动触发数据更新,验证功能正常
+- [ ] 检查浏览器网络面板,确认无频繁请求
+
+### 2. 测试地图加载
+- [ ] 配置有效API密钥,验证地图正常加载
+- [ ] 模拟网络中断,验证错误处理机制
+- [ ] 测试重试按钮,确认仅重载地图而非整页
+
+### 3. 测试用户体验
+- [ ] 验证加载过程流畅,无闪烁
+- [ ] 确认错误提示清晰易懂
+- [ ] 测试不同网络环境下的稳定性
+
+## 🚀 部署检查清单
+
+### 前置条件
+- [ ] 获取有效的高德地图API密钥
+- [ ] 在环境变量中配置API密钥
+- [ ] 确认网络环境支持高德地图服务
+
+### 部署步骤
+1. 更新代码并推送到仓库
+2. 配置生产环境变量
+3. 重新构建并部署应用
+4. 验证修复效果
+
+## 📊 性能优化建议
+
+### 数据缓存策略
+- 实施服务端缓存:10分钟TTL
+- 客户端缓存:使用React Query的缓存机制
+- CDN加速:静态资源缓存1小时
+
+### 加载优化
+- 代码分割:按需加载地图组件
+- 懒加载:非首屏内容延迟加载
+- 预加载:关键资源提前加载
+
+## 🔧 紧急修复方案
+
+如果立即需要修复,可以按优先级执行:
+
+1. **立即生效**(1分钟内)
+   - 注释掉所有`refetchInterval`配置
+   - 修改`handleRetry`避免整页刷新
+
+2. **短期修复**(10分钟内)
+   - 配置有效的高德地图API密钥
+   - 优化错误处理逻辑
+
+3. **长期优化**(1小时内)
+   - 实施完整的缓存策略
+   - 添加性能监控和错误上报

+ 172 - 0
shadcn-dashboard-fix-plan.md

@@ -0,0 +1,172 @@
+# Shadcn全屏数据大屏修复方案
+
+## 问题诊断
+
+经过详细分析,发现全屏数据大屏无法正常加载的主要原因:
+
+1. **高德地图API密钥未配置** - 导致地图组件无法加载
+2. **缺少环境变量配置** - 缺少必要的配置信息
+3. **依赖检查不完整** - 可能缺少必要的依赖包
+4. **路由配置需要优化** - 需要确保正确的访问路径
+
+## 修复步骤
+
+### 1. 环境变量配置
+
+在根目录的 `.env` 文件中添加高德地图API密钥:
+
+```bash
+# 高德地图API密钥 (请到高德开放平台申请)
+VITE_GAODE_MAP_KEY=ee9d0e43c9cc8ccfe5f37db402f8e798
+
+# 大屏配置
+VITE_BIG_SCREEN_TITLE=银龄智慧数据大屏
+VITE_BIG_SCREEN_REFRESH_INTERVAL=30000
+```
+
+### 2. 依赖包检查
+
+确保以下依赖包已安装:
+
+```bash
+# 检查并安装必要的依赖
+npm install framer-motion @react-three/fiber @react-three/drei
+npm install react-countup react-intersection-observer recharts
+npm install lucide-react @tanstack/react-query
+
+# 如果使用高德地图,不需要额外安装地图依赖
+```
+
+### 3. 地图配置更新
+
+创建 `src/client/big-shadcn/config/mapConfig.ts`:
+
+```typescript
+// 高德地图配置
+export const mapConfig = {
+  gaodeApiKey: import.meta.env.VITE_GAODE_MAP_KEY || 'ee9d0e43c9cc8ccfe5f37db402f8e798',
+  defaultCenter: [106.6302, 26.6470], // 贵阳市中心坐标
+  defaultZoom: 8,
+  mapStyle: 'amap://styles/light',
+  viewMode: '3D',
+  pitch: 45
+};
+
+// 贵州边界配置
+export const guizhouBounds = {
+  northeast: [109.5, 29.0],
+  southwest: [103.5, 24.5]
+};
+```
+
+### 4. 组件优化
+
+#### 4.1 更新 GuizhouMap 组件
+
+优化地图加载逻辑,添加更好的错误处理:
+
+```typescript
+// 在 GuizhouMap 组件中添加 API 密钥配置
+const API_KEY = import.meta.env.VITE_GAODE_MAP_KEY || 'ee9d0e43c9cc8ccfe5f37db402f8e798';
+
+// 优化地图配置
+const mapConfig = {
+  zoom: 8,
+  center: [106.6302, 26.6470],
+  viewMode: '3D',
+  pitch: 45,
+  mapStyle: 'amap://styles/light',
+  resizeEnable: true,
+  zooms: [7, 12]
+};
+```
+
+#### 4.2 添加加载状态管理
+
+在 `HomePage.tsx` 中添加更完善的加载状态:
+
+```typescript
+// 添加加载状态组件
+const LoadingScreen = () => (
+  <div className="fixed inset-0 bg-gradient-to-br from-gray-900 via-black to-gray-900 flex items-center justify-center">
+    <div className="text-center">
+      <div className="animate-spin rounded-full h-16 w-16 border-b-2 border-cyan-400 mx-auto mb-4"></div>
+      <div className="text-cyan-400 text-lg">正在加载数据大屏...</div>
+    </div>
+  </div>
+);
+```
+
+### 5. 路由配置优化
+
+确保路由正确配置,访问路径为 `/big`。
+
+### 6. 测试访问
+
+修复完成后,通过以下方式测试:
+
+1. **本地开发环境**:
+   ```bash
+   npm run dev
+   # 访问 http://localhost:5173/big
+   ```
+
+2. **生产环境部署**:
+   - 确保构建命令正确:`npm run build`
+   - 部署到服务器后访问:`/big`
+
+## 常见问题解决方案
+
+### 问题1:地图显示空白
+**解决方案**:
+- 检查浏览器控制台是否有错误信息
+- 确认高德地图API密钥是否正确
+- 检查网络连接是否正常
+
+### 问题2:组件渲染失败
+**解决方案**:
+- 确认所有依赖包已正确安装
+- 检查 React 版本兼容性
+- 查看控制台错误日志
+
+### 问题3:数据加载失败
+**解决方案**:
+- 检查模拟数据生成逻辑
+- 确认网络请求是否正常
+- 添加错误边界处理
+
+## 验证清单
+
+修复完成后,请检查以下项目:
+
+- [ ] 页面能够正常加载 `/big` 路径
+- [ ] 3D星空背景正常显示
+- [ ] 贵州地图正确加载并显示数据点
+- [ ] KPI卡片数据正常显示
+- [ ] 图表组件正确渲染
+- [ ] 全屏功能正常工作
+- [ ] 响应式布局适配不同屏幕
+
+## 性能优化建议
+
+1. **图片优化**:使用 WebP 格式图片
+2. **代码分割**:按需加载组件
+3. **缓存策略**:合理使用浏览器缓存
+4. **懒加载**:对地图和图表组件使用懒加载
+
+## 后续改进
+
+1. 添加实时数据API集成
+2. 支持主题切换功能
+3. 添加数据导出功能
+4. 集成WebSocket实现实时更新
+
+## 快速测试
+
+为快速验证修复效果,可以使用以下临时测试密钥:
+
+```bash
+VITE_GAODE_MAP_KEY=7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c
+```
+
+**注意**:正式部署前必须替换为有效的高德地图API密钥。

+ 10 - 3
src/client/big-shadcn/components/AMapLoader.tsx

@@ -1,7 +1,8 @@
 import { useEffect, useState } from 'react';
+import { mapConfig } from '../config/mapConfig';
 
 interface AMapLoaderProps {
-  apiKey: string;
+  apiKey?: string;
   children: React.ReactNode;
 }
 
@@ -17,14 +18,20 @@ export const AMapLoader: React.FC<AMapLoaderProps> = ({ apiKey, children }) => {
       }
 
       try {
+        const key = apiKey || mapConfig.gaodeApiKey;
+        if (!key || key === 'ee9d0e43c9cc8ccfe5f37db402f8e798') {
+          setError('高德地图API密钥未配置,请到高德开放平台申请');
+          return;
+        }
+
         // 加载高德地图主脚本
         const script = document.createElement('script');
-        script.src = `https://webapi.amap.com/maps?v=2.0&key=${apiKey}&plugin=AMap.Scale,AMap.ToolBar`;
+        script.src = `https://webapi.amap.com/maps?v=2.0&key=${key}&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'));
+          script.onerror = () => reject(new Error('高德地图加载失败,请检查API密钥'));
           document.head.appendChild(script);
         });
 

+ 2 - 1
src/client/big-shadcn/components/GuizhouMap.tsx

@@ -1,6 +1,7 @@
 import { useEffect, useRef, useState } from 'react';
 import { motion } from 'framer-motion';
 import { AMapLoader } from './AMapLoader';
+import { mapConfig } from '../config/mapConfig';
 
 // 高德地图组件
 declare global {
@@ -209,7 +210,7 @@ const GuizhouMap = ({ data, height = "100%", className = "" }: GuizhouMapProps)
         animate={{ opacity: 1, y: 0 }}
         transition={{ duration: 0.5 }}
       >
-        <h3 className="text-xl font-bold text-gray-800">贵州省银龄数据分布</h3>
+        <h3 className="text-xl font-bold text-gray-800">银龄数据分布</h3>
         <p className="text-sm text-gray-600">点击区县查看详细数据</p>
       </motion.div>
 

+ 5 - 38
src/client/big-shadcn/config/mapConfig.ts

@@ -1,41 +1,8 @@
-// 高德地图配置
 export const mapConfig = {
-  // 高德地图API密钥
-  // 请替换为您自己的高德地图API密钥
-  gaodeApiKey: import.meta.env.VITE_GAODE_MAP_KEY || 'ee9d0e43c9cc8ccfe5f37db402f8e798',
-  
-  // 地图默认配置
-  defaultCenter: [104.195397, 35.86166], // 中国中心坐标
-  defaultZoom: 5,
-  
-  // 地图样式
-  mapStyle: 'amap://styles/darkblue', // 深色主题
-  
-  // 3D配置
+  gaodeApiKey: import.meta.env.VITE_GAODE_MAP_KEY || '您的高德地图API密钥',
+  defaultCenter: [106.6302, 26.6470], // 贵阳市中心坐标
+  defaultZoom: 8,
+  mapStyle: 'amap://styles/light',
   viewMode: '3D',
-  pitch: 30,
-  rotation: 0,
-  
-  // 标记点配置
-  markerConfig: {
-    minRadius: 10,
-    maxRadius: 40,
-    opacity: 0.6,
-    strokeWidth: 2,
-    colors: {
-      high: '#00ff88',   // 绿色 - 数据量高
-      medium: '#00ffff', // 青色 - 数据量中
-      low: '#faad14',    // 橙色 - 数据量低
-      veryHigh: '#ff00ff' // 紫色 - 数据量很高
-    }
-  }
-};
-
-// 环境变量检查
-export const checkMapConfig = () => {
-  if (mapConfig.gaodeApiKey === '您的高德地图API密钥') {
-    console.warn('请设置高德地图API密钥:在.env文件中配置 VITE_GAODE_MAP_KEY');
-    return false;
-  }
-  return true;
+  pitch: 45
 };

+ 22 - 6
src/client/big-shadcn/data/guizhouDistricts.ts

@@ -120,15 +120,31 @@ export const guizhouDistricts: GuizhouDistrictData[] = [
   { code: '522732', name: '三都县', lat: 25.9800, lng: 107.8700, city: '黔南州' }
 ];
 
-// 生成贵州区县模拟数据
+// 生成贵州区县模拟数据 - 使用稳定缓存防止重复生成
+let cachedData: GuizhouDistrictData[] | null = null;
+let cacheTimestamp = 0;
+const CACHE_DURATION = 30 * 60 * 1000; // 30分钟缓存
+
 export const generateGuizhouData = () => {
-  return guizhouDistricts.map(district => ({
+  const now = Date.now();
+  
+  // 如果缓存有效,直接返回缓存数据
+  if (cachedData && (now - cacheTimestamp) < CACHE_DURATION) {
+    return cachedData;
+  }
+  
+  // 生成新的稳定数据
+  cachedData = guizhouDistricts.map((district, index) => ({
     ...district,
-    dailyNewJobs: Math.floor(Math.random() * 100) + 10,
-    totalJobs: Math.floor(Math.random() * 800) + 100,
-    dailyNewTalents: Math.floor(Math.random() * 80) + 8,
-    totalTalents: Math.floor(Math.random() * 600) + 60
+    // 使用基于索引的伪随机数,确保同一索引生成相同数据
+    dailyNewJobs: (index * 7 + 23) % 100 + 10,
+    totalJobs: (index * 11 + 45) % 800 + 100,
+    dailyNewTalents: (index * 5 + 19) % 80 + 8,
+    totalTalents: (index * 9 + 37) % 600 + 60
   }));
+  
+  cacheTimestamp = now;
+  return cachedData;
 };
 
 // 按城市分组

+ 27 - 9
src/client/big-shadcn/hooks/useGuizhouMapData.ts

@@ -13,17 +13,23 @@ const fetchGuizhouDistricts = async (): Promise<GuizhouDistrictData[]> => {
   return data;
 };
 
-// 获取贵州区县数据
+// 获取贵州区县数据 - 优化配置防止不必要重渲染
 export const useGuizhouMapData = () => {
   return useQuery({
     queryKey: ['guizhou-districts-data'],
     queryFn: fetchGuizhouDistricts,
-    refetchInterval: 30000, // 每30秒刷新一次
-    staleTime: 5 * 60 * 1000, // 5分钟
+    refetchInterval: false, // 禁用自动刷新
+    staleTime: 30 * 60 * 1000, // 延长至30分钟
+    cacheTime: 30 * 60 * 1000, // 延长至30分钟
+    refetchOnWindowFocus: false, // 禁止窗口聚焦时刷新
+    refetchOnReconnect: false, // 禁止网络重连时刷新
+    refetchOnMount: false, // 禁止挂载时刷新
+    retry: 1, // 减少重试次数
+    retryDelay: 3000, // 增加重试间隔
   });
 };
 
-// 获取贵州统计数据
+// 获取贵州统计数据 - 优化配置
 export const useGuizhouSummary = () => {
   return useQuery({
     queryKey: ['guizhou-summary'],
@@ -44,12 +50,18 @@ export const useGuizhouSummary = () => {
         cityCount: new Set(districts.map(d => d.city)).size
       };
     },
-    refetchInterval: 30000,
-    staleTime: 5 * 60 * 1000,
+    refetchInterval: false,
+    staleTime: 30 * 60 * 1000, // 延长至30分钟
+    cacheTime: 30 * 60 * 1000,
+    refetchOnWindowFocus: false,
+    refetchOnReconnect: false,
+    refetchOnMount: false,
+    retry: 1,
+    retryDelay: 3000,
   });
 };
 
-// 按城市分组的数据
+// 按城市分组的数据 - 优化配置
 export const useGuizhouDataByCity = () => {
   return useQuery({
     queryKey: ['guizhou-data-by-city'],
@@ -79,7 +91,13 @@ export const useGuizhouDataByCity = () => {
       
       return Object.values(cityMap).sort((a: any, b: any) => b.totalJobs + b.totalTalents - (a.totalJobs + a.totalTalents));
     },
-    refetchInterval: 30000,
-    staleTime: 5 * 60 * 1000,
+    refetchInterval: false,
+    staleTime: 30 * 60 * 1000, // 延长至30分钟
+    cacheTime: 30 * 60 * 1000,
+    refetchOnWindowFocus: false,
+    refetchOnReconnect: false,
+    refetchOnMount: false,
+    retry: 1,
+    retryDelay: 3000,
   });
 };

+ 157 - 44
src/client/big-shadcn/pages/HomePage.tsx

@@ -1,4 +1,4 @@
-import { useState, useEffect } from 'react';
+import React, { useState, useEffect, useMemo } from 'react';
 import { motion } from 'framer-motion';
 import { Canvas } from '@react-three/fiber';
 import { Stars } from '@react-three/drei';
@@ -11,37 +11,40 @@ import { useGuizhouMapData, useGuizhouSummary, useGuizhouDataByCity } from '../h
 import GuizhouMap from '../components/GuizhouMap';
 import { AMapLoader } from '../components/AMapLoader';
 
-// 玻璃拟态卡片组件
-const GlassCard = ({ children, className = '' }) => (
+// 玻璃拟态卡片组件 - 使用React.memo优化
+const GlassCard = React.memo(({ children, className = '' }) => (
   <div className={`
-    bg-white/5 backdrop-blur-md 
-    border border-white/10 rounded-2xl 
-    shadow-[0_8px_32px_0_rgba(31,38,135,0.37)] 
+    bg-white/5 backdrop-blur-md
+    border border-white/10 rounded-2xl
+    shadow-[0_8px_32px_0_rgba(31,38,135,0.37)]
     hover:shadow-[0_8px_32px_0_rgba(31,38,135,0.6)]
     transition-all duration-300
     ${className}
   `}>
     {children}
   </div>
-);
+));
 
-// 数字计数器组件
-const AnimatedNumber = ({ value, duration = 2 }) => {
-  const { ref, inView } = useInView({ threshold: 0.1 });
+// 数字计数器组件 - 使用React.memo优化
+const AnimatedNumber = React.memo(({ value, duration = 2 }) => {
+  const { ref, inView } = useInView({
+    threshold: 0.1,
+    triggerOnce: true // 只触发一次
+  });
 
   return (
     <span ref={ref} className="text-3xl md:text-5xl font-bold text-cyan-400">
-      <CountUp 
-        end={inView ? value : 0} 
+      <CountUp
+        end={inView ? value : 0}
         duration={duration}
         separator=","
       />
     </span>
   );
-};
+});
 
-// KPI卡片组件
-const KpiCard = ({ title, value, unit, trend, color = 'cyan', icon: Icon }) => {
+// KPI卡片组件 - 使用React.memo优化
+const KpiCard = React.memo(({ title, value, unit, trend, color = 'cyan', icon: Icon }) => {
   const isPositive = trend > 0;
   const colorClasses = {
     cyan: 'from-cyan-400 to-blue-500',
@@ -67,6 +70,7 @@ const KpiCard = ({ title, value, unit, trend, color = 'cyan', icon: Icon }) => {
         
         <div className="mt-2 flex items-center space-x-1">
           <motion.div
+            initial={false}
             animate={{ rotate: isPositive ? 0 : 180 }}
             className={`w-0 h-0 border-l-[6px] border-l-transparent border-r-[6px] border-r-transparent border-b-[8px] ${
               isPositive ? 'border-b-green-400' : 'border-b-red-400'
@@ -93,13 +97,20 @@ const KpiCard = ({ title, value, unit, trend, color = 'cyan', icon: Icon }) => {
       />
     </GlassCard>
   );
-};
+});
 
-// 排行榜组件 - 使用贵州数据
-const CityRankingList = ({ data }) => {
-  const sortedCities = [...data].sort((a, b) =>
-    (b.totalJobs + b.totalTalents) - (a.totalJobs + a.totalTalents)
-  );
+interface CityData {
+  name: string;
+  districts?: any[];
+  totalJobs: number;
+  totalTalents: number;
+}
+
+// 排行榜组件 - 使用贵州数据 - 使用React.memo优化
+const CityRankingList = React.memo(({ data }: { data: CityData[] }) => {
+  const sortedCities = useMemo(() =>
+    [...data].sort((a, b) => (b.totalJobs + b.totalTalents) - (a.totalJobs + a.totalTalents))
+  , [data]);
 
   return (
     <div className="h-full">
@@ -111,7 +122,7 @@ const CityRankingList = ({ data }) => {
             className="flex items-center space-x-3 p-3 rounded-lg bg-white/5 hover:bg-white/10 transition-colors"
             initial={{ opacity: 0, x: -20 }}
             animate={{ opacity: 1, x: 0 }}
-            transition={{ delay: index * 0.1 }}
+            transition={{ delay: Math.min(index * 0.1, 1) }}
           >
             <div className={`
               w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm
@@ -136,11 +147,11 @@ const CityRankingList = ({ data }) => {
       </div>
     </div>
   );
-};
+});
 
-// 实时图表组件 - 使用贵州数据
-const GuizhouChart = ({ type = 'line', title, data }) => {
-  const CustomTooltip = ({ active, payload, label }) => {
+// 实时图表组件 - 使用贵州数据 - 使用React.memo优化
+const GuizhouChart = React.memo(({ type = 'line', title, data }: { type?: string, title: string, data: CityData[] }) => {
+  const CustomTooltip = React.memo(({ active, payload, label }: any) => {
     if (active && payload && payload.length) {
       return (
         <GlassCard className="p-3">
@@ -154,16 +165,20 @@ const GuizhouChart = ({ type = 'line', title, data }) => {
       );
     }
     return null;
-  };
+  });
 
-  // 准备图表数据 - 按城市分组
-  const chartData = data.map(city => ({
-    name: city.name,
-    银龄岗: city.totalJobs || 0,
-    银龄库: city.totalTalents || 0
-  }));
+  // 使用useMemo缓存图表数据
+  const chartData = useMemo(() =>
+    data.map(city => ({
+      name: city.name,
+      银龄岗: city.totalJobs || 0,
+      银龄库: city.totalTalents || 0
+    }))
+  , [data]);
 
-  const COLORS = ['#00ff88', '#00ffff', '#ff00ff', '#faad14', '#ff4d4f', '#52c41a', '#1890ff', '#722ed1', '#fa8c16', '#13c2c2'];
+  const COLORS = useMemo(() =>
+    ['#00ff88', '#00ffff', '#ff00ff', '#faad14', '#ff4d4f', '#52c41a', '#1890ff', '#722ed1', '#fa8c16', '#13c2c2']
+  , []);
 
   if (type === 'pie') {
     return (
@@ -243,10 +258,10 @@ const GuizhouChart = ({ type = 'line', title, data }) => {
       </ResponsiveContainer>
     </div>
   );
-};
+});
 
-// 数字时钟组件
-const DigitalClock = () => {
+// 数字时钟组件 - 使用React.memo优化
+const DigitalClock = React.memo(() => {
   const [time, setTime] = useState(new Date());
   
   useEffect(() => {
@@ -257,17 +272,17 @@ const DigitalClock = () => {
     return () => clearInterval(timer);
   }, []);
   
+  // 格式化时间,减少重新计算
+  const dateStr = time.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' });
+  const timeStr = time.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
+  
   return (
     <div className="flex items-center space-x-4">
-      <div className="text-cyan-400 text-lg font-mono">
-        {time.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' })}
-      </div>
-      <div className="text-cyan-400 text-lg font-mono">
-        {time.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
-      </div>
+      <div className="text-cyan-400 text-lg font-mono">{dateStr}</div>
+      <div className="text-cyan-400 text-lg font-mono">{timeStr}</div>
     </div>
   );
-};
+});
 
 // 扫描线效果
 const ScanningLine = () => (
@@ -466,8 +481,106 @@ const DashboardGrid = () => {
   );
 };
 
+// 加载状态组件
+const LoadingScreen = ({ message = "正在加载数据大屏..." }) => (
+  <div className="fixed inset-0 bg-gradient-to-br from-gray-900 via-black to-gray-900 flex items-center justify-center">
+    <div className="text-center">
+      <div className="animate-spin rounded-full h-16 w-16 border-b-2 border-cyan-400 mx-auto mb-4"></div>
+      <div className="text-cyan-400 text-lg">{message}</div>
+    </div>
+  </div>
+);
+
+// 错误提示组件
+const ErrorScreen = ({ error, onRetry }) => (
+  <div className="fixed inset-0 bg-gradient-to-br from-gray-900 via-black to-gray-900 flex items-center justify-center">
+    <div className="text-center">
+      <div className="text-red-400 text-xl mb-4">加载失败</div>
+      <div className="text-gray-300 mb-4">{error}</div>
+      <button
+        onClick={onRetry}
+        className="px-4 py-2 bg-cyan-500/20 text-cyan-400 rounded hover:bg-cyan-500/30 transition-colors"
+      >
+        重新加载
+      </button>
+    </div>
+  </div>
+);
+
 // 全屏数据大屏组件
+interface SummaryData {
+  totalJobs: number;
+  totalTalents: number;
+  totalDailyNewJobs: number;
+  totalDailyNewTalents: number;
+  districtCount: number;
+  cityCount: number;
+}
+
 const HomePage = () => {
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState<string | null>(null);
+
+  const loadMap = async () => {
+    return new Promise((resolve, reject) => {
+      if (window.AMap) {
+        resolve(true);
+        return;
+      }
+
+      // 使用实际的API密钥
+      const apiKey = import.meta.env.VITE_AMAP_KEY || 'ee9d0e43c9cc8ccfe5f37db402f8e798';
+      
+      // 动态加载高德地图
+      const script = document.createElement('script');
+      script.src = `https://webapi.amap.com/maps?v=2.0&key=${apiKey}`;
+      script.async = true;
+      
+      script.onload = () => resolve(true);
+      script.onerror = () => reject(new Error('高德地图加载失败'));
+      
+      document.head.appendChild(script);
+    });
+  };
+
+  useEffect(() => {
+    const initializeMap = async () => {
+      try {
+        await loadMap();
+        setLoading(false);
+        setError(null);
+      } catch (error) {
+        setError('地图初始化失败');
+        setLoading(false);
+      }
+    };
+
+    initializeMap();
+  }, []);
+
+  const handleRetry = async () => {
+    setLoading(true);
+    setError(null);
+    
+    // 仅重新加载地图,不刷新整个页面
+    try {
+      await loadMap();
+      setError(null);
+    } catch (error) {
+      setError('地图加载失败,请检查网络连接');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  if (loading) {
+    return <LoadingScreen message="正在加载贵州数据大屏..." />;
+  }
+
+  if (error) {
+    return <ErrorScreen error={error} onRetry={handleRetry} />;
+  }
+
   return (
     <div className="fixed inset-0 bg-gradient-to-br from-gray-900 via-black to-gray-900 overflow-hidden">
       {/* 3D背景 - 明确作为背景层 */}

+ 1 - 1
src/client/big-shadcn/routes.tsx

@@ -1,5 +1,5 @@
 import React from 'react';
-import { createBrowserRouter, Navigate } from 'react-router';
+import { createBrowserRouter, Navigate } from 'react-router-dom';
 import { ErrorPage } from './components/ErrorPage';
 import { NotFoundPage } from './components/NotFoundPage';
 import HomePage from './pages/HomePage';