完整代码+在线演示
Treecatalogue: 自封装树形结构目录组件 (gitee.com)
使用
- 数据源
const treeData = ref([
{
id:1,
label:'a',
value:'1',
children:[
{
id:2,
label:'a-a',
value:'2',
children:[
{
id:3,
label:'a-a-a',
value:'3',
children:[]
},
{
id:4,
label:'a-a-b',
value:'4',
children:[]
}
]
}
]
},
{
id:5,
label:'b',
value:'5',
children:[
{
id:6,
label:'b-a',
value:'6',
children:[
{
id:7,
label:'b-a-a',
value:'7',
children:[]
},
{
id:8,
label:'b-a-b',
value:'8',
children:[]
}
]
}
]
},
{
id:9,
label:'c',
value:'9',
children:[
{
id:10,
label:'c-a',
value:'10',
children:[]
}
]
}
])
- 使用方式
<TreeCatalogue
:treeData="treeData" @Remove="remove" @Create="create"
@Rename="rename" @click="nodeClick">
</TreeCatalogue>
实现过程
vue 注册自定义事件
最开始思路是,注册自定义事件
defineProps 和 defineEmits 进行父子组件通信
但是实现树形控件的过程中,采取了递归(子组件里)
然后bug 点击子组件不能触发父组件
pinia实现通信
全局都能访问到的store 存储是否点击信息
//创建列表数据相关的小仓库
import { defineStore } from 'pinia'
let dataListStore = defineStore('datalist', {
//小仓库存储数据的地方
state(){
return {
name:'',
id:0,
isClickDelete:false,
isClickAdd:false,
isClickRename:false,
isClickNode:false,
}
}
})
//对外暴露获取小仓库的方法
export default dataListStore;
子组件中
//点击事件
const toggleChildren=(item:any)=>{
//给当前点击的节点添加属性
item.isOpen=!item.isOpen
console.log("点击了",item);
// item.isOpen = !item.isOpen;
store.isClickNode=true
}
//删除
const deleteItem=(id:number)=>{
store.id=id;
console.log("removeId",store.id);
store.isClickDelete=!store.isClickDelete;
//子组件触发不了,递归触发不了这个方法
// emit('Remove',id)
open.value = false;
}
//增加
const addItem=(id:number)=>{
store.id=id;
store.name=content.value
console.log("addId",store.id);
//当连续点击两次的时候,watch监听不到,因为两次点击的是同一个id
store.isClickAdd=!store.isClickAdd;
// emit('Create',id)
open2.value = false;
}
//重命名
const renameItem=(id:number)=>{
store.id=id;
store.name=newName.value
console.log("renameId",store.id);
//当连续点击两次的时候,watch监听不到,因为两次点击的是同一个id
store.isClickRename=!store.isClickRename;
// emit('Rename',id)
open3.value = false;
}
父组件中
//删除
const remove = (id:number)=>{
console.log("删除id",id);
//如何删除对应id
removeNodeById(treeData.value,id)
}
//添加
const create = (id:number)=>{
console.log("///add");
//给元素id==id 的元素的children 数组添加元素,name=store.name
getMaxId(treeData.value)
maxId+=1//maxId改变
// 创建一个新的子节点
const newChild = {
id: maxId,
label: store.name,
value: store.name,
children: []
};
getNodeById(treeData.value,id,newChild)
}
//重命名
const rename = (id:number)=>{
// 给元素id==id 的元素 重命名为store.name
renameNodeById(treeData.value,id,store.name)
}
//点击
const nodeClick=(id:number)=>{
console.log("点击了",id);
}
//监听pinia中数据的改变
//节点点击
watch(isClickDelete,()=>{
nodeClick(id.value)
})
//删除
watch(isClickDelete,()=>{
remove(id.value)
})
//重命名
watch(isClickRename,()=>{
rename(id.value)
})
//添加
watch(isClickAdd,()=>{
create(id.value)
})
具体实现增加,删除,重命名的方法
/递归遍历树形结构删除
function removeNodeById(tree:any, idToRemove:number) {
// 遍历当前层级的节点
for (let i = tree.length - 1; i >= 0; i--) {
const node = tree[i];
// 如果当前节点的 id 匹配要删除的 id
if (node.id === idToRemove) {
// 获取id对应的数组下标
const index = tree.indexOf(node);
// 删除当前节点
tree.splice(index, 1);
// 无需继续遍历子节点,因为当前节点已被删除
return;
}
// 如果当前节点有子节点,递归调用以删除子树中的节点
if (node.children && node.children.length > 0) {
removeNodeById(node.children, idToRemove);
}
}
}
//递归遍历树形结构获取当前元素,给当前元素添加子元素
function getNodeById(tree:any, idToAddChild:number,newChild:any) {
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
// 如果找到匹配的节点
if (node.id === idToAddChild) {
// 确保 children 数组存在
if (!node.children) {
node.children = [];
}
// 添加新的子节点
node.children.push(newChild);
return; // 找到后返回,不需要继续遍历
}
// 如果当前节点有子节点,递归查找
if (node.children && node.children.length > 0) {
getNodeById(node.children, idToAddChild, newChild);
}
}
}
//递归遍历树形结构获取当前元素.重命名当前元素
function renameNodeById(tree:any, idToRename:number, newName:string) {
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
// 如果找到匹配的节点
if (node.id === idToRename) {
// 重命名当前节点
node.label = newName;
node.value = newName;
return; // 找到后返回,不需要继续遍历
}
// 如果当前节点有子节点,递归查找
if (node.children && node.children.length > 0) {
renameNodeById(node.children, idToRename, newName);
}
}
}
//递归获取目前已有的最大id
function getMaxId(tree:any) {
// 遍历当前层级的节点
for (let i = tree.length - 1; i >= 0; i--) {
const node = tree[i];
// 如果当前节点的 id 大于 maxId,则更新 maxId
if (node.id > maxId) {
maxId = node.id;
}
// 如果当前节点有子节点,递归调用更新maxId
if (node.children && node.children.length > 0) {
getMaxId(node.children);
}
}
}