树形列表: 可以无限次递归显示
设想: 递归组件 + 控制元素的显隐(dom操作/vue数据控制)
第一步:实现递归组件 伪代码
// 引用组件的页面 v-if="item.children" 不成为死循环
<tree-component :propdata='data' v-if="item.children"></tree-component>
...
// 递归数据
data: [{
name: '1级',
url: '/url1',
children: [{
name: '2级',
url: '/url2'
children: [{
name: '3级',
url: '/url3'
}]
}]
}]
// 子组件
<div class="container">
<div v-for="item in propdata" class="item-div" @click="handleClick($event)">
<p>{{item.name}}<p>
// 递归核心
<tree-component v-if="item.children" :propdata="item.children"></tree-component>
</div>
</div>
new Vue({
name: 'treeComponent' // 当前组件定义名字属性,才能被递归引用
})
第二步:控制元素的显隐
渲染出的页面大概
<div class="container">
<div class="item-div">
<p>上一级</p>
<div class="container">
<p> 子元素 </p>
...
</div>
</div>
</div>
1. dom元素直接隐藏
-- 只要会js的display,就能写
-- 弃用:1.vue最好不直接操作dom 2.不好控制动画,子级内容显示隐藏应该有上下收缩
<div class="container">
<div v-for="item in propdata" class="item-div" @click="handleClick($event)">
<p>{{item.name}}<p>
// 递归核心
<tree-component v-if="item.children" :propdata="item.children($event, item)"></tree-component>
</div>
</div>
js:
handleClick (event, item) {
// item有子级就是展开、关闭功能,没有子级跳转到指定路由
if (item.children && item.children.length > 0) {
// event.currentTarget响应绑定事件的元素 event.target响应点击的元素
// childNodes 是class = item-div元素
let chidNodes = event.currentTarget.childNodes;
for(let index = 0; index < childNodes.length; index++){
// 找到包含‘contaienr’的元素 = 当前点击的下一级
let itemChild = childNodes[index];
let className = itemChild.className;
if(className && className.indexOf('contaienr') > -1){
let dispaly = itemChild.style.display;
if(display ==== 'none'){
itemChild.style.display = 'block';
}else{
itemChild.style.display = 'none';
}
}
}
}else {
this.$router.push(item.url)
}
}
2.监听数据改变 -- 数据中每一级加一个变量visible,控制自身
// 父组件 -- 不直接改变data值
dataForEach(data, boolean){ // boolean控制全部显示还是隐藏 true隐藏
data.forEach((item, index) => {
if (Array.isArray(item.children)) {
item.visible = boolean
// 只有两层, 为了全部隐藏或展开
this.putDataChange(index, item)
this.dataForEach(item.children, boolean)
}
})
},
// 改变数据
putDataChange(index, val) {
this.$set(this.data, index, val)
}
-------加上了visible
// component.js 递归组件
<div class="container">
<div v-for="item in propdata" class="item-div" @click="handleClick($event, item, index)">
<p>{{item.name}}<p>
<tree-component v-if="item.children&&item.visible" :propdata="item.children($event, item)"></tree-component>
</div>
</div>
new Vue({
props:['propdata'], // 不能直接使用,需要监听变化
data () {
return {
items: []
}
},
watch: {
'propdata': function(val){
this.items = val
}
},
mounted () {
this.items = this.propdata
},
methods: {
handleClick (event, prop,index) {
if (prop.hasOwnProperty('visible')) {
prop.visible = !prop.visible
// vue的数组更新检测,直接改变一个值不会检测变动
// 导致只能递归一次 (~~~)
this.$emit('putDataChange', index, prop) // 父元素改变数据
} else {
this.$router.push(prop.url)
}
}
}
})
额外问题: url地址输入对应的导航高亮
<div v-for="item in propdata" class="item-div" :class="{'active': item.url === activeUrl}"...>
js:
watch:{
'$route': function(to, from){
this.activeIndex = to.path
}
},
mounted () {
this.activeIndex = this.$route.path
}
目前为止出现两个问题: 1. 只能递归一次,硬伤 2.收缩展开的动画,检测height高度变化
- 问题1: 只能递归一次
- 根本原因 :在无法更新数组数据
- 解决方案: 每次改变替换数组,深拷贝
- 过程: 开始想用递归组件上绑定父子通信事件,以为事件也会每个递归直到最外层父组件,结果。。。考虑祖孙通信事件,递归组件不知道怎么绑定,哎。。。想到不同组件通信可以用bus事件总线,感觉应该行。因为项目有用vuex就选择了它,果然很好用。
vuex-1.js
state: {
data: ...
},
CHANGEDATA(state, data) {
state.data = data
},
actions: {
changedata ({commit, state}) {
// 考虑到与后台会有异步操作
let data = []
data = deepCopy(state.data) // deepCopy深拷贝
commit('CHANGEDATA', data)
},
}
// 子组件
handleClick(item){
item.visible = !item.visible
this.$store.dispatch('changedata')
}
问题2:根据height收缩动画,开始dom操作在每次点击后对应display代码块中,后来用数组数据操作,还是想着先获取子级高度,再在用户点击的元素上去加动画。。。
第一个失败尝试:前提是dom操作
缺点: 1.操作dom,还加了个data-height,2.因为默认展开,第一次height=0,没有任何效果
template:
<div class="container" :data-height='propdata.length * 40 + 1'></div>
js:
handleClick(){
.... 这里代码是dom操作的click
if(display === 'none'){
itemChild.style.display = 'block';
// 纯dom操作
itemChild.style.height = itemChild.getAttribute('data-height') + 'px'
}else{
itemChild.style.display = 'none';
itemChild.style.height = 0
}
}
第二个失败尝试: 前提是数组visible控制
template:
// 亮(xia)点(yan):style绑定了height,还是用data-height记录高度。
只有两级,每一个元素高度相同 40 -- 重点:只能有两级,不然高度完全不对
.... <tree-component v-if="item.children"
:style="{height: !item.visible ? item.children.length * 40 + 1 + 'px' : 0}"
:data-height='item.children.length * 40 + 1'
:propData="item.children" :activeclass="activeclass" ></tree-component>
js: 不变--毕竟只能控制两级
看着element-ui的实现,心中一阵凉意。于是学(bei)习(bi)了一把,还是要好好学习。哪怕现在我没什么用 看了有收缩效果的el-tree\el-collapse,都用了一个东西import ElCollapseTransition from 'element-ui/src/transitions/collapse-transition'; 看到了transition+scrollHeight 哇~~~我是渣渣
// 子组件
template: vuex版
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:before-leave="beforeLeave"
v-on:leave="leave"
>
<tree-component>...</tree-componet>
</transition>
js:
--- 只是高度,源码中还有paddingTop..我没有考虑padding。。。
beforeEnter (el) {
el.style.height = '0';
},
enter (el) {
if (el.scrollHeight !== 0) {
el.style.height = el.scrollHeight + 'px';
} else {
el.style.height = '';
}
el.style.overflow = 'hidden';
},
afterEnter (el) {
el.style.height = ''
},
beforeLeave (el) {
el.style.height = el.scrollHeight + 'px'
el.style.overflow = 'hidden';
},
leave (el) {
if (el.scrollHeight !== 0) {
el.style.height = 0
}
}
假装总结:树形列表: 递归组件 + bus/vuex通信改变数组数据 + height动画(transition+scrollHeight)
问题: 走了太多弯路,组件不完善,只是简单实现。没有懒加载
加后台数据
存在几个问题:
- 1.store状态中的id值不能直接在mounted中获取, id来源是页面头组件中赋值
computed: { deptId () { return this.$store.state.userInfo.id } }, watch: { deptId: function (val) { this.$store.commit("CHANGEDATA", val) // must id } } - 2.删除按钮和删除弹窗中的确定不在同一组件,vuex
- 3.展开点击后请求后台数据,添加 删除 移动 === 需要增加字段children(子级),level(间距left),visible(显示或隐藏) === 各个功能中添加后台接口