feat: 全面UI优化 — 5套主题系统 + 设计体系 + 菜单重组 + 页面优化

1. 主题系统扩展 (5套主题)
   - ocean-deep(深海蓝) / light-professional(明亮白) / midnight-purple(午夜紫)
   - pine-green(松林绿) / orange-vitality(活力橙)
   - useTheme hook 重写支持5主题 + 向后兼容
   - AppearanceSettings 5主题预览卡片

2. 设计体系 (index.css)
   - 按钮系统: dg-btn-primary/secondary/ghost/danger + 尺寸变体
   - 徽章系统: dg-badge-* (8色)
   - 卡片/表格: dg-card-section, dg-table-row, dg-table-header
   - 状态点: dg-status-dot-* (4色)
   - 动效关键帧: slideInLeft, scaleIn

3. 菜单重组 (menu.ts)
   - 5分组: 概览/数据源/工单/权限/系统
   - 6个图标更新: Code2/FileEdit/ShieldCheck/FolderLock/ScrollText/Timer
   - 新增 permission 权限码字段

4. Layout 侧边栏增强
   - 左侧渐变指示条 + 发光效果
   - hover 微动画 + 图标变色
   - 面包屑导航头部
   - 主题切换循环5主题

5. 核心页面优化
   - TicketsPage: 设计系统类 + 颜色对齐
   - QueriesPage: 去Card依赖 + dg-table/dg-btn统一
   - Breadcrumb组件 + useBreadcrumb hook
This commit is contained in:
logan committed 2026-05-27 01:59:08 +08:00
1 parent 4e2df4d3d7
commit 746a000e8a
25 files changed
+7718 -178

No files matched your search

+9 -2
View File
@@ -1,7 +1,7 @@
import { useState, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '@/hooks/useAuth';
import { useTheme } from '@/hooks/useTheme';
import { useTheme, THEME_OPTIONS, type Theme } from '@/hooks/useTheme';
import api from '@/api/request';
export interface LoginState {
@@ -33,7 +33,14 @@ export function useLogin(): LoginState {
// Apply user preferences from backend (cross-browser sync)
try {
const prefs = await api.get<{ theme?: string; login_style?: string }>('/user/preferences');
if (prefs?.theme) setTheme(prefs.theme as 'dark' | 'light');
if (prefs?.theme) {
const t = prefs.theme;
if (t === 'dark' || t === 'light') {
setTheme(t === 'dark' ? 'ocean-deep' : 'light-professional');
} else if (THEME_OPTIONS.some(o => o.id === t)) {
setTheme(t as Theme);
}
}
if (prefs?.login_style) localStorage.setItem('login-style', prefs.login_style);
} catch { /* ignore */ }
navigate('/dashboard');
+41 -10
View File
@@ -1,22 +1,54 @@
import { createContext, useContext, useState, useCallback, useMemo, useEffect } from 'react';
export type Theme = 'dark' | 'light';
export type Theme = 'ocean-deep' | 'light-professional' | 'midnight-purple' | 'pine-green' | 'orange-vitality';
export interface ThemeOption {
id: Theme;
name: string;
description: string;
isDark: boolean;
primaryColor: string;
}
export const THEME_OPTIONS: ThemeOption[] = [
{ id: 'ocean-deep', name: '深海蓝', description: '专业稳重的深蓝色调', isDark: true, primaryColor: '#3b82f6' },
{ id: 'light-professional', name: '明亮白', description: '清爽干净的浅色主题', isDark: false, primaryColor: '#2563eb' },
{ id: 'midnight-purple', name: '午夜紫', description: '神秘优雅的紫色调', isDark: true, primaryColor: '#a855f7' },
{ id: 'pine-green', name: '松林绿', description: '自然清新的绿色主题', isDark: true, primaryColor: '#22c55e' },
{ id: 'orange-vitality', name: '活力橙', description: '温暖活力的橙色主题', isDark: true, primaryColor: '#f97316' },
];
const THEME_ORDER: Theme[] = THEME_OPTIONS.map(o => o.id);
const DEFAULT_THEME: Theme = 'ocean-deep';
// Backward compatibility: map old theme names to new ones
function migrateLegacyTheme(value: string | null): Theme | null {
if (value === 'dark') return 'ocean-deep';
if (value === 'light') return 'light-professional';
return null;
}
interface ThemeContextType {
theme: Theme;
toggleTheme: () => void;
setTheme: (theme: Theme) => void;
isDark: boolean;
isLight: boolean;
}
const ThemeContext = createContext<ThemeContextType | null>(null);
function getInitialTheme(): Theme {
if (typeof window === 'undefined') return 'dark';
const stored = localStorage.getItem('dg-theme') as Theme | null;
if (stored === 'dark' || stored === 'light') return stored;
return 'dark';
if (typeof window === 'undefined') return DEFAULT_THEME;
const stored = localStorage.getItem('dg-theme');
const migrated = migrateLegacyTheme(stored);
if (migrated) {
// Persist the migrated value so we don't migrate every time
localStorage.setItem('dg-theme', migrated);
return migrated;
}
if (stored && THEME_ORDER.includes(stored as Theme)) {
return stored as Theme;
}
return DEFAULT_THEME;
}
function applyTheme(theme: Theme) {
@@ -32,7 +64,8 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
const toggleTheme = useCallback(() => {
setThemeState(prev => {
const next = prev === 'dark' ? 'light' : 'dark';
const idx = THEME_ORDER.indexOf(prev);
const next = THEME_ORDER[(idx + 1) % THEME_ORDER.length];
localStorage.setItem('dg-theme', next);
return next;
});
@@ -47,8 +80,6 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
theme,
toggleTheme,
setTheme,
isDark: theme === 'dark',
isLight: theme === 'light',
}), [theme, toggleTheme, setTheme]);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;