FastAPI+React19 ERP系统实战 第01期

在这里插入图片描述

一、基础环境

1.1 项目依赖

package.json

{"name": "erp-web","version": "1.0.0","description": "ERP系统前端 - React 19","main": "index.js","type": "module","scripts": {"dev": "vite","build": "vite build","preview": "vite preview"},"dependencies": {"lucide-react": "^0.400.0","react": "^19.0.0","react-dom": "^19.0.0","react-router-dom": "^6.22.0","recharts": "^3.0.2"},"devDependencies": {"@vitejs/plugin-react": "^4.2.1","vite": "^5.1.0"},"engines": {"node": ">=18.0.0","pnpm": ">=8.0.0"},"packageManager": "pnpm@8.15.0","keywords": ["erp","react","dashboard"],"author": "源滚滚AI编程","license": "MIT"
}

1.2 vite 配置

vite.config.js

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'export default defineConfig({plugins: [react()],resolve: {alias: {'@': path.resolve(__dirname, './src')}},server: {port: 3000,open: true},build: {outDir: 'dist',sourcemap: true}
}) 

1.3 根HTML

index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"/><link rel="icon" type="image/svg+xml" href="/vite.svg"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><title>ERP系统 - 企业资源管理</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html> 

1.4 全局样式

src/index.css

* {margin: 0;padding: 0;box-sizing: border-box;
}body {font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen','Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;background-color: #f8fafc;color: #1e293b;line-height: 1.6;
}#root {min-height: 100vh;
}/* 滚动条样式 */
::-webkit-scrollbar {width: 6px;height: 6px;
}::-webkit-scrollbar-track {background: #f1f5f9;
}::-webkit-scrollbar-thumb {background: #cbd5e1;border-radius: 3px;
}::-webkit-scrollbar-thumb:hover {background: #94a3b8;
}/* 按钮重置 */
button {border: none;background: none;cursor: pointer;font-family: inherit;
}/* 链接重置 */
a {text-decoration: none;color: inherit;
}/* 输入框重置 */
input, textarea, select {font-family: inherit;border: none;outline: none;
}/* 列表重置 */
ul, ol {list-style: none;
} 

1.5 程序入口

src/main.jsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'ReactDOM.createRoot(document.getElementById('root')).render(<React.StrictMode><App /></React.StrictMode>,
) 

1.6 根组件样式

src/App.css

.app {display: flex;min-height: 100vh;background-color: #f8fafc;
}.main-content {flex: 1;display: flex;flex-direction: column;overflow: hidden;
}/* 响应式设计 */
@media (max-width: 768px) {.app {flex-direction: column;}.main-content {margin-left: 0;}
} 

1.7 根组件

src/App.jsx

import React from 'react'
import './App.css'function App() {return (<div className="app"><h1>你好,React</h1></div>)
}export default App 

二、核心布局组件

2.1 图标组件

src/components/Icon.jsx

import React from 'react'
import * as LucideIcons from 'lucide-react'/*** 通用Icon组件* @param {string} name - 图标名称(与lucide-react导出名一致)* @param {number} size - 图标大小* @param {string} color - 图标颜色* @param {string} className - 额外class* @param {object} rest - 其他props*/
const Icon = ({ name, size = 20, color = 'currentColor', className = '', ...rest }) => {const LucideIcon = LucideIcons[name]if (!LucideIcon) return nullreturn <LucideIcon size={size} color={color} className={className} {...rest} />
}export default Icon 

2.2 头部组件样式

src/components/Header/Header.css

