FastAPI+React19开发ERP系统实战第04期

一、效果预览

1.1 首页

在这里插入图片描述

1.2 首页暗黑模式

在这里插入图片描述

1.3 登录页

在这里插入图片描述

1.4 登录页暗黑模式

在这里插入图片描述

二、搭建React开发环境

2.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","zustand": "^5.0.6"},"devDependencies": {"@vitejs/plugin-react": "^4.2.1","sass": "^1.89.2","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"
}

安装依赖:

npm install -g pnpm
pnpm i

2.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}
}) 

2.3 项目根HTML文件

index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"/><link rel="icon" type="image/svg+xml" href="/favicon.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> 

2.4 项目主入口程序

src/main.jsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import { ThemeProvider } from './theme';
import './index.scss';ReactDOM.createRoot(document.getElementById('root')).render(<React.StrictMode><ThemeProvider><App /></ThemeProvider></React.StrictMode>,
); 

2.5 项目的全局样式

src/index.scss

/*** 全局样式*/@import './theme/styles/variables.scss';* {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: var(--color-background);color: var(--color-text);line-height: var(--line-height-normal);transition: background-color var(--transition-normal), color var(--transition-normal);
}#root {min-height: 100vh;background: var(--gradient-background);
}/* 滚动条样式 */
::-webkit-scrollbar {width: 6px;height: 6px;
}::-webkit-scrollbar-track {background: var(--color-background-secondary);
}::-webkit-scrollbar-thumb {background: var(--color-border-dark);border-radius: 3px;&:hover {background: var(--color-text-muted);}
}/* 按钮重置 */
button {border: none;background: none;cursor: pointer;font-family: inherit;color: inherit;transition: all var(--transition-fast);
}/* 链接重置 */
a {text-decoration: none;color: inherit;transition: color var(--transition-fast);&:hover {color: var(--color-primary);}
}/* 输入框重置 */
input, textarea, select {font-family: inherit;border: none;outline: none;background: var(--color-surface);color: var(--color-text);transition: all var(--transition-fast);&::placeholder {color: var(--color-text-muted);}
}/* 列表重置 */
ul, ol {list-style: none;
}/* 主题加载状态 */
.theme-loading {display: flex;flex-direction: column;align-items: center;justify-content: center;min-height: 100vh;background: var(--gradient-background);color: var(--color-text);font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;.theme-loading-spinner {width: 40px;height: 40px;border: 3px solid var(--color-border);border-top: 3px solid var(--color-primary);border-radius: 50%;animation: spin 1s linear infinite;margin-bottom: var(--spacing-md);}p {font-size: var(--font-size-md);font-weight: var(--font-weight-medium);margin: 0;}
}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }
}/* 通用工具类 */
.text-primary { color: var(--color-primary); }
.text-secondary { color: var(--color-secondary); }
.text-success { color: var(--color-success); }
.text-warning { color: var(--color-warning); }
.text-error { color: var(--color-error); }
.text-info { color: var(--color-info); }
.text-muted { color: var(--color-text-muted); }.bg-primary { background-color: var(--color-primary); }
.bg-secondary { background-color: var(--color-secondary); }
.bg-surface { background-color: var(--color-surface); }
.bg-success { background-color: var(--color-success); }
.bg-warning { background-color: var(--color-warning); }
.bg-error { background-color: var(--color-error); }
.bg-info { background-color: var(--color-info); }.shadow-small { box-shadow: var(--shadow-small); }
.shadow-medium { box-shadow: var(--shadow-medium); }
.shadow-large { box-shadow: var(--shadow-large); }
.shadow-glow { box-shadow: var(--shadow-glow); }.gradient-primary { background: var(--gradient-primary); }
.gradient-secondary { background: var(--gradient-secondary); }
.gradient-card { background: var(--gradient-card); }.border-radius-small { border-radius: var(--border-radius-small); }
.border-radius-medium { border-radius: var(--border-radius-medium); }
.border-radius-large { border-radius: var(--border-radius-large); }/* 响应式工具类 */
@media (max-width: 768px) {.hidden-mobile { display: none !important; }
}@media (min-width: 769px) {.hidden-desktop { display: none !important; }
} 

2.7 项目根组件

App.jsx

/*** 应用主入口组件*/import React from 'react';
import { LoginPage } from '@/pages/login';
import { MainLayout } from '@/pages/layout';
import { HomePage } from '@/pages/home';
import ProtectedRoute from '@/components/ProtectedRoute';
import './App.scss';const App = () => {return (<ProtectedRoute fallback={<LoginPage />}><MainLayout><HomePage /></MainLayout></ProtectedRoute>);
};export default App; 

2.8 项目根组件样式

App.scss

/*** 应用主组件样式*/.app {display: flex;min-height: 100vh;background-color: var(--color-background);
}.main-content {flex: 1;display: flex;flex-direction: column;overflow: hidden;
}.loading-container {display: flex;flex-direction: column;align-items: center;justify-content: center;min-height: 100vh;background: var(--gradient-background);color: var(--color-text);font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;.loading-spinner {width: 40px;height: 40px;border: 3px solid var(--color-border);border-top: 3px solid var(--color-primary);border-radius: 50%;animation: spin 1s linear infinite;margin-bottom: var(--spacing-md);}p {font-size: var(--font-size-md);font-weight: var(--font-weight-medium);margin: 0;}
}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }
}/* 响应式设计 */
@media (max-width: 768px) {.app {flex-direction: column;}.main-content {margin-left: 0;}
} 

三、项目的主题切换

3.1 主题模块入口文件

src/theme/index.js

// 主题模块入口文件
export { default as ThemeProvider } from './components/ThemeProvider';
export { default as ThemeToggle } from './components/ThemeToggle';
export { useThemeStore } from './stores/themeStore';
export * from './types/themeTypes';
export * from './constants/themeConstants'; 

3.2 主题相关的类型定义

src/theme/types/themeTypes.js

/*** 主题相关的类型定义*/// 主题类型
export const ThemeType = {TECH_BLUE: 'tech-blue',TECH_BLACK: 'tech-black'
};// 主题配置类型
export const ThemeConfig = {name: '',displayName: '',colors: {primary: '',secondary: '',background: '',surface: '',text: '',textSecondary: '',border: '',accent: '',success: '',warning: '',error: '',info: ''},shadows: {small: '',medium: '',large: ''},gradients: {primary: '',secondary: '',background: ''}
};// 主题状态类型
export const ThemeState = {currentTheme: ThemeType.TECH_BLUE,isDark: false,isLoading: false
}; 

3.3 主题变量定义

src/theme/styles/variables.scss

/*** 主题变量定义*/// CSS 自定义属性 (CSS Variables)
:root {// 主色调 - 使用更加现代的蓝紫色系,与暗黑模式保持一致--color-primary: #6366f1;          // 现代紫蓝色--color-primary-light: #8b5cf6;    // 亮紫色--color-primary-dark: #4f46e5;     // 深紫蓝色--color-secondary: #06b6d4;        // 青色--color-secondary-light: #22d3ee;  // 亮青色--color-secondary-dark: #0891b2;   // 深青色// 背景色系 - 使用更加柔和的现代背景色--color-background: #fafafc;       // 极浅的蓝灰色背景--color-background-secondary: #f4f6f8; // 次级背景--color-surface: #ffffff;          // 纯白表面--color-surface-secondary: #f8fafc; // 次级表面色// 文字色系 - 更加现代的文字色彩层次--color-text: #1e293b;            // 主文字色 - 深蓝灰--color-text-secondary: #475569;   // 次级文字色 - 中蓝灰--color-text-muted: #64748b;       // 弱化文字色 - 浅蓝灰--color-text-inverse: #ffffff;     // 反色文字// 边框色系 - 更加精致的边框处理--color-border: rgba(99, 102, 241, 0.08);     // 主边框色 - 极其透明的紫蓝色--color-border-light: rgba(99, 102, 241, 0.05); // 亮边框色 - 更加透明--color-border-dark: rgba(99, 102, 241, 0.12);  // 深边框色 - 稍微明显// 强调色 - 使用更加和谐的强调色--color-accent: #f59e0b;           // 金黄色强调--color-accent-light: #fbbf24;     // 亮金黄色--color-accent-dark: #d97706;      // 深金黄色// 状态色系 - 现代化的状态色--color-success: #10b981;          // 成功色--color-success-light: #d1fae5;    // 成功色浅色背景--color-success-dark: #059669;     // 成功色深色--color-warning: #f59e0b;          // 警告色--color-warning-light: #fef3c7;    // 警告色浅色背景--color-warning-dark: #d97706;     // 警告色深色--color-error: #ef4444;            // 错误色--color-error-light: #fee2e2;      // 错误色浅色背景--color-error-dark: #dc2626;       // 错误色深色--color-info: #06b6d4;             // 信息色--color-info-light: #cffafe;       // 信息色浅色背景--color-info-dark: #0891b2;        // 信息色深色// 阴影系统 - 带有科技感的精致阴影--shadow-small: 0 1px 3px rgba(99, 102, 241, 0.08), 0 1px 2px rgba(99, 102, 241, 0.16);--shadow-medium: 0 4px 6px rgba(99, 102, 241, 0.05), 0 2px 4px rgba(99, 102, 241, 0.04),0 0 0 1px rgba(99, 102, 241, 0.03);--shadow-large: 0 10px 15px rgba(99, 102, 241, 0.08), 0 4px 6px rgba(99, 102, 241, 0.04),0 0 0 1px rgba(99, 102, 241, 0.03);--shadow-glow: 0 0 20px rgba(99, 102, 241, 0.15),0 0 40px rgba(99, 102, 241, 0.08);// 渐变系统 - 现代科技感的渐变--gradient-primary: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #06b6d4 100%);--gradient-secondary: linear-gradient(135deg, #f59e0b 0%, #ef4444 100%);--gradient-background: linear-gradient(135deg, #fafafc 0%, #f4f6f8 50%, #f8fafc 100%);--gradient-card: linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(248, 250, 252, 0.8) 50%, rgba(255, 255, 255, 0.95) 100%);--gradient-text: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #06b6d4 100%);// 特殊效果渐变--gradient-neon: linear-gradient(135deg, rgba(99, 102, 241, 0.15) 0%, rgba(139, 92, 246, 0.12) 50%, rgba(6, 182, 212, 0.15) 100%);--gradient-glass: linear-gradient(135deg, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0.4) 100%);// 过渡动画--transition-fast: 0.15s ease-in-out;--transition-normal: 0.3s ease-in-out;--transition-slow: 0.5s ease-in-out;// 边框圆角--border-radius-small: 8px;--border-radius-medium: 12px;--border-radius-large: 20px;--border-radius-full: 9999px;// 间距--spacing-xs: 4px;--spacing-sm: 8px;--spacing-md: 16px;--spacing-lg: 24px;--spacing-xl: 32px;--spacing-2xl: 48px;--spacing-3xl: 64px;// 字体大小--font-size-xs: 12px;--font-size-sm: 14px;--font-size-md: 16px;--font-size-lg: 18px;--font-size-xl: 20px;--font-size-2xl: 24px;--font-size-3xl: 32px;--font-size-4xl: 40px;// 字体权重--font-weight-normal: 400;--font-weight-medium: 500;--font-weight-semibold: 600;--font-weight-bold: 700;// 行高--line-height-tight: 1.25;--line-height-normal: 1.5;--line-height-relaxed: 1.75;// Z-index--z-index-dropdown: 1000;--z-index-modal: 1050;--z-index-tooltip: 1100;--z-index-toast: 1200;
}// 科技暗黑主题 - 重新设计
[data-theme="tech-black"] {// 主色调 - 使用更加协调的蓝紫色系--color-primary: #6366f1;          // 现代紫蓝色--color-primary-light: #8b5cf6;    // 亮紫色--color-primary-dark: #4f46e5;     // 深紫蓝色--color-secondary: #06b6d4;        // 青色--color-secondary-light: #22d3ee;  // 亮青色--color-secondary-dark: #0891b2;   // 深青色// 背景色系 - 使用更加现代的深色背景--color-background: #0f0f23;       // 深紫黑背景--color-background-secondary: #1a1a2e; // 次级背景--color-surface: #16213e;          // 表面色--color-surface-secondary: #1a1a2e; // 次级表面色// 文字色系 - 更加柔和的文字色彩--color-text: #e2e8f0;            // 主文字色--color-text-secondary: #94a3b8;   // 次级文字色--color-text-muted: #64748b;       // 弱化文字色--color-text-inverse: #0f0f23;     // 反色文字// 边框色系 - 更加柔和透明的边框处理--color-border: rgba(99, 102, 241, 0.08);     // 主边框色 - 极其透明的紫蓝色--color-border-light: rgba(99, 102, 241, 0.12); // 亮边框色 - 稍微明显一点--color-border-dark: rgba(15, 15, 35, 0.8);    // 深边框色 - 深色透明// 强调色 - 使用更加和谐的强调色--color-accent: #f59e0b;           // 金黄色强调--color-accent-light: #fbbf24;     // 亮金黄色--color-accent-dark: #d97706;      // 深金黄色// 状态色系 - 暗黑主题下的状态色--color-success: #10b981;          // 成功色--color-success-light: rgba(16, 185, 129, 0.15); // 成功色浅色背景--color-success-dark: #059669;     // 成功色深色--color-warning: #f59e0b;          // 警告色--color-warning-light: rgba(245, 158, 11, 0.15); // 警告色浅色背景--color-warning-dark: #d97706;     // 警告色深色--color-error: #ef4444;            // 错误色--color-error-light: rgba(239, 68, 68, 0.15); // 错误色浅色背景--color-error-dark: #dc2626;       // 错误色深色--color-info: #06b6d4;             // 信息色--color-info-light: rgba(6, 182, 212, 0.15); // 信息色浅色背景--color-info-dark: #0891b2;        // 信息色深色// 阴影系统 - 带有科技感的发光效果--shadow-small: 0 1px 3px rgba(99, 102, 241, 0.12), 0 1px 2px rgba(99, 102, 241, 0.24);--shadow-medium: 0 4px 6px rgba(99, 102, 241, 0.07), 0 2px 4px rgba(99, 102, 241, 0.06),0 0 0 1px rgba(99, 102, 241, 0.05);--shadow-large: 0 10px 15px rgba(99, 102, 241, 0.1), 0 4px 6px rgba(99, 102, 241, 0.05),0 0 0 1px rgba(99, 102, 241, 0.05);--shadow-glow: 0 0 20px rgba(99, 102, 241, 0.4),0 0 40px rgba(99, 102, 241, 0.2);// 渐变系统 - 更加科技感的渐变--gradient-primary: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #06b6d4 100%);--gradient-secondary: linear-gradient(135deg, #f59e0b 0%, #ef4444 100%);--gradient-background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 50%, #16213e 100%);--gradient-card: linear-gradient(135deg, rgba(99, 102, 241, 0.05) 0%, rgba(22, 33, 62, 0.8) 50%, rgba(26, 26, 46, 0.9) 100%);--gradient-text: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #06b6d4 100%);// 特殊效果渐变--gradient-neon: linear-gradient(135deg, rgba(99, 102, 241, 0.8) 0%, rgba(139, 92, 246, 0.6) 50%, rgba(6, 182, 212, 0.8) 100%);--gradient-glass: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%);
} 

3.4 主题常量定义

src/theme/constants/themeConstants.js

