feat(FileTree): 改进文件树交互并添加主题支持

- 修复目录加载逻辑,避免空数组误判为已加载
- 改进新建/重命名操作的父目录路径计算
- 为文件树节点添加悬停提示和主题样式适配
- 在工具栏添加设置按钮
- 扩展主题系统以支持暗色模式
This commit is contained in:
cfq 2026-01-26 14:06:34 +08:00
parent 6b26049909
commit 4a7bd83835
4 changed files with 88 additions and 14 deletions

View File

@ -49,7 +49,8 @@ window.services = {
name: item.name,
path: path.join(dirPath, item.name),
type: item.isDirectory() ? "directory" : "file",
children: item.isDirectory() ? [] : undefined, // 目录初始化为空数组,标记为可展开
// children 不设置或设为 undefined表示未加载如果设为 [],组件会认为已加载且为空
children: undefined,
}));
} catch (error) {
console.error("读取目录失败:", error);

View File

@ -21,7 +21,7 @@
>
<template #title="{ dataRef }">
<a-dropdown :trigger="['contextmenu']">
<span class="tree-node-title">{{ dataRef.name }}</span>
<span class="tree-node-title" :title="dataRef.name">{{ dataRef.name }}</span>
<template #overlay>
<a-menu @click="(e) => handleMenuClick(e, dataRef)">
<a-menu-item key="open" v-if="dataRef.type === 'file'">打开</a-menu-item>
@ -101,11 +101,12 @@ const handleRefresh = async () => {
const onLoadData = (treeNode) => {
return new Promise(async (resolve) => {
if (treeNode.dataRef.children) {
// 使 key (path)
if (treeNode.dataRef.children && treeNode.dataRef.children.length > 0) {
resolve();
return;
}
await loadDirectory(treeNode.eventKey); // eventKey is path
await loadDirectory(treeNode.key || treeNode.eventKey);
resolve();
});
};
@ -116,7 +117,7 @@ const onSelect = (keys, { node }) => {
}
};
const isMdFile = (name) => name.toLowerCase().endsWith('.md');
const isMdFile = (name) => name && name.toLowerCase().endsWith('.md');
const isPinned = (path) => config.pinnedFiles.includes(path);
@ -153,7 +154,7 @@ const handleMenuClick = async ({ key }, node) => {
if (window.services?.deleteItem) {
await window.services.deleteItem(node.path);
message.success('删除成功');
await refresh(); //
await refresh();
}
}
});
@ -192,7 +193,23 @@ const handleModalOk = async () => {
}
const node = currentNode.value;
const parentPath = node.type === 'directory' ? node.path : node.path.substring(0, node.path.lastIndexOf(window.utools.isWindows() ? '\\' : '/'));
// node.path node
//
// Ant Design Tree node
// directory
// file
let parentPath = '';
if (currentAction.value === 'newFile' || currentAction.value === 'newFolder') {
if (node.type === 'directory') {
parentPath = node.path;
} else {
parentPath = node.path.substring(0, node.path.lastIndexOf(window.utools.isWindows() ? '\\' : '/'));
}
} else {
// rename
parentPath = node.path.substring(0, node.path.lastIndexOf(window.utools.isWindows() ? '\\' : '/'));
}
try {
if (currentAction.value === 'rename') {
@ -200,14 +217,13 @@ const handleModalOk = async () => {
await window.services.renameItem(node.path, newPath);
message.success('重命名成功');
} else if (currentAction.value === 'newFile') {
// node
const separator = window.utools.isWindows() ? '\\' : '/';
const newPath = `${node.path}${separator}${modalInputValue.value}`;
const newPath = `${parentPath}${separator}${modalInputValue.value}`;
await window.services.writeFile(newPath, ''); //
message.success('创建文件成功');
} else if (currentAction.value === 'newFolder') {
const separator = window.utools.isWindows() ? '\\' : '/';
const newPath = `${node.path}${separator}${modalInputValue.value}`;
const newPath = `${parentPath}${separator}${modalInputValue.value}`;
await window.services.createDirectory(newPath);
message.success('创建文件夹成功');
}
@ -225,6 +241,8 @@ const handleModalOk = async () => {
height: 100%;
display: flex;
flex-direction: column;
background-color: var(--card-background, #fff);
color: var(--text-color, #000);
}
.tree-header {
@ -232,7 +250,7 @@ const handleModalOk = async () => {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #f0f0f0;
border-bottom: 1px solid var(--border-color, #f0f0f0);
}
.title {
@ -245,6 +263,17 @@ const handleModalOk = async () => {
padding: 8px 0;
}
/* 覆盖 Ant Design Tree 的一些样式以美化 */
:deep(.ant-tree) {
background: transparent;
color: inherit;
}
:deep(.ant-tree-treenode) {
padding-bottom: 4px;
}
.empty-tip {
padding: 20px;
text-align: center;
@ -252,7 +281,30 @@ const handleModalOk = async () => {
}
.tree-node-title {
display: inline-block;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1; /* allow it to take remaining space but not force width */
margin-left: 4px; /* spacing between icon and text */
}
:deep(.ant-tree-node-content-wrapper) {
display: flex;
align-items: center;
overflow: hidden;
min-height: 28px; /* improve touch target / spacing */
padding: 0 4px;
border-radius: 4px;
transition: all 0.2s;
}
:deep(.ant-tree-node-content-wrapper:hover) {
background-color: var(--hover-background, rgba(0, 0, 0, 0.04));
}
:deep(.ant-tree-node-selected .ant-tree-node-content-wrapper) {
background-color: var(--primary-color-bg, #e6f7ff) !important;
color: var(--primary-color, #1890ff);
}
</style>

View File

@ -29,6 +29,10 @@
<a-button type="text" size="small" @click="$emit('toggle-git')" title="Git 操作">
<template #icon><GithubOutlined /></template>
</a-button>
<a-button type="text" size="small" @click="$emit('toggle-settings')" title="设置">
<template #icon><SettingOutlined /></template>
</a-button>
<a-dropdown>
<a-button type="text" size="small" title="导出">

View File

@ -18,7 +18,24 @@ export function useTheme() {
const updateCSSVariables = () => {
const root = document.documentElement;
root.style.setProperty('--primary-color', primaryColor.value);
// Ant Design uses generated colors, but we can set the main one for our custom CSS
// 生成一个浅色背景用于选中态
// 简单变淡处理,或者直接使用 opacity
root.style.setProperty('--primary-color-bg', primaryColor.value + '1A'); // 10% opacity
if (isDark.value) {
root.style.setProperty('--card-background', '#1f1f1f');
root.style.setProperty('--text-color', 'rgba(255, 255, 255, 0.85)');
root.style.setProperty('--border-color', '#303030');
root.style.setProperty('--hover-background', 'rgba(255, 255, 255, 0.08)');
root.style.setProperty('--text-color-secondary', 'rgba(255, 255, 255, 0.45)');
} else {
root.style.setProperty('--card-background', '#ffffff');
root.style.setProperty('--text-color', 'rgba(0, 0, 0, 0.85)');
root.style.setProperty('--border-color', '#f0f0f0');
root.style.setProperty('--hover-background', 'rgba(0, 0, 0, 0.04)');
root.style.setProperty('--text-color-secondary', 'rgba(0, 0, 0, 0.45)');
}
};
const getAntdTheme = () => {