.header {background: white;padding: 20px 30px;display: flex;align-items: center;justify-content: space-between;border-bottom: 1px solid #e2e8f0;box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}.header-left {display: flex;flex-direction: column;
}.page-title {font-size: 24px;font-weight: 700;color: #1e293b;margin: 0;
}.page-subtitle {font-size: 14px;color: #64748b;margin: 4px 0 0 0;
}.header-right {display: flex;align-items: center;gap: 20px;
}.search-box {position: relative;display: flex;align-items: center;
}.search-box svg {position: absolute;left: 12px;color: #94a3b8;
}.search-input {padding: 10px 10px 10px 40px;border: 1px solid #e2e8f0;border-radius: 8px;background: #f8fafc;width: 300px;font-size: 14px;transition: all 0.2s ease;
}.search-input:focus {outline: none;border-color: #667eea;background: white;box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}.header-actions {display: flex;align-items: center;gap: 12px;
}.action-btn {position: relative;padding: 8px;border-radius: 8px;color: #64748b;transition: all 0.2s ease;background: transparent;
}.action-btn:hover {background: #f1f5f9;color: #1e293b;
}.notification-badge {position: absolute;top: 4px;right: 4px;background: #ef4444;color: white;font-size: 10px;font-weight: 600;padding: 2px 6px;border-radius: 10px;min-width: 16px;text-align: center;
}.user-menu {margin-left: 8px;
}.user-btn {display: flex;align-items: center;gap: 8px;padding: 8px 12px;border-radius: 8px;background: #f1f5f9;color: #1e293b;font-weight: 500;transition: all 0.2s ease;
}.user-btn:hover {background: #e2e8f0;
}.user-avatar {width: 32px;height: 32px;background: #667eea;border-radius: 50%;display: flex;align-items: center;justify-content: center;color: white;
}/* 响应式设计 */
@media (max-width: 768px) {.header {padding: 15px 20px;flex-direction: column;gap: 15px;align-items: stretch;}.header-right {justify-content: space-between;}.search-input {width: 200px;}.page-title {font-size: 20px;}
} 

2.3 头部组件

src/components/Header/index.jsx

import React from 'react'
import Icon from '@/components/Icon'
import './Header.css'const Header = () => {return (<header className="header"><div className="header-left"><h1 className="page-title">仪表板</h1><p className="page-subtitle">欢迎回来,管理员</p></div><div className="header-right"><div className="search-box"><Icon name="Search" size={20} color="#94a3b8" /><input type="text" placeholder="搜索..." className="search-input"/></div><div className="header-actions"><button className="action-btn notification-btn"><Icon name="Bell" size={20} /><span className="notification-badge">3</span></button><button className="action-btn"><Icon name="Settings" size={20} /></button><div className="user-menu"><button className="user-btn"><div className="user-avatar"><Icon name="User" size={16} /></div><span>管理员</span></button></div></div></div></header>)
}export default Header 

2.4 侧边栏组件样式

src/components/Sidebar/Sidebar.css

.sidebar {width: 280px;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);color: white;display: flex;flex-direction: column;transition: all 0.3s ease;position: relative;z-index: 1000;
}.sidebar.collapsed {width: 80px;
}.sidebar-header {padding: 20px;display: flex;align-items: center;justify-content: space-between;border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}.logo {font-size: 20px;font-weight: 700;color: white;
}.collapse-btn {color: white;background: rgba(255, 255, 255, 0.1);border-radius: 8px;padding: 8px;transition: all 0.2s ease;
}.collapse-btn:hover {background: rgba(255, 255, 255, 0.2);
}.sidebar-nav {flex: 1;padding: 20px 0;
}.sidebar-nav ul {list-style: none;
}.nav-item {display: flex;align-items: center;padding: 12px 20px;color: rgba(255, 255, 255, 0.8);transition: all 0.2s ease;text-decoration: none;gap: 12px;
}.nav-item:hover {background: rgba(255, 255, 255, 0.1);color: white;
}.nav-item.active {background: rgba(255, 255, 255, 0.2);color: white;border-right: 3px solid white;
}.nav-item span {font-weight: 500;
}.sidebar-footer {padding: 20px;border-top: 1px solid rgba(255, 255, 255, 0.1);
}.user-info {display: flex;align-items: center;gap: 12px;
}.avatar {width: 40px;height: 40px;background: rgba(255, 255, 255, 0.2);border-radius: 50%;display: flex;align-items: center;justify-content: center;color: white;
}.user-details {display: flex;flex-direction: column;
}.user-name {font-weight: 600;font-size: 14px;
}.user-role {font-size: 12px;opacity: 0.8;
}/* 移动端样式 */
.mobile-menu-btn {display: none;position: fixed;top: 20px;left: 20px;z-index: 1001;background: #667eea;color: white;border-radius: 8px;padding: 8px;
}.mobile-overlay {position: fixed;top: 0;left: 0;right: 0;bottom: 0;background: rgba(0, 0, 0, 0.5);z-index: 999;
}@media (max-width: 768px) {.mobile-menu-btn {display: block;}.sidebar {position: fixed;left: -280px;top: 0;height: 100vh;transform: translateX(0);transition: transform 0.3s ease;}.sidebar.mobile-open {transform: translateX(280px);}.sidebar.collapsed {width: 280px;}
} 

