首先根据产品需求,确定该组件的功能有哪些!!!
一、结合功能需求我想到的是
- 1、data传入树型数据
- 2、提供自定义节点插槽(类似于el-tree的default插槽)
- 3、右击方法
- 4、单击收缩/展开当前节点的子节点
二、定义数据结构
[
{
children:[],
name:"菜单1",
open:false, // 为了方便我放在一起了
...
//其他的属性随便你了
}
]
三、目录结构
// components/TreeC
-- TreeC
------index.vue
------Tree.vue
使用示例
<TreeC:data="fileTree" @right-click="divRightClick" @node-click="nodeClick">
<template #default="{ data }">
<!-- // 这里我只写了名字,其他的样式排版随便你了 -->
<div>
{{ data.name }}
</div>
</template>
</TreeC>
下面我们先处理插槽的问题。
使用示例中已经开始使用插槽了,这个时候我需要在tree中使用插槽,但是我的结构是这样的
//TreeC/index.vue
<template>
<div class="overflow-auto">
<div class="pb-4">
<div class="px-16px">根目录</div>
<Tree :data="treeData" class="pr-16px"></Tree>
</div>
</div>
</template>
//我的tree组件应该是这样的
// TreeC/Tree.vue
<template>
<div class=" pl-30px">
<div v-for="d in data" :key="d.name" class=" pt-22px">
<div class="flex items-center" :style="{ '--item-left': itemLeft }">
<!-- 标题 -->
<div class="flex-1 z-2" @contextmenu.stop="($event) => rightClick($event, d)" @click.stop="changeOpenStatus(d)">
{{ d.name }}
</div>
</template>
</div>
<Tree v-if="d.children && d.children.length > 0 && d.open" :data="d.children" :parent-data="d"></Tree>
</div>
</div>
</template>
// 这里我已经给节点添加了右击(rightClick),单机(changeOpenStatus)
看到这里我是如何把插槽传递给Tree组件呢?
因为在index.vue我是不需要使用这个default插槽的,所以我想到了provide/inject
于是:
// TreeC/index.vue
<script lang="ts" setup>
import Tree from "./Tree.vue";
import { PROJECT_PROVIDE_SYMBOL } from "@/static/symbol";
const slots = getCurrentInstance()?.slots;
provide(PROJECT_PROVIDE_SYMBOL, { slots });
</script>
//TreeC/Tree.vue
const parentInject = inject<{
slots: {
[key: string]: VNode;
};
}>(PROJECT_PROVIDE_SYMBOL);
这个时候不管Tree在实际循环中嵌套多少层树,我都能拿到index.vue传过来的default插槽!
走到这里我该如何渲染呢?这个时候我发现我的组件文件需要扩充,于是我新建了个NodeTitle.vue
// components/TreeC
-- TreeC
------index.vue
------Tree.vue
------NodeTitle.vue
修改后:
// TreeC/Tree.vue
<template>
<div class="pl-30px">
<div v-for="d in data" :key="d.name" class="pt-22px">
<div class="flex items-center" :style="{ '--item-left': itemLeft }">
<!-- 标题 -->
<div class=" flex-1 z-2" @contextmenu.stop="($event) => rightClick($event, d)" @click.stop="changeOpenStatus(d)">
<NodeTitle v-if="parentInject && parentInject.slots.default" :data="d"></NodeTitle>
{{ d.name }}
</div>
</template>
</div>
<Tree v-if="d.children && d.children.length > 0 && d.open" :data="d.children" :parent-data="d"></Tree>
</div>
</div>
</template>
//TreeC/NodeTitle.vue
<script lang="ts">
import { defineComponent, inject, renderSlot, VNode } from "vue";
import { TreeFile } from "./type";
import { PROJECT_PROVIDE_SYMBOL } from "@/static/symbol";
export default defineComponent({
name: "TreeNodeTitle",
props: {
data: {
required: true,
type: Object as () => TreeFile
}
},
setup(props) {
const parentInject = inject<{
slots: {
[key: string]: VNode;
};
}>(PROJECT_PROVIDE_SYMBOL);
return () => {
return renderSlot(parentInject?.slots as unknown as any, "default", { data: props.data });
};
}
});
</script>
这样插槽就完成了
四、右击、单击方法实现
同样使用了provide/inject
// TreeC/index.vue
const emits = defineEmits(["right-click", "node-click"]);
function rightNodeClick(event: Event, nodeData: ProjectTree, node: TreeNode) {
emits("right-click", event, nodeData, node);
}
function nodeClick(nodeData: ProjectTree, node: any) {
emits("node-click", nodeData, node);
}
provide(PROJECT_PROVIDE_SYMBOL, { rightNodeClick, nodeClick, slots });
// TreeC/Tree.vue
const parentInject = inject<{
rightNodeClick: (event: Event, data: TreeFile, node?: TreeNode) => void;
nodeClick: (data: TreeFile, node?: TreeNode) => void;
slots: {
[key: string]: VNode;
};
}>(PROJECT_PROVIDE_SYMBOL);
function rightClick(event: Event, nodeData: TreeFile) {
parentInject && parentInject.rightNodeClick(event, nodeData, { level: props.lavel!, parent: props.parentData, data: nodeData });
}
function changeOpenStatus(e: TreeFile) {
e.open = !e.open;
parentInject && parentInject.nodeClick(e, { level: props.lavel!, parent: props.parentData, data: e });
}
实现方法千千万,首先需要滤清思路~~
欢迎前端交流
QQ群:362909884(禁止广告、内置GPT机器人)