From 6a8b3902a284421481c362bb60645273388e53cd Mon Sep 17 00:00:00 2001 From: cfq Date: Mon, 26 Jan 2026 14:37:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=B8=BB=E9=A2=98?= =?UTF-8?q?=E6=8C=81=E4=B9=85=E5=8C=96=E4=B8=8E=E5=8E=86=E5=8F=B2=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 useTheme 中添加 setGlobalTheme 方法,支持全局主题设置 - 扩展全局数据服务,支持保存主题、上次打开目录和历史目录 - 添加历史目录弹窗,支持从历史记录快速打开目录 - 修改目录打开逻辑,自动更新历史记录并持久化配置 - 在工具栏添加历史目录切换按钮 --- public/preload/services.js | 44 +++++++++++++++ src/App.vue | 106 ++++++++++++++++++++++++++++++++---- src/components/Toolbar.vue | 10 +++- src/composables/useTheme.js | 9 ++- 4 files changed, 155 insertions(+), 14 deletions(-) diff --git a/public/preload/services.js b/public/preload/services.js index 410a872..9dccb2d 100644 --- a/public/preload/services.js +++ b/public/preload/services.js @@ -303,5 +303,49 @@ window.services = { console.error("保存配置失败", e); return false; } + }, + + // --- Global Data Service --- + // 保存全局数据(如上次打开的目录、历史记录、主题等) + // 存储在 uTools 插件数据目录下的 global-data.json + getGlobalDataPath() { + // 优先使用 uTools 提供的路径,或者 fallback 到 userData + // 注意:window.utools.getPath('userData') 可能在 preload 中不可直接用,需谨慎 + // 但通常 preload 中可以使用 window.utools + // 为了稳妥,我们可以尝试使用 utools.dbStorage 如果只是简单 KV,但用户要求 JSON 文件。 + // 我们这里使用 electron 的 userData 目录 + const userDataPath = window.utools.getPath('userData'); + return path.join(userDataPath, 'md-helper-global.json'); + }, + + async loadGlobalData() { + try { + const dataPath = this.getGlobalDataPath(); + if (fs.existsSync(dataPath)) { + const content = await fs.promises.readFile(dataPath, 'utf-8'); + return JSON.parse(content); + } + } catch (e) { + console.error("读取全局数据失败", e); + } + return { + lastOpenedDir: '', + historyDirs: [], // string[] + theme: { + primaryColor: '#1890ff', + isDark: false + } + }; + }, + + async saveGlobalData(data) { + try { + const dataPath = this.getGlobalDataPath(); + await fs.promises.writeFile(dataPath, JSON.stringify(data, null, 2), 'utf-8'); + return true; + } catch (e) { + console.error("保存全局数据失败", e); + return false; + } } }; diff --git a/src/App.vue b/src/App.vue index 061e778..6fa7c74 100644 --- a/src/App.vue +++ b/src/App.vue @@ -14,12 +14,14 @@ import { SettingOutlined } from '@ant-design/icons-vue'; const { setRootPath } = useFileTree(); const { loadConfig } = useConfig(); -const { getAntdTheme, primaryColor, toggleTheme, isDark, toggleDarkMode } = useTheme(); +const { getAntdTheme, primaryColor, toggleTheme, isDark, toggleDarkMode, setGlobalTheme } = useTheme(); const route = ref(""); const showSearch = ref(false); const showGit = ref(false); const showSettings = ref(false); +const showHistory = ref(false); // 历史记录弹窗 +const historyDirs = ref([]); // 历史目录列表 const themeColors = [ '#1890ff', // 默认蓝 @@ -34,27 +36,88 @@ const themeColors = [ const antdTheme = ref(getAntdTheme()); -watch([primaryColor, isDark], () => { +// 加载全局配置 +const loadGlobalConfig = async () => { + if (window.services?.loadGlobalData) { + const data = await window.services.loadGlobalData(); + if (data) { + // 恢复主题 + if (data.theme) { + setGlobalTheme(data.theme.primaryColor, data.theme.isDark); + } + + // 恢复历史记录 + if (data.historyDirs) { + historyDirs.value = data.historyDirs; + } + + // 自动加载上次打开的目录 + if (data.lastOpenedDir && window.services.pathExists(data.lastOpenedDir)) { + await openDirectory(data.lastOpenedDir); + } else if (historyDirs.value.length > 0) { + // 如果上次打开的目录不存在,但有历史记录,可以弹窗让用户选择,或者默认不打开 + // 这里选择展示历史记录弹窗让用户选 + showHistory.value = true; + } + } + } +}; + +// 保存全局配置 +const saveGlobalConfig = async () => { + if (window.services?.saveGlobalData) { + await window.services.saveGlobalData({ + lastOpenedDir: state.rootPath, // from useFileTree + historyDirs: historyDirs.value, + theme: { + primaryColor: primaryColor.value, + isDark: isDark.value + } + }); + } +}; + +// 封装打开目录的逻辑,包含更新历史记录 +const openDirectory = async (path) => { + await setRootPath(path); + await loadConfig(path); + + // 更新历史记录 + if (!historyDirs.value.includes(path)) { + historyDirs.value.unshift(path); + if (historyDirs.value.length > 10) historyDirs.value.pop(); + } else { + // 移到第一位 + const index = historyDirs.value.indexOf(path); + historyDirs.value.splice(index, 1); + historyDirs.value.unshift(path); + } + + await saveGlobalConfig(); +}; + +watch([primaryColor, isDark], async () => { antdTheme.value = getAntdTheme(); + // 主题变化时保存 + await saveGlobalConfig(); }); onMounted(() => { window.utools.onPluginEnter(async (action) => { route.value = action.code; + // 加载全局配置 (包含主题和上次打开的目录) + // 注意:如果 action 指定了打开特定目录,则覆盖上次打开的目录 + await loadGlobalConfig(); + // 如果有payload,可能是直接打开文件或目录 if (action.type === 'files' && action.payload.length > 0) { const file = action.payload[0]; if (file.isDirectory) { - await setRootPath(file.path); - await loadConfig(file.path); + await openDirectory(file.path); } else { - // 如果是文件,需要确定根目录。通常取文件所在目录 - // 或者弹窗询问?这里简化处理,假设用户通过 uTools 选择了目录进入 + // 如果是文件... } - } else { - // 默认行为,可能需要用户手动选择目录 - // 或者读取上次打开的目录(如果有持久化) } }); @@ -67,12 +130,19 @@ const handleSelectDirectory = async () => { if (window.services?.selectDirectory) { const paths = window.services.selectDirectory(); if (paths && paths.length > 0) { - await setRootPath(paths[0]); - await loadConfig(paths[0]); + await openDirectory(paths[0]); } } }; +const handleHistorySelect = (path) => { + openDirectory(path); + showHistory.value = false; +}; + +const { state } = useFileTree(); // 需要获取 rootPath 用于保存 + + diff --git a/src/components/Toolbar.vue b/src/components/Toolbar.vue index 98d6046..9c238e1 100644 --- a/src/components/Toolbar.vue +++ b/src/components/Toolbar.vue @@ -33,6 +33,10 @@ + + + + @@ -57,7 +61,9 @@ import { SaveOutlined, SearchOutlined, GithubOutlined, - ExportOutlined + ExportOutlined, + SettingOutlined, + HistoryOutlined } from '@ant-design/icons-vue'; import { useFileTree } from '../composables/useFileTree'; import { useTabs } from '../composables/useTabs'; @@ -68,7 +74,7 @@ const { state } = useFileTree(); const { activeTab } = useTabs(); const { saveCurrentFile } = useEditor(); -defineEmits(['select-directory', 'toggle-search', 'toggle-git', 'toggle-settings']); +defineEmits(['select-directory', 'toggle-search', 'toggle-git', 'toggle-settings', 'toggle-history']); const canSave = computed(() => { return activeTab.value && activeTab.value.type === 'file' && activeTab.value.isDirty; diff --git a/src/composables/useTheme.js b/src/composables/useTheme.js index 55702f8..f53c998 100644 --- a/src/composables/useTheme.js +++ b/src/composables/useTheme.js @@ -47,11 +47,18 @@ export function useTheme() { }; }; + const setGlobalTheme = (color, dark) => { + if (color) primaryColor.value = color; + if (typeof dark === 'boolean') isDark.value = dark; + updateCSSVariables(); + }; + return { primaryColor, isDark, toggleTheme, toggleDarkMode, - getAntdTheme + getAntdTheme, + setGlobalTheme }; }