2.5 侧边栏组件

src/components/Sidebar/index.jsx

import React, { useState } from 'react'
import Icon from '@/components/Icon'
import './Sidebar.css'const Sidebar = () => {const [isCollapsed, setIsCollapsed] = useState(false)const [isMobileOpen, setIsMobileOpen] = useState(false)const menuItems = [{ icon: 'Home', label: '首页', active: true },{ icon: 'Users', label: '客户管理' },{ icon: 'Package', label: '库存管理' },{ icon: 'ShoppingCart', label: '订单管理' },{ icon: 'BarChart3', label: '财务报表' },{ icon: 'Settings', label: '系统设置' }]const toggleSidebar = () => {setIsCollapsed(!isCollapsed)}const toggleMobileMenu = () => {setIsMobileOpen(!isMobileOpen)}return (<>{/* 移动端菜单按钮 */}<button className="mobile-menu-btn" onClick={toggleMobileMenu}><Icon name={isMobileOpen ? 'X' : 'Menu'} size={24} /></button>{/* 侧边栏 */}<div className={`sidebar ${isCollapsed ? 'collapsed' : ''} ${isMobileOpen ? 'mobile-open' : ''}`}><div className="sidebar-header"><div className="logo">{!isCollapsed && <span>ERP系统</span>}</div><button className="collapse-btn" onClick={toggleSidebar}><Icon name={isCollapsed ? 'Menu' : 'X'} size={20} /></button></div><nav className="sidebar-nav"><ul>{menuItems.map((item, index) => (<li key={index}><a href="#" className={`nav-item ${item.active ? 'active' : ''}`}onClick={(e) => {e.preventDefault()}}><Icon name={item.icon} size={20} color={item.active ? '#fff' : '#e0e7ef'} />{!isCollapsed && <span>{item.label}</span>}</a></li>))}</ul></nav><div className="sidebar-footer"><div className="user-info"><div className="avatar"><Icon name="Users" size={20} /></div>{!isCollapsed && (<div className="user-details"><span className="user-name">管理员</span><span className="user-role">系统管理员</span></div>)}</div></div></div>{/* 移动端遮罩 */}{isMobileOpen && (<div className="mobile-overlay" onClick={toggleMobileMenu}></div>)}</>)
}export default Sidebar 

2.6 面板组件

src/components/Dashboard/index.jsx

import React from 'react'const Dashboard = () => {return (<div className="dashboard"><h1>面板组件</h1></div>)
}export default Dashboard 

2.7 根组件样式

src/App.css

.app {display: flex;min-height: 100vh;background-color: #f8fafc;
}.main-content {flex: 1;display: flex;flex-direction: column;overflow: hidden;
}/* 响应式设计 */
@media (max-width: 768px) {.app {flex-direction: column;}.main-content {margin-left: 0;}
} 

2.8 根组件优化

src/App.jsx

import React from 'react'
import Sidebar from '@/components/Sidebar'
import Header from '@/components/Header'
import Dashboard from '@/components/Dashboard'
import './App.css'function App() {return (<div className="app"><Sidebar /><div className="main-content"><Header /><Dashboard /></div></div>)
}export default App 

三、首页内容组件

3.1 统计卡片样式

src/components/StatCard/StatCard.css

.stat-card {background: white;border-radius: 12px;padding: 24px;box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);transition: all 0.2s ease;border: 1px solid #e2e8f0;
}.stat-card:hover {transform: translateY(-2px);box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}.stat-header {display: flex;justify-content: space-between;align-items: center;margin-bottom: 16px;
}.stat-icon {width: 48px;height: 48px;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);border-radius: 12px;display: flex;align-items: center;justify-content: center;font-size: 20px;
}.stat-change {display: flex;align-items: center;gap: 4px;font-size: 12px;font-weight: 600;padding: 4px 8px;border-radius: 6px;
}.stat-change.positive {background: #dcfce7;color: #166534;
}.stat-change.negative {background: #fee2e2;color: #dc2626;
}.stat-content {display: flex;flex-direction: column;gap: 8px;
}.stat-title {font-size: 14px;font-weight: 500;color: #64748b;margin: 0;
}.stat-value {font-size: 28px;font-weight: 700;color: #1e293b;margin: 0;
}/* 响应式设计 */
@media (max-width: 768px) {.stat-card {padding: 20px;}.stat-value {font-size: 24px;}.stat-icon {width: 40px;height: 40px;font-size: 18px;}
} 

