1.为什么封装?
业务需要,公司PC端用到了树组件,pc端用EXt组件做的,相对来说比较容易,PDA需要同步,但是PDA用的框架比较古老,cube UI,相信很多人都没听过,下面这个实践我是引入了vantUI,因为现在毕竟没人用cubeUI,引入组件库的目的是用它自带的UI风格和ICON图标。下面是实践结果:
2.废话不多说,先介绍一下他的功能
2.1 单选,只能选中最后一级子节点(公司业务是这样,代码贴上可以自己改)
2.2 展开收缩功能
2.3 第一级手风琴功能(比较复杂,代码里边有注释)
2.4 回显功能
3.直接贴代码
app.vue
<template>
<div id="app">
<van-nav-bar title="Tree Component Demo" />
<N-tree
:treeData="treeData"
:selected-item="selectedItem"
@item-click="onSelectItem"
@toggle-expand="onExpandItem"
/>
</div>
</template>
<script>
import NTree from './components/N-tree.vue'
export default {
name: 'App',
components: {
NTree,
},
data() {
return {
selectedItem: null,
treeData: [
{
id: 1,
name: 'Node 1',
expanded: false,
leaf: false,
children: [
{
id: 2,
name: 'Node 1.1',
expanded: false,
leaf: false,
children: [
{ id: 3, name: 'Node 1.1.1', expanded: false, leaf: true, children: [] },
{
id: 4,
name: 'Node 1.1.2',
expanded: false,
leaf: false,
children: [
{ id: 5, name: 'Node 1.1.2.1', expanded: false, leaf: true, children: [] },
{ id: 6, name: 'Node 1.1.2.2', expanded: false, leaf: true, children: [] },
],
},
],
},
{ id: 7, name: 'Node 1.2', expanded: false, leaf: true, children: [] },
{
id: 8,
name: 'Node 1.3',
expanded: false,
leaf: false,
children: [
{
id: 9,
name: 'Node 1.3.1',
expanded: false,
leaf: false,
children: [
{
id: 10,
name: 'Node 1.3.1.1',
expanded: false,
leaf: false,
children: [
{ id: 11, name: 'Node 1.3.1.1.1', expanded: false, leaf: true, children: [] },
{ id: 12, name: 'Node 1.3.1.1.2', expanded: false, leaf: true, children: [] },
],
},
],
},
],
},
],
},
{
id: 13,
name: 'Node 2',
expanded: false,
leaf: false,
children: [
{
id: 14,
name: 'Node 2.1',
expanded: false,
leaf: false,
children: [
{ id: 15, name: 'Node 2.1.1', expanded: false, leaf: true, children: [] },
{
id: 16,
name: 'Node 2.1.2',
expanded: false,
leaf: false,
children: [
{
id: 17,
name: 'Node 2.1.2.1',
expanded: false,
leaf: false,
children: [
{
id: 18,
name: 'Node 2.1.2.1.1',
expanded: false,
leaf: false,
children: [
{ id: 19, name: 'Node 2.1.2.1.1.1', expanded: false, leaf: true, children: [] },
],
},
],
},
],
},
],
},
],
},
{
id: 20,
name: 'Node 3',
expanded: false,
leaf: false,
children: [
{
id: 21,
name: 'Node 3.1',
expanded: false,
leaf: false,
children: [
{
id: 22,
name: 'Node 3.1.1',
expanded: false,
leaf: false,
children: [
{
id: 23,
name: 'Node 3.1.1.1',
expanded: false,
leaf: false,
children: [
{ id: 24, name: 'Node 3.1.1.1.1', expanded: false, leaf: true, children: [] },
{ id: 25, name: 'Node 3.1.1.1.2', expanded: false, leaf: true, children: [] },
],
},
{
id: 26,
name: 'Node 3.1.1.2',
expanded: false,
leaf: false,
children: [
{
id: 27,
name: 'Node 3.1.1.2.1',
expanded: false,
leaf: true,
children: [],
},
],
},
],
},
],
},
],
},
],
}
},
methods: {
onSelectItem(item) {
this.selectedItem = item
console.log('Selected item:', item)
},
onExpandItem(item) {
console.log('Expand/Collapse item:', item)
},
},
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: left;
color: #2c3e50;
}
</style>
N-tree-item.vue
<template>
<div class="tree-item" :style="{ paddingLeft: `${level * 8}px` }">
<div class="tree-item-content" @click="onClickItem">
<span class="expand-toggle" v-if="item.children && item.children.length" @click.stop="onToggleExpand">
<van-icon :name="item.expanded ? 'arrow-down' : 'arrow'"></van-icon>
</span>
<span :class="{ selected: selectedItem && selectedItem.id === item.id }">{{ item.name }}</span>
<span v-if="selectedItem && selectedItem.id === item.id" class="selected-icon">
<van-icon name="certificate" />
</span>
</div>
<div v-if="item.children && item.children.length && item.expanded">
<N-tree-item
v-for="(child, index) in item.children"
:key="index"
:item="child"
:level="level + 1"
@item-click="$emit('item-click', $event)"
@toggle-expand="$emit('toggle-expand', $event)"
:selected-item="selectedItem"
/>
</div>
</div>
</template>
<script>
export default {
name: 'N-tree-item',
props: {
item: {
type: Object,
required: true,
},
level: {
type: Number,
default: 0,
},
selectedItem: {
type: Object,
default: () => null,
},
},
methods: {
onClickItem() {
if (this.item && this.item.leaf) {
this.$emit('item-click', this.item);
}
if (!this.item.leaf) {
this.onToggleExpand()
}
},
onToggleExpand() {
this.$emit('toggle-expand', this.item, this.level); // 传递 level 参数
},
},
}
</script>
<style scoped>
.tree-item {
margin-bottom: 5px;
}
.tree-item-content {
cursor: pointer;
line-height: 3rem;
font-size: 1.2rem;
border-bottom: 0.026667rem solid #D9D9D9;
white-space: normal;
word-break: break-word;
position: relative;
margin-left: 0.15rem;
}
.expand-toggle {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
font-size: 1rem;
}
.cubeic-arrow, .cubeic-select {
transition: transform 0.3s;
}
.selected {
color: green;
font-weight: bold;
}
.selected-icon {
position: absolute;
right: 5px;
top: 50%;
transform: translateY(-50%);
color: green;
}
.cubeic-ok::before {
content: "\E601";
color: green;
font-size: 25px;
}
.cubeic-arrow {
font-size: 26px;
color: #c8c8cd;
margin-right: 8px;
}
.cubeic-select::before {
content: "\E609";
color: #c8c8cd;
font-size: 25px;
margin-right: 8px;
}
.van-icon-arrow-down:before {
content: '\e65e';
font-size: 10px;
margin-right: 5px;
}
.van-icon-arrow:before {
content: '\e660';
font-size: 10px;
margin-right: 5px;
}
</style>
N-tree.vue
<template>
<div class="tree-container" style="border-top: 0.026667rem solid #D9D9D9;">
<group label-width="auto" label-margin-right="2em" label-align="left">
<N-tree-item
v-for="(item, index) in treeData"
:key="index"
:item="item"
:level="0"
@item-click="onItemClick"
@toggle-expand="onToggleExpand"
:selected-item="selectedItem"
/>
</group>
</div>
</template>
<script>
import NTreeItem from './N-tree-item.vue'
export default {
name: 'N-tree',
components: {
NTreeItem,
},
props: {
treeData: {
type: Array,
default: () => [],
},
selectedItem: {
type: Object,
default: () => null,
},
},
methods: {
onItemClick(item) {
this.$emit('item-click', item);
},
// 处理展开/折叠逻辑,确保第一层级只有一个节点展开
onToggleExpand(item, level) {
if (level === 0) {
// 只在第一层级应用手风琴效果
if (item.expanded) {
// 如果节点已经展开,则将其折叠
item.expanded = false;
} else {
// 如果要展开节点,则关闭同层级的其他节点及其所有子节点
this.closeSiblingsAndChildren(this.treeData, item);
// 然后展开当前节点
item.expanded = true;
}
} else {
// 对非第一层级的节点,正常展开/折叠
item.expanded = !item.expanded;
}
this.$emit('toggle-expand', item);
},
// 递归关闭第一层级的兄弟节点及其所有子节点
closeSiblingsAndChildren(nodes, currentItem) {
nodes.forEach(node => {
// 如果是第一层级的节点且不是当前点击的节点,则关闭
if (node !== currentItem && node.expanded) {
this.closeAllChildren(node); // 关闭该节点及其所有子节点
node.expanded = false;
}
});
},
// 递归关闭所有子节点
closeAllChildren(node) {
if (node.children && node.children.length) {
node.children.forEach(child => {
child.expanded = false;
// 递归关闭子节点的子节点
this.closeAllChildren(child);
});
}
}
},
}
</script>
<style scoped>
</style>
index.html
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Mobile Vue 2 App</title>
</head>
<body>
<div id="app"></div>
</body>
</html>