FastAPI+React19 ERP系统实战 第01期

一、基础环境

1.1 项目依赖

package.json

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

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

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

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

js 复制代码
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

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

js 复制代码
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

js 复制代码
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 null
  return <LucideIcon size={size} color={color} className={className} {...rest} />
}

export default Icon 

2.2 头部组件样式

src/components/Header/Header.css

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

js 复制代码
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

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

js 复制代码
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

js 复制代码
import React from 'react'

const Dashboard = () => {
  return (
    <div className="dashboard">
      <h1>面板组件</h1>
    </div>
  )
}

export default Dashboard 

2.7 根组件样式

src/App.css

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

js 复制代码
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

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

js 复制代码
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

js 复制代码
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

js 复制代码
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>
      <Pie
        data={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

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

js 复制代码
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 = null
  if (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

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

js 复制代码
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

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

js 复制代码
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 
相关推荐
Python私教8 小时前
FastAPI+React19 ERP系统实战 第02期
fastapi
一笑code2 天前
vue/微信小程序/h5 实现react的boundary
微信小程序·vue·react
小宁爱Python3 天前
FastAPI+Sqlite+HTML的登录注册与文件上传系统:完整实现指南
sqlite·html·fastapi
fairymt3 天前
LoRA 问答微调与部署全流程:基于 LLaMA-Factory + DeepSeek + FastAPI 打造专属大模型
fastapi
chenkangck5012 天前
Uvicorn 入门详解
python·fastapi
ZHOU_WUYI14 天前
GitHub OAuth 认证示例
flask·github·react
SuperherRo14 天前
Web攻防-XSS跨站&浏览器UXSS&突变MXSS&Vue&React&Electron框架&JQuery库&写法和版本
vue.js·electron·jquery·react·xss·mxss·uxss
remandancy.h16 天前
FastAPI:(2)开启FastAPI
fastapi
飞鸟malred16 天前
nextjs入门
前端·react·nextjs