Modify the theme storage schema

This commit is contained in:
2025-08-17 19:34:35 +08:00
parent 5b88efcfbe
commit 873a3c0e60
11 changed files with 587 additions and 239 deletions

View File

@@ -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",

View File

@@ -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(),
}
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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
}