feat: 实现增删目录和文件以及重命名文件

This commit is contained in:
Frankie Huang 2025-04-20 01:45:45 +08:00
parent 3f693be1bd
commit b718dc293e
3 changed files with 362 additions and 96 deletions

View File

@ -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>
import MarkdownEditor from './components/MarkdownEditor.vue'
import LeftSidebar from './components/LeftSidebar.vue'
import { ref, watch, useTemplateRef, onMounted, onUnmounted } from "vue";
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 MarkdownEditor from './components/MarkdownEditor.vue'
import SelectFolder from './components/SelectFolder.vue'
import FolderTree from './components/FolderTree.vue'
//
const greetMsg = ref("");
@ -15,8 +39,44 @@ async function greet() {
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 markdownCode = ref("# Hello Markdown")
const markdownCode = ref("# Hello Markdown");
const appendToolbar = [
{
name: "|",
@ -45,15 +105,16 @@ const appendToolbar = [
},
];
// localStorage imageUploadURL
const imageUploadURLOfEditor = ref(localStorage.getItem('imageUploadURLOfEditor') || "")
const imageUploadURLOfEditor = ref(localStorage.getItem('imageUploadURLOfEditor') || "");
function changeImageUploadURL(newImageUploadURL) {
console.log("new imageUploadURL: ", newImageUploadURL)
localStorage.setItem('imageUploadURLOfEditor', newImageUploadURL)
console.log("new imageUploadURL: ", newImageUploadURL);
localStorage.setItem('imageUploadURLOfEditor', newImageUploadURL);
}
async function initMarkdownEditor() {
//
if (currentFilePath.value.length > 0) {
try {
markdownCode.value = await readTextFile(currentFilePath.value)
markdownCode.value = await readTextFile(currentFilePath.value);
Message.success('已读取文件内容,并加载到编辑器中:' + currentFilePath.value);
} catch (err) {
Message.error('文件读取失败:' + err);
@ -63,76 +124,24 @@ async function initMarkdownEditor() {
}
// markdown
function reloadEditorHeight() {
markdownRef.value.resetHeight(window.innerHeight)
markdownRef.value.resetHeight(window.innerHeight);
}
// markdown
function reloadEditorWidth() {
// 使 splitRight.value.offsetWidth 使 100%
const width = splitRight.value.offsetWidth
markdownRef.value.resetWidth("100%")
const width = splitRight.value.offsetWidth;
markdownRef.value.resetWidth("100%");
}
// markdown
const handleWindowResize = () => {
reloadEditorHeight()
reloadEditorWidth()
reloadEditorHeight();
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) {
try {
markdownRef.value.setMarkdownCode(await readTextFile(filePath))
currentFilePath.value = filePath
markdownRef.value.setMarkdownCode(await readTextFile(filePath));
currentFilePath.value = filePath;
Message.success('已读取文件内容,并加载到编辑器中:' + filePath);
} catch (err) {
Message.error('文件读取失败:' + err);
@ -145,6 +154,10 @@ async function writeFileContent() {
return;
}
try {
const exist = await exists(currentFilePath.value);
if (!exist) {
throw new Error(currentFilePath.value + " 文件不存在.");
}
await writeTextFile(currentFilePath.value, markdownRef.value.getMarkdownCode())
Message.success('文件更新成功:' + currentFilePath.value);
} catch (err) {
@ -161,34 +174,6 @@ onUnmounted(() => {
})
</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>
body {
overflow: hidden;

View 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>