feat: 添加主题持久化与历史目录功能
- 在 useTheme 中添加 setGlobalTheme 方法,支持全局主题设置 - 扩展全局数据服务,支持保存主题、上次打开目录和历史目录 - 添加历史目录弹窗,支持从历史记录快速打开目录 - 修改目录打开逻辑,自动更新历史记录并持久化配置 - 在工具栏添加历史目录切换按钮
This commit is contained in:
parent
4a7bd83835
commit
6a8b3902a2
|
|
@ -303,5 +303,49 @@ window.services = {
|
||||||
console.error("保存配置失败", e);
|
console.error("保存配置失败", e);
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
106
src/App.vue
106
src/App.vue
|
|
@ -14,12 +14,14 @@ import { SettingOutlined } from '@ant-design/icons-vue';
|
||||||
|
|
||||||
const { setRootPath } = useFileTree();
|
const { setRootPath } = useFileTree();
|
||||||
const { loadConfig } = useConfig();
|
const { loadConfig } = useConfig();
|
||||||
const { getAntdTheme, primaryColor, toggleTheme, isDark, toggleDarkMode } = useTheme();
|
const { getAntdTheme, primaryColor, toggleTheme, isDark, toggleDarkMode, setGlobalTheme } = useTheme();
|
||||||
|
|
||||||
const route = ref("");
|
const route = ref("");
|
||||||
const showSearch = ref(false);
|
const showSearch = ref(false);
|
||||||
const showGit = ref(false);
|
const showGit = ref(false);
|
||||||
const showSettings = ref(false);
|
const showSettings = ref(false);
|
||||||
|
const showHistory = ref(false); // 历史记录弹窗
|
||||||
|
const historyDirs = ref([]); // 历史目录列表
|
||||||
|
|
||||||
const themeColors = [
|
const themeColors = [
|
||||||
'#1890ff', // 默认蓝
|
'#1890ff', // 默认蓝
|
||||||
|
|
@ -34,27 +36,88 @@ const themeColors = [
|
||||||
|
|
||||||
const antdTheme = ref(getAntdTheme());
|
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();
|
antdTheme.value = getAntdTheme();
|
||||||
|
// 主题变化时保存
|
||||||
|
await saveGlobalConfig();
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.utools.onPluginEnter(async (action) => {
|
window.utools.onPluginEnter(async (action) => {
|
||||||
route.value = action.code;
|
route.value = action.code;
|
||||||
|
|
||||||
|
// 加载全局配置 (包含主题和上次打开的目录)
|
||||||
|
// 注意:如果 action 指定了打开特定目录,则覆盖上次打开的目录
|
||||||
|
await loadGlobalConfig();
|
||||||
|
|
||||||
// 如果有payload,可能是直接打开文件或目录
|
// 如果有payload,可能是直接打开文件或目录
|
||||||
if (action.type === 'files' && action.payload.length > 0) {
|
if (action.type === 'files' && action.payload.length > 0) {
|
||||||
const file = action.payload[0];
|
const file = action.payload[0];
|
||||||
if (file.isDirectory) {
|
if (file.isDirectory) {
|
||||||
await setRootPath(file.path);
|
await openDirectory(file.path);
|
||||||
await loadConfig(file.path);
|
|
||||||
} else {
|
} else {
|
||||||
// 如果是文件,需要确定根目录。通常取文件所在目录
|
// 如果是文件...
|
||||||
// 或者弹窗询问?这里简化处理,假设用户通过 uTools 选择了目录进入
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// 默认行为,可能需要用户手动选择目录
|
|
||||||
// 或者读取上次打开的目录(如果有持久化)
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -67,12 +130,19 @@ const handleSelectDirectory = async () => {
|
||||||
if (window.services?.selectDirectory) {
|
if (window.services?.selectDirectory) {
|
||||||
const paths = window.services.selectDirectory();
|
const paths = window.services.selectDirectory();
|
||||||
if (paths && paths.length > 0) {
|
if (paths && paths.length > 0) {
|
||||||
await setRootPath(paths[0]);
|
await openDirectory(paths[0]);
|
||||||
await loadConfig(paths[0]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleHistorySelect = (path) => {
|
||||||
|
openDirectory(path);
|
||||||
|
showHistory.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { state } = useFileTree(); // 需要获取 rootPath 用于保存
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -83,6 +153,7 @@ const handleSelectDirectory = async () => {
|
||||||
@toggle-search="showSearch = !showSearch"
|
@toggle-search="showSearch = !showSearch"
|
||||||
@toggle-git="showGit = !showGit"
|
@toggle-git="showGit = !showGit"
|
||||||
@toggle-settings="showSettings = !showSettings"
|
@toggle-settings="showSettings = !showSettings"
|
||||||
|
@toggle-history="showHistory = !showHistory"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
|
|
@ -125,6 +196,19 @@ const handleSelectDirectory = async () => {
|
||||||
<a-switch v-model:checked="isDark" @change="toggleDarkMode" />
|
<a-switch v-model:checked="isDark" @change="toggleDarkMode" />
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</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>
|
</div>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,10 @@
|
||||||
<a-button type="text" size="small" @click="$emit('toggle-settings')" title="设置">
|
<a-button type="text" size="small" @click="$emit('toggle-settings')" title="设置">
|
||||||
<template #icon><SettingOutlined /></template>
|
<template #icon><SettingOutlined /></template>
|
||||||
</a-button>
|
</a-button>
|
||||||
|
|
||||||
|
<a-button type="text" size="small" @click="$emit('toggle-history')" title="切换历史目录">
|
||||||
|
<template #icon><HistoryOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
|
||||||
<a-dropdown>
|
<a-dropdown>
|
||||||
<a-button type="text" size="small" title="导出">
|
<a-button type="text" size="small" title="导出">
|
||||||
|
|
@ -57,7 +61,9 @@ import {
|
||||||
SaveOutlined,
|
SaveOutlined,
|
||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
GithubOutlined,
|
GithubOutlined,
|
||||||
ExportOutlined
|
ExportOutlined,
|
||||||
|
SettingOutlined,
|
||||||
|
HistoryOutlined
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import { useFileTree } from '../composables/useFileTree';
|
import { useFileTree } from '../composables/useFileTree';
|
||||||
import { useTabs } from '../composables/useTabs';
|
import { useTabs } from '../composables/useTabs';
|
||||||
|
|
@ -68,7 +74,7 @@ const { state } = useFileTree();
|
||||||
const { activeTab } = useTabs();
|
const { activeTab } = useTabs();
|
||||||
const { saveCurrentFile } = useEditor();
|
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(() => {
|
const canSave = computed(() => {
|
||||||
return activeTab.value && activeTab.value.type === 'file' && activeTab.value.isDirty;
|
return activeTab.value && activeTab.value.type === 'file' && activeTab.value.isDirty;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
return {
|
||||||
primaryColor,
|
primaryColor,
|
||||||
isDark,
|
isDark,
|
||||||
toggleTheme,
|
toggleTheme,
|
||||||
toggleDarkMode,
|
toggleDarkMode,
|
||||||
getAntdTheme
|
getAntdTheme,
|
||||||
|
setGlobalTheme
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue