
一、基础环境
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