feat(文件树): 实现后端搜索并优化前端搜索性能
- 将文件搜索逻辑移至后端 services.js,支持文件夹名称匹配 - 重构前端搜索,使用防抖调用后端搜索 API - 搜索结果自动构建树形结构并展开所有匹配目录 - 移除前端递归过滤逻辑,提升大目录搜索性能
This commit is contained in:
parent
738f320e3d
commit
00a7e919f7
|
|
@ -201,6 +201,16 @@ window.services = {
|
||||||
const fullPath = path.join(currentDir, item.name);
|
const fullPath = path.join(currentDir, item.name);
|
||||||
if (item.isDirectory()) {
|
if (item.isDirectory()) {
|
||||||
if (item.name === ".git" || item.name === "node_modules") continue; // 忽略特定目录
|
if (item.name === ".git" || item.name === "node_modules") continue; // 忽略特定目录
|
||||||
|
|
||||||
|
// 如果文件夹名匹配,也加入结果
|
||||||
|
if (item.name.toLowerCase().includes(keyword.toLowerCase())) {
|
||||||
|
allFiles.push({
|
||||||
|
name: item.name,
|
||||||
|
path: fullPath,
|
||||||
|
type: "directory",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await traverse(fullPath);
|
await traverse(fullPath);
|
||||||
} else {
|
} else {
|
||||||
if (item.name.toLowerCase().includes(keyword.toLowerCase())) {
|
if (item.name.toLowerCase().includes(keyword.toLowerCase())) {
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ import { useTabs } from '../composables/useTabs';
|
||||||
import { useConfig } from '../composables/useConfig';
|
import { useConfig } from '../composables/useConfig';
|
||||||
import { message, Modal } from 'ant-design-vue';
|
import { message, Modal } from 'ant-design-vue';
|
||||||
|
|
||||||
const { state, loadDirectory, refresh } = useFileTree();
|
const { state, loadDirectory, refresh, searchFiles } = useFileTree();
|
||||||
const { openFile } = useTabs();
|
const { openFile } = useTabs();
|
||||||
const { config, addPinnedFile, removePinnedFile, setShowHiddenItems } = useConfig();
|
const { config, addPinnedFile, removePinnedFile, setShowHiddenItems } = useConfig();
|
||||||
|
|
||||||
|
|
@ -142,76 +142,22 @@ const filterNodes = (nodes) => {
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchFilterNodes = (nodes, keyword) => {
|
|
||||||
if (!Array.isArray(nodes) || nodes.length === 0) return [];
|
|
||||||
const lowerKeyword = keyword.toLowerCase();
|
|
||||||
|
|
||||||
const result = [];
|
|
||||||
for (const node of nodes) {
|
|
||||||
const nameMatch = node.name.toLowerCase().includes(lowerKeyword);
|
|
||||||
// 递归处理子节点
|
|
||||||
let filteredChildren = [];
|
|
||||||
if (node.children) {
|
|
||||||
filteredChildren = searchFilterNodes(node.children, keyword);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果当前节点匹配,或者子节点有匹配项,则保留
|
|
||||||
if (nameMatch || filteredChildren.length > 0) {
|
|
||||||
result.push({
|
|
||||||
...node,
|
|
||||||
children: node.children ? filteredChildren : undefined
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取所有需要展开的路径
|
|
||||||
const getExpandedKeys = (nodes, keyword, result = []) => {
|
|
||||||
if (!keyword) return result;
|
|
||||||
const lowerKeyword = keyword.toLowerCase();
|
|
||||||
|
|
||||||
for (const node of nodes) {
|
|
||||||
const nameMatch = node.name.toLowerCase().includes(lowerKeyword);
|
|
||||||
let hasChildMatch = false;
|
|
||||||
|
|
||||||
if (node.children) {
|
|
||||||
const initialLength = result.length;
|
|
||||||
getExpandedKeys(node.children, keyword, result);
|
|
||||||
if (result.length > initialLength) {
|
|
||||||
hasChildMatch = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果子节点有匹配,当前节点需要展开
|
|
||||||
if (hasChildMatch) {
|
|
||||||
result.push(node.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const treeData = computed(() => {
|
const treeData = computed(() => {
|
||||||
// 1. 基础过滤(隐藏文件)
|
// 1. 基础过滤(隐藏文件)
|
||||||
|
// 搜索模式下,state.nodes 已经是过滤后的结果(虽然可能包含隐藏文件,如果 searchFileName 没过滤的话)
|
||||||
|
// 这里我们还是统一过滤一下隐藏文件
|
||||||
let nodes = filterNodes(state.nodes);
|
let nodes = filterNodes(state.nodes);
|
||||||
|
|
||||||
// 2. 搜索过滤
|
|
||||||
if (searchKeyword.value) {
|
|
||||||
nodes = searchFilterNodes(nodes, searchKeyword.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return transformNodes(nodes);
|
return transformNodes(nodes);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听搜索关键词,自动展开
|
// 防抖搜索
|
||||||
|
let searchTimer = null;
|
||||||
watch(searchKeyword, (newVal) => {
|
watch(searchKeyword, (newVal) => {
|
||||||
if (newVal) {
|
if (searchTimer) clearTimeout(searchTimer);
|
||||||
const nodes = filterNodes(state.nodes); // 基于基础过滤后的节点计算
|
searchTimer = setTimeout(() => {
|
||||||
const keysToExpand = [];
|
searchFiles(newVal);
|
||||||
getExpandedKeys(nodes, newVal, keysToExpand);
|
}, 300);
|
||||||
// 合并并去重
|
|
||||||
state.expandedKeys = [...new Set([...state.expandedKeys, ...keysToExpand])];
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const toggleShowHiddenItems = async () => {
|
const toggleShowHiddenItems = async () => {
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,105 @@ export function useFileTree() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 构建树辅助函数
|
||||||
|
const buildTreeFromSearchResults = (items, rootPath) => {
|
||||||
|
const rootNodes = [];
|
||||||
|
const sep = window.utools.isWindows() ? '\\' : '/';
|
||||||
|
|
||||||
|
items.forEach(item => {
|
||||||
|
// 计算相对路径
|
||||||
|
if (!item.path.startsWith(rootPath)) return;
|
||||||
|
|
||||||
|
let relative = item.path.substring(rootPath.length);
|
||||||
|
if (relative.startsWith(sep)) {
|
||||||
|
relative = relative.substring(1);
|
||||||
|
}
|
||||||
|
if (!relative) return;
|
||||||
|
|
||||||
|
const parts = relative.split(sep);
|
||||||
|
|
||||||
|
let currentLevel = rootNodes;
|
||||||
|
let currentPath = rootPath;
|
||||||
|
|
||||||
|
parts.forEach((part, index) => {
|
||||||
|
const isLast = index === parts.length - 1;
|
||||||
|
// 如果是最后一项,使用 item 的类型;中间节点必定是 directory
|
||||||
|
const type = isLast ? item.type : 'directory';
|
||||||
|
|
||||||
|
currentPath = currentPath.endsWith(sep) ? currentPath + part : currentPath + sep + part;
|
||||||
|
|
||||||
|
let existingNode = currentLevel.find(n => n.name === part);
|
||||||
|
|
||||||
|
if (!existingNode) {
|
||||||
|
existingNode = {
|
||||||
|
id: currentPath,
|
||||||
|
path: currentPath,
|
||||||
|
name: part,
|
||||||
|
type: type,
|
||||||
|
children: type === 'directory' ? [] : undefined
|
||||||
|
};
|
||||||
|
currentLevel.push(existingNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'directory') {
|
||||||
|
if (!existingNode.children) existingNode.children = [];
|
||||||
|
currentLevel = existingNode.children;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
const sortNodes = (nodes) => {
|
||||||
|
nodes.sort(compareFileTreeItems);
|
||||||
|
nodes.forEach(node => {
|
||||||
|
if (node.children) sortNodes(node.children);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
sortNodes(rootNodes);
|
||||||
|
|
||||||
|
return rootNodes;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 递归获取所有目录路径用于展开
|
||||||
|
const getAllDirectoryPaths = (nodes, result = []) => {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.type === 'directory') {
|
||||||
|
result.push(node.path);
|
||||||
|
if (node.children) {
|
||||||
|
getAllDirectoryPaths(node.children, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 搜索文件
|
||||||
|
const searchFiles = async (keyword) => {
|
||||||
|
if (!state.rootPath) return;
|
||||||
|
|
||||||
|
if (!keyword) {
|
||||||
|
// 关键词为空,恢复根目录视图
|
||||||
|
await loadDirectory(state.rootPath);
|
||||||
|
state.expandedKeys = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.services?.searchFileName) return;
|
||||||
|
|
||||||
|
state.loading = true;
|
||||||
|
try {
|
||||||
|
const items = await window.services.searchFileName(state.rootPath, keyword);
|
||||||
|
const tree = buildTreeFromSearchResults(items, state.rootPath);
|
||||||
|
state.nodes = tree;
|
||||||
|
// 自动展开所有搜索结果中的目录
|
||||||
|
state.expandedKeys = getAllDirectoryPaths(tree);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Search failed", error);
|
||||||
|
} finally {
|
||||||
|
state.loading = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
setRootPath,
|
setRootPath,
|
||||||
|
|
@ -116,6 +215,7 @@ export function useFileTree() {
|
||||||
expandNode,
|
expandNode,
|
||||||
collapseNode,
|
collapseNode,
|
||||||
toggleExpand,
|
toggleExpand,
|
||||||
refresh
|
refresh,
|
||||||
|
searchFiles
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue