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 
相关推荐
友莘居士10 小时前
Dify中的Agent和发现和调用mcp工具两个节点调用的异同
agent·react·dify·functioncalling·mcp
cts61811 小时前
全文检索官网示例
python·全文检索·fastapi
aiguangyuan1 天前
前端开发性能优化概要
系统架构·vue·react·前端开发
半新半旧2 天前
FastAPI中间件
中间件·fastapi
爱吃羊的老虎3 天前
【后端】FastAPI的Pydantic 模型
数据库·后端·python·fastapi
Elastic 中国社区官方博客3 天前
使用 FastAPI 构建 Elasticsearch API
大数据·数据库·python·elasticsearch·搜索引擎·全文检索·fastapi
陈小桔3 天前
SQLALchemy
python·fastapi
alpszero4 天前
使用UV管理FastAPI项目
fastapi·uv
**梯度已爆炸**5 天前
Python Web框架详解:Flask、Streamlit、FastAPI
python·flask·fastapi·streamlit
叶 落5 天前
Component cannot be used as a JSX component
typescript·react