用vue3封装一个树形结构
时间: 2023-05-26 20:06:48 浏览: 143
基于ztree封装的Vue树形组件,轻松实现海量数据的高性能渲染.zip
以下是一个基于Vue3的树形结构组件封装,包含展开/折叠、勾选/取消勾选、选中/取消选中等常见功能。
```html
<template>
<div class="tree">
<div v-for="item in data" :key="item.id">
<div :class="{ 'tree-node': true, 'has-children': item.children && item.children.length > 0, 'expanded': item.expanded, 'checked': item.checked, 'selected': item.selected }">
<span class="tree-node-toggler" v-if="item.children && item.children.length > 0" @click="toggleNode(item)">{{ item.expanded ? '-' : '+' }}</span>
<label class="tree-node-label">
<input type="checkbox" v-if="showCheckbox" v-model="item.checked" @change="checkNode(item)">
<span class="tree-node-text" :class="{ 'tree-node-selected': item.selected }" @click="selectNode(item)">{{ item.name }}</span>
</label>
</div>
<Tree v-if="item.children && item.children.length > 0 && item.expanded" :data="item.children" :show-checkbox="showCheckbox" @node-selected="handleNodeSelected"></Tree>
</div>
</div>
</template>
<script>
import { defineComponent, watch } from 'vue'
export default defineComponent({
name: 'Tree',
props: {
data: {
type: Array,
required: true
},
showCheckbox: {
type: Boolean,
default: false
},
},
emits: ['node-selected'],
setup(props, { emit }) {
const toggleNode = (node) => {
node.expanded = !node.expanded
}
const checkNode = (node) => {
if (node.checked && node.children) {
node.children.forEach((child) => {
child.checked = true
})
} else if (!node.checked && node.children) {
node.children.forEach((child) => {
child.checked = false
})
}
if (node.parent) {
const siblings = node.parent.children
const allChecked = siblings.every((sibling) => sibling.checked)
const allUnchecked = siblings.every((sibling) => !sibling.checked)
if (allChecked) {
node.parent.checked = true
} else if (allUnchecked) {
node.parent.checked = false
} else {
node.parent.checked = null
}
}
}
const selectNode = (node) => {
const traverse = (node, fn) => {
fn(node)
if (node.children && node.children.length > 0) {
node.children.forEach((child) => {
traverse(child, fn)
})
}
}
traverse(props.data[0], (n) => {
n.selected = (n === node)
})
emit('node-selected', node)
}
const handleNodeSelected = (node) => {
emit('node-selected', node)
}
watch(() => props.data, () => {
props.data.forEach((node) => {
if (node.children && node.children.length > 0) {
node.children.forEach((child) => {
child.parent = node
})
} else {
node.children = null
}
})
}, { deep: true })
return {
toggleNode,
checkNode,
selectNode,
handleNodeSelected
}
}
})
</script>
<style scoped>
.tree {
margin-left: 16px;
}
.tree-node {
display: flex;
align-items: center;
margin-top: 4px;
}
.tree-node-toggler {
margin-right: 4px;
font-size: 12px;
cursor: pointer;
}
.tree-node-label {
display: flex;
align-items: center;
cursor: pointer;
}
.tree-node-text {
margin-left: 4px;
}
.tree-node-selected {
font-weight: bold;
}
.has-children .tree-node-toggler:before {
content: '\f067';
font-family: 'Font Awesome 5 Free';
font-weight: 900;
margin-right: 4px;
}
.expanded .tree-node-toggler:before {
content: '\f068';
}
.checked .tree-node-text {
text-decoration: line-through;
}
.selected .tree-node-text {
background-color: #e6f7ff;
}
</style>
```
使用方式:
```html
<template>
<Tree :data="data" :show-checkbox="true" @node-selected="handleNodeSelected"></Tree>
</template>
<script>
import { defineComponent, reactive } from 'vue'
import Tree from './Tree.vue'
export default defineComponent({
name: 'App',
components: { Tree },
setup() {
const data = reactive([
{
id: 1,
name: 'Node 1',
children: [
{
id: 2,
name: 'Node 1.1',
children: [
{
id: 3,
name: 'Node 1.1.1'
},
{
id: 4,
name: 'Node 1.1.2'
}
]
},
{
id: 5,
name: 'Node 1.2'
},
{
id: 6,
name: 'Node 1.3',
children: [
{
id: 7,
name: 'Node 1.3.1'
}
]
}
]
},
{
id: 8,
name: 'Node 2'
}
])
const handleNodeSelected = (node) => {
console.log('Selected node:', node)
}
return {
data,
handleNodeSelected
}
}
})
</script>
```
阅读全文