效果尽量仿着 element 做。预览地址
组件之间的通信
<x-menu :selected.sync=”selected”
>
<x-sub-menu name=”extension”>
<template slot=”title”> 扩展 </template>
<x-menu-item name=”mac”>for Mac</x-menu-item>
<x-menu-item name=”windows”>for Windows</x-menu-item>
</x-sub-menu>
<x-sub-menu name=”learn”>
<template slot=”title”> 如何使用 </template>
<x-menu-item name=”fast”> 快速入门 </x-menu-item>
<x-menu-item name=”advanced”> 进阶配置 </x-menu-item>
<x-menu-item name=”package”> 多语言支持 </x-menu-item>
</x-menu>
selected 的传递
如图所示,关于(selected: 被选中的那个 item), 在 menu 里面控制,通过 watchChild“监听每个 item”, 一旦 menu-item 被点击便会触发
this.$emit(‘menuItemUpdate’,this.name)
这里 menu 便会通过 menu-item 的 $emit 和 $on 实现数据的传递
watchChild(){
this.items.forEach(vm=>{
vm.$on(‘menuItemUpdate’,name=>{
this.$emit(‘update:selected’,[name])
})
})
},
这里的 this.items 就是收集的每一个 menu-item, 这在一开始就已经完成了。要用到依赖注入, 这里面子组件 menu-item 都直接操作 menu 的 data,耦合度非常高。
//menu
provide(){
return {
root:this
}
}
//menu-item
inject:[‘root’],
created(){
this.root.addItem(this)
//………
},
然后告诉 menu-item 你可以被选中了。updated 钩子函数用作完成这个任务再适合不过了
updated(){
this.updateChild()
},
methods:{
updateChild(){
this.items.forEach(vm=>{
if(this.selected.indexOf(vm.name)>-1){
vm.selected = true
}else{
vm.selected = false
}
})
}
}
路径元素的收集
当 menu-item 被选中时,触发 tellParents 函数,收集这条路径上所有父元素的 name, 把这个收集好的数据放到 selectedArr 里面。这个还可以用来高亮路径上的父元素。在 click 的时候触发 onClick 函数
onClick(){
//………..
let subFather = this.$parent.$el.classList.contains(‘x-sub-menu’) this.$parent.$el.classList.contains(‘x-sub-menu’)
let groupFather = this.$parent.$el.classList.contains(‘x-menu-item-group’)
//……….
if(subFather||groupFather){
//…….
this.tellParents(this)
//…….
}else{
//…….
}
},
tellParents 递归调用自身来收集 name
tellParents(that){
if(that.$parent.$parent.$options.name===’x-sub-menu’ ||that.$parent.$parent.$options.name===’x-menu-item-group’){
this.root.selectedArr.unshift(that.$parent.name)
this.tellParents(that.$parent)
}else{
this.root.selectedArr.unshift(that.$parent.name)
console.log(this.root.selectedArr)
}
},
最后就是我希望在 menu-item 被选中后,可以关闭路径的所有弹出框 popover。这同样需要一个递归遍历
childClosePopover(){
if(this.$parent.$options.name===’x-sub-menu’){
this.open = false
this.$parent.childClosePopover()
}
},
自定义主题颜色的实现
参照了一下 Menu Attribute。这里的文字颜色和图标颜色是同步的,hover 的 css 效果和 active 的一样
text-color 文字颜色。
active-color 被选中颜色。
back-ground-color 正常背景色。
active-back-ground-color 被选中的背景色。
数据传递
如图所示的紫色的路线,通过递归遍历通知 menu 树里面的每一个分支,改变颜色,是否垂直,disabled 等等。大部分添加的功能都在这条线上实现通信。
js 控制颜色的更改
watch:{
selected(){
if(this.selected&&……..{
this.$refs.item.style.backgroundColor = ……..
this.$refs.item.style.color = ………
//………..
}else{
//…………
}
}
}
弹出框的问题
element 对弹出框的操作是以在 body 上添加子节点的方式。这样子最直接的就是避免了可能存在 overflow:hideen 的问题。后面的定位只需要用 js 来完成就行了,还要考虑到 scroll 的问题。
最麻烦的地方在于动画过渡上,单纯的用 css 会有弹出框回到原点的问题。这里就需要用到 javascript 钩子。官网在这上面的说的很明确,但是实现过程中也踩了一些坑
动画瞬间完成的问题,这在官网上的解释时只用 JavaScript 做过渡的时候 (没用 css) 的情况下动画会瞬间完成,要使用 done。之后试了并没有用。这里并不是只用 JavaScript 做过渡的。之后想了一下,既然 beforeEnter 和 enter 瞬间就执行了,并没有动画的事件间隔,为什么不自己加一个 settimeout 呢,问题就解决了。可能这种解决方法并不是很好,但目前就只想到这些了。
enter(el) {
setTimeout(()=>{
//…..
})
},
钩子中设置的一些额外的 css 属性要在 afterEnter 改回来啊,其中就例如 overflow:hidden 和 height,导致在 vertical 垂直面板上弹出框不会撑开父元素的问题。
afterLeave(el){
el.style.overflow = ”
if(this.vertical){
el.style.height=”
}
},
后面就是和定位弹出框一样类似的 js 操作,在 menu 导航菜单里面我并没有这么做,后面会改成这样的吧。
beforeEnter(el) {
el.style.position= ‘absolute’
el.style.transform=…….
//………
},
enter(el) {
//……
},
处理一些细枝末节的问题
hover 触发和 click 触发
弹出框移出消失的时间控制和动画抖动。
高亮线条只在一级子组件中存在
menu-item-group 组件的添加,其实只是作为一个中转站而已,无非是拷贝一些函数。
一些多层嵌套的位置问题,尚未完成。
遇到的一些 css 问题
横版里面 active 下面的亮条显示, 一开始就是设置 menu-item 里面的 border-bottom,因为同时设置了 transition。在显示的时候会有高度抖动的问题。后来改用伪类完成,不过在自定义颜色的时候非常麻烦。这个方法也放弃了。最后只有在下面加一个 div 代替 border-bottom 作为高亮线条,方法虽然很蠢,但是有效。
scoped 里面给某些子组件添加 css 样式用到了 /deep/ 深度作用。
简单的总结?
其实以这种显而易见的数据流作为基础,多增加一些功能无非是函数的添加和 css 的修改。找 bug 和维护也是相对比较轻松的。