一个简洁的树形组件实现,作为一个练手。
index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>树形组件</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<link rel="stylesheet" type="text/css" href = "./index.css" />
<style type="text/css"></style>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.5.2/vue.min.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
</head>
<body>
<template id="tree">
<ul class="ul-wrapper">
<li v-for="(item,idx) in list" :key="idx">
<div class="tree-node-content" @click="updateExpand(item)">
<icon :style="{opacity: item.children ? 1 : 0}" :class="['el-icon','el-icon-caret-right',item.expanded ? 'caret-down' : '']"></icon>
<el-checkbox
v-model="item.choosed"
@change="chooseChildren(item)"></el-checkbox>
<span class="label">{{ item.label }}</span>
</div>
<transition name="tree-show">
<tree v-show="item.expanded" v-if="item.children" :list="item.children"></tree>
</transition>
</li>
</ul>
</template>
<main id="app" class="container">
<tree :list="dat"></tree>
</main>
<script src="./index.js"></script>
</body>
</html>
这里用到el-icon,是一个箭头,用.caret-down
控制箭头向右or向下。
index.css
/* 参考:
https://element.eleme.cn/#/zh-CN/component/tree
https://cn.vuejs.org/v2/guide/transitions.html#%E8%BF%87%E6%B8%A1%E6%A8%A1%E5%BC%8F
*/
body{
margin: 0;
}
ul{
list-style-type: none;
}
.container{
width: 100%;
height: 100vh;
display: grid;
place-items: center;
}
.ul-wrapper{
background-color: white;
user-select: none;
color: #606266;
font-size: 14px;
padding-left: 16px;
}
.tree-node-content{
cursor: pointer;
padding: 4px 0;
}
.ul-wrapper .tree-node-content:hover{
background-color: #f5f7fa;
}
.tree-node-content icon{
transition: all .5s;
}
icon.caret-down{
transform: rotate(90deg);
}
.tree-show-enter-active, .tree-show-leave-active {
transition: opacity .5s;
}
.tree-show-enter-to, .tree-show-leave-to {
opacity: 0;
}
貌似都很显然,没啥说的。
index.js
"use strict";
function main() {
Vue.component('tree', {
props: {
list: Array
},
created() {
this.list.forEach(item => {
this.$set(item, 'expanded', false)
this.$set(item, 'choosed', false)
return item
})
},
methods: {
updateExpand(item) {
item.expanded = !item.expanded
},
chooseChildren(item, val = false, dep = 0) {
if (dep) {
item.choosed = val
} else {
val = item.choosed//根节点已更改过
}
if (!item.hasOwnProperty('children')) return
for (let ch of item.children) {
this.chooseChildren(ch, val, dep + 1)
}
}
},
template: '#tree'
})
let vm = new Vue({
el: '#app',
data() {
return {
dat: [{
label: '一级 1',
children: [{
label: '二级 1-1',
children: [{
label: '三级 1-1-1'
}]
}]
}, {
label: '一级 2',
children: [{
label: '二级 2-1',
children: [{
label: '三级 2-1-1'
}]
}, {
label: '二级 2-2',
children: [{
label: '三级 2-2-1'
}]
}]
}, {
label: '一级 3',
children: [{
label: '二级 3-1',
children: [{
label: '三级 3-1-1'
}]
}, {
label: '二级 3-2',
children: [{
label: '三级 3-2-1'
},{
label: '三级 3-2-2'
}]
}]
}]
}
},
methods: {}
});
}
$(document).ready(main);
组件在created这个生命周期插入两条新属性,expanded表示是否展开children,choosed表示是否选中(规定所有后代的选中状态应等于当前节点的)。在created时插入,这样插入新属性的代码就只会在组件创建的时候执行。
chooseChildren就是一个dfs,更新所有后代的选中状态。值得注意的是,被点击的那个节点已经修改为最新了。我们用深度==0判定是不是根,是根就获取所有后代应取的共同值,否则进行赋值。