3.2 统计卡片

src/components/StatCard/index.jsx

import React from 'react'
import Icon from '@/components/Icon'
import './StatCard.css'const StatCard = ({ title, value, change, changeType, icon }) => {return (<div className="stat-card"><div className="stat-header"><div className="stat-icon"><Icon name={icon} size={24} color="#fff" /></div><div className={`stat-change ${changeType}`}><Icon name={changeType === 'positive' ? 'TrendingUp' : 'TrendingDown'} size={16} /><span>{change}</span></div></div><div className="stat-content"><h3 className="stat-title">{title}</h3><p className="stat-value">{value}</p></div></div>)
}export default StatCard 

3.3 销量折线图组件

src/components/Charts/SalesLineChart.jsx

import React from 'react'
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'const data = [{ date: '06-01', sales: 3200 },{ date: '06-05', sales: 4200 },{ date: '06-10', sales: 3900 },{ date: '06-15', sales: 5200 },{ date: '06-20', sales: 4800 },{ date: '06-25', sales: 6100 },{ date: '06-30', sales: 7000 },
]const SalesLineChart = () => (<ResponsiveContainer width="100%" height={260}><LineChart data={data} margin={{ top: 20, right: 30, left: 0, bottom: 0 }}><CartesianGrid strokeDasharray="3 3" /><XAxis dataKey="date" /><YAxis /><Tooltip /><Line type="monotone" dataKey="sales" stroke="#667eea" strokeWidth={3} dot={{ r: 5 }} activeDot={{ r: 7 }} /></LineChart></ResponsiveContainer>
)export default SalesLineChart 

3.4 产品饼图组件

src/components/Charts/ProductPieChart.jsx

import React from 'react'
import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer, Legend } from 'recharts'const data = [{ name: '电子产品', value: 400 },{ name: '服装', value: 300 },{ name: '食品', value: 300 },{ name: '家居', value: 200 },
]const COLORS = ['#667eea', '#764ba2', '#f093fb', '#f5576c']const ProductPieChart = () => (<ResponsiveContainer width="100%" height={260}><PieChart><Piedata={data}cx="50%"cy="50%"labelLine={false}label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}outerRadius={90}fill="#8884d8"dataKey="value">{data.map((entry, index) => (<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />))}</Pie><Tooltip /><Legend /></PieChart></ResponsiveContainer>
)export default ProductPieChart 

3.5 图表卡片组件样式

src/components/ChartCard/ChartCard.css

