当前位置: 首页 > news >正文

【码源】智能无人仓库管理系统(详细码源上~基于React+TypeScript+Vite):

【码源】智能无人仓库管理系统(详细码源上~基于React+TypeScript+Vite):

  • `详细码源上:`
        • `Empty.tsx #空状态组件:`
        • ` LoginPage.tsx #登录页面组件:`
        • ` authContext.ts #认证状态管理:`
        • ` useTheme.ts #主题切换Hook:`
        • `utils.ts #通用工具函数 :`
        • `Home.tsx #首页仪表板:`
        • `GoodsManagement.tsx #货物管理:`
        • `RobotManagement.tsx #机器人管理 :`
        • `AnalyticsPage.tsx #数据分析:`
        • `App.tsx #主应用组件:`
        • `main.tsx #应用入口:`
        • `index.css #全局样式:`
        • `package.json #项目配置:`
        • `vite.config.ts #Vite配置:`
        • `tailwind.config.js #Tailwind配置:`
        • `tsconfig.json #TypeScript配置:`

详细码源上:

Empty.tsx #空状态组件:
import { toast } from "sonner";
import { cn } from "@/lib/utils";/*** 空状态组件* 用于在没有内容时显示占位信息*/
export function Empty() {return (<div className={cn("flex h-full items-center justify-center")} onClick={() => toast('Coming soon')}>Empty</div>);
}
LoginPage.tsx #登录页面组件:
import React, { useState, useContext } from 'react';
import { motion } from 'framer-motion';
import { Lock, User, Eye, EyeOff, AlertCircle, Mail, HelpCircle, ArrowLeft, Server } from 'lucide-react';
import { toast } from 'sonner';
import { useNavigate } from 'react-router-dom';
import { useTheme } from '@/hooks/useTheme';
import { AuthContext } from '@/contexts/authContext';/*** 登录页面组件* 处理用户认证和登录逻辑*/
const LoginPage: React.FC = () => {// 使用主题hookconst { theme, toggleTheme } = useTheme();// 获取认证上下文const { setIsAuthenticated } = useContext(AuthContext);// 登录表单状态const [username, setUsername] = useState('admin');const [password, setPassword] = useState('password');const [showPassword, setShowPassword] = useState(false);const [isLoading, setIsLoading] = useState(false);const [loginError, setLoginError] = useState('');const navigate = useNavigate();const handleLogin = (e: React.FormEvent) => {e.preventDefault();// 清除之前的错误信息setLoginError('');// 表单验证if (!username || !password) {setLoginError('请输入用户名和密码');return;}// 模拟登录请求setIsLoading(true);setTimeout(() => {// 简单的模拟验证逻辑if (username === 'admin' && password === 'password') {// 登录成功,更新认证状态并重定向到首页setIsAuthenticated(true);navigate('/');toast.success('登录成功');} else {// 登录失败setLoginError('用户名或密码错误');setIsLoading(false);}}, 1000);};const handleForgotPassword = () => {toast('忘记密码功能即将上线');};const handleRegister = () => {toast('注册功能即将上线');};return (<div className={`min-h-screen flex flex-col ${theme === 'dark' ? 'dark bg-gray-900 text-white' : 'bg-gray-50 text-gray-900'}`}>{/* 顶部导航栏 - 仅包含主题切换按钮 */}<header className="bg-white dark:bg-gray-800 shadow-sm"><div className="flex justify-end p-4"><button onClick={toggleTheme}className="p-2 rounded-full text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-200"><HelpCircle size={20} /></button></div></header>{/* 主要内容区域 */}<main className="flex-1 flex items-center justify-center p-4"><motion.divinitial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}transition={{ duration: 0.5 }}className="w-full max-w-md bg-white dark:bg-gray-800 rounded-xl shadow-xl overflow-hidden border border-gray-100 dark:border-gray-700"><div className="p-6 sm:p-8"><div className="text-center mb-8"><div className="flex justify-center mb-4"><motion.divinitial={{ scale: 0.8, opacity: 0 }}animate={{ scale: 1, opacity: 1 }}transition={{ duration: 0.5, delay: 0.2 }}className="w-16 h-16 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center shadow-lg"><Server size={32} className="text-white" /></motion.div></div><motion.h2 initial={{ y: 10, opacity: 0 }}animate={{ y: 0, opacity: 1 }}transition={{ duration: 0.4, delay: 0.3 }}className="text-2xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">智能无人仓库管理系统</motion.h2><motion.p initial={{ y: 10, opacity: 0 }}animate={{ y: 0, opacity: 1 }}transition={{ duration: 0.4, delay: 0.4 }}className="mt-2 text-sm text-gray-500 dark:text-gray-400">请登录以继续</motion.p></div><form onSubmit={handleLogin} className="space-y-5">{/* 用户名输入框 */}<motion.divinitial={{ y: 20, opacity: 0 }}animate={{ y: 0, opacity: 1 }}transition={{ duration: 0.4, delay: 0.5 }}><label htmlFor="username" className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">用户名</label><div className="relative"><div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"><User size={18} className="text-gray-400" /></div><inputtype="text"id="username"value={username}onChange={(e) => setUsername(e.target.value)}className="block w-full pl-10 pr-3 py-2.5 border border-gray-300 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:text-white transition-all duration-200"placeholder="请输入用户名"autoComplete="username"/></div></motion.div>{/* 密码输入框 */}<motion.divinitial={{ y: 20, opacity: 0 }}animate={{ y: 0, opacity: 1 }}transition={{ duration: 0.4, delay: 0.6 }}><label htmlFor="password" className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">密码</label><div className="relative"><div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"><Lock size={18} className="text-gray-400" /></div><inputtype={showPassword ? "text" : "password"}id="password"value={password}onChange={(e) => setPassword(e.target.value)}className="block w-full pl-10 pr-10 py-2.5 border border-gray-300 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:text-white transition-all duration-200"placeholder="请输入密码"autoComplete="current-password"/><div className="absolute inset-y-0 right-0 pr-3 flex items-center"><buttontype="button"onClick={() => setShowPassword(!showPassword)}className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 focus:outline-none transition-colors duration-200">{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}</button></div></div></motion.div>{/* 错误提示 */}{loginError && (<motion.divinitial={{ opacity: 0, height: 0 }}animate={{ opacity: 1, height: 'auto' }}exit={{ opacity: 0, height: 0 }}className="bg-red-50 dark:bg-red-900/30 border border-red-100 dark:border-red-800 text-red-700 dark:text-red-300 px-4 py-3 rounded-lg text-sm"><div className="flex"><AlertCircle size={18} className="flex-shrink-0 mt-0.5 mr-2" /><p>{loginError}</p></div></motion.div>)}{/* 记住我和忘记密码 */}<motion.div initial={{ y: 20, opacity: 0 }}animate={{ y: 0, opacity: 1 }}transition={{ duration: 0.4, delay: 0.7 }}className="flex items-center justify-between"><div className="flex items-center"><inputtype="checkbox"id="remember-me"className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 dark:border-gray-700 rounded transition-all duration-200"/><label htmlFor="remember-me" className="ml-2 block text-sm text-gray-700 dark:text-gray-300">记住我</label></div><buttontype="button"onClick={handleForgotPassword}className="text-sm text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 transition-colors duration-200">忘记密码?</button></motion.div>{/* 登录按钮 */}<motion.buttoninitial={{ y: 20, opacity: 0 }}animate={{ y: 0, opacity: 1 }}transition={{ duration: 0.4, delay: 0.8 }}type="submit"disabled={isLoading}className="w-full flex justify-center items-center py-2.5 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200 transform hover:translate-y-[-2px] hover:shadow-md">{isLoading ? (<><svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle><path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>登录中...</>) : ("登录")}</motion.button></form>{/* 分隔线和注册链接 */}<motion.div initial={{ y: 20, opacity: 0 }}animate={{ y: 0, opacity: 1 }}transition={{ duration: 0.4, delay: 0.9 }}className="mt-6"><div className="relative"><div className="absolute inset-0 flex items-center"><div className="w-full border-t border-gray-200 dark:border-gray-700"></div></div><div className="relative flex justify-center text-sm"><span className="px-2 bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400">或者</span></div></div><div className="mt-6"><buttononClick={handleRegister}disabled={isLoading}className="w-full flex justify-center items-center py-2.5 px-4 border border-gray-300 dark:border-gray-700 rounded-lg shadow-sm text-sm font-medium bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200 transform hover:translate-y-[-2px] hover:shadow-md">注册新账号</button></div></motion.div></div>{/* 页脚 */}<div className="bg-gray-50 dark:bg-gray-900 p-4 text-center text-xs text-gray-500 dark:text-gray-400">© 2025 智能无人仓库管理系统 版权所有</div></motion.div></main></div>);
};export default LoginPage;
authContext.ts #认证状态管理:
import { createContext } from "react";/*** 认证上下文* 用于在整个应用中共享用户认证状态*/
export const AuthContext = createContext({/*** 用户是否已认证*/isAuthenticated: false,/*** 设置用户认证状态的函数*/setIsAuthenticated: (value: boolean) => {},/*** 用户登出函数*/logout: () => {},
});
useTheme.ts #主题切换Hook:
import { useState, useEffect } from 'react';/*** 主题类型定义*/
type Theme = 'light' | 'dark';/*** 自定义Hook:用于管理应用程序的主题设置* @returns 包含当前主题、切换主题方法和是否为暗色主题的布尔值*/
export function useTheme() {// 初始化主题状态,优先从localStorage读取,否则根据系统设置const [theme, setTheme] = useState<Theme>(() => {const savedTheme = localStorage.getItem('theme') as Theme;if (savedTheme) {return savedTheme;}// 检查用户系统偏好的颜色方案return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';});// 当主题变更时,更新HTML元素的class和localStorageuseEffect(() => {// 移除旧的主题类document.documentElement.classList.remove('light', 'dark');// 添加新的主题类document.documentElement.classList.add(theme);// 保存主题到localStorage以便持久化localStorage.setItem('theme', theme);// 添加平滑过渡效果,提升用户体验document.documentElement.style.transition = 'background-color 0.3s ease, color 0.3s ease';}, [theme]);/*** 切换主题函数*/const toggleTheme = () => {setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');};// 返回主题相关状态和方法return {theme,toggleTheme,isDark: theme === 'dark'};
} 
utils.ts #通用工具函数 :
// 导入clsx和tailwind-merge库,用于处理Tailwind CSS类名
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"/*** 组合多个CSS类名,并通过tailwind-merge合并Tailwind类名,避免冲突* @param inputs 要组合的CSS类名* @returns 合并后的CSS类名字符串*/
export function cn(...inputs: ClassValue[]) {return twMerge(clsx(inputs))
}
Home.tsx #首页仪表板:
import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { LineChart, Line, AreaChart, Area, BarChart, Bar, PieChart, Pie,XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer,Cell
} from 'recharts';
import {Activity, BarChart2, Database, Package, Palette, Server, Settings, Users, User, Menu, X, Bell, Search, LogOut,TrendingUp, TrendingDown, CheckCircle, AlertCircle, Clock
} from 'lucide-react';
import { useTheme } from '@/hooks/useTheme';
import { AuthContext } from '@/contexts/authContext';
import { useContext } from 'react';
import { toast } from 'sonner';
import { useNavigate } from 'react-router-dom';// 模拟数据
const warehouseStats = [{ name: '库存总量', value: 15320, icon: <Database size={20} />, color: 'bg-blue-500' },{ name: '今日入库', value: 342, icon: <Package size={20} />, color: 'bg-green-500' },{ name: '今日出库', value: 289, icon: <Package size={20} />, color: 'bg-orange-500' },{ name: '机器人数量', value: 18, icon: <Server size={20} />, color: 'bg-purple-500' },
];const inventoryTrendData = [{ date: '10/18', inventory: 14800, in: 310, out: 290 },{ date: '10/19', inventory: 14820, in: 290, out: 270 },{ date: '10/20', inventory: 14840, in: 320, out: 300 },{ date: '10/21', inventory: 14860, in: 280, out: 260 },{ date: '10/22', inventory: 14880, in: 330, out: 310 },{ date: '10/23', inventory: 14900, in: 300, out: 280 },{ date: '10/24', inventory: 14920, in: 350, out: 330 },{ date: '10/25', inventory: 15320, in: 342, out: 289 },
];const categoryData = [{ name: '电子产品', value: 38 },{ name: '服装鞋帽', value: 22 },{ name: '食品饮料', value: 18 },{ name: '家居用品', value: 12 },{ name: '其他', value: 10 },
];const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884d8'];const recentActivities = [{ id: 1, type: '入库', item: '智能手机 A12', quantity: 50, time: '10:25', status: '完成' },{ id: 2, type: '出库', item: '笔记本电脑 XPS', quantity: 20, time: '09:18', status: '完成' },{ id: 3, type: '移库', item: '智能手表 S3', quantity: 15, time: '08:42', status: '进行中' },{ id: 4, type: '盘点', item: '无线耳机 P5', quantity: 100, time: '07:30', status: '待处理' },{ id: 5, type: '补货', item: '平板电脑 T8', quantity: 30, time: '昨天', status: '完成' },
];const robotStatus = [{ id: 'R001', status: '正常', battery: 92, task: '入库搬运', location: 'A区-03-05' },{ id: 'R002', status: '正常', battery: 85, task: '出库准备', location: 'B区-01-02' },{ id: 'R003', status: '维护中', battery: 45, task: '充电中', location: '充电区-02' },{ id: 'R004', status: '正常', battery: 78, task: '货架整理', location: 'C区-05-01' },{ id: 'R005', status: '故障', battery: 20, task: '等待维修', location: '维修区' },
];/*** 首页组件* 展示仓库运营的概览信息和主要数据*/
export default function Home() {// 使用主题hookconst { theme, toggleTheme } = useTheme();// 获取认证上下文const { isAuthenticated, logout } = useContext(AuthContext);// 侧边栏状态const [sidebarOpen, setSidebarOpen] = useState(true);// 当前时间状态const [currentTime, setCurrentTime] = useState(new Date());const navigate = useNavigate();// 每分钟更新一次当前时间useEffect(() => {const timer = setInterval(() => {setCurrentTime(new Date());}, 60000);// 清理定时器return () => clearInterval(timer);}, []);/*** 切换侧边栏显示状态*/const toggleSidebar = () => {setSidebarOpen(!sidebarOpen);};/*** 格式化时间为HH:MM格式* @param date 日期对象* @returns 格式化后的时间字符串*/const formatTime = (date: Date) => {return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });};/*** 格式化日期为YYYY年MM月DD日 星期X格式* @param date 日期对象* @returns 格式化后的日期字符串*/const formatDate = (date: Date) => {return date.toLocaleDateString('zh-CN', {year: 'numeric',month: 'long',day: 'numeric',weekday: 'long'});};/*** 处理用户登出*/const handleLogout = () => {logout();toast('已成功登出');};// 生成仓库地图模拟数据const generateWarehouseMap = () => {const rows = 8;const cols = 10;const map = [];for (let i = 0; i < rows; i++) {for (let j = 0; j < cols; j++) {// 随机生成货架位置const isShelf = Math.random() > 0.3;// 随机生成机器人位置(较少出现)const isRobot = Math.random() > 0.95;map.push(<div key={`${i}-${j}`}className={`w-12 h-12 flex items-center justify-center border ${isShelf ? 'bg-blue-100 dark:bg-blue-900' : 'bg-gray-50 dark:bg-gray-800'}${isRobot ? 'bg-green-200 dark:bg-green-800' : ''}${isShelf || isRobot ? 'border-blue-300 dark:border-blue-700' : 'border-gray-200 dark:border-gray-700'}`}>{isRobot && <Server size={20} className="text-green-600 dark:text-green-400" />}{isShelf && !isRobot && `${String.fromCharCode(65 + Math.floor(i/2))}${j+1}`}</div>);}}return map;};return (<div className={`min-h-screen flex flex-col ${theme === 'dark' ? 'dark bg-gray-900 text-white' : 'bg-gray-50 text-gray-900'} transition-colors duration-300`}>{/* 顶部导航栏 */}<header className="bg-white dark:bg-gray-800 shadow-sm z-10 transition-colors duration-300"><div className="flex items-center justify-between px-4 py-3"><div className="flex items-center"><button onClick={toggleSidebar}className="p-2 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-200"><Menu size={20} /></button><h1 className="ml-4 text-xl font-bold text-blue-600 dark:text-blue-400">智能无人仓库管理系统</h1></div><div className="flex items-center space-x-4"><div className="hidden md:block text-right"><div className="text-sm text-gray-500 dark:text-gray-400">{formatDate(currentTime)}</div><div className="text-lg font-medium">{formatTime(currentTime)}</div></div><div className="relative"><button className="p-2 rounded-full text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-200 relative"><Bell size={20} /><span className="absolute top-1 right-1 w-2 h-2 bg-red-500 rounded-full animate-ping"></span><span className="absolute top-1 right-1 w-2 h-2 bg-red-500 rounded-full"></span></button></div><button onClick={toggleTheme}className="p-2 rounded-full text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-200"><Palette size={20} /></button><div className="flex items-center space-x-2"><div className="w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900 flex items-center justify-center text-blue-600 dark:text-blue-400"><User size={16} /></div><div className="hidden md:block"><div className="text-sm font-medium">管理员</div><button onClick={handleLogout}className="text-xs text-gray-500 dark:text-gray-400 hover:text-red-500 dark:hover:text-red-400 flex items-center"><LogOut size={12} className="mr-1" /> 退出</button></div></div></div></div></header>{/* 主内容区域 */}<div className="flex flex-1 overflow-hidden">{/* 侧边栏 */}<AnimatePresence>{(sidebarOpen || window.innerWidth >= 1024) && (<motion.asideinitial={{ width: 0, opacity: 0 }}animate={{ width: 240, opacity: 1 }}exit={{ width: 0, opacity: 0 }}transition={{ duration: 0.3 }}className="bg-white dark:bg-gray-800 shadow-md overflow-y-auto flex-shrink-0"><nav className="p-4"><div className="space-y-1"><button className="flex items-center w-full px-4 py-2 rounded-lg bg-blue-50 dark:bg-blue-900 text-blue-600 dark:text-blue-400 font-medium"><Activity size={18} className="mr-3" />仪表盘</button><button className="flex items-center w-full px-4 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-200" onClick={() => navigate('/goods-management')}><Package size={18} className="mr-3" />货物管理</button><button className="flex items-center w-full px-4 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-200" onClick={() => navigate('/robot-management')}><Server size={18} className="mr-3" />机器人管理</button><button className="flex items-center w-full px-4 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-200" onClick={() => navigate('/inventory-management')}><Database size={18} className="mr-3" />库存管理</button><button className="flex items-center w-full px-4 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-200" onClick={() => navigate('/analytics')}><BarChart2 size={18} className="mr-3" />数据分析</button><button className="flex items-center w-full px-4 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-200" onClick={() => navigate('/user-management')}><Users size={18} className="mr-3" />用户管理</button><button className="flex items-center w-full px-4 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-200" onClick={() => navigate('/system-settings')}><Settings size={18} className="mr-3" />系统设置</button></div></nav></motion.aside>)}</AnimatePresence>{/* 主内容 */}<main className="flex-1 overflow-y-auto p-4 md:p-6 bg-gray-50 dark:bg-gray-900">{/* 欢迎信息 */}<div className="mb-6"><h2 className="text-2xl font-bold">欢迎回来,管理员</h2><p className="text-gray-500 dark:text-gray-400">这是您的仓库运营概览</p></div>{/* 统计卡片 */}<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">{warehouseStats.map((stat, index) => (<motion.divkey={index}initial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}transition={{ duration: 0.3, delay: index * 0.1 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5 hover:shadow-md transition-all duration-300 border border-gray-100 dark:border-gray-700 hover:translate-y-[-4px]"><div className="flex justify-between items-start"><div><p className="text-gray-500 dark:text-gray-400 text-sm">{stat.name}</p><h3 className="text-2xl font-bold mt-1">{stat.value}</h3></div><div className={`${stat.color} text-white p-2 rounded-lg`}>{stat.icon}</div></div><div className="mt-3 flex items-center">{stat.name === '库存总量' || stat.name === '今日入库' ? (<span className="text-green-500 flex items-center text-sm"><TrendingUp size={14} className="mr-1" />2.4%</span>) : (<span className="text-red-500 flex items-center text-sm"><TrendingDown size={14} className="mr-1" />1.2%</span>)}<span className="text-gray-400 text-xs ml-2">较昨日</span></div></motion.div>))}</div>{/* 图表区域 */}<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">{/* 库存趋势图 */}<motion.divinitial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}transition={{ duration: 0.3, delay: 0.2 }}className="lg:col-span-2 bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><div className="flex justify-between items-center mb-4"><h3 className="font-bold text-lg">库存趋势</h3><div className="flex space-x-2"><button className="px-3 py-1 text-xs rounded-full bg-blue-50 dark:bg-blue-900 text-blue-600 dark:text-blue-400">今日</button><button className="px-3 py-1 text-xs rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">本周</button><button className="px-3 py-1 text-xs rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">本月</button></div></div><div className="h-80"><ResponsiveContainer width="100%" height="100%"><AreaChartdata={inventoryTrendData}margin={{ top: 10, right: 30, left: 0, bottom: 0 }}><defs><linearGradient id="colorInventory" x1="0" y1="0" x2="0" y2="1"><stop offset="5%" stopColor="#3b82f6" stopOpacity={0.8} /><stop offset="95%" stopColor="#3b82f6" stopOpacity={0} /></linearGradient></defs><CartesianGrid strokeDasharray="3 3" stroke={theme === 'dark' ? '#374151' : '#e5e7eb'} /><CartesianGrid strokeDasharray="3 3" stroke={theme === 'dark' ? '#374151' : '#e5e7eb'} /><XAxis dataKey="date" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><YAxis stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} /><Legend /><Area type="monotone" dataKey="inventory" stroke="#3b82f6" fillOpacity={1} fill="url(#colorInventory)" strokeWidth={2}/><Line type="monotone" dataKey="in" stroke="#10b981" strokeWidth={2} dot={{ r: 3 }}/><Line type="monotone" dataKey="out" stroke="#f97316" strokeWidth={2} dot={{ r: 3 }}/></AreaChart></ResponsiveContainer></div></motion.div>{/* 货物分类饼图 */}<motion.divinitial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}transition={{ duration: 0.3, delay: 0.3 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><h3 className="font-bold text-lg mb-4">货物分类</h3><div className="h-64 flex justify-center"><ResponsiveContainer width="100%" height="100%"><PieChart><Piedata={categoryData}cx="50%"cy="50%"innerRadius={60}outerRadius={80}fill="#8884d8"paddingAngle={2}dataKey="value"label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}labelLine={false}>{categoryData.map((entry, index) => (<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />))}</Pie><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} /></PieChart></ResponsiveContainer></div><div className="grid grid-cols-2 gap-2 mt-2">{categoryData.map((item, index) => (<div key={index} className="flex items-center"><div className="w-3 h-3 rounded-full mr-2" style={{ backgroundColor: COLORS[index % COLORS.length] }}></div><span className="text-sm">{item.name}</span></div>))}</div></motion.div></div>{/* 机器人状态和最近活动 */}<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">{/* 机器人状态 */}<motion.divinitial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}transition={{ duration: 0.3, delay: 0.4 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5 border border-gray-100 dark:border-gray-700"><div className="flex justify-between items-center mb-4"><h3 className="font-bold text-lg">机器人状态</h3><button className="text-blue-600 dark:text-blue-400 text-sm hover:underline">查看全部</button></div><div className="overflow-x-auto"><table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700"><thead><tr><th className="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">ID</th><th className="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">状态</th><th className="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">电量</th><th className="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">任务</th><th className="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">位置</th></tr></thead><tbody className="divide-y divide-gray-200 dark:divide-gray-700">{robotStatus.map((robot) => (<tr key={robot.id} className="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200"><td className="px-3 py-3 whitespace-nowrap text-sm">{robot.id}</td><td className="px-3 py-3 whitespace-nowrap text-sm"><span className={`px-2 py-1 text-xs rounded-full${robot.status === '正常' ? 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200' : robot.status === '维护中' ? 'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200' : 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200'}`}>{robot.status}</span></td><td className="px-3 py-3 whitespace-nowrap text-sm"><div className="flex items-center"><div className="w-16 h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden"><div className={`h-full ${robot.battery > 70 ? 'bg-green-500' : robot.battery > 30 ? 'bg-yellow-500' : 'bg-red-500'}`}style={{ width: `${robot.battery}%` }}></div></div><span className="ml-2">{robot.battery}%</span></div></td><td className="px-3 py-3 whitespace-nowrap text-sm">{robot.task}</td><td className="px-3 py-3 whitespace-nowrap text-sm">{robot.location}</td></tr>))}</tbody></table></div></motion.div>{/* 最近活动 */}<motion.divinitial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}transition={{ duration: 0.3, delay: 0.5 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5 border border-gray-100 dark:border-gray-700"><div className="flex justify-between items-center mb-4"><h3 className="font-bold text-lg">最近活动</h3><button className="text-blue-600 dark:text-blue-400 text-sm hover:underline">查看全部</button></div><div className="space-y-4">{recentActivities.map((activity) => (<div key={activity.id} className="flex items-start p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200"><div className={`p-2 rounded-full mr-3${activity.type === '入库' ? 'bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-400' : activity.type === '出库' ? 'bg-orange-100 dark:bg-orange-900 text-orange-600 dark:text-orange-400' :activity.type === '移库' ? 'bg-purple-100 dark:bg-purple-900 text-purple-600 dark:text-purple-400' :activity.type === '盘点' ? 'bg-yellow-100 dark:bg-yellow-900 text-yellow-600 dark:text-yellow-400' :'bg-green-100 dark:bg-green-900 text-green-600 dark:text-green-400'}`}><Package size={18} /></div><div className="flex-1"><div className="flex justify-between items-start"><p className="font-medium">{activity.type}操作</p><span className="text-xs text-gray-500 dark:text-gray-400">{activity.time}</span></div><p className="text-sm text-gray-600 dark:text-gray-300">{activity.item}</p><div className="flex justify-between items-center mt-1"><span className="text-xs text-gray-500 dark:text-gray-400">数量: {activity.quantity}</span><span className={`text-xs px-2 py-0.5 rounded-full${activity.status === '完成' ? 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200' : activity.status === '进行中' ? 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200' : 'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200'}`}>{activity.status}</span></div></div></div>))}</div></motion.div></div>{/* 仓库地图 */}<motion.divinitial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}transition={{ duration: 0.3, delay: 0.6 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5 mb-6 border border-gray-100 dark:border-gray-700"><div className="flex justify-between items-center mb-4"><h3 className="font-bold text-lg">仓库地图</h3><div className="flex space-x-2"><div className="flex items-center"><div className="w-3 h-3 bg-blue-100 dark:bg-blue-900 border border-blue-300 dark:border-blue-700 mr-1"></div><span className="text-xs">货架</span></div><div className="flex items-center"><div className="w-3 h-3 bg-green-200 dark:bg-green-800 border border-blue-300 dark:border-blue-700 mr-1"></div><span className="text-xs">机器人</span></div></div></div><div className="flex justify-center"><div className="grid grid-cols-10 gap-1 p-2 border-2 border-blue-500 dark:border-blue-400 rounded-lg max-w-2xl mx-auto shadow-inner shadow-blue-100 dark:shadow-blue-900/20">{generateWarehouseMap()}</div></div></motion.div></main></div>{/* 页脚 */}<footer className="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 py-4"><div className="px-4 text-center text-sm text-gray-500 dark:text-gray-400">© 2025 智能无人仓库管理系统 | 基于Java+SpringBoot开发</div></footer></div>);
}
GoodsManagement.tsx #货物管理:
import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Package, Search, Plus, Edit, Trash2, Eye, Filter, X, CheckCircle, AlertCircle, ChevronDown, BarChart2, Layers,Clock, RefreshCw, Settings, Database
} from 'lucide-react';
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, Cell
} from 'recharts';
import { useTheme } from '@/hooks/useTheme';
import { toast } from 'sonner';// 货物数据类型定义
interface GoodsItem {id: string;name: string;category: string;quantity: number;location: string;status: 'in-stock' | 'low-stock' | 'out-of-stock';shelfLife?: string;lastUpdated: string;weight: number;dimensions: string;supplier: string;barcode: string;
}// 货物分类数据
const categoryData = [{ name: '电子产品', value: 38 },{ name: '服装鞋帽', value: 22 },{ name: '食品饮料', value: 18 },{ name: '家居用品', value: 12 },{ name: '其他', value: 10 },
];const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884d8'];// 模拟货物数据
const generateMockGoods = (): GoodsItem[] => {const categories = ['电子产品', '服装鞋帽', '食品饮料', '家居用品', '其他'];const statuses: Array<'in-stock' | 'low-stock' | 'out-of-stock'> = ['in-stock', 'low-stock', 'out-of-stock'];const locations = ['A区-01-01', 'A区-01-02', 'A区-02-01', 'B区-01-01', 'B区-02-01', 'C区-01-01'];const suppliers = ['供应商A', '供应商B', '供应商C', '供应商D', '供应商E'];return Array.from({ length: 50 }, (_, index) => {const quantity = Math.floor(Math.random() * 500);let status: 'in-stock' | 'low-stock' | 'out-of-stock' = 'in-stock';if (quantity === 0) status = 'out-of-stock';else if (quantity < 50) status = 'low-stock';return {id: `GOODS-${String(index + 1).padStart(4, '0')}`,name: `货物名称 ${index + 1}`,category: categories[Math.floor(Math.random() * categories.length)],quantity,location: locations[Math.floor(Math.random() * locations.length)],status,shelfLife: Math.random() > 0.5 ? '12个月' : undefined,lastUpdated: new Date(Date.now() - Math.floor(Math.random() * 30) * 24 * 60 * 60 * 1000).toLocaleDateString('zh-CN'),weight: Math.round(Math.random() * 1000) / 10,dimensions: `${Math.floor(Math.random() * 100) + 10}x${Math.floor(Math.random() * 100) + 10}x${Math.floor(Math.random() * 100) + 10}cm`,supplier: suppliers[Math.floor(Math.random() * suppliers.length)],barcode: `BC${Math.floor(Math.random() * 10000000000)}`};});
};const GoodsManagement: React.FC = () => {const { theme } = useTheme();const [goods, setGoods] = useState<GoodsItem[]>([]);const [filteredGoods, setFilteredGoods] = useState<GoodsItem[]>([]);const [searchTerm, setSearchTerm] = useState('');const [selectedCategory, setSelectedCategory] = useState('all');const [selectedStatus, setSelectedStatus] = useState('all');const [isAddModalOpen, setIsAddModalOpen] = useState(false);const [isEditModalOpen, setIsEditModalOpen] = useState(false);const [isDetailModalOpen, setIsDetailModalOpen] = useState(false);const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);const [currentGoods, setCurrentGoods] = useState<GoodsItem | null>(null);const [newGoods, setNewGoods] = useState<Partial<GoodsItem>>({name: '',category: '',quantity: 0,location: '',status: 'in-stock',weight: 0,dimensions: '',supplier: '',barcode: ''});const [isLoading, setIsLoading] = useState(true);const [currentPage, setCurrentPage] = useState(1);const [itemsPerPage] = useState(10);const [isFilterDropdownOpen, setIsFilterDropdownOpen] = useState(false);// 加载货物数据useEffect(() => {// 模拟API请求延迟const timer = setTimeout(() => {const mockData = generateMockGoods();setGoods(mockData);setFilteredGoods(mockData);setIsLoading(false);}, 1000);return () => clearTimeout(timer);}, []);// 搜索和筛选功能useEffect(() => {let result = [...goods];// 搜索筛选if (searchTerm) {result = result.filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase()) || item.id.toLowerCase().includes(searchTerm.toLowerCase()) ||item.barcode.toLowerCase().includes(searchTerm.toLowerCase()));}// 分类筛选if (selectedCategory !== 'all') {result = result.filter(item => item.category === selectedCategory);}// 状态筛选if (selectedStatus !== 'all') {result = result.filter(item => item.status === selectedStatus);}setFilteredGoods(result);setCurrentPage(1); // 重置到第一页}, [searchTerm, selectedCategory, selectedStatus, goods]);// 分页功能const indexOfLastItem = currentPage * itemsPerPage;const indexOfFirstItem = indexOfLastItem - itemsPerPage;const currentItems = filteredGoods.slice(indexOfFirstItem, indexOfLastItem);const totalPages = Math.ceil(filteredGoods.length / itemsPerPage);// 处理添加货物const handleAddGoods = () => {if (!newGoods.name || !newGoods.category || !newGoods.location) {toast.error('请填写必要的货物信息');return;}const status: 'in-stock' | 'low-stock' | 'out-of-stock' = newGoods.quantity === 0 ? 'out-of-stock' : newGoods.quantity! < 50 ? 'low-stock' : 'in-stock';const goodsToAdd: GoodsItem = {id: `GOODS-${String(goods.length + 1).padStart(4, '0')}`,name: newGoods.name!,category: newGoods.category!,quantity: newGoods.quantity || 0,location: newGoods.location!,status,shelfLife: newGoods.shelfLife,lastUpdated: new Date().toLocaleDateString('zh-CN'),weight: newGoods.weight || 0,dimensions: newGoods.dimensions || '',supplier: newGoods.supplier || '',barcode: newGoods.barcode || ''};setGoods([...goods, goodsToAdd]);setIsAddModalOpen(false);setNewGoods({name: '',category: '',quantity: 0,location: '',status: 'in-stock',weight: 0,dimensions: '',supplier: '',barcode: ''});toast.success('货物添加成功');};// 处理编辑货物const handleEditGoods = () => {if (!currentGoods) return;const status: 'in-stock' | 'low-stock' | 'out-of-stock' = currentGoods.quantity === 0 ? 'out-of-stock' : currentGoods.quantity < 50 ? 'low-stock' : 'in-stock';const updatedGoods = { ...currentGoods, status, lastUpdated: new Date().toLocaleDateString('zh-CN') };const updatedGoodsList = goods.map(item => item.id === currentGoods.id ? updatedGoods : item);setGoods(updatedGoodsList);setIsEditModalOpen(false);setCurrentGoods(null);toast.success('货物更新成功');};// 处理删除货物const handleDeleteGoods = () => {if (!currentGoods) return;const updatedGoodsList = goods.filter(item => item.id !== currentGoods.id);setGoods(updatedGoodsList);setIsDeleteModalOpen(false);setCurrentGoods(null);toast.success('货物删除成功');};// 打开编辑模态框const openEditModal = (item: GoodsItem) => {setCurrentGoods({ ...item });setIsEditModalOpen(true);};// 打开详情模态框const openDetailModal = (item: GoodsItem) => {setCurrentGoods({ ...item });setIsDetailModalOpen(true);};// 打开删除确认模态框const openDeleteModal = (item: GoodsItem) => {setCurrentGoods({ ...item });setIsDeleteModalOpen(true);};// 刷新数据const handleRefresh = () => {setIsLoading(true);// 模拟API请求延迟const timer = setTimeout(() => {const mockData = generateMockGoods();setGoods(mockData);setFilteredGoods(mockData);setIsLoading(false);toast.success('数据刷新成功');}, 1000);return () => clearTimeout(timer);};// 渲染状态标签const renderStatusBadge = (status: string) => {switch (status) {case 'in-stock':return <span className="px-2 py-1 text-xs rounded-full bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200">库存充足</span>;case 'low-stock':return <span className="px-2 py-1 text-xs rounded-full bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200">库存不足</span>;case 'out-of-stock':return <span className="px-2 py-1 text-xs rounded-full bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200">缺货</span>;default:return <span className="px-2 py-1 text-xs rounded-full bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200">未知</span>;}};// 分页控制const handlePageChange = (page: number) => {setCurrentPage(page);};return (<div className="p-4 md:p-6">{/* 页面标题和操作区 */}<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-6"><div><h2 className="text-2xl font-bold">货物管理</h2><p className="text-gray-500 dark:text-gray-400">管理仓库中的所有货物信息</p></div><div className="flex space-x-3 mt-4 md:mt-0"><button onClick={handleRefresh}className="flex items-center px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200"><RefreshCw size={16} className="mr-2" />刷新</button><button onClick={() => setIsAddModalOpen(true)}className="flex items-center px-4 py-2 bg-blue-600 dark:bg-blue-700 text-white rounded-lg shadow-sm hover:bg-blue-700 dark:hover:bg-blue-800 transition-colors duration-200"><Plus size={16} className="mr-2" />添加货物</button></div></div>{/* 搜索和筛选区 */}<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-4 mb-6"><div className="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4"><div className="relative flex-1"><Search size={18} className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" /><inputtype="text"placeholder="搜索货物名称、ID或条码..."className="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={searchTerm}onChange={(e) => setSearchTerm(e.target.value)}/>{searchTerm && (<button onClick={() => setSearchTerm('')}className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"><X size={16} /></button>)}</div><div className="relative"><buttononClick={() => setIsFilterDropdownOpen(!isFilterDropdownOpen)}className="flex items-center px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200"><Filter size={16} className="mr-2" />筛选<ChevronDown size={16} className={`ml-2 transition-transform duration-200 ${isFilterDropdownOpen ? 'transform rotate-180' : ''}`} /></button><AnimatePresence>{isFilterDropdownOpen && (<motion.divinitial={{ opacity: 0, y: -10 }}animate={{ opacity: 1, y: 0 }}exit={{ opacity: 0, y: -10 }}transition={{ duration: 0.2 }}className="absolute top-full left-0 mt-2 w-64 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg shadow-lg z-10 p-4"><div className="mb-4"><label className="block text-sm font-medium mb-2">货物分类</label><selectclassName="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={selectedCategory}onChange={(e) => setSelectedCategory(e.target.value)}><option value="all">全部分类</option><option value="电子产品">电子产品</option><option value="服装鞋帽">服装鞋帽</option><option value="食品饮料">食品饮料</option><option value="家居用品">家居用品</option><option value="其他">其他</option></select></div><div><label className="block text-sm font-medium mb-2">库存状态</label><selectclassName="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={selectedStatus}onChange={(e) => setSelectedStatus(e.target.value)}><option value="all">全部状态</option><option value="in-stock">库存充足</option><option value="low-stock">库存不足</option><option value="out-of-stock">缺货</option></select></div><div className="mt-4 flex justify-end"><buttononClick={() => {setSelectedCategory('all');setSelectedStatus('all');setIsFilterDropdownOpen(false);}}className="text-sm text-blue-600 dark:text-blue-400 hover:underline">重置筛选</button></div></motion.div>)}</AnimatePresence></div></div></div>{/* 货物列表和统计图表 */}<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">{/* 货物列表 */}<div className="lg:col-span-2 bg-white dark:bg-gray-800 rounded-xl shadow-sm overflow-hidden"><div className="p-4 border-b border-gray-200 dark:border-gray-700"><h3 className="font-bold text-lg">货物列表</h3></div>{isLoading ? (<div className="h-96 flex items-center justify-center"><div className="flex flex-col items-center"><RefreshCw size={32} className="text-blue-500 animate-spin" /><p className="mt-2 text-gray-500 dark:text-gray-400">加载中...</p></div></div>) : filteredGoods.length === 0 ? (<div className="h-96 flex items-center justify-center"><div className="text-center"><Database size={48} className="mx-auto text-gray-300 dark:text-gray-600 mb-2" /><p className="text-gray-500 dark:text-gray-400">没有找到匹配的货物</p><button onClick={() => {setSearchTerm('');setSelectedCategory('all');setSelectedStatus('all');}}className="mt-2 text-sm text-blue-600 dark:text-blue-400 hover:underline">清除筛选条件</button></div></div>) : (<><div className="overflow-x-auto"><table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700"><thead><tr><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">ID</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">货物名称</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">分类</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">数量</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">位置</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">状态</th><th className="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">操作</th></tr></thead><tbody className="divide-y divide-gray-200 dark:divide-gray-700">{currentItems.map((item) => (<tr key={item.id} className="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200"><td className="px-4 py-3 whitespace-nowrap text-sm font-medium">{item.id}</td><td className="px-4 py-3 whitespace-nowrap text-sm">{item.name}</td><td className="px-4 py-3 whitespace-nowrap text-sm">{item.category}</td><td className="px-4 py-3 whitespace-nowrap text-sm">{item.quantity}</td><td className="px-4 py-3 whitespace-nowrap text-sm">{item.location}</td><td className="px-4 py-3 whitespace-nowrap text-sm">{renderStatusBadge(item.status)}</td><td className="px-4 py-3 whitespace-nowrap text-right text-sm font-medium"><div className="flex justify-end space-x-2"><buttononClick={() => openDetailModal(item)}className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300"><Eye size={16} /></button><buttononClick={() => openEditModal(item)}className="text-green-600 dark:text-green-400 hover:text-green-800 dark:hover:text-green-300"><Edit size={16} /></button><buttononClick={() => openDeleteModal(item)}className="text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300"><Trash2 size={16} /></button></div></td></tr>))}</tbody></table></div>{/* 分页控件 */}<div className="px-4 py-3 flex items-center justify-between border-t border-gray-200 dark:border-gray-700"><div className="flex-1 flex justify-between sm:hidden"><buttononClick={() => handlePageChange(currentPage - 1)}disabled={currentPage === 1}className={`relative inline-flex items-center px-4 py-2 border rounded-md text-sm font-medium ${currentPage === 1 ? 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed' : 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700'}`}>上一页</button><buttononClick={() => handlePageChange(currentPage + 1)}disabled={currentPage === totalPages}className={`ml-3 relative inline-flex items-center px-4 py-2 border rounded-md text-sm font-medium ${currentPage === totalPages ? 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed' : 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700'}`}>下一页</button></div><div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between"><div><p className="text-sm text-gray-700 dark:text-gray-300">显示第 <span className="font-medium">{indexOfFirstItem + 1}</span><span className="font-medium">{Math.min(indexOfLastItem, filteredGoods.length)}</span> 条,共 <span className="font-medium">{filteredGoods.length}</span> 条记录</p></div><div><nav className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">{[...Array(totalPages)].map((_, index) => {// 只显示当前页、首页、末页以及前后各一页if (index === 0 || index === totalPages - 1 || Math.abs(index - (currentPage - 1)) <= 1) {return (<buttonkey={index}onClick={() => handlePageChange(index + 1)}className={`relative inline-flex items-center px-2 py-2 rounded-md text-sm font-medium ${currentPage === index + 1? 'bg-blue-50 dark:bg-blue-900 border-blue-300 dark:border-blue-700 text-blue-600 dark:text-blue-400': 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700'}`}>{index + 1}</button>);}// 添加省略号if ((index === 2 && currentPage > 4) ||(index === totalPages - 3 && currentPage < totalPages - 3)) {return (<span key={index} className="relative inline-flex items-center px-2 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-sm text-gray-700 dark:text-gray-300">...</span>);}return null;})}</nav></div></div></div></>)}</div>{/* 货物分类统计图表 */}<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><h3 className="font-bold text-lg mb-4">货物分类统计</h3><div className="h-64 flex justify-center"><ResponsiveContainer width="100%" height="100%"><BarChartdata={categoryData}margin={{ top: 10, right: 10, left: 10, bottom: 40 }}><CartesianGrid strokeDasharray="3 3" stroke={theme === 'dark' ? '#374151' : '#e5e7eb'} /><XAxis dataKey="name" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'}angle={-45}textAnchor="end"height={60}/><YAxis stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} /><Legend /><Bar dataKey="value" name="百分比">{categoryData.map((entry, index) => (<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />))}</Bar></BarChart></ResponsiveContainer></div>{/* 快速统计卡片 */}<div className="mt-6 space-y-3"><div className="flex justify-between items-center"><div className="flex items-center"><Package size={18} className="mr-2 text-blue-500" /><span className="text-sm">总货物种类</span></div><span className="font-bold">{goods.length}</span></div><div className="flex justify-between items-center"><div className="flex items-center"><CheckCircle size={18} className="mr-2 text-green-500" /><span className="text-sm">库存充足</span></div><span className="font-bold">{goods.filter(item => item.status === 'in-stock').length}</span></div><div className="flex justify-between items-center"><div className="flex items-center"><AlertCircle size={18} className="mr-2 text-yellow-500" /><span className="text-sm">库存不足</span></div><span className="font-bold">{goods.filter(item => item.status === 'low-stock').length}</span></div><div className="flex justify-between items-center"><div className="flex items-center"><X size={18} className="mr-2 text-red-500" /><span className="text-sm">缺货</span></div><span className="font-bold">{goods.filter(item => item.status === 'out-of-stock').length}</span></div></div></div></div>{/* 添加货物模态框 */}<AnimatePresence>{isAddModalOpen && (<motion.divinitial={{ opacity: 0 }}animate={{ opacity: 1 }}exit={{ opacity: 0 }}transition={{ duration: 0.2 }}className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"onClick={() => setIsAddModalOpen(false)}><motion.divinitial={{ scale: 0.9, opacity: 0 }}animate={{ scale: 1, opacity: 1 }}exit={{ scale: 0.9, opacity: 0 }}transition={{ duration: 0.2 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-lg w-full max-w-2xl max-h-[90vh] overflow-y-auto"onClick={(e) => e.stopPropagation()}><div className="p-5 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center"><h3 className="font-bold text-lg">添加新货物</h3><button onClick={() => setIsAddModalOpen(false)} className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"><X size={20} /></button></div><div className="p-5"><div className="grid grid-cols-1 md:grid-cols-2 gap-4"><div><label className="block text-sm font-medium mb-2">货物名称 <span className="text-red-500">*</span></label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={newGoods.name || ''}onChange={(e) => setNewGoods({ ...newGoods, name: e.target.value })}/></div><div><label className="block text-sm font-medium mb-2">货物分类 <span className="text-red-500">*</span></label><selectclassName="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={newGoods.category || ''}onChange={(e) => setNewGoods({ ...newGoods, category: e.target.value })}><option value="">请选择分类</option><option value="电子产品">电子产品</option><option value="服装鞋帽">服装鞋帽</option><option value="食品饮料">食品饮料</option><option value="家居用品">家居用品</option><option value="其他">其他</option></select></div><div><label className="block text-sm font-medium mb-2">数量</label><inputtype="number"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={newGoods.quantity || 0}onChange={(e) => setNewGoods({ ...newGoods, quantity: parseInt(e.target.value) || 0 })}/></div><div><label className="block text-sm font-medium mb-2">存放位置 <span className="text-red-500">*</span></label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={newGoods.location || ''}onChange={(e) => setNewGoods({ ...newGoods, location: e.target.value })}placeholder="例如:A区-01-01"/></div><div><label className="block text-sm font-medium mb-2">保质期</label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={newGoods.shelfLife || ''}onChange={(e) => setNewGoods({ ...newGoods, shelfLife: e.target.value })}placeholder="例如:12个月"/></div><div><label className="block text-sm font-medium mb-2">重量 (kg)</label><inputtype="number"step="0.1"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={newGoods.weight || 0}onChange={(e) => setNewGoods({ ...newGoods, weight: parseFloat(e.target.value) || 0 })}/></div><div><label className="block text-sm font-medium mb-2">尺寸</label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={newGoods.dimensions || ''}onChange={(e) => setNewGoods({ ...newGoods, dimensions: e.target.value })}placeholder="例如:30x20x15cm"/></div><div><label className="block text-sm font-medium mb-2">供应商</label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={newGoods.supplier || ''}onChange={(e) => setNewGoods({ ...newGoods, supplier: e.target.value })}/></div><div className="md:col-span-2"><label className="block text-sm font-medium mb-2">条码</label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={newGoods.barcode || ''}onChange={(e) => setNewGoods({ ...newGoods, barcode: e.target.value })}/></div></div></div><div className="p-5 border-t border-gray-200 dark:border-gray-700 flex justify-end space-x-3"><buttononClick={() => setIsAddModalOpen(false)}className="px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200">取消</button><buttononClick={handleAddGoods}className="px-4 py-2 bg-blue-600 dark:bg-blue-700 text-white rounded-lg shadow-sm hover:bg-blue-700 dark:hover:bg-blue-800 transition-colors duration-200">保存</button></div></motion.div></motion.div>)}</AnimatePresence>{/* 编辑货物模态框 */}<AnimatePresence>{isEditModalOpen && currentGoods && (<motion.divinitial={{ opacity: 0 }}animate={{ opacity: 1 }}exit={{ opacity: 0 }}transition={{ duration: 0.2 }}className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"onClick={() => setIsEditModalOpen(false)}><motion.divinitial={{ scale: 0.9, opacity: 0 }}animate={{ scale: 1, opacity: 1 }}exit={{ scale: 0.9, opacity: 0 }}transition={{ duration: 0.2 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-lg w-full max-w-2xl max-h-[90vh] overflow-y-auto"onClick={(e) => e.stopPropagation()}><div className="p-5 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center"><h3 className="font-bold text-lg">编辑货物</h3><button onClick={() => setIsEditModalOpen(false)} className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"><X size={20} /></button></div><div className="p-5"><div className="grid grid-cols-1 md:grid-cols-2 gap-4"><div><label className="block text-sm font-medium mb-2">货物ID</label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-gray-100 dark:bg-gray-900 text-gray-500 dark:text-gray-400 cursor-not-allowed"value={currentGoods.id}disabled/></div><div><label className="block text-sm font-medium mb-2">货物名称 <span className="text-red-500">*</span></label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={currentGoods.name}onChange={(e) => setCurrentGoods({ ...currentGoods, name: e.target.value })}/></div><div><label className="block text-sm font-medium mb-2">货物分类 <span className="text-red-500">*</span></label><selectclassName="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={currentGoods.category}onChange={(e) => setCurrentGoods({ ...currentGoods, category: e.target.value })}><option value="电子产品">电子产品</option><option value="服装鞋帽">服装鞋帽</option><option value="食品饮料">食品饮料</option><option value="家居用品">家居用品</option><option value="其他">其他</option></select></div><div><label className="block text-sm font-medium mb-2">数量</label><inputtype="number"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={currentGoods.quantity}onChange={(e) => setCurrentGoods({ ...currentGoods, quantity: parseInt(e.target.value) || 0 })}/></div><div><label className="block text-sm font-medium mb-2">存放位置 <span className="text-red-500">*</span></label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={currentGoods.location}onChange={(e) => setCurrentGoods({ ...currentGoods, location: e.target.value })}/></div><div><label className="block text-sm font-medium mb-2">保质期</label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={currentGoods.shelfLife || ''}onChange={(e) => setCurrentGoods({ ...currentGoods, shelfLife: e.target.value })}placeholder="例如:12个月"/></div><div><label className="block text-sm font-medium mb-2">重量 (kg)</label><inputtype="number"step="0.1"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={currentGoods.weight}onChange={(e) => setCurrentGoods({ ...currentGoods, weight: parseFloat(e.target.value) || 0 })}/></div><div><label className="block text-sm font-medium mb-2">尺寸</label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={currentGoods.dimensions}onChange={(e) => setCurrentGoods({ ...currentGoods, dimensions: e.target.value })}/></div><div><label className="block text-sm font-medium mb-2">供应商</label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={currentGoods.supplier}onChange={(e) => setCurrentGoods({ ...currentGoods, supplier: e.target.value })}/></div><div><label className="block text-sm font-medium mb-2">条码</label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={currentGoods.barcode}onChange={(e) => setCurrentGoods({ ...currentGoods, barcode: e.target.value })}/></div><div><label className="block text-sm font-medium mb-2">库存状态</label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-gray-100 dark:bg-gray-900 text-gray-500 dark:text-gray-400 cursor-not-allowed"value={currentGoods.status === 'in-stock' ? '库存充足' : currentGoods.status === 'low-stock' ? '库存不足' : '缺货'}disabled/></div><div><label className="block text-sm font-medium mb-2">最后更新时间</label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-gray-100 dark:bg-gray-900 text-gray-500 dark:text-gray-400 cursor-not-allowed"value={currentGoods.lastUpdated}disabled/></div></div></div><div className="p-5 border-t border-gray-200 dark:border-gray-700 flex justify-end space-x-3"><buttononClick={() => setIsEditModalOpen(false)}className="px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200">取消</button><buttononClick={handleEditGoods}className="px-4 py-2 bg-blue-600 dark:bg-blue-700 text-white rounded-lg shadow-sm hover:bg-blue-700 dark:hover:bg-blue-800 transition-colors duration-200">更新</button></div></motion.div></motion.div>)}</AnimatePresence>{/* 货物详情模态框 */}<AnimatePresence>{isDetailModalOpen && currentGoods && (<motion.divinitial={{ opacity: 0 }}animate={{ opacity: 1 }}exit={{ opacity: 0 }}transition={{ duration: 0.2 }}className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"onClick={() => setIsDetailModalOpen(false)}><motion.divinitial={{ scale: 0.9, opacity: 0 }}animate={{ scale: 1, opacity: 1 }}exit={{ scale: 0.9, opacity: 0 }}transition={{ duration: 0.2 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-lg w-full max-w-xl max-h-[90vh] overflow-y-auto"onClick={(e) => e.stopPropagation()}><div className="p-5 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center"><h3 className="font-bold text-lg">货物详情</h3><button onClick={() => setIsDetailModalOpen(false)} className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"><X size={20} /></button></div><div className="p-5"><div className="mb-6"><div className="flex justify-center mb-4"><div className="w-20 h-20 rounded-full bg-blue-100 dark:bg-blue-900 flex items-center justify-center text-blue-600 dark:text-blue-400"><Package size={32} /></div></div><h4 className="text-xl font-bold text-center">{currentGoods.name}</h4><p className="text-center text-gray-500 dark:text-gray-400">{currentGoods.id}</p></div><div className="space-y-4"><div className="grid grid-cols-1 md:grid-cols-2 gap-4"><div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">货物分类</p><p className="font-medium">{currentGoods.category}</p></div><div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">库存状态</p><p className="font-medium">{renderStatusBadge(currentGoods.status)}</p></div><div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">当前数量</p><p className="font-medium">{currentGoods.quantity}</p></div><div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">存放位置</p><p className="font-medium">{currentGoods.location}</p></div>{currentGoods.shelfLife && (<div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">保质期</p><p className="font-medium">{currentGoods.shelfLife}</p></div>)}<div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">重量</p><p className="font-medium">{currentGoods.weight} kg</p></div><div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">尺寸</p><p className="font-medium">{currentGoods.dimensions}</p></div><div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">供应商</p><p className="font-medium">{currentGoods.supplier}</p></div><div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg md:col-span-2"><p className="text-sm text-gray-500 dark:text-gray-400">条码</p><p className="font-medium">{currentGoods.barcode}</p></div><div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">最后更新</p><p className="font-medium">{currentGoods.lastUpdated}</p></div></div></div></div><div className="p-5 border-t border-gray-200 dark:border-gray-700 flex justify-end"><buttononClick={() => setIsDetailModalOpen(false)}className="px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200">关闭</button></div></motion.div></motion.div>)}</AnimatePresence>{/* 删除确认模态框 */}<AnimatePresence>{isDeleteModalOpen && currentGoods && (<motion.divinitial={{ opacity: 0 }}animate={{ opacity: 1 }}exit={{ opacity: 0 }}transition={{ duration: 0.2 }}className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"onClick={() => setIsDeleteModalOpen(false)}><motion.divinitial={{ scale: 0.9, opacity: 0 }}animate={{ scale: 1, opacity: 1 }}exit={{ scale: 0.9, opacity: 0 }}transition={{ duration: 0.2 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-lg w-full max-w-md"onClick={(e) => e.stopPropagation()}><div className="p-5 text-center"><div className="flex justify-center mb-4"><div className="w-16 h-16 rounded-full bg-red-100 dark:bg-red-900 flex items-center justify-center text-red-600 dark:text-red-400"><AlertCircle size={24} /></div></div><h3 className="font-bold text-lg mb-2">确认删除</h3><p className="text-gray-500 dark:text-gray-400">您确定要删除货物 <span className="font-medium">{currentGoods.name}</span> 吗?此操作无法撤销。</p></div><div className="p-5 border-t border-gray-200 dark:border-gray-700 flex justify-center space-x-3"><buttononClick={() => setIsDeleteModalOpen(false)}className="px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200">取消</button><buttononClick={handleDeleteGoods}className="px-4 py-2 bg-red-600 dark:bg-red-700 text-white rounded-lg shadow-sm hover:bg-red-700 dark:hover:bg-red-800 transition-colors duration-200">删除</button></div></motion.div></motion.div>)}</AnimatePresence></div>);
};export default GoodsManagement;
RobotManagement.tsx #机器人管理 :
import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Server, Search, Plus, Edit, Eye, Filter, X, CheckCircle, AlertCircle, ChevronDown, RefreshCw,MapPin, Battery, Activity, Settings, Clock, Calendar
} from 'lucide-react';
import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, Cell
} from 'recharts';
import { useTheme } from '@/hooks/useTheme';
import { toast } from 'sonner';// 机器人数据类型定义
interface Robot {id: string;name: string;model: string;status: 'normal' | 'maintenance' | 'error' | 'idle';batteryLevel: number;temperature: number;location: string;currentTask: string;taskStatus: 'pending' | 'in-progress' | 'completed' | 'failed';lastMaintenance: string;nextMaintenance: string;operationHours: number;errorCount: number;efficiency: number;ipAddress: string;firmwareVersion: string;sensors: {camera: boolean;lidar: boolean;ultrasonic: boolean;infrared: boolean;};
}// 任务类型定义
interface Task {id: string;type: string;priority: 'low' | 'medium' | 'high';status: 'pending' | 'in-progress' | 'completed' | 'failed';assignedTo: string | null;startTime: string;endTime: string | null;description: string;locationFrom: string;locationTo: string;
}// 模拟机器人数据
const generateMockRobots = (): Robot[] => {const models = ['R-500', 'R-700', 'R-900', 'R-1000', 'R-1200'];const statuses: Array<'normal' | 'maintenance' | 'error' | 'idle'> = ['normal', 'maintenance', 'error', 'idle'];const locations = ['A区-01-01', 'A区-02-03', 'B区-01-02', 'B区-03-05', 'C区-01-01', 'C区-02-04', '充电区-01', '充电区-02', '维修区'];const tasks = ['入库搬运', '出库准备', '货架整理', '库存盘点', '货物移库', '充电中', '系统自检', '等待任务'];const taskStatuses: Array<'pending' | 'in-progress' | 'completed' | 'failed'> = ['pending', 'in-progress', 'completed', 'failed'];return Array.from({ length: 20 }, (_, index) => {const status = statuses[Math.floor(Math.random() * statuses.length)];const batteryLevel = Math.floor(Math.random() * 100);const temperature = Math.floor(Math.random() * 30) + 20;const lastMaintenanceDate = new Date(Date.now() - Math.floor(Math.random() * 30) * 24 * 60 * 60 * 1000);const nextMaintenanceDate = new Date(lastMaintenanceDate.getTime() + 90 * 24 * 60 * 60 * 1000);return {id: `R${String(index + 1).padStart(3, '0')}`,name: `机器人${index + 1}`,model: models[Math.floor(Math.random() * models.length)],status,batteryLevel,temperature,location: status === 'error' ? '维修区' : locations[Math.floor(Math.random() * locations.length)],currentTask: status === 'maintenance' ? '系统维护' : tasks[Math.floor(Math.random() * tasks.length)],taskStatus: taskStatuses[Math.floor(Math.random() * taskStatuses.length)],lastMaintenance: lastMaintenanceDate.toLocaleDateString('zh-CN'),nextMaintenance: nextMaintenanceDate.toLocaleDateString('zh-CN'),operationHours: Math.floor(Math.random() * 5000) + 500,errorCount: status === 'error' ? Math.floor(Math.random() * 5) + 1 : Math.floor(Math.random() * 3),efficiency: Math.floor(Math.random() * 30) + 70,ipAddress: `192.168.1.${Math.floor(Math.random() * 255) + 1}`,firmwareVersion: `v${Math.floor(Math.random() * 3) + 1}.${Math.floor(Math.random() * 10)}.${Math.floor(Math.random() * 10)}`,sensors: {camera: Math.random() > 0.1,lidar: Math.random() > 0.1,ultrasonic: Math.random() > 0.1,infrared: Math.random() > 0.1}};});
};// 模拟任务数据
const generateMockTasks = (): Task[] => {const taskTypes = ['入库搬运', '出库准备', '货架整理', '库存盘点', '货物移库', '系统巡检'];const priorities: Array<'low' | 'medium' | 'high'> = ['low', 'medium', 'high'];const statuses: Array<'pending' | 'in-progress' | 'completed' | 'failed'> = ['pending', 'in-progress', 'completed', 'failed'];const locations = ['A区-01-01', 'A区-02-03', 'B区-01-02', 'B区-03-05', 'C区-01-01', 'C区-02-04'];return Array.from({ length: 30 }, (_, index) => {const status = statuses[Math.floor(Math.random() * statuses.length)];const startDate = new Date(Date.now() - Math.floor(Math.random() * 7) * 24 * 60 * 60 * 1000);const endDate = status === 'completed' || status === 'failed' ? new Date(startDate.getTime() + Math.floor(Math.random() * 8) * 60 * 60 * 1000) : null;return {id: `T${String(index + 1).padStart(4, '0')}`,type: taskTypes[Math.floor(Math.random() * taskTypes.length)],priority: priorities[Math.floor(Math.random() * priorities.length)],status,assignedTo: status === 'in-progress' || status === 'completed' || status === 'failed' ? `R${String(Math.floor(Math.random() * 20) + 1).padStart(3, '0')}` : null,startTime: startDate.toLocaleString('zh-CN'),endTime: endDate ? endDate.toLocaleString('zh-CN') : null,description: `${taskTypes[Math.floor(Math.random() * taskTypes.length)]}任务#${index + 1}`,locationFrom: locations[Math.floor(Math.random() * locations.length)],locationTo: locations[Math.floor(Math.random() * locations.length)]};});
};// 模拟性能数据
const generatePerformanceData = () => {const days = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];return days.map(day => ({day,efficiency: Math.floor(Math.random() * 20) + 75,errorRate: Math.random() * 3,tasksCompleted: Math.floor(Math.random() * 15) + 10}));
};// 机器人状态标签渲染函数
const renderStatusBadge = (status: string) => {switch (status) {case 'normal':return <span className="px-2 py-1 text-xs rounded-full bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200">正常运行</span>;case 'maintenance':return <span className="px-2 py-1 text-xs rounded-full bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200">维护中</span>;case 'error':return <span className="px-2 py-1 text-xs rounded-full bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200">故障</span>;case 'idle':return <span className="px-2 py-1 text-xs rounded-full bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200">空闲</span>;default:return <span className="px-2 py-1 text-xs rounded-full bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200">未知</span>;}
};// 任务状态标签渲染函数
const renderTaskStatusBadge = (status: string) => {switch (status) {case 'pending':return <span className="px-2 py-1 text-xs rounded-full bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200">待处理</span>;case 'in-progress':return <span className="px-2 py-1 text-xs rounded-full bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200">进行中</span>;case 'completed':return <span className="px-2 py-1 text-xs rounded-full bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200">已完成</span>;case 'failed':return <span className="px-2 py-1 text-xs rounded-full bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200">失败</span>;default:return <span className="px-2 py-1 text-xs rounded-full bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200">未知</span>;}
};// 优先级标签渲染函数
const renderPriorityBadge = (priority: string) => {switch (priority) {case 'low':return <span className="px-2 py-1 text-xs rounded-full bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200"></span>;case 'medium':return <span className="px-2 py-1 text-xs rounded-full bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200"></span>;case 'high':return <span className="px-2 py-1 text-xs rounded-full bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200"></span>;default:return <span className="px-2 py-1 text-xs rounded-full bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200">未知</span>;}
};const RobotManagement: React.FC = () => {const { theme } = useTheme();const [robots, setRobots] = useState<Robot[]>([]);const [filteredRobots, setFilteredRobots] = useState<Robot[]>([]);const [tasks, setTasks] = useState<Task[]>([]);const [filteredTasks, setFilteredTasks] = useState<Task[]>([]);const [performanceData, setPerformanceData] = useState(generatePerformanceData());const [searchTerm, setSearchTerm] = useState('');const [selectedStatus, setSelectedStatus] = useState('all');const [selectedModel, setSelectedModel] = useState('all');const [isDetailModalOpen, setIsDetailModalOpen] = useState(false);const [isAssignTaskModalOpen, setIsAssignTaskModalOpen] = useState(false);const [isEditRobotModalOpen, setIsEditRobotModalOpen] = useState(false);const [currentRobot, setCurrentRobot] = useState<Robot | null>(null);const [isLoading, setIsLoading] = useState(true);const [currentPage, setCurrentPage] = useState(1);const [itemsPerPage] = useState(10);const [isFilterDropdownOpen, setIsFilterDropdownOpen] = useState(false);const [activeTab, setActiveTab] = useState<'robots' | 'tasks'>('robots');const [newTask, setNewTask] = useState<Partial<Task>>({type: '',priority: 'medium',description: '',locationFrom: '',locationTo: ''});// 加载数据useEffect(() => {// 模拟API请求延迟const timer = setTimeout(() => {const mockRobotsData = generateMockRobots();const mockTasksData = generateMockTasks();setRobots(mockRobotsData);setFilteredRobots(mockRobotsData);setTasks(mockTasksData);setFilteredTasks(mockTasksData);setIsLoading(false);}, 1000);return () => clearTimeout(timer);}, []);// 搜索和筛选机器人useEffect(() => {let result = [...robots];// 搜索筛选if (searchTerm) {result = result.filter(item => item.id.toLowerCase().includes(searchTerm.toLowerCase()) || item.name.toLowerCase().includes(searchTerm.toLowerCase()) ||item.model.toLowerCase().includes(searchTerm.toLowerCase()));}// 状态筛选if (selectedStatus !== 'all') {result = result.filter(item => item.status === selectedStatus);}// 型号筛选if (selectedModel !== 'all') {result = result.filter(item => item.model === selectedModel);}setFilteredRobots(result);setCurrentPage(1); // 重置到第一页}, [searchTerm, selectedStatus, selectedModel, robots]);// 搜索和筛选任务useEffect(() => {let result = [...tasks];// 搜索筛选if (searchTerm) {result = result.filter(item => item.id.toLowerCase().includes(searchTerm.toLowerCase()) || item.type.toLowerCase().includes(searchTerm.toLowerCase()) ||item.description.toLowerCase().includes(searchTerm.toLowerCase()));}setFilteredTasks(result);setCurrentPage(1); // 重置到第一页}, [searchTerm, tasks]);// 分页功能const indexOfLastItem = currentPage * itemsPerPage;const indexOfFirstItem = indexOfLastItem - itemsPerPage;const currentItems = activeTab === 'robots' ? filteredRobots.slice(indexOfFirstItem, indexOfLastItem): filteredTasks.slice(indexOfFirstItem, indexOfLastItem);const totalPages = Math.ceil((activeTab === 'robots' ? filteredRobots.length : filteredTasks.length) / itemsPerPage);// 打开机器人详情模态框const openDetailModal = (robot: Robot) => {setCurrentRobot({ ...robot });setIsDetailModalOpen(true);};// 打开分配任务模态框const openAssignTaskModal = (robot: Robot) => {setCurrentRobot({ ...robot });setIsAssignTaskModalOpen(true);};// 打开编辑机器人模态框const openEditRobotModal = (robot: Robot) => {setCurrentRobot({ ...robot });setIsEditRobotModalOpen(true);};// 刷新数据const handleRefresh = () => {setIsLoading(true);// 模拟API请求延迟const timer = setTimeout(() => {const mockRobotsData = generateMockRobots();const mockTasksData = generateMockTasks();const mockPerformanceData = generatePerformanceData();setRobots(mockRobotsData);setFilteredRobots(mockRobotsData);setTasks(mockTasksData);setFilteredTasks(mockTasksData);setPerformanceData(mockPerformanceData);setIsLoading(false);toast.success('数据刷新成功');}, 1000);return () => clearTimeout(timer);};// 处理任务分配const handleAssignTask = () => {if (!currentRobot || !newTask.type || !newTask.description || !newTask.locationFrom || !newTask.locationTo) {toast.error('请填写完整的任务信息');return;}// 创建新任务const taskToAdd: Task = {id: `T${String(tasks.length + 1).padStart(4, '0')}`,type: newTask.type as string,priority: newTask.priority as 'low' | 'medium' | 'high',status: 'pending',assignedTo: currentRobot.id,startTime: new Date().toLocaleString('zh-CN'),endTime: null,description: newTask.description as string,locationFrom: newTask.locationFrom as string,locationTo: newTask.locationTo as string};// 更新机器人状态const updatedRobots = robots.map(robot => robot.id === currentRobot.id ? { ...robot, currentTask: newTask.type as string, taskStatus: 'pending' } : robot);setTasks([taskToAdd, ...tasks]);setRobots(updatedRobots);setIsAssignTaskModalOpen(false);setNewTask({type: '',priority: 'medium',description: '',locationFrom: '',locationTo: ''});toast.success(`已为${currentRobot.name}分配任务`);};// 处理机器人信息更新const handleUpdateRobot = () => {if (!currentRobot) return;const updatedRobots = robots.map(robot => robot.id === currentRobot.id ? currentRobot : robot);setRobots(updatedRobots);setIsEditRobotModalOpen(false);setCurrentRobot(null);toast.success('机器人信息更新成功');};// 分页控制const handlePageChange = (page: number) => {setCurrentPage(page);};// 检查传感器状态const checkSensorStatus = (sensorStatus: boolean) => {return sensorStatus ? '正常' : '故障';};// 获取机器人状态图标const getStatusIcon = (status: string) => {switch (status) {case 'normal':return <CheckCircle size={16} className="text-green-500" />;case 'maintenance':return <Settings size={16} className="text-yellow-500" />;case 'error':return <AlertCircle size={16} className="text-red-500" />;case 'idle':return <Clock size={16} className="text-blue-500" />;default:return null;}};// 电池状态样式const getBatteryStyle = (level: number) => {if (level > 70) return 'bg-green-500';if (level > 30) return 'bg-yellow-500';return 'bg-red-500';};return (<div className="p-4 md:p-6">{/* 页面标题和操作区 */}<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-6"><div><h2 className="text-2xl font-bold">机器人管理</h2><p className="text-gray-500 dark:text-gray-400">管理仓库中的所有机器人设备</p></div><div className="flex space-x-3 mt-4 md:mt-0"><button onClick={handleRefresh}className="flex items-center px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200"><RefreshCw size={16} className="mr-2" />刷新</button>{activeTab === 'robots' && currentRobot && (<button onClick={() => openAssignTaskModal(currentRobot)}className="flex items-center px-4 py-2 bg-blue-600 dark:bg-blue-700 text-white rounded-lg shadow-sm hover:bg-blue-700 dark:hover:bg-blue-800 transition-colors duration-200"><Plus size={16} className="mr-2" />分配任务</button>)}</div></div>{/* 标签切换 */}<div className="flex border-b border-gray-200 dark:border-gray-700 mb-6"><buttononClick={() => setActiveTab('robots')}className={`px-4 py-3 font-medium text-sm border-b-2 ${activeTab === 'robots' ? 'border-blue-500 text-blue-600 dark:text-blue-400' : 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-600'}`}>机器人列表</button><buttononClick={() => setActiveTab('tasks')}className={`px-4 py-3 font-medium text-sm border-b-2 ${activeTab === 'tasks' ? 'border-blue-500 text-blue-600 dark:text-blue-400' : 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-600'}`}>任务管理</button></div>{/* 搜索和筛选区 */}<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-4 mb-6"><div className="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4"><div className="relative flex-1"><Search size={18} className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" /><inputtype="text"placeholder={activeTab === 'robots' ? "搜索机器人ID、名称或型号..." : "搜索任务ID、类型或描述..."}className="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={searchTerm}onChange={(e) => setSearchTerm(e.target.value)}/>{searchTerm && (<button onClick={() => setSearchTerm('')}className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"><X size={16} /></button>)}</div>{activeTab === 'robots' && (<div className="relative"><buttononClick={() => setIsFilterDropdownOpen(!isFilterDropdownOpen)}className="flex items-center px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200"><Filter size={16} className="mr-2" />筛选<ChevronDown size={16} className={`ml-2 transition-transform duration-200 ${isFilterDropdownOpen ? 'transform rotate-180' : ''}`} /></button><AnimatePresence>{isFilterDropdownOpen && (<motion.divinitial={{ opacity: 0, y: -10 }}animate={{ opacity: 1, y: 0 }}exit={{ opacity: 0, y: -10 }}transition={{ duration: 0.2 }}className="absolute top-full left-0 mt-2 w-64 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg shadow-lg z-10 p-4"><div className="mb-4"><label className="block text-sm font-medium mb-2">机器人状态</label><selectclassName="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={selectedStatus}onChange={(e) => setSelectedStatus(e.target.value)}><option value="all">全部状态</option><option value="normal">正常运行</option><option value="maintenance">维护中</option><option value="error">故障</option><option value="idle">空闲</option></select></div><div><label className="block text-sm font-medium mb-2">机器人型号</label><selectclassName="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={selectedModel}onChange={(e) => setSelectedModel(e.target.value)}><option value="all">全部型号</option><option value="R-500">R-500</option><option value="R-700">R-700</option><option value="R-900">R-900</option><option value="R-1000">R-1000</option><option value="R-1200">R-1200</option></select></div><div className="mt-4 flex justify-end"><buttononClick={() => {setSelectedStatus('all');setSelectedModel('all');setIsFilterDropdownOpen(false);}}className="text-sm text-blue-600 dark:text-blue-400 hover:underline">重置筛选</button></div></motion.div>)}</AnimatePresence></div>)}</div></div>{/* 主要内容区域 */}<div className={`grid grid-cols-1 ${activeTab === 'robots' ? 'lg:grid-cols-3' : 'lg:grid-cols-1'} gap-6 mb-6`}>{/* 机器人列表或任务列表 */}<div className={activeTab === 'robots' ? "lg:col-span-2" : "lg:col-span-1"}><div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm overflow-hidden"><div className="p-5 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center"><h3 className="font-bold text-lg">{activeTab === 'robots' ? '机器人列表' : '任务列表'}</h3><div className="text-sm text-gray-500 dark:text-gray-400">{activeTab === 'robots' ? `共 ${filteredRobots.length} 台机器人` : `共 ${filteredTasks.length} 个任务`}</div></div>{isLoading ? (<div className="h-96 flex items-center justify-center"><div className="flex flex-col items-center"><RefreshCw size={32} className="text-blue-500 animate-spin" /><p className="mt-2 text-gray-500 dark:text-gray-400">加载中...</p></div></div>) : (activeTab === 'robots' && filteredRobots.length === 0) || (activeTab === 'tasks' && filteredTasks.length === 0) ? (<div className="h-96 flex items-center justify-center"><div className="text-center"><Server size={48} className="mx-auto text-gray-300 dark:text-gray-600 mb-2" /><p className="text-gray-500 dark:text-gray-400">没有找到匹配的{activeTab === 'robots' ? '机器人' : '任务'}</p><button onClick={() => {setSearchTerm('');setSelectedStatus('all');setSelectedModel('all');}}className="mt-2 text-sm text-blue-600 dark:text-blue-400 hover:underline">清除筛选条件</button></div></div>) : (<><div className="overflow-x-auto"><table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">{activeTab === 'robots' ? (<><thead><tr><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">ID</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">名称</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">型号</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">状态</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">电量</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">位置</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">当前任务</th><th className="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">操作</th></tr></thead><tbody className="divide-y divide-gray-200 dark:divide-gray-700">{currentItems.map((robot) => (<tr key={robot.id} className="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200"onClick={() => setCurrentRobot(robot)}><td className="px-4 py-3 whitespace-nowrap text-sm font-medium">{robot.id}</td><td className="px-4 py-3 whitespace-nowrap text-sm">{robot.name}</td><td className="px-4 py-3 whitespace-nowrap text-sm">{robot.model}</td><td className="px-4 py-3 whitespace-nowrap text-sm">{renderStatusBadge(robot.status)}</td><td className="px-4 py-3 whitespace-nowrap text-sm"><div className="flex items-center"><Battery size={14} className={`mr-1 ${getBatteryStyle(robot.batteryLevel)} text-white rounded`} /><span>{robot.batteryLevel}%</span></div></td><td className="px-4 py-3 whitespace-nowrap text-sm">{robot.location}</td><td className="px-4 py-3 whitespace-nowrap text-sm">{robot.currentTask}</td><td className="px-4 py-3 whitespace-nowrap text-right text-sm font-medium"><div className="flex justify-end space-x-2"><buttononClick={(e) => {e.stopPropagation();openDetailModal(robot);}}className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300"><Eye size={16} /></button><buttononClick={(e) => {e.stopPropagation();openAssignTaskModal(robot);}}className="text-green-600 dark:text-green-400 hover:text-green-800 dark:hover:text-green-300"><Plus size={16} /></button><buttononClick={(e) => {e.stopPropagation();openEditRobotModal(robot);}}className="text-purple-600 dark:text-purple-400 hover:text-purple-800 dark:hover:text-purple-300"><Edit size={16} /></button></div></td></tr>))}</tbody></>) : (<><thead><tr><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">ID</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">类型</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">优先级</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">状态</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">分配给</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">开始时间</th><th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">结束时间</th></tr></thead><tbody className="divide-y divide-gray-200 dark:divide-gray-700">{currentItems.map((task) => (<tr key={task.id} className="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200"><td className="px-4 py-3 whitespace-nowrap text-sm font-medium">{task.id}</td><td className="px-4 py-3 whitespace-nowrap text-sm">{task.type}</td><td className="px-4 py-3 whitespace-nowrap text-sm">{renderPriorityBadge(task.priority)}</td><td className="px-4 py-3 whitespace-nowrap text-sm">{renderTaskStatusBadge(task.status)}</td><td className="px-4 py-3 whitespace-nowrap text-sm">{task.assignedTo || '未分配'}</td><td className="px-4 py-3 whitespace-nowrap text-sm">{task.startTime}</td><td className="px-4 py-3 whitespace-nowrap text-sm">{task.endTime || '-'}</td></tr>))}</tbody></>)}</table></div>{/* 分页控件 */}<div className="px-4 py-3 flex items-center justify-between border-t border-gray-200 dark:border-gray-700"><div className="flex-1 flex justify-between sm:hidden"><buttononClick={() => handlePageChange(currentPage - 1)}disabled={currentPage === 1}className={`relative inline-flex items-center px-4 py-2 border rounded-md text-sm font-medium ${currentPage === 1 ? 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed' : 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700'}`}>上一页</button><buttononClick={() => handlePageChange(currentPage + 1)}disabled={currentPage === totalPages}className={`ml-3 relative inline-flex items-center px-4 py-2 border rounded-md text-sm font-medium ${currentPage === totalPages ? 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed' : 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700'}`}>下一页</button></div><div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between"><div><p className="text-sm text-gray-700 dark:text-gray-300">显示第 <span className="font-medium">{indexOfFirstItem + 1}</span><span className="font-medium">{Math.min(indexOfLastItem, activeTab === 'robots' ? filteredRobots.length : filteredTasks.length)}</span> 条,共 <span className="font-medium">{activeTab === 'robots' ? filteredRobots.length : filteredTasks.length}</span> 条记录</p></div><div><nav className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">{[...Array(totalPages)].map((_, index) => {// 只显示当前页、首页、末页以及前后各一页if (index === 0 || index === totalPages - 1 || Math.abs(index - (currentPage - 1)) <= 1) {return (<buttonkey={index}onClick={() => handlePageChange(index + 1)}className={`relative inline-flex items-center px-2 py-2 rounded-md text-sm font-medium ${currentPage === index + 1? 'bg-blue-50 dark:bg-blue-900 border-blue-300 dark:border-blue-700 text-blue-600 dark:text-blue-400': 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700'}`}>{index + 1}</button>);}// 添加省略号if ((index === 2 && currentPage > 4) ||(index === totalPages - 3 && currentPage < totalPages - 3)) {return (<span key={index} className="relative inline-flex items-center px-2 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-sm text-gray-700 dark:text-gray-300">...</span>);}return null;})}</nav></div></div></div></>)}</div></div>{/* 机器人统计图表 */}{activeTab === 'robots' && (<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><h3 className="font-bold text-lg mb-4">机器人性能统计</h3><div className="h-64"><ResponsiveContainer width="100%" height="100%"><LineChartdata={performanceData}margin={{ top: 5, right: 5, left: 5, bottom: 25 }}><CartesianGrid strokeDasharray="3 3" stroke={theme === 'dark' ? '#374151' : '#e5e7eb'} /><XAxis dataKey="day" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'}angle={-45}textAnchor="end"height={60}/><YAxis yAxisId="left" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><YAxis yAxisId="right" orientation="right" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} /><Legend /><Line yAxisId="left"type="monotone" dataKey="efficiency" name="工作效率 (%)" stroke="#3b82f6" strokeWidth={2}dot={{ r: 3 }}/><Line yAxisId="left"type="monotone" dataKey="errorRate" name="故障率 (%)" stroke="#ef4444" strokeWidth={2}dot={{ r: 3 }}/><Line yAxisId="right"type="monotone" dataKey="tasksCompleted" name="完成任务数" stroke="#10b981" strokeWidth={2}dot={{ r: 3 }}/></LineChart></ResponsiveContainer></div>{/* 快速统计卡片 */}<div className="mt-6 space-y-3"><div className="flex justify-between items-center"><div className="flex items-center"><Server size={18} className="mr-2 text-blue-500" /><span className="text-sm">正常运行</span></div><span className="font-bold">{robots.filter(robot => robot.status === 'normal').length}</span></div><div className="flex justify-between items-center"><div className="flex items-center"><Settings size={18} className="mr-2 text-yellow-500" /><span className="text-sm">维护中</span></div><span className="font-bold">{robots.filter(robot => robot.status === 'maintenance').length}</span></div><div className="flex justify-between items-center"><div className="flex items-center"><AlertCircle size={18} className="mr-2 text-red-500" /><span className="text-sm">故障</span></div><span className="font-bold">{robots.filter(robot => robot.status === 'error').length}</span></div><div className="flex justify-between items-center"><div className="flex items-center"><Clock size={18} className="mr-2 text-purple-500" /><span className="text-sm">空闲</span></div><span className="font-bold">{robots.filter(robot => robot.status === 'idle').length}</span></div><div className="flex justify-between items-center pt-3 border-t border-gray-200 dark:border-gray-700"><div className="flex items-center"><Activity size={18} className="mr-2 text-green-500" /><span className="text-sm">平均效率</span></div><span className="font-bold">{Math.round(robots.reduce((sum, robot) => sum + robot.efficiency, 0) / Math.max(1, robots.length))}%</span></div></div></div>)}</div>{/* 机器人详情模态框 */}<AnimatePresence>{isDetailModalOpen && currentRobot && (<motion.divinitial={{ opacity: 0 }}animate={{ opacity: 1 }}exit={{ opacity: 0 }}transition={{ duration: 0.2 }}className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"onClick={() => setIsDetailModalOpen(false)}><motion.divinitial={{ scale: 0.9, opacity: 0 }}animate={{ scale: 1, opacity: 1 }}exit={{ scale: 0.9, opacity: 0 }}transition={{ duration: 0.2 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-lg w-full max-w-3xl max-h-[90vh] overflow-y-auto"onClick={(e) => e.stopPropagation()}><div className="p-5 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center"><h3 className="font-bold text-lg">机器人详情</h3><button onClick={() => setIsDetailModalOpen(false)} className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"><X size={20} /></button></div><div className="p-5"><div className="flex flex-col md:flex-row gap-6"><div className="md:w-1/4"><div className="flex justify-center mb-4"><div className="w-24 h-24 rounded-full bg-blue-100 dark:bg-blue-900 flex items-center justify-center text-blue-600 dark:text-blue-400"><Server size={40} /></div></div><h4 className="text-xl font-bold text-center">{currentRobot.name}</h4><p className="text-center text-gray-500 dark:text-gray-400 mb-4">{currentRobot.id}</p><div className="space-y-3 p-4 bg-gray-50 dark:bg-gray-900 rounded-lg"><div className="flex items-center justify-between"><span className="text-sm">型号</span><span className="font-medium">{currentRobot.model}</span></div><div className="flex items-center justify-between"><span className="text-sm">状态</span><span className="flex items-center">{getStatusIcon(currentRobot.status)}<span className="ml-1">{currentRobot.status === 'normal' ? '正常运行' :currentRobot.status === 'maintenance' ? '维护中' :currentRobot.status === 'error' ? '故障' : '空闲'}</span></span></div><div className="flex items-center justify-between"><span className="text-sm">电量</span><span className={`font-medium ${currentRobot.batteryLevel > 70 ? 'text-green-600 dark:text-green-400' :currentRobot.batteryLevel > 30 ? 'text-yellow-600 dark:text-yellow-400' :'text-red-600 dark:text-red-400'}`}>{currentRobot.batteryLevel}%</span></div><div className="flex items-center justify-between"><span className="text-sm">温度</span><span className={`font-medium ${currentRobot.temperature > 60 ? 'text-red-600 dark:text-red-400' :currentRobot.temperature > 50 ? 'text-yellow-600 dark:text-yellow-400' :'text-green-600 dark:text-green-400'}`}>{currentRobot.temperature}°C</span></div></div></div><div className="md:w-3/4 space-y-5"><div className="grid grid-cols-1 md:grid-cols-2 gap-4"><div className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">当前任务</p><p className="font-medium">{currentRobot.currentTask}</p></div><div className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">任务状态</p><p className="font-medium">{currentRobot.taskStatus === 'pending' ? '待处理' :currentRobot.taskStatus === 'in-progress' ? '进行中' :currentRobot.taskStatus === 'completed' ? '已完成' : '失败'}</p></div><div className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">当前位置</p><div className="flex items-center mt-1"><MapPin size={16} className="text-blue-500 mr-1" /><p className="font-medium">{currentRobot.location}</p></div></div><div className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">工作效率</p><p className="font-medium">{currentRobot.efficiency}%</p></div><div className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">运行时长</p><p className="font-medium">{currentRobot.operationHours} 小时</p></div><div className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">故障次数</p><p className="font-medium">{currentRobot.errorCount}</p></div><div className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">IP地址</p><p className="font-medium">{currentRobot.ipAddress}</p></div><div className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg"><p className="text-sm text-gray-500 dark:text-gray-400">固件版本</p><p className="font-medium">{currentRobot.firmwareVersion}</p></div></div><div><h4 className="font-medium mb-3">传感器状态</h4><div className="grid grid-cols-2 md:grid-cols-4 gap-3"><div className={`p-3 rounded-lg border ${currentRobot.sensors.camera ? 'border-green-200 dark:border-green-900 bg-green-50 dark:bg-green-900/30' : 'border-red-200 dark:border-red-900 bg-red-50 dark:bg-red-900/30'}`}><p className="text-sm">摄像头</p><p className={`font-medium mt-1 ${currentRobot.sensors.camera ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}`}>{checkSensorStatus(currentRobot.sensors.camera)}</p></div><div className={`p-3 rounded-lg border ${currentRobot.sensors.lidar ? 'border-green-200 dark:border-green-900 bg-green-50 dark:bg-green-900/30' : 'border-red-200 dark:border-red-900 bg-red-50 dark:bg-red-900/30'}`}><p className="text-sm">激光雷达</p><p className={`font-medium mt-1 ${currentRobot.sensors.lidar ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}`}>{checkSensorStatus(currentRobot.sensors.lidar)}</p></div><div className={`p-3 rounded-lg border ${currentRobot.sensors.ultrasonic ? 'border-green-200 dark:border-green-900 bg-green-50 dark:bg-green-900/30' : 'border-red-200 dark:border-red-900 bg-red-50 dark:bg-red-900/30'}`}><p className="text-sm">超声波</p><p className={`font-medium mt-1 ${currentRobot.sensors.ultrasonic ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}`}>{checkSensorStatus(currentRobot.sensors.ultrasonic)}</p></div><div className={`p-3 rounded-lg border ${currentRobot.sensors.infrared ? 'border-green-200 dark:border-green-900 bg-green-50 dark:bg-green-900/30' : 'border-red-200 dark:border-red-900 bg-red-50 dark:bg-red-900/30'}`}><p className="text-sm">红外传感器</p><p className={`font-medium mt-1 ${currentRobot.sensors.infrared ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}`}>{checkSensorStatus(currentRobot.sensors.infrared)}</p></div></div></div><div><h4 className="font-medium mb-3">维护记录</h4><div className="grid grid-cols-1 md:grid-cols-2 gap-4"><div className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg"><div className="flex items-center"><Calendar size={16} className="text-gray-500 dark:text-gray-400 mr-2" /><p className="text-sm text-gray-500 dark:text-gray-400">上次维护</p></div><p className="font-medium mt-1">{currentRobot.lastMaintenance}</p></div><div className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg"><div className="flex items-center"><Calendar size={16} className="text-gray-500 dark:text-gray-400 mr-2" /><p className="text-sm text-gray-500 dark:text-gray-400">下次维护</p></div><p className="font-medium mt-1">{currentRobot.nextMaintenance}</p></div></div></div></div></div></div><div className="p-5 border-t border-gray-200 dark:border-gray-700 flex justify-end"><buttononClick={() => setIsDetailModalOpen(false)}className="px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200">关闭</button></div></motion.div></motion.div>)}</AnimatePresence>{/* 分配任务模态框 */}<AnimatePresence>{isAssignTaskModalOpen && currentRobot && (<motion.divinitial={{ opacity: 0 }}animate={{ opacity: 1 }}exit={{ opacity: 0 }}transition={{ duration: 0.2 }}className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"onClick={() => setIsAssignTaskModalOpen(false)}><motion.divinitial={{ scale: 0.9, opacity: 0 }}animate={{ scale: 1, opacity: 1 }}exit={{ scale: 0.9, opacity: 0 }}transition={{ duration: 0.2 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-lg w-full max-w-xl max-h-[90vh] overflow-y-auto"onClick={(e) => e.stopPropagation()}><div className="p-5 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center"><h3 className="font-bold text-lg">分配任务</h3><button onClick={() => setIsAssignTaskModalOpen(false)} className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"><X size={20} /></button></div><div className="p-5"><div className="flex items-center mb-4 p-3 bg-blue-50 dark:bg-blue-900/30 rounded-lg"><Server size={24} className="text-blue-600 dark:text-blue-400 mr-3" /><div><p className="font-medium">{currentRobot.name}</p><p className="text-sm text-gray-500 dark:text-gray-400">{currentRobot.id}</p></div></div><div className="space-y-4"><div><label className="block text-sm font-medium mb-2">任务类型 <span className="text-red-500">*</span></label><selectclassName="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={newTask.type || ''}onChange={(e) => setNewTask({ ...newTask, type: e.target.value })}><option value="">请选择任务类型</option><option value="入库搬运">入库搬运</option><option value="出库准备">出库准备</option><option value="货架整理">货架整理</option><option value="库存盘点">库存盘点</option><option value="货物移库">货物移库</option><option value="系统巡检">系统巡检</option></select></div><div><label className="block text-sm font-medium mb-2">优先级</label><selectclassName="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={newTask.priority}onChange={(e) => setNewTask({ ...newTask, priority: e.target.value as 'low' | 'medium' | 'high' })}><option value="low"></option><option value="medium"></option><option value="high"></option></select></div><div><label className="block text-sm font-medium mb-2">任务描述 <span className="text-red-500">*</span></label><textareaclassName="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 min-h-[100px]"value={newTask.description || ''}onChange={(e) => setNewTask({ ...newTask, description: e.target.value })}placeholder="请输入任务描述..."></textarea></div><div className="grid grid-cols-1 md:grid-cols-2 gap-4"><div><label className="block text-sm font-medium mb-2">起始位置 <span className="text-red-500">*</span></label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={newTask.locationFrom || ''}onChange={(e) => setNewTask({ ...newTask, locationFrom: e.target.value })}placeholder="例如:A区-01-01"/></div><div><label className="block text-sm font-medium mb-2">目标位置 <span className="text-red-500">*</span></label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={newTask.locationTo || ''}onChange={(e) => setNewTask({ ...newTask, locationTo: e.target.value })}placeholder="例如:B区-02-03"/></div></div></div></div><div className="p-5 border-t border-gray-200 dark:border-gray-700 flex justify-end space-x-3"><buttononClick={() => setIsAssignTaskModalOpen(false)}className="px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200">取消</button><buttononClick={handleAssignTask}className="px-4 py-2 bg-blue-600 dark:bg-blue-700 text-white rounded-lg shadow-sm hover:bg-blue-700 dark:hover:bg-blue-800 transition-colors duration-200">分配任务</button></div></motion.div></motion.div>)}</AnimatePresence>{/* 编辑机器人模态框 */}<AnimatePresence>{isEditRobotModalOpen && currentRobot && (<motion.divinitial={{ opacity: 0 }}animate={{ opacity: 1 }}exit={{ opacity: 0 }}transition={{ duration: 0.2 }}className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"onClick={() => setIsEditRobotModalOpen(false)}><motion.divinitial={{ scale: 0.9, opacity: 0 }}animate={{ scale: 1, opacity: 1 }}exit={{ scale: 0.9, opacity: 0 }}transition={{ duration: 0.2 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-lg w-full max-w-xl max-h-[90vh] overflow-y-auto"onClick={(e) => e.stopPropagation()}><div className="p-5 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center"><h3 className="font-bold text-lg">编辑机器人信息</h3><button onClick={() => setIsEditRobotModalOpen(false)} className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"><X size={20} /></button></div><div className="p-5"><div className="space-y-4"><div><label className="block text-sm font-medium mb-2">机器人ID</label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-gray-100 dark:bg-gray-900 text-gray-500 dark:text-gray-400 cursor-not-allowed"value={currentRobot.id}disabled/></div><div><label className="block text-sm font-medium mb-2">机器人名称 <span className="text-red-500">*</span></label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={currentRobot.name}onChange={(e) => setCurrentRobot({ ...currentRobot, name: e.target.value })}/></div><div><label className="block text-sm font-medium mb-2">机器人型号 <span className="text-red-500">*</span></label><selectclassName="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={currentRobot.model}onChange={(e) => setCurrentRobot({ ...currentRobot, model: e.target.value })}><option value="R-500">R-500</option><option value="R-700">R-700</option><option value="R-900">R-900</option><option value="R-1000">R-1000</option><option value="R-1200">R-1200</option></select></div><div><label className="block text-sm font-medium mb-2">固件版本</label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={currentRobot.firmwareVersion}onChange={(e) => setCurrentRobot({ ...currentRobot, firmwareVersion: e.target.value })}/></div><div><label className="block text-sm font-medium mb-2">IP地址</label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={currentRobot.ipAddress}onChange={(e) => setCurrentRobot({ ...currentRobot, ipAddress: e.target.value })}/></div><div><label className="block text-sm font-medium mb-2">下次维护日期</label><inputtype="text"className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={currentRobot.nextMaintenance}onChange={(e) => setCurrentRobot({ ...currentRobot, nextMaintenance: e.target.value })}placeholder="YYYY-MM-DD"/></div></div></div><div className="p-5 border-t border-gray-200 dark:border-gray-700 flex justify-end space-x-3"><buttononClick={() => setIsEditRobotModalOpen(false)}className="px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200">取消</button><buttononClick={handleUpdateRobot}className="px-4 py-2 bg-blue-600 dark:bg-blue-700 text-white rounded-lg shadow-sm hover:bg-blue-700 dark:hover:bg-blue-800 transition-colors duration-200">保存</button></div></motion.div></motion.div>)}</AnimatePresence></div>);
};export default RobotManagement;
AnalyticsPage.tsx #数据分析:
import React, { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import { BarChart2, Search, Filter, RefreshCw, Calendar, TrendingUp, TrendingDown, Download, ChevronDown,ArrowUpRight, PieChart, LineChart, BarChart as BarChartIcon, Package
} from 'lucide-react';
import { LineChart as RechartsLineChart, Line, BarChart, Bar, PieChart as RechartsPieChart, Pie,XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer,Cell, AreaChart, Area
} from 'recharts';
import { useTheme } from '@/hooks/useTheme';
import { toast } from 'sonner';// 模拟库存趋势数据
const generateInventoryTrendData = () => {const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月'];let inventory = 12000;return months.map(month => {const inQuantity = Math.floor(Math.random() * 2000) + 1000;const outQuantity = Math.floor(Math.random() * 2000) + 1000;inventory = Math.max(8000, inventory + inQuantity - outQuantity);return {month,inventory,in: inQuantity,out: outQuantity};});
};// 模拟机器人效率数据
const generateRobotEfficiencyData = () => {const days = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];return days.map(day => ({day,efficiency: Math.floor(Math.random() * 20) + 75,errorRate: Math.random() * 3,tasksCompleted: Math.floor(Math.random() * 15) + 10}));
};// 模拟货物分类数据
const generateCategoryData = () => {return [{ name: '电子产品', value: 38, color: '#0088FE' },{ name: '服装鞋帽', value: 22, color: '#00C49F' },{ name: '食品饮料', value: 18, color: '#FFBB28' },{ name: '家居用品', value: 12, color: '#FF8042' },{ name: '其他', value: 10, color: '#8884d8' },];
};// 模拟周转率数据
const generateTurnoverData = () => {const months = ['1月', '2月', '3月', '4月', '5月', '6月'];return months.map(month => ({month,turnover: Math.floor(Math.random() * 50000) + 100000,avgInventory: Math.floor(Math.random() * 2000) + 10000}));
};// 模拟入库/出库数据
const generateInOutData = () => {const days = ['1月1日', '1月2日', '1月3日', '1月4日', '1月5日', '1月6日', '1月7日', '1月8日', '1月9日', '1月10日'];return days.map(day => ({day,in: Math.floor(Math.random() * 500) + 200,out: Math.floor(Math.random() * 400) + 150}));
};// 模拟仓库利用率数据
const generateWarehouseUtilizationData = () => {return [{ name: 'A区', utilization: 85 },{ name: 'B区', utilization: 72 },{ name: 'C区', utilization: 65 },{ name: 'D区', utilization: 45 },{ name: 'E区', utilization: 90 },];
};const AnalyticsPage: React.FC = () => {const { theme } = useTheme();const [inventoryTrend, setInventoryTrend] = useState(generateInventoryTrendData());const [robotEfficiency, setRobotEfficiency] = useState(generateRobotEfficiencyData());const [categoryData, setCategoryData] = useState(generateCategoryData());const [turnoverData, setTurnoverData] = useState(generateTurnoverData());const [inOutData, setInOutData] = useState(generateInOutData());const [warehouseUtilization, setWarehouseUtilization] = useState(generateWarehouseUtilizationData());const [searchTerm, setSearchTerm] = useState('');const [isLoading, setIsLoading] = useState(true);const [isDateFilterOpen, setIsDateFilterOpen] = useState(false);const [dateRange, setDateRange] = useState({ start: '', end: '' });const [activeTab, setActiveTab] = useState<'overview' | 'inventory' | 'robot' | 'warehouse'>('overview');// 加载数据useEffect(() => {// 模拟API请求延迟const timer = setTimeout(() => {setIsLoading(false);}, 800);return () => clearTimeout(timer);}, []);// 刷新数据const handleRefresh = () => {setIsLoading(true);// 模拟API请求延迟const timer = setTimeout(() => {setInventoryTrend(generateInventoryTrendData());setRobotEfficiency(generateRobotEfficiencyData());setCategoryData(generateCategoryData());setTurnoverData(generateTurnoverData());setInOutData(generateInOutData());setWarehouseUtilization(generateWarehouseUtilizationData());setIsLoading(false);toast.success('数据刷新成功');}, 800);return () => clearTimeout(timer);};// 导出数据const handleExportData = () => {toast.success('数据导出成功');};return (<div className="p-4 md:p-6">{/* 页面标题和操作区 */}<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-6"><div><h2 className="text-2xl font-bold">数据分析</h2><p className="text-gray-500 dark:text-gray-400">仓库运营数据分析与可视化</p></div><div className="flex space-x-3 mt-4 md:mt-0"><div className="relative"><buttononClick={() => setIsDateFilterOpen(!isDateFilterOpen)}className="flex items-center px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200"><Calendar size={16} className="mr-2" />日期筛选<ChevronDown size={16} className={`ml-2 transition-transform duration-200 ${isDateFilterOpen ? 'transform rotate-180' : ''}`} /></button>{isDateFilterOpen && (<div className="absolute top-full right-0 mt-2 w-64 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg shadow-lg z-10 p-4"><div className="mb-3"><label className="block text-sm font-medium mb-1">开始日期</label><inputtype="date"className="w-full px-3 py-1.5 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"value={dateRange.start}onChange={(e) => setDateRange({ ...dateRange, start: e.target.value })}/></div><div className="mb-3"><label className="block text-sm font-medium mb-1">结束日期</label><inputtype="date"className="w-full px-3 py-1.5 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"value={dateRange.end}onChange={(e) => setDateRange({ ...dateRange, end: e.target.value })}/></div><div className="flex justify-end space-x-2"><buttononClick={() => {setDateRange({ start: '', end: '' });setIsDateFilterOpen(false);}}className="text-sm px-2 py-1 border border-gray-300 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700">重置</button><buttononClick={() => {setIsDateFilterOpen(false);toast.success('日期筛选已应用');}}className="text-sm px-2 py-1 bg-blue-600 dark:bg-blue-700 text-white rounded hover:bg-blue-700 dark:hover:bg-blue-800">应用</button></div></div>)}</div><button onClick={handleExportData}className="flex items-center px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200"><Download size={16} className="mr-2" />导出</button><button onClick={handleRefresh}className="flex items-center px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200"><RefreshCw size={16} className="mr-2" />刷新</button></div></div>{/* 搜索和筛选区 */}<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-4 mb-6"><div className="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4"><div className="relative flex-1"><Search size={18} className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" /><inputtype="text"placeholder="搜索数据..."className="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"value={searchTerm}onChange={(e) => setSearchTerm(e.target.value)}/></div><div className="flex space-x-2"><buttononClick={() => setActiveTab('overview')}className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors duration-200 ${activeTab === 'overview' ? 'bg-blue-100 dark:bg-blue-900/50 text-blue-600 dark:text-blue-400' : 'bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700'}`}>概览</button><buttononClick={() => setActiveTab('inventory')}className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors duration-200 ${activeTab === 'inventory' ? 'bg-blue-100 dark:bg-blue-900/50 text-blue-600 dark:text-blue-400' : 'bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700'}`}>库存分析</button><buttononClick={() => setActiveTab('robot')}className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors duration-200 ${activeTab === 'robot' ? 'bg-blue-100 dark:bg-blue-900/50 text-blue-600 dark:text-blue-400' : 'bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700'}`}>机器人分析</button><buttononClick={() => setActiveTab('warehouse')}className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors duration-200 ${activeTab === 'warehouse' ? 'bg-blue-100 dark:bg-blue-900/50 text-blue-600 dark:text-blue-400' : 'bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700'}`}>仓库分析</button></div></div></div>{isLoading ? (<div className="min-h-[70vh] flex items-center justify-center"><div className="flex flex-col items-center"><RefreshCw size={32} className="text-blue-500 animate-spin" /><p className="mt-2 text-gray-500 dark:text-gray-400">加载数据中...</p></div></div>) : (<>{/* 概览标签页 */}{activeTab === 'overview' && (<motion.divinitial={{ opacity: 0 }}animate={{ opacity: 1 }}transition={{ duration: 0.5 }}>{/* 关键指标卡片 */}<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6"><motion.divinitial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}transition={{ duration: 0.3 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5 hover:shadow-md transition-shadow duration-300"><div className="flex justify-between items-start"><div><p className="text-gray-500 dark:text-gray-400 text-sm">总库存量</p><h3 className="text-2xl font-bold mt-1">{inventoryTrend[inventoryTrend.length - 1]?.inventory.toLocaleString() || '0'}</h3></div><div className="bg-blue-500 text-white p-2 rounded-lg"><Package size={20} /></div></div><div className="mt-3 flex items-center"><span className="text-green-500 flex items-center text-sm"><TrendingUp size={14} className="mr-1" />2.4%</span><span className="text-gray-400 text-xs ml-2">较上月</span></div></motion.div><motion.divinitial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}transition={{ duration: 0.3, delay: 0.1 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5 hover:shadow-md transition-shadow duration-300"><div className="flex justify-between items-start"><div><p className="text-gray-500 dark:text-gray-400 text-sm">本月入库量</p><h3 className="text-2xl font-bold mt-1">{inventoryTrend[inventoryTrend.length - 1]?.in.toLocaleString() || '0'}</h3></div><div className="bg-green-500 text-white p-2 rounded-lg"><ArrowUpRight size={20} /></div></div><div className="mt-3 flex items-center"><span className="text-green-500 flex items-center text-sm"><TrendingUp size={14} className="mr-1" />5.7%</span><span className="text-gray-400 text-xs ml-2">较上月</span></div></motion.div><motion.divinitial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}transition={{ duration: 0.3, delay: 0.2 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5 hover:shadow-md transition-shadow duration-300"><div className="flex justify-between items-start"><div><p className="text-gray-500 dark:text-gray-400 text-sm">本月出库量</p><h3 className="text-2xl font-bold mt-1">{inventoryTrend[inventoryTrend.length - 1]?.out.toLocaleString() || '0'}</h3></div><div className="bg-red-500 text-white p-2 rounded-lg"><TrendingDown size={20} /></div></div><div className="mt-3 flex items-center"><span className="text-red-500 flex items-center text-sm"><TrendingDown size={14} className="mr-1" />1.2%</span><span className="text-gray-400 text-xs ml-2">较上月</span></div></motion.div><motion.divinitial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}transition={{ duration: 0.3, delay: 0.3 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5 hover:shadow-md transition-shadow duration-300"><div className="flex justify-between items-start"><div><p className="text-gray-500 dark:text-gray-400 text-sm">平均机器人效率</p><h3 className="text-2xl font-bold mt-1">{Math.round(robotEfficiency.reduce((sum, day) => sum + day.efficiency, 0) / robotEfficiency.length)}%</h3></div><div className="bg-purple-500 text-white p-2 rounded-lg"><BarChartIcon size={20} /></div></div><div className="mt-3 flex items-center"><span className="text-green-500 flex items-center text-sm"><TrendingUp size={14} className="mr-1" />3.1%</span><span className="text-gray-400 text-xs ml-2">较上周</span></div></motion.div></div>{/* 图表区域 - 第一行 */}<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">{/* 库存趋势图 */}<motion.divinitial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}transition={{ duration: 0.3, delay: 0.4 }}className="lg:col-span-2 bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><div className="flex justify-between items-center mb-4"><h3 className="font-bold text-lg">库存趋势</h3><div className="flex space-x-2"><button className="px-3 py-1 text-xs rounded-full bg-blue-50 dark:bg-blue-900 text-blue-600 dark:text-blue-400">月度</button><button className="px-3 py-1 text-xs rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">季度</button><button className="px-3 py-1 text-xs rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">年度</button></div></div><div className="h-80"><ResponsiveContainer width="100%" height="100%"><AreaChartdata={inventoryTrend}margin={{ top: 10, right: 30, left: 0, bottom: 0 }}><defs><linearGradient id="colorInventory" x1="0" y1="0" x2="0" y2="1"><stop offset="5%" stopColor="#3b82f6" stopOpacity={0.8} /><stop offset="95%" stopColor="#3b82f6" stopOpacity={0} /></linearGradient></defs><CartesianGrid strokeDasharray="3 3" stroke={theme === 'dark' ? '#374151' : '#e5e7eb'} /><XAxis dataKey="month" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><YAxis stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} /><Legend /><Area type="monotone" dataKey="inventory" stroke="#3b82f6" fillOpacity={1} fill="url(#colorInventory)" strokeWidth={2}name="库存量"/><Line type="monotone" dataKey="in" stroke="#10b981" strokeWidth={2} dot={{ r: 3 }}name="入库量"/><Line type="monotone" dataKey="out" stroke="#f97316" strokeWidth={2} dot={{ r: 3 }}name="出库量"/></AreaChart></ResponsiveContainer></div></motion.div>{/* 货物分类饼图 */}<motion.divinitial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}transition={{ duration: 0.3, delay: 0.5 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><h3 className="font-bold text-lg mb-4">货物分类分布</h3><div className="h-64 flex justify-center"><ResponsiveContainer width="100%" height="100%"><RechartsPieChart><Piedata={categoryData}cx="50%"cy="50%"innerRadius={60}outerRadius={80}fill="#8884d8"paddingAngle={2}dataKey="value"label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}labelLine={false}>{categoryData.map((entry, index) => (<Cell key={`cell-${index}`} fill={entry.color} />))}</Pie><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} /></RechartsPieChart></ResponsiveContainer></div><div className="grid grid-cols-2 gap-2 mt-2">{categoryData.map((item, index) => (<div key={index} className="flex items-center"><div className="w-3 h-3 rounded-full mr-2" style={{ backgroundColor: item.color }}></div><span className="text-sm">{item.name}</span></div>))}</div></motion.div></div>{/* 图表区域 - 第二行 */}<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">{/* 机器人效率图 */}<motion.divinitial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}transition={{ duration: 0.3, delay: 0.6 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><div className="flex justify-between items-center mb-4"><h3 className="font-bold text-lg">机器人效率分析</h3><div className="flex space-x-2"><button className="px-3 py-1 text-xs rounded-full bg-blue-50 dark:bg-blue-900 text-blue-600 dark:text-blue-400">周度</button><button className="px-3 py-1 text-xs rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">月度</button><button className="px-3 py-1 text-xs rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">季度</button></div></div><div className="h-80"><ResponsiveContainer width="100%" height="100%"><RechartsLineChartdata={robotEfficiency}margin={{ top: 10, right: 30, left: 0, bottom: 0 }}><CartesianGrid strokeDasharray="3 3" stroke={theme === 'dark' ? '#374151' : '#e5e7eb'} /><XAxis dataKey="day" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><YAxis yAxisId="left" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><YAxis yAxisId="right" orientation="right" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} /><Legend /><Line yAxisId="left"type="monotone" dataKey="efficiency" name="工作效率 (%)" stroke="#3b82f6" strokeWidth={2}dot={{ r: 3 }}/><Line yAxisId="left"type="monotone" dataKey="errorRate" name="故障率 (%)" stroke="#ef4444" strokeWidth={2}dot={{ r: 3 }}/><Line yAxisId="right"type="monotone" dataKey="tasksCompleted" name="完成任务数" stroke="#10b981" strokeWidth={2}dot={{ r: 3 }}/></RechartsLineChart></ResponsiveContainer></div></motion.div>{/* 仓库周转率 */}<motion.divinitial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}transition={{ duration: 0.3, delay: 0.7 }}className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><div className="flex justify-between items-center mb-4"><h3 className="font-bold text-lg">库存周转率分析</h3><div className="flex space-x-2"><button className="px-3 py-1 text-xs rounded-full bg-blue-50 dark:bg-blue-900 text-blue-600 dark:text-blue-400">月度</button><button className="px-3 py-1 text-xs rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">季度</button><button className="px-3 py-1 text-xs rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">年度</button></div></div><div className="h-80"><ResponsiveContainer width="100%" height="100%"><BarChartdata={turnoverData}margin={{ top: 10, right: 30, left: 0, bottom: 0 }}><CartesianGrid strokeDasharray="3 3" stroke={theme === 'dark' ? '#374151' : '#e5e7eb'} /><XAxis dataKey="month" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><YAxis yAxisId="left" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><YAxis yAxisId="right" orientation="right" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} /><Legend /><Bar yAxisId="left" dataKey="turnover" name="销售额" fill="#8884d8" /><Bar yAxisId="right" dataKey="avgInventory" name="平均库存" fill="#82ca9d" /></BarChart></ResponsiveContainer></div></motion.div></div></motion.div>)}{/* 库存分析标签页 */}{activeTab === 'inventory' && (<motion.divinitial={{ opacity: 0 }}animate={{ opacity: 1 }}transition={{ duration: 0.5 }}className="space-y-6"><div className="grid grid-cols-1 lg:grid-cols-2 gap-6">{/* 入库/出库对比 */}<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><h3 className="font-bold text-lg mb-4">日入库/出库对比</h3><div className="h-80"><ResponsiveContainer width="100%" height="100%"><BarChartdata={inOutData}margin={{ top: 10, right: 30, left: 0, bottom: 0 }}><CartesianGrid strokeDasharray="3 3" stroke={theme === 'dark' ? '#374151' : '#e5e7eb'} /><XAxis dataKey="day" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><YAxis stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} /><Legend /><Bar dataKey="in" name="入库量" fill="#10b981" /><Bar dataKey="out" name="出库量" fill="#ef4444" /></BarChart></ResponsiveContainer></div></div>{/* 货物分类分布 */}<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><h3 className="font-bold text-lg mb-4">货物分类分布</h3><div className="h-80 flex justify-center"><ResponsiveContainer width="100%" height="100%"><RechartsPieChart><Piedata={categoryData}cx="50%"cy="50%"innerRadius={60}outerRadius={80}fill="#8884d8"paddingAngle={2}dataKey="value"label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}labelLine={false}>{categoryData.map((entry, index) => (<Cell key={`cell-${index}`} fill={entry.color} />))}</Pie><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} /></RechartsPieChart></ResponsiveContainer></div></div></div>{/* 库存周转率 */}<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><div className="flex justify-between items-center mb-4"><h3 className="font-bold text-lg">库存周转率趋势</h3><div className="flex space-x-2"><button className="px-3 py-1 text-xs rounded-full bg-blue-50 dark:bg-blue-900 text-blue-600 dark:text-blue-400">月度</button><button className="px-3 py-1 text-xs rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">季度</button><button className="px-3 py-1 text-xs rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">年度</button></div></div><div className="h-80"><ResponsiveContainer width="100%" height="100%"><RechartsLineChartdata={turnoverData.map(item => ({...item,turnoverRate: (item.turnover / item.avgInventory).toFixed(2)}))}margin={{ top: 10, right: 30, left: 0, bottom: 0 }}><CartesianGrid strokeDasharray="3 3" stroke={theme === 'dark' ? '#374151' : '#e5e7eb'} /><XAxis dataKey="month" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><YAxis stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} /><Legend /><Line type="monotone" dataKey="turnoverRate" name="库存周转率" stroke="#3b82f6" strokeWidth={2}dot={{ r: 3 }}/></RechartsLineChart></ResponsiveContainer></div></div></motion.div>)}{/* 机器人分析标签页 */}{activeTab === 'robot' && (<motion.divinitial={{ opacity: 0 }}animate={{ opacity: 1 }}transition={{ duration: 0.5 }}className="space-y-6"><div className="grid grid-cols-1 lg:grid-cols-2 gap-6">{/* 机器人效率分析 */}<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><div className="flex justify-between items-center mb-4"><h3 className="font-bold text-lg">机器人效率趋势</h3><div className="flex space-x-2"><button className="px-3 py-1 text-xs rounded-full bg-blue-50 dark:bg-blue-900 text-blue-600 dark:text-blue-400">周度</button><button className="px-3 py-1 text-xs rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">月度</button><button className="px-3 py-1 text-xs rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">季度</button></div></div><div className="h-80"><ResponsiveContainer width="100%" height="100%"><RechartsLineChartdata={robotEfficiency}margin={{ top: 10, right: 30, left: 0, bottom: 0 }}><CartesianGrid strokeDasharray="3 3" stroke={theme === 'dark' ? '#374151' : '#e5e7eb'} /><XAxis dataKey="day" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><YAxis stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} /><Legend /><Line type="monotone" dataKey="efficiency" name="工作效率 (%)" stroke="#3b82f6" strokeWidth={2}dot={{ r: 3 }}/><Line type="monotone" dataKey="errorRate" name="故障率 (%)" stroke="#ef4444" strokeWidth={2}dot={{ r: 3 }}/></RechartsLineChart></ResponsiveContainer></div></div>{/* 机器人任务完成情况 */}<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><div className="flex justify-between items-center mb-4"><h3 className="font-bold text-lg">每日完成任务数</h3><div className="flex space-x-2"><button className="px-3 py-1 text-xs rounded-full bg-blue-50 dark:bg-blue-900 text-blue-600 dark:text-blue-400">周度</button><button className="px-3 py-1 text-xs rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">月度</button><button className="px-3 py-1 text-xs rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">季度</button></div></div><div className="h-80"><ResponsiveContainer width="100%" height="100%"><BarChartdata={robotEfficiency}margin={{ top: 10, right: 30, left: 0, bottom: 0 }}><CartesianGrid strokeDasharray="3 3" stroke={theme === 'dark' ? '#374151' : '#e5e7eb'} /><XAxis dataKey="day" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><YAxis stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} /><Legend /><Bar dataKey="tasksCompleted" name="完成任务数" fill="#10b981" /></BarChart></ResponsiveContainer></div></div></div>{/* 机器人效率与任务量关系 */}<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><h3 className="font-bold text-lg mb-4">机器人效率与任务量关系</h3><div className="h-80"><ResponsiveContainer width="100%" height="100%"><RechartsLineChartdata={robotEfficiency}margin={{ top: 10, right: 30, left: 0, bottom: 0 }}><CartesianGrid strokeDasharray="3 3" stroke={theme === 'dark' ? '#374151' : '#e5e7eb'} /><XAxis dataKey="day" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><YAxis yAxisId="left" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><YAxis yAxisId="right" orientation="right" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} /><Legend /><Line yAxisId="left"type="monotone" dataKey="efficiency" name="工作效率 (%)" stroke="#3b82f6" strokeWidth={2}dot={{ r: 3 }}/><Line yAxisId="right"type="monotone" dataKey="tasksCompleted" name="完成任务数" stroke="#10b981" strokeWidth={2}dot={{ r: 3 }}/></RechartsLineChart></ResponsiveContainer></div></div></motion.div>)}{/* 仓库分析标签页 */}{activeTab === 'warehouse' && (<motion.divinitial={{ opacity: 0 }}animate={{ opacity: 1 }}transition={{ duration: 0.5 }}className="space-y-6"><div className="grid grid-cols-1 lg:grid-cols-2 gap-6">{/* 仓库利用率 */}<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><h3 className="font-bold text-lg mb-4">仓库区域利用率</h3><div className="h-80"><ResponsiveContainer width="100%" height="100%"><BarChartdata={warehouseUtilization}layout="vertical"margin={{ top: 10, right: 30, left: 20, bottom: 10 }}><CartesianGrid strokeDasharray="3 3" stroke={theme === 'dark' ? '#374151' : '#e5e7eb'} /><XAxis type="number" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><YAxis dataKey="name" type="category" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} formatter={(value) => [`${value}%`, '利用率']}/><Legend /><Bar dataKey="utilization" name="利用率">{warehouseUtilization.map((entry, index) => (<Cell key={`cell-${index}`} fill={entry.utilization > 80 ? '#ef4444' : entry.utilization > 60 ? '#f59e0b' : '#10b981'} />))}</Bar></BarChart></ResponsiveContainer></div></div>{/* 入库/出库对比 */}<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><h3 className="font-bold text-lg mb-4">日入库/出库对比</h3><div className="h-80"><ResponsiveContainer width="100%" height="100%"><BarChartdata={inOutData}margin={{ top: 10, right: 30, left: 0, bottom: 0 }}><CartesianGrid strokeDasharray="3 3" stroke={theme === 'dark' ? '#374151' : '#e5e7eb'} /><XAxis dataKey="day" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><YAxis stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} /><Legend /><Bar dataKey="in" name="入库量" fill="#10b981" /><Bar dataKey="out" name="出库量" fill="#ef4444" /></BarChart></ResponsiveContainer></div></div></div>{/* 库存趋势 */}<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-5"><div className="flex justify-between items-center mb-4"><h3 className="font-bold text-lg">库存趋势分析</h3><div className="flex space-x-2"><button className="px-3 py-1 text-xs rounded-full bg-blue-50 dark:bg-blue-900 text-blue-600 dark:text-blue-400">月度</button><button className="px-3 py-1 text-xs rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">季度</button><button className="px-3 py-1 text-xs rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">年度</button></div></div><div className="h-80"><ResponsiveContainer width="100%" height="100%"><AreaChartdata={inventoryTrend}margin={{ top: 10, right: 30, left: 0, bottom: 0 }}><defs><linearGradient id="colorInventoryTrend" x1="0" y1="0" x2="0" y2="1"><stop offset="5%" stopColor="#3b82f6" stopOpacity={0.8} /><stop offset="95%" stopColor="#3b82f6" stopOpacity={0} /></linearGradient></defs><CartesianGrid strokeDasharray="3 3" stroke={theme === 'dark' ? '#374151' : '#e5e7eb'} /><XAxis dataKey="month" stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><YAxis stroke={theme === 'dark' ? '#9ca3af' : '#6b7280'} /><Tooltip contentStyle={{ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',border: `1px solid ${theme === 'dark' ? '#374151' : '#e5e7eb'}`,color: theme === 'dark' ? '#ffffff' : '#000000'}} /><Legend /><Area type="monotone" dataKey="inventory" stroke="#3b82f6" fillOpacity={1} fill="url(#colorInventoryTrend)" strokeWidth={2}name="库存量"/></AreaChart></ResponsiveContainer></div></div></motion.div>)}</>)}</div>);
};export default AnalyticsPage;
App.tsx #主应用组件:
import { Routes, Route, Navigate } from "react-router-dom";
import Home from "@/pages/Home";
import GoodsManagement from "@/pages/GoodsManagement";
import RobotManagement from "@/pages/RobotManagement";
import InventoryManagement from "@/pages/InventoryManagement";
import AnalyticsPage from "@/pages/AnalyticsPage";
import UserManagement from "@/pages/UserManagement";
import SystemSettings from "@/pages/SystemSettings";
import LoginPage from "@/components/LoginPage";
import { useState } from "react";
import { motion } from "framer-motion";
import { AuthContext } from '@/contexts/authContext';
import { toast } from 'sonner';/*** 保护路由组件* 用于限制未认证用户访问受保护的页面* @param children 要渲染的子组件* @returns 如果用户已认证则渲染子组件,否则重定向到登录页*/
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {const { isAuthenticated } = React.useContext(AuthContext);// 检查用户是否已认证if (!isAuthenticated) {return <Navigate to="/login" replace />;}return children;
};export default function App() {// 默认设置为未认证状态,需要登录后才能访问const [isAuthenticated, setIsAuthenticated] = useState(false);/*** 用户登出函数* 清除认证状态并显示成功提示*/const logout = () => {setIsAuthenticated(false);toast.success('已成功登出');};return (// 提供认证上下文,使整个应用可以访问认证状态<AuthContext.Providervalue={{ isAuthenticated, setIsAuthenticated, logout }}>{/* 路由配置 */}<Routes>{/* 登录页路由 - 如果已认证则重定向到首页 */}<Route path="/login" element={isAuthenticated ? <Navigate to="/" replace /> : <LoginPage />} />{/* 保护的路由 - 需要认证才能访问 */}<Route path="/" element={<ProtectedRoute><Home /></ProtectedRoute>} /><Route path="/goods-management" element={<ProtectedRoute><GoodsManagement /></ProtectedRoute>} /><Route path="/robot-management" element={<ProtectedRoute><RobotManagement /></ProtectedRoute>} /><Route path="/inventory-management" element={<ProtectedRoute><InventoryManagement /></ProtectedRoute>} /><Route path="/analytics" element={<ProtectedRoute><AnalyticsPage /></ProtectedRoute>} /><Route path="/user-management" element={<ProtectedRoute><UserManagement /></ProtectedRoute>} /><Route path="/system-settings" element={<ProtectedRoute><SystemSettings /></ProtectedRoute>} />{/* 404页面 - 处理不存在的路由 */}<Route path="*" element={<motion.div initial={{ opacity: 0 }}animate={{ opacity: 1 }}className="flex flex-col items-center justify-center h-screen bg-gray-50 dark:bg-gray-900"><motion.divinitial={{ scale: 0.8, opacity: 0 }}animate={{ scale: 1, opacity: 1 }}transition={{ duration: 0.5, type: "spring" }}className="text-6xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent mb-4">404</motion.div><motion.p initial={{ y: 20, opacity: 0 }}animate={{ y: 0, opacity: 1 }}transition={{ duration: 0.5, delay: 0.2 }}className="text-xl text-gray-600 dark:text-gray-300 mb-8">页面不存在</motion.p><motion.button initial={{ y: 20, opacity: 0 }}animate={{ y: 0, opacity: 1 }}transition={{ duration: 0.5, delay: 0.4 }}whileHover={{ scale: 1.05 }}whileTap={{ scale: 0.95 }}onClick={() => {window.history.back();toast('返回上一页');}}className="px-6 py-3 bg-gradient-to-r from-blue-600 to-indigo-600 text-white rounded-lg shadow-md hover:shadow-lg transition-all duration-200">返回上一页</motion.button></motion.div>} /></Routes></AuthContext.Provider>);
}
main.tsx #应用入口:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { Toaster } from 'sonner';
import App from "./App.tsx";// 获取根DOM元素并渲染应用
createRoot(document.getElementById("root")!).render(<StrictMode>{/* BrowserRouter 用于提供路由功能 */}<BrowserRouter>{/* 应用主组件 */}<App />{/* Toaster 用于显示通知提示 */}<Toaster /></BrowserRouter></StrictMode>
);
index.css #全局样式:
/* Tailwind CSS Base Styles */
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";/* CSS Variables - Theme Colors */
:root {--primary: #3b82f6;--primary-light: #60a5fa;--primary-dark: #2563eb;--secondary: #8b5cf6;--secondary-light: #a78bfa;--success: #10b981;--warning: #f59e0b;--danger: #ef4444;--info: #3b82f6;--bg-primary: #ffffff;--bg-secondary: #f9fafb;--bg-tertiary: #f3f4f6;--text-primary: #1f2937;--text-secondary: #4b5563;--text-tertiary: #9ca3af;--border-light: #e5e7eb;--border-medium: #d1d5db;font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";line-height: 1.5;font-weight: 400;font-synthesis: none;text-rendering: optimizeLegibility;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;
}/* Dark Theme Variables */
.dark {--primary: #3b82f6;--primary-light: #60a5fa;--primary-dark: #2563eb;--secondary: #8b5cf6;--secondary-light: #a78bfa;--success: #10b981;--warning: #f59e0b;--danger: #ef4444;--info: #3b82f6;--bg-primary: #111827;--bg-secondary: #1f2937;--bg-tertiary: #374151;--text-primary: #f9fafb;--text-secondary: #d1d5db;--text-tertiary: #9ca3af;--border-light: #374151;--border-medium: #4b5563;
}/* Custom Scrollbar Styles */
::-webkit-scrollbar {width: 8px;height: 8px;
}::-webkit-scrollbar-track {background: var(--bg-tertiary);border-radius: 4px;
}::-webkit-scrollbar-thumb {background: var(--border-medium);border-radius: 4px;transition: background-color 0.2s;
}::-webkit-scrollbar-thumb:hover {background: var(--text-tertiary);
}/* Animation Keyframes */
@keyframes fadeIn {from { opacity: 0; }to { opacity: 1; }
}@keyframes slideIn {from { transform: translateY(10px); opacity: 0; }to { transform: translateY(0); opacity: 1; }
}@keyframes pulse {0%, 100% { opacity: 1; }50% { opacity: 0.5; }
}@keyframes float {0%, 100% { transform: translateY(0); }50% { transform: translateY(-5px); }
}/* Animation Classes */
.fade-in {animation: fadeIn 0.3s ease-in-out;
}.slide-in {animation: slideIn 0.3s ease-in-out;
}.pulse {animation: pulse 2s infinite;
}.float {animation: float 3s ease-in-out infinite;
}/* Global Button Styles */
button {transition: all 0.2s ease-in-out;
}button:hover {transform: translateY(-1px);
}button:active {transform: translateY(0);
}/* Input Focus Styles */
input:focus, select:focus, textarea:focus {transition: all 0.2s ease-in-out;box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}/* Card Styles */
.card {transition: all 0.3s ease-in-out;
}.card:hover {box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}.dark .card:hover {box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3), 0 10px 10px -5px rgba(0, 0, 0, 0.08);
}
package.json #项目配置:
{"name": "project_template_react","private": true,"version": "0.0.1","type": "module","scripts": {"dev:client": "vite --host --port 3000","dev": "pnpm dev:client","build:client": "vite build --outDir dist/static","build": "rm -rf dist && pnpm build:client && cp package.json dist && touch dist/build.flag"},"dependencies": {"clsx": "^2.1.1","framer-motion": "^12.9.2","lucide-react": "^0.447.0","react": "^18.3.1","react-dom": "^18.3.1","react-router-dom": "^7.3.0","recharts": "^2.15.1","sonner": "^2.0.2","tailwind-merge": "^3.0.2","zod": "^3.24.2"},"devDependencies": {"@types/react": "^18.3.12","@types/react-dom": "^18.3.1","@vitejs/plugin-react": "^4.3.4","autoprefixer": "^10.4.21","postcss": "^8.5.3","tailwindcss": "^3.4.17","typescript": "~5.7.2","vite": "^6.2.0","vite-tsconfig-paths": "^5.1.4"}
}
vite.config.ts #Vite配置:
/** WARNING: DON'T EDIT THIS FILE */
/** WARNING: DON'T EDIT THIS FILE */
/** WARNING: DON'T EDIT THIS FILE */import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";function getPlugins() {const plugins = [react(), tsconfigPaths()];return plugins;
}export default defineConfig({plugins: getPlugins(),
});
tailwind.config.js #Tailwind配置:
/** WARNING: DON'T EDIT THIS FILE */
/** WARNING: DON'T EDIT THIS FILE */
/** WARNING: DON'T EDIT THIS FILE *//** @type {import('tailwindcss').Config} */export default {darkMode: "class",content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],theme: {container: {center: true,},extend: {},},plugins: [],
};
tsconfig.json #TypeScript配置:
/** WARNING: DON'T EDIT THIS FILE */
/** WARNING: DON'T EDIT THIS FILE */
/** WARNING: DON'T EDIT THIS FILE */{"compilerOptions": {"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo","target": "ES2020","useDefineForClassFields": true,"lib": ["ES2020", "DOM", "DOM.Iterable"],"module": "ESNext","skipLibCheck": true,/* Bundler mode */"moduleResolution": "bundler","allowImportingTsExtensions": true,"isolatedModules": true,"moduleDetection": "force","noEmit": true,"jsx": "react-jsx",/* Linting */"strict": true,"noUnusedLocals": true,"noUnusedParameters": true,"noFallthroughCasesInSwitch": true,"noUncheckedSideEffectImports": true,/* Paths */"baseUrl": "./","paths": {"@/*": ["./src/*"]}},"include": ["src"]
}
http://www.dtcms.com/a/540237.html

