♻️ Refactor theme module
This commit is contained in:
@@ -1170,7 +1170,7 @@ export class Theme {
|
|||||||
this["type"] = ("" as ThemeType);
|
this["type"] = ("" as ThemeType);
|
||||||
}
|
}
|
||||||
if (!("colors" in $$source)) {
|
if (!("colors" in $$source)) {
|
||||||
this["colors"] = (new ThemeColorConfig());
|
this["colors"] = ({} as ThemeColorConfig);
|
||||||
}
|
}
|
||||||
if (!("isDefault" in $$source)) {
|
if (!("isDefault" in $$source)) {
|
||||||
this["isDefault"] = false;
|
this["isDefault"] = false;
|
||||||
@@ -1199,303 +1199,9 @@ export class Theme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ThemeColorConfig 主题颜色配置(与前端 ThemeColors 接口保持一致)
|
* ThemeColorConfig 使用与前端 ThemeColors 相同的结构,存储任意主题键值
|
||||||
*/
|
*/
|
||||||
export class ThemeColorConfig {
|
export type ThemeColorConfig = { [_: string]: any };
|
||||||
/**
|
|
||||||
* 主题基本信息
|
|
||||||
* 主题名称
|
|
||||||
*/
|
|
||||||
"name": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否为深色主题
|
|
||||||
*/
|
|
||||||
"dark": boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 基础色调
|
|
||||||
* 主背景色
|
|
||||||
*/
|
|
||||||
"background": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 次要背景色(用于代码块交替背景)
|
|
||||||
*/
|
|
||||||
"backgroundSecondary": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 面板背景
|
|
||||||
*/
|
|
||||||
"surface": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 下拉菜单背景
|
|
||||||
*/
|
|
||||||
"dropdownBackground": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 下拉菜单边框
|
|
||||||
*/
|
|
||||||
"dropdownBorder": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文本颜色
|
|
||||||
* 主文本色
|
|
||||||
*/
|
|
||||||
"foreground": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 次要文本色
|
|
||||||
*/
|
|
||||||
"foregroundSecondary": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注释色
|
|
||||||
*/
|
|
||||||
"comment": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 语法高亮色 - 核心
|
|
||||||
* 关键字
|
|
||||||
*/
|
|
||||||
"keyword": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 字符串
|
|
||||||
*/
|
|
||||||
"string": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 函数名
|
|
||||||
*/
|
|
||||||
"function": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数字
|
|
||||||
*/
|
|
||||||
"number": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 操作符
|
|
||||||
*/
|
|
||||||
"operator": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 变量
|
|
||||||
*/
|
|
||||||
"variable": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 类型
|
|
||||||
*/
|
|
||||||
"type": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 语法高亮色 - 扩展
|
|
||||||
* 常量
|
|
||||||
*/
|
|
||||||
"constant": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 存储类型(如 static, const)
|
|
||||||
*/
|
|
||||||
"storage": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 参数
|
|
||||||
*/
|
|
||||||
"parameter": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 类名
|
|
||||||
*/
|
|
||||||
"class": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标题(Markdown等)
|
|
||||||
*/
|
|
||||||
"heading": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 无效内容/错误
|
|
||||||
*/
|
|
||||||
"invalid": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 正则表达式
|
|
||||||
*/
|
|
||||||
"regexp": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 界面元素
|
|
||||||
* 光标
|
|
||||||
*/
|
|
||||||
"cursor": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 选中背景
|
|
||||||
*/
|
|
||||||
"selection": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 失焦选中背景
|
|
||||||
*/
|
|
||||||
"selectionBlur": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 当前行高亮
|
|
||||||
*/
|
|
||||||
"activeLine": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 行号
|
|
||||||
*/
|
|
||||||
"lineNumber": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 活动行号颜色
|
|
||||||
*/
|
|
||||||
"activeLineNumber": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 边框和分割线
|
|
||||||
* 边框色
|
|
||||||
*/
|
|
||||||
"borderColor": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 浅色边框
|
|
||||||
*/
|
|
||||||
"borderLight": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 搜索和匹配
|
|
||||||
* 搜索匹配
|
|
||||||
*/
|
|
||||||
"searchMatch": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 匹配括号
|
|
||||||
*/
|
|
||||||
"matchingBracket": string;
|
|
||||||
|
|
||||||
/** Creates a new ThemeColorConfig instance. */
|
|
||||||
constructor($$source: Partial<ThemeColorConfig> = {}) {
|
|
||||||
if (!("name" in $$source)) {
|
|
||||||
this["name"] = "";
|
|
||||||
}
|
|
||||||
if (!("dark" in $$source)) {
|
|
||||||
this["dark"] = false;
|
|
||||||
}
|
|
||||||
if (!("background" in $$source)) {
|
|
||||||
this["background"] = "";
|
|
||||||
}
|
|
||||||
if (!("backgroundSecondary" in $$source)) {
|
|
||||||
this["backgroundSecondary"] = "";
|
|
||||||
}
|
|
||||||
if (!("surface" in $$source)) {
|
|
||||||
this["surface"] = "";
|
|
||||||
}
|
|
||||||
if (!("dropdownBackground" in $$source)) {
|
|
||||||
this["dropdownBackground"] = "";
|
|
||||||
}
|
|
||||||
if (!("dropdownBorder" in $$source)) {
|
|
||||||
this["dropdownBorder"] = "";
|
|
||||||
}
|
|
||||||
if (!("foreground" in $$source)) {
|
|
||||||
this["foreground"] = "";
|
|
||||||
}
|
|
||||||
if (!("foregroundSecondary" in $$source)) {
|
|
||||||
this["foregroundSecondary"] = "";
|
|
||||||
}
|
|
||||||
if (!("comment" in $$source)) {
|
|
||||||
this["comment"] = "";
|
|
||||||
}
|
|
||||||
if (!("keyword" in $$source)) {
|
|
||||||
this["keyword"] = "";
|
|
||||||
}
|
|
||||||
if (!("string" in $$source)) {
|
|
||||||
this["string"] = "";
|
|
||||||
}
|
|
||||||
if (!("function" in $$source)) {
|
|
||||||
this["function"] = "";
|
|
||||||
}
|
|
||||||
if (!("number" in $$source)) {
|
|
||||||
this["number"] = "";
|
|
||||||
}
|
|
||||||
if (!("operator" in $$source)) {
|
|
||||||
this["operator"] = "";
|
|
||||||
}
|
|
||||||
if (!("variable" in $$source)) {
|
|
||||||
this["variable"] = "";
|
|
||||||
}
|
|
||||||
if (!("type" in $$source)) {
|
|
||||||
this["type"] = "";
|
|
||||||
}
|
|
||||||
if (!("constant" in $$source)) {
|
|
||||||
this["constant"] = "";
|
|
||||||
}
|
|
||||||
if (!("storage" in $$source)) {
|
|
||||||
this["storage"] = "";
|
|
||||||
}
|
|
||||||
if (!("parameter" in $$source)) {
|
|
||||||
this["parameter"] = "";
|
|
||||||
}
|
|
||||||
if (!("class" in $$source)) {
|
|
||||||
this["class"] = "";
|
|
||||||
}
|
|
||||||
if (!("heading" in $$source)) {
|
|
||||||
this["heading"] = "";
|
|
||||||
}
|
|
||||||
if (!("invalid" in $$source)) {
|
|
||||||
this["invalid"] = "";
|
|
||||||
}
|
|
||||||
if (!("regexp" in $$source)) {
|
|
||||||
this["regexp"] = "";
|
|
||||||
}
|
|
||||||
if (!("cursor" in $$source)) {
|
|
||||||
this["cursor"] = "";
|
|
||||||
}
|
|
||||||
if (!("selection" in $$source)) {
|
|
||||||
this["selection"] = "";
|
|
||||||
}
|
|
||||||
if (!("selectionBlur" in $$source)) {
|
|
||||||
this["selectionBlur"] = "";
|
|
||||||
}
|
|
||||||
if (!("activeLine" in $$source)) {
|
|
||||||
this["activeLine"] = "";
|
|
||||||
}
|
|
||||||
if (!("lineNumber" in $$source)) {
|
|
||||||
this["lineNumber"] = "";
|
|
||||||
}
|
|
||||||
if (!("activeLineNumber" in $$source)) {
|
|
||||||
this["activeLineNumber"] = "";
|
|
||||||
}
|
|
||||||
if (!("borderColor" in $$source)) {
|
|
||||||
this["borderColor"] = "";
|
|
||||||
}
|
|
||||||
if (!("borderLight" in $$source)) {
|
|
||||||
this["borderLight"] = "";
|
|
||||||
}
|
|
||||||
if (!("searchMatch" in $$source)) {
|
|
||||||
this["searchMatch"] = "";
|
|
||||||
}
|
|
||||||
if (!("matchingBracket" in $$source)) {
|
|
||||||
this["matchingBracket"] = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new ThemeColorConfig instance from a string or object.
|
|
||||||
*/
|
|
||||||
static createFrom($$source: any = {}): ThemeColorConfig {
|
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
|
||||||
return new ThemeColorConfig($$parsedSource as Partial<ThemeColorConfig>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ThemeType 主题类型枚举
|
* ThemeType 主题类型枚举
|
||||||
@@ -1636,6 +1342,11 @@ var $$createType6 = (function $$initCreateType6(...args): any {
|
|||||||
});
|
});
|
||||||
const $$createType7 = $Create.Map($Create.Any, $Create.Any);
|
const $$createType7 = $Create.Map($Create.Any, $Create.Any);
|
||||||
const $$createType8 = HotkeyCombo.createFrom;
|
const $$createType8 = HotkeyCombo.createFrom;
|
||||||
const $$createType9 = ThemeColorConfig.createFrom;
|
var $$createType9 = (function $$initCreateType9(...args): any {
|
||||||
|
if ($$createType9 === $$initCreateType9) {
|
||||||
|
$$createType9 = $$createType7;
|
||||||
|
}
|
||||||
|
return $$createType9(...args);
|
||||||
|
});
|
||||||
const $$createType10 = GithubConfig.createFrom;
|
const $$createType10 = GithubConfig.createFrom;
|
||||||
const $$createType11 = GiteaConfig.createFrom;
|
const $$createType11 = GiteaConfig.createFrom;
|
||||||
|
|||||||
@@ -18,23 +18,10 @@ import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/applic
|
|||||||
import * as models$0 from "../models/models.js";
|
import * as models$0 from "../models/models.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetAllThemes 获取所有主题
|
* GetThemeByName 通过名称获取主题覆盖,若不存在则返回 nil
|
||||||
*/
|
*/
|
||||||
export function GetAllThemes(): Promise<(models$0.Theme | null)[]> & { cancel(): void } {
|
export function GetThemeByName(name: string): Promise<models$0.Theme | null> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(2425053076) as any;
|
let $resultPromise = $Call.ByID(1938954770, name) as any;
|
||||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
|
||||||
return $$createType2($result);
|
|
||||||
}) as any;
|
|
||||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
|
||||||
return $typingPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GetThemeByID 根据ID或名称获取主题
|
|
||||||
* 如果 id > 0,按ID查询;如果 id = 0,按名称查询
|
|
||||||
*/
|
|
||||||
export function GetThemeByIdOrName(id: number, ...name: string[]): Promise<models$0.Theme | null> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(127385338, id, name) as any;
|
|
||||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||||
return $$createType1($result);
|
return $$createType1($result);
|
||||||
}) as any;
|
}) as any;
|
||||||
@@ -43,10 +30,10 @@ export function GetThemeByIdOrName(id: number, ...name: string[]): Promise<model
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ResetTheme 重置主题为预设配置
|
* ResetTheme 删除指定主题的覆盖配置
|
||||||
*/
|
*/
|
||||||
export function ResetTheme(id: number, ...name: string[]): Promise<void> & { cancel(): void } {
|
export function ResetTheme(name: string): Promise<void> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(1806334457, id, name) as any;
|
let $resultPromise = $Call.ByID(1806334457, name) as any;
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +46,7 @@ export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ServiceStartup 服务启动时初始化
|
* ServiceStartup 服务启动
|
||||||
*/
|
*/
|
||||||
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(2915959937, options) as any;
|
let $resultPromise = $Call.ByID(2915959937, options) as any;
|
||||||
@@ -67,14 +54,13 @@ export function ServiceStartup(options: application$0.ServiceOptions): Promise<v
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UpdateTheme 更新主题
|
* UpdateTheme 保存或更新主题覆盖
|
||||||
*/
|
*/
|
||||||
export function UpdateTheme(id: number, colors: models$0.ThemeColorConfig): Promise<void> & { cancel(): void } {
|
export function UpdateTheme(name: string, colors: models$0.ThemeColorConfig): Promise<void> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(70189749, id, colors) as any;
|
let $resultPromise = $Call.ByID(70189749, name, colors) as any;
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private type creation functions
|
// Private type creation functions
|
||||||
const $$createType0 = models$0.Theme.createFrom;
|
const $$createType0 = models$0.Theme.createFrom;
|
||||||
const $$createType1 = $Create.Nullable($$createType0);
|
const $$createType1 = $Create.Nullable($$createType0);
|
||||||
const $$createType2 = $Create.Array($$createType1);
|
|
||||||
|
|||||||
@@ -1,191 +1,156 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import {SystemThemeType, ThemeType, Theme, ThemeColorConfig} from '@/../bindings/voidraft/internal/models/models';
|
import { SystemThemeType, ThemeType, ThemeColorConfig } from '@/../bindings/voidraft/internal/models/models';
|
||||||
import { ThemeService } from '@/../bindings/voidraft/internal/services';
|
import { ThemeService } from '@/../bindings/voidraft/internal/services';
|
||||||
import { useConfigStore } from './configStore';
|
import { useConfigStore } from './configStore';
|
||||||
import { useEditorStore } from './editorStore';
|
import { useEditorStore } from './editorStore';
|
||||||
import type { ThemeColors } from '@/views/editor/theme/types';
|
import type { ThemeColors } from '@/views/editor/theme/types';
|
||||||
|
import { cloneThemeColors, FALLBACK_THEME_NAME, themePresetList, themePresetMap } from '@/views/editor/theme/presets';
|
||||||
|
|
||||||
|
type ThemeOption = { name: string; type: ThemeType };
|
||||||
|
|
||||||
|
const resolveThemeName = (name?: string) =>
|
||||||
|
name && themePresetMap[name] ? name : FALLBACK_THEME_NAME;
|
||||||
|
|
||||||
|
const createThemeOptions = (type: ThemeType): ThemeOption[] =>
|
||||||
|
themePresetList
|
||||||
|
.filter(preset => preset.type === type)
|
||||||
|
.map(preset => ({ name: preset.name, type: preset.type }));
|
||||||
|
|
||||||
|
const darkThemeOptions = createThemeOptions(ThemeType.ThemeTypeDark);
|
||||||
|
const lightThemeOptions = createThemeOptions(ThemeType.ThemeTypeLight);
|
||||||
|
|
||||||
|
const cloneColors = (colors: ThemeColorConfig): ThemeColors =>
|
||||||
|
JSON.parse(JSON.stringify(colors)) as ThemeColors;
|
||||||
|
|
||||||
|
const getPresetColors = (name: string): ThemeColors => {
|
||||||
|
const preset = themePresetMap[name] ?? themePresetMap[FALLBACK_THEME_NAME];
|
||||||
|
const colors = cloneThemeColors(preset.colors);
|
||||||
|
colors.themeName = name;
|
||||||
|
return colors;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchThemeColors = async (themeName: string): Promise<ThemeColors> => {
|
||||||
|
const safeName = resolveThemeName(themeName);
|
||||||
|
try {
|
||||||
|
const theme = await ThemeService.GetThemeByName(safeName);
|
||||||
|
if (theme?.colors) {
|
||||||
|
const colors = cloneColors(theme.colors);
|
||||||
|
colors.themeName = safeName;
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load theme override:', error);
|
||||||
|
}
|
||||||
|
return getPresetColors(safeName);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 主题管理 Store
|
|
||||||
* 职责:管理主题状态、颜色配置和预设主题列表
|
|
||||||
*/
|
|
||||||
export const useThemeStore = defineStore('theme', () => {
|
export const useThemeStore = defineStore('theme', () => {
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
|
|
||||||
// 所有主题列表
|
|
||||||
const allThemes = ref<Theme[]>([]);
|
|
||||||
|
|
||||||
// 当前主题的颜色配置
|
|
||||||
const currentColors = ref<ThemeColors | null>(null);
|
const currentColors = ref<ThemeColors | null>(null);
|
||||||
|
|
||||||
// 计算属性:当前系统主题模式
|
const currentTheme = computed(
|
||||||
const currentTheme = computed(() =>
|
() => configStore.config?.appearance?.systemTheme || SystemThemeType.SystemThemeAuto
|
||||||
configStore.config?.appearance?.systemTheme || SystemThemeType.SystemThemeAuto
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 计算属性:当前是否为深色模式
|
const isDarkMode = computed(
|
||||||
const isDarkMode = computed(() =>
|
() =>
|
||||||
currentTheme.value === SystemThemeType.SystemThemeDark ||
|
currentTheme.value === SystemThemeType.SystemThemeDark ||
|
||||||
(currentTheme.value === SystemThemeType.SystemThemeAuto &&
|
(currentTheme.value === SystemThemeType.SystemThemeAuto &&
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').matches)
|
window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||||
);
|
);
|
||||||
|
|
||||||
// 计算属性:根据类型获取主题列表
|
const availableThemes = computed<ThemeOption[]>(() =>
|
||||||
const darkThemes = computed(() =>
|
isDarkMode.value ? darkThemeOptions : lightThemeOptions
|
||||||
allThemes.value.filter(t => t.type === ThemeType.ThemeTypeDark)
|
|
||||||
);
|
|
||||||
|
|
||||||
const lightThemes = computed(() =>
|
|
||||||
allThemes.value.filter(t => t.type === ThemeType.ThemeTypeLight)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 计算属性:当前可用的主题列表
|
|
||||||
const availableThemes = computed(() =>
|
|
||||||
isDarkMode.value ? darkThemes.value : lightThemes.value
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 应用主题到 DOM
|
|
||||||
const applyThemeToDOM = (theme: SystemThemeType) => {
|
const applyThemeToDOM = (theme: SystemThemeType) => {
|
||||||
const themeMap = {
|
const themeMap = {
|
||||||
[SystemThemeType.SystemThemeAuto]: 'auto',
|
[SystemThemeType.SystemThemeAuto]: 'auto',
|
||||||
[SystemThemeType.SystemThemeDark]: 'dark',
|
[SystemThemeType.SystemThemeDark]: 'dark',
|
||||||
[SystemThemeType.SystemThemeLight]: 'light'
|
[SystemThemeType.SystemThemeLight]: 'light',
|
||||||
};
|
};
|
||||||
document.documentElement.setAttribute('data-theme', themeMap[theme]);
|
document.documentElement.setAttribute('data-theme', themeMap[theme]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 从数据库加载所有主题
|
const loadThemeColors = async (themeName?: string) => {
|
||||||
const loadAllThemes = async () => {
|
const targetName = resolveThemeName(
|
||||||
try {
|
themeName || configStore.config?.appearance?.currentTheme
|
||||||
const themes = await ThemeService.GetAllThemes();
|
);
|
||||||
allThemes.value = (themes || []).filter((t): t is Theme => t !== null);
|
currentColors.value = await fetchThemeColors(targetName);
|
||||||
return allThemes.value;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load themes from database:', error);
|
|
||||||
allThemes.value = [];
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化主题颜色
|
|
||||||
const initThemeColors = async () => {
|
|
||||||
// 加载所有主题
|
|
||||||
await loadAllThemes();
|
|
||||||
|
|
||||||
// 从配置获取当前主题名称并加载
|
|
||||||
const currentThemeName = configStore.config?.appearance?.currentTheme || 'default-dark';
|
|
||||||
|
|
||||||
const theme = allThemes.value.find(t => t.name === currentThemeName);
|
|
||||||
|
|
||||||
if (!theme) {
|
|
||||||
console.error(`Theme not found: ${currentThemeName}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 直接设置当前主题颜色
|
|
||||||
currentColors.value = theme.colors as ThemeColors;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化主题
|
|
||||||
const initializeTheme = async () => {
|
const initializeTheme = async () => {
|
||||||
const theme = currentTheme.value;
|
applyThemeToDOM(currentTheme.value);
|
||||||
applyThemeToDOM(theme);
|
await loadThemeColors();
|
||||||
await initThemeColors();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设置系统主题模式(深色/浅色/自动)
|
|
||||||
const setTheme = async (theme: SystemThemeType) => {
|
const setTheme = async (theme: SystemThemeType) => {
|
||||||
await configStore.setSystemTheme(theme);
|
await configStore.setSystemTheme(theme);
|
||||||
applyThemeToDOM(theme);
|
applyThemeToDOM(theme);
|
||||||
refreshEditorTheme();
|
refreshEditorTheme();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 切换到指定的预设主题
|
|
||||||
const switchToTheme = async (themeName: string) => {
|
const switchToTheme = async (themeName: string) => {
|
||||||
const theme = allThemes.value.find(t => t.name === themeName);
|
if (!themePresetMap[themeName]) {
|
||||||
if (!theme) {
|
|
||||||
console.error('Theme not found:', themeName);
|
console.error('Theme not found:', themeName);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接设置当前主题颜色
|
await loadThemeColors(themeName);
|
||||||
currentColors.value = theme.colors as ThemeColors;
|
|
||||||
|
|
||||||
// 持久化到配置
|
|
||||||
await configStore.setCurrentTheme(themeName);
|
await configStore.setCurrentTheme(themeName);
|
||||||
|
|
||||||
// 刷新编辑器
|
|
||||||
refreshEditorTheme();
|
refreshEditorTheme();
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新当前主题的颜色配置
|
|
||||||
const updateCurrentColors = (colors: Partial<ThemeColors>) => {
|
const updateCurrentColors = (colors: Partial<ThemeColors>) => {
|
||||||
if (!currentColors.value) return;
|
if (!currentColors.value) return;
|
||||||
Object.assign(currentColors.value, colors);
|
Object.assign(currentColors.value, colors);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 保存当前主题颜色到数据库
|
|
||||||
const saveCurrentTheme = async () => {
|
const saveCurrentTheme = async () => {
|
||||||
if (!currentColors.value) {
|
if (!currentColors.value) {
|
||||||
throw new Error('No theme selected');
|
throw new Error('No theme selected');
|
||||||
}
|
}
|
||||||
|
|
||||||
const theme = allThemes.value.find(t => t.name === currentColors.value!.themeName);
|
const themeName = resolveThemeName(currentColors.value.themeName);
|
||||||
if (!theme) {
|
currentColors.value.themeName = themeName;
|
||||||
throw new Error('Theme not found');
|
|
||||||
}
|
await ThemeService.UpdateTheme(themeName, currentColors.value as unknown as ThemeColorConfig);
|
||||||
|
|
||||||
await ThemeService.UpdateTheme(theme.id, currentColors.value as ThemeColorConfig);
|
await loadThemeColors(themeName);
|
||||||
|
refreshEditorTheme();
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重置当前主题为预设配置
|
|
||||||
const resetCurrentTheme = async () => {
|
const resetCurrentTheme = async () => {
|
||||||
if (!currentColors.value) {
|
if (!currentColors.value) {
|
||||||
throw new Error('No theme selected');
|
throw new Error('No theme selected');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用后端重置
|
|
||||||
await ThemeService.ResetTheme(0, currentColors.value.themeName);
|
|
||||||
|
|
||||||
// 重新加载所有主题
|
|
||||||
await loadAllThemes();
|
|
||||||
|
|
||||||
const updatedTheme = allThemes.value.find(t => t.name === currentColors.value!.themeName);
|
const themeName = resolveThemeName(currentColors.value.themeName);
|
||||||
|
await ThemeService.ResetTheme(themeName);
|
||||||
if (updatedTheme) {
|
|
||||||
currentColors.value = updatedTheme.colors as ThemeColors;
|
await loadThemeColors(themeName);
|
||||||
}
|
|
||||||
|
|
||||||
refreshEditorTheme();
|
refreshEditorTheme();
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 刷新编辑器主题
|
|
||||||
const refreshEditorTheme = () => {
|
const refreshEditorTheme = () => {
|
||||||
applyThemeToDOM(currentTheme.value);
|
applyThemeToDOM(currentTheme.value);
|
||||||
|
|
||||||
const editorStore = useEditorStore();
|
const editorStore = useEditorStore();
|
||||||
editorStore?.applyThemeSettings();
|
editorStore?.applyThemeSettings();
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 状态
|
|
||||||
allThemes,
|
|
||||||
darkThemes,
|
|
||||||
lightThemes,
|
|
||||||
availableThemes,
|
availableThemes,
|
||||||
currentTheme,
|
currentTheme,
|
||||||
currentColors,
|
currentColors,
|
||||||
isDarkMode,
|
isDarkMode,
|
||||||
|
|
||||||
// 方法
|
|
||||||
setTheme,
|
setTheme,
|
||||||
switchToTheme,
|
switchToTheme,
|
||||||
initializeTheme,
|
initializeTheme,
|
||||||
loadAllThemes,
|
|
||||||
updateCurrentColors,
|
updateCurrentColors,
|
||||||
saveCurrentTheme,
|
saveCurrentTheme,
|
||||||
resetCurrentTheme,
|
resetCurrentTheme,
|
||||||
|
|||||||
52
frontend/src/views/editor/theme/presets.ts
Normal file
52
frontend/src/views/editor/theme/presets.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import type {ThemeColors} from './types';
|
||||||
|
import {ThemeType} from '@/../bindings/voidraft/internal/models/models';
|
||||||
|
import {defaultDarkColors} from './dark/default-dark';
|
||||||
|
import {defaultLightColors} from './light/default-light';
|
||||||
|
import {config as draculaColors} from './dark/dracula';
|
||||||
|
import {config as auraColors} from './dark/aura';
|
||||||
|
import {config as githubDarkColors} from './dark/github-dark';
|
||||||
|
import {config as materialDarkColors} from './dark/material-dark';
|
||||||
|
import {config as oneDarkColors} from './dark/one-dark';
|
||||||
|
import {config as solarizedDarkColors} from './dark/solarized-dark';
|
||||||
|
import {config as tokyoNightColors} from './dark/tokyo-night';
|
||||||
|
import {config as tokyoNightStormColors} from './dark/tokyo-night-storm';
|
||||||
|
import {config as githubLightColors} from './light/github-light';
|
||||||
|
import {config as materialLightColors} from './light/material-light';
|
||||||
|
import {config as solarizedLightColors} from './light/solarized-light';
|
||||||
|
import {config as tokyoNightDayColors} from './light/tokyo-night-day';
|
||||||
|
|
||||||
|
export interface ThemePreset {
|
||||||
|
name: string;
|
||||||
|
type: ThemeType;
|
||||||
|
colors: ThemeColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FALLBACK_THEME_NAME = defaultDarkColors.themeName;
|
||||||
|
|
||||||
|
export const themePresetList: ThemePreset[] = [
|
||||||
|
{name: defaultDarkColors.themeName, type: ThemeType.ThemeTypeDark, colors: defaultDarkColors},
|
||||||
|
{name: draculaColors.themeName, type: ThemeType.ThemeTypeDark, colors: draculaColors},
|
||||||
|
{name: auraColors.themeName, type: ThemeType.ThemeTypeDark, colors: auraColors},
|
||||||
|
{name: githubDarkColors.themeName, type: ThemeType.ThemeTypeDark, colors: githubDarkColors},
|
||||||
|
{name: materialDarkColors.themeName, type: ThemeType.ThemeTypeDark, colors: materialDarkColors},
|
||||||
|
{name: oneDarkColors.themeName, type: ThemeType.ThemeTypeDark, colors: oneDarkColors},
|
||||||
|
{name: solarizedDarkColors.themeName, type: ThemeType.ThemeTypeDark, colors: solarizedDarkColors},
|
||||||
|
{name: tokyoNightColors.themeName, type: ThemeType.ThemeTypeDark, colors: tokyoNightColors},
|
||||||
|
{name: tokyoNightStormColors.themeName, type: ThemeType.ThemeTypeDark, colors: tokyoNightStormColors},
|
||||||
|
{name: defaultLightColors.themeName, type: ThemeType.ThemeTypeLight, colors: defaultLightColors},
|
||||||
|
{name: githubLightColors.themeName, type: ThemeType.ThemeTypeLight, colors: githubLightColors},
|
||||||
|
{name: materialLightColors.themeName, type: ThemeType.ThemeTypeLight, colors: materialLightColors},
|
||||||
|
{name: solarizedLightColors.themeName, type: ThemeType.ThemeTypeLight, colors: solarizedLightColors},
|
||||||
|
{name: tokyoNightDayColors.themeName, type: ThemeType.ThemeTypeLight, colors: tokyoNightDayColors},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const themePresetMap: Record<string, ThemePreset> = themePresetList.reduce(
|
||||||
|
(map, preset) => {
|
||||||
|
map[preset.name] = preset;
|
||||||
|
return map;
|
||||||
|
},
|
||||||
|
{} as Record<string, ThemePreset>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const cloneThemeColors = (colors: ThemeColors): ThemeColors =>
|
||||||
|
JSON.parse(JSON.stringify(colors)) as ThemeColors;
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
import { useConfigStore } from '@/stores/configStore';
|
import { useConfigStore } from '@/stores/configStore';
|
||||||
import { useThemeStore } from '@/stores/themeStore';
|
import { useThemeStore } from '@/stores/themeStore';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { computed, watch, onMounted, ref } from 'vue';
|
import { computed, watch, onMounted, ref, nextTick } from 'vue';
|
||||||
import SettingSection from '../components/SettingSection.vue';
|
import SettingSection from '../components/SettingSection.vue';
|
||||||
import SettingItem from '../components/SettingItem.vue';
|
import SettingItem from '../components/SettingItem.vue';
|
||||||
import { SystemThemeType, LanguageType } from '@/../bindings/voidraft/internal/models/models';
|
import { SystemThemeType, LanguageType } from '@/../bindings/voidraft/internal/models/models';
|
||||||
@@ -50,7 +50,10 @@ const resetButtonState = ref({
|
|||||||
|
|
||||||
// 当前选中的主题名称
|
// 当前选中的主题名称
|
||||||
const currentThemeName = computed({
|
const currentThemeName = computed({
|
||||||
get: () => themeStore.currentColors?.name || '',
|
get: () =>
|
||||||
|
themeStore.currentColors?.themeName ||
|
||||||
|
configStore.config.appearance.currentTheme ||
|
||||||
|
'',
|
||||||
set: async (themeName: string) => {
|
set: async (themeName: string) => {
|
||||||
await themeStore.switchToTheme(themeName);
|
await themeStore.switchToTheme(themeName);
|
||||||
syncTempColors();
|
syncTempColors();
|
||||||
@@ -89,21 +92,39 @@ onMounted(() => {
|
|||||||
// 从 ThemeColors 接口自动提取所有颜色字段
|
// 从 ThemeColors 接口自动提取所有颜色字段
|
||||||
const colorKeys = computed(() => {
|
const colorKeys = computed(() => {
|
||||||
if (!tempColors.value) return [];
|
if (!tempColors.value) return [];
|
||||||
|
|
||||||
// 获取所有字段,排除 name 和 dark(这两个是元数据)
|
return Object.keys(tempColors.value)
|
||||||
return Object.keys(tempColors.value).filter(key =>
|
.filter(key => key !== 'themeName' && key !== 'dark')
|
||||||
key !== 'name' && key !== 'dark'
|
.sort((a, b) => a.localeCompare(b));
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 颜色配置列表
|
const colorList = computed(() =>
|
||||||
const colorList = computed(() =>
|
|
||||||
colorKeys.value.map(colorKey => ({
|
colorKeys.value.map(colorKey => ({
|
||||||
key: colorKey,
|
key: colorKey,
|
||||||
label: t(`settings.themeColors.${colorKey}`)
|
label: colorKey
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const colorSearch = ref('');
|
||||||
|
const showSearch = ref(false);
|
||||||
|
const searchInputRef = ref<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
|
const filteredColorList = computed(() => {
|
||||||
|
const keyword = colorSearch.value.trim().toLowerCase();
|
||||||
|
if (!keyword) return colorList.value;
|
||||||
|
return colorList.value.filter(color => color.key.toLowerCase().includes(keyword));
|
||||||
|
});
|
||||||
|
|
||||||
|
const toggleSearch = async () => {
|
||||||
|
showSearch.value = !showSearch.value;
|
||||||
|
if (showSearch.value) {
|
||||||
|
await nextTick();
|
||||||
|
searchInputRef.value?.focus();
|
||||||
|
} else {
|
||||||
|
colorSearch.value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 处理重置按钮点击
|
// 处理重置按钮点击
|
||||||
const handleResetClick = () => {
|
const handleResetClick = () => {
|
||||||
if (resetButtonState.value.confirming) {
|
if (resetButtonState.value.confirming) {
|
||||||
@@ -193,7 +214,7 @@ const updateSystemTheme = async (event: Event) => {
|
|||||||
await themeStore.setTheme(selectedSystemTheme);
|
await themeStore.setTheme(selectedSystemTheme);
|
||||||
|
|
||||||
const availableThemes = themeStore.availableThemes;
|
const availableThemes = themeStore.availableThemes;
|
||||||
const currentThemeName = currentColors.value?.name;
|
const currentThemeName = currentColors.value?.themeName;
|
||||||
const isCurrentThemeAvailable = availableThemes.some(t => t.name === currentThemeName);
|
const isCurrentThemeAvailable = availableThemes.some(t => t.name === currentThemeName);
|
||||||
|
|
||||||
if (!isCurrentThemeAvailable && availableThemes.length > 0) {
|
if (!isCurrentThemeAvailable && availableThemes.length > 0) {
|
||||||
@@ -242,7 +263,7 @@ const handlePickerClose = () => {
|
|||||||
<!-- 预设主题选择 -->
|
<!-- 预设主题选择 -->
|
||||||
<SettingItem :title="t('settings.presetTheme')">
|
<SettingItem :title="t('settings.presetTheme')">
|
||||||
<select class="select-input" v-model="currentThemeName" :disabled="hasUnsavedChanges">
|
<select class="select-input" v-model="currentThemeName" :disabled="hasUnsavedChanges">
|
||||||
<option v-for="theme in themeStore.availableThemes" :key="theme.id" :value="theme.name">
|
<option v-for="theme in themeStore.availableThemes" :key="theme.name" :value="theme.name">
|
||||||
{{ theme.name }}
|
{{ theme.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -251,14 +272,27 @@ const handlePickerClose = () => {
|
|||||||
|
|
||||||
<!-- 自定义主题颜色配置 -->
|
<!-- 自定义主题颜色配置 -->
|
||||||
<SettingSection :title="t('settings.customThemeColors')">
|
<SettingSection :title="t('settings.customThemeColors')">
|
||||||
<template #title>
|
|
||||||
<div class="theme-section-title">
|
|
||||||
<span class="section-title-text">{{ t('settings.customThemeColors') }}</span>
|
|
||||||
<span v-if="currentColors.name" class="current-theme-name">{{ currentColors.name }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #title-right>
|
<template #title-right>
|
||||||
<div class="theme-controls">
|
<div class="theme-controls">
|
||||||
|
<div :class="['theme-search-wrapper', showSearch ? 'active' : '']">
|
||||||
|
<input
|
||||||
|
ref="searchInputRef"
|
||||||
|
class="theme-search-input"
|
||||||
|
type="text"
|
||||||
|
v-model="colorSearch"
|
||||||
|
placeholder="Search..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button class="search-toggle-button" @click="toggleSearch" :title="showSearch ? '关闭搜索' : '搜索颜色'">
|
||||||
|
<svg v-if="!showSearch" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<circle cx="11" cy="11" r="8"></circle>
|
||||||
|
<path d="m21 21-4.35-4.35"></path>
|
||||||
|
</svg>
|
||||||
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||||
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="!hasUnsavedChanges"
|
v-if="!hasUnsavedChanges"
|
||||||
:class="['reset-button', resetButtonState.confirming ? 'reset-button-confirming' : '']"
|
:class="['reset-button', resetButtonState.confirming ? 'reset-button-confirming' : '']"
|
||||||
@@ -280,7 +314,7 @@ const handlePickerClose = () => {
|
|||||||
<!-- 颜色列表 -->
|
<!-- 颜色列表 -->
|
||||||
<div class="color-list">
|
<div class="color-list">
|
||||||
<SettingItem
|
<SettingItem
|
||||||
v-for="color in colorList"
|
v-for="color in filteredColorList"
|
||||||
:key="color.key"
|
:key="color.key"
|
||||||
:title="color.label"
|
:title="color.label"
|
||||||
class="color-setting-item"
|
class="color-setting-item"
|
||||||
@@ -318,10 +352,6 @@ const handlePickerClose = () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.settings-page {
|
|
||||||
//max-width: 1000px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-input {
|
.select-input {
|
||||||
min-width: 140px;
|
min-width: 140px;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
@@ -349,27 +379,6 @@ const handlePickerClose = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 主题部分标题
|
|
||||||
.theme-section-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title-text {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.current-theme-name {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--settings-text-secondary);
|
|
||||||
font-weight: normal;
|
|
||||||
padding: 2px 8px;
|
|
||||||
background-color: var(--settings-input-bg);
|
|
||||||
border: 1px solid var(--settings-input-border);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 主题控制区域
|
// 主题控制区域
|
||||||
.theme-controls {
|
.theme-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -441,6 +450,78 @@ const handlePickerClose = () => {
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.theme-search-wrapper {
|
||||||
|
width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-search-wrapper.active {
|
||||||
|
width: 150px;
|
||||||
|
margin-right: 8px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-search-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border: 1px solid var(--settings-input-border);
|
||||||
|
border-radius: 20px;
|
||||||
|
background-color: var(--settings-input-bg);
|
||||||
|
color: var(--settings-text);
|
||||||
|
font-size: 12px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
height: 25px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #4a9eff;
|
||||||
|
background-color: var(--settings-card-bg);
|
||||||
|
box-shadow: 0 0 0 3px rgba(74, 158, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--settings-text-secondary);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-toggle-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
margin-right: 8px;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid var(--settings-input-border);
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--settings-button-bg);
|
||||||
|
color: var(--settings-button-text);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #4a9eff;
|
||||||
|
background-color: var(--settings-button-hover-bg);
|
||||||
|
transform: scale(1.05);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.color-setting-item {
|
.color-setting-item {
|
||||||
:deep(.setting-item-content) {
|
:deep(.setting-item-content) {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -495,4 +576,4 @@ const handlePickerClose = () => {
|
|||||||
color: var(--settings-text-secondary);
|
color: var(--settings-text-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -19,16 +19,17 @@ onMounted(async () => {
|
|||||||
|
|
||||||
// 字体选择选项
|
// 字体选择选项
|
||||||
const fontFamilyOptions = computed(() => configStore.fontOptions);
|
const fontFamilyOptions = computed(() => configStore.fontOptions);
|
||||||
const currentFontFamily = computed(() => configStore.config.editing.fontFamily);
|
const fontFamilyModel = computed({
|
||||||
|
get: () =>
|
||||||
// 字体选择
|
configStore.config.editing.fontFamily ||
|
||||||
const handleFontFamilyChange = async (event: Event) => {
|
fontFamilyOptions.value[0]?.value ||
|
||||||
const target = event.target as HTMLSelectElement;
|
'',
|
||||||
const fontFamily = target.value;
|
set: async (fontFamily: string) => {
|
||||||
if (fontFamily) {
|
if (fontFamily) {
|
||||||
await configStore.setFontFamily(fontFamily);
|
await configStore.setFontFamily(fontFamily);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
// 字体粗细选项
|
// 字体粗细选项
|
||||||
const fontWeightOptions = [
|
const fontWeightOptions = [
|
||||||
@@ -44,10 +45,14 @@ const fontWeightOptions = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// 字体粗细选择
|
// 字体粗细选择
|
||||||
const handleFontWeightChange = async (event: Event) => {
|
const fontWeightModel = computed({
|
||||||
const target = event.target as HTMLSelectElement;
|
get: () => configStore.config.editing.fontWeight || fontWeightOptions[0].value,
|
||||||
await configStore.setFontWeight(target.value);
|
set: async (value: string) => {
|
||||||
};
|
if (value) {
|
||||||
|
await configStore.setFontWeight(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 行高控制
|
// 行高控制
|
||||||
const increaseLineHeight = async () => {
|
const increaseLineHeight = async () => {
|
||||||
@@ -118,8 +123,7 @@ const handleAutoSaveDelayChange = async (event: Event) => {
|
|||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
class="font-family-select"
|
class="font-family-select"
|
||||||
:value="currentFontFamily"
|
v-model="fontFamilyModel"
|
||||||
@change="handleFontFamilyChange"
|
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="option in fontFamilyOptions"
|
v-for="option in fontFamilyOptions"
|
||||||
@@ -146,8 +150,7 @@ const handleAutoSaveDelayChange = async (event: Event) => {
|
|||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
class="font-weight-select"
|
class="font-weight-select"
|
||||||
:value="configStore.config.editing.fontWeight"
|
v-model="fontWeightModel"
|
||||||
@change="handleFontWeightChange"
|
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="option in fontWeightOptions"
|
v-for="option in fontWeightOptions"
|
||||||
@@ -385,4 +388,4 @@ const handleAutoSaveDelayChange = async (event: Event) => {
|
|||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -14,67 +14,21 @@ const (
|
|||||||
ThemeTypeLight ThemeType = "light"
|
ThemeTypeLight ThemeType = "light"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ThemeColorConfig 主题颜色配置(与前端 ThemeColors 接口保持一致)
|
// ThemeColorConfig 使用与前端 ThemeColors 相同的结构,存储任意主题键值
|
||||||
type ThemeColorConfig struct {
|
type ThemeColorConfig map[string]interface{}
|
||||||
// 主题基本信息
|
|
||||||
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"` // 匹配括号
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value 实现 driver.Valuer 接口,用于将 ThemeColorConfig 存储到数据库
|
// Value 实现 driver.Valuer 接口,用于将 ThemeColorConfig 存储到数据库
|
||||||
func (tc ThemeColorConfig) Value() (driver.Value, error) {
|
func (tc ThemeColorConfig) Value() (driver.Value, error) {
|
||||||
|
if tc == nil {
|
||||||
|
return json.Marshal(map[string]interface{}{})
|
||||||
|
}
|
||||||
return json.Marshal(tc)
|
return json.Marshal(tc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scan 实现 sql.Scanner 接口,用于从数据库读取 ThemeColorConfig
|
// Scan 实现 sql.Scanner 接口,用于从数据库读取 ThemeColorConfig
|
||||||
func (tc *ThemeColorConfig) Scan(value interface{}) error {
|
func (tc *ThemeColorConfig) Scan(value interface{}) error {
|
||||||
if value == nil {
|
if value == nil {
|
||||||
|
*tc = ThemeColorConfig{}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +42,13 @@ func (tc *ThemeColorConfig) Scan(value interface{}) error {
|
|||||||
return fmt.Errorf("cannot scan %T into ThemeColorConfig", value)
|
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 主题数据库模型
|
// Theme 主题数据库模型
|
||||||
@@ -101,679 +61,3 @@ type Theme struct {
|
|||||||
CreatedAt string `db:"created_at" json:"createdAt"`
|
CreatedAt string `db:"created_at" json:"createdAt"`
|
||||||
UpdatedAt string `db:"updated_at" json:"updatedAt"`
|
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",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"voidraft/internal/models"
|
"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 {
|
func (ts *ThemeService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||||
ts.ctx = ctx
|
ts.ctx = ctx
|
||||||
|
return nil
|
||||||
// 初始化默认主题
|
|
||||||
return ts.initializeDefaultThemes()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDB 获取数据库连接
|
// getDB 获取数据库连接
|
||||||
@@ -44,141 +43,27 @@ func (ts *ThemeService) getDB() *sql.DB {
|
|||||||
return ts.databaseService.db
|
return ts.databaseService.db
|
||||||
}
|
}
|
||||||
|
|
||||||
// initializeDefaultThemes 初始化所有预设主题
|
// GetThemeByName 通过名称获取主题覆盖,若不存在则返回 nil
|
||||||
func (ts *ThemeService) initializeDefaultThemes() error {
|
func (ts *ThemeService) GetThemeByName(name string) (*models.Theme, error) {
|
||||||
db := ts.getDB()
|
db := ts.getDB()
|
||||||
if db == nil {
|
if db == nil {
|
||||||
return fmt.Errorf("database not available")
|
return nil, fmt.Errorf("database not available")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取所有已存在的主题名称
|
trimmed := strings.TrimSpace(name)
|
||||||
existingThemes := make(map[string]bool)
|
if trimmed == "" {
|
||||||
rows, err := db.Query("SELECT name FROM themes")
|
return nil, fmt.Errorf("theme name cannot be empty")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义所有预设主题配置
|
query := `
|
||||||
now := time.Now().Format("2006-01-02 15:04:05")
|
SELECT id, name, type, colors, is_default, created_at, updated_at
|
||||||
presetThemes := []struct {
|
FROM themes
|
||||||
config *models.ThemeColorConfig
|
WHERE name = ?
|
||||||
themeType models.ThemeType
|
LIMIT 1
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
theme := &models.Theme{}
|
theme := &models.Theme{}
|
||||||
db := ts.getDB()
|
err := db.QueryRow(query, trimmed).Scan(
|
||||||
err := db.QueryRow(query, args...).Scan(
|
|
||||||
&theme.ID,
|
&theme.ID,
|
||||||
&theme.Name,
|
&theme.Name,
|
||||||
&theme.Type,
|
&theme.Type,
|
||||||
@@ -190,133 +75,89 @@ func (ts *ThemeService) GetThemeByIdOrName(id int, name ...string) (*models.Them
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
if id > 0 {
|
return nil, nil
|
||||||
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, fmt.Errorf("failed to get theme: %w", err)
|
return nil, fmt.Errorf("failed to query theme: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return theme, nil
|
return theme, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateTheme 更新主题
|
// UpdateTheme 保存或更新主题覆盖
|
||||||
func (ts *ThemeService) UpdateTheme(id int, colors models.ThemeColorConfig) error {
|
func (ts *ThemeService) UpdateTheme(name string, colors models.ThemeColorConfig) error {
|
||||||
query := `
|
|
||||||
UPDATE themes
|
|
||||||
SET colors = ?, updated_at = ?
|
|
||||||
WHERE id = ?
|
|
||||||
`
|
|
||||||
|
|
||||||
db := ts.getDB()
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update theme: %w", err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetTheme 重置主题为预设配置
|
// ResetTheme 删除指定主题的覆盖配置
|
||||||
func (ts *ThemeService) ResetTheme(id int, name ...string) error {
|
func (ts *ThemeService) ResetTheme(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
|
|
||||||
`
|
|
||||||
|
|
||||||
db := ts.getDB()
|
db := ts.getDB()
|
||||||
rows, err := db.Query(query)
|
if db == nil {
|
||||||
if err != nil {
|
return fmt.Errorf("database not available")
|
||||||
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 {
|
trimmed := strings.TrimSpace(name)
|
||||||
return nil, fmt.Errorf("failed to iterate themes: %w", err)
|
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 服务关闭
|
// ServiceShutdown 服务关闭
|
||||||
|
|||||||
Reference in New Issue
Block a user