feat: 实现增删目录和文件以及重命名文件
This commit is contained in:
parent
3f693be1bd
commit
b718dc293e
177
src/App.vue
177
src/App.vue
@ -1,11 +1,35 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Split v-model="split">
|
||||||
|
<template #left>
|
||||||
|
<div class="split-left" :class="hiddenSplitLeft">
|
||||||
|
<LeftSidebar v-model:rootFolderPath="rootFolderPath" v-model:currentFilePath="currentFilePath">
|
||||||
|
</LeftSidebar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #trigger>
|
||||||
|
<div class="split-trigger"></div>
|
||||||
|
</template>
|
||||||
|
<template #right>
|
||||||
|
<div ref="splitRight" class="split-right">
|
||||||
|
<MarkdownEditor ref="markdownRef" width="100%" height="100%" :autoInit="false"
|
||||||
|
:markdownCode="markdownCode" :imageUpload="true" :imageUploadURL="imageUploadURLOfEditor"
|
||||||
|
:imageUploadURLChange="changeImageUploadURL" :onload="reloadEditorHeight"
|
||||||
|
:onFullScreenExit="handleWindowResize" @update:markdownCode="newCode => markdownCode = newCode"
|
||||||
|
:appendToolbar="appendToolbar" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Split>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import MarkdownEditor from './components/MarkdownEditor.vue'
|
||||||
|
import LeftSidebar from './components/LeftSidebar.vue'
|
||||||
import { ref, watch, useTemplateRef, onMounted, onUnmounted } from "vue";
|
import { ref, watch, useTemplateRef, onMounted, onUnmounted } from "vue";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
|
import { readTextFile, writeTextFile, exists } from '@tauri-apps/plugin-fs';
|
||||||
import { Message } from 'view-ui-plus'
|
import { Message } from 'view-ui-plus'
|
||||||
import MarkdownEditor from './components/MarkdownEditor.vue'
|
|
||||||
import SelectFolder from './components/SelectFolder.vue'
|
|
||||||
import FolderTree from './components/FolderTree.vue'
|
|
||||||
|
|
||||||
// 原始示例数据,仅供参考
|
// 原始示例数据,仅供参考
|
||||||
const greetMsg = ref("");
|
const greetMsg = ref("");
|
||||||
@ -15,8 +39,44 @@ async function greet() {
|
|||||||
greetMsg.value = await invoke("greet", { name: name.value });
|
greetMsg.value = await invoke("greet", { name: name.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用 Split 组件动态控制页面左右布局
|
||||||
|
const split = ref(0.2);
|
||||||
|
const splitRight = useTemplateRef('splitRight');
|
||||||
|
const hiddenSplitLeft = ref("");
|
||||||
|
watch(split, (newSplit) => {
|
||||||
|
// 当 split 小于某个值时,隐藏左边布局
|
||||||
|
if (newSplit < 0.05) {
|
||||||
|
split.value = 0.01
|
||||||
|
if (hiddenSplitLeft.value == "") {
|
||||||
|
hiddenSplitLeft.value = "hidden"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (hiddenSplitLeft.value == "hidden") {
|
||||||
|
hiddenSplitLeft.value = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态调整 MarkdownEditor 的宽度。如果不执行 reset 的话,CodeMirror 内部编辑器不会自适应宽度
|
||||||
|
reloadEditorWidth()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听根目录路径。如果之前已经选过目录,初始界面直接加载该目录
|
||||||
|
const rootFolderPath = ref(localStorage.getItem('rootPathOfFolderTree') || "");
|
||||||
|
watch(rootFolderPath, (newRootPath) => {
|
||||||
|
localStorage.setItem('rootPathOfFolderTree', newRootPath);
|
||||||
|
})
|
||||||
|
|
||||||
|
// <FolderTree> 完成文件树渲染后,点击文件得到 currentFilePath
|
||||||
|
const currentFilePath = ref(localStorage.getItem('currentSelectedFilePath') || "");
|
||||||
|
// 读取 currentFilePath 的文件内容,填充到 MarkdownEditor 之中
|
||||||
|
watch(currentFilePath, async (newFilePath) => {
|
||||||
|
localStorage.setItem('currentSelectedFilePath', newFilePath);
|
||||||
|
await readFileContent(newFilePath);
|
||||||
|
})
|
||||||
|
|
||||||
|
// 加载 Markdown 编辑器
|
||||||
const markdownRef = ref(null);
|
const markdownRef = ref(null);
|
||||||
const markdownCode = ref("# Hello Markdown")
|
const markdownCode = ref("# Hello Markdown");
|
||||||
const appendToolbar = [
|
const appendToolbar = [
|
||||||
{
|
{
|
||||||
name: "|",
|
name: "|",
|
||||||
@ -45,15 +105,16 @@ const appendToolbar = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
// 支持从 localStorage 读写 imageUploadURL
|
// 支持从 localStorage 读写 imageUploadURL
|
||||||
const imageUploadURLOfEditor = ref(localStorage.getItem('imageUploadURLOfEditor') || "")
|
const imageUploadURLOfEditor = ref(localStorage.getItem('imageUploadURLOfEditor') || "");
|
||||||
function changeImageUploadURL(newImageUploadURL) {
|
function changeImageUploadURL(newImageUploadURL) {
|
||||||
console.log("new imageUploadURL: ", newImageUploadURL)
|
console.log("new imageUploadURL: ", newImageUploadURL);
|
||||||
localStorage.setItem('imageUploadURLOfEditor', newImageUploadURL)
|
localStorage.setItem('imageUploadURLOfEditor', newImageUploadURL);
|
||||||
}
|
}
|
||||||
async function initMarkdownEditor() {
|
async function initMarkdownEditor() {
|
||||||
|
// 打开上一次选择的文件的内容
|
||||||
if (currentFilePath.value.length > 0) {
|
if (currentFilePath.value.length > 0) {
|
||||||
try {
|
try {
|
||||||
markdownCode.value = await readTextFile(currentFilePath.value)
|
markdownCode.value = await readTextFile(currentFilePath.value);
|
||||||
Message.success('已读取文件内容,并加载到编辑器中:' + currentFilePath.value);
|
Message.success('已读取文件内容,并加载到编辑器中:' + currentFilePath.value);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Message.error('文件读取失败:' + err);
|
Message.error('文件读取失败:' + err);
|
||||||
@ -63,76 +124,24 @@ async function initMarkdownEditor() {
|
|||||||
}
|
}
|
||||||
// 矫正 markdown 编辑器的高度
|
// 矫正 markdown 编辑器的高度
|
||||||
function reloadEditorHeight() {
|
function reloadEditorHeight() {
|
||||||
markdownRef.value.resetHeight(window.innerHeight)
|
markdownRef.value.resetHeight(window.innerHeight);
|
||||||
}
|
}
|
||||||
// 矫正 markdown 编辑器的宽度
|
// 矫正 markdown 编辑器的宽度
|
||||||
function reloadEditorWidth() {
|
function reloadEditorWidth() {
|
||||||
// 可以使用 splitRight.value.offsetWidth 也可以使用 100% 来调整
|
// 可以使用 splitRight.value.offsetWidth 也可以使用 100% 来调整
|
||||||
const width = splitRight.value.offsetWidth
|
const width = splitRight.value.offsetWidth;
|
||||||
markdownRef.value.resetWidth("100%")
|
markdownRef.value.resetWidth("100%");
|
||||||
}
|
}
|
||||||
// 监听页面宽度和高度,调整 markdown 编辑器的高宽度
|
// 监听页面宽度和高度,调整 markdown 编辑器的高宽度
|
||||||
const handleWindowResize = () => {
|
const handleWindowResize = () => {
|
||||||
reloadEditorHeight()
|
reloadEditorHeight();
|
||||||
reloadEditorWidth()
|
reloadEditorWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 动态控制页面左右布局
|
|
||||||
const split = ref(0.2)
|
|
||||||
const hiddenSplitRight = ref("")
|
|
||||||
const splitRight = useTemplateRef('splitRight')
|
|
||||||
watch(split, (newSplit, oldSplit) => {
|
|
||||||
// 当 split 小于某个值时,隐藏左边布局
|
|
||||||
if (newSplit < 0.05) {
|
|
||||||
split.value = 0.01
|
|
||||||
if (hiddenSplitRight.value == "") {
|
|
||||||
hiddenSplitRight.value = "hidden"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (hiddenSplitRight.value == "hidden") {
|
|
||||||
hiddenSplitRight.value = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 动态调整 MarkdownEditor 的宽度。如果不执行 reset 的话,CodeMirror 内部编辑器不会自适应宽度
|
|
||||||
reloadEditorWidth()
|
|
||||||
})
|
|
||||||
|
|
||||||
// 如果之前已经选过目录,那么直接加载该目录
|
|
||||||
const rootPathOfFolderTree = ref(localStorage.getItem('rootPathOfFolderTree') || "")
|
|
||||||
function changeRootPath(newRootPath) {
|
|
||||||
localStorage.setItem('rootPathOfFolderTree', newRootPath)
|
|
||||||
rootPathOfFolderTree.value = newRootPath
|
|
||||||
}
|
|
||||||
// <SelectFolder> 返回的文件树数据,增加自定义数据,便于传给 <FolderTree> 进行渲染
|
|
||||||
function appendDataForNode(nodeData) {
|
|
||||||
if (!nodeData.directory) {
|
|
||||||
// 给文件节点,增加 suffix 字段,用于表示文件后缀,便于 <FolderTree> 过滤文件
|
|
||||||
let splitResult = nodeData.name.split('.');
|
|
||||||
let fileSuffix = splitResult.length > 1 ? splitResult.pop() : '';
|
|
||||||
nodeData.suffix = fileSuffix;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// <SelectFolder> 选择目录后赋值给 folderTreeData,然后再传递给 <FolderTree>
|
|
||||||
const folderTreeData = ref(null)
|
|
||||||
function showFileTree(treeData) {
|
|
||||||
folderTreeData.value = treeData
|
|
||||||
}
|
|
||||||
|
|
||||||
// <FolderTree> 完成文件树渲染后,点击文件得到 currentFilePath
|
|
||||||
// 读取 currentFilePath 的文件内容,填充到 MarkdownEditor 之中
|
|
||||||
const currentFilePath = ref(localStorage.getItem('currentSelectedFilePath') || "");
|
|
||||||
async function fileSelected(fileNodeData) {
|
|
||||||
console.log(fileNodeData)
|
|
||||||
const filePath = fileNodeData.path
|
|
||||||
localStorage.setItem('currentSelectedFilePath', filePath)
|
|
||||||
await readFileContent(filePath)
|
|
||||||
}
|
|
||||||
async function readFileContent(filePath) {
|
async function readFileContent(filePath) {
|
||||||
try {
|
try {
|
||||||
markdownRef.value.setMarkdownCode(await readTextFile(filePath))
|
markdownRef.value.setMarkdownCode(await readTextFile(filePath));
|
||||||
currentFilePath.value = filePath
|
currentFilePath.value = filePath;
|
||||||
Message.success('已读取文件内容,并加载到编辑器中:' + filePath);
|
Message.success('已读取文件内容,并加载到编辑器中:' + filePath);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Message.error('文件读取失败:' + err);
|
Message.error('文件读取失败:' + err);
|
||||||
@ -145,6 +154,10 @@ async function writeFileContent() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
const exist = await exists(currentFilePath.value);
|
||||||
|
if (!exist) {
|
||||||
|
throw new Error(currentFilePath.value + " 文件不存在.");
|
||||||
|
}
|
||||||
await writeTextFile(currentFilePath.value, markdownRef.value.getMarkdownCode())
|
await writeTextFile(currentFilePath.value, markdownRef.value.getMarkdownCode())
|
||||||
Message.success('文件更新成功:' + currentFilePath.value);
|
Message.success('文件更新成功:' + currentFilePath.value);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -161,34 +174,6 @@ onUnmounted(() => {
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<Split v-model="split">
|
|
||||||
<template #left>
|
|
||||||
<div class="split-left">
|
|
||||||
<SelectFolder :class="hiddenSplitRight" :rootPath="rootPathOfFolderTree"
|
|
||||||
:nodeDataCallback="appendDataForNode" @update:rootPath="changeRootPath"
|
|
||||||
@folder-selected="showFileTree" />
|
|
||||||
<FolderTree :class="hiddenSplitRight" :treeData="folderTreeData" :expandLevel="1"
|
|
||||||
:selectedNode="[currentFilePath]" :specifyFileSuffix="['md']" @file-selected="fileSelected" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #trigger>
|
|
||||||
<div class="split-trigger"></div>
|
|
||||||
</template>
|
|
||||||
<template #right>
|
|
||||||
<div ref="splitRight" class="split-right">
|
|
||||||
<MarkdownEditor ref="markdownRef" width="100%" height="100%" :autoInit="false"
|
|
||||||
:markdownCode="markdownCode" :imageUpload="true" :imageUploadURL="imageUploadURLOfEditor"
|
|
||||||
:imageUploadURLChange="changeImageUploadURL" :onload="reloadEditorHeight"
|
|
||||||
:onFullScreenExit="handleWindowResize" @update:markdownCode="newCode => markdownCode = newCode"
|
|
||||||
:appendToolbar="appendToolbar" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Split>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
body {
|
body {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
281
src/components/LeftSidebar.vue
Normal file
281
src/components/LeftSidebar.vue
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
<template>
|
||||||
|
<SelectFolder :rootPath="rootFolderPath" :nodeDataCallback="appendDataForNode" @update:rootPath="changeRootPath"
|
||||||
|
@folder-selected="showFileTree" />
|
||||||
|
<FolderTree ref="folderTreeRef" :treeData="folderTreeData" :expandLevel="1" :selectedNode="[currentFilePath]"
|
||||||
|
:specifyFileSuffix="['md']" @file-selected="fileSelected" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import SelectFolder from './SelectFolder.vue';
|
||||||
|
import FolderTree from './FolderTree.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { mkdir, remove, create, exists, rename } from '@tauri-apps/plugin-fs';
|
||||||
|
import { Message } from 'view-ui-plus'
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
currentFilePath: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
rootFolderPath: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits([
|
||||||
|
'update:currentFilePath',
|
||||||
|
'update:rootFolderPath'
|
||||||
|
]);
|
||||||
|
|
||||||
|
const folderTreeRef = ref(null)
|
||||||
|
// <SelectFolder> 返回的文件树数据,增加自定义数据,便于传给 <FolderTree> 进行渲染
|
||||||
|
function appendDataForNode(nodeData) {
|
||||||
|
if (nodeData.directory) {
|
||||||
|
// 当前节点为文件夹,增加“新建目录”“新建文件”等右键菜单
|
||||||
|
nodeData.contextMenuData = [
|
||||||
|
{
|
||||||
|
name: "新建目录",
|
||||||
|
handler: (nodeRenderData) => {
|
||||||
|
// 新建子节点时自动展开当前文件夹
|
||||||
|
nodeRenderData.expand = true;
|
||||||
|
// 定义子节点为 input 输入框
|
||||||
|
let subNodeData = {
|
||||||
|
input: true,
|
||||||
|
completer: async (data, newName) => {
|
||||||
|
newName = newName.trim();
|
||||||
|
// 如果输入名称为空,则取消当前节点
|
||||||
|
if (newName.length == 0) {
|
||||||
|
folderTreeRef.value.removeTreeNode(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 文件名不能包含 / 符号
|
||||||
|
if (newName.indexOf('/') >= 0) {
|
||||||
|
Message.error('不能包含 / 符号');
|
||||||
|
folderTreeRef.value.removeTreeNode(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let createSubNodeOfParent = false;
|
||||||
|
// 输入名称不为空,尝试在该目录下新建子节点
|
||||||
|
try {
|
||||||
|
const newPath = nodeData.path + '/' + newName;
|
||||||
|
data.sourceData.path = newPath;
|
||||||
|
data.sourceData.name = newName;
|
||||||
|
// 给新增的子节点也执行 appendDataForNode 操作
|
||||||
|
appendDataForNode(data.sourceData);
|
||||||
|
// 重新渲染当前节点
|
||||||
|
if (folderTreeRef.value.reRenderTreeNode(data) === false) {
|
||||||
|
throw new Error("变更文件树失败");
|
||||||
|
}
|
||||||
|
createSubNodeOfParent = true;
|
||||||
|
// 最后再尝试操作系统创建目录
|
||||||
|
await mkdir(newPath);
|
||||||
|
Message.success('创建目录成功: ' + newPath);
|
||||||
|
} catch (err) {
|
||||||
|
Message.error('创建目录失败: ' + err);
|
||||||
|
// 如果最后操作系统接口失败,需要回滚文件树新增的节点
|
||||||
|
if (createSubNodeOfParent) {
|
||||||
|
folderTreeRef.value.removeTreeNode(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
path: "", // 留空,在 completer 中填充
|
||||||
|
name: "", // 留空,在 completer 中填充
|
||||||
|
directory: true, // 当前新建的是目录,所以为 true
|
||||||
|
};
|
||||||
|
// 将子节点挂载到文件树上,在当前目录之下
|
||||||
|
folderTreeRef.value.appendTreeNode(nodeRenderData, subNodeData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "新建文件",
|
||||||
|
handler: (nodeRenderData) => {
|
||||||
|
// 新建子节点时自动展开当前文件夹
|
||||||
|
nodeRenderData.expand = true;
|
||||||
|
// 定义子节点为 input 输入框
|
||||||
|
let subNodeData = {
|
||||||
|
input: true,
|
||||||
|
completer: async (data, newName) => {
|
||||||
|
newName = newName.trim();
|
||||||
|
// 如果输入名称为空,则取消当前节点
|
||||||
|
if (newName.length == 0) {
|
||||||
|
folderTreeRef.value.removeTreeNode(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 文件名不能包含 / 符号
|
||||||
|
if (newName.indexOf('/') >= 0) {
|
||||||
|
Message.error('不能包含 / 符号');
|
||||||
|
folderTreeRef.value.removeTreeNode(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 如果文件不以 .md 为后缀,同样取消当前输入框
|
||||||
|
if (!newName.endsWith('.md')) {
|
||||||
|
Message.error('只允许 .md 文件');
|
||||||
|
folderTreeRef.value.removeTreeNode(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let createSubNodeOfParent = false;
|
||||||
|
// 输入名称不为空,尝试在该目录下新建子节点
|
||||||
|
try {
|
||||||
|
const newPath = nodeData.path + '/' + newName;
|
||||||
|
// 检查新的路径是否已存在,避免覆盖其他文件
|
||||||
|
const exist = await exists(newPath);
|
||||||
|
if (exist) {
|
||||||
|
// 恢复当前节点并报错
|
||||||
|
folderTreeRef.value.removeTreeNode(data);
|
||||||
|
throw new Error("存在同名文件.");
|
||||||
|
}
|
||||||
|
data.sourceData.path = newPath;
|
||||||
|
data.sourceData.name = newName;
|
||||||
|
// 给新增的子节点也执行 appendDataForNode 操作
|
||||||
|
appendDataForNode(data.sourceData);
|
||||||
|
// 重新渲染当前节点
|
||||||
|
if (folderTreeRef.value.reRenderTreeNode(data) === false) {
|
||||||
|
throw new Error("变更文件树失败");
|
||||||
|
}
|
||||||
|
createSubNodeOfParent = true;
|
||||||
|
// 最后再尝试操作系统创建目录
|
||||||
|
await create(newPath);
|
||||||
|
Message.success('创建文件成功: ' + newPath);
|
||||||
|
} catch (err) {
|
||||||
|
Message.error('创建文件失败: ' + err);
|
||||||
|
// 如果最后操作系统接口失败,需要回滚文件树新增的节点
|
||||||
|
if (createSubNodeOfParent) {
|
||||||
|
folderTreeRef.value.removeTreeNode(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
path: "", // 留空,在 completer 中填充
|
||||||
|
name: "", // 留空,在 completer 中填充
|
||||||
|
directory: false, // 当前新建的是文件,所以为 false
|
||||||
|
};
|
||||||
|
// 将子节点挂载到文件树上,在当前目录之下
|
||||||
|
folderTreeRef.value.appendTreeNode(nodeRenderData, subNodeData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "删除",
|
||||||
|
handler: async (nodeRenderData) => {
|
||||||
|
try {
|
||||||
|
if (folderTreeRef.value.removeTreeNode(nodeRenderData) === false) {
|
||||||
|
throw new Error("变更文件树失败");
|
||||||
|
}
|
||||||
|
await remove(nodeRenderData.path, { recursive: true });
|
||||||
|
Message.success('删除目录成功: ' + nodeRenderData.path);
|
||||||
|
} catch (err) {
|
||||||
|
Message.error('删除目录失败: ' + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
// 给文件节点,增加 suffix 字段,用于表示文件后缀,便于 <FolderTree> 过滤文件
|
||||||
|
let splitResult = nodeData.name.split('.');
|
||||||
|
let fileSuffix = splitResult.length > 1 ? splitResult.pop() : '';
|
||||||
|
nodeData.suffix = fileSuffix;
|
||||||
|
// 当前节点为文件,增加“重命名”“删除”等右键菜单
|
||||||
|
nodeData.contextMenuData = [
|
||||||
|
{
|
||||||
|
name: "重命名",
|
||||||
|
handler: (nodeRenderData) => {
|
||||||
|
// 将该节点转为 input 输入框
|
||||||
|
nodeRenderData.sourceData.input = true;
|
||||||
|
nodeRenderData.sourceData.completer = async (data, newName) => {
|
||||||
|
newName = newName.trim();
|
||||||
|
// 如果输入名称为空,不做任何变动,恢复当前节点
|
||||||
|
if (newName.length == 0) {
|
||||||
|
folderTreeRef.value.reRenderTreeNode(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 文件名不能包含 / 符号
|
||||||
|
if (newName.indexOf('/') >= 0) {
|
||||||
|
Message.error('不能包含 / 符号');
|
||||||
|
folderTreeRef.value.reRenderTreeNode(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 如果文件不以 .md 为后缀,同样恢复当前节点
|
||||||
|
if (!newName.endsWith('.md')) {
|
||||||
|
Message.error('只允许 .md 文件');
|
||||||
|
folderTreeRef.value.reRenderTreeNode(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 尝试重命名该节点
|
||||||
|
let newRenderNode = null;
|
||||||
|
let oldName = '';
|
||||||
|
let oldPath = nodeData.path;
|
||||||
|
let splitPathResult = oldPath.split('/');
|
||||||
|
try {
|
||||||
|
if (splitPathResult.length <= 1) {
|
||||||
|
// 恢复当前节点并报错
|
||||||
|
folderTreeRef.value.reRenderTreeNode(data);
|
||||||
|
throw new Error("该节点文件路径非法.");
|
||||||
|
}
|
||||||
|
oldName = splitPathResult.pop();
|
||||||
|
let newPath = splitPathResult.join('/') + '/' + newName;
|
||||||
|
// 检查新的路径是否已存在,避免覆盖其他文件
|
||||||
|
const exist = await exists(newPath);
|
||||||
|
if (exist) {
|
||||||
|
// 恢复当前节点并报错
|
||||||
|
folderTreeRef.value.reRenderTreeNode(data);
|
||||||
|
throw new Error("存在同名文件.");
|
||||||
|
}
|
||||||
|
data.sourceData.path = newPath;
|
||||||
|
data.sourceData.name = newName;
|
||||||
|
// 新的节点数据需要执行 appendDataForNode 操作
|
||||||
|
appendDataForNode(data.sourceData);
|
||||||
|
// 重新渲染当前节点
|
||||||
|
newRenderNode = folderTreeRef.value.reRenderTreeNode(data);
|
||||||
|
if (newRenderNode === false) {
|
||||||
|
throw new Error("变更文件树失败");
|
||||||
|
}
|
||||||
|
// 最后再尝试操作系统重命名
|
||||||
|
await rename(oldPath, newPath);
|
||||||
|
Message.success('重命名成功: ' + newPath);
|
||||||
|
} catch (err) {
|
||||||
|
Message.error('重命名失败: ' + err);
|
||||||
|
// 如果最后操作系统接口失败,需要回滚文件树新增的节点
|
||||||
|
if (newRenderNode !== null && newRenderNode !== false) {
|
||||||
|
newRenderNode.sourceData.path = oldPath;
|
||||||
|
newRenderNode.sourceData.name = oldName;
|
||||||
|
folderTreeRef.value.reRenderTreeNode(newRenderNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 将当前节点重新渲染为输入框
|
||||||
|
folderTreeRef.value.reRenderTreeNode(nodeRenderData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "删除",
|
||||||
|
handler: async (nodeRenderData) => {
|
||||||
|
try {
|
||||||
|
if (folderTreeRef.value.removeTreeNode(nodeRenderData) === false) {
|
||||||
|
throw new Error("变更文件树失败");
|
||||||
|
}
|
||||||
|
await remove(nodeRenderData.path, { recursive: true });
|
||||||
|
Message.success('删除文件成功: ' + nodeRenderData.path);
|
||||||
|
} catch (err) {
|
||||||
|
Message.error('删除文件失败: ' + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// <SelectFolder> 选择目录后赋值给 folderTreeData,然后再传递给 <FolderTree>
|
||||||
|
const folderTreeData = ref(null)
|
||||||
|
function showFileTree(treeData) {
|
||||||
|
folderTreeData.value = treeData
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeRootPath = (newRootPath) => {
|
||||||
|
emit('update:rootFolderPath', newRootPath); // 触发双向绑定更新
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileSelected = (fileRenderData) => {
|
||||||
|
emit('update:currentFilePath', fileRenderData.path); // 触发双向绑定更新
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Loading…
x
Reference in New Issue
Block a user