feat: 添加主题持久化与历史目录功能

- 在 useTheme 中添加 setGlobalTheme 方法,支持全局主题设置
- 扩展全局数据服务,支持保存主题、上次打开目录和历史目录
- 添加历史目录弹窗,支持从历史记录快速打开目录
- 修改目录打开逻辑,自动更新历史记录并持久化配置
- 在工具栏添加历史目录切换按钮
This commit is contained in:
cfq 2026-01-26 14:37:27 +08:00
parent 4a7bd83835
commit 6a8b3902a2
4 changed files with 155 additions and 14 deletions

View File

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

View File

@ -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
</script>
<template>
@ -83,6 +153,7 @@ const handleSelectDirectory = async () => {
@toggle-search="showSearch = !showSearch"
@toggle-git="showGit = !showGit"
@toggle-settings="showSettings = !showSettings"
@toggle-history="showHistory = !showHistory"
/>
<div class="main-content">
@ -125,6 +196,19 @@ const handleSelectDirectory = async () => {
<a-switch v-model:checked="isDark" @change="toggleDarkMode" />
</div>
</a-modal>
<!-- 历史记录弹窗 -->
<a-modal v-model:open="showHistory" title="选择历史目录" :footer="null">
<a-list :data-source="historyDirs">
<template #renderItem="{ item }">
<a-list-item>
<a-button type="link" @click="handleHistorySelect(item)">
{{ item }}
</a-button>
</a-list-item>
</template>
</a-list>
</a-modal>
</div>
</ConfigProvider>
</template>

View File

@ -34,6 +34,10 @@
<template #icon><SettingOutlined /></template>
</a-button>
<a-button type="text" size="small" @click="$emit('toggle-history')" title="切换历史目录">
<template #icon><HistoryOutlined /></template>
</a-button>
<a-dropdown>
<a-button type="text" size="small" title="导出">
<template #icon><ExportOutlined /></template>
@ -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;

View File

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