.chart-card {background: white;border-radius: 12px;padding: 24px;box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);border: 1px solid #e2e8f0;
}.chart-header {display: flex;justify-content: space-between;align-items: flex-start;margin-bottom: 24px;
}.chart-title {font-size: 18px;font-weight: 600;color: #1e293b;margin: 0 0 4px 0;
}.chart-subtitle {font-size: 14px;color: #64748b;margin: 0;
}.chart-actions {display: flex;gap: 8px;
}.chart-action-btn {padding: 6px 12px;background: #f1f5f9;color: #374151;border-radius: 6px;font-size: 12px;font-weight: 500;transition: all 0.2s ease;
}.chart-action-btn:hover {background: #e2e8f0;
}.chart-content {min-height: 300px;display: flex;align-items: center;justify-content: center;
}.chart-placeholder {display: flex;flex-direction: column;align-items: center;gap: 16px;color: #94a3b8;text-align: center;
}.chart-placeholder p {font-size: 14px;margin: 0;
}/* 柱状图样式 */
.chart-bars {display: flex;align-items: end;gap: 4px;height: 200px;padding: 20px 0;
}.chart-bar {width: 20px;background: linear-gradient(to top, #667eea, #764ba2);border-radius: 2px 2px 0 0;transition: all 0.3s ease;
}.chart-bar:hover {background: linear-gradient(to top, #5a67d8, #6b46c1);transform: scaleY(1.05);
}/* 饼图样式 */
.pie-segments {position: relative;width: 120px;height: 120px;border-radius: 50%;background: conic-gradient(var(--color) 0deg var(--percentage),#e2e8f0 var(--percentage) 360deg);
}.pie-segment {position: absolute;width: 100%;height: 100%;border-radius: 50%;background: conic-gradient(var(--color) 0deg var(--percentage),transparent var(--percentage) 360deg);
}/* 响应式设计 */
@media (max-width: 768px) {.chart-card {padding: 20px;}.chart-header {flex-direction: column;gap: 16px;align-items: stretch;}.chart-actions {justify-content: flex-end;}.chart-content {min-height: 250px;}.chart-bars {height: 150px;}.chart-bar {width: 16px;}
} 

3.6 图表卡片组件

src/components/ChartCard/index.jsx

import React from 'react'
import SalesLineChart from '@/components/Charts/SalesLineChart'
import ProductPieChart from '@/components/Charts/ProductPieChart'
import './ChartCard.css'const ChartCard = ({ title, subtitle, type }) => {let chartContent = nullif (type === 'line') {chartContent = <SalesLineChart />} else if (type === 'pie') {chartContent = <ProductPieChart />}return (<div className="chart-card"><div className="chart-header"><div><h3 className="chart-title">{title}</h3><p className="chart-subtitle">{subtitle}</p></div><div className="chart-actions"><button className="chart-action-btn">导出</button><button className="chart-action-btn">刷新</button></div></div><div className="chart-content">{chartContent}</div></div>)
}export default ChartCard 

3.7 最近订单组件样式

src/components/RecentOrders/RecentOrders.css

.recent-orders {background: white;border-radius: 12px;padding: 24px;box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);border: 1px solid #e2e8f0;height: 100%;
}.orders-header {display: flex;justify-content: space-between;align-items: center;margin-bottom: 20px;
}.orders-header h3 {font-size: 18px;font-weight: 600;color: #1e293b;margin: 0;
}.view-all-btn {padding: 6px 12px;background: #f1f5f9;color: #374151;border-radius: 6px;font-size: 12px;font-weight: 500;transition: all 0.2s ease;
}.view-all-btn:hover {background: #e2e8f0;
}.orders-list {display: flex;flex-direction: column;gap: 12px;
}.order-item {display: flex;align-items: center;gap: 16px;padding: 16px;background: #f8fafc;border-radius: 8px;transition: all 0.2s ease;border: 1px solid #e2e8f0;
}.order-item:hover {background: #f1f5f9;transform: translateX(4px);
}.order-info {flex: 1;display: flex;flex-direction: column;gap: 4px;
}.order-id {font-size: 14px;font-weight: 600;color: #1e293b;
}.order-customer {font-size: 13px;color: #64748b;
}.order-amount {font-size: 14px;font-weight: 600;color: #059669;
}.order-status {display: flex;align-items: center;gap: 6px;min-width: 80px;
}.status-icon {color: #94a3b8;
}.status-icon.completed {color: #059669;
}.status-icon.processing {color: #d97706;
}.status-icon.pending {color: #dc2626;
}.status-text {font-size: 12px;font-weight: 500;color: #64748b;
}.order-time {display: flex;flex-direction: column;align-items: flex-end;gap: 2px;min-width: 80px;
}.order-date {font-size: 12px;color: #64748b;
}.order-time-text {font-size: 11px;color: #94a3b8;
}.order-actions {padding: 4px;color: #94a3b8;border-radius: 4px;transition: all 0.2s ease;
}.order-actions:hover {background: #e2e8f0;color: #374151;
}/* 响应式设计 */
@media (max-width: 768px) {.recent-orders {padding: 20px;}.order-item {flex-direction: column;align-items: flex-start;gap: 12px;}.order-status {align-self: flex-start;}.order-time {align-self: flex-start;align-items: flex-start;}.order-actions {align-self: flex-end;margin-top: -40px;}
} 

3.8 最近订单组件

src/components/RecentOrders/index.jsx

import React from 'react'
import { Clock, CheckCircle, AlertCircle, MoreVertical } from 'lucide-react'
import './RecentOrders.css'const RecentOrders = () => {const orders = [{id: '#ORD-001',customer: '张三',amount: '¥2,450',status: 'completed',date: '2024-01-15',time: '14:30'},{id: '#ORD-002',customer: '李四',amount: '¥1,890',status: 'pending',date: '2024-01-15',time: '13:45'},{id: '#ORD-003',customer: '王五',amount: '¥3,200',status: 'processing',date: '2024-01-15',time: '12:20'},{id: '#ORD-004',customer: '赵六',amount: '¥980',status: 'completed',date: '2024-01-15',time: '11:15'},{id: '#ORD-005',customer: '钱七',amount: '¥1,650',status: 'pending',date: '2024-01-15',time: '10:30'}]const getStatusIcon = (status) => {switch (status) {case 'completed':return <CheckCircle size={16} className="status-icon completed" />case 'processing':return <Clock size={16} className="status-icon processing" />case 'pending':return <AlertCircle size={16} className="status-icon pending" />default:return <Clock size={16} className="status-icon" />}}const getStatusText = (status) => {switch (status) {case 'completed':return '已完成'case 'processing':return '处理中'case 'pending':return '待处理'default:return '未知'}}return (<div className="recent-orders"><div className="orders-header"><h3>最近订单</h3><button className="view-all-btn">查看全部</button></div><div className="orders-list">{orders.map((order, index) => (<div key={index} className="order-item"><div className="order-info"><div className="order-id">{order.id}</div><div className="order-customer">{order.customer}</div><div className="order-amount">{order.amount}</div></div><div className="order-status">{getStatusIcon(order.status)}<span className="status-text">{getStatusText(order.status)}</span></div><div className="order-time"><div className="order-date">{order.date}</div><div className="order-time-text">{order.time}</div></div><button className="order-actions"><MoreVertical size={16} /></button></div>))}</div></div>)
}export default RecentOrders 

3.9 首页组件样式

src/components/Dashboard/Dashboard.css

.dashboard {flex: 1;overflow-y: auto;background: #f8fafc;
}.dashboard-content {padding: 30px;max-width: 1400px;margin: 0 auto;
}/* 统计卡片网格 */
.stats-grid {display: grid;grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));gap: 20px;margin-bottom: 30px;
}/* 主要内容区域 */
.dashboard-main {display: grid;grid-template-columns: 2fr 1fr;gap: 30px;margin-bottom: 30px;
}.charts-section {display: flex;flex-direction: column;gap: 20px;
}.orders-section {min-height: 400px;
}/* 快速操作区域 */
.quick-actions {background: white;border-radius: 12px;padding: 24px;box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}.quick-actions h3 {font-size: 18px;font-weight: 600;color: #1e293b;margin-bottom: 20px;
}.actions-grid {display: grid;grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));gap: 16px;
}.action-card {display: flex;flex-direction: column;align-items: center;gap: 12px;padding: 20px;background: #f8fafc;border-radius: 8px;transition: all 0.2s ease;border: 1px solid #e2e8f0;
}.action-card:hover {background: #f1f5f9;transform: translateY(-2px);box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}.action-icon {font-size: 24px;width: 48px;height: 48px;display: flex;align-items: center;justify-content: center;background: white;border-radius: 50%;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}.action-text {font-weight: 500;color: #374151;text-align: center;
}/* 响应式设计 */
@media (max-width: 1024px) {.dashboard-main {grid-template-columns: 1fr;}.charts-section {order: 2;}.orders-section {order: 1;}
}@media (max-width: 768px) {.dashboard-content {padding: 20px;}.stats-grid {grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));gap: 15px;}.actions-grid {grid-template-columns: repeat(2, 1fr);}
}@media (max-width: 480px) {.stats-grid {grid-template-columns: 1fr;}.actions-grid {grid-template-columns: 1fr;}
} 

