From 873a3c0e60a260ecc435da39a71268661ae6708a Mon Sep 17 00:00:00 2001 From: landaiqing Date: Sun, 17 Aug 2025 19:34:35 +0800 Subject: [PATCH] :sparkles: Modify the theme storage schema --- .../voidraft/internal/models/models.ts | 147 +++++----- .../voidraft/internal/services/index.ts | 2 + .../internal/services/themeservice.ts | 104 +++++++ frontend/src/stores/configStore.ts | 126 +------- frontend/src/stores/themeStore.ts | 60 ++-- .../views/settings/pages/AppearancePage.vue | 8 +- internal/models/config.go | 6 +- internal/models/theme.go | 68 ++++- internal/services/database_service.go | 19 ++ internal/services/service_manager.go | 11 + internal/services/theme_service.go | 275 ++++++++++++++++++ 11 files changed, 587 insertions(+), 239 deletions(-) create mode 100644 frontend/bindings/voidraft/internal/services/themeservice.ts create mode 100644 internal/services/theme_service.go diff --git a/frontend/bindings/voidraft/internal/models/models.ts b/frontend/bindings/voidraft/internal/models/models.ts index 9cd16d8..ccf45d7 100644 --- a/frontend/bindings/voidraft/internal/models/models.ts +++ b/frontend/bindings/voidraft/internal/models/models.ts @@ -114,11 +114,6 @@ export class AppearanceConfig { */ "systemTheme": SystemThemeType; - /** - * 自定义主题配置 - */ - "customTheme": CustomThemeConfig; - /** Creates a new AppearanceConfig instance. */ constructor($$source: Partial = {}) { if (!("language" in $$source)) { @@ -127,9 +122,6 @@ export class AppearanceConfig { if (!("systemTheme" in $$source)) { this["systemTheme"] = ("" as SystemThemeType); } - if (!("customTheme" in $$source)) { - this["customTheme"] = (new CustomThemeConfig()); - } Object.assign(this, $$source); } @@ -138,11 +130,7 @@ export class AppearanceConfig { * Creates a new AppearanceConfig instance from a string or object. */ static createFrom($$source: any = {}): AppearanceConfig { - const $$createField2_0 = $$createType6; let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; - if ("customTheme" in $$parsedSource) { - $$parsedSource["customTheme"] = $$createField2_0($$parsedSource["customTheme"]); - } return new AppearanceConfig($$parsedSource as Partial); } } @@ -201,49 +189,6 @@ export class ConfigMetadata { } } -/** - * CustomThemeConfig 自定义主题配置 - */ -export class CustomThemeConfig { - /** - * 深色主题配置 - */ - "darkTheme": ThemeColorConfig; - - /** - * 浅色主题配置 - */ - "lightTheme": ThemeColorConfig; - - /** Creates a new CustomThemeConfig instance. */ - constructor($$source: Partial = {}) { - if (!("darkTheme" in $$source)) { - this["darkTheme"] = (new ThemeColorConfig()); - } - if (!("lightTheme" in $$source)) { - this["lightTheme"] = (new ThemeColorConfig()); - } - - Object.assign(this, $$source); - } - - /** - * Creates a new CustomThemeConfig instance from a string or object. - */ - static createFrom($$source: any = {}): CustomThemeConfig { - const $$createField0_0 = $$createType7; - const $$createField1_0 = $$createType7; - let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; - if ("darkTheme" in $$parsedSource) { - $$parsedSource["darkTheme"] = $$createField0_0($$parsedSource["darkTheme"]); - } - if ("lightTheme" in $$parsedSource) { - $$parsedSource["lightTheme"] = $$createField1_0($$parsedSource["lightTheme"]); - } - return new CustomThemeConfig($$parsedSource as Partial); - } -} - /** * Document represents a document in the system */ @@ -428,7 +373,7 @@ export class Extension { * Creates a new Extension instance from a string or object. */ static createFrom($$source: any = {}): Extension { - const $$createField3_0 = $$createType8; + const $$createField3_0 = $$createType6; let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; if ("config" in $$parsedSource) { $$parsedSource["config"] = $$createField3_0($$parsedSource["config"]); @@ -561,7 +506,7 @@ export class GeneralConfig { * Creates a new GeneralConfig instance from a string or object. */ static createFrom($$source: any = {}): GeneralConfig { - const $$createField5_0 = $$createType10; + const $$createField5_0 = $$createType8; let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; if ("globalHotkey" in $$parsedSource) { $$parsedSource["globalHotkey"] = $$createField5_0($$parsedSource["globalHotkey"]); @@ -1171,6 +1116,58 @@ export enum TabType { TabTypeTab = "tab", }; +/** + * Theme 主题数据库模型 + */ +export class Theme { + "id": number; + "name": string; + "type": ThemeType; + "colors": ThemeColorConfig; + "isDefault": boolean; + "createdAt": time$0.Time; + "updatedAt": time$0.Time; + + /** Creates a new Theme instance. */ + constructor($$source: Partial = {}) { + if (!("id" in $$source)) { + this["id"] = 0; + } + if (!("name" in $$source)) { + this["name"] = ""; + } + if (!("type" in $$source)) { + this["type"] = ("" as ThemeType); + } + if (!("colors" in $$source)) { + this["colors"] = (new ThemeColorConfig()); + } + if (!("isDefault" in $$source)) { + this["isDefault"] = false; + } + if (!("createdAt" in $$source)) { + this["createdAt"] = null; + } + if (!("updatedAt" in $$source)) { + this["updatedAt"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Theme instance from a string or object. + */ + static createFrom($$source: any = {}): Theme { + const $$createField3_0 = $$createType9; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("colors" in $$parsedSource) { + $$parsedSource["colors"] = $$createField3_0($$parsedSource["colors"]); + } + return new Theme($$parsedSource as Partial); + } +} + /** * ThemeColorConfig 主题颜色配置 */ @@ -1379,6 +1376,19 @@ export class ThemeColorConfig { } } +/** + * ThemeType 主题类型枚举 + */ +export enum ThemeType { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + ThemeTypeDark = "dark", + ThemeTypeLight = "light", +}; + /** * UpdateSourceType 更新源类型 */ @@ -1477,8 +1487,8 @@ export class UpdatesConfig { * Creates a new UpdatesConfig instance from a string or object. */ static createFrom($$source: any = {}): UpdatesConfig { - const $$createField6_0 = $$createType11; - const $$createField7_0 = $$createType12; + const $$createField6_0 = $$createType10; + const $$createField7_0 = $$createType11; let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; if ("github" in $$parsedSource) { $$parsedSource["github"] = $$createField6_0($$parsedSource["github"]); @@ -1497,15 +1507,14 @@ const $$createType2 = AppearanceConfig.createFrom; const $$createType3 = UpdatesConfig.createFrom; const $$createType4 = GitBackupConfig.createFrom; const $$createType5 = ConfigMetadata.createFrom; -const $$createType6 = CustomThemeConfig.createFrom; -const $$createType7 = ThemeColorConfig.createFrom; -var $$createType8 = (function $$initCreateType8(...args): any { - if ($$createType8 === $$initCreateType8) { - $$createType8 = $$createType9; +var $$createType6 = (function $$initCreateType6(...args): any { + if ($$createType6 === $$initCreateType6) { + $$createType6 = $$createType7; } - return $$createType8(...args); + return $$createType6(...args); }); -const $$createType9 = $Create.Map($Create.Any, $Create.Any); -const $$createType10 = HotkeyCombo.createFrom; -const $$createType11 = GithubConfig.createFrom; -const $$createType12 = GiteaConfig.createFrom; +const $$createType7 = $Create.Map($Create.Any, $Create.Any); +const $$createType8 = HotkeyCombo.createFrom; +const $$createType9 = ThemeColorConfig.createFrom; +const $$createType10 = GithubConfig.createFrom; +const $$createType11 = GiteaConfig.createFrom; diff --git a/frontend/bindings/voidraft/internal/services/index.ts b/frontend/bindings/voidraft/internal/services/index.ts index 2f85a3d..92c9ff0 100644 --- a/frontend/bindings/voidraft/internal/services/index.ts +++ b/frontend/bindings/voidraft/internal/services/index.ts @@ -13,6 +13,7 @@ import * as MigrationService from "./migrationservice.js"; import * as SelfUpdateService from "./selfupdateservice.js"; import * as StartupService from "./startupservice.js"; import * as SystemService from "./systemservice.js"; +import * as ThemeService from "./themeservice.js"; import * as TranslationService from "./translationservice.js"; import * as TrayService from "./trayservice.js"; import * as WindowService from "./windowservice.js"; @@ -29,6 +30,7 @@ export { SelfUpdateService, StartupService, SystemService, + ThemeService, TranslationService, TrayService, WindowService diff --git a/frontend/bindings/voidraft/internal/services/themeservice.ts b/frontend/bindings/voidraft/internal/services/themeservice.ts new file mode 100644 index 0000000..ba4f925 --- /dev/null +++ b/frontend/bindings/voidraft/internal/services/themeservice.ts @@ -0,0 +1,104 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * ThemeService 主题服务 + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Call as $Call, Create as $Create} from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as models$0 from "../models/models.js"; + +/** + * CreateTheme 创建新主题 + */ +export function CreateTheme(theme: models$0.Theme | null): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(3274757686, theme) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType1($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * GetAllThemes 获取所有主题 + */ +export function GetAllThemes(): Promise<(models$0.Theme | null)[]> & { cancel(): void } { + let $resultPromise = $Call.ByID(2425053076) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType2($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * GetDefaultThemes 获取默认主题 + */ +export function GetDefaultThemes(): Promise<{ [_: string]: models$0.Theme | null }> & { cancel(): void } { + let $resultPromise = $Call.ByID(3801788118) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType3($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * GetThemeByType 根据类型获取默认主题 + */ +export function GetThemeByType(themeType: models$0.ThemeType): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(1680465265, themeType) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType1($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * ResetThemeColors 重置主题颜色为默认值 + */ +export function ResetThemeColors(themeType: models$0.ThemeType): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(342461245, themeType) as any; + return $resultPromise; +} + +/** + * ServiceShutdown 服务关闭 + */ +export function ServiceShutdown(): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(1676749034) as any; + return $resultPromise; +} + +/** + * ServiceStartup 服务启动时初始化 + */ +export function ServiceStartup(options: application$0.ServiceOptions): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(2915959937, options) as any; + return $resultPromise; +} + +/** + * UpdateThemeColors 更新主题颜色 + */ +export function UpdateThemeColors(themeType: models$0.ThemeType, colors: models$0.ThemeColorConfig): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(2750902529, themeType, colors) as any; + return $resultPromise; +} + +// Private type creation functions +const $$createType0 = models$0.Theme.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = $Create.Array($$createType1); +const $$createType3 = $Create.Map($Create.Any, $$createType1); diff --git a/frontend/src/stores/configStore.ts b/frontend/src/stores/configStore.ts index e0f61e4..c89d044 100644 --- a/frontend/src/stores/configStore.ts +++ b/frontend/src/stores/configStore.ts @@ -78,8 +78,7 @@ const EDITING_CONFIG_KEY_MAP: EditingConfigKeyMap = { const APPEARANCE_CONFIG_KEY_MAP: AppearanceConfigKeyMap = { language: 'appearance.language', - systemTheme: 'appearance.systemTheme', - customTheme: 'appearance.customTheme' + systemTheme: 'appearance.systemTheme' } as const; const UPDATES_CONFIG_KEY_MAP: UpdatesConfigKeyMap = { @@ -189,80 +188,10 @@ const DEFAULT_CONFIG: AppConfig = { tabType: CONFIG_LIMITS.tabType.default, autoSaveDelay: 5000 }, - appearance: { - language: LanguageType.LangZhCN, - systemTheme: SystemThemeType.SystemThemeAuto, - customTheme: { - darkTheme: { - // 基础色调 - background: '#252B37', - backgroundSecondary: '#213644', - surface: '#474747', - foreground: '#9BB586', - foregroundSecondary: '#9c9c9c', - - // 语法高亮 - comment: '#6272a4', - keyword: '#ff79c6', - string: '#f1fa8c', - function: '#50fa7b', - number: '#bd93f9', - operator: '#ff79c6', - variable: '#8fbcbb', - type: '#8be9fd', - - // 界面元素 - cursor: '#fff', - selection: '#0865a9aa', - selectionBlur: '#225377aa', - activeLine: 'rgba(255,255,255,0.04)', - lineNumber: 'rgba(255,255,255, 0.15)', - activeLineNumber: 'rgba(255,255,255, 0.6)', - - // 边框分割线 - borderColor: '#1e222a', - borderLight: 'rgba(255,255,255, 0.1)', - - // 搜索匹配 - searchMatch: '#8fbcbb', - matchingBracket: 'rgba(255,255,255,0.1)' - }, - lightTheme: { - // 基础色调 - background: '#ffffff', - backgroundSecondary: '#f1faf1', - surface: '#f5f5f5', - foreground: '#444d56', - foregroundSecondary: '#6a737d', - - // 语法高亮 - comment: '#6a737d', - keyword: '#d73a49', - string: '#032f62', - function: '#005cc5', - number: '#005cc5', - operator: '#d73a49', - variable: '#24292e', - type: '#6f42c1', - - // 界面元素 - cursor: '#000', - selection: '#77baff8c', - selectionBlur: '#b2c2ca85', - activeLine: '#000000', - lineNumber: '#000000', - activeLineNumber: '#000000', - - // 边框分割线 - borderColor: '#dfdfdf', - borderLight: '#0000000C', - - // 搜索匹配 - searchMatch: '#005cc5', - matchingBracket: 'rgba(0,0,0,0.1)' - } - } - }, + appearance: { + language: LanguageType.LangZhCN, + systemTheme: SystemThemeType.SystemThemeAuto + }, updates: { version: "1.0.0", autoUpdate: true, @@ -478,50 +407,7 @@ export const useConfigStore = defineStore('config', () => { await updateAppearanceConfig('systemTheme', systemTheme); }; - // 更新自定义主题方法 - const updateCustomTheme = async (themeType: 'darkTheme' | 'lightTheme', colorKey: string, colorValue: string): Promise => { - // 确保配置已加载 - if (!state.configLoaded && !state.isLoading) { - await initConfig(); - } - try { - // 深拷贝当前配置 - const customTheme = JSON.parse(JSON.stringify(state.config.appearance.customTheme)); - - // 更新对应主题的颜色值 - customTheme[themeType][colorKey] = colorValue; - - // 更新整个自定义主题配置到后端 - await ConfigService.Set(APPEARANCE_CONFIG_KEY_MAP.customTheme, customTheme); - - // 更新前端状态 - state.config.appearance.customTheme = customTheme; - } catch (error) { - throw error; - } - }; - - // 设置整个自定义主题配置 - const setCustomTheme = async (customTheme: any): Promise => { - // 确保配置已加载 - if (!state.configLoaded && !state.isLoading) { - await initConfig(); - } - - try { - // 更新整个自定义主题配置到后端 - await ConfigService.Set(APPEARANCE_CONFIG_KEY_MAP.customTheme, customTheme); - - // 更新前端状态 - state.config.appearance.customTheme = customTheme; - - // 确保Vue能检测到变化 - state.config.appearance = { ...state.config.appearance }; - } catch (error) { - throw error; - } - }; // 初始化语言设置 const initializeLanguage = async (): Promise => { @@ -586,8 +472,6 @@ export const useConfigStore = defineStore('config', () => { // 主题相关方法 setSystemTheme, - updateCustomTheme, - setCustomTheme, // 字体大小操作 ...adjusters.fontSize, diff --git a/frontend/src/stores/themeStore.ts b/frontend/src/stores/themeStore.ts index 7774455..c40bd49 100644 --- a/frontend/src/stores/themeStore.ts +++ b/frontend/src/stores/themeStore.ts @@ -1,6 +1,7 @@ import {defineStore} from 'pinia'; import {computed, reactive} from 'vue'; -import {SystemThemeType} from '@/../bindings/voidraft/internal/models/models'; +import {SystemThemeType, ThemeType, ThemeColorConfig} from '@/../bindings/voidraft/internal/models/models'; +import {ThemeService} from '@/../bindings/voidraft/internal/services'; import {useConfigStore} from './configStore'; import {useEditorStore} from './editorStore'; import {defaultDarkColors} from '@/views/editor/theme/dark'; @@ -24,16 +25,21 @@ export const useThemeStore = defineStore('theme', () => { configStore.config?.appearance?.systemTheme || SystemThemeType.SystemThemeAuto ); - // 初始化主题颜色 - 从配置加载 - const initializeThemeColors = () => { - const customTheme = configStore.config?.appearance?.customTheme; - if (customTheme) { - if (customTheme.darkTheme) { - Object.assign(themeColors.darkTheme, customTheme.darkTheme); + // 初始化主题颜色 - 从数据库加载 + const initializeThemeColors = async () => { + try { + const themes = await ThemeService.GetDefaultThemes(); + if (themes.dark) { + Object.assign(themeColors.darkTheme, themes.dark.colors); } - if (customTheme.lightTheme) { - Object.assign(themeColors.lightTheme, customTheme.lightTheme); + if (themes.light) { + Object.assign(themeColors.lightTheme, themes.light.colors); } + } catch (error) { + console.warn('Failed to load themes from database, using defaults:', error); + // 如果数据库加载失败,使用默认主题 + Object.assign(themeColors.darkTheme, defaultDarkColors); + Object.assign(themeColors.lightTheme, defaultLightColors); } }; @@ -46,10 +52,10 @@ export const useThemeStore = defineStore('theme', () => { }; // 初始化主题 - const initializeTheme = () => { + const initializeTheme = async () => { const theme = configStore.config?.appearance?.systemTheme || SystemThemeType.SystemThemeAuto; applyThemeToDOM(theme); - initializeThemeColors(); + await initializeThemeColors(); }; // 设置主题 @@ -84,31 +90,35 @@ export const useThemeStore = defineStore('theme', () => { return hasChanges; }; - // 保存主题颜色到配置 + // 保存主题颜色到数据库 const saveThemeColors = async () => { - const customTheme = { - darkTheme: { ...themeColors.darkTheme }, - lightTheme: { ...themeColors.lightTheme } - }; - - await configStore.setCustomTheme(customTheme); + try { + const darkColors = ThemeColorConfig.createFrom(themeColors.darkTheme); + const lightColors = ThemeColorConfig.createFrom(themeColors.lightTheme); + + await ThemeService.UpdateThemeColors(ThemeType.ThemeTypeDark, darkColors); + await ThemeService.UpdateThemeColors(ThemeType.ThemeTypeLight, lightColors); + } catch (error) { + console.error('Failed to save theme colors:', error); + throw error; + } }; // 重置主题颜色 const resetThemeColors = async (themeType: 'darkTheme' | 'lightTheme') => { try { - // 1. 更新内存中的颜色状态 + const dbThemeType = themeType === 'darkTheme' ? ThemeType.ThemeTypeDark : ThemeType.ThemeTypeLight; + + // 1. 调用后端重置服务 + await ThemeService.ResetThemeColors(dbThemeType); + + // 2. 更新内存中的颜色状态 if (themeType === 'darkTheme') { Object.assign(themeColors.darkTheme, defaultDarkColors); - } - - if (themeType === 'lightTheme') { + } else { Object.assign(themeColors.lightTheme, defaultLightColors); } - // 2. 保存到配置 - await saveThemeColors(); - // 3. 刷新编辑器主题 refreshEditorTheme(); diff --git a/frontend/src/views/settings/pages/AppearancePage.vue b/frontend/src/views/settings/pages/AppearancePage.vue index 7deccf7..8d99f98 100644 --- a/frontend/src/views/settings/pages/AppearancePage.vue +++ b/frontend/src/views/settings/pages/AppearancePage.vue @@ -16,8 +16,8 @@ const themeStore = useThemeStore(); // 添加临时颜色状态 const tempColors = ref({ - darkTheme: { ...configStore.config.appearance.customTheme?.darkTheme || defaultDarkColors }, - lightTheme: { ...configStore.config.appearance.customTheme?.lightTheme || defaultLightColors } + darkTheme: { ...defaultDarkColors }, + lightTheme: { ...defaultLightColors } }); // 标记是否有未保存的更改 @@ -71,9 +71,9 @@ const currentThemeMode = computed(() => { return isDark ? 'dark' : 'light'; }); -// 监听配置变更,更新临时颜色 +// 监听主题颜色变更,更新临时颜色 watch( - () => configStore.config.appearance.customTheme, + () => themeStore.themeColors, (newValue) => { if (!hasUnsavedChanges.value) { tempColors.value = { diff --git a/internal/models/config.go b/internal/models/config.go index d2faa00..4406151 100644 --- a/internal/models/config.go +++ b/internal/models/config.go @@ -101,9 +101,8 @@ type EditingConfig struct { // AppearanceConfig 外观设置配置 type AppearanceConfig struct { - Language LanguageType `json:"language"` // 界面语言 - SystemTheme SystemThemeType `json:"systemTheme"` // 系统界面主题 - CustomTheme CustomThemeConfig `json:"customTheme"` // 自定义主题配置 + Language LanguageType `json:"language"` // 界面语言 + SystemTheme SystemThemeType `json:"systemTheme"` // 系统界面主题 } // UpdatesConfig 更新设置配置 @@ -171,7 +170,6 @@ func NewDefaultAppConfig() *AppConfig { Appearance: AppearanceConfig{ Language: LangEnUS, SystemTheme: SystemThemeAuto, - CustomTheme: *NewDefaultCustomThemeConfig(), }, Updates: UpdatesConfig{ Version: "1.3.0", diff --git a/internal/models/theme.go b/internal/models/theme.go index b4260e7..4df4756 100644 --- a/internal/models/theme.go +++ b/internal/models/theme.go @@ -1,5 +1,20 @@ package models +import ( + "database/sql/driver" + "encoding/json" + "fmt" + "time" +) + +// ThemeType 主题类型枚举 +type ThemeType string + +const ( + ThemeTypeDark ThemeType = "dark" + ThemeTypeLight ThemeType = "light" +) + // ThemeColorConfig 主题颜色配置 type ThemeColorConfig struct { // 基础色调 @@ -36,15 +51,44 @@ type ThemeColorConfig struct { MatchingBracket string `json:"matchingBracket"` // 匹配括号 } -// CustomThemeConfig 自定义主题配置 -type CustomThemeConfig struct { - DarkTheme ThemeColorConfig `json:"darkTheme"` // 深色主题配置 - LightTheme ThemeColorConfig `json:"lightTheme"` // 浅色主题配置 +// Theme 主题数据库模型 +type Theme struct { + ID int `db:"id" json:"id"` + Name string `db:"name" json:"name"` + Type ThemeType `db:"type" json:"type"` + Colors ThemeColorConfig `db:"colors" json:"colors"` + IsDefault bool `db:"is_default" json:"isDefault"` + CreatedAt time.Time `db:"created_at" json:"createdAt"` + UpdatedAt time.Time `db:"updated_at" json:"updatedAt"` +} + +// Value 实现 driver.Valuer 接口,用于将 ThemeColorConfig 存储到数据库 +func (tc ThemeColorConfig) Value() (driver.Value, error) { + return json.Marshal(tc) +} + +// Scan 实现 sql.Scanner 接口,用于从数据库读取 ThemeColorConfig +func (tc *ThemeColorConfig) Scan(value interface{}) error { + if value == nil { + return nil + } + + var bytes []byte + switch v := value.(type) { + case []byte: + bytes = v + case string: + bytes = []byte(v) + default: + return fmt.Errorf("cannot scan %T into ThemeColorConfig", value) + } + + return json.Unmarshal(bytes, tc) } // NewDefaultDarkTheme 创建默认深色主题配置 -func NewDefaultDarkTheme() ThemeColorConfig { - return ThemeColorConfig{ +func NewDefaultDarkTheme() *ThemeColorConfig { + return &ThemeColorConfig{ // 基础色调 Background: "#252B37", BackgroundSecondary: "#213644", @@ -81,8 +125,8 @@ func NewDefaultDarkTheme() ThemeColorConfig { } // NewDefaultLightTheme 创建默认浅色主题配置 -func NewDefaultLightTheme() ThemeColorConfig { - return ThemeColorConfig{ +func NewDefaultLightTheme() *ThemeColorConfig { + return &ThemeColorConfig{ // 基础色调 Background: "#ffffff", BackgroundSecondary: "#f1faf1", @@ -117,11 +161,3 @@ func NewDefaultLightTheme() ThemeColorConfig { MatchingBracket: "#00000019", } } - -// NewDefaultCustomThemeConfig 创建默认自定义主题配置 -func NewDefaultCustomThemeConfig() *CustomThemeConfig { - return &CustomThemeConfig{ - DarkTheme: NewDefaultDarkTheme(), - LightTheme: NewDefaultLightTheme(), - } -} diff --git a/internal/services/database_service.go b/internal/services/database_service.go index f0154d4..b41e4d4 100644 --- a/internal/services/database_service.go +++ b/internal/services/database_service.go @@ -63,6 +63,19 @@ CREATE TABLE IF NOT EXISTS key_bindings ( updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(command, extension) )` + + // Themes table + sqlCreateThemesTable = ` +CREATE TABLE IF NOT EXISTS themes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + type TEXT NOT NULL, + colors TEXT NOT NULL, + is_default INTEGER NOT NULL DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(type, is_default) +)` ) // ColumnInfo 存储列的信息 @@ -112,6 +125,8 @@ func (ds *DatabaseService) registerAllModels() { ds.RegisterModel("extensions", &models.Extension{}) // 快捷键表 ds.RegisterModel("key_bindings", &models.KeyBinding{}) + // 主题表 + ds.RegisterModel("themes", &models.Theme{}) } // ServiceStartup initializes the service when the application starts @@ -181,6 +196,7 @@ func (ds *DatabaseService) createTables() error { sqlCreateDocumentsTable, sqlCreateExtensionsTable, sqlCreateKeyBindingsTable, + sqlCreateThemesTable, } for _, table := range tables { @@ -204,6 +220,9 @@ func (ds *DatabaseService) createIndexes() error { `CREATE INDEX IF NOT EXISTS idx_key_bindings_command ON key_bindings(command)`, `CREATE INDEX IF NOT EXISTS idx_key_bindings_extension ON key_bindings(extension)`, `CREATE INDEX IF NOT EXISTS idx_key_bindings_enabled ON key_bindings(enabled)`, + // Themes indexes + `CREATE INDEX IF NOT EXISTS idx_themes_type ON themes(type)`, + `CREATE INDEX IF NOT EXISTS idx_themes_is_default ON themes(is_default)`, } for _, index := range indexes { diff --git a/internal/services/service_manager.go b/internal/services/service_manager.go index 8f40bac..7fa2277 100644 --- a/internal/services/service_manager.go +++ b/internal/services/service_manager.go @@ -23,6 +23,7 @@ type ServiceManager struct { startupService *StartupService selfUpdateService *SelfUpdateService translationService *TranslationService + themeService *ThemeService BackupService *BackupService logger *log.LogService } @@ -77,6 +78,9 @@ func NewServiceManager() *ServiceManager { // 初始化翻译服务 translationService := NewTranslationService(logger) + // 初始化主题服务 + themeService := NewThemeService(databaseService, logger) + // 初始化备份服务 backupService := NewBackupService(configService, databaseService, logger) @@ -119,6 +123,7 @@ func NewServiceManager() *ServiceManager { startupService: startupService, selfUpdateService: selfUpdateService, translationService: translationService, + themeService: themeService, BackupService: backupService, logger: logger, } @@ -141,6 +146,7 @@ func (sm *ServiceManager) GetServices() []application.Service { application.NewService(sm.startupService), application.NewService(sm.selfUpdateService), application.NewService(sm.translationService), + application.NewService(sm.themeService), application.NewService(sm.BackupService), } return services @@ -210,3 +216,8 @@ func (sm *ServiceManager) GetWindowService() *WindowService { func (sm *ServiceManager) GetDocumentService() *DocumentService { return sm.documentService } + +// GetThemeService 获取主题服务实例 +func (sm *ServiceManager) GetThemeService() *ThemeService { + return sm.themeService +} diff --git a/internal/services/theme_service.go b/internal/services/theme_service.go new file mode 100644 index 0000000..79453cd --- /dev/null +++ b/internal/services/theme_service.go @@ -0,0 +1,275 @@ +package services + +import ( + "context" + "database/sql" + "fmt" + "time" + "voidraft/internal/models" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/services/log" +) + +// ThemeService 主题服务 +type ThemeService struct { + databaseService *DatabaseService + logger *log.LogService + ctx context.Context +} + +// NewThemeService 创建新的主题服务 +func NewThemeService(databaseService *DatabaseService, logger *log.LogService) *ThemeService { + if logger == nil { + logger = log.New() + } + + return &ThemeService{ + databaseService: databaseService, + logger: logger, + } +} + +// ServiceStartup 服务启动时初始化 +func (ts *ThemeService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + ts.ctx = ctx + + // 初始化默认主题 + return ts.initializeDefaultThemes() +} + +// getDB 获取数据库连接 +func (ts *ThemeService) getDB() *sql.DB { + return ts.databaseService.db +} + +// initializeDefaultThemes 初始化默认主题 +func (ts *ThemeService) initializeDefaultThemes() error { + db := ts.getDB() + if db == nil { + return fmt.Errorf("database not available") + } + + // 检查是否已存在默认主题 + var count int + err := db.QueryRow("SELECT COUNT(*) FROM themes WHERE is_default = 1").Scan(&count) + if err != nil { + return fmt.Errorf("failed to check existing themes: %w", err) + } + + if count > 0 { + return nil // 默认主题已存在 + } + + // 创建默认深色主题 + darkTheme := &models.Theme{ + Name: "Default Dark", + Type: models.ThemeTypeDark, + Colors: *models.NewDefaultDarkTheme(), + IsDefault: true, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + // 创建默认浅色主题 + lightTheme := &models.Theme{ + Name: "Default Light", + Type: models.ThemeTypeLight, + Colors: *models.NewDefaultLightTheme(), + IsDefault: true, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + // 插入默认主题 + if _, err := ts.CreateTheme(darkTheme); err != nil { + return fmt.Errorf("failed to create default dark theme: %w", err) + } + + if _, err := ts.CreateTheme(lightTheme); err != nil { + return fmt.Errorf("failed to create default light theme: %w", err) + } + + return nil +} + +// GetDefaultThemes 获取默认主题 +func (ts *ThemeService) GetDefaultThemes() (map[models.ThemeType]*models.Theme, error) { + query := ` + SELECT id, name, type, colors, is_default, created_at, updated_at + FROM themes + WHERE is_default = 1 + ORDER BY type + ` + + db := ts.getDB() + rows, err := db.Query(query) + if err != nil { + return nil, fmt.Errorf("failed to query default themes: %w", err) + } + defer rows.Close() + + themes := make(map[models.ThemeType]*models.Theme) + for rows.Next() { + theme := &models.Theme{} + err := rows.Scan( + &theme.ID, + &theme.Name, + &theme.Type, + &theme.Colors, + &theme.IsDefault, + &theme.CreatedAt, + &theme.UpdatedAt, + ) + if err != nil { + return nil, fmt.Errorf("failed to scan theme: %w", err) + } + themes[theme.Type] = theme + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("failed to iterate themes: %w", err) + } + + return themes, nil +} + +// GetThemeByType 根据类型获取默认主题 +func (ts *ThemeService) GetThemeByType(themeType models.ThemeType) (*models.Theme, error) { + query := ` + SELECT id, name, type, colors, is_default, created_at, updated_at + FROM themes + WHERE type = ? AND is_default = 1 + LIMIT 1 + ` + + theme := &models.Theme{} + db := ts.getDB() + err := db.QueryRow(query, themeType).Scan( + &theme.ID, + &theme.Name, + &theme.Type, + &theme.Colors, + &theme.IsDefault, + &theme.CreatedAt, + &theme.UpdatedAt, + ) + + if err != nil { + if err == sql.ErrNoRows { + return nil, fmt.Errorf("no default theme found for type: %s", themeType) + } + return nil, fmt.Errorf("failed to get theme by type: %w", err) + } + + return theme, nil +} + +// UpdateThemeColors 更新主题颜色 +func (ts *ThemeService) UpdateThemeColors(themeType models.ThemeType, colors models.ThemeColorConfig) error { + query := ` + UPDATE themes + SET colors = ?, updated_at = ? + WHERE type = ? AND is_default = 1 + ` + + db := ts.getDB() + _, err := db.Exec(query, colors, time.Now(), themeType) + if err != nil { + return fmt.Errorf("failed to update theme colors: %w", err) + } + + return nil +} + +// ResetThemeColors 重置主题颜色为默认值 +func (ts *ThemeService) ResetThemeColors(themeType models.ThemeType) error { + var defaultColors models.ThemeColorConfig + + switch themeType { + case models.ThemeTypeDark: + defaultColors = *models.NewDefaultDarkTheme() + case models.ThemeTypeLight: + defaultColors = *models.NewDefaultLightTheme() + default: + return fmt.Errorf("unknown theme type: %s", themeType) + } + + return ts.UpdateThemeColors(themeType, defaultColors) +} + +// CreateTheme 创建新主题 +func (ts *ThemeService) CreateTheme(theme *models.Theme) (*models.Theme, error) { + query := ` + INSERT INTO themes (name, type, colors, is_default, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?) + ` + + db := ts.getDB() + result, err := db.Exec( + query, + theme.Name, + theme.Type, + theme.Colors, + theme.IsDefault, + theme.CreatedAt, + theme.UpdatedAt, + ) + + if err != nil { + return nil, fmt.Errorf("failed to create theme: %w", err) + } + + id, err := result.LastInsertId() + if err != nil { + return nil, fmt.Errorf("failed to get theme ID: %w", err) + } + + theme.ID = int(id) + return theme, nil +} + +// GetAllThemes 获取所有主题 +func (ts *ThemeService) GetAllThemes() ([]*models.Theme, error) { + query := ` + SELECT id, name, type, colors, is_default, created_at, updated_at + FROM themes + ORDER BY is_default DESC, created_at ASC + ` + + db := ts.getDB() + rows, err := db.Query(query) + if err != nil { + return nil, fmt.Errorf("failed to query themes: %w", err) + } + defer rows.Close() + + var themes []*models.Theme + for rows.Next() { + theme := &models.Theme{} + err := rows.Scan( + &theme.ID, + &theme.Name, + &theme.Type, + &theme.Colors, + &theme.IsDefault, + &theme.CreatedAt, + &theme.UpdatedAt, + ) + if err != nil { + return nil, fmt.Errorf("failed to scan theme: %w", err) + } + themes = append(themes, theme) + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("failed to iterate themes: %w", err) + } + + return themes, nil +} + +// ServiceShutdown 服务关闭 +func (ts *ThemeService) ServiceShutdown() error { + return nil +}