♻️ Refactor theme module

This commit is contained in:
2025-11-20 23:07:12 +08:00
parent 5584a46ca2
commit fc7c162e2f
8 changed files with 388 additions and 1465 deletions

View File

@@ -14,67 +14,21 @@ const (
ThemeTypeLight ThemeType = "light"
)
// ThemeColorConfig 主题颜色配置(与前端 ThemeColors 接口保持一致)
type ThemeColorConfig struct {
// 主题基本信息
Name string `json:"name"` // 主题名称
Dark bool `json:"dark"` // 是否为深色主题
// 基础色调
Background string `json:"background"` // 主背景色
BackgroundSecondary string `json:"backgroundSecondary"` // 次要背景色(用于代码块交替背景)
Surface string `json:"surface"` // 面板背景
DropdownBackground string `json:"dropdownBackground"` // 下拉菜单背景
DropdownBorder string `json:"dropdownBorder"` // 下拉菜单边框
// 文本颜色
Foreground string `json:"foreground"` // 主文本色
ForegroundSecondary string `json:"foregroundSecondary"` // 次要文本色
Comment string `json:"comment"` // 注释色
// 语法高亮色 - 核心
Keyword string `json:"keyword"` // 关键字
String string `json:"string"` // 字符串
Function string `json:"function"` // 函数名
Number string `json:"number"` // 数字
Operator string `json:"operator"` // 操作符
Variable string `json:"variable"` // 变量
Type string `json:"type"` // 类型
// 语法高亮色 - 扩展
Constant string `json:"constant"` // 常量
Storage string `json:"storage"` // 存储类型(如 static, const
Parameter string `json:"parameter"` // 参数
Class string `json:"class"` // 类名
Heading string `json:"heading"` // 标题Markdown等
Invalid string `json:"invalid"` // 无效内容/错误
Regexp string `json:"regexp"` // 正则表达式
// 界面元素
Cursor string `json:"cursor"` // 光标
Selection string `json:"selection"` // 选中背景
SelectionBlur string `json:"selectionBlur"` // 失焦选中背景
ActiveLine string `json:"activeLine"` // 当前行高亮
LineNumber string `json:"lineNumber"` // 行号
ActiveLineNumber string `json:"activeLineNumber"` // 活动行号颜色
// 边框和分割线
BorderColor string `json:"borderColor"` // 边框色
BorderLight string `json:"borderLight"` // 浅色边框
// 搜索和匹配
SearchMatch string `json:"searchMatch"` // 搜索匹配
MatchingBracket string `json:"matchingBracket"` // 匹配括号
}
// ThemeColorConfig 使用与前端 ThemeColors 相同的结构,存储任意主题键值
type ThemeColorConfig map[string]interface{}
// Value 实现 driver.Valuer 接口,用于将 ThemeColorConfig 存储到数据库
func (tc ThemeColorConfig) Value() (driver.Value, error) {
if tc == nil {
return json.Marshal(map[string]interface{}{})
}
return json.Marshal(tc)
}
// Scan 实现 sql.Scanner 接口,用于从数据库读取 ThemeColorConfig
func (tc *ThemeColorConfig) Scan(value interface{}) error {
if value == nil {
*tc = ThemeColorConfig{}
return nil
}
@@ -88,7 +42,13 @@ func (tc *ThemeColorConfig) Scan(value interface{}) error {
return fmt.Errorf("cannot scan %T into ThemeColorConfig", value)
}
return json.Unmarshal(bytes, tc)
var data map[string]interface{}
if err := json.Unmarshal(bytes, &data); err != nil {
return err
}
*tc = data
return nil
}
// Theme 主题数据库模型
@@ -101,679 +61,3 @@ type Theme struct {
CreatedAt string `db:"created_at" json:"createdAt"`
UpdatedAt string `db:"updated_at" json:"updatedAt"`
}
// NewDefaultDarkTheme 创建默认深色主题配置(与前端 defaultDarkColors 完全一致)
func NewDefaultDarkTheme() *ThemeColorConfig {
return &ThemeColorConfig{
// 主题信息
Name: "default-dark",
Dark: true,
// 基础色调
Background: "#252B37",
BackgroundSecondary: "#213644",
Surface: "#474747",
DropdownBackground: "#252B37",
DropdownBorder: "#ffffff19",
// 文本颜色
Foreground: "#9BB586",
ForegroundSecondary: "#9c9c9c",
Comment: "#6272a4",
// 语法高亮色 - 核心
Keyword: "#ff79c6",
String: "#f1fa8c",
Function: "#50fa7b",
Number: "#bd93f9",
Operator: "#ff79c6",
Variable: "#8fbcbb",
Type: "#8be9fd",
// 语法高亮色 - 扩展
Constant: "#bd93f9",
Storage: "#ff79c6",
Parameter: "#8fbcbb",
Class: "#8be9fd",
Heading: "#ff79c6",
Invalid: "#d30102",
Regexp: "#f1fa8c",
// 界面元素
Cursor: "#ffffff",
Selection: "#0865a9",
SelectionBlur: "#225377",
ActiveLine: "#ffffff0a",
LineNumber: "#ffffff26",
ActiveLineNumber: "#ffffff99",
// 边框和分割线
BorderColor: "#1e222a",
BorderLight: "#ffffff19",
// 搜索和匹配
SearchMatch: "#8fbcbb",
MatchingBracket: "#ffffff19",
}
}
// NewDefaultLightTheme 创建默认浅色主题配置(与前端 defaultLightColors 完全一致)
func NewDefaultLightTheme() *ThemeColorConfig {
return &ThemeColorConfig{
// 主题信息
Name: "default-light",
Dark: false,
// 基础色调
Background: "#ffffff",
BackgroundSecondary: "#f1faf1",
Surface: "#f5f5f5",
DropdownBackground: "#ffffff",
DropdownBorder: "#e1e4e8",
// 文本颜色
Foreground: "#444d56",
ForegroundSecondary: "#6a737d",
Comment: "#6a737d",
// 语法高亮色 - 核心
Keyword: "#d73a49",
String: "#032f62",
Function: "#005cc5",
Number: "#005cc5",
Operator: "#d73a49",
Variable: "#24292e",
Type: "#6f42c1",
// 语法高亮色 - 扩展
Constant: "#005cc5",
Storage: "#d73a49",
Parameter: "#24292e",
Class: "#6f42c1",
Heading: "#d73a49",
Invalid: "#cb2431",
Regexp: "#032f62",
// 界面元素
Cursor: "#000000",
Selection: "#77baff",
SelectionBlur: "#b2c2ca",
ActiveLine: "#0000000a",
LineNumber: "#00000040",
ActiveLineNumber: "#000000aa",
// 边框和分割线
BorderColor: "#dfdfdf",
BorderLight: "#0000000c",
// 搜索和匹配
SearchMatch: "#005cc5",
MatchingBracket: "#00000019",
}
}
// NewDraculaTheme 创建 Dracula 深色主题配置
func NewDraculaTheme() *ThemeColorConfig {
return &ThemeColorConfig{
Name: "dracula",
Dark: true,
Background: "#282A36",
BackgroundSecondary: "#323543FF",
Surface: "#282A36",
DropdownBackground: "#282A36",
DropdownBorder: "#191A21",
Foreground: "#F8F8F2",
ForegroundSecondary: "#F8F8F2",
Comment: "#6272A4",
Keyword: "#FF79C6",
String: "#F1FA8C",
Function: "#50FA7B",
Number: "#BD93F9",
Operator: "#FF79C6",
Variable: "#F8F8F2",
Type: "#8BE9FD",
Constant: "#BD93F9",
Storage: "#FF79C6",
Parameter: "#F8F8F2",
Class: "#8BE9FD",
Heading: "#BD93F9",
Invalid: "#FF5555",
Regexp: "#F1FA8C",
Cursor: "#F8F8F2",
Selection: "#44475A",
SelectionBlur: "#44475A",
ActiveLine: "#53576c22",
LineNumber: "#6272A4",
ActiveLineNumber: "#F8F8F2",
BorderColor: "#191A21",
BorderLight: "#F8F8F219",
SearchMatch: "#50FA7B",
MatchingBracket: "#44475A",
}
}
// NewAuraTheme 创建 Aura 深色主题配置
func NewAuraTheme() *ThemeColorConfig {
return &ThemeColorConfig{
Name: "aura",
Dark: true,
Background: "#21202e",
BackgroundSecondary: "#2B2A3BFF",
Surface: "#21202e",
DropdownBackground: "#21202e",
DropdownBorder: "#3b334b",
Foreground: "#edecee",
ForegroundSecondary: "#edecee",
Comment: "#6d6d6d",
Keyword: "#a277ff",
String: "#61ffca",
Function: "#ffca85",
Number: "#61ffca",
Operator: "#a277ff",
Variable: "#edecee",
Type: "#82e2ff",
Constant: "#61ffca",
Storage: "#a277ff",
Parameter: "#edecee",
Class: "#82e2ff",
Heading: "#a277ff",
Invalid: "#ff6767",
Regexp: "#61ffca",
Cursor: "#a277ff",
Selection: "#3d375e7f",
SelectionBlur: "#3d375e7f",
ActiveLine: "#4d4b6622",
LineNumber: "#a394f033",
ActiveLineNumber: "#cdccce",
BorderColor: "#3b334b",
BorderLight: "#edecee19",
SearchMatch: "#61ffca",
MatchingBracket: "#a394f033",
}
}
// NewGitHubDarkTheme 创建 GitHub Dark 主题配置
func NewGitHubDarkTheme() *ThemeColorConfig {
return &ThemeColorConfig{
Name: "github-dark",
Dark: true,
Background: "#24292e",
BackgroundSecondary: "#2E343BFF",
Surface: "#24292e",
DropdownBackground: "#24292e",
DropdownBorder: "#1b1f23",
Foreground: "#d1d5da",
ForegroundSecondary: "#d1d5da",
Comment: "#6a737d",
Keyword: "#f97583",
String: "#9ecbff",
Function: "#79b8ff",
Number: "#79b8ff",
Operator: "#f97583",
Variable: "#ffab70",
Type: "#79b8ff",
Constant: "#79b8ff",
Storage: "#f97583",
Parameter: "#e1e4e8",
Class: "#b392f0",
Heading: "#79b8ff",
Invalid: "#f97583",
Regexp: "#9ecbff",
Cursor: "#c8e1ff",
Selection: "#3392FF44",
SelectionBlur: "#3392FF44",
ActiveLine: "#4d566022",
LineNumber: "#444d56",
ActiveLineNumber: "#e1e4e8",
BorderColor: "#1b1f23",
BorderLight: "#d1d5da19",
SearchMatch: "#79b8ff",
MatchingBracket: "#17E5E650",
}
}
// NewMaterialDarkTheme 创建 Material Dark 主题配置
func NewMaterialDarkTheme() *ThemeColorConfig {
return &ThemeColorConfig{
Name: "material-dark",
Dark: true,
Background: "#263238",
BackgroundSecondary: "#2D3E46FF",
Surface: "#263238",
DropdownBackground: "#263238",
DropdownBorder: "#FFFFFF10",
Foreground: "#EEFFFF",
ForegroundSecondary: "#EEFFFF",
Comment: "#546E7A",
Keyword: "#C792EA",
String: "#C3E88D",
Function: "#82AAFF",
Number: "#F78C6C",
Operator: "#C792EA",
Variable: "#EEFFFF",
Type: "#B2CCD6",
Constant: "#F78C6C",
Storage: "#C792EA",
Parameter: "#EEFFFF",
Class: "#FFCB6B",
Heading: "#C3E88D",
Invalid: "#FF5370",
Regexp: "#89DDFF",
Cursor: "#FFCC00",
Selection: "#80CBC420",
SelectionBlur: "#80CBC420",
ActiveLine: "#4c616c22",
LineNumber: "#37474F",
ActiveLineNumber: "#607a86",
BorderColor: "#FFFFFF10",
BorderLight: "#EEFFFF19",
SearchMatch: "#82AAFF",
MatchingBracket: "#263238",
}
}
// NewOneDarkTheme 创建 One Dark 主题配置
func NewOneDarkTheme() *ThemeColorConfig {
return &ThemeColorConfig{
Name: "one-dark",
Dark: true,
Background: "#282c34",
BackgroundSecondary: "#313949FF",
Surface: "#353a42",
DropdownBackground: "#21252b",
DropdownBorder: "#7d8799",
Foreground: "#abb2bf",
ForegroundSecondary: "#7d8799",
Comment: "#7d8799",
Keyword: "#c678dd",
String: "#98c379",
Function: "#61afef",
Number: "#e5c07b",
Operator: "#56b6c2",
Variable: "#e06c75",
Type: "#e5c07b",
Constant: "#d19a66",
Storage: "#c678dd",
Parameter: "#e06c75",
Class: "#e5c07b",
Heading: "#e06c75",
Invalid: "#ffffff",
Regexp: "#56b6c2",
Cursor: "#528bff",
Selection: "#3E4451",
SelectionBlur: "#3E4451",
ActiveLine: "#6699ff0b",
LineNumber: "#7d8799",
ActiveLineNumber: "#abb2bf",
BorderColor: "#21252b",
BorderLight: "#abb2bf19",
SearchMatch: "#61afef",
MatchingBracket: "#bad0f847",
}
}
// NewSolarizedDarkTheme 创建 Solarized Dark 主题配置
func NewSolarizedDarkTheme() *ThemeColorConfig {
return &ThemeColorConfig{
Name: "solarized-dark",
Dark: true,
Background: "#002B36",
BackgroundSecondary: "#003643FF",
Surface: "#002B36",
DropdownBackground: "#002B36",
DropdownBorder: "#2AA19899",
Foreground: "#93A1A1",
ForegroundSecondary: "#93A1A1",
Comment: "#586E75",
Keyword: "#859900",
String: "#2AA198",
Function: "#268BD2",
Number: "#D33682",
Operator: "#859900",
Variable: "#268BD2",
Type: "#CB4B16",
Constant: "#CB4B16",
Storage: "#93A1A1",
Parameter: "#268BD2",
Class: "#CB4B16",
Heading: "#268BD2",
Invalid: "#DC322F",
Regexp: "#DC322F",
Cursor: "#D30102",
Selection: "#274642",
SelectionBlur: "#274642",
ActiveLine: "#005b7022",
LineNumber: "#93A1A1",
ActiveLineNumber: "#949494",
BorderColor: "#073642",
BorderLight: "#93A1A119",
SearchMatch: "#2AA198",
MatchingBracket: "#073642",
}
}
// NewTokyoNightTheme 创建 Tokyo Night 主题配置
func NewTokyoNightTheme() *ThemeColorConfig {
return &ThemeColorConfig{
Name: "tokyo-night",
Dark: true,
Background: "#1a1b26",
BackgroundSecondary: "#272839FF",
Surface: "#1a1b26",
DropdownBackground: "#1a1b26",
DropdownBorder: "#787c99",
Foreground: "#787c99",
ForegroundSecondary: "#787c99",
Comment: "#444b6a",
Keyword: "#bb9af7",
String: "#9ece6a",
Function: "#7aa2f7",
Number: "#ff9e64",
Operator: "#bb9af7",
Variable: "#c0caf5",
Type: "#0db9d7",
Constant: "#bb9af7",
Storage: "#bb9af7",
Parameter: "#c0caf5",
Class: "#c0caf5",
Heading: "#89ddff",
Invalid: "#ff5370",
Regexp: "#b4f9f8",
Cursor: "#c0caf5",
Selection: "#515c7e40",
SelectionBlur: "#515c7e40",
ActiveLine: "#43455c22",
LineNumber: "#363b54",
ActiveLineNumber: "#737aa2",
BorderColor: "#16161e",
BorderLight: "#787c9919",
SearchMatch: "#7aa2f7",
MatchingBracket: "#16161e",
}
}
// NewTokyoNightStormTheme 创建 Tokyo Night Storm 主题配置
func NewTokyoNightStormTheme() *ThemeColorConfig {
return &ThemeColorConfig{
Name: "tokyo-night-storm",
Dark: true,
Background: "#24283b",
BackgroundSecondary: "#2B3151FF",
Surface: "#24283b",
DropdownBackground: "#24283b",
DropdownBorder: "#7982a9",
Foreground: "#7982a9",
ForegroundSecondary: "#7982a9",
Comment: "#565f89",
Keyword: "#bb9af7",
String: "#9ece6a",
Function: "#7aa2f7",
Number: "#ff9e64",
Operator: "#bb9af7",
Variable: "#c0caf5",
Type: "#2ac3de",
Constant: "#bb9af7",
Storage: "#bb9af7",
Parameter: "#c0caf5",
Class: "#c0caf5",
Heading: "#89ddff",
Invalid: "#ff5370",
Regexp: "#b4f9f8",
Cursor: "#c0caf5",
Selection: "#6f7bb630",
SelectionBlur: "#6f7bb630",
ActiveLine: "#4d547722",
LineNumber: "#3b4261",
ActiveLineNumber: "#737aa2",
BorderColor: "#1f2335",
BorderLight: "#7982a919",
SearchMatch: "#7aa2f7",
MatchingBracket: "#1f2335",
}
}
// 浅色主题预设配置
// NewGitHubLightTheme 创建 GitHub Light 主题配置
func NewGitHubLightTheme() *ThemeColorConfig {
return &ThemeColorConfig{
Name: "github-light",
Dark: false,
Background: "#fff",
BackgroundSecondary: "#f1faf1",
Surface: "#fff",
DropdownBackground: "#fff",
DropdownBorder: "#e1e4e8",
Foreground: "#444d56",
ForegroundSecondary: "#444d56",
Comment: "#6a737d",
Keyword: "#d73a49",
String: "#032f62",
Function: "#005cc5",
Number: "#005cc5",
Operator: "#d73a49",
Variable: "#e36209",
Type: "#005cc5",
Constant: "#005cc5",
Storage: "#d73a49",
Parameter: "#24292e",
Class: "#6f42c1",
Heading: "#005cc5",
Invalid: "#cb2431",
Regexp: "#032f62",
Cursor: "#044289",
Selection: "#0366d625",
SelectionBlur: "#0366d625",
ActiveLine: "#c6c6c622",
LineNumber: "#1b1f234d",
ActiveLineNumber: "#24292e",
BorderColor: "#e1e4e8",
BorderLight: "#444d5619",
SearchMatch: "#005cc5",
MatchingBracket: "#34d05840",
}
}
// NewMaterialLightTheme 创建 Material Light 主题配置
func NewMaterialLightTheme() *ThemeColorConfig {
return &ThemeColorConfig{
Name: "material-light",
Dark: false,
Background: "#FAFAFA",
BackgroundSecondary: "#f1faf1",
Surface: "#FAFAFA",
DropdownBackground: "#FAFAFA",
DropdownBorder: "#00000010",
Foreground: "#90A4AE",
ForegroundSecondary: "#90A4AE",
Comment: "#90A4AE",
Keyword: "#7C4DFF",
String: "#91B859",
Function: "#6182B8",
Number: "#F76D47",
Operator: "#7C4DFF",
Variable: "#90A4AE",
Type: "#8796B0",
Constant: "#F76D47",
Storage: "#7C4DFF",
Parameter: "#90A4AE",
Class: "#FFB62C",
Heading: "#91B859",
Invalid: "#E53935",
Regexp: "#39ADB5",
Cursor: "#272727",
Selection: "#80CBC440",
SelectionBlur: "#80CBC440",
ActiveLine: "#c2c2c222",
LineNumber: "#CFD8DC",
ActiveLineNumber: "#7E939E",
BorderColor: "#00000010",
BorderLight: "#90A4AE19",
SearchMatch: "#6182B8",
MatchingBracket: "#FAFAFA",
}
}
// NewSolarizedLightTheme 创建 Solarized Light 主题配置
func NewSolarizedLightTheme() *ThemeColorConfig {
return &ThemeColorConfig{
Name: "solarized-light",
Dark: false,
Background: "#FDF6E3",
BackgroundSecondary: "#FFEEBCD4",
Surface: "#FDF6E3",
DropdownBackground: "#FDF6E3",
DropdownBorder: "#D3AF86",
Foreground: "#586E75",
ForegroundSecondary: "#586E75",
Comment: "#93A1A1",
Keyword: "#859900",
String: "#2AA198",
Function: "#268BD2",
Number: "#D33682",
Operator: "#859900",
Variable: "#268BD2",
Type: "#CB4B16",
Constant: "#CB4B16",
Storage: "#586E75",
Parameter: "#268BD2",
Class: "#CB4B16",
Heading: "#268BD2",
Invalid: "#DC322F",
Regexp: "#DC322F",
Cursor: "#657B83",
Selection: "#EEE8D5",
SelectionBlur: "#EEE8D5",
ActiveLine: "#d5bd5c22",
LineNumber: "#586E75",
ActiveLineNumber: "#567983",
BorderColor: "#EEE8D5",
BorderLight: "#586E7519",
SearchMatch: "#268BD2",
MatchingBracket: "#EEE8D5",
}
}
// NewTokyoNightDayTheme 创建 Tokyo Night Day 主题配置
func NewTokyoNightDayTheme() *ThemeColorConfig {
return &ThemeColorConfig{
Name: "tokyo-night-day",
Dark: false,
Background: "#e1e2e7",
BackgroundSecondary: "#D2D8EFFF",
Surface: "#e1e2e7",
DropdownBackground: "#e1e2e7",
DropdownBorder: "#6a6f8e",
Foreground: "#6a6f8e",
ForegroundSecondary: "#6a6f8e",
Comment: "#9da3c2",
Keyword: "#9854f1",
String: "#587539",
Function: "#2e7de9",
Number: "#b15c00",
Operator: "#9854f1",
Variable: "#3760bf",
Type: "#07879d",
Constant: "#9854f1",
Storage: "#9854f1",
Parameter: "#3760bf",
Class: "#3760bf",
Heading: "#006a83",
Invalid: "#ff3e64",
Regexp: "#2e5857",
Cursor: "#3760bf",
Selection: "#8591b840",
SelectionBlur: "#8591b840",
ActiveLine: "#a7aaba22",
LineNumber: "#b3b6cd",
ActiveLineNumber: "#68709a",
BorderColor: "#e9e9ec",
BorderLight: "#6a6f8e19",
SearchMatch: "#2e7de9",
MatchingBracket: "#e9e9ec",
}
}