3.10 首页组件

src/components/Dashboard/index.jsx

import React from 'react'
import StatCard from '@/components/StatCard'
import ChartCard from '@/components/ChartCard'
import RecentOrders from '@/components/RecentOrders'
import './Dashboard.css'const Dashboard = () => {const stats = [{title: '总销售额',value: '¥128,450',change: '+12.5%',changeType: 'positive',icon: 'CreditCard'},{title: '订单数量',value: '1,234',change: '+8.2%',changeType: 'positive',icon: 'Package'},{title: '客户数量',value: '856',change: '+15.3%',changeType: 'positive',icon: 'Users'},{title: '库存预警',value: '12',change: '-3.1%',changeType: 'negative',icon: 'AlertTriangle'}]return (<div className="dashboard"><div className="dashboard-content">{/* 统计卡片 */}<div className="stats-grid">{stats.map((stat, index) => (<StatCard key={index} {...stat} />))}</div>{/* 图表和订单区域 */}<div className="dashboard-main"><div className="charts-section"><ChartCard title="销售趋势"subtitle="最近30天销售数据"type="line"/><ChartCard title="产品分类"subtitle="各产品类别销售占比"type="pie"/></div><div className="orders-section"><RecentOrders /></div></div>{/* 快速操作 */}<div className="quick-actions"><h3>快速操作</h3><div className="actions-grid"><button className="action-card"><span className="action-icon"></span><span className="action-text">新建订单</span></button><button className="action-card"><span className="action-icon">👤</span><span className="action-text">添加客户</span></button><button className="action-card"><span className="action-icon">📊</span><span className="action-text">生成报表</span></button><button className="action-card"><span className="action-icon">⚙️</span><span className="action-text">系统设置</span></button></div></div></div></div>)
}export default Dashboard 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/90118.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/90118.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【机器学习笔记 Ⅱ】1 神经网络