相关文章:

  • 初识 Spring Boot
  • 人工智能助推城市规划新纪元:佛山年会深度解析大模型革新
  • c 网站开发 pdf洛阳已经开始群体感染了
  • 【Delphi】操纵EXE文件中版本信息(RT_VERSION)
  • 一周新闻热点事件seo 哪些媒体网站可以发新闻
  • Vue3的Pinia详解
  • 移动端性能监控探索:可观测 Android 采集探针架构与实现
  • OSS文件上传错误No buffer space available
  • 搜狐视频网站联盟怎么做企业网站优化的方式
  • c 网站做微信支付功能济源建设工程管理处网站
  • Visual Studio 编译带显卡加速的 OpenCV
  • 【EDA软件】【文件合并烧录操作方法】
  • Termux 部署 NextCloude 私人云盘,旧手机变云盘
  • 【优选算法】DC-Mergesort-Harmonies:分治-归并的算法之谐
  • WPF 控件速查 PDF 笔记(可直接落地版)(带图片)
  • 淘宝实时拍立淘按图搜索数据|商品详情|数据分析提取教程
  • WinCC的CS架构部署
  • 房地产店铺首页设计过程门户网站优化报价
  • 河南建设安全监督网站WordPress集成tipask
  • Taro 自定义tab栏和自定义导航栏
  • 辛格迪客户案例 | 迈威生物电子合约(eSignDMS)项目
  • 《微信小程序》第七章:TabBar设计
  • 国外做外链常用的网站网站 建设 网站设计公司
  • Flutter package 内部加载该包的 assets 时,需要使用 packages/ 前缀
  • 5 种简单方法将文件从 Mac 传输到 iPhone
  • ETH, Lwip,Tcp之间关系
  • 神经网络之向量相似性
  • 关于前端文件处理-实战篇
  • <script setup lang=“ts“>+uniapp实现轮播(swiper)效果
  • 网站建设与设计方案现在什么网页游戏最好玩最火