feat: 文件树支持右键菜单,以及增删节点操作

This commit is contained in:
Frankie Huang 2025-04-15 02:04:36 +08:00
parent e4b5fab2fa
commit a3cf386bd5

View File

@ -1,5 +1,12 @@
<template>
<Tree ref="tree" :data="treeRenderData" :render="renderContent" expand-node></Tree>
<Tree ref="tree" :data="treeRenderData" :render="renderContent" @on-contextmenu="handleContextMenu" expand-node>
<template #contextMenu>
<DropdownItem v-for="item in currentMenuNode.contextMenuData" :key="item.name"
@click="item.handler(currentMenuNode)">
{{ item.name }}
</DropdownItem>
</template>
</Tree>
</template>
<script>
@ -20,6 +27,16 @@ export default {
suffix: "txt",
directory: false,
}],
// 如果定义了 contextMenuData,则开启右键菜单
// 需要定义 name 和 handler,分别对应显示的文本和点击事件
contextMenuData: [
{
name: "新建目录",
handler: (nodeRenderData) => {
console.log(nodeRenderData);
}
}
],
},
},
selectedNode: {
@ -46,7 +63,9 @@ export default {
},
data() {
return {
root: null,
treeRenderData: [],
currentMenuNode: {},
}
},
mounted() {
@ -58,17 +77,27 @@ export default {
},
},
methods: {
// 以 treeData 为源数据,渲染文件树
// 以 treeData 为源数据,生成完整的文件树数据 treeRenderData
renderTreeByTreeData(currentTreeData) {
const renderNode = this.renderNodeByNodeData(currentTreeData, 0)
this.treeRenderData = renderNode == null ? [] : [renderNode]
},
// 渲染文件树节点。level 表示 nodeData 在文件树的层级(从 0 开始计数)
// 生成文件树节点数据。level 表示 nodeData 在文件树的层级(从 0 开始计数)
renderNodeByNodeData(nodeData, level) {
// 判断必须包含的字段,没有则返回 null
if (nodeData == null || nodeData == undefined) {
return null
}
// 如果 input 为 true,保留原 nodeData 数据,然后渲染成 input 框,并绑定输入完成事件
if (nodeData.input === true) {
let nodeRenderData = nodeData;
nodeRenderData.input = true;
nodeRenderData.completer = nodeData.completer ?
nodeData.completer : (data, value) => {
this.$Message.error("Input Node have no completer.");
};
return nodeRenderData
}
if (nodeData.path == undefined ||
nodeData.name == undefined ||
nodeData.directory == undefined ||
@ -98,6 +127,12 @@ export default {
path: nodeData.path,
level: level,
}
if (nodeData.contextMenuData && nodeData.contextMenuData.length > 0) {
// contextmenu = true 用于开启右键菜单
nodeRenderData.contextmenu = true
// contextMenuData 是渲染右键菜单的源数据
nodeRenderData.contextMenuData = nodeData.contextMenuData
}
if (nodeData.directory) {
let childrenRenderData = []
nodeData.nodes.forEach(node => {
@ -113,7 +148,45 @@ export default {
}
return nodeRenderData
},
// 负责文件树每个节点的渲染,决策每个节点的样式
renderContent(h, { root, node, data }) {
// 将 root 树存储起来,便于后续操作文件树增删节点
this.root = root;
// 给 data 绑定 nodeKey 和 parentKey,便于向上索引
data.nodeKey = node.nodeKey;
data.parentKey = node.parent;
// 将节点渲染为输入框,并绑定输入完成事件
if (data.input) {
return h('span', {
style: {
display: 'inline-block',
width: '100%'
},
}, [
h('span', [
// h(resolveComponent('Input'), {
// 使用 resolveComponent 时 blur 事件失效,改用原生 input 框
h('Input', {
type: 'text',
autofocus: true,
autoCorrect: "off",
spellCheck: "false",
class: 'ivu-input ivu-input-default',
style: { marginRight: '8px' },
// 当 input 框触发 enter 或 blur 事件时,执行 completer 回调
onKeyup: (event) => {
if (event.key === "Enter") {
data.completer(data, event.target.value)
}
},
onBlur: (event) => {
data.completer(data, event.target.value)
}
}),
])
]);
}
// 其他正常节点渲染事件
return h('span', {
style: {
display: 'inline-block',
@ -132,9 +205,10 @@ export default {
])
]);
},
selectNode(nodeData) {
// 当点击某个节点时,设置选中状态,并触发事件
selectNode(nodeRenderData) {
// 文件夹不做任何动作
if (nodeData.directory) {
if (nodeRenderData.directory) {
return
}
@ -143,10 +217,37 @@ export default {
selectedNodes.forEach(element => {
element.selected = false
});
nodeData.selected = true
nodeRenderData.selected = true
// 向父组件暴露当前选中的文件
this.$emit('file-selected', nodeData);
this.$emit('file-selected', nodeRenderData);
},
// 给一个节点增加子节点
appendTreeNode(parentRenderData, nodeData) {
const renderNode = this.renderNodeByNodeData(nodeData, parent.level + 1);
// 由于文件树有过滤规则,此处 renderNode 可能为 null
if (renderNode == null) {
return false;
}
parentRenderData.children.push(renderNode);
return true;
},
// 删除文件树中某个子节点
removeTreeNode(nodeRenderData) {
try {
const parentKey = this.root.find(el => el.nodeKey === nodeRenderData.nodeKey).parent;
const parent = this.root.find(el => el.nodeKey === parentKey).node;
const index = parent.children.indexOf(nodeRenderData);
parent.children.splice(index, 1);
return true;
} catch (err) {
console.log("remove treeNode failed.");
return false;
}
},
// 对某个节点进行右键操作时,给 currentMenuNode 赋值,便于渲染右键菜单
handleContextMenu(nodeRenderData) {
this.currentMenuNode = nodeRenderData;
},
}
}