feat: 添加部门管理功能、主题切换和多语言支持
refactor(dashboard): 重构用户管理页面和路由结构 feat(server): 实现部门管理API和RBAC增强功能 docs: 更新用户手册和管理员指南文档 style: 统一图标使用和组件命名规范 test: 添加部门服务和数据隔离测试用例 chore: 更新依赖和配置文件
This commit is contained in:
File diff suppressed because it is too large
Load Diff
107
dashboard/src/contexts/ThemeContext.tsx
Normal file
107
dashboard/src/contexts/ThemeContext.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
useEffect,
|
||||
ReactNode,
|
||||
FC,
|
||||
} from 'react';
|
||||
|
||||
type Theme = 'light' | 'dark' | 'auto';
|
||||
|
||||
interface ThemeContextType {
|
||||
theme: Theme;
|
||||
resolvedTheme: 'light' | 'dark';
|
||||
setTheme: (theme: Theme) => void;
|
||||
toggleTheme: () => void;
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||
|
||||
export const useTheme = () => {
|
||||
const context = useContext(ThemeContext);
|
||||
if (!context) {
|
||||
throw new Error('useTheme must be used within a ThemeProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface ThemeProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const ThemeProvider: FC<ThemeProviderProps> = ({ children }) => {
|
||||
const [theme, setThemeState] = useState<Theme>(() => {
|
||||
const saved = localStorage.getItem('theme') as Theme;
|
||||
return saved || 'auto';
|
||||
});
|
||||
|
||||
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>(() => {
|
||||
if (theme === 'auto') {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
return theme;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
|
||||
const handleChange = (e: MediaQueryListEvent) => {
|
||||
if (theme === 'auto') {
|
||||
setResolvedTheme(e.matches ? 'dark' : 'light');
|
||||
}
|
||||
};
|
||||
|
||||
mediaQuery.addEventListener('change', handleChange);
|
||||
return () => mediaQuery.removeEventListener('change', handleChange);
|
||||
}, [theme]);
|
||||
|
||||
useEffect(() => {
|
||||
const applyTheme = (isDark: boolean) => {
|
||||
const root = document.documentElement;
|
||||
|
||||
if (isDark) {
|
||||
root.classList.add('dark');
|
||||
root.style.setProperty('--text-primary', '#e5e7eb');
|
||||
root.style.setProperty('--text-secondary', '#d1d5db');
|
||||
root.style.setProperty('--text-tertiary', '#9ca3af');
|
||||
root.style.setProperty('--background-light', '#1f2937');
|
||||
root.style.setProperty('--background-white', '#111827');
|
||||
root.style.setProperty('--background-gray', '#374151');
|
||||
root.style.setProperty('--background-dark', '#0f172a');
|
||||
root.style.setProperty('--border-color', '#374151');
|
||||
root.style.setProperty('--border-hover', '#4b5563');
|
||||
} else {
|
||||
root.classList.remove('dark');
|
||||
root.style.setProperty('--text-primary', '#262626');
|
||||
root.style.setProperty('--text-secondary', '#595959');
|
||||
root.style.setProperty('--text-tertiary', '#8c8c8c');
|
||||
root.style.setProperty('--background-light', '#f5f5f5');
|
||||
root.style.setProperty('--background-white', '#ffffff');
|
||||
root.style.setProperty('--background-gray', '#f0f0f0');
|
||||
root.style.setProperty('--background-dark', '#1f2937');
|
||||
root.style.setProperty('--border-color', '#e8e8e8');
|
||||
root.style.setProperty('--border-hover', '#d9d9d9');
|
||||
}
|
||||
};
|
||||
|
||||
const isDark = theme === 'dark' || (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
setResolvedTheme(isDark ? 'dark' : 'light');
|
||||
applyTheme(isDark);
|
||||
}, [theme]);
|
||||
|
||||
const setTheme = (newTheme: Theme) => {
|
||||
setThemeState(newTheme);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
};
|
||||
|
||||
const toggleTheme = () => {
|
||||
setTheme(resolvedTheme === 'light' ? 'dark' : 'light');
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme, toggleTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -2,6 +2,14 @@ import { createContext, useContext, useState, useEffect, ReactNode, FC } from 'r
|
||||
|
||||
export type UserRole = 'ADMIN' | 'MANAGER' | 'OPERATOR' | 'FINANCE' | 'SOURCING' | 'LOGISTICS' | 'ANALYST';
|
||||
|
||||
export interface Department {
|
||||
id: string;
|
||||
name: string;
|
||||
parentId: string | null;
|
||||
path: string;
|
||||
depth: number;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -9,6 +17,13 @@ export interface User {
|
||||
role: UserRole;
|
||||
avatar?: string;
|
||||
permissions: string[];
|
||||
departments?: string[];
|
||||
shops?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
platform: string;
|
||||
departmentId: string;
|
||||
}>;
|
||||
subscription?: {
|
||||
plan: 'free' | 'basic' | 'pro' | 'enterprise';
|
||||
expiresAt?: string;
|
||||
|
||||
Reference in New Issue
Block a user