feat: 全站 PageShell 设计系统与专业 UI 统一

- 新增 PageShell/PageTabs/ContentPanel/PageLoading/PageEmpty 页面骨架
- index.css 设计令牌文档化,布局令牌与纯色主按钮(去除渐变粉彩)
- 全站路由页接入 PageShell,云账单三 Tab 为标杆
- 主题体系:跟随系统 + 主流/开发者分组,新增 Dracula/Tokyo Night,One Dark 暖灰差异化
- 登录页/侧栏/顶栏去渐变光球,企业克制风
- 修复 AIAlertAnalyzer JSX 与构建依赖导入问题
This commit is contained in:
Logan committed 2026-07-01 12:45:38 +08:00
1 parent 1acb0c49af
commit e363ded445
213 files changed
+7148 -6818

No files matched your search

+93 -29
View File
@@ -1,41 +1,97 @@
// 主题常量与持久化 — 供 useTheme / ThemeProvider 使用 // Logan.Li
// 主题常量与持久化 — 主流主题 + 开发者主题 + 跟随系统 // Logan.Li
import { getItem, setItem, STORAGE_KEYS } from './storage';
export type Theme =
/** 用户选择的主题偏好(含跟随系统) */
export type ThemePreference =
| 'system'
| 'ocean-deep'
| 'light-professional'
| 'midnight-purple'
| 'pine-green'
| 'orange-vitality';
| 'one-dark'
| 'catppuccin'
| 'dracula'
| 'tokyo-night'
| 'nord';
/** 实际应用到 data-theme 的具体主题 */
export type ResolvedTheme = Exclude<ThemePreference, 'system'>;
export interface ThemeOption {
id: Theme;
id: ThemePreference;
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' },
];
export const THEME_ORDER: Theme[] = THEME_OPTIONS.map((o) => o.id);
export const DEFAULT_THEME: Theme = 'ocean-deep';
/** 兼容旧版 dark/light 主题名 // Logan.Li */
export function migrateLegacyTheme(value: string | null): Theme | null {
if (value === 'dark') return 'ocean-deep';
if (value === 'light') return 'light-professional';
return null;
export interface ThemeGroup {
label: string;
options: ThemeOption[];
}
export function getInitialTheme(): Theme {
/** 主流主题 — 默认推荐 */
const MAINSTREAM_THEMES: ThemeOption[] = [
{ id: 'system', name: '跟随系统', description: '自动匹配操作系统浅色/深色', isDark: false, primaryColor: 'linear-gradient(135deg, #2563eb 50%, #0d1117 50%)' },
{ id: 'ocean-deep', name: '深海蓝', description: 'GitHub 风格蓝黑专业暗色', isDark: true, primaryColor: '#0d1117' },
{ id: 'light-professional', name: '明亮白', description: '企业控制台清爽浅色', isDark: false, primaryColor: '#2563eb' },
];
/** 开发者主题 — 高辨识度扩展 */
const DEVELOPER_THEMES: ThemeOption[] = [
{ id: 'one-dark', name: 'One Dark', description: 'Atom 暖灰 IDE,紫绿语法渐变', isDark: true, primaryColor: '#c678dd' },
{ id: 'dracula', name: 'Dracula', description: '经典紫粉暗色,极高辨识度', isDark: true, primaryColor: '#bd93f9' },
{ id: 'tokyo-night', name: 'Tokyo Night', description: '东京霓虹深蓝紫,VS Code 热门', isDark: true, primaryColor: '#7aa2f7' },
{ id: 'catppuccin', name: 'Catppuccin', description: '马卡龙柔和暗色,护眼', isDark: true, primaryColor: '#89b4fa' },
];
export const THEME_GROUPS: ThemeGroup[] = [
{ label: '主流', options: MAINSTREAM_THEMES },
{ label: '开发者', options: DEVELOPER_THEMES },
];
export const THEME_OPTIONS: ThemeOption[] = [
...MAINSTREAM_THEMES,
...DEVELOPER_THEMES,
];
export const THEME_ORDER: ThemePreference[] = THEME_OPTIONS.map((o) => o.id);
export const DEFAULT_THEME: ThemePreference = 'system';
const LEGACY_THEME_MAP: Record<string, ThemePreference> = {
dark: 'ocean-deep',
light: 'light-professional',
'midnight-purple': 'one-dark',
'pine-green': 'catppuccin',
'orange-vitality': 'tokyo-night',
nord: 'tokyo-night',
};
export function migrateLegacyTheme(value: string | null): ThemePreference | null {
if (!value) return null;
return LEGACY_THEME_MAP[value] ?? null;
}
/** 读取系统 color-scheme 偏好 // Logan.Li */
export function getSystemColorScheme(): 'light' | 'dark' {
if (typeof window === 'undefined') return 'dark';
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
/** 将用户偏好解析为具体主题 // Logan.Li */
export function resolveThemePreference(pref: ThemePreference): ResolvedTheme {
if (pref === 'system') {
return getSystemColorScheme() === 'dark' ? 'ocean-deep' : 'light-professional';
}
return pref;
}
export function isThemeDark(pref: ThemePreference): boolean {
if (pref === 'system') return getSystemColorScheme() === 'dark';
const opt = THEME_OPTIONS.find((o) => o.id === pref);
return opt?.isDark ?? true;
}
export function getInitialTheme(): ThemePreference {
if (typeof window === 'undefined') return DEFAULT_THEME;
const stored = getItem(STORAGE_KEYS.theme);
const migrated = migrateLegacyTheme(stored);
@@ -43,22 +99,30 @@ export function getInitialTheme(): Theme {
setItem(STORAGE_KEYS.theme, migrated);
return migrated;
}
if (stored && THEME_ORDER.includes(stored as Theme)) {
return stored as Theme;
if (stored && THEME_ORDER.includes(stored as ThemePreference)) {
return stored as ThemePreference;
}
/* nord 已下架,自动迁移到 Tokyo Night // Logan.Li */
if (stored === 'nord') {
setItem(STORAGE_KEYS.theme, 'tokyo-night');
return 'tokyo-night';
}
return DEFAULT_THEME;
}
export function persistTheme(theme: Theme): void {
export function persistTheme(theme: ThemePreference): void {
setItem(STORAGE_KEYS.theme, theme);
}
export function applyThemeToDocument(theme: Theme): void {
export function applyThemeToDocument(pref: ThemePreference): void {
if (typeof document === 'undefined') return;
document.documentElement.setAttribute('data-theme', theme);
const resolved = resolveThemePreference(pref);
document.documentElement.setAttribute('data-theme', resolved);
document.documentElement.setAttribute('data-theme-preference', pref);
document.documentElement.style.colorScheme = isThemeDark(pref) ? 'dark' : 'light';
}
export function getNextTheme(current: Theme): Theme {
export function getNextTheme(current: ThemePreference): ThemePreference {
const idx = THEME_ORDER.indexOf(current);
return THEME_ORDER[(idx + 1) % THEME_ORDER.length];
}