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