/*** 主题常量定义*/import { ThemeType } from '../types/themeTypes';// 科技蓝主题配置
export const TECH_BLUE_THEME = {name: ThemeType.TECH_BLUE,displayName: '科技蓝',colors: {// 主色调primary: '#0066ff',primaryLight: '#3385ff',primaryDark: '#0052cc',// 辅助色secondary: '#00d4ff',secondaryLight: '#33ddff',secondaryDark: '#00aacc',// 背景色background: '#f8fafc',backgroundSecondary: '#f1f5f9',surface: '#ffffff',surfaceSecondary: '#f8fafc',// 文字色text: '#1e293b',textSecondary: '#64748b',textMuted: '#94a3b8',textInverse: '#ffffff',// 边框色border: '#e2e8f0',borderLight: '#f1f5f9',borderDark: '#cbd5e1',// 强调色accent: '#8b5cf6',accentLight: '#a78bfa',accentDark: '#7c3aed',// 状态色success: '#10b981',successLight: '#34d399',successDark: '#059669',warning: '#f59e0b',warningLight: '#fbbf24',warningDark: '#d97706',error: '#ef4444',errorLight: '#f87171',errorDark: '#dc2626',info: '#3b82f6',infoLight: '#60a5fa',infoDark: '#2563eb'},shadows: {small: '0 1px 3px rgba(0, 102, 255, 0.12), 0 1px 2px rgba(0, 102, 255, 0.24)',medium: '0 4px 6px rgba(0, 102, 255, 0.07), 0 2px 4px rgba(0, 102, 255, 0.06)',large: '0 10px 15px rgba(0, 102, 255, 0.1), 0 4px 6px rgba(0, 102, 255, 0.05)',glow: '0 0 20px rgba(0, 102, 255, 0.3)'},gradients: {primary: 'linear-gradient(135deg, #0066ff 0%, #00d4ff 100%)',secondary: 'linear-gradient(135deg, #8b5cf6 0%, #3b82f6 100%)',background: 'linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%)',card: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)'}
};// 科技黑主题配置
export const TECH_BLACK_THEME = {name: ThemeType.TECH_BLACK,displayName: '科技黑',colors: {// 主色调primary: '#00ff88',primaryLight: '#33ffaa',primaryDark: '#00cc6a',// 辅助色secondary: '#00ffff',secondaryLight: '#33ffff',secondaryDark: '#00cccc',// 背景色background: '#0a0a0a',backgroundSecondary: '#111111',surface: '#1a1a1a',surfaceSecondary: '#222222',// 文字色text: '#ffffff',textSecondary: '#b3b3b3',textMuted: '#808080',textInverse: '#000000',// 边框色border: '#333333',borderLight: '#404040',borderDark: '#262626',// 强调色accent: '#ff6b6b',accentLight: '#ff8e8e',accentDark: '#ff4757',// 状态色success: '#00ff88',successLight: '#33ffaa',successDark: '#00cc6a',warning: '#ffb347',warningLight: '#ffc266',warningDark: '#ff9500',error: '#ff4757',errorLight: '#ff6b7a',errorDark: '#ff3742',info: '#00d4ff',infoLight: '#33ddff',infoDark: '#00aacc'},shadows: {small: '0 1px 3px rgba(0, 255, 136, 0.12), 0 1px 2px rgba(0, 255, 136, 0.24)',medium: '0 4px 6px rgba(0, 255, 136, 0.07), 0 2px 4px rgba(0, 255, 136, 0.06)',large: '0 10px 15px rgba(0, 255, 136, 0.1), 0 4px 6px rgba(0, 255, 136, 0.05)',glow: '0 0 20px rgba(0, 255, 136, 0.4)'},gradients: {primary: 'linear-gradient(135deg, #00ff88 0%, #00ffff 100%)',secondary: 'linear-gradient(135deg, #ff6b6b 0%, #00d4ff 100%)',background: 'linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%)',card: 'linear-gradient(135deg, #1a1a1a 0%, #222222 100%)'}
};// 主题映射
export const THEMES = {[ThemeType.TECH_BLUE]: TECH_BLUE_THEME,[ThemeType.TECH_BLACK]: TECH_BLACK_THEME
};// 默认主题
export const DEFAULT_THEME = ThemeType.TECH_BLUE;// 本地存储键
export const THEME_STORAGE_KEY = 'erp-theme'; 

3.5 主题状态管理

src/theme/stores/themeStore.js

/*** 主题状态管理*/import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { ThemeType } from '../types/themeTypes';
import { THEMES, DEFAULT_THEME, THEME_STORAGE_KEY } from '../constants/themeConstants';// 应用主题到DOM
const applyTheme = (theme) => {const root = document.documentElement;// 设置data-theme属性root.setAttribute('data-theme', theme);// 获取主题配置const themeConfig = THEMES[theme];if (!themeConfig) return;// 手动映射颜色变量,确保与CSS变量名一致const colorMapping = {primary: '--color-primary',primaryLight: '--color-primary-light',primaryDark: '--color-primary-dark',secondary: '--color-secondary',secondaryLight: '--color-secondary-light',secondaryDark: '--color-secondary-dark',background: '--color-background',backgroundSecondary: '--color-background-secondary',surface: '--color-surface',surfaceSecondary: '--color-surface-secondary',text: '--color-text',textSecondary: '--color-text-secondary',textMuted: '--color-text-muted',textInverse: '--color-text-inverse',border: '--color-border',borderLight: '--color-border-light',borderDark: '--color-border-dark',accent: '--color-accent',accentLight: '--color-accent-light',accentDark: '--color-accent-dark',success: '--color-success',successLight: '--color-success-light',successDark: '--color-success-dark',warning: '--color-warning',warningLight: '--color-warning-light',warningDark: '--color-warning-dark',error: '--color-error',errorLight: '--color-error-light',errorDark: '--color-error-dark',info: '--color-info',infoLight: '--color-info-light',infoDark: '--color-info-dark'};// 应用颜色变量Object.entries(themeConfig.colors).forEach(([key, value]) => {const cssVarName = colorMapping[key];if (cssVarName) {root.style.setProperty(cssVarName, value);}});// 应用阴影变量Object.entries(themeConfig.shadows).forEach(([key, value]) => {const cssVarName = `--shadow-${key}`;root.style.setProperty(cssVarName, value);});// 应用渐变变量Object.entries(themeConfig.gradients).forEach(([key, value]) => {const cssVarName = `--gradient-${key}`;root.style.setProperty(cssVarName, value);});
};// 创建主题状态管理store
export const useThemeStore = create(persist((set, get) => ({// 状态currentTheme: DEFAULT_THEME,isDark: false,isLoading: false,// 获取当前主题配置getCurrentThemeConfig: () => {const currentTheme = get().currentTheme;return THEMES[currentTheme] || THEMES[DEFAULT_THEME];},// 设置主题setTheme: (theme) => {if (!THEMES[theme]) {console.warn(`主题 "${theme}" 不存在,使用默认主题`);theme = DEFAULT_THEME;}set({currentTheme: theme,isDark: theme === ThemeType.TECH_BLACK});// 应用主题到DOMapplyTheme(theme);},// 切换主题toggleTheme: () => {const currentTheme = get().currentTheme;const newTheme = currentTheme === ThemeType.TECH_BLUE ? ThemeType.TECH_BLACK : ThemeType.TECH_BLUE;get().setTheme(newTheme);},// 切换到科技蓝主题setTechBlueTheme: () => {get().setTheme(ThemeType.TECH_BLUE);},// 切换到科技黑主题setTechBlackTheme: () => {get().setTheme(ThemeType.TECH_BLACK);},// 初始化主题initializeTheme: () => {set({ isLoading: true });try {const savedTheme = localStorage.getItem(THEME_STORAGE_KEY);const theme = savedTheme && THEMES[savedTheme] ? savedTheme : DEFAULT_THEME;get().setTheme(theme);} catch (error) {console.error('初始化主题失败:', error);get().setTheme(DEFAULT_THEME);} finally {set({ isLoading: false });}},// 获取主题列表getThemeList: () => {return Object.values(THEMES).map(theme => ({name: theme.name,displayName: theme.displayName,isDark: theme.name === ThemeType.TECH_BLACK}));},// 重置主题resetTheme: () => {get().setTheme(DEFAULT_THEME);}}),{name: THEME_STORAGE_KEY,partialize: (state) => ({currentTheme: state.currentTheme,isDark: state.isDark}),onRehydrateStorage: () => (state) => {// 重新水合后的回调if (state) {// 应用保存的主题applyTheme(state.currentTheme);state.initializeTheme();}}})
); 

3.6 主题提供者组件

src/theme/components/ThemeProvider.jsx

/*** 主题提供者组件*/import React, { useEffect } from 'react';
import { useThemeStore } from '../stores/themeStore';const ThemeProvider = ({ children }) => {const { initializeTheme, isLoading } = useThemeStore();useEffect(() => {// 初始化主题initializeTheme();}, [initializeTheme]);// 如果主题正在加载,可以显示加载状态if (isLoading) {return (<div className="theme-loading"><div className="theme-loading-spinner"></div><p>正在加载主题...</p></div>);}return <>{children}</>;
};export default ThemeProvider; 

3.7 主题切换组件

src/theme/components/ThemeToggle.jsx

/*** 主题切换组件*/import React from 'react';
import { Sun, Moon, Palette } from 'lucide-react';
import { useThemeStore } from '../stores/themeStore';
import { ThemeType } from '../types/themeTypes';
import './ThemeToggle.scss';const ThemeToggle = ({ showLabel = false, variant = 'button' }) => {const { currentTheme, isDark, toggleTheme, getThemeList, setTheme } = useThemeStore();// 简单切换按钮if (variant === 'button') {return (<button className="theme-toggle-btn"onClick={toggleTheme}title={`切换到${isDark ? '科技蓝' : '科技黑'}主题`}aria-label="切换主题">{isDark ? <Sun size={20} /> : <Moon size={20} />}{showLabel && (<span className="theme-toggle-label">{isDark ? '科技蓝' : '科技黑'}</span>)}</button>);}// 下拉菜单式切换if (variant === 'dropdown') {const [isOpen, setIsOpen] = React.useState(false);const themeList = getThemeList();return (<div className="theme-toggle-dropdown"><button className="theme-toggle-trigger"onClick={() => setIsOpen(!isOpen)}aria-label="选择主题"><Palette size={20} />{showLabel && <span>主题</span>}</button>{isOpen && (<div className="theme-toggle-menu">{themeList.map((theme) => (<buttonkey={theme.name}className={`theme-option ${currentTheme === theme.name ? 'active' : ''}`}onClick={() => {setTheme(theme.name);setIsOpen(false);}}><div className="theme-preview">{theme.isDark ? <Moon size={16} /> : <Sun size={16} />}</div><span className="theme-name">{theme.displayName}</span>{currentTheme === theme.name && (<div className="theme-check"></div>)}</button>))}</div>)}{isOpen && (<div className="theme-toggle-overlay"onClick={() => setIsOpen(false)}/>)}</div>);}// 卡片式切换if (variant === 'cards') {const themeList = getThemeList();return (<div className="theme-toggle-cards">{showLabel && <h3 className="theme-cards-title">选择主题</h3>}<div className="theme-cards-grid">{themeList.map((theme) => (<buttonkey={theme.name}className={`theme-card ${currentTheme === theme.name ? 'active' : ''}`}onClick={() => setTheme(theme.name)}><div className={`theme-card-preview ${theme.name}`}><div className="theme-card-icon">{theme.isDark ? <Moon size={24} /> : <Sun size={24} />}</div><div className="theme-card-colors"><div className="color-dot primary"></div><div className="color-dot secondary"></div><div className="color-dot accent"></div></div></div><div className="theme-card-info"><h4 className="theme-card-name">{theme.displayName}</h4><p className="theme-card-desc">{theme.isDark ? '深色科技风格' : '明亮科技风格'}</p></div>{currentTheme === theme.name && (<div className="theme-card-check"><div className="check-icon"></div></div>)}</button>))}</div></div>);}return null;
};export default ThemeToggle; 

3.8 主题切换组件样式

src/theme/components/ThemeToggle.scss

/*** 主题切换组件样式*/.theme-toggle {position: relative;display: inline-block;// 按钮变体&.theme-toggle-button {.theme-toggle-btn {display: flex;align-items: center;justify-content: center;width: 40px;height: 40px;border-radius: var(--border-radius-small);background: var(--color-background-secondary);border: 1px solid var(--color-border);color: var(--color-text-secondary);cursor: pointer;transition: all var(--transition-fast);position: relative;overflow: hidden;&::before {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: var(--gradient-primary);opacity: 0;transition: opacity var(--transition-fast);}&:hover {background: var(--color-border-light);border-color: var(--color-primary);color: var(--color-text);transform: translateY(-1px);box-shadow: var(--shadow-small);&::before {opacity: 0.1;}}&:active {transform: translateY(0);}svg {position: relative;z-index: 1;transition: transform var(--transition-fast);}&:hover svg {transform: rotate(180deg);}}}// 下拉菜单变体&.theme-toggle-dropdown {.theme-toggle-btn {display: flex;align-items: center;gap: var(--spacing-sm);padding: var(--spacing-sm) var(--spacing-md);border-radius: var(--border-radius-small);background: var(--color-background-secondary);border: 1px solid var(--color-border);color: var(--color-text-secondary);cursor: pointer;transition: all var(--transition-fast);font-size: var(--font-size-sm);&:hover {background: var(--color-border-light);border-color: var(--color-primary);color: var(--color-text);transform: translateY(-1px);box-shadow: var(--shadow-small);}.theme-name {font-weight: var(--font-weight-medium);line-height: var(--line-height-normal);}.dropdown-arrow {transition: transform var(--transition-fast);}&.active .dropdown-arrow {transform: rotate(180deg);}}.theme-dropdown {position: absolute;top: 100%;right: 0;margin-top: var(--spacing-sm);background: var(--color-surface);border: 1px solid var(--color-border);border-radius: var(--border-radius-medium);box-shadow: var(--shadow-large);min-width: 200px;z-index: var(--z-index-dropdown);overflow: hidden;opacity: 0;transform: translateY(-10px) scale(0.95);transition: all var(--transition-fast);&.show {opacity: 1;transform: translateY(0) scale(1);}.theme-option {width: 100%;padding: var(--spacing-md);display: flex;align-items: center;gap: var(--spacing-md);background: none;border: none;color: var(--color-text-secondary);cursor: pointer;transition: all var(--transition-fast);text-align: left;&:hover {background: var(--color-background-secondary);color: var(--color-text);}&.active {background: var(--color-primary);color: var(--color-text-inverse);.theme-preview {border-color: var(--color-text-inverse);}}.theme-preview {width: 20px;height: 20px;border-radius: 50%;border: 2px solid var(--color-border);background: var(--preview-bg);transition: all var(--transition-fast);}.theme-details {flex: 1;.theme-name {font-weight: var(--font-weight-medium);font-size: var(--font-size-sm);line-height: var(--line-height-tight);}.theme-description {font-size: var(--font-size-xs);color: var(--color-text-muted);line-height: var(--line-height-normal);margin-top: 2px;}.active & .theme-description {color: rgba(255, 255, 255, 0.8);}}}}}// 卡片变体&.theme-toggle-card {.theme-cards {display: grid;grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));gap: var(--spacing-md);.theme-card {padding: var(--spacing-lg);border: 2px solid var(--color-border);border-radius: var(--border-radius-medium);background: var(--color-surface);cursor: pointer;transition: all var(--transition-normal);position: relative;overflow: hidden;&::before {content: '';position: absolute;top: 0;left: 0;right: 0;height: 4px;background: var(--preview-bg);opacity: 0;transition: opacity var(--transition-fast);}&:hover {border-color: var(--color-primary);transform: translateY(-2px);box-shadow: var(--shadow-medium);&::before {opacity: 1;}}&.active {border-color: var(--color-primary);background: var(--color-primary);color: var(--color-text-inverse);&::before {opacity: 1;}.theme-preview {border-color: var(--color-text-inverse);}.theme-description {color: rgba(255, 255, 255, 0.8);}}.theme-header {display: flex;align-items: center;gap: var(--spacing-md);margin-bottom: var(--spacing-md);.theme-preview {width: 32px;height: 32px;border-radius: 50%;border: 2px solid var(--color-border);background: var(--preview-bg);transition: all var(--transition-fast);}.theme-name {font-size: var(--font-size-lg);font-weight: var(--font-weight-semibold);color: var(--color-text);line-height: var(--line-height-tight);}.active & .theme-name {color: var(--color-text-inverse);}}.theme-description {font-size: var(--font-size-sm);color: var(--color-text-secondary);line-height: var(--line-height-normal);}.theme-colors {display: flex;gap: var(--spacing-xs);margin-top: var(--spacing-md);.color-dot {width: 12px;height: 12px;border-radius: 50%;border: 1px solid var(--color-border);}}}}}
}// 主题预览色彩定义
.theme-toggle .theme-option,
.theme-toggle .theme-card {&[data-theme="tech-blue"] {--preview-bg: linear-gradient(135deg, #0066ff 0%, #00d4ff 100%);}&[data-theme="tech-black"] {--preview-bg: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #06b6d4 100%);}
}// 响应式设计
@media (max-width: 768px) {.theme-toggle.theme-toggle-card .theme-cards {grid-template-columns: 1fr;}.theme-toggle.theme-toggle-dropdown .theme-dropdown {right: -16px;left: -16px;min-width: auto;}
} 

四、全局公共方法

4.1 本地存储工具类

src/utils/storage.js

/*** 本地存储工具类* 用于管理用户登录状态和其他本地数据*/const STORAGE_KEYS = {ACCESS_TOKEN: 'access_token',REFRESH_TOKEN: 'refresh_token',USER_INFO: 'user_info',THEME: 'theme',LANGUAGE: 'language',
};class StorageUtil {/*** 设置访问令牌* @param {string} token - 访问令牌*/static setAccessToken(token) {if (token) {localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, token);} else {localStorage.removeItem(STORAGE_KEYS.ACCESS_TOKEN);}}/*** 获取访问令牌* @returns {string|null} 访问令牌*/static getAccessToken() {return localStorage.getItem(STORAGE_KEYS.ACCESS_TOKEN);}/*** 设置刷新令牌* @param {string} token - 刷新令牌*/static setRefreshToken(token) {if (token) {localStorage.setItem(STORAGE_KEYS.REFRESH_TOKEN, token);} else {localStorage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);}}/*** 获取刷新令牌* @returns {string|null} 刷新令牌*/static getRefreshToken() {return localStorage.getItem(STORAGE_KEYS.REFRESH_TOKEN);}/*** 设置用户信息* @param {object} userInfo - 用户信息对象*/static setUserInfo(userInfo) {if (userInfo) {localStorage.setItem(STORAGE_KEYS.USER_INFO, JSON.stringify(userInfo));} else {localStorage.removeItem(STORAGE_KEYS.USER_INFO);}}/*** 获取用户信息* @returns {object|null} 用户信息对象*/static getUserInfo() {const userInfo = localStorage.getItem(STORAGE_KEYS.USER_INFO);try {return userInfo ? JSON.parse(userInfo) : null;} catch (error) {console.error('解析用户信息失败:', error);return null;}}/*** 设置登录信息(包括令牌和用户信息)* @param {object} loginData - 登录数据* @param {string} loginData.access_token - 访问令牌* @param {string} loginData.refresh_token - 刷新令牌* @param {object} loginData.user - 用户信息*/static setLoginInfo(loginData) {if (loginData) {this.setAccessToken(loginData.access_token);this.setRefreshToken(loginData.refresh_token);this.setUserInfo(loginData.user);}}/*** 获取完整的登录信息* @returns {object} 登录信息对象*/static getLoginInfo() {return {access_token: this.getAccessToken(),refresh_token: this.getRefreshToken(),user: this.getUserInfo(),};}/*** 检查用户是否已登录* @returns {boolean} 是否已登录*/static isLoggedIn() {const accessToken = this.getAccessToken();const userInfo = this.getUserInfo();return !!(accessToken && userInfo);}/*** 清除所有登录相关信息*/static clearLoginInfo() {this.setAccessToken(null);this.setRefreshToken(null);this.setUserInfo(null);}/*** 设置主题* @param {string} theme - 主题名称*/static setTheme(theme) {if (theme) {localStorage.setItem(STORAGE_KEYS.THEME, theme);} else {localStorage.removeItem(STORAGE_KEYS.THEME);}}/*** 获取主题* @returns {string|null} 主题名称*/static getTheme() {return localStorage.getItem(STORAGE_KEYS.THEME);}/*** 设置语言* @param {string} language - 语言代码*/static setLanguage(language) {if (language) {localStorage.setItem(STORAGE_KEYS.LANGUAGE, language);} else {localStorage.removeItem(STORAGE_KEYS.LANGUAGE);}}/*** 获取语言* @returns {string|null} 语言代码*/static getLanguage() {return localStorage.getItem(STORAGE_KEYS.LANGUAGE);}/*** 清除所有存储数据*/static clearAll() {Object.values(STORAGE_KEYS).forEach(key => {localStorage.removeItem(key);});// 清除旧的Zustand persist数据localStorage.removeItem('erp-login-storage');}/*** 清理旧的存储数据*/static cleanupOldData() {// 清除旧的Zustand persist数据localStorage.removeItem('erp-login-storage');}/*** 获取存储大小(字节)* @returns {number} 存储大小*/static getStorageSize() {let total = 0;for (let key in localStorage) {if (localStorage.hasOwnProperty(key)) {total += localStorage[key].length + key.length;}}return total;}/*** 检查是否支持localStorage* @returns {boolean} 是否支持*/static isSupported() {try {const test = '__localStorage_test__';localStorage.setItem(test, test);localStorage.removeItem(test);return true;} catch (e) {return false;}}
}// 导出存储键常量和工具类
export { STORAGE_KEYS };
export default StorageUtil; 

4.2 准备资源文件

src/assets/logo.svg

src/assets/logo-icon.svg

4.3 受保护的路由组件

src/components/ProtectedRoute.jsx

/*** 路由保护组件* 用于保护需要登录才能访问的页面*/import React, { useEffect, useState } from 'react';
import { useLoginStore } from '@/pages/login/stores/loginStore';
import StorageUtil from '@/utils/storage';
import './ProtectedRoute.scss';const ProtectedRoute = ({ children, fallback }) => {const { isAuthenticated, setUser } = useLoginStore();const [isChecking, setIsChecking] = useState(true);useEffect(() => {// 检查localStorage中的登录信息const token = StorageUtil.getAccessToken();const user = StorageUtil.getUserInfo();if (token && user) {// 恢复登录状态setUser({token: token,userInfo: user});}setIsChecking(false);}, [setUser]);// 如果还在检查中,显示加载状态if (isChecking) {return (<div className="loading-container"><div className="loading-spinner"></div><p>正在加载...</p></div>);}// 如果未登录,显示登录页面if (!isAuthenticated) {return fallback;}// 已登录,显示受保护的内容return children;
};export default ProtectedRoute; 

4.4 受保护的路由组件样式

src/components/ProtectedRoute.scss

/*** 路由保护组件样式*/.loading-container {min-height: 100vh;display: flex;flex-direction: column;align-items: center;justify-content: center;background: var(--gradient-background);gap: var(--spacing-lg);// 明亮模式下的加载容器&:not([data-theme="tech-black"]) {background: var(--gradient-background);}// 暗黑主题下的加载容器[data-theme="tech-black"] & {background: var(--gradient-background);}.loading-spinner {width: 48px;height: 48px;border: 4px solid rgba(99, 102, 241, 0.1);border-top: 4px solid var(--color-primary);border-radius: 50%;animation: spin 1s linear infinite;// 明亮模式下的加载动画&:not([data-theme="tech-black"]) {border: 4px solid rgba(99, 102, 241, 0.1);border-top: 4px solid var(--color-primary);box-shadow: 0 0 20px rgba(99, 102, 241, 0.2);}// 暗黑主题下的加载动画[data-theme="tech-black"] & {border: 4px solid rgba(99, 102, 241, 0.2);border-top: 4px solid var(--color-primary);box-shadow: 0 0 20px rgba(99, 102, 241, 0.4);}}p {color: var(--color-text-secondary);font-size: var(--font-size-md);font-weight: var(--font-weight-medium);margin: 0;opacity: 0.8;// 明亮模式下的文字效果&:not([data-theme="tech-black"]) {background: var(--gradient-text);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;}// 暗黑主题下的文字效果[data-theme="tech-black"] & {background: var(--gradient-text);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;filter: drop-shadow(0 0 10px rgba(99, 102, 241, 0.3));}}
}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }
} 

4.5 组件模块导出文件

src/components/index.js

/*** 组件导出文件*/export { default as ProtectedRoute } from './ProtectedRoute'; 

五、登录页面

5.1 登录模块入口文件

src/pages/login/index.js

// 登录模块入口文件
export { default as LoginPage } from './components/LoginPage';
export { default as LoginForm } from './components/LoginForm';
export { loginAPI } from './services/loginAPI';
export { useLoginStore } from './stores/loginStore';
export * from './types/loginTypes'; 

5.2 登录相关的类型定义

src/pages/login/types/loginTypes.js

/*** 登录相关的类型定义*/// 登录表单数据类型
export const LoginFormData = {username: '',password: ''
};// 登录响应数据类型
export const LoginResponse = {code: 0,msg: '',status: false,timestamp: '',request_id: '',data: {access_token: '',token_type: '',expires_in: 0,user_info: {id: 0,username: '',email: '',is_active: false,is_superuser: false,last_login: '',created_at: '',updated_at: ''}}
};// 用户信息类型
export const UserInfo = {id: 0,username: '',email: '',is_active: false,is_superuser: false,last_login: '',created_at: '',updated_at: ''
};// 登录状态类型
export const LoginState = {isLoading: false,error: null,isAuthenticated: false,user: null,token: null
}; 

5.3 登录相关的状态管理

src/pages/stores/loginStore.js

/*** 登录相关的状态管理* Zustand只负责状态管理,持久化统一使用StorageUtil*/import { create } from 'zustand';
import { loginAPI, tokenUtils } from '../services/loginAPI';
import StorageUtil from '@/utils/storage';// 初始状态
const initialState = {user: null,token: null,isLoading: false,isAuthenticated: false,error: null,
};// 创建登录状态管理store
export const useLoginStore = create((set, get) => ({// 状态...initialState,// 设置加载状态setLoading: (loading) => set({ isLoading: loading }),// 设置错误setError: (error) => set({ error }),// 清除错误clearError: () => set({ error: null }),// 设置用户信息和登录状态setUser: (userData) => {const { token, userInfo } = userData;// 保存到本地存储StorageUtil.setLoginInfo({access_token: token,user: userInfo});// 更新Zustand状态set({user: {...userInfo,token},token,isAuthenticated: true,isLoading: false,error: null});},// 登录login: async (credentials) => {set({ isLoading: true, error: null });try {const result = await loginAPI.login(credentials);if (result.code === 10000 && result.data) {const { access_token, user_info } = result.data;if (access_token && user_info) {const userData = {token: access_token,userInfo: user_info};get().setUser(userData);return { success: true, data: result.data };} else {set({ isLoading: false, error: '登录响应数据不完整' });return { success: false, error: '登录响应数据不完整' };}} else {set({ isLoading: false, error: result.msg || '登录失败' });return { success: false, error: result.msg };}} catch (error) {set({ isLoading: false, error: error.message || '网络错误' });return { success: false, error: error.message };}},// 登出logout: async () => {try {// 调用后端登出APIawait loginAPI.logout();} catch (error) {console.error('登出API调用失败:', error);} finally {// 清除存储和状态StorageUtil.clearLoginInfo();set(initialState);}},// 检查token有效性checkToken: async () => {try {const result = await loginAPI.checkToken();return result.code === 10000;} catch (error) {console.error('Token验证失败:', error);return false;}},// 获取当前用户信息getCurrentUser: async () => {try {const result = await loginAPI.getCurrentUser();if (result.code === 10000) {const currentUser = get().user;if (currentUser) {const updatedUser = {...currentUser,...result.data};StorageUtil.setUserInfo(updatedUser);set({ user: updatedUser });}return { success: true, data: result.data };}} catch (error) {console.error('获取用户信息失败:', error);get().logout();}},// 刷新tokenrefreshToken: async () => {try {const result = await loginAPI.refreshToken();if (result.code === 10000) {const currentUser = get().user;if (currentUser) {StorageUtil.setAccessToken(result.data.access_token);const updatedUser = {...currentUser,token: result.data.access_token};set({token: result.data.access_token,user: updatedUser});}return { success: true };}} catch (error) {console.error('刷新token失败:', error);get().logout();return { success: false };}},// 检查是否已登录isLoggedIn: () => {const storeAuth = get().isAuthenticated;const storageAuth = StorageUtil.isLoggedIn();return storeAuth && storageAuth;},// 重置状态reset: () => {StorageUtil.clearLoginInfo();set(initialState);}
})); 

5.4 登录相关的API服务

src/pages/login/services/loginAPI.js

/*** 登录相关的API服务*/const API_BASE_URL = 'http://localhost:8888';// 通用请求函数
async function apiRequest(endpoint, options = {}) {const url = `${API_BASE_URL}${endpoint}`;const defaultOptions = {headers: {'Content-Type': 'application/json',...options.headers,},};// 如果有token,添加到请求头const token = localStorage.getItem('erp_token');if (token) {defaultOptions.headers.Authorization = `Bearer ${token}`;}const config = {...defaultOptions,...options,headers: {...defaultOptions.headers,...options.headers,},};try {const response = await fetch(url, config);const data = await response.json();if (response.ok) {return data;} else {throw new Error(data.msg || '请求失败');}} catch (error) {console.error('API请求错误:', error);throw error;}
}// 登录相关API
export const loginAPI = {// 用户登录login: async (credentials) => {return apiRequest('/login/', {method: 'POST',body: JSON.stringify(credentials),});},// 获取当前用户信息getCurrentUser: async () => {return apiRequest('/login/me');},// 刷新令牌refreshToken: async () => {return apiRequest('/login/refresh', {method: 'POST',});},// 用户登出logout: async () => {return apiRequest('/login/logout', {method: 'POST',});},// 检查令牌有效性checkToken: async () => {return apiRequest('/login/check');},
};// 工具函数
export const tokenUtils = {// 设置tokensetToken: (token) => {localStorage.setItem('erp_token', token);},// 获取tokengetToken: () => {return localStorage.getItem('erp_token');},// 清除tokenclearToken: () => {localStorage.removeItem('erp_token');},// 检查是否有tokenhasToken: () => {return !!localStorage.getItem('erp_token');},
}; 

5.5 登录页面样式

src/pages/login/styles/login.scss

/*** 登录页面样式 - 现代化科技风格*/.login-container {min-height: 100vh;display: flex;align-items: center;justify-content: center;background: var(--gradient-background);padding: var(--spacing-lg);font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;position: relative;overflow: hidden;// 背景装饰效果&::before {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.1) 0%, transparent 50%),radial-gradient(circle at 80% 70%, rgba(6, 182, 212, 0.1) 0%, transparent 50%),radial-gradient(circle at 50% 50%, rgba(139, 92, 246, 0.05) 0%, transparent 50%);pointer-events: none;}// 明亮模式下的背景效果&:not([data-theme="tech-black"]) {background: var(--gradient-background);&::after {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: var(--gradient-glass);opacity: 0.3;pointer-events: none;}}// 暗黑主题下的背景效果[data-theme="tech-black"] & {background: var(--gradient-background);&::after {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: linear-gradient(135deg, rgba(15, 15, 35, 0.8) 0%, rgba(26, 26, 46, 0.9) 50%, rgba(22, 33, 62, 0.8) 100%);pointer-events: none;}}
}.login-card {background: var(--gradient-card);backdrop-filter: blur(20px);border-radius: var(--border-radius-large);padding: var(--spacing-3xl);box-shadow: var(--shadow-large);width: 100%;max-width: 420px;border: 1px solid var(--color-border);transition: all var(--transition-normal);position: relative;z-index: 1;// 明亮模式下的卡片效果&:not([data-theme="tech-black"]) {border: 1px solid var(--color-border);box-shadow: 0 20px 40px rgba(99, 102, 241, 0.08),0 0 30px rgba(99, 102, 241, 0.15);&::before {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: var(--gradient-glass);border-radius: inherit;opacity: 0.5;pointer-events: none;}}// 暗黑主题下的卡片效果[data-theme="tech-black"] & {border: 1px solid rgba(99, 102, 241, 0.2);box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5),0 0 30px rgba(99, 102, 241, 0.3);&::before {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: var(--gradient-glass);border-radius: inherit;opacity: 0.1;pointer-events: none;}}}.login-header {text-align: center;margin-bottom: var(--spacing-xl);position: relative;z-index: 2;.logo {margin-bottom: var(--spacing-lg);color: var(--color-primary);font-size: var(--font-size-4xl);font-weight: var(--font-weight-bold);background: var(--gradient-text);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;// 明亮模式下的Logo效果&:not([data-theme="tech-black"]) {filter: drop-shadow(0 0 10px rgba(99, 102, 241, 0.2));}// 暗黑主题下的Logo效果[data-theme="tech-black"] & {text-shadow: 0 0 20px rgba(99, 102, 241, 0.5);filter: drop-shadow(0 0 10px rgba(99, 102, 241, 0.3));}}h1 {margin: 0 0 var(--spacing-sm) 0;color: var(--color-text);font-size: var(--font-size-3xl);font-weight: var(--font-weight-bold);background: var(--gradient-text);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;// 明亮模式下的标题效果&:not([data-theme="tech-black"]) {filter: drop-shadow(0 0 8px rgba(99, 102, 241, 0.15));}// 暗黑主题下的标题效果[data-theme="tech-black"] & {text-shadow: 0 0 15px rgba(99, 102, 241, 0.4);}}p {margin: 0;color: var(--color-text-secondary);font-size: var(--font-size-md);opacity: 0.9;}
}.login-form {display: flex;flex-direction: column;gap: var(--spacing-lg);position: relative;z-index: 2;
}.error-message {background: var(--color-error-light);color: var(--color-error-dark);padding: var(--spacing-sm) var(--spacing-md);border-radius: var(--border-radius-small);font-size: var(--font-size-sm);border: 1px solid var(--color-error);backdrop-filter: blur(5px);// 明亮模式下的错误消息&:not([data-theme="tech-black"]) {background: rgba(239, 68, 68, 0.1);border: 1px solid rgba(239, 68, 68, 0.2);}// 暗黑主题下的错误消息[data-theme="tech-black"] & {background: rgba(239, 68, 68, 0.2);border: 1px solid rgba(239, 68, 68, 0.3);}
}.input-group {position: relative;
}.input-wrapper {position: relative;display: flex;align-items: center;.input-icon {position: absolute;left: var(--spacing-md);color: var(--color-text-muted);z-index: 1;transition: all var(--transition-fast);// 明亮模式下的图标效果&:not([data-theme="tech-black"]) {filter: drop-shadow(0 0 3px rgba(99, 102, 241, 0.1));}// 暗黑主题下的图标效果[data-theme="tech-black"] & {filter: drop-shadow(0 0 5px rgba(99, 102, 241, 0.3));}}input {width: 100%;padding: var(--spacing-md) var(--spacing-md) var(--spacing-md) var(--spacing-2xl);border: 2px solid var(--color-border);border-radius: var(--border-radius-medium);font-size: var(--font-size-md);transition: all var(--transition-normal);background: var(--color-surface);color: var(--color-text);backdrop-filter: blur(5px);// 明亮模式下的输入框效果&:not([data-theme="tech-black"]) {background: rgba(99, 102, 241, 0.03);border: 2px solid var(--color-border);&:focus {outline: none;border-color: var(--color-primary);background: rgba(99, 102, 241, 0.08);box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1), 0 0 20px rgba(99, 102, 241, 0.15);}}&:focus {outline: none;border-color: var(--color-primary);box-shadow: 0 0 0 3px var(--color-primary-light);}&:disabled {background: var(--color-background-secondary);cursor: not-allowed;opacity: 0.6;}&::placeholder {color: var(--color-text-muted);}// 暗黑主题下的输入框效果[data-theme="tech-black"] & {background: rgba(99, 102, 241, 0.1);border: 2px solid rgba(99, 102, 241, 0.2);&:focus {background: rgba(99, 102, 241, 0.15);border-color: var(--color-primary);box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2), 0 0 20px rgba(99, 102, 241, 0.3);}}}&:focus-within .input-icon {color: var(--color-primary);transform: scale(1.1);// 明亮模式聚焦效果&:not([data-theme="tech-black"]) {filter: drop-shadow(0 0 8px rgba(99, 102, 241, 0.3));}// 暗黑主题聚焦效果[data-theme="tech-black"] & {filter: drop-shadow(0 0 10px rgba(99, 102, 241, 0.6));}}.password-toggle {position: absolute;right: var(--spacing-md);background: none;border: none;color: var(--color-text-muted);cursor: pointer;padding: var(--spacing-xs);display: flex;align-items: center;transition: all var(--transition-fast);border-radius: var(--border-radius-small);&:hover {color: var(--color-primary);background: rgba(99, 102, 241, 0.1);transform: scale(1.1);}&:disabled {cursor: not-allowed;opacity: 0.5;}}
}.login-button {background: var(--gradient-primary);color: var(--color-text-inverse);border: none;padding: var(--spacing-lg);border-radius: var(--border-radius-medium);font-size: var(--font-size-md);font-weight: var(--font-weight-semibold);cursor: pointer;transition: all var(--transition-normal);display: flex;align-items: center;justify-content: center;gap: var(--spacing-sm);margin-top: var(--spacing-sm);position: relative;overflow: hidden;backdrop-filter: blur(10px);// 按钮发光效果&::before {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: var(--gradient-primary);opacity: 0;transition: opacity var(--transition-normal);z-index: -1;}// 明亮模式下的按钮效果&:not([data-theme="tech-black"]) {box-shadow: 0 4px 15px rgba(99, 102, 241, 0.3);}// 暗黑主题下的按钮效果[data-theme="tech-black"] & {box-shadow: 0 4px 15px rgba(99, 102, 241, 0.5);}&:disabled {opacity: 0.6;cursor: not-allowed;}.loading-spinner {width: 20px;height: 20px;border: 2px solid rgba(255, 255, 255, 0.3);border-top: 2px solid white;border-radius: 50%;animation: spin 1s linear infinite;}
}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }
}.login-footer {text-align: center;margin-top: var(--spacing-xl);padding-top: var(--spacing-lg);border-top: 1px solid var(--color-border);position: relative;z-index: 2;// 明亮模式下的页脚效果&:not([data-theme="tech-black"]) {border-top: 1px solid var(--color-border);&::before {content: '';position: absolute;top: 0;left: 50%;transform: translateX(-50%);width: 60%;height: 1px;background: var(--gradient-primary);opacity: 0.3;}}// 暗黑主题下的页脚效果[data-theme="tech-black"] & {border-top: 1px solid rgba(99, 102, 241, 0.2);&::before {content: '';position: absolute;top: 0;left: 50%;transform: translateX(-50%);width: 60%;height: 1px;background: var(--gradient-primary);opacity: 0.5;}}p {margin: 0;color: var(--color-text-secondary);font-size: var(--font-size-sm);opacity: 0.8;}
}/* 响应式设计 */
@media (max-width: 480px) {.login-container {padding: var(--spacing-md);}.login-card {padding: var(--spacing-2xl) var(--spacing-lg);max-width: 100%;}.login-header {margin-bottom: var(--spacing-lg);.logo {font-size: var(--font-size-3xl);margin-bottom: var(--spacing-md);}h1 {font-size: var(--font-size-2xl);}p {font-size: var(--font-size-sm);}}.input-wrapper {input {padding: var(--spacing-sm) var(--spacing-sm) var(--spacing-sm) var(--spacing-xl);font-size: var(--font-size-sm);}.input-icon {left: var(--spacing-sm);}.password-toggle {right: var(--spacing-sm);padding: var(--spacing-xs);}}.login-button {padding: var(--spacing-md);font-size: var(--font-size-sm);}.login-footer {margin-top: var(--spacing-lg);padding-top: var(--spacing-md);}
} 

5.6 登录表单组件

src/pages/login/components/LoginForm.jsx

/*** 登录表单组件*/import React, { useState } from 'react';
import { Eye, EyeOff, User, Lock, LogIn } from 'lucide-react';
import { useLoginStore } from '../stores/loginStore';const LoginForm = () => {const [formData, setFormData] = useState({username: '',password: ''});const [showPassword, setShowPassword] = useState(false);const [error, setError] = useState('');// 从登录store获取状态和方法const { login, isLoading } = useLoginStore();const handleInputChange = (e) => {const { name, value } = e.target;setFormData(prev => ({...prev,[name]: value}));// 清除错误信息if (error) setError('');};const handleSubmit = async (e) => {e.preventDefault();if (!formData.username || !formData.password) {setError('请输入用户名和密码');return;}setError('');try {// 使用登录store的登录方法const result = await login(formData);if (result.success) {// 登录成功,不需要额外处理,store已经处理了状态更新} else {// 登录失败setError(result.error || '登录失败');}} catch (err) {console.error('登录错误:', err);setError(err.message || '网络错误,请检查服务器连接');}};const togglePasswordVisibility = () => {setShowPassword(!showPassword);};return (<form onSubmit={handleSubmit} className="login-form">{error && <div className="error-message">{error}</div>}<div className="input-group"><div className="input-wrapper"><User size={20} className="input-icon" /><inputtype="text"name="username"placeholder="用户名"value={formData.username}onChange={handleInputChange}disabled={isLoading}autoComplete="username"/></div></div><div className="input-group"><div className="input-wrapper"><Lock size={20} className="input-icon" /><inputtype={showPassword ? 'text' : 'password'}name="password"placeholder="密码"value={formData.password}onChange={handleInputChange}disabled={isLoading}autoComplete="current-password"/><buttontype="button"className="password-toggle"onClick={togglePasswordVisibility}disabled={isLoading}>{showPassword ? <EyeOff size={20} /> : <Eye size={20} />}</button></div></div><buttontype="submit"className="login-button"disabled={isLoading}>{isLoading ? (<div className="loading-spinner"></div>) : (<><LogIn size={20} />登录</>)}</button></form>);
};export default LoginForm; 

5.7登录页面

src/pages/login/components/LoginPage.jsx

/*** 登录页面组件*/import React from 'react';
import LoginForm from './LoginForm';
import '../styles/login.scss';const LoginPage = () => {return (<div className="login-page">{/* 背景装饰 */}<div className="background-decoration"><div className="decoration-circle decoration-1"></div><div className="decoration-circle decoration-2"></div><div className="decoration-circle decoration-3"></div></div><div className="login-container"><div className="login-card">{/* Logo和标题 */}<div className="login-header"><div className="logo-container"><svg width="48" height="48" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="loginGradient" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style={{stopColor:'#6366f1', stopOpacity:1}} /><stop offset="50%" style={{stopColor:'#8b5cf6', stopOpacity:1}} /><stop offset="100%" style={{stopColor:'#06b6d4', stopOpacity:1}} /></linearGradient><filter id="logoGlow"><feGaussianBlur stdDeviation="3" result="coloredBlur"/><feMerge> <feMergeNode in="coloredBlur"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><circle cx="16" cy="16" r="14" fill="url(#loginGradient)" opacity="0.1"/><g transform="translate(8, 8)" filter="url(#logoGlow)"><path d="M8 2 L10 4 L14 4 L16 2 L16 6 L14 8 L16 10 L16 14 L14 16 L10 16 L8 14 L4 14 L2 16 L2 12 L4 10 L2 8 L2 4 L4 2 Z" fill="none" stroke="url(#loginGradient)" strokeWidth="1.5"/><circle cx="8" cy="8" r="3" fill="url(#loginGradient)" opacity="0.8"/><circle cx="8" cy="8" r="1.5" fill="#ffffff"/></g></svg></div><h1 className="login-title">源智ERP</h1><p className="login-subtitle">智能企业资源管理系统</p></div>{/* 登录表单 */}<LoginForm /></div></div></div>);
};export default LoginPage; 

六、布局页面

6.1 布局页面入口文件

src/pages/layout/index.js

// 布局模块入口文件
export { default as MainLayout } from './components/MainLayout';
export { default as Header } from './components/Header';
export { default as Sidebar } from './components/Sidebar';
export * from './types/layoutTypes'; 

6.2 布局相关的类型定义

src/pages/layout/types/layoutTypes.js

/*** 布局相关的类型定义*/// 菜单项类型
export const MenuItem = {icon: '',label: '',active: false,path: '',children: []
};// 统计卡片类型
export const StatCard = {title: '',value: '',change: '',changeType: 'positive', // 'positive' | 'negative'icon: ''
};// 订单类型
export const Order = {id: '',customer: '',amount: '',status: '', // 'completed' | 'processing' | 'pending'date: '',time: ''
};// 图表数据类型
export const ChartData = {name: '',value: 0,date: ''
};// 布局配置类型
export const LayoutConfig = {sidebarCollapsed: false,mobileMenuOpen: false,theme: 'light' // 'light' | 'dark'
}; 

6.3 面包屑导航样式

src/pages/layout/styles/Breadcrumb.scss

/*** 面包屑导航样式*/.breadcrumb {display: flex;align-items: center;gap: var(--spacing-xs);margin: 0;padding: 0;.breadcrumb-item {display: flex;align-items: center;gap: var(--spacing-xs);.breadcrumb-icon {color: var(--color-primary);flex-shrink: 0;}.breadcrumb-text {font-size: var(--font-size-lg);font-weight: var(--font-weight-medium);color: var(--color-text-secondary);transition: all var(--transition-fast);cursor: pointer;white-space: nowrap;&:hover {color: var(--color-primary);}&.current {font-weight: var(--font-weight-bold);color: var(--color-text);cursor: default;// 明亮模式下的当前页面效果&:not([data-theme="tech-black"]) {background: var(--gradient-text);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;}// 暗黑主题下的当前页面效果[data-theme="tech-black"] & {background: var(--gradient-text);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;}}}.breadcrumb-separator {color: var(--color-text-muted);flex-shrink: 0;opacity: 0.6;}}
}// 响应式设计
@media (max-width: 768px) {.breadcrumb {.breadcrumb-item {.breadcrumb-text {font-size: var(--font-size-base);}}}
} 

6.4 头部样式

src/pages/layout/styles/Header.scss

/*** 头部组件样式*/.header {background: var(--color-surface);padding: 0 var(--spacing-lg);display: flex;align-items: center;justify-content: space-between;border-bottom: 1px solid var(--color-border);box-shadow: var(--shadow-small);transition: all var(--transition-normal);position: relative;height: 72px; // 固定高度box-sizing: border-box;z-index: 1000;// 明亮模式下的现代效果&:not([data-theme="tech-black"]) {background: var(--gradient-card);backdrop-filter: blur(10px);border-bottom: 1px solid var(--color-border);&::before {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: var(--gradient-glass);opacity: 0.5;pointer-events: none;}&::after {content: '';position: absolute;bottom: 0;left: 0;right: 0;height: 1px;background: var(--gradient-primary);opacity: 0.1;}}// 暗黑主题下的特殊效果[data-theme="tech-black"] & {background: var(--gradient-card);backdrop-filter: blur(10px);border-bottom: 1px solid rgba(99, 102, 241, 0.08);&::before {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: var(--gradient-glass);opacity: 0.3;pointer-events: none;}&::after {content: '';position: absolute;bottom: 0;left: 0;right: 0;height: 1px;background: var(--gradient-primary);opacity: 0.2;}}.header-left {display: flex;align-items: center;justify-content: flex-start;}.header-right {display: flex;align-items: center;gap: var(--spacing-lg);.search-box {position: relative;display: flex;align-items: center;svg {position: absolute;left: var(--spacing-sm);color: var(--color-text-muted);z-index: 1;}.search-input {padding: var(--spacing-sm) var(--spacing-sm) var(--spacing-sm) var(--spacing-2xl);border: 1px solid var(--color-border);border-radius: var(--border-radius-small);background: var(--color-background-secondary);width: 300px;height: 40px;font-size: var(--font-size-sm);color: var(--color-text);transition: all var(--transition-normal);box-sizing: border-box;// 明亮模式下的搜索框效果&:not([data-theme="tech-black"]) {background: rgba(99, 102, 241, 0.03);border: 1px solid var(--color-border);backdrop-filter: blur(5px);&:focus {outline: none;border-color: var(--color-primary);background: rgba(99, 102, 241, 0.08);box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1), 0 0 20px rgba(99, 102, 241, 0.15);}}&:focus {outline: none;border-color: var(--color-primary);background: var(--color-surface);box-shadow: 0 0 0 3px rgba(var(--color-primary-light), 0.1);}&::placeholder {color: var(--color-text-muted);}// 暗黑主题下的搜索框效果[data-theme="tech-black"] & {background: rgba(99, 102, 241, 0.1);border: 1px solid rgba(99, 102, 241, 0.2);&:focus {background: rgba(99, 102, 241, 0.15);border-color: var(--color-primary);box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2), 0 0 20px rgba(99, 102, 241, 0.3);}}}}.header-actions {display: flex;align-items: center;gap: var(--spacing-sm);.action-btn {position: relative;padding: var(--spacing-sm);border-radius: var(--border-radius-small);color: var(--color-text-secondary);transition: all var(--transition-fast);background: transparent;border: none;cursor: pointer;width: 40px;height: 40px;display: flex;align-items: center;justify-content: center;// 明亮模式下的按钮效果&:not([data-theme="tech-black"]) {background: rgba(99, 102, 241, 0.03);border: 1px solid var(--color-border);backdrop-filter: blur(5px);&:hover {background: rgba(99, 102, 241, 0.08);color: var(--color-text);transform: translateY(-1px);box-shadow: 0 0 15px rgba(99, 102, 241, 0.2);}}&:hover {background: var(--color-background-secondary);color: var(--color-text);transform: translateY(-1px);}&.notification-btn {.notification-badge {position: absolute;top: var(--spacing-xs);right: var(--spacing-xs);background: var(--color-error);color: var(--color-text-inverse);font-size: var(--font-size-xs);font-weight: var(--font-weight-semibold);padding: 2px var(--spacing-xs);border-radius: var(--border-radius-full);min-width: 16px;text-align: center;line-height: 1;// 明亮模式下的通知徽章效果&:not([data-theme="tech-black"]) {background: var(--gradient-secondary);box-shadow: 0 0 10px rgba(239, 68, 68, 0.3);animation: pulse 2s infinite;}// 暗黑主题下的通知徽章效果[data-theme="tech-black"] & {background: var(--gradient-secondary);box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);animation: pulse 2s infinite;}}}// 暗黑主题下的操作按钮效果[data-theme="tech-black"] & {background: rgba(99, 102, 241, 0.1);border: 1px solid rgba(99, 102, 241, 0.2);&:hover {background: rgba(99, 102, 241, 0.2);border-color: var(--color-primary);box-shadow: 0 0 15px rgba(99, 102, 241, 0.4);}}}.user-menu {margin-left: var(--spacing-sm);position: relative;.user-btn {display: flex;align-items: center;gap: var(--spacing-sm);padding: var(--spacing-sm) var(--spacing-md);border-radius: var(--border-radius-small);background: var(--color-background-secondary);color: var(--color-text);font-weight: var(--font-weight-medium);font-size: var(--font-size-sm);transition: all var(--transition-fast);border: none;cursor: pointer;height: 40px;box-sizing: border-box;// 明亮模式下的用户按钮效果&:not([data-theme="tech-black"]) {background: rgba(99, 102, 241, 0.03);border: 1px solid var(--color-border);backdrop-filter: blur(5px);&:hover {background: rgba(99, 102, 241, 0.08);transform: translateY(-1px);box-shadow: 0 0 15px rgba(99, 102, 241, 0.2);}}&:hover {background: var(--color-border-light);transform: translateY(-1px);box-shadow: var(--shadow-small);}.user-avatar {width: 32px;height: 32px;background: var(--gradient-primary);border-radius: 50%;display: flex;align-items: center;justify-content: center;color: var(--color-text-inverse);font-weight: var(--font-weight-medium);// 明亮模式下的头像效果&:not([data-theme="tech-black"]) {background: var(--gradient-neon);border: 2px solid rgba(99, 102, 241, 0.2);box-shadow: 0 0 15px rgba(99, 102, 241, 0.2);}// 暗黑主题下的头像效果[data-theme="tech-black"] & {background: var(--gradient-neon);border: 2px solid rgba(99, 102, 241, 0.3);box-shadow: 0 0 15px rgba(99, 102, 241, 0.4);}}// 暗黑主题下的用户按钮效果[data-theme="tech-black"] & {background: rgba(99, 102, 241, 0.1);border: 1px solid rgba(99, 102, 241, 0.2);&:hover {background: rgba(99, 102, 241, 0.2);border-color: var(--color-primary);box-shadow: 0 0 15px rgba(99, 102, 241, 0.4);}}}.user-dropdown {position: absolute;top: calc(100% + var(--spacing-xs));right: 0;background: var(--color-surface);border: 1px solid var(--color-border);border-radius: var(--border-radius-medium);box-shadow: var(--shadow-large);min-width: 240px;z-index: 9999;opacity: 0;visibility: hidden;transform: translateY(-8px);transition: all var(--transition-fast);overflow: hidden;// 明亮模式下的下拉菜单效果&:not([data-theme="tech-black"]) {background: var(--gradient-card);backdrop-filter: blur(20px);border: 1px solid var(--color-border);box-shadow: 0 20px 40px rgba(99, 102, 241, 0.08),0 0 30px rgba(99, 102, 241, 0.15);}// 暗黑主题下的下拉菜单效果[data-theme="tech-black"] & {background: var(--gradient-card);backdrop-filter: blur(20px);border: 1px solid rgba(99, 102, 241, 0.2);box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5),0 0 30px rgba(99, 102, 241, 0.3);}&.show {opacity: 1;visibility: visible;transform: translateY(0);}.user-info {padding: var(--spacing-md);display: flex;align-items: center;gap: var(--spacing-md);border-bottom: 1px solid var(--color-border);// 明亮模式下的用户信息区域&:not([data-theme="tech-black"]) {border-bottom: 1px solid var(--color-border);}// 暗黑主题下的用户信息区域[data-theme="tech-black"] & {border-bottom: 1px solid rgba(99, 102, 241, 0.1);}.user-avatar-large {width: 48px;height: 48px;background: var(--gradient-primary);border-radius: 50%;display: flex;align-items: center;justify-content: center;color: var(--color-text-inverse);font-weight: var(--font-weight-medium);flex-shrink: 0;// 明亮模式下的大头像效果&:not([data-theme="tech-black"]) {background: var(--gradient-neon);border: 2px solid rgba(99, 102, 241, 0.2);box-shadow: 0 0 20px rgba(99, 102, 241, 0.3);}// 暗黑主题下的大头像效果[data-theme="tech-black"] & {background: var(--gradient-neon);border: 2px solid rgba(99, 102, 241, 0.3);box-shadow: 0 0 20px rgba(99, 102, 241, 0.5);}}.user-details {display: flex;flex-direction: column;min-width: 0;.user-name {font-weight: var(--font-weight-semibold);color: var(--color-text);font-size: var(--font-size-sm);line-height: var(--line-height-tight);margin: 0 0 2px 0;// 明亮模式下的用户名效果&:not([data-theme="tech-black"]) {background: var(--gradient-text);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;}// 暗黑主题下的用户名效果[data-theme="tech-black"] & {background: var(--gradient-text);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;}}.user-role {color: var(--color-text-secondary);font-size: var(--font-size-xs);line-height: var(--line-height-normal);margin: 0;opacity: 0.8;}}}.dropdown-divider {height: 1px;background: var(--color-border);margin: 0;// 明亮模式下的分割线&:not([data-theme="tech-black"]) {background: var(--color-border);}// 暗黑主题下的分割线效果[data-theme="tech-black"] & {background: rgba(99, 102, 241, 0.1);box-shadow: 0 0 5px rgba(99, 102, 241, 0.1);}}.dropdown-item {width: 100%;padding: var(--spacing-md);display: flex;align-items: center;gap: var(--spacing-md);background: none;border: none;color: var(--color-text-secondary);font-size: var(--font-size-sm);cursor: pointer;transition: all var(--transition-fast);text-align: left;&:last-child {border-bottom: none;}svg {color: var(--color-text-muted);transition: color var(--transition-fast);width: 16px;height: 16px;}&:hover {background: var(--color-background-secondary);color: var(--color-text);svg {color: var(--color-text-secondary);}}// 明亮模式下的菜单项效果&:not([data-theme="tech-black"]) {&:hover {background: rgba(99, 102, 241, 0.08);color: var(--color-text);svg {color: var(--color-primary);filter: drop-shadow(0 0 5px rgba(99, 102, 241, 0.3));}}}// 暗黑主题下的菜单项效果[data-theme="tech-black"] & {&:hover {background: rgba(99, 102, 241, 0.2);color: var(--color-text);svg {color: var(--color-primary);filter: drop-shadow(0 0 5px rgba(99, 102, 241, 0.5));}}}}}}}}
}@keyframes pulse {0%, 100% {opacity: 1;transform: scale(1);}50% {opacity: 0.8;transform: scale(1.1);}
}/* 移动端样式 */
@media (max-width: 768px) {.header {padding: var(--spacing-md) var(--spacing-lg);.header-left {// 移动端面包屑样式由独立组件处理}.header-right {gap: var(--spacing-sm);.search-box {display: none;}.header-actions {gap: var(--spacing-xs);}.user-menu {margin-left: 0;.user-btn {padding: var(--spacing-xs) var(--spacing-sm);font-size: var(--font-size-xs);.user-avatar {width: 28px;height: 28px;}}}}}
} 

6.5 布局样式

src/pages/layout/styles/layout.scss

/*** 布局相关样式*/.main-layout {display: flex;min-height: 100vh;background-color: var(--color-background);transition: background-color var(--transition-normal);
}.main-content {flex: 1;display: flex;flex-direction: column;overflow: hidden;
}.loading-container {display: flex;flex-direction: column;align-items: center;justify-content: center;min-height: 100vh;background: var(--gradient-primary);color: var(--color-text-inverse);font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;.loading-spinner {width: 40px;height: 40px;border: 3px solid rgba(255, 255, 255, 0.3);border-top: 3px solid var(--color-text-inverse);border-radius: 50%;animation: spin 1s linear infinite;margin-bottom: var(--spacing-md);}p {font-size: var(--font-size-md);font-weight: var(--font-weight-medium);margin: 0;line-height: var(--line-height-normal);}
}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }
}/* 响应式设计 */
@media (max-width: 768px) {.main-layout {flex-direction: column;}.main-content {margin-left: 0;}
} 

6.6 侧边栏样式

src/pages/layout/styles/Sidebar.scss

/*** 侧边栏组件样式*/.sidebar {width: 280px;background: var(--gradient-primary);color: var(--color-text-inverse);display: flex;flex-direction: column;transition: all var(--transition-normal);position: relative;z-index: var(--z-index-dropdown);box-shadow: var(--shadow-medium);// 明亮模式下的现代效果&:not([data-theme="tech-black"]) {background: var(--gradient-card);color: var(--color-text);border-right: 1px solid var(--color-border);backdrop-filter: blur(10px);&::before {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: var(--gradient-neon);opacity: 0.3;pointer-events: none;}&::after {content: '';position: absolute;top: 0;right: 0;width: 1px;height: 100%;background: var(--gradient-primary);opacity: 0.1;}}// 暗黑主题下的特殊效果[data-theme="tech-black"] & {background: var(--gradient-background);border-right: 1px solid rgba(99, 102, 241, 0.08);&::before {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: var(--gradient-neon);opacity: 0.05;pointer-events: none;}&::after {content: '';position: absolute;top: 0;right: 0;width: 1px;height: 100%;background: var(--gradient-primary);opacity: 0.2;}}&.collapsed {width: 80px;.sidebar-header .logo {display: none;}.nav-item span {display: none;}}.sidebar-header {padding: 0 var(--spacing-lg);display: flex;align-items: center;justify-content: space-between;border-bottom: 1px solid rgba(255, 255, 255, 0.1);position: relative;height: 72px; // 固定高度box-sizing: border-box;// 明亮模式下的头部效果&:not([data-theme="tech-black"]) {border-bottom: 1px solid var(--color-border);&::after {content: '';position: absolute;bottom: 0;left: 0;right: 0;height: 1px;background: var(--gradient-primary);opacity: 0.1;}}// 暗黑主题下的头部效果[data-theme="tech-black"] & {border-bottom: 1px solid rgba(99, 102, 241, 0.08);&::after {content: '';position: absolute;bottom: 0;left: 0;right: 0;height: 1px;background: var(--gradient-primary);opacity: 0.2;}}.logo {font-size: var(--font-size-xl);font-weight: var(--font-weight-bold);color: var(--color-text-inverse);line-height: var(--line-height-tight);// 明亮模式下的Logo效果&:not([data-theme="tech-black"]) {color: var(--color-text);background: var(--gradient-text);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;filter: drop-shadow(0 0 10px rgba(99, 102, 241, 0.2));}// 暗黑主题下的Logo效果[data-theme="tech-black"] & {background: var(--gradient-text);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;text-shadow: 0 0 20px rgba(99, 102, 241, 0.5);}}.collapse-btn {color: var(--color-text-inverse);background: rgba(255, 255, 255, 0.1);border-radius: var(--border-radius-small);padding: var(--spacing-sm);transition: all var(--transition-fast);border: none;cursor: pointer;&:hover {background: rgba(255, 255, 255, 0.2);transform: scale(1.05);}// 暗黑主题下的按钮效果[data-theme="tech-black"] & {background: rgba(99, 102, 241, 0.2);border: 1px solid rgba(99, 102, 241, 0.3);&:hover {background: rgba(99, 102, 241, 0.4);box-shadow: 0 0 15px rgba(99, 102, 241, 0.5);}}}}.sidebar-nav {flex: 1;padding: var(--spacing-lg) 0;ul {list-style: none;margin: 0;padding: 0;}.nav-item {display: flex;align-items: center;padding: var(--spacing-md) var(--spacing-lg);color: rgba(255, 255, 255, 0.8);transition: all var(--transition-fast);text-decoration: none;gap: var(--spacing-md);position: relative;cursor: pointer;// 明亮模式下的导航项基础样式&:not([data-theme="tech-black"]) {color: var(--color-text-secondary);&:hover {background: rgba(99, 102, 241, 0.08);color: var(--color-text);transform: translateX(4px);box-shadow: inset 0 0 20px rgba(99, 102, 241, 0.05);}&.active {background: rgba(99, 102, 241, 0.12);color: var(--color-text);border-right: 3px solid var(--color-primary);box-shadow: inset 0 0 30px rgba(99, 102, 241, 0.1);&::after {background: var(--gradient-primary);width: 3px;}&::before {content: '';position: absolute;left: 0;top: 0;bottom: 0;width: 3px;background: var(--gradient-primary);}}}&:hover {background: rgba(255, 255, 255, 0.1);color: var(--color-text-inverse);transform: translateX(4px);}&.active {background: rgba(255, 255, 255, 0.2);color: var(--color-text-inverse);&::after {content: '';position: absolute;right: 0;top: 0;bottom: 0;width: 3px;background: var(--color-text-inverse);}}// 暗黑主题下的导航项效果[data-theme="tech-black"] & {&:hover {background: rgba(99, 102, 241, 0.2);box-shadow: inset 0 0 20px rgba(99, 102, 241, 0.1);}&.active {background: rgba(99, 102, 241, 0.3);border-right: 3px solid var(--color-primary);box-shadow: inset 0 0 30px rgba(99, 102, 241, 0.2);&::after {background: var(--gradient-primary);width: 3px;}&::before {content: '';position: absolute;left: 0;top: 0;bottom: 0;width: 3px;background: var(--gradient-primary);}}}span {font-weight: var(--font-weight-medium);font-size: var(--font-size-sm);line-height: var(--line-height-normal);}svg {flex-shrink: 0;transition: transform var(--transition-fast);// 明亮模式下的图标效果&:not([data-theme="tech-black"]) {filter: drop-shadow(0 0 3px rgba(99, 102, 241, 0.1));}// 暗黑主题下的图标效果[data-theme="tech-black"] & {filter: drop-shadow(0 0 5px rgba(99, 102, 241, 0.3));}}&:hover svg {transform: scale(1.1);// 明亮模式悬停效果&:not([data-theme="tech-black"]) {filter: drop-shadow(0 0 8px rgba(99, 102, 241, 0.3));}// 暗黑主题悬停效果[data-theme="tech-black"] & {filter: drop-shadow(0 0 10px rgba(99, 102, 241, 0.6));}}}}}/* 移动端样式 */
.mobile-menu-btn {display: none;position: fixed;top: var(--spacing-lg);left: var(--spacing-lg);z-index: calc(var(--z-index-dropdown) + 1);background: var(--color-primary);color: var(--color-text-inverse);border-radius: var(--border-radius-small);padding: var(--spacing-sm);border: none;cursor: pointer;box-shadow: var(--shadow-medium);transition: all var(--transition-fast);&:hover {background: var(--color-primary-dark);transform: scale(1.05);}// 暗黑主题下的移动端按钮效果[data-theme="tech-black"] & {background: var(--gradient-primary);box-shadow: var(--shadow-glow);&:hover {box-shadow: 0 0 30px rgba(99, 102, 241, 0.8);}}
}.mobile-overlay {position: fixed;top: 0;left: 0;right: 0;bottom: 0;background: rgba(0, 0, 0, 0.5);z-index: calc(var(--z-index-dropdown) - 1);opacity: 0;animation: fadeIn var(--transition-fast) ease forwards;// 暗黑主题下的遮罩效果[data-theme="tech-black"] & {background: rgba(15, 15, 35, 0.8);backdrop-filter: blur(10px);}
}@keyframes fadeIn {to {opacity: 1;}
}@media (max-width: 768px) {.mobile-menu-btn {display: block;}.sidebar {position: fixed;left: -280px;top: 0;height: 100vh;transform: translateX(0);transition: transform var(--transition-normal) ease;&.mobile-open {transform: translateX(280px);}&.collapsed {width: 280px;.sidebar-header .logo {display: block;}.nav-item span {display: block;}}}
} 

6.7 面包屑导航组件

src/pages/layout/components/Breadcrumb.jsx

/*** 面包屑导航组件*/import React from 'react';
import { ChevronRight } from 'lucide-react';
import '../styles/Breadcrumb.scss';const Breadcrumb = ({ items = [] }) => {if (!items || items.length === 0) return null;return (<nav className="breadcrumb">{items.map((item, index) => (<div key={index} className="breadcrumb-item">{index === 0 && item.icon && (<item.icon size={16} className="breadcrumb-icon" />)}<span className={`breadcrumb-text ${index === items.length - 1 ? 'current' : ''}`}onClick={() => item.onClick && item.onClick()}>{item.label}</span>{index < items.length - 1 && (<ChevronRight size={14} className="breadcrumb-separator" />)}</div>))}</nav>);
};export default Breadcrumb; 

6.8 头部组件

src/pages/layout/components/Header.jsx

/*** 头部组件*/import React, { useState } from 'react';
import { LogOut, User, Settings, Bell, Search, Home } from 'lucide-react';
import { ThemeToggle } from '@/theme';
import Breadcrumb from './Breadcrumb';
import '../styles/Header.scss';const Header = ({ user, onLogout }) => {const [showUserMenu, setShowUserMenu] = useState(false);const handleLogout = async () => {await onLogout();setShowUserMenu(false);};// 面包屑导航数据 - 这里可以根据路由动态生成const breadcrumbs = [{ label: '首页', icon: Home, path: '/' },{ label: '数据看板', path: '/dashboard' }];return (<header className="header"><div className="header-left"><Breadcrumb items={breadcrumbs} /></div><div className="header-right"><div className="search-box"><Search size={20} color="#94a3b8" /><input type="text" placeholder="搜索..." className="search-input"/></div><div className="header-actions"><ThemeToggle variant="button" /><button className="action-btn notification-btn"><Bell size={20} /><span className="notification-badge">3</span></button><button className="action-btn"><Settings size={20} /></button><div className="user-menu"><button className="user-btn"onClick={() => setShowUserMenu(!showUserMenu)}><div className="user-avatar"><User size={16} /></div><span>{user?.username || '管理员'}</span></button>{showUserMenu && (<div className={`user-dropdown ${showUserMenu ? 'show' : ''}`}><div className="user-info"><div className="user-avatar-large"><User size={20} /></div><div className="user-details"><span className="user-name">{user?.username || '管理员'}</span><span className="user-role">{user?.is_superuser ? '超级管理员' : '普通用户'}</span></div></div><div className="dropdown-divider"></div><button className="dropdown-item" onClick={handleLogout}><LogOut size={16} /><span>退出登录</span></button></div>)}</div></div></div></header>);
};export default Header; 

6.9 主布局组件

src/pages/layout/components/MainLayout.jsx

/*** 主布局组件*/import React, { useState, useEffect } from 'react';
import { useLoginStore } from '@/pages/login/stores/loginStore';
import Header from './Header';
import Sidebar from './Sidebar';
import '../styles/layout.scss';// 常量定义
const LOADING_DELAY = 1000; // 模拟加载延迟时间(毫秒)const MainLayout = ({ children }) => {const { user, logout } = useLoginStore();const [loading, setLoading] = useState(true);useEffect(() => {// 模拟加载过程const timer = setTimeout(() => {setLoading(false);}, LOADING_DELAY);return () => clearTimeout(timer);}, []);if (loading) {return (<div className="loading-container"><div className="loading-spinner"></div><p>系统加载中...</p></div>);}return (<div className="main-layout"><Sidebar /><div className="main-content"><Header user={user} onLogout={logout} />{children}</div></div>);
};export default MainLayout; 

6.10 侧边栏组件

src/pages/layout/components/Sidebar.jsx

import React, { useState } from 'react';
import { Home, Package, Users, BarChart3, Settings, ChevronLeft, Menu } from 'lucide-react';
import logoSvg from '@/assets/logo.svg';
import '../styles/Sidebar.scss';const Sidebar = () => {const [collapsed, setCollapsed] = useState(false)const [mobileOpen, setMobileOpen] = useState(false)const menuItems = [{ icon: Home, label: '首页', active: true },{ icon: Package, label: '产品管理', active: false },{ icon: Users, label: '客户管理', active: false },{ icon: BarChart3, label: '数据分析', active: false },{ icon: Settings, label: '系统设置', active: false },]return (<><button className="mobile-menu-btn"onClick={() => setMobileOpen(!mobileOpen)}><Menu size={20} /></button>{mobileOpen && (<div className="mobile-overlay"onClick={() => setMobileOpen(false)}/>)}<aside className={`sidebar ${collapsed ? 'collapsed' : ''} ${mobileOpen ? 'mobile-open' : ''}`}><div className="sidebar-header"><div className="logo"><img src={logoSvg} alt="源智ERP" className="logo-image" /></div><button className="collapse-btn"onClick={() => setCollapsed(!collapsed)}><ChevronLeft size={20} /></button></div><nav className="sidebar-nav"><ul>{menuItems.map((item, index) => (<li key={index}><a href="#" className={`nav-item ${item.active ? 'active' : ''}`}><item.icon size={20} /><span>{item.label}</span></a></li>))}</ul></nav></aside></>);
};export default Sidebar; 

七、首页

7.1 首页模块入口文件

src/pages/home/index.js

// 首页模块入口文件
export { default as HomePage } from './components/HomePage';
export { default as Dashboard } from './components/Dashboard';
export { default as StatCard } from './components/StatCard';
export { default as ChartCard } from './components/ChartCard';
export { default as RecentOrders } from './components/RecentOrders';
export { default as SalesLineChart } from './components/Charts/SalesLineChart';
export { default as ProductPieChart } from './components/Charts/ProductPieChart';
export * from './types/homeTypes'; 

7.2 首页相关的类型定义

src/pages/home/types/homeTypes.js

/*** 首页相关的类型定义*/// 统计卡片类型
export const StatCardType = {title: '',value: '',change: '',changeType: 'positive', // 'positive' | 'negative'icon: ''
};// 订单类型
export const OrderType = {id: '',customer: '',amount: '',status: '', // 'completed' | 'processing' | 'pending'date: '',time: ''
};// 图表数据类型
export const ChartDataType = {name: '',value: 0,date: '',sales: 0
};// 产品分类数据类型
export const ProductCategoryType = {name: '',value: 0
};// 销售数据类型
export const SalesDataType = {date: '',sales: 0
};// 快速操作类型
export const QuickActionType = {icon: '',text: '',action: () => {}
};// 仪表板配置类型
export const DashboardConfigType = {showStats: true,showCharts: true,showRecentOrders: true,showQuickActions: true,refreshInterval: 30000 // 30秒
}; 

7.3 图表卡片样式

src/pages/home/styles/ChartCard.scss

/*** 图表卡片样式*/.chart-card {background: var(--color-surface);border-radius: var(--border-radius-medium);padding: var(--spacing-2xl);box-shadow: var(--shadow-small);border: 1px solid var(--color-border);transition: all var(--transition-normal);position: relative;overflow: hidden;// 暗黑主题下的特殊效果[data-theme="tech-black"] & {background: var(--gradient-card);backdrop-filter: blur(10px);border: 1px solid rgba(99, 102, 241, 0.08);&::before {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: var(--gradient-glass);opacity: 0;transition: opacity var(--transition-normal);pointer-events: none;}}&:hover {box-shadow: var(--shadow-medium);// 暗黑主题悬停效果[data-theme="tech-black"] & {box-shadow: var(--shadow-glow);border-color: var(--color-primary);&::before {opacity: 1;}}}.chart-header {display: flex;justify-content: space-between;align-items: flex-start;margin-bottom: var(--spacing-2xl);.chart-title {font-size: var(--font-size-lg);font-weight: var(--font-weight-semibold);color: var(--color-text);margin: 0 0 var(--spacing-xs) 0;line-height: var(--line-height-tight);// 暗黑主题下的标题效果[data-theme="tech-black"] & {background: var(--gradient-text);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;}}.chart-subtitle {font-size: var(--font-size-sm);color: var(--color-text-secondary);margin: 0;line-height: var(--line-height-normal);}.chart-actions {display: flex;gap: var(--spacing-sm);.chart-action-btn {padding: var(--spacing-xs) var(--spacing-md);background: var(--color-background-secondary);color: var(--color-text-secondary);border-radius: var(--border-radius-small);font-size: var(--font-size-xs);font-weight: var(--font-weight-medium);transition: all var(--transition-fast);text-decoration: none;border: 1px solid var(--color-border);&:hover {background: var(--color-border-light);color: var(--color-text);transform: translateY(-1px);}// 暗黑主题下的按钮效果[data-theme="tech-black"] & {background: rgba(99, 102, 241, 0.1);border: 1px solid rgba(99, 102, 241, 0.2);&:hover {background: rgba(99, 102, 241, 0.2);border-color: var(--color-primary);box-shadow: 0 0 10px rgba(99, 102, 241, 0.3);}}}}}.chart-content {min-height: 300px;display: flex;align-items: center;justify-content: center;position: relative;.chart-placeholder {display: flex;flex-direction: column;align-items: center;gap: var(--spacing-md);color: var(--color-text-muted);text-align: center;.chart-placeholder-icon {font-size: 48px;opacity: 0.3;}p {font-size: var(--font-size-sm);margin: 0;line-height: var(--line-height-normal);}}}/* 柱状图样式 */.chart-bars {display: flex;align-items: end;gap: var(--spacing-xs);height: 200px;padding: var(--spacing-lg) 0;.chart-bar {width: 20px;background: var(--gradient-primary);border-radius: 2px 2px 0 0;transition: all var(--transition-normal);position: relative;&:hover {background: var(--gradient-secondary);transform: scaleY(1.05);}// 暗黑主题下的柱状图效果[data-theme="tech-black"] & {box-shadow: 0 0 10px rgba(99, 102, 241, 0.3);&::after {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: var(--gradient-neon);opacity: 0;transition: opacity var(--transition-normal);}&:hover::after {opacity: 0.8;}}}}/* 饼图样式 */.pie-segments {position: relative;width: 120px;height: 120px;border-radius: 50%;background: conic-gradient(var(--color) 0deg var(--percentage),var(--color-border) 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);}// 暗黑主题下的饼图效果[data-theme="tech-black"] & {box-shadow: 0 0 20px rgba(99, 102, 241, 0.3);&::before {content: '';position: absolute;inset: -2px;border-radius: 50%;background: var(--gradient-primary);z-index: -1;opacity: 0.3;}}}/* 图表图例 */.chart-legend {display: flex;flex-wrap: wrap;gap: var(--spacing-md);margin-top: var(--spacing-lg);.legend-item {display: flex;align-items: center;gap: var(--spacing-sm);.legend-color {width: 12px;height: 12px;border-radius: 50%;background: var(--color);// 暗黑主题下的图例效果[data-theme="tech-black"] & {box-shadow: 0 0 5px var(--color);}}.legend-label {font-size: var(--font-size-sm);color: var(--color-text-secondary);line-height: var(--line-height-normal);}.legend-value {font-size: var(--font-size-sm);font-weight: var(--font-weight-medium);color: var(--color-text);line-height: var(--line-height-normal);}}}
}/* 响应式设计 */
@media (max-width: 768px) {.chart-card {padding: var(--spacing-lg);.chart-header {flex-direction: column;gap: var(--spacing-md);align-items: stretch;.chart-actions {justify-content: flex-end;}}.chart-content {min-height: 250px;}.chart-bars {height: 150px;.chart-bar {width: 16px;}}}
} 

7.4 首页面板样式

src/pages/home/styles/Dashboard.scss

/*** 仪表板样式*/.dashboard {flex: 1;overflow-y: auto;background: var(--color-background);min-height: 100vh;position: relative;// 暗黑主题下的特殊背景效果[data-theme="tech-black"] & {background: var(--gradient-background);&::before {content: '';position: fixed;top: 0;left: 0;right: 0;bottom: 0;background: radial-gradient(circle at 20% 20%, rgba(99, 102, 241, 0.1) 0%, transparent 50%),radial-gradient(circle at 80% 80%, rgba(139, 92, 246, 0.1) 0%, transparent 50%),radial-gradient(circle at 40% 60%, rgba(6, 182, 212, 0.05) 0%, transparent 50%);pointer-events: none;z-index: 0;}}.dashboard-content {padding: var(--spacing-xl);max-width: 1400px;margin: 0 auto;position: relative;z-index: 1;}/* 统计卡片网格 */.stats-grid {display: grid;grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));gap: var(--spacing-lg);margin-bottom: var(--spacing-xl);}/* 主要内容区域 */.dashboard-main {display: grid;grid-template-columns: 2fr 1fr;gap: var(--spacing-xl);margin-bottom: var(--spacing-xl);.charts-section {display: flex;flex-direction: column;gap: var(--spacing-lg);}.orders-section {min-height: 400px;}}/* 快速操作区域 */.quick-actions {background: var(--color-surface);border-radius: var(--border-radius-medium);padding: var(--spacing-2xl);box-shadow: var(--shadow-small);border: 1px solid var(--color-border);transition: all var(--transition-normal);position: relative;overflow: hidden;// 暗黑主题下的快速操作效果[data-theme="tech-black"] & {background: var(--gradient-card);backdrop-filter: blur(10px);border: 1px solid rgba(99, 102, 241, 0.08);&::before {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: var(--gradient-glass);opacity: 0;transition: opacity var(--transition-normal);pointer-events: none;}}&:hover {box-shadow: var(--shadow-medium);// 暗黑主题悬停效果[data-theme="tech-black"] & {box-shadow: var(--shadow-glow);border-color: var(--color-primary);&::before {opacity: 1;}}}h3 {font-size: var(--font-size-lg);font-weight: var(--font-weight-semibold);color: var(--color-text);margin-bottom: var(--spacing-lg);line-height: var(--line-height-tight);// 暗黑主题下的标题效果[data-theme="tech-black"] & {background: var(--gradient-text);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;}}.actions-grid {display: grid;grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));gap: var(--spacing-md);.action-card {display: flex;flex-direction: column;align-items: center;gap: var(--spacing-md);padding: var(--spacing-lg);background: var(--color-background-secondary);border-radius: var(--border-radius-small);transition: all var(--transition-normal);border: 1px solid var(--color-border);cursor: pointer;position: relative;overflow: hidden;&:hover {background: var(--color-border-light);transform: translateY(-2px);box-shadow: var(--shadow-medium);border-color: var(--color-primary);}// 暗黑主题下的操作卡片效果[data-theme="tech-black"] & {background: rgba(99, 102, 241, 0.05);border: 1px solid rgba(99, 102, 241, 0.1);&::before {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: var(--gradient-neon);opacity: 0;transition: opacity var(--transition-normal);}&:hover {background: rgba(99, 102, 241, 0.1);border-color: var(--color-primary);box-shadow: 0 0 20px rgba(99, 102, 241, 0.3);&::before {opacity: 0.1;}}}.action-icon {font-size: var(--font-size-2xl);width: 48px;height: 48px;display: flex;align-items: center;justify-content: center;background: var(--gradient-card);border-radius: 50%;box-shadow: var(--shadow-small);transition: all var(--transition-normal);position: relative;z-index: 1;// 暗黑主题下的图标效果[data-theme="tech-black"] & {background: var(--gradient-neon);border: 2px solid rgba(99, 102, 241, 0.3);box-shadow: 0 0 15px rgba(99, 102, 241, 0.4);}}&:hover .action-icon {background: var(--gradient-primary);color: var(--color-text-inverse);transform: scale(1.1);// 暗黑主题悬停效果[data-theme="tech-black"] & {box-shadow: 0 0 25px rgba(99, 102, 241, 0.6);border-color: var(--color-primary);}}.action-text {font-weight: var(--font-weight-medium);color: var(--color-text-secondary);text-align: center;font-size: var(--font-size-sm);line-height: var(--line-height-normal);transition: color var(--transition-normal);position: relative;z-index: 1;}&:hover .action-text {color: var(--color-text);// 暗黑主题悬停效果[data-theme="tech-black"] & {color: var(--color-primary);}}}}}
}/* 响应式设计 */
@media (max-width: 1024px) {.dashboard .dashboard-main {grid-template-columns: 1fr;.charts-section {order: 2;}.orders-section {order: 1;}}
}@media (max-width: 768px) {.dashboard {.dashboard-content {padding: var(--spacing-lg);}.stats-grid {grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));gap: var(--spacing-md);}.quick-actions .actions-grid {grid-template-columns: repeat(2, 1fr);}}
}@media (max-width: 480px) {.dashboard {.stats-grid {grid-template-columns: 1fr;}.quick-actions .actions-grid {grid-template-columns: 1fr;}}
} 

7.5 最近订单样式

src/pages/home/styles/RecentOrders.scss

/*** 最近订单样式*/.recent-orders {background: var(--color-surface);border-radius: var(--border-radius-medium);padding: var(--spacing-2xl);box-shadow: var(--shadow-small);border: 1px solid var(--color-border);height: 100%;transition: all var(--transition-normal);position: relative;overflow: hidden;// 暗黑主题下的特殊效果[data-theme="tech-black"] & {background: var(--gradient-card);backdrop-filter: blur(10px);border: 1px solid rgba(99, 102, 241, 0.08);&::before {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: var(--gradient-glass);opacity: 0;transition: opacity var(--transition-normal);pointer-events: none;}}&:hover {box-shadow: var(--shadow-medium);// 暗黑主题悬停效果[data-theme="tech-black"] & {box-shadow: var(--shadow-glow);border-color: var(--color-primary);&::before {opacity: 1;}}}.orders-header {display: flex;justify-content: space-between;align-items: center;margin-bottom: var(--spacing-lg);h3 {font-size: var(--font-size-lg);font-weight: var(--font-weight-semibold);color: var(--color-text);margin: 0;line-height: var(--line-height-tight);// 暗黑主题下的标题效果[data-theme="tech-black"] & {background: var(--gradient-text);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;}}.view-all-btn {padding: var(--spacing-xs) var(--spacing-md);background: var(--color-background-secondary);color: var(--color-text-secondary);border-radius: var(--border-radius-small);font-size: var(--font-size-xs);font-weight: var(--font-weight-medium);transition: all var(--transition-fast);text-decoration: none;border: 1px solid var(--color-border);&:hover {background: var(--color-border-light);color: var(--color-text);transform: translateY(-1px);}// 暗黑主题下的按钮效果[data-theme="tech-black"] & {background: rgba(99, 102, 241, 0.1);border: 1px solid rgba(99, 102, 241, 0.2);&:hover {background: rgba(99, 102, 241, 0.2);border-color: var(--color-primary);box-shadow: 0 0 10px rgba(99, 102, 241, 0.3);}}}}.orders-list {display: flex;flex-direction: column;gap: var(--spacing-md);.order-item {display: flex;align-items: center;gap: var(--spacing-md);padding: var(--spacing-md);background: var(--color-background-secondary);border-radius: var(--border-radius-small);transition: all var(--transition-fast);border: 1px solid var(--color-border);position: relative;&:hover {background: var(--color-border-light);transform: translateX(4px);box-shadow: var(--shadow-small);}// 暗黑主题下的订单项效果[data-theme="tech-black"] & {background: rgba(99, 102, 241, 0.05);border: 1px solid rgba(99, 102, 241, 0.1);&::before {content: '';position: absolute;left: 0;top: 0;bottom: 0;width: 3px;background: var(--gradient-primary);opacity: 0;transition: opacity var(--transition-fast);}&:hover {background: rgba(99, 102, 241, 0.1);border-color: rgba(99, 102, 241, 0.3);box-shadow: 0 0 15px rgba(99, 102, 241, 0.2);&::before {opacity: 1;}}}.order-info {flex: 1;display: flex;flex-direction: column;gap: var(--spacing-xs);.order-id {font-size: var(--font-size-sm);font-weight: var(--font-weight-semibold);color: var(--color-text);line-height: var(--line-height-tight);// 暗黑主题下的订单ID效果[data-theme="tech-black"] & {background: var(--gradient-text);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;}}.order-customer {font-size: var(--font-size-xs);color: var(--color-text-secondary);line-height: var(--line-height-normal);}.order-amount {font-size: var(--font-size-sm);font-weight: var(--font-weight-semibold);color: var(--color-success);line-height: var(--line-height-tight);// 暗黑主题下的金额效果[data-theme="tech-black"] & {color: #10b981;text-shadow: 0 0 10px rgba(16, 185, 129, 0.5);}}}.order-status {display: flex;align-items: center;gap: var(--spacing-xs);min-width: 80px;.status-icon {color: var(--color-text-muted);transition: color var(--transition-fast);&.completed {color: var(--color-success);// 暗黑主题下的完成状态效果[data-theme="tech-black"] & {color: #10b981;filter: drop-shadow(0 0 5px rgba(16, 185, 129, 0.5));}}&.processing {color: var(--color-warning);// 暗黑主题下的处理中状态效果[data-theme="tech-black"] & {color: #f59e0b;filter: drop-shadow(0 0 5px rgba(245, 158, 11, 0.5));}}&.pending {color: var(--color-error);// 暗黑主题下的待处理状态效果[data-theme="tech-black"] & {color: #ef4444;filter: drop-shadow(0 0 5px rgba(239, 68, 68, 0.5));}}}.status-text {font-size: var(--font-size-xs);font-weight: var(--font-weight-medium);color: var(--color-text-secondary);line-height: var(--line-height-normal);}}.order-time {display: flex;flex-direction: column;align-items: flex-end;gap: 2px;min-width: 80px;.order-date {font-size: var(--font-size-xs);color: var(--color-text-secondary);line-height: var(--line-height-normal);}.order-time-text {font-size: var(--font-size-xs);color: var(--color-text-muted);line-height: var(--line-height-normal);}}.order-actions {padding: var(--spacing-xs);color: var(--color-text-muted);border-radius: var(--border-radius-small);transition: all var(--transition-fast);cursor: pointer;&:hover {background: var(--color-border-light);color: var(--color-text-secondary);transform: scale(1.1);}// 暗黑主题下的操作按钮效果[data-theme="tech-black"] & {&:hover {background: rgba(99, 102, 241, 0.2);color: var(--color-primary);box-shadow: 0 0 10px rgba(99, 102, 241, 0.3);}}}}}
}/* 响应式设计 */
@media (max-width: 768px) {.recent-orders {padding: var(--spacing-lg);.orders-list .order-item {flex-direction: column;align-items: flex-start;gap: var(--spacing-md);.order-status {align-self: flex-start;}.order-time {align-self: flex-start;align-items: flex-start;}.order-actions {align-self: flex-end;margin-top: calc(-1 * var(--spacing-2xl));}}}
} 

7.6 统计卡片样式

src/pages/home/styles/StatCard.scss

/*** 统计卡片样式*/.stat-card {background: var(--color-surface);border-radius: var(--border-radius-medium);padding: var(--spacing-2xl);box-shadow: var(--shadow-small);transition: all var(--transition-normal);border: 1px solid var(--color-border);position: relative;overflow: hidden;&::before {content: '';position: absolute;top: 0;left: 0;right: 0;height: 3px;background: var(--gradient-primary);opacity: 0;transition: opacity var(--transition-normal);}// 明亮模式下的现代效果&:not([data-theme="tech-black"]) {background: var(--gradient-card);backdrop-filter: blur(10px);border: 1px solid var(--color-border);&::after {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: var(--gradient-glass);opacity: 0;transition: opacity var(--transition-normal);pointer-events: none;}}// 暗黑主题下的特殊效果[data-theme="tech-black"] & {background: var(--gradient-card);backdrop-filter: blur(10px);border: 1px solid rgba(99, 102, 241, 0.08);&::after {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: var(--gradient-glass);opacity: 0;transition: opacity var(--transition-normal);pointer-events: none;}}&:hover {transform: translateY(-2px);box-shadow: var(--shadow-medium);border-color: var(--color-primary);&::before {opacity: 1;}// 明亮模式悬停效果&:not([data-theme="tech-black"]) {box-shadow: 0 0 30px rgba(99, 102, 241, 0.15);border-color: var(--color-primary);&::after {opacity: 0.8;}}// 暗黑主题悬停效果[data-theme="tech-black"] & {box-shadow: var(--shadow-glow);border-color: var(--color-primary);&::after {opacity: 1;}}}.stat-header {display: flex;justify-content: space-between;align-items: center;margin-bottom: var(--spacing-md);.stat-icon {width: 48px;height: 48px;background: var(--gradient-card);border-radius: var(--border-radius-medium);display: flex;align-items: center;justify-content: center;font-size: var(--font-size-xl);color: var(--color-text-inverse);transition: all var(--transition-normal);position: relative;// 明亮模式下的图标效果&:not([data-theme="tech-black"]) {background: var(--gradient-neon);color: var(--color-primary);box-shadow: 0 0 20px rgba(99, 102, 241, 0.15);&::before {content: '';position: absolute;inset: -2px;background: var(--gradient-primary);border-radius: inherit;z-index: -1;opacity: 0;transition: opacity var(--transition-normal);}}// 暗黑主题下的图标效果[data-theme="tech-black"] & {background: var(--gradient-neon);box-shadow: 0 0 20px rgba(99, 102, 241, 0.3);&::before {content: '';position: absolute;inset: -2px;background: var(--gradient-primary);border-radius: inherit;z-index: -1;opacity: 0;transition: opacity var(--transition-normal);}}}.stat-change {display: flex;align-items: center;gap: var(--spacing-xs);font-size: var(--font-size-xs);font-weight: var(--font-weight-semibold);padding: var(--spacing-xs) var(--spacing-sm);border-radius: var(--border-radius-small);line-height: var(--line-height-tight);&.positive {background: var(--color-success-light);color: var(--color-success-dark);// 明亮模式下的成功状态&:not([data-theme="tech-black"]) {background: rgba(16, 185, 129, 0.1);color: #10b981;border: 1px solid rgba(16, 185, 129, 0.2);backdrop-filter: blur(5px);}// 暗黑主题下的成功状态[data-theme="tech-black"] & {background: rgba(16, 185, 129, 0.2);color: #10b981;border: 1px solid rgba(16, 185, 129, 0.3);}}&.negative {background: var(--color-error-light);color: var(--color-error);// 明亮模式下的错误状态&:not([data-theme="tech-black"]) {background: rgba(239, 68, 68, 0.1);color: #ef4444;border: 1px solid rgba(239, 68, 68, 0.2);backdrop-filter: blur(5px);}// 暗黑主题下的错误状态[data-theme="tech-black"] & {background: rgba(239, 68, 68, 0.2);color: #ef4444;border: 1px solid rgba(239, 68, 68, 0.3);}}}}.stat-content {display: flex;flex-direction: column;gap: var(--spacing-sm);.stat-title {font-size: var(--font-size-sm);font-weight: var(--font-weight-medium);color: var(--color-text-secondary);margin: 0;line-height: var(--line-height-normal);}.stat-value {font-size: var(--font-size-3xl);font-weight: var(--font-weight-bold);color: var(--color-text);margin: 0;line-height: var(--line-height-tight);background: var(--gradient-text);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;position: relative;// 明亮模式下的数值效果&:not([data-theme="tech-black"]) {filter: drop-shadow(0 0 10px rgba(99, 102, 241, 0.2));}// 暗黑主题下的数值效果[data-theme="tech-black"] & {text-shadow: 0 0 20px rgba(99, 102, 241, 0.5);filter: drop-shadow(0 0 10px rgba(99, 102, 241, 0.3));}}}&:hover .stat-header .stat-icon {background: var(--gradient-primary);transform: scale(1.1);// 明亮模式悬停效果&:not([data-theme="tech-black"]) {&::before {opacity: 1;}box-shadow: 0 0 30px rgba(99, 102, 241, 0.4);}// 暗黑主题悬停效果[data-theme="tech-black"] & {&::before {opacity: 1;}box-shadow: 0 0 30px rgba(99, 102, 241, 0.6);}}
}/* 响应式设计 */
@media (max-width: 768px) {.stat-card {padding: var(--spacing-lg);.stat-content .stat-value {font-size: var(--font-size-2xl);}.stat-header .stat-icon {width: 40px;height: 40px;font-size: var(--font-size-lg);}}
} 

7.7 产品饼图组件

src/pages/home/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; 

7.8 销量折线图组件

src/pages/home/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; 

7.9 图表卡片组件

src/pages/home/components/ChartCard.jsx

/*** 图表卡片组件*/import React from 'react';
import { MoreHorizontal, Download, RefreshCw } from 'lucide-react';
import '../styles/ChartCard.scss';const ChartCard = ({ title, subtitle, children, actions = true }) => {return (<div className="chart-card"><div className="chart-header"><div><h3 className="chart-title">{title}</h3>{subtitle && <p className="chart-subtitle">{subtitle}</p>}</div>{actions && (<div className="chart-actions"><button className="chart-action-btn"><RefreshCw size={16} /></button><button className="chart-action-btn"><Download size={16} /></button><button className="chart-action-btn"><MoreHorizontal size={16} /></button></div>)}</div><div className="chart-content">{children || (<div className="chart-placeholder"><div className="chart-placeholder-icon">📊</div><p>图表数据加载中...</p></div>)}</div></div>);
};export default ChartCard; 

7.10 仪表板组件

src/pages/home/components/Dashboard.jsx

/*** 仪表板组件*/import React from 'react';
import { Plus, ShoppingCart, Users, Package, TrendingUp } from 'lucide-react';
import StatCard from './StatCard';
import ChartCard from './ChartCard';
import RecentOrders from './RecentOrders';
import SalesLineChart from './Charts/SalesLineChart';
import ProductPieChart from './Charts/ProductPieChart';
import '../styles/Dashboard.scss';const Dashboard = () => {const stats = [{title: '总销售额',value: '¥128,430',change: '+12.5%',changeType: 'positive',icon: TrendingUp},{title: '订单数量',value: '1,234',change: '+8.2%',changeType: 'positive',icon: ShoppingCart},{title: '客户数量',value: '856',change: '+3.1%',changeType: 'positive',icon: Users},{title: '产品数量',value: '432',change: '-2.4%',changeType: 'negative',icon: Package}];const quickActions = [{ icon: Plus, label: '新建订单' },{ icon: Users, label: '添加客户' },{ icon: Package, label: '添加产品' },{ icon: ShoppingCart, label: '库存管理' }];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"><ChartCardtitle="销售趋势"subtitle="过去30天的销售数据"><SalesLineChart /></ChartCard><ChartCardtitle="产品分布"subtitle="各类产品销售占比"><ProductPieChart /></ChartCard></div><div className="orders-section"><RecentOrders /></div></div>{/* 快速操作 */}<div className="quick-actions"><h3>快速操作</h3><div className="actions-grid">{quickActions.map((action, index) => (<div key={index} className="action-card"><div className="action-icon"><action.icon size={24} /></div><span className="action-text">{action.label}</span></div>))}</div></div></div></div>);
};export default Dashboard; 

7.11 首页组件

src/pages/home/components/HomePage.jsx

/*** 首页主组件*/import React from 'react';
import Dashboard from './Dashboard';const HomePage = () => {return <Dashboard />;
};export default HomePage; 

7.12 最近订单组件

src/pages/home/components/RecentOrders.jsx

/*** 最近订单组件*/import React from 'react';
import { CheckCircle, Clock, AlertCircle, MoreHorizontal } from 'lucide-react';
import '../styles/RecentOrders.scss';const RecentOrders = () => {const orders = [{id: '#12345',customer: '张三',amount: '¥2,380',status: 'completed',date: '2024-01-15',time: '14:30'},{id: '#12346',customer: '李四',amount: '¥1,250',status: 'processing',date: '2024-01-15',time: '13:45'},{id: '#12347',customer: '王五',amount: '¥3,680',status: 'pending',date: '2024-01-15',time: '12:20'},{id: '#12348',customer: '赵六',amount: '¥890',status: 'completed',date: '2024-01-14',time: '16:15'},{id: '#12349',customer: '钱七',amount: '¥1,450',status: 'processing',date: '2024-01-14',time: '15: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 null;}};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><a href="#" className="view-all-btn">查看全部</a></div><div className="orders-list">{orders.map((order) => (<div key={order.id} 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"><MoreHorizontal size={16} /></button></div>))}</div></div>);
};export default RecentOrders; 

7.13 统计卡片组件

src/pages/home/components/StatCard.jsx

/*** 统计卡片组件*/import React from 'react';
import { TrendingUp, TrendingDown } from 'lucide-react';
import '../styles/StatCard.scss';const StatCard = ({ title, value, change, changeType, icon: Icon }) => {return (<div className="stat-card"><div className="stat-header"><div className="stat-icon"><Icon size={20} /></div><div className={`stat-change ${changeType}`}>{changeType === 'positive' ? (<TrendingUp size={12} />) : (<TrendingDown size={12} />)}<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; 

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

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

相关文章

数据库|了解达梦数据库并做安装前的准备

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 你都用过哪些数据库&#xff1f; 使用过的数据库中觉得哪个数据库最好用&#xff1f; 你使用过达梦数据库吗&#xff1f; 最近在做的一个SCADA项目&#xff0c;要求信创版本&#xff0c;其中数据库也要使用信创目录…

Java驱动AI革命:Spring AI八篇进阶指南——从架构基础到企业级智能系统实战

系列文章目录 提示&#xff1a;下面列出了整个系列的完整目录&#xff0c;建议收藏本篇作为总览入口&#xff1a;本人将在7月份更新完毕 第一篇&#xff1a;Spring AI 概述与架构设计 第二篇&#xff1a;Spring AI 基本组件详解——ChatClient、Prompt、Memory 第三篇&#x…

hysAnalyser --- 支持菁彩视听双Vivid媒体信息解析

摘要 本文主要介绍 hysAnalyser 支持HDR Vivid格式的分析案例&#xff0c;满足用户分析HDR vivid 和 Audio Vivid格式的需要。 现将 hysAnalyser 新版本(v1.1.000)发布给网友使用&#xff0c;希望能帮助到更多音视频开发的爱好者。使用过程中&#xff0c;若遇到问题请您通过 G…

C++中NULL等于啥

文章目录 **一、`NULL` 的标准定义****二、常见实现方式**1. **定义为整数 `0`**2. **定义为 `0L` 或 `(void*)0`**(较少见)**三、与C语言的关键区别****四、`NULL` 在C++中的问题**1. **重载函数匹配歧义**2. **模板参数推导错误****五、C++11+ 的替代方案:`nullptr`****六…

pyhton基础【20】面向对象进阶一

目录 一.进阶 类方法和静态方法 属性(Properties) 继承和多态 抽象基类(Abstract Base Classes - ABCs) 魔术方法(Magic Methods) 组合和聚合 使用场景 二.私有属性 实现对数据的隐藏 设置私有属性 添加额外对属性操作的方法 三.私有方法 实现对方法的隐藏 直接…

渗透信息收集- Web应用漏洞与指纹信息收集以及情报收集

目录 1. 整体流程与目标概述 2. 常用工具及其用途 2.1 扫描与枚举工具 2.2 情报与数据聚合工具 2.3 流量拦截与手工验证工具 3. 详细技术手法与步骤 3.1 准备阶段 3.2 主动扫描与指纹识别 3.3 数据交叉验证与漏洞确认 3.4 进一步渗透与隐蔽操作 4. 实际工作经验与注…

ASP.NET代码审计 MVC架构 SQL注入漏洞n

接口路由 /Maintenance/GetMaintenanceList MaintenanceController.cs代码 Maintenance 控制器里面的 GetMaintenanceList 方法 接收参数 id 传进 MaintenanceManager.GetMaintenanceList 方法调用 MaintenanceManager.cs代码 这里 id 和 faultId 不一样是不影响的 C# 按顺序匹…

Python入门Day4

Python中数据的常用操作 数据拷贝 根据以下代码可以看出l1和l2实际上都是对于数据的引用&#xff0c;当l1被改变了&#xff0c;l2也会发生同样的改变&#xff0c;l2 l1只是将l2指向了l1所指向的地址。 >>> l1 [1,2,[3,4],[5,6]] >>> l2 l1 >>>…

计算机网络中的常用表项梳理

核心表项对比 表项 全称 工作层级 主要功能 涉及设备 典型生命周期 MAC表 媒体访问控制表 数据链路层&#xff08;二层&#xff09; Mac地址和端口关系 交换机、网桥 动态学习 FDB表 转发数据库 &#xff08;Forwarding DataBase&#xff09; 数据链路层&#xf…

百度轮岗:任命新CFO,崔珊珊退居业务二线

文 | 大力财经2025 年 7 月 1 日&#xff0c;百度组织再次变革&#xff0c;崔珊珊退居二线引发的行业关注。百度创始人李彦宏发布的内部信&#xff0c;宣布的新一轮组织调整里&#xff0c;崔珊珊退居二线这一变动&#xff0c;格外引人瞩目。崔珊珊&#xff0c;这位在百度人力资…

TAMPER-RTC(STM32F103) 引脚说明

我来查看ST官方手册中关于TAMPER-RTC引脚的具体说明。 Ran tool Ran tool Ran tool Read file: doc/STM32F103VGT6/STM32F103VGT6_specification.txt Read file: doc/STM32F103VGT6/STM32F103VGT6_specification.txt Ran tool Read file: doc/STM32F103VGT6/STM32F103VGT6_spec…

BUUCTF在线评测-练习场-WebCTF习题[极客大挑战 2019]HardSQL1-flag获取、解析

解题思路 打开靶场、熟悉的感觉 上次是过滤了很多字符&#xff0c;用了双写绕过进行注入即可&#xff0c;这次进阶了难度 先老规矩判断下闭合 11 123 报错提示 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version…

MyBatis动态SQL进阶:复杂查询与性能优化实战

引言 在复杂业务场景中&#xff0c;SQL查询往往需要动态拼接条件、复用代码片段&#xff0c;并支持批量操作。MyBatis的动态SQL功能提供了强大的解决方案&#xff0c;本文将深入解析<choose>条件分支、<sql>片段复用、批量操作优化等核心技巧&#xff0c;助你写出高…

@Transactional 注解失效的场景及原因分析

先分析一下 1&#xff0c;内部调用&#xff0c;原对象调用&#xff0c;不是代理对象调用 2&#xff0c;private方法&#xff0c;源码中&#xff0c;只能是public方法 3&#xff0c;异常被捕获了&#xff0c;事物拦截器&#xff0c;无法感知 4&#xff0c;子线程调用&#x…

使用unity创建项目,进行动画制作

1. 创建unity项目 error: error CS0006: Metadata file Library/PackageCache/com.unity.collab-proxy2.8.2/Lib/Editor/PlasticSCM/log4netPlastic.dll could not be found error CS0006: Metadata file Library/PackageCache/com.unity.collab-proxy2.8.2/Lib/Editor/Plasti…

Centos系统及国产麒麟系统设置自己写的go服务的开机启动项完整教程

1、创建服务文件 在 /etc/systemd/system/ 下新建服务配置文件&#xff08;需sudo权限&#xff09;&#xff0c;例如&#xff1a; sudo nano /etc/systemd/system/mygo.service 如下图&#xff0c;创建的mygo.service 2、创建内容如下&#xff1a; DescriptionThe go HTTP a…

Java面试宝典: IO流

1. 下面哪个流类属于面向字符的输入流() 选项: A. BufferedWriter B. FileInputStream C. ObjectInputStream D. InputStreamReader 答案:D 详细分析: 字符流与字节流的本质区别: 字符流(Character Streams)以Unicode字符为单位操作数据,适用于文本处理字节流(Byte…

黑马python(二十五)

目录&#xff1a;1.数据输出-输出为Python对象2.数据输出-输出到文件中3.综合案例1.数据输出-输出为Python对象2.数据输出-输出到文件中移动文件到文件夹&#xff1a;生成了好多文件&#xff0c;因为Rdd是有分区的 &#xff0c;会把数据分散到各个分区去存储&#xff0c;因为电…

【LeetCode 热题 100】41. 缺失的第一个正数——(解法一)暴力解

Problem: 41. 缺失的第一个正数 题目&#xff1a;给你一个未排序的整数数组 nums &#xff0c;请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。 文章目录整体思路完整代码时空复杂度时间复杂度&#xff1a;O(N log N)…

在运行 Laravel Sail 前,需安装 Docker Desktop 并完成基础配置/具体步骤

一、安装 Docker Desktop&#xff08;必备环境&#xff09; Windows 系统 &#xff08;windows安装包 有两个版本&#xff09; 架构版本查看 1. Win R‌ 输入 ‌cmd‌ 打开命令提示符&#xff1b; 2. ‌输入命令‌&#xff1a; bash echo %PROCESSOR_ARCHITECTURE% 3. ‌结果…