feat(文件树): 实现文件树与标签页的联动选择和滚动定位
- 在 HomeTab 中显示文件名和完整路径,提升可读性 - 监听 activeTab 变化,自动在文件树中选中对应文件并展开父目录 - 添加滚动定位功能,确保选中节点在可视区域内 - 优化文件树模板结构,提高代码可维护性
This commit is contained in:
parent
e29391c893
commit
e56fc4b5f1
|
|
@ -1,13 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="file-tree-container">
|
<div class="file-tree-container">
|
||||||
<div class="tree-header">
|
<div class="tree-header">
|
||||||
<a-input
|
<a-input v-model:value="searchKeyword" placeholder="搜索文件..." size="small" allow-clear class="search-input">
|
||||||
v-model:value="searchKeyword"
|
|
||||||
placeholder="搜索文件..."
|
|
||||||
size="small"
|
|
||||||
allow-clear
|
|
||||||
class="search-input"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<SearchOutlined />
|
<SearchOutlined />
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -16,12 +10,7 @@
|
||||||
<a-button type="text" size="small" @click="handleRefresh" title="刷新">
|
<a-button type="text" size="small" @click="handleRefresh" title="刷新">
|
||||||
<template #icon><ReloadOutlined /></template>
|
<template #icon><ReloadOutlined /></template>
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button
|
<a-button type="text" size="small" @click="toggleShowHiddenItems" :title="showHiddenItems ? '不显示隐藏项' : '显示隐藏项'">
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
@click="toggleShowHiddenItems"
|
|
||||||
:title="showHiddenItems ? '不显示隐藏项' : '显示隐藏项'"
|
|
||||||
>
|
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<EyeOutlined v-if="showHiddenItems" />
|
<EyeOutlined v-if="showHiddenItems" />
|
||||||
<EyeInvisibleOutlined v-else />
|
<EyeInvisibleOutlined v-else />
|
||||||
|
|
@ -34,19 +23,10 @@
|
||||||
<a-empty description="请选择目录" />
|
<a-empty description="请选择目录" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="tree-content">
|
<div v-else class="tree-content" ref="treeContentRef">
|
||||||
<a-directory-tree
|
<a-directory-tree ref="treeRef" v-model:expandedKeys="state.expandedKeys" v-model:selectedKeys="selectedKeys" :tree-data="treeData" :field-names="{ title: 'name', key: 'path', children: 'children' }" :load-data="onLoadData" @select="onSelect">
|
||||||
v-model:expandedKeys="state.expandedKeys"
|
|
||||||
:tree-data="treeData"
|
|
||||||
:field-names="{ title: 'name', key: 'path', children: 'children' }"
|
|
||||||
:load-data="onLoadData"
|
|
||||||
@select="onSelect"
|
|
||||||
>
|
|
||||||
<template #icon="{ dataRef, expanded }">
|
<template #icon="{ dataRef, expanded }">
|
||||||
<component
|
<component :is="getFileIcon(dataRef.name, dataRef.type, expanded)" :style="{ color: getFileIconColor(dataRef.name, dataRef.type), fontSize: '18px', marginRight: '6px' }" />
|
||||||
:is="getFileIcon(dataRef.name, dataRef.type, expanded)"
|
|
||||||
:style="{ color: getFileIconColor(dataRef.name, dataRef.type), fontSize: '18px', marginRight: '6px' }"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<template #title="{ dataRef }">
|
<template #title="{ dataRef }">
|
||||||
<a-dropdown :trigger="['contextmenu']">
|
<a-dropdown :trigger="['contextmenu']">
|
||||||
|
|
@ -57,7 +37,7 @@
|
||||||
|
|
||||||
<template v-if="isMdFile(dataRef.name)">
|
<template v-if="isMdFile(dataRef.name)">
|
||||||
<a-menu-item key="pin">
|
<a-menu-item key="pin">
|
||||||
{{ isPinned(dataRef.path) ? '取消置顶' : '置顶' }}
|
{{ isPinned(dataRef.path) ? "取消置顶" : "置顶" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -84,55 +64,115 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 重命名/新建弹窗 -->
|
<!-- 重命名/新建弹窗 -->
|
||||||
<a-modal
|
<a-modal v-model:open="modalVisible" :title="modalTitle" @ok="handleModalOk">
|
||||||
v-model:open="modalVisible"
|
|
||||||
:title="modalTitle"
|
|
||||||
@ok="handleModalOk"
|
|
||||||
>
|
|
||||||
<a-input v-model:value="modalInputValue" :placeholder="modalPlaceholder" />
|
<a-input v-model:value="modalInputValue" :placeholder="modalPlaceholder" />
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch, nextTick } from "vue";
|
||||||
import {
|
import { EyeInvisibleOutlined, EyeOutlined, ReloadOutlined, SearchOutlined, FileOutlined, FileMarkdownOutlined, FileImageOutlined, FilePdfOutlined, CodeOutlined, FileTextOutlined, FolderFilled, FolderOpenFilled } from "@ant-design/icons-vue";
|
||||||
EyeInvisibleOutlined,
|
import { useFileTree } from "../composables/useFileTree";
|
||||||
EyeOutlined,
|
import { useTabs } from "../composables/useTabs";
|
||||||
ReloadOutlined,
|
import { useConfig } from "../composables/useConfig";
|
||||||
SearchOutlined,
|
import { message, Modal } from "ant-design-vue";
|
||||||
FileOutlined,
|
|
||||||
FileMarkdownOutlined,
|
|
||||||
FileImageOutlined,
|
|
||||||
FilePdfOutlined,
|
|
||||||
CodeOutlined,
|
|
||||||
FileTextOutlined,
|
|
||||||
FolderFilled,
|
|
||||||
FolderOpenFilled
|
|
||||||
} from '@ant-design/icons-vue';
|
|
||||||
import { useFileTree } from '../composables/useFileTree';
|
|
||||||
import { useTabs } from '../composables/useTabs';
|
|
||||||
import { useConfig } from '../composables/useConfig';
|
|
||||||
import { message, Modal } from 'ant-design-vue';
|
|
||||||
|
|
||||||
const { state, loadDirectory, refresh, searchFiles } = useFileTree();
|
const { state, loadDirectory, refresh, searchFiles } = useFileTree();
|
||||||
const { openFile } = useTabs();
|
const { openFile, activeTab } = useTabs();
|
||||||
const { config, addPinnedFile, removePinnedFile, setShowHiddenItems } = useConfig();
|
const { config, addPinnedFile, removePinnedFile, setShowHiddenItems } = useConfig();
|
||||||
|
|
||||||
const modalVisible = ref(false);
|
const modalVisible = ref(false);
|
||||||
const modalTitle = ref('');
|
const modalTitle = ref("");
|
||||||
const modalInputValue = ref('');
|
const modalInputValue = ref("");
|
||||||
const modalPlaceholder = ref('');
|
const modalPlaceholder = ref("");
|
||||||
const currentAction = ref(null);
|
const currentAction = ref(null);
|
||||||
const currentNode = ref(null);
|
const currentNode = ref(null);
|
||||||
const searchKeyword = ref('');
|
const searchKeyword = ref("");
|
||||||
|
const selectedKeys = ref([]);
|
||||||
|
const treeContentRef = ref(null);
|
||||||
|
|
||||||
|
// 监听 activeTab 变化,实现联动
|
||||||
|
watch(
|
||||||
|
activeTab,
|
||||||
|
async (tab) => {
|
||||||
|
if (tab?.filePath) {
|
||||||
|
await revealFile(tab.filePath);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const revealFile = async (filePath) => {
|
||||||
|
if (!state.rootPath || !filePath.startsWith(state.rootPath)) return;
|
||||||
|
|
||||||
|
// 1. 设置选中
|
||||||
|
selectedKeys.value = [filePath];
|
||||||
|
|
||||||
|
const separator = window.utools.isWindows() ? "\\" : "/";
|
||||||
|
|
||||||
|
// 2. 计算需要展开的父路径
|
||||||
|
let currentPath = filePath;
|
||||||
|
const parentsToLoad = [];
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const lastSepIndex = currentPath.lastIndexOf(separator);
|
||||||
|
if (lastSepIndex === -1) break;
|
||||||
|
|
||||||
|
currentPath = currentPath.substring(0, lastSepIndex);
|
||||||
|
|
||||||
|
// 如果已经到了根目录的上一级或者更短,停止
|
||||||
|
// 注意:state.rootPath 是根目录路径,我们需要展开的是根目录下的子目录
|
||||||
|
if (currentPath.length < state.rootPath.length) break;
|
||||||
|
if (currentPath === state.rootPath) break;
|
||||||
|
|
||||||
|
parentsToLoad.unshift(currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 逐级加载并展开
|
||||||
|
for (const path of parentsToLoad) {
|
||||||
|
// 即使已经展开,为了保险起见(可能之前加载失败或未完全加载),也可以检查一下
|
||||||
|
// 这里主要处理未展开的情况
|
||||||
|
if (!state.expandedKeys.includes(path)) {
|
||||||
|
// 确保数据已加载
|
||||||
|
await loadDirectory(path);
|
||||||
|
state.expandedKeys.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 滚动到可视区域
|
||||||
|
// 由于 Ant Design Vue 的 Tree 组件未开启虚拟滚动(virtual),scrollTo 方法可能无效。
|
||||||
|
// 我们回归到 DOM 操作,使用 scrollIntoView。
|
||||||
|
// 考虑到节点展开可能涉及 DOM 更新延迟或动画,我们增加一个简单的重试机制。
|
||||||
|
const tryScroll = (retryCount = 0) => {
|
||||||
|
if (!treeContentRef.value) return;
|
||||||
|
|
||||||
|
// 在当前组件范围内查找选中节点
|
||||||
|
// 尝试匹配 ant-tree-treenode-selected (行) 或 ant-tree-node-selected (内容)
|
||||||
|
const selectedNode = treeContentRef.value.querySelector(".ant-tree-treenode-selected") || treeContentRef.value.querySelector(".ant-tree-node-selected");
|
||||||
|
|
||||||
|
if (selectedNode) {
|
||||||
|
selectedNode.scrollIntoView({ block: "center", behavior: "smooth" });
|
||||||
|
} else {
|
||||||
|
// 如果没找到,且重试次数未耗尽,则延迟后重试
|
||||||
|
// 100ms * 10 = 1s,足够覆盖大部分展开动画和 DOM 渲染时间
|
||||||
|
if (retryCount < 20) {
|
||||||
|
setTimeout(() => tryScroll(retryCount + 1), 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
tryScroll();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 将 nodes 转换为 treeData,主要处理 isLeaf
|
// 将 nodes 转换为 treeData,主要处理 isLeaf
|
||||||
const transformNodes = (nodes) => {
|
const transformNodes = (nodes) => {
|
||||||
return nodes.map(node => ({
|
return nodes.map((node) => ({
|
||||||
...node,
|
...node,
|
||||||
isLeaf: node.type === 'file',
|
isLeaf: node.type === "file",
|
||||||
children: node.children === undefined ? undefined : transformNodes(node.children)
|
children: node.children === undefined ? undefined : transformNodes(node.children),
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -140,8 +180,8 @@ const showHiddenItems = computed(() => !!config.fileTree?.showHiddenItems);
|
||||||
|
|
||||||
const isHiddenItemName = (name) => {
|
const isHiddenItemName = (name) => {
|
||||||
if (!name) return false;
|
if (!name) return false;
|
||||||
if (name.startsWith('.')) return true;
|
if (name.startsWith(".")) return true;
|
||||||
if (name === 'node_modules') return true;
|
if (name === "node_modules") return true;
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -151,13 +191,13 @@ const filterNodes = (nodes) => {
|
||||||
// 1. 处理隐藏文件
|
// 1. 处理隐藏文件
|
||||||
let filtered = nodes;
|
let filtered = nodes;
|
||||||
if (!showHiddenItems.value) {
|
if (!showHiddenItems.value) {
|
||||||
filtered = nodes.filter(node => !isHiddenItemName(node?.name));
|
filtered = nodes.filter((node) => !isHiddenItemName(node?.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 递归处理子节点
|
// 递归处理子节点
|
||||||
return filtered.map(node => ({
|
return filtered.map((node) => ({
|
||||||
...node,
|
...node,
|
||||||
children: node.children === undefined ? undefined : filterNodes(node.children)
|
children: node.children === undefined ? undefined : filterNodes(node.children),
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -201,46 +241,46 @@ const onLoadData = (treeNode) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelect = (keys, { node }) => {
|
const onSelect = (keys, { node }) => {
|
||||||
if (node.dataRef.type === 'file') {
|
if (node.dataRef.type === "file") {
|
||||||
openFile(node.dataRef, state.rootPath);
|
openFile(node.dataRef, state.rootPath);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isMdFile = (name) => name && name.toLowerCase().endsWith('.md');
|
const isMdFile = (name) => name && name.toLowerCase().endsWith(".md");
|
||||||
|
|
||||||
const FILE_TYPE_CONFIG = {
|
const FILE_TYPE_CONFIG = {
|
||||||
md: { icon: FileMarkdownOutlined, color: '#1890ff' },
|
md: { icon: FileMarkdownOutlined, color: "#1890ff" },
|
||||||
markdown: { icon: FileMarkdownOutlined, color: '#1890ff' },
|
markdown: { icon: FileMarkdownOutlined, color: "#1890ff" },
|
||||||
png: { icon: FileImageOutlined, color: '#722ed1' },
|
png: { icon: FileImageOutlined, color: "#722ed1" },
|
||||||
jpg: { icon: FileImageOutlined, color: '#722ed1' },
|
jpg: { icon: FileImageOutlined, color: "#722ed1" },
|
||||||
jpeg: { icon: FileImageOutlined, color: '#722ed1' },
|
jpeg: { icon: FileImageOutlined, color: "#722ed1" },
|
||||||
gif: { icon: FileImageOutlined, color: '#722ed1' },
|
gif: { icon: FileImageOutlined, color: "#722ed1" },
|
||||||
webp: { icon: FileImageOutlined, color: '#722ed1' },
|
webp: { icon: FileImageOutlined, color: "#722ed1" },
|
||||||
svg: { icon: FileImageOutlined, color: '#722ed1' },
|
svg: { icon: FileImageOutlined, color: "#722ed1" },
|
||||||
ico: { icon: FileImageOutlined, color: '#722ed1' },
|
ico: { icon: FileImageOutlined, color: "#722ed1" },
|
||||||
pdf: { icon: FilePdfOutlined, color: '#f5222d' },
|
pdf: { icon: FilePdfOutlined, color: "#f5222d" },
|
||||||
js: { icon: CodeOutlined, color: '#52c41a' },
|
js: { icon: CodeOutlined, color: "#52c41a" },
|
||||||
json: { icon: CodeOutlined, color: '#52c41a' },
|
json: { icon: CodeOutlined, color: "#52c41a" },
|
||||||
ts: { icon: CodeOutlined, color: '#52c41a' },
|
ts: { icon: CodeOutlined, color: "#52c41a" },
|
||||||
css: { icon: CodeOutlined, color: '#52c41a' },
|
css: { icon: CodeOutlined, color: "#52c41a" },
|
||||||
html: { icon: CodeOutlined, color: '#52c41a' },
|
html: { icon: CodeOutlined, color: "#52c41a" },
|
||||||
vue: { icon: CodeOutlined, color: '#52c41a' },
|
vue: { icon: CodeOutlined, color: "#52c41a" },
|
||||||
xml: { icon: CodeOutlined, color: '#52c41a' },
|
xml: { icon: CodeOutlined, color: "#52c41a" },
|
||||||
yaml: { icon: CodeOutlined, color: '#52c41a' },
|
yaml: { icon: CodeOutlined, color: "#52c41a" },
|
||||||
yml: { icon: CodeOutlined, color: '#52c41a' },
|
yml: { icon: CodeOutlined, color: "#52c41a" },
|
||||||
txt: { icon: FileTextOutlined, color: 'inherit' },
|
txt: { icon: FileTextOutlined, color: "inherit" },
|
||||||
log: { icon: FileTextOutlined, color: 'inherit' },
|
log: { icon: FileTextOutlined, color: "inherit" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFileExtension = (fileName) => {
|
const getFileExtension = (fileName) => {
|
||||||
if (!fileName) return '';
|
if (!fileName) return "";
|
||||||
const lastDotIndex = fileName.lastIndexOf('.');
|
const lastDotIndex = fileName.lastIndexOf(".");
|
||||||
if (lastDotIndex === -1 || lastDotIndex === 0) return '';
|
if (lastDotIndex === -1 || lastDotIndex === 0) return "";
|
||||||
return fileName.substring(lastDotIndex + 1).toLowerCase();
|
return fileName.substring(lastDotIndex + 1).toLowerCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFileIcon = (fileName, type, expanded) => {
|
const getFileIcon = (fileName, type, expanded) => {
|
||||||
if (type === 'directory') {
|
if (type === "directory") {
|
||||||
return expanded ? FolderOpenFilled : FolderFilled;
|
return expanded ? FolderOpenFilled : FolderFilled;
|
||||||
}
|
}
|
||||||
const ext = getFileExtension(fileName);
|
const ext = getFileExtension(fileName);
|
||||||
|
|
@ -248,11 +288,11 @@ const getFileIcon = (fileName, type, expanded) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFileIconColor = (fileName, type) => {
|
const getFileIconColor = (fileName, type) => {
|
||||||
if (type === 'directory') {
|
if (type === "directory") {
|
||||||
return '#ffc60a'; // 文件夹黄色
|
return "#ffc60a"; // 文件夹黄色
|
||||||
}
|
}
|
||||||
const ext = getFileExtension(fileName);
|
const ext = getFileExtension(fileName);
|
||||||
return FILE_TYPE_CONFIG[ext]?.color || 'inherit';
|
return FILE_TYPE_CONFIG[ext]?.color || "inherit";
|
||||||
};
|
};
|
||||||
|
|
||||||
const isPinned = (path) => config.pinnedFiles.includes(path);
|
const isPinned = (path) => config.pinnedFiles.includes(path);
|
||||||
|
|
@ -261,62 +301,62 @@ const handleMenuClick = async ({ key }, node) => {
|
||||||
currentNode.value = node;
|
currentNode.value = node;
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'open':
|
case "open":
|
||||||
openFile(node, state.rootPath);
|
openFile(node, state.rootPath);
|
||||||
break;
|
break;
|
||||||
case 'pin':
|
case "pin":
|
||||||
if (isPinned(node.path)) {
|
if (isPinned(node.path)) {
|
||||||
await removePinnedFile(state.rootPath, node.path);
|
await removePinnedFile(state.rootPath, node.path);
|
||||||
message.success('已取消置顶');
|
message.success("已取消置顶");
|
||||||
} else {
|
} else {
|
||||||
await addPinnedFile(state.rootPath, node.path);
|
await addPinnedFile(state.rootPath, node.path);
|
||||||
message.success('已置顶');
|
message.success("已置顶");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'rename':
|
case "rename":
|
||||||
modalTitle.value = '重命名';
|
modalTitle.value = "重命名";
|
||||||
modalInputValue.value = node.name;
|
modalInputValue.value = node.name;
|
||||||
currentAction.value = 'rename';
|
currentAction.value = "rename";
|
||||||
modalVisible.value = true;
|
modalVisible.value = true;
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case "delete":
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '确认删除',
|
title: "确认删除",
|
||||||
content: `确定要删除 ${node.name} 吗?此操作不可恢复。`,
|
content: `确定要删除 ${node.name} 吗?此操作不可恢复。`,
|
||||||
okText: '删除',
|
okText: "删除",
|
||||||
okType: 'danger',
|
okType: "danger",
|
||||||
cancelText: '取消',
|
cancelText: "取消",
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
if (window.services?.deleteItem) {
|
if (window.services?.deleteItem) {
|
||||||
await window.services.deleteItem(node.path);
|
await window.services.deleteItem(node.path);
|
||||||
message.success('删除成功');
|
message.success("删除成功");
|
||||||
await refresh();
|
await refresh();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'copyPath':
|
case "copyPath":
|
||||||
// 使用 uTools 复制
|
// 使用 uTools 复制
|
||||||
window.utools.copyText(node.path);
|
window.utools.copyText(node.path);
|
||||||
message.success('路径已复制');
|
message.success("路径已复制");
|
||||||
break;
|
break;
|
||||||
case 'openInExplorer':
|
case "openInExplorer":
|
||||||
if (window.services?.openItemLocation) {
|
if (window.services?.openItemLocation) {
|
||||||
window.services.openItemLocation(node.path);
|
window.services.openItemLocation(node.path);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'newFile':
|
case "newFile":
|
||||||
modalTitle.value = '新建文件';
|
modalTitle.value = "新建文件";
|
||||||
modalInputValue.value = '';
|
modalInputValue.value = "";
|
||||||
modalPlaceholder.value = '请输入文件名 (包含后缀)';
|
modalPlaceholder.value = "请输入文件名 (包含后缀)";
|
||||||
currentAction.value = 'newFile';
|
currentAction.value = "newFile";
|
||||||
modalVisible.value = true;
|
modalVisible.value = true;
|
||||||
break;
|
break;
|
||||||
case 'newFolder':
|
case "newFolder":
|
||||||
modalTitle.value = '新建文件夹';
|
modalTitle.value = "新建文件夹";
|
||||||
modalInputValue.value = '';
|
modalInputValue.value = "";
|
||||||
modalPlaceholder.value = '请输入文件夹名';
|
modalPlaceholder.value = "请输入文件夹名";
|
||||||
currentAction.value = 'newFolder';
|
currentAction.value = "newFolder";
|
||||||
modalVisible.value = true;
|
modalVisible.value = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -324,7 +364,7 @@ const handleMenuClick = async ({ key }, node) => {
|
||||||
|
|
||||||
const handleModalOk = async () => {
|
const handleModalOk = async () => {
|
||||||
if (!modalInputValue.value) {
|
if (!modalInputValue.value) {
|
||||||
message.warning('请输入内容');
|
message.warning("请输入内容");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,39 +375,39 @@ const handleModalOk = async () => {
|
||||||
// 如果是 directory,新建在其中。
|
// 如果是 directory,新建在其中。
|
||||||
// 如果是 file,新建在同级。
|
// 如果是 file,新建在同级。
|
||||||
|
|
||||||
let parentPath = '';
|
let parentPath = "";
|
||||||
if (currentAction.value === 'newFile' || currentAction.value === 'newFolder') {
|
if (currentAction.value === "newFile" || currentAction.value === "newFolder") {
|
||||||
if (node.type === 'directory') {
|
if (node.type === "directory") {
|
||||||
parentPath = node.path;
|
parentPath = node.path;
|
||||||
} else {
|
} else {
|
||||||
parentPath = node.path.substring(0, node.path.lastIndexOf(window.utools.isWindows() ? '\\' : '/'));
|
parentPath = node.path.substring(0, node.path.lastIndexOf(window.utools.isWindows() ? "\\" : "/"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// rename
|
// rename
|
||||||
parentPath = node.path.substring(0, node.path.lastIndexOf(window.utools.isWindows() ? '\\' : '/'));
|
parentPath = node.path.substring(0, node.path.lastIndexOf(window.utools.isWindows() ? "\\" : "/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (currentAction.value === 'rename') {
|
if (currentAction.value === "rename") {
|
||||||
const newPath = node.path.replace(node.name, modalInputValue.value);
|
const newPath = node.path.replace(node.name, modalInputValue.value);
|
||||||
await window.services.renameItem(node.path, newPath);
|
await window.services.renameItem(node.path, newPath);
|
||||||
message.success('重命名成功');
|
message.success("重命名成功");
|
||||||
} else if (currentAction.value === 'newFile') {
|
} else if (currentAction.value === "newFile") {
|
||||||
const separator = window.utools.isWindows() ? '\\' : '/';
|
const separator = window.utools.isWindows() ? "\\" : "/";
|
||||||
const newPath = `${parentPath}${separator}${modalInputValue.value}`;
|
const newPath = `${parentPath}${separator}${modalInputValue.value}`;
|
||||||
await window.services.writeFile(newPath, ''); // 创建空文件
|
await window.services.writeFile(newPath, ""); // 创建空文件
|
||||||
message.success('创建文件成功');
|
message.success("创建文件成功");
|
||||||
} else if (currentAction.value === 'newFolder') {
|
} else if (currentAction.value === "newFolder") {
|
||||||
const separator = window.utools.isWindows() ? '\\' : '/';
|
const separator = window.utools.isWindows() ? "\\" : "/";
|
||||||
const newPath = `${parentPath}${separator}${modalInputValue.value}`;
|
const newPath = `${parentPath}${separator}${modalInputValue.value}`;
|
||||||
await window.services.createDirectory(newPath);
|
await window.services.createDirectory(newPath);
|
||||||
message.success('创建文件夹成功');
|
message.success("创建文件夹成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
modalVisible.value = false;
|
modalVisible.value = false;
|
||||||
await refresh();
|
await refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('操作失败: ' + error.message);
|
message.error("操作失败: " + error.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -417,7 +457,6 @@ const handleModalOk = async () => {
|
||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.empty-tip {
|
.empty-tip {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
@ -468,5 +507,4 @@ const handleModalOk = async () => {
|
||||||
background-color: var(--primary-color-bg, #e6f7ff) !important;
|
background-color: var(--primary-color-bg, #e6f7ff) !important;
|
||||||
color: var(--primary-color, #1890ff);
|
color: var(--primary-color, #1890ff);
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@
|
||||||
<a-list-item class="file-item" @click="handleOpenFile(item)">
|
<a-list-item class="file-item" @click="handleOpenFile(item)">
|
||||||
<div class="file-info">
|
<div class="file-info">
|
||||||
<FileMarkdownOutlined />
|
<FileMarkdownOutlined />
|
||||||
<span class="file-path">{{ item }}</span>
|
<span class="file-path">{{ getFileName(item) }}</span>
|
||||||
|
<span class="file-path-sub">{{ item }}</span>
|
||||||
</div>
|
</div>
|
||||||
</a-list-item>
|
</a-list-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue