|
|
@@ -1,9 +1,511 @@
|
|
|
-import React from 'react';
|
|
|
+import { useState, useEffect } from 'react';
|
|
|
+import { motion } from 'framer-motion';
|
|
|
+import { Canvas } from '@react-three/fiber';
|
|
|
+import { Stars } from '@react-three/drei';
|
|
|
+import { useQuery } from '@tanstack/react-query';
|
|
|
+import CountUp from 'react-countup';
|
|
|
+import { useInView } from 'react-intersection-observer';
|
|
|
+import { MapContainer, TileLayer, CircleMarker, Popup } from 'react-leaflet';
|
|
|
+import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
|
|
+import 'leaflet/dist/leaflet.css';
|
|
|
|
|
|
-const HomePage: React.FC = () => {
|
|
|
+// 玻璃拟态卡片组件
|
|
|
+const GlassCard = ({ 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)]
|
|
|
+ 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 });
|
|
|
+
|
|
|
+ return (
|
|
|
+ <span ref={ref} className="text-3xl md:text-5xl font-bold text-cyan-400">
|
|
|
+ <CountUp
|
|
|
+ end={inView ? value : 0}
|
|
|
+ duration={duration}
|
|
|
+ separator=","
|
|
|
+ />
|
|
|
+ </span>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+// KPI卡片组件
|
|
|
+const KpiCard = ({ title, value, unit, trend, color = 'cyan' }) => {
|
|
|
+ const isPositive = trend > 0;
|
|
|
+ const colorClasses = {
|
|
|
+ cyan: 'from-cyan-400 to-blue-500',
|
|
|
+ green: 'from-green-400 to-emerald-500',
|
|
|
+ red: 'from-red-400 to-pink-500',
|
|
|
+ purple: 'from-purple-400 to-indigo-500',
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <GlassCard className="p-4 md:p-6 relative overflow-hidden">
|
|
|
+ {/* 背景光效 */}
|
|
|
+ <div className={`absolute inset-0 bg-gradient-to-br ${colorClasses[color]} opacity-10`} />
|
|
|
+
|
|
|
+ <div className="relative z-10">
|
|
|
+ <h3 className="text-sm md:text-lg text-gray-300 mb-2">{title}</h3>
|
|
|
+ <div className="flex items-baseline space-x-2">
|
|
|
+ <AnimatedNumber value={value} />
|
|
|
+ <span className="text-lg text-gray-400">{unit}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="mt-2 flex items-center space-x-1">
|
|
|
+ <motion.div
|
|
|
+ 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'
|
|
|
+ }`}
|
|
|
+ />
|
|
|
+ <span className={`text-sm ${isPositive ? 'text-green-400' : 'text-red-400'}`}>
|
|
|
+ {Math.abs(trend)}%
|
|
|
+ </span>
|
|
|
+ <span className="text-xs text-gray-500">vs 上期</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 脉冲效果 */}
|
|
|
+ <motion.div
|
|
|
+ className="absolute top-2 right-2 w-2 h-2 bg-green-400 rounded-full"
|
|
|
+ animate={{
|
|
|
+ scale: [1, 1.5, 1],
|
|
|
+ opacity: [1, 0.5, 1],
|
|
|
+ }}
|
|
|
+ transition={{
|
|
|
+ duration: 2,
|
|
|
+ repeat: Infinity,
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </GlassCard>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+// 实时地图组件
|
|
|
+const RealTimeMap = () => {
|
|
|
+ const [positions, setPositions] = useState([
|
|
|
+ { id: 1, lat: 39.9042, lng: 116.4074, deviceId: '银龄智慧001', status: '正常' },
|
|
|
+ { id: 2, lat: 31.2304, lng: 121.4737, deviceId: '银龄智慧002', status: '正常' },
|
|
|
+ { id: 3, lat: 22.5431, lng: 114.0579, deviceId: '银龄智慧003', status: '警告' },
|
|
|
+ { id: 4, lat: 39.0842, lng: 117.2006, deviceId: '银龄智慧004', status: '正常' },
|
|
|
+ { id: 5, lat: 30.2796, lng: 120.1597, deviceId: '银龄智慧005', status: '正常' },
|
|
|
+ { id: 6, lat: 23.1291, lng: 113.2644, deviceId: '银龄智慧006', status: '正常' },
|
|
|
+ { id: 7, lat: 36.6512, lng: 117.1200, deviceId: '银龄智慧007', status: '故障' },
|
|
|
+ { id: 8, lat: 29.5630, lng: 106.5516, deviceId: '银龄智慧008', status: '正常' },
|
|
|
+ { id: 9, lat: 34.2632, lng: 108.9480, deviceId: '银龄智慧009', status: '正常' },
|
|
|
+ { id: 10, lat: 25.0389, lng: 102.7183, deviceId: '银龄智慧010', status: '警告' },
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 模拟数据更新
|
|
|
+ useEffect(() => {
|
|
|
+ const interval = setInterval(() => {
|
|
|
+ setPositions(prev => prev.map(pos => ({
|
|
|
+ ...pos,
|
|
|
+ status: Math.random() > 0.9 ? '警告' : pos.status,
|
|
|
+ lng: pos.lng + (Math.random() - 0.5) * 0.01
|
|
|
+ })));
|
|
|
+ }, 5000);
|
|
|
+
|
|
|
+ return () => clearInterval(interval);
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="h-full w-full rounded-xl overflow-hidden relative">
|
|
|
+ <MapContainer
|
|
|
+ center={[35.8617, 104.1954]}
|
|
|
+ zoom={4}
|
|
|
+ className="h-full w-full"
|
|
|
+ style={{ background: 'transparent' }}
|
|
|
+ >
|
|
|
+ <TileLayer
|
|
|
+ url="https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png"
|
|
|
+ attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
|
|
+ />
|
|
|
+
|
|
|
+ {positions.map((pos) => (
|
|
|
+ <CircleMarker
|
|
|
+ key={pos.id}
|
|
|
+ center={[pos.lat, pos.lng]}
|
|
|
+ radius={pos.status === '故障' ? 10 : 8}
|
|
|
+ pathOptions={{
|
|
|
+ fillColor: pos.status === '故障' ? '#ff4d4f' : pos.status === '警告' ? '#faad14' : '#00ff88',
|
|
|
+ color: pos.status === '故障' ? '#ff4d4f' : pos.status === '警告' ? '#faad14' : '#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().toLocaleTimeString()}</p>
|
|
|
+ </div>
|
|
|
+ </Popup>
|
|
|
+ </CircleMarker>
|
|
|
+ ))}
|
|
|
+ </MapContainer>
|
|
|
+
|
|
|
+ {/* 地图遮罩 */}
|
|
|
+ <div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent pointer-events-none" />
|
|
|
+
|
|
|
+ {/* 地图标题 */}
|
|
|
+ <div className="absolute top-4 left-4 bg-black/50 backdrop-blur-sm px-4 py-2 rounded-lg border border-white/10">
|
|
|
+ <h3 className="text-xl font-bold text-white">银龄智慧服务覆盖地图</h3>
|
|
|
+ <p className="text-sm text-gray-300">实时监控全国服务节点状态</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+// 实时图表组件
|
|
|
+const RealTimeChart = ({ type = 'line', title, dataKey = 'value' }) => {
|
|
|
+ // 生成模拟数据
|
|
|
+ const generateData = () => {
|
|
|
+ const data = [];
|
|
|
+ const now = new Date();
|
|
|
+
|
|
|
+ for (let i = 59; i >= 0; i--) {
|
|
|
+ const time = new Date(now.getTime() - i * 60000);
|
|
|
+ const formattedTime = time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
|
+
|
|
|
+ if (type === 'line') {
|
|
|
+ data.push({
|
|
|
+ time: formattedTime,
|
|
|
+ value: Math.floor(Math.random() * 1000) + 500
|
|
|
+ });
|
|
|
+ } else if (type === 'bar') {
|
|
|
+ data.push({
|
|
|
+ time: formattedTime,
|
|
|
+ value: Math.floor(Math.random() * 500) + 100
|
|
|
+ });
|
|
|
+ } else if (type === 'pie') {
|
|
|
+ return [
|
|
|
+ { name: '华东', value: 35 },
|
|
|
+ { name: '华北', value: 25 },
|
|
|
+ { name: '华南', value: 20 },
|
|
|
+ { name: '西部', value: 15 },
|
|
|
+ { name: '东北', value: 5 }
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return data;
|
|
|
+ };
|
|
|
+
|
|
|
+ const [chartData, setChartData] = useState(generateData());
|
|
|
+
|
|
|
+ // 模拟数据更新
|
|
|
+ useEffect(() => {
|
|
|
+ const interval = setInterval(() => {
|
|
|
+ setChartData(generateData());
|
|
|
+ }, 5000);
|
|
|
+
|
|
|
+ return () => clearInterval(interval);
|
|
|
+ }, [type]);
|
|
|
+
|
|
|
+ const CustomTooltip = ({ active, payload, label }) => {
|
|
|
+ if (active && payload && payload.length) {
|
|
|
+ return (
|
|
|
+ <GlassCard className="p-2">
|
|
|
+ <p className="text-cyan-400">{`${label}`}</p>
|
|
|
+ <p className="text-white">{`${payload[0].value}`}</p>
|
|
|
+ </GlassCard>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 饼图特殊处理
|
|
|
+ if (type === 'pie') {
|
|
|
+ return (
|
|
|
+ <div className="h-full w-full">
|
|
|
+ <h3 className="text-lg font-semibold text-cyan-400 mb-4">{title}</h3>
|
|
|
+ <ResponsiveContainer width="100%" height="80%">
|
|
|
+ <AreaChart data={chartData}>
|
|
|
+ <defs>
|
|
|
+ <linearGradient id="colorGradient" x1="0" y1="0" x2="0" y2="1">
|
|
|
+ <stop offset="5%" stopColor="#00ff88" stopOpacity={0.8}/>
|
|
|
+ <stop offset="95%" stopColor="#00ff88" stopOpacity={0.1}/>
|
|
|
+ </linearGradient>
|
|
|
+ </defs>
|
|
|
+ <CartesianGrid strokeDasharray="3 3" stroke="#ffffff20" />
|
|
|
+ <XAxis
|
|
|
+ dataKey="name"
|
|
|
+ stroke="#ffffff60"
|
|
|
+ tick={{ fill: '#ffffff80' }}
|
|
|
+ />
|
|
|
+ <YAxis
|
|
|
+ stroke="#ffffff60"
|
|
|
+ tick={{ fill: '#ffffff80' }}
|
|
|
+ />
|
|
|
+ <Tooltip content={<CustomTooltip />} />
|
|
|
+ <Area
|
|
|
+ type="monotone"
|
|
|
+ dataKey="value"
|
|
|
+ stroke="#00ff88"
|
|
|
+ strokeWidth={2}
|
|
|
+ fillOpacity={1}
|
|
|
+ fill="url(#colorGradient)"
|
|
|
+ />
|
|
|
+ </AreaChart>
|
|
|
+ </ResponsiveContainer>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="h-full w-full">
|
|
|
+ <h3 className="text-lg font-semibold text-cyan-400 mb-4">{title}</h3>
|
|
|
+ <ResponsiveContainer width="100%" height="80%">
|
|
|
+ <AreaChart data={chartData}>
|
|
|
+ <defs>
|
|
|
+ <linearGradient id="colorGradient" x1="0" y1="0" x2="0" y2="1">
|
|
|
+ <stop offset="5%" stopColor="#00ff88" stopOpacity={0.8}/>
|
|
|
+ <stop offset="95%" stopColor="#00ff88" stopOpacity={0.1}/>
|
|
|
+ </linearGradient>
|
|
|
+ </defs>
|
|
|
+ <CartesianGrid strokeDasharray="3 3" stroke="#ffffff20" />
|
|
|
+ <XAxis
|
|
|
+ dataKey="time"
|
|
|
+ stroke="#ffffff60"
|
|
|
+ tick={{ fill: '#ffffff80' }}
|
|
|
+ />
|
|
|
+ <YAxis
|
|
|
+ stroke="#ffffff60"
|
|
|
+ tick={{ fill: '#ffffff80' }}
|
|
|
+ />
|
|
|
+ <Tooltip content={<CustomTooltip />} />
|
|
|
+ <Area
|
|
|
+ type="monotone"
|
|
|
+ dataKey={dataKey}
|
|
|
+ stroke="#00ff88"
|
|
|
+ strokeWidth={2}
|
|
|
+ fillOpacity={1}
|
|
|
+ fill="url(#colorGradient)"
|
|
|
+ />
|
|
|
+ </AreaChart>
|
|
|
+ </ResponsiveContainer>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+// 排行榜组件
|
|
|
+const RankingList = () => {
|
|
|
+ const [rankings] = useState([
|
|
|
+ { id: 1, name: '上海银龄服务中心', value: '服务人数: 12,589', score: 98.7 },
|
|
|
+ { id: 2, name: '北京智慧养老社区', value: '服务人数: 9,842', score: 96.5 },
|
|
|
+ { id: 3, name: '广州颐养中心', value: '服务人数: 8,756', score: 94.2 },
|
|
|
+ { id: 4, name: '深圳老年公寓', value: '服务人数: 7,412', score: 92.8 },
|
|
|
+ { id: 5, name: '杭州康养社区', value: '服务人数: 6,953', score: 90.5 },
|
|
|
+ { id: 6, name: '南京夕阳红公寓', value: '服务人数: 5,871', score: 88.3 },
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="h-full">
|
|
|
+ <h3 className="text-lg font-semibold text-cyan-400 mb-4">服务质量排行</h3>
|
|
|
+ <div className="space-y-2">
|
|
|
+ {rankings.map((item, index) => (
|
|
|
+ <motion.div
|
|
|
+ key={item.id}
|
|
|
+ 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 }}
|
|
|
+ >
|
|
|
+ <div className={`
|
|
|
+ w-8 h-8 rounded-full flex items-center justify-center font-bold
|
|
|
+ ${index === 0 ? 'bg-yellow-500 text-black' : ''}
|
|
|
+ ${index === 1 ? 'bg-gray-400 text-black' : ''}
|
|
|
+ ${index === 2 ? 'bg-orange-600 text-white' : ''}
|
|
|
+ ${index > 2 ? 'bg-gray-600 text-white' : ''}
|
|
|
+ `}>
|
|
|
+ {index + 1}
|
|
|
+ </div>
|
|
|
+ <div className="flex-1">
|
|
|
+ <p className="text-white font-medium">{item.name}</p>
|
|
|
+ <p className="text-gray-400 text-sm">{item.value}</p>
|
|
|
+ </div>
|
|
|
+ <div className="text-cyan-400 font-bold">
|
|
|
+ {item.score}
|
|
|
+ </div>
|
|
|
+ </motion.div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+// 数字时钟组件
|
|
|
+const DigitalClock = () => {
|
|
|
+ const [time, setTime] = useState(new Date());
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ const timer = setInterval(() => {
|
|
|
+ setTime(new Date());
|
|
|
+ }, 1000);
|
|
|
+
|
|
|
+ return () => clearInterval(timer);
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="flex items-center space-x-2">
|
|
|
+ <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>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+// 扫描线效果
|
|
|
+const ScanningLine = () => (
|
|
|
+ <motion.div
|
|
|
+ className="absolute inset-0 pointer-events-none"
|
|
|
+ animate={{
|
|
|
+ background: [
|
|
|
+ 'linear-gradient(transparent 0%, rgba(0,255,136,0.1) 50%, transparent 100%)',
|
|
|
+ 'linear-gradient(transparent 100%, rgba(0,255,136,0.1) 50%, transparent 0%)',
|
|
|
+ ],
|
|
|
+ }}
|
|
|
+ transition={{
|
|
|
+ duration: 2,
|
|
|
+ repeat: Infinity,
|
|
|
+ ease: "linear",
|
|
|
+ }}
|
|
|
+ style={{
|
|
|
+ backgroundSize: '100% 200%',
|
|
|
+ backgroundPosition: '0% 0%',
|
|
|
+ }}
|
|
|
+ />
|
|
|
+);
|
|
|
+
|
|
|
+// 主仪表盘组件
|
|
|
+const DashboardGrid = () => (
|
|
|
+ <div className="flex-1 p-4 md:p-8 grid grid-cols-12 gap-4 auto-rows-fr">
|
|
|
+ {/* 顶部标题区 */}
|
|
|
+ <motion.div
|
|
|
+ className="col-span-12 h-20"
|
|
|
+ initial={{ opacity: 0, y: -50 }}
|
|
|
+ animate={{ opacity: 1, y: 0 }}
|
|
|
+ transition={{ duration: 0.8 }}
|
|
|
+ >
|
|
|
+ <div className="h-full glass-card rounded-xl flex items-center justify-between px-6">
|
|
|
+ <h1 className="text-2xl md:text-4xl font-bold bg-gradient-to-r from-cyan-400 to-purple-400 bg-clip-text text-transparent">
|
|
|
+ 银龄智慧数据大屏
|
|
|
+ </h1>
|
|
|
+ <DigitalClock />
|
|
|
+ </div>
|
|
|
+ </motion.div>
|
|
|
+
|
|
|
+ {/* 左侧统计区 */}
|
|
|
+
|
|
|
+ {/* 左侧上部分 - 四大核心指标 */}
|
|
|
+ <motion.div
|
|
|
+ className="col-span-12 md:col-span-3 space-y-4"
|
|
|
+ initial={{ opacity: 0, x: -50 }}
|
|
|
+ animate={{ opacity: 1, x: 0 }}
|
|
|
+ transition={{ duration: 0.8, delay: 0.2 }}
|
|
|
+ >
|
|
|
+ <KpiCard title="服务人数" value={123456} unit="人" trend={12.5} color="cyan" />
|
|
|
+ <KpiCard title="服务时长" value={89012} unit="小时" trend={8.3} color="green" />
|
|
|
+ <KpiCard title="异常事件" value={42} unit="起" trend={-5.2} color="red" />
|
|
|
+ <KpiCard title="满意度" value={98.7} unit="%" trend={2.1} color="purple" />
|
|
|
+ </motion.div>
|
|
|
+
|
|
|
+ {/* 中间主图表区 */}
|
|
|
+ <motion.div
|
|
|
+ className="col-span-12 md:col-span-6"
|
|
|
+ initial={{ opacity: 0, scale: 0.9 }}
|
|
|
+ animate={{ opacity: 1, scale: 1 }}
|
|
|
+ transition={{ duration: 0.8, delay: 0.4 }}
|
|
|
+ >
|
|
|
+ <div className="h-full glass-card rounded-xl p-4">
|
|
|
+ <RealTimeMap />
|
|
|
+ </div>
|
|
|
+ </motion.div>
|
|
|
+
|
|
|
+ {/* 右侧排行榜 */}
|
|
|
+ <motion.div
|
|
|
+ className="col-span-12 md:col-span-3"
|
|
|
+ initial={{ opacity: 0, x: 50 }}
|
|
|
+ animate={{ opacity: 1, x: 0 }}
|
|
|
+ transition={{ duration: 0.8, delay: 0.6 }}
|
|
|
+ >
|
|
|
+ <div className="h-full glass-card rounded-xl p-4">
|
|
|
+ <RankingList />
|
|
|
+ </div>
|
|
|
+ </motion.div>
|
|
|
+
|
|
|
+ {/* 底部图表区 */}
|
|
|
+ <motion.div
|
|
|
+ className="col-span-12 md:col-span-4"
|
|
|
+ initial={{ opacity: 0, y: 50 }}
|
|
|
+ animate={{ opacity: 1, y: 0 }}
|
|
|
+ transition={{ duration: 0.8, delay: 0.8 }}
|
|
|
+ >
|
|
|
+ <div className="h-64 glass-card rounded-xl p-4">
|
|
|
+ <RealTimeChart type="line" title="服务趋势" />
|
|
|
+ </div>
|
|
|
+ </motion.div>
|
|
|
+
|
|
|
+ <motion.div
|
|
|
+ className="col-span-12 md:col-span-4"
|
|
|
+ initial={{ opacity: 0, y: 50 }}
|
|
|
+ animate={{ opacity: 1, y: 0 }}
|
|
|
+ transition={{ duration: 0.8, delay: 1.0 }}
|
|
|
+ >
|
|
|
+ <div className="h-64 glass-card rounded-xl p-4">
|
|
|
+ <RealTimeChart type="bar" title="设备分布" />
|
|
|
+ </div>
|
|
|
+ </motion.div>
|
|
|
+
|
|
|
+ <motion.div
|
|
|
+ className="col-span-12 md:col-span-4"
|
|
|
+ initial={{ opacity: 0, y: 50 }}
|
|
|
+ animate={{ opacity: 1, y: 0 }}
|
|
|
+ transition={{ duration: 0.8, delay: 1.2 }}
|
|
|
+ >
|
|
|
+ <div className="h-64 glass-card rounded-xl p-4">
|
|
|
+ <RealTimeChart type="pie" title="区域分布" />
|
|
|
+ </div>
|
|
|
+ </motion.div>
|
|
|
+ </div>
|
|
|
+);
|
|
|
+
|
|
|
+// 全屏数据大屏组件
|
|
|
+const HomePage = () => {
|
|
|
return (
|
|
|
- <div className="min-h-screen">
|
|
|
+ <div className="fixed inset-0 bg-gradient-to-br from-gray-900 via-black to-gray-900 overflow-hidden">
|
|
|
+ {/* 3D背景 */}
|
|
|
+ <Canvas className="absolute inset-0">
|
|
|
+ <Stars radius={100} depth={50} count={5000} factor={4} saturation={0} fade speed={1} />
|
|
|
+ </Canvas>
|
|
|
+
|
|
|
+ {/* 网格背景 */}
|
|
|
+ <div className="absolute inset-0 bg-[linear-gradient(to_right,#ffffff08_1px,transparent_1px),linear-gradient(to_bottom,#ffffff08_1px,transparent_1px)] bg-[size:50px_50px]" />
|
|
|
+
|
|
|
+ {/* 主内容层 */}
|
|
|
+ <div className="relative z-10 h-full flex flex-col">
|
|
|
+ <DashboardGrid />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 动态光效 */}
|
|
|
+ <div className="absolute inset-0 bg-gradient-to-r from-cyan-500/10 via-transparent to-purple-500/10 animate-pulse" />
|
|
|
+
|
|
|
+ {/* 扫描线效果 */}
|
|
|
+ <ScanningLine />
|
|
|
</div>
|
|
|
);
|
|
|
};
|