View File

@@ -5,6 +5,7 @@ import (
"database/sql"
"errors"
"fmt"
"strings"
"time"
"voidraft/internal/models"
@@ -31,12 +32,10 @@ func NewThemeService(databaseService *DatabaseService, logger *log.LogService) *
}
}
// ServiceStartup 服务启动时初始化
// ServiceStartup 服务启动
func (ts *ThemeService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
ts.ctx = ctx
// 初始化默认主题
return ts.initializeDefaultThemes()
return nil
}
// getDB 获取数据库连接
@@ -44,141 +43,27 @@ func (ts *ThemeService) getDB() *sql.DB {
return ts.databaseService.db
}
// initializeDefaultThemes 初始化所有预设主题
func (ts *ThemeService) initializeDefaultThemes() error {
// GetThemeByName 通过名称获取主题覆盖,若不存在则返回 nil
func (ts *ThemeService) GetThemeByName(name string) (*models.Theme, error) {
db := ts.getDB()
if db == nil {
return fmt.Errorf("database not available")
return nil, fmt.Errorf("database not available")
}
// 获取所有已存在的主题名称
existingThemes := make(map[string]bool)
rows, err := db.Query("SELECT name FROM themes")
if err != nil {
return fmt.Errorf("failed to query existing themes: %w", err)
}
defer rows.Close()
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
return fmt.Errorf("failed to scan theme name: %w", err)
}
existingThemes[name] = true
trimmed := strings.TrimSpace(name)
if trimmed == "" {
return nil, fmt.Errorf("theme name cannot be empty")
}
// 定义所有预设主题配置
now := time.Now().Format("2006-01-02 15:04:05")
presetThemes := []struct {
config *models.ThemeColorConfig
themeType models.ThemeType
isDefault bool
}{
// 默认主题
{models.NewDefaultDarkTheme(), models.ThemeTypeDark, true},
{models.NewDefaultLightTheme(), models.ThemeTypeLight, true},
// 深色主题预设
{models.NewDraculaTheme(), models.ThemeTypeDark, false},
{models.NewAuraTheme(), models.ThemeTypeDark, false},
{models.NewGitHubDarkTheme(), models.ThemeTypeDark, false},
{models.NewMaterialDarkTheme(), models.ThemeTypeDark, false},
{models.NewOneDarkTheme(), models.ThemeTypeDark, false},
{models.NewSolarizedDarkTheme(), models.ThemeTypeDark, false},
{models.NewTokyoNightTheme(), models.ThemeTypeDark, false},
{models.NewTokyoNightStormTheme(), models.ThemeTypeDark, false},
// 浅色主题预设
{models.NewGitHubLightTheme(), models.ThemeTypeLight, false},
{models.NewMaterialLightTheme(), models.ThemeTypeLight, false},
{models.NewSolarizedLightTheme(), models.ThemeTypeLight, false},
{models.NewTokyoNightDayTheme(), models.ThemeTypeLight, false},
}
// 筛选出需要创建的主题
var themesToCreate []*models.Theme
for _, preset := range presetThemes {
if !existingThemes[preset.config.Name] {
themesToCreate = append(themesToCreate, &models.Theme{
Name: preset.config.Name,
Type: preset.themeType,
Colors: *preset.config,
IsDefault: preset.isDefault,
CreatedAt: now,
UpdatedAt: now,
})
}
}
if len(themesToCreate) == 0 {
return nil
}
// 批量插入主题
tx, err := db.Begin()
if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err)
}
defer tx.Rollback()
stmt, err := tx.Prepare(`
INSERT INTO themes (name, type, colors, is_default, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?)
`)
if err != nil {
return fmt.Errorf("failed to prepare statement: %w", err)
}
defer stmt.Close()
for _, theme := range themesToCreate {
_, err := stmt.Exec(
theme.Name,
theme.Type,
theme.Colors,
theme.IsDefault,
theme.CreatedAt,
theme.UpdatedAt,
)
if err != nil {
return fmt.Errorf("failed to insert theme %s: %w", theme.Name, err)
}
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("failed to commit transaction: %w", err)
}
return nil
}
// GetThemeByID 根据ID或名称获取主题
// 如果 id > 0按ID查询如果 id = 0按名称查询
func (ts *ThemeService) GetThemeByIdOrName(id int, name ...string) (*models.Theme, error) {
var query string
var args []interface{}
if id > 0 {
query = `
SELECT id, name, type, colors, is_default, created_at, updated_at
FROM themes
WHERE id = ?
LIMIT 1
`
args = []interface{}{id}
} else if len(name) > 0 && name[0] != "" {
query = `
SELECT id, name, type, colors, is_default, created_at, updated_at
FROM themes
WHERE name = ?
LIMIT 1
`
args = []interface{}{name[0]}
} else {
return nil, fmt.Errorf("either id or name must be provided")
}
query := `
SELECT id, name, type, colors, is_default, created_at, updated_at
FROM themes
WHERE name = ?
LIMIT 1
`
theme := &models.Theme{}
db := ts.getDB()
err := db.QueryRow(query, args...).Scan(
err := db.QueryRow(query, trimmed).Scan(
&theme.ID,
&theme.Name,
&theme.Type,
@@ -190,133 +75,89 @@ func (ts *ThemeService) GetThemeByIdOrName(id int, name ...string) (*models.Them
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
if id > 0 {
return nil, fmt.Errorf("theme not found with id: %d", id)
}
return nil, fmt.Errorf("theme not found with name: %s", name[0])
return nil, nil
}
return nil, fmt.Errorf("failed to get theme: %w", err)
return nil, fmt.Errorf("failed to query theme: %w", err)
}
return theme, nil
}
// UpdateTheme 更新主题
func (ts *ThemeService) UpdateTheme(id int, colors models.ThemeColorConfig) error {
query := `
UPDATE themes
SET colors = ?, updated_at = ?
WHERE id = ?
`
// UpdateTheme 保存或更新主题覆盖
func (ts *ThemeService) UpdateTheme(name string, colors models.ThemeColorConfig) error {
db := ts.getDB()
result, err := db.Exec(query, colors, time.Now().Format("2006-01-02 15:04:05"), id)
if db == nil {
return fmt.Errorf("database not available")
}
trimmed := strings.TrimSpace(name)
if trimmed == "" {
return fmt.Errorf("theme name cannot be empty")
}
if colors == nil {
colors = models.ThemeColorConfig{}
}
colors["themeName"] = trimmed
themeType := models.ThemeTypeDark
if raw, ok := colors["dark"].(bool); ok && !raw {
themeType = models.ThemeTypeLight
}
now := time.Now().Format("2006-01-02 15:04:05")
existing, err := ts.GetThemeByName(trimmed)
if err != nil {
return err
}
if existing == nil {
_, err = db.Exec(
`INSERT INTO themes (name, type, colors, is_default, created_at, updated_at) VALUES (?, ?, ?, 0, ?, ?)`,
trimmed,
themeType,
colors,
now,
now,
)
if err != nil {
return fmt.Errorf("failed to insert theme: %w", err)
}
return nil
}
_, err = db.Exec(
`UPDATE themes SET type = ?, colors = ?, updated_at = ? WHERE name = ?`,
themeType,
colors,
now,
trimmed,
)
if err != nil {
return fmt.Errorf("failed to update theme: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get rows affected: %w", err)
}
if rowsAffected == 0 {
return fmt.Errorf("theme not found with id: %d", id)
}
return nil
}
// ResetTheme 重置主题为预设配置
func (ts *ThemeService) ResetTheme(id int, name ...string) error {
// 先获取主题信息
theme, err := ts.GetThemeByIdOrName(id, name...)
if err != nil {
return err
}
// 根据主题名称获取预设配置
var presetConfig *models.ThemeColorConfig
switch theme.Name {
// 默认主题
case "default-dark":
presetConfig = models.NewDefaultDarkTheme()
case "default-light":
presetConfig = models.NewDefaultLightTheme()
// 深色主题预设
case "dracula":
presetConfig = models.NewDraculaTheme()
case "aura":
presetConfig = models.NewAuraTheme()
case "github-dark":
presetConfig = models.NewGitHubDarkTheme()
case "material-dark":
presetConfig = models.NewMaterialDarkTheme()
case "one-dark":
presetConfig = models.NewOneDarkTheme()
case "solarized-dark":
presetConfig = models.NewSolarizedDarkTheme()
case "tokyo-night":
presetConfig = models.NewTokyoNightTheme()
case "tokyo-night-storm":
presetConfig = models.NewTokyoNightStormTheme()
// 浅色主题预设
case "github-light":
presetConfig = models.NewGitHubLightTheme()
case "material-light":
presetConfig = models.NewMaterialLightTheme()
case "solarized-light":
presetConfig = models.NewSolarizedLightTheme()
case "tokyo-night-day":
presetConfig = models.NewTokyoNightDayTheme()
default:
return fmt.Errorf("no preset configuration found for theme: %s", theme.Name)
}
return ts.UpdateTheme(id, *presetConfig)
}
// 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, type DESC, name ASC
`
// ResetTheme 删除指定主题的覆盖配置
func (ts *ThemeService) ResetTheme(name string) error {
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 db == nil {
return fmt.Errorf("database not available")
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("failed to iterate themes: %w", err)
trimmed := strings.TrimSpace(name)
if trimmed == "" {
return fmt.Errorf("theme name cannot be empty")
}
return themes, nil
if _, err := db.Exec(`DELETE FROM themes WHERE name = ?`, trimmed); err != nil {
return fmt.Errorf("failed to reset theme: %w", err)
}
return nil
}
// ServiceShutdown 服务关闭