神经网络是一种受生物神经元启发设计的机器学习模型&#xff0c;能够通过多层非线性变换学习复杂的输入-输出关系。它是深度学习的基础&#xff0c;广泛应用于图像识别、自然语言处理、游戏AI等领域。1. 核心思想 生物类比&#xff1a;模仿人脑神经元的工作方式&#xff0c;通过…

谢飞机的Java高级开发面试:从Spring Boot到分布式架构的蜕变之旅

面试现场&#xff1a;谢飞机的求职奇遇记 "请坐&#xff0c;谢先生。我看你简历上写了精通Lombok&#xff1f;"面试官推了推金丝眼镜。 谢飞机一愣&#xff1a;"啊...这个..."突然掏出手机&#xff0c;"您看我GitHub开源项目里用了SneakyThrows&…

一站式整合:解锁高效后端管理利器——Motia

在当今的科技世界中&#xff0c;企业对于后端系统的要求越来越高。无论是处理复杂的 API 请求、管理后台任务&#xff0c;还是集成 AI 代理&#xff0c;这些都需要一个强大的框架来支撑。而今天&#xff0c;我们要介绍的 Motia 正是这样一个现代化、统一的后端框架&#xff0c;…

如何将信息从 iPhone 同步到Mac(完整步骤和示意图)

如果您是 Apple 用户&#xff0c;您一定知道在所有设备上保持同步是多么便捷。Apple 生态系统的一大亮点就是能够在 iPhone 和 Mac 之间同步 iMessage 和短信。如果您想了解如何将信息从 iPhone 同步到 Mac&#xff0c;千万不要错过本教程。快速浏览一下这些方法&#xff1a;第…

【C++字符串变换】2022-7-20

