问题: i18n 批量转换 (commit55f216c) 生成了 813 个 hash 键引用但翻译从未写入 JSON, 导致用户在受影响模块看到 hash 键路径(如 monitor.quickquerybarts.s64559524)而非实际文本. 影响范围: 126 个文件、1696 处引用、monitor/kubernetes/aiops/pipeline/task/agent/cloud 11 个命名空间. 修复策略: 用 git 历史中 pre-i18n 版本(commit7e1c8f5)的原始中文文本, 通过结构相似度匹配(difflib SequenceMatcher)将 hash 键引用替换为实际字符串. 对于组件拆分后产生的新文件,映射到其原始源文件查找中文文本. 修复明细: - monitor 命名空间: 335 键修复(站点拨测/日志中心/中间件监控/告警规则/SNMP/主机监控) - kubernetes 命名空间: 195 键修复(资源编辑器/Deployment编辑器/事件/命名空间/Pod日志) - aiops 命名空间: 70 键修复(容量预测/成本预测/告警分析) - pipeline/task/agent/cloud/common 命名空间: 其余键修复 验证: tsc -b=0, npm build✓ (16.87s), ESLint=0/0/0, go build=0, 17/17 测试通过, Docker healthy
129 lines
4.6 KiB
TypeScript
129 lines
4.6 KiB
TypeScript
// 主题常量与持久化 — 主流主题 + 开发者主题 + 跟随系统 // Logan.Li
|
|
|
|
import { getItem, setItem, STORAGE_KEYS } from './storage';
|
|
import i18n from '@/i18n';
|
|
|
|
/** 用户选择的主题偏好(含跟随系统) */
|
|
export type ThemePreference =
|
|
| 'system'
|
|
| 'ocean-deep'
|
|
| 'light-professional'
|
|
| 'one-dark'
|
|
| 'catppuccin'
|
|
| 'dracula'
|
|
| 'tokyo-night'
|
|
| 'nord';
|
|
|
|
/** 实际应用到 data-theme 的具体主题 */
|
|
export type ResolvedTheme = Exclude<ThemePreference, 'system'>;
|
|
|
|
export interface ThemeOption {
|
|
id: ThemePreference;
|
|
name: string;
|
|
description: string;
|
|
isDark: boolean;
|
|
primaryColor: string;
|
|
}
|
|
|
|
export interface ThemeGroup {
|
|
label: string;
|
|
options: ThemeOption[];
|
|
}
|
|
|
|
/** 主流主题 — 默认推荐 */
|
|
const MAINSTREAM_THEMES: ThemeOption[] = [
|
|
{ id: 'system', name: i18n.t('common.lightDark'), description: i18n.t('跟随系统'), isDark: false, primaryColor: 'linear-gradient(135deg, #2563eb 50%, #0d1117 50%)' },
|
|
{ id: 'ocean-deep', name: i18n.t('深海蓝'), description: i18n.t('深海蓝'), isDark: true, primaryColor: '#0d1117' },
|
|
{ id: 'light-professional', name: i18n.t('明亮白'), description: i18n.t('企业控制台清爽浅色'), isDark: false, primaryColor: '#2563eb' },
|
|
];
|
|
|
|
/** 开发者主题 — 高辨识度扩展 */
|
|
const DEVELOPER_THEMES: ThemeOption[] = [
|
|
{ id: 'one-dark', name: 'One Dark', description: i18n.t('Atom 暖灰 IDE,紫绿语法渐变'), isDark: true, primaryColor: '#c678dd' },
|
|
{ id: 'dracula', name: 'Dracula', description: i18n.t('经典紫粉暗色,极高辨识度'), isDark: true, primaryColor: '#bd93f9' },
|
|
{ id: 'tokyo-night', name: 'Tokyo Night', description: i18n.t('东京霓虹深蓝紫,VS Code 热门'), isDark: true, primaryColor: '#7aa2f7' },
|
|
{ id: 'catppuccin', name: 'Catppuccin', description: i18n.t('马卡龙柔和暗色,护眼'), isDark: true, primaryColor: '#89b4fa' },
|
|
];
|
|
|
|
export const THEME_GROUPS: ThemeGroup[] = [
|
|
{ label: i18n.t('主流'), options: MAINSTREAM_THEMES },
|
|
{ label: i18n.t('开发者'), 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);
|
|
if (migrated) {
|
|
setItem(STORAGE_KEYS.theme, migrated);
|
|
return migrated;
|
|
}
|
|
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: ThemePreference): void {
|
|
setItem(STORAGE_KEYS.theme, theme);
|
|
}
|
|
|
|
export function applyThemeToDocument(pref: ThemePreference): void {
|
|
if (typeof document === 'undefined') return;
|
|
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: ThemePreference): ThemePreference {
|
|
const idx = THEME_ORDER.indexOf(current);
|
|
return THEME_ORDER[(idx + 1) % THEME_ORDER.length];
|
|
} |