/** * 主题配置的 localStorage 存储管理模块 * 统一处理主题相关的存储操作,包括加载、保存、导出、导入和迁移旧 key */ import type { UserThemeConfig } from './tokens' /** * 主题存储 key 定义 * 统一使用 'maibot-theme-*' 前缀,替代现有的 'ui-theme'、'maibot-ui-theme' 和 'accent-color' */ export const THEME_STORAGE_KEYS = { MODE: 'maibot-theme-mode', PRESET: 'maibot-theme-preset', ACCENT: 'maibot-theme-accent', OVERRIDES: 'maibot-theme-overrides', CUSTOM_CSS: 'maibot-theme-custom-css', } as const /** * 默认主题配置 */ const DEFAULT_THEME_CONFIG: UserThemeConfig = { selectedPreset: 'light', accentColor: 'blue', tokenOverrides: {}, customCSS: '', } /** * 从 localStorage 加载完整主题配置 * 缺失值使用合理默认值 * * @returns 加载的主题配置对象 */ export function loadThemeConfig(): UserThemeConfig { const preset = localStorage.getItem(THEME_STORAGE_KEYS.PRESET) const accent = localStorage.getItem(THEME_STORAGE_KEYS.ACCENT) const overridesStr = localStorage.getItem(THEME_STORAGE_KEYS.OVERRIDES) const customCSS = localStorage.getItem(THEME_STORAGE_KEYS.CUSTOM_CSS) // 解析 tokenOverrides JSON let tokenOverrides = {} if (overridesStr) { try { tokenOverrides = JSON.parse(overridesStr) } catch { // JSON 解析失败,使用空对象 tokenOverrides = {} } } return { selectedPreset: preset || DEFAULT_THEME_CONFIG.selectedPreset, accentColor: accent || DEFAULT_THEME_CONFIG.accentColor, tokenOverrides, customCSS: customCSS || DEFAULT_THEME_CONFIG.customCSS, } } /** * 保存完整主题配置到 localStorage * * @param config - 要保存的主题配置 */ export function saveThemeConfig(config: UserThemeConfig): void { localStorage.setItem(THEME_STORAGE_KEYS.PRESET, config.selectedPreset) localStorage.setItem(THEME_STORAGE_KEYS.ACCENT, config.accentColor) localStorage.setItem(THEME_STORAGE_KEYS.OVERRIDES, JSON.stringify(config.tokenOverrides)) localStorage.setItem(THEME_STORAGE_KEYS.CUSTOM_CSS, config.customCSS) } /** * 部分更新主题配置 * 先加载现有配置,合并部分更新,再保存 * * @param partial - 部分主题配置更新 */ export function saveThemePartial(partial: Partial): void { const current = loadThemeConfig() const updated: UserThemeConfig = { ...current, ...partial, } saveThemeConfig(updated) } /** * 导出主题配置为美化格式的 JSON 字符串 * * @returns 格式化的 JSON 字符串 */ export function exportThemeJSON(): string { const config = loadThemeConfig() return JSON.stringify(config, null, 2) } /** * 从 JSON 字符串导入主题配置 * 包含基础的格式和字段校验 * * @param json - JSON 字符串 * @returns 导入结果,包含成功状态和错误列表 */ export function importThemeJSON( json: string, ): { success: boolean; errors: string[] } { const errors: string[] = [] // JSON 格式校验 let config: unknown try { config = JSON.parse(json) } catch (error) { return { success: false, errors: [`Invalid JSON format: ${error instanceof Error ? error.message : 'Unknown error'}`], } } // 基本对象类型校验 if (typeof config !== 'object' || config === null) { return { success: false, errors: ['Configuration must be a JSON object'], } } const configObj = config as Record // 必要字段存在性校验 if (typeof configObj.selectedPreset !== 'string') { errors.push('selectedPreset must be a string') } if (typeof configObj.accentColor !== 'string') { errors.push('accentColor must be a string') } if (typeof configObj.customCSS !== 'string') { errors.push('customCSS must be a string') } if (configObj.tokenOverrides !== undefined && typeof configObj.tokenOverrides !== 'object') { errors.push('tokenOverrides must be an object') } if (errors.length > 0) { return { success: false, errors } } // 校验通过,保存配置 const validConfig: UserThemeConfig = { selectedPreset: configObj.selectedPreset as string, accentColor: configObj.accentColor as string, tokenOverrides: (configObj.tokenOverrides as Partial) || {}, customCSS: configObj.customCSS as string, } saveThemeConfig(validConfig) return { success: true, errors: [] } } /** * 重置主题配置为默认值 * 删除所有 THEME_STORAGE_KEYS 对应的 localStorage 项 */ export function resetThemeToDefault(): void { Object.values(THEME_STORAGE_KEYS).forEach((key) => { localStorage.removeItem(key) }) } /** * 迁移旧的 localStorage key 到新 key * 处理: * - 'ui-theme' 或 'maibot-ui-theme' → 'maibot-theme-mode' * - 'accent-color' → 'maibot-theme-accent' * 迁移完成后删除旧 key,避免重复迁移 */ export function migrateOldKeys(): void { // 迁移主题模式 // 优先使用 'ui-theme'(因为 ThemeProvider 默认使用它) const uiTheme = localStorage.getItem('ui-theme') const maiTheme = localStorage.getItem('maibot-ui-theme') const newMode = localStorage.getItem(THEME_STORAGE_KEYS.MODE) if (!newMode) { if (uiTheme) { localStorage.setItem(THEME_STORAGE_KEYS.MODE, uiTheme) } else if (maiTheme) { localStorage.setItem(THEME_STORAGE_KEYS.MODE, maiTheme) } } // 迁移强调色 const accentColor = localStorage.getItem('accent-color') const newAccent = localStorage.getItem(THEME_STORAGE_KEYS.ACCENT) if (accentColor && !newAccent) { localStorage.setItem(THEME_STORAGE_KEYS.ACCENT, accentColor) } // 删除旧 key localStorage.removeItem('ui-theme') localStorage.removeItem('maibot-ui-theme') localStorage.removeItem('accent-color') }