缘由C问题-字符串变换-编程语言-CSDN问答 void 字符串变换() {string s "", t ""; char f; int x 0, g 1, l 0;cin >> s; l s.size();while (x <l){f s[x];if (f s[x 1]){g, s.erase(x, 1);}else{if (g > 1){s.erase(x, 1);t s.sub…

Web攻防-XMLXXE无回显带外SSRF元数据DTD实体OOB盲注文件拓展

知识点&#xff1a; 1、WEB攻防-XML&XXE-注入原理&分类&修复 2、WEB攻防-XML&XXE-文件读取&SSRF&实体引用 3、WEB攻防-XML&XXE-无回显&升级拓展&挖掘思路 一、演示案例-WEB攻防-XML&XXE-注入原理&分类&安全影响 详细点 XML被…

Node中Unexpected end of form 错误

文章目录Unexpected end of form 错误为什么 app.use(upload.any()) 会导致 Unexpected end of form 错误&#xff1f;1. 主要问题分析app.use(upload.any()) 的问题错误场景2. 解决方案✅ 方案 1&#xff1a;仅在需要文件上传的路由使用 Multer&#xff08;推荐&#xff09;✅…

通过Curtain 解决方案保障BIM模型安全共享—建筑业的防泄密实战

某跨海大桥项目突发数据泄露事件&#xff1a;主桥钢结构的BIM模型被外泄&#xff0c;核心参数流入竞争对手手中&#xff0c;导致项目风险评估升级。调查发现&#xff0c;泄漏源头是一名施工方的项目经理。尽管BIM系统已经能够控制哪些人可以阅读、修改、甚至下载资料的权限&…

ULVAC爱发科RFS03D RF POWER SUPPLY INSTRUCTION MANUAL RF射频电源

ULVAC爱发科RFS03D RF POWER SUPPLY INSTRUCTION MANUAL RF射频电源

暑假算法日记第三天

目标​&#xff1a;刷完灵神专题训练算法题单 阶段目标&#x1f4cc;&#xff1a;【算法题单】滑动窗口与双指针 LeetCode题目: 3439. 重新安排会议得到最多空余时间 I2134. 最少交换次数来组合所有的 1 II1297. 子串的最大出现次数2653. 滑动子数组的美丽值1888. 使二进制字符…

了解业务分析技术梗概

业务分析技术 以下基于BABOK V3框架&#xff0c;结合业务分析师&#xff08;BA&#xff09;的实际工作场景&#xff0c;系统梳理50项业务分析技术、常用工具、学习路径及文档应用指南。内容综合BABOK官方标准及行业实践&#xff0c;旨在提升BA的工作效能。 一、BABOK V3 技术体…

小红的数字删除 - 牛客

小红的数字删除 题目不难&#xff0c;忽略了一个 corner case&#xff0c;导致我在某次面试没有 AK。 10003 对于这个 case&#xff0c;只考虑前导零 全部删除是不对的&#xff0c;剩下的 3 也不能删。 void solve(){string s;cin >> s;int res0;vector<int> a(…

Linux网络: socket初识

一些概念 简单了解一下TCP,UDP这两个协议&#xff0c;和一些概念 TCP与UDP 学校教过TCP是 传输层协议有连接可靠传输面向字节流 而UDP是 传输层协议无连接不可靠传输面向数据报 当时完全不知道这些什么意思 网络字节序 网络通信&#xff0c;要接收和发送数据。我们知道…

AI时代的弯道超车之第二十七章:AI技术的发展方向

在这个AI重塑世界的时代,你还在原地观望吗?是时候弯道超车,抢占先机了! 李尚龙倾力打造——《AI时代的弯道超车:用人工智能逆袭人生》专栏,带你系统掌握AI知识,从入门到实战,全方位提升认知与竞争力! 内容亮点: AI基础 + 核心技术讲解 职场赋能 + 创业路径揭秘 打破…

RabbitMQ用法的6种核心模式全面解析

文章目录**一、RabbitMQ核心架构解析**1. AMQP协议模型2. 消息流转原理**二、六大核心用法详解****1. 简单队列模式&#xff08;Hello World&#xff09;****2. 工作队列模式&#xff08;Work Queues&#xff09;****3. 发布/订阅模式&#xff08;Pub/Sub&#xff09;****4. 路…

深入协程调试:协程调试工具与实战

本文系统梳理主流协程调试工具&#xff0c;结合完整代码示例与实战技巧&#xff0c;助你高效解决异步编程难题一、协程调试的核心挑战 协程的非线性执行流是调试的最大挑战&#xff1a; 传统断点调试难以追踪协程切换堆栈信息不完整或丢失上下文并发竞争条件难以复现 #mermaid-…

Git 日常开发实战命令大全

&#x1f9f0; Git 日常开发实战命令大全 本文整理了 Git 在日常开发中高频使用的命令集合&#xff0c;覆盖从基础操作到进阶技巧的完整流程&#xff0c;方便留存查阅&#x1f440; &#xff0c;最后附上所有指令。其中内容包括&#xff1a; ✅ 本地仓库管理&#xff1a;添加文…

力扣 hot100 Day37

25. K 个一组翻转链表 给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。 你不能只是…

【力扣 中等 C】516. 最长回文子序列

目录 题目 解法一 题目 待添加 解法一 int max(int a, int b) {return a > b ? a : b; }int longestPalindromeSubseq(char* s) {const int len strlen(s);int dp[len];for (int i len - 1; i > 0; i--) {dp[i] 1;int leftDown;if (i 1 < len) {leftDown dp…

DAY 54 Inception网络及其思考

知识点回顾&#xff1a; 传统计算机视觉发展史&#xff1a;LeNet-->AlexNet-->VGGNet-->nceptionNet-->ResNet 之所以说传统&#xff0c;是因为现在主要是针对backbone-neck-head这样的范式做文章 inception模块和网络特征融合方法阶段性总结&#xff1a;逐元素相加…