|
|
@@ -1,7 +1,9 @@
|
|
|
<template>
|
|
|
- <menu-item v-if="isLeaf" :menu="menu" ref="title" @selected="onSubMenuSelected" :depth="depth" :collapse="collapse" :width="width" :colors="colors" :activeId="activeId"></menu-item>
|
|
|
+ <menu-item v-if="isLeaf" :menu="menu" ref="title" @selected="onSubMenuSelected" :depth="depth" :collapse="collapse"
|
|
|
+ :width="width" :colors="colors" :activeId="activeId"></menu-item>
|
|
|
<li v-else @mouseenter="enterBody" @mouseleave="leaveBody">
|
|
|
- <div class="sub-menu-title" :style="titelStyle" ref="title" @click.stop="isOpen=!isOpen" @mouseenter="hover = true" @mouseleave="hover = false">
|
|
|
+ <div class="sub-menu-title" :style="titleStyle" ref="title" @click.stop="isOpen=!isOpen"
|
|
|
+ @mouseenter="hover = true" @mouseleave="hover = false">
|
|
|
<i class="icon fa-fw" :class="menu.icon" v-if="menu.icon"></i>
|
|
|
<transition name="el-fade-in">
|
|
|
<span v-if="!collapse || depth > 0">{{menu.name}}</span>
|
|
|
@@ -14,248 +16,258 @@
|
|
|
<div class="sub-menu-wrapper" :style="menuStyle" v-show="isOpen">
|
|
|
<ul class="sub-menu" :style="subMenuStyle" ref="child">
|
|
|
<!-- <slot :isOpen="isOpen" @onSelected="onSubMenuSelected"></slot> -->
|
|
|
- <sub-menu v-for="item in menu.children" :key="item.id" :menu="item" :depth="depth+1" :width="width" :collapse="collapse" :colors="colors" :activeId="activeId" @selected="onSubMenuSelected" @opened="onSubMenuOpened"></sub-menu>
|
|
|
+ <sub-menu v-for="item in menu.children" :key="item.id" :menu="item" :depth="depth+1" :width="width"
|
|
|
+ :collapse="collapse" :colors="colors" :activeId="activeId" @selected="onSubMenuSelected"
|
|
|
+ @opened="onSubMenuOpened"></sub-menu>
|
|
|
</ul>
|
|
|
</div>
|
|
|
</transition>
|
|
|
</li>
|
|
|
</template>
|
|
|
<script>
|
|
|
-import tinyColor from 'tinycolor2'
|
|
|
-import MenuItem from './MenuItem'
|
|
|
-export default {
|
|
|
- name: 'SubMenu',
|
|
|
- props: {
|
|
|
- menu: {
|
|
|
- type: Object,
|
|
|
- required: true
|
|
|
- },
|
|
|
- depth: {
|
|
|
- type: Number,
|
|
|
- default: 0
|
|
|
- },
|
|
|
- parentIsOpen: {
|
|
|
- type: Boolean,
|
|
|
- default: false
|
|
|
- },
|
|
|
- collapse: {
|
|
|
- type: Boolean,
|
|
|
- default: false
|
|
|
- },
|
|
|
- width: {
|
|
|
- type: Number
|
|
|
- },
|
|
|
- colors: {
|
|
|
- type: Object
|
|
|
- },
|
|
|
- activeId: {}
|
|
|
- },
|
|
|
- data() {
|
|
|
- return {
|
|
|
- x: 0,
|
|
|
- y: 0,
|
|
|
- w: 0,
|
|
|
- h: 0,
|
|
|
- c: 0,
|
|
|
- isOpen: false,
|
|
|
- active: false,
|
|
|
- collapsing: false,
|
|
|
- hover: false
|
|
|
- }
|
|
|
- },
|
|
|
- mounted() {
|
|
|
- this.reCale()
|
|
|
- },
|
|
|
- computed: {
|
|
|
- isLeaf() {
|
|
|
- return !(this.menu.children instanceof Array)
|
|
|
+ import MenuItem from './MenuItem'
|
|
|
+
|
|
|
+ export default {
|
|
|
+ name: 'SubMenu',
|
|
|
+ props: {
|
|
|
+ menu: {
|
|
|
+ type: Object,
|
|
|
+ required: true
|
|
|
+ },
|
|
|
+ depth: {
|
|
|
+ type: Number,
|
|
|
+ default: 0
|
|
|
+ },
|
|
|
+ parentIsOpen: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ collapse: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ width: {
|
|
|
+ type: Number
|
|
|
+ },
|
|
|
+ colors: {
|
|
|
+ type: Object
|
|
|
+ },
|
|
|
+ activeId: {}
|
|
|
},
|
|
|
- titelStyle() {
|
|
|
- let style = {
|
|
|
- paddingLeft: this.depth === 1 && !this.collapse ? '45px ' : '',
|
|
|
- color: this.colors.textColor
|
|
|
- }
|
|
|
- if (this.active && (this.collapse || (this.depth > 0))) {
|
|
|
- style.backgroundColor = this.colors.activeBackgroundColor
|
|
|
- style.borderLeft = `2px solid ${this.colors.textColor}`
|
|
|
- } else if (this.hover) {
|
|
|
- style.backgroundColor = this.colors.hoverBackgroundColor
|
|
|
- } else {
|
|
|
- style.backgroundColor = this.colors.backgroundColor
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ w: 0,
|
|
|
+ h: 0,
|
|
|
+ c: 0,
|
|
|
+ isOpen: false,
|
|
|
+ active: false,
|
|
|
+ collapsing: false,
|
|
|
+ hover: false
|
|
|
}
|
|
|
- return style
|
|
|
},
|
|
|
- menuStyle() {
|
|
|
- if ((this.collapse || (this.depth > 0)) && !this.collapsing) {
|
|
|
+ mounted() {
|
|
|
+ this.reCale()
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ isLeaf() {
|
|
|
+ return !(this.menu.children instanceof Array)
|
|
|
+ },
|
|
|
+ titleStyle() {
|
|
|
let style = {
|
|
|
- position: 'absolute',
|
|
|
- left: this.x + this.w + 'px',
|
|
|
- height: 56 * this.c + 10 + 'px',
|
|
|
- paddingLeft: '5px',
|
|
|
- zIndex: 3
|
|
|
+ paddingLeft: this.depth === 1 && !this.collapse ? '45px ' : '',
|
|
|
+ color: this.colors.textColor
|
|
|
}
|
|
|
- if (this.y + 56 * this.c + 10 > window.innerHeight) {
|
|
|
- style.top = window.innerHeight - 56 * this.c - 15 + 'px'
|
|
|
+ if (this.active && (this.collapse || (this.depth > 0))) {
|
|
|
+ style.backgroundColor = this.colors.activeBackgroundColor
|
|
|
+ style.borderLeft = `2px solid ${this.colors.textColor}`
|
|
|
+ } else if (this.hover) {
|
|
|
+ style.backgroundColor = this.colors.hoverBackgroundColor
|
|
|
} else {
|
|
|
- style.top = this.y + 'px'
|
|
|
+ style.backgroundColor = this.colors.backgroundColor
|
|
|
}
|
|
|
return style
|
|
|
- }
|
|
|
- return {
|
|
|
- height: 56 * this.c + 'px'
|
|
|
- }
|
|
|
- },
|
|
|
- subMenuStyle() {
|
|
|
- let style = {
|
|
|
- backgroundColor: this.colors.backgroundColor
|
|
|
- }
|
|
|
- if ((this.depth > 0 || this.collapse) && !this.collapsing) {
|
|
|
- style = {
|
|
|
- ...style,
|
|
|
- width: this.width + 'px',
|
|
|
- boxSizing: 'border-box',
|
|
|
- padding: '5px 0',
|
|
|
- borderRadius: '2px',
|
|
|
- boxShadow: '0 2px 12px 0 rgba(0, 0, 0, 0.1)'
|
|
|
+ },
|
|
|
+ menuStyle() {
|
|
|
+ if ((this.collapse || (this.depth > 0)) && !this.collapsing) {
|
|
|
+ let style = {
|
|
|
+ position: 'absolute',
|
|
|
+ left: this.x + this.w + 'px',
|
|
|
+ height: 56 * this.c + 10 + 'px',
|
|
|
+ paddingLeft: '5px',
|
|
|
+ zIndex: 3
|
|
|
+ }
|
|
|
+ if (this.$refs.title && (this.$refs.title.getBoundingClientRect().y + 56 * this.c + 15) > window.innerHeight) {
|
|
|
+ let offset = this.$refs.title.getBoundingClientRect().y + 56 * this.c + 15 - window.innerHeight
|
|
|
+ style.top = this.y - offset + 'px'
|
|
|
+ } else {
|
|
|
+ style.top = this.y + 'px'
|
|
|
+ }
|
|
|
+ // if (this.y + 56 * this.c + 10 > window.innerHeight) {
|
|
|
+ // style.top = window.innerHeight - 56 * this.c - 15 + 'px'
|
|
|
+ // } else {
|
|
|
+ // style.top = this.y + 'px'
|
|
|
+ // }
|
|
|
+ return style
|
|
|
}
|
|
|
- }
|
|
|
- return style
|
|
|
- },
|
|
|
- transitionName() {
|
|
|
- if (this.collapsing) {
|
|
|
- return 'menu-collapse'
|
|
|
- }
|
|
|
- return this.collapse || (this.depth > 0) ? 'menu-zoom' : 'menu-collapse'
|
|
|
- }
|
|
|
- },
|
|
|
- methods: {
|
|
|
- activeChange(activeId) {
|
|
|
- if (activeId instanceof Array && activeId.indexOf(this.menu.id) === -1) {
|
|
|
- this.active = false
|
|
|
- }
|
|
|
- this.$children.forEach(child => {
|
|
|
- child.activeChange(activeId)
|
|
|
- })
|
|
|
- },
|
|
|
- openedChange(openedId) {
|
|
|
- if (openedId.indexOf(this.menu.id) === -1) {
|
|
|
- this.isOpen = false
|
|
|
- }
|
|
|
- this.$children.forEach(child => {
|
|
|
- child.openedChange(openedId)
|
|
|
- })
|
|
|
- },
|
|
|
- reCale() {
|
|
|
- this.x = this.$refs.title.offsetLeft
|
|
|
- this.y = this.$refs.title.offsetTop
|
|
|
- this.w = this.$refs.title.offsetWidth
|
|
|
- this.h = this.$refs.title.offsetHeight
|
|
|
- if (!this.isLeaf) {
|
|
|
- this.c = this.$refs.child.childElementCount
|
|
|
- }
|
|
|
- },
|
|
|
- enterBody(e) {
|
|
|
- if (this.collapse || (this.depth > 0)) {
|
|
|
- this.isOpen = true
|
|
|
- }
|
|
|
- },
|
|
|
- leaveBody(e) {
|
|
|
- if (this.collapse || (this.depth > 0)) {
|
|
|
- this.isOpen = false
|
|
|
+ return {
|
|
|
+ height: 56 * this.c + 'px'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ subMenuStyle() {
|
|
|
+ let style = {
|
|
|
+ backgroundColor: this.colors.backgroundColor
|
|
|
+ }
|
|
|
+ if ((this.depth > 0 || this.collapse) && !this.collapsing) {
|
|
|
+ style = {
|
|
|
+ ...style,
|
|
|
+ width: this.width + 'px',
|
|
|
+ boxSizing: 'border-box',
|
|
|
+ padding: '5px 0',
|
|
|
+ borderRadius: '2px',
|
|
|
+ boxShadow: '0 2px 12px 0 rgba(0, 0, 0, 0.1)'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return style
|
|
|
+ },
|
|
|
+ transitionName() {
|
|
|
+ if (this.collapsing) {
|
|
|
+ return 'menu-collapse'
|
|
|
+ }
|
|
|
+ return this.collapse || (this.depth > 0) ? 'menu-zoom' : 'menu-collapse'
|
|
|
}
|
|
|
},
|
|
|
- onSubMenuSelected(e) {
|
|
|
- this.active = true
|
|
|
- if (this.depth === 0 && !this.collapse) {
|
|
|
- this.isOpen = true
|
|
|
- }
|
|
|
- let selected = [this.menu.id, ...e]
|
|
|
- if (this.isLeaf) {
|
|
|
- this.$emit('selected', e)
|
|
|
- } else {
|
|
|
- this.$emit('selected', selected)
|
|
|
+ methods: {
|
|
|
+ activeChange(activeId) {
|
|
|
+ if (activeId instanceof Array && activeId.indexOf(this.menu.id) === -1) {
|
|
|
+ this.active = false
|
|
|
+ }
|
|
|
+ this.$children.forEach(child => {
|
|
|
+ child.activeChange(activeId)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ openedChange(openedId) {
|
|
|
+ if (openedId.indexOf(this.menu.id) === -1) {
|
|
|
+ this.isOpen = false
|
|
|
+ }
|
|
|
+ this.$children.forEach(child => {
|
|
|
+ child.openedChange(openedId)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ reCale() {
|
|
|
+ this.x = this.$refs.title.offsetLeft
|
|
|
+ this.y = this.$refs.title.offsetTop
|
|
|
+ this.w = this.$refs.title.offsetWidth
|
|
|
+ this.h = this.$refs.title.offsetHeight
|
|
|
+ if (!this.isLeaf) {
|
|
|
+ this.c = this.$refs.child.childElementCount
|
|
|
+ }
|
|
|
+ },
|
|
|
+ enterBody(e) {
|
|
|
+ if (this.collapse || (this.depth > 0)) {
|
|
|
+ this.isOpen = true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ leaveBody(e) {
|
|
|
+ if (this.collapse || (this.depth > 0)) {
|
|
|
+ //this.isOpen = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onSubMenuSelected(e) {
|
|
|
+ this.active = true
|
|
|
+ if (this.depth === 0 && !this.collapse) {
|
|
|
+ this.isOpen = true
|
|
|
+ }
|
|
|
+ let selected = [this.menu.id, ...e]
|
|
|
+ if (this.isLeaf) {
|
|
|
+ this.$emit('selected', e)
|
|
|
+ } else {
|
|
|
+ this.$emit('selected', selected)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onSubMenuOpened(e) {
|
|
|
+ this.$emit('opened', [this.menu.id, ...e])
|
|
|
}
|
|
|
},
|
|
|
- onSubMenuOpened(e) {
|
|
|
- this.$emit('opened', [this.menu.id, ...e])
|
|
|
- }
|
|
|
- },
|
|
|
- watch: {
|
|
|
- parentIsOpen(val) {
|
|
|
- if (!val) this.isOpen = false
|
|
|
- },
|
|
|
- isOpen(val) {
|
|
|
- this.reCale()
|
|
|
- if (val) {
|
|
|
- this.$emit('opened', [this.menu.id])
|
|
|
+ watch: {
|
|
|
+ parentIsOpen(val) {
|
|
|
+ if (!val) this.isOpen = false
|
|
|
+ },
|
|
|
+ isOpen(val) {
|
|
|
+ this.reCale()
|
|
|
+ if (val) {
|
|
|
+ this.$emit('opened', [this.menu.id])
|
|
|
+ }
|
|
|
+ },
|
|
|
+ collapse(val) {
|
|
|
+ if (val) {
|
|
|
+ this.collapsing = true
|
|
|
+ setTimeout(() => {
|
|
|
+ this.collapsing = false
|
|
|
+ }, 300);
|
|
|
+ this.isOpen = false
|
|
|
+ } else {
|
|
|
+ this.isOpen = this.active && this.depth === 0 ? true : false
|
|
|
+ }
|
|
|
}
|
|
|
},
|
|
|
- collapse(val) {
|
|
|
- if (val) {
|
|
|
- this.collapsing = true
|
|
|
- setTimeout(() => {
|
|
|
- this.collapsing = false
|
|
|
- }, 300);
|
|
|
- this.isOpen = false
|
|
|
- } else {
|
|
|
- this.isOpen = this.active && this.depth === 0 ? true : false
|
|
|
- }
|
|
|
+ components: {
|
|
|
+ MenuItem
|
|
|
}
|
|
|
- },
|
|
|
- components: {
|
|
|
- MenuItem
|
|
|
}
|
|
|
-}
|
|
|
</script>
|
|
|
<style lang="less" scoped>
|
|
|
-.sub-menu-title {
|
|
|
- height: 56px;
|
|
|
- line-height: 56px;
|
|
|
- font-size: 14px;
|
|
|
- padding: 0 20px;
|
|
|
- box-sizing: border-box;
|
|
|
- transition: background-color 0.2s, color 0.2s;
|
|
|
- position: relative;
|
|
|
- .icon {
|
|
|
- margin-right: 6px;
|
|
|
- }
|
|
|
- .el-icon-arrow-left {
|
|
|
- position: absolute;
|
|
|
- right: 20px;
|
|
|
- top: 50%;
|
|
|
- margin-top: -7px;
|
|
|
- font-size: 12px;
|
|
|
- transition: all 0.3s;
|
|
|
- &.open {
|
|
|
- transform: rotate(-90deg);
|
|
|
+ .sub-menu-title {
|
|
|
+ height: 56px;
|
|
|
+ line-height: 56px;
|
|
|
+ font-size: 14px;
|
|
|
+ padding: 0 20px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ transition: background-color 0.2s, color 0.2s;
|
|
|
+ position: relative;
|
|
|
+ .icon {
|
|
|
+ margin-right: 6px;
|
|
|
+ }
|
|
|
+ .el-icon-arrow-left {
|
|
|
+ position: absolute;
|
|
|
+ right: 20px;
|
|
|
+ top: 50%;
|
|
|
+ margin-top: -7px;
|
|
|
+ font-size: 12px;
|
|
|
+ transition: all 0.3s;
|
|
|
+ &.open {
|
|
|
+ transform: rotate(-90deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ * {
|
|
|
+ vertical-align: middle;
|
|
|
}
|
|
|
}
|
|
|
- * {
|
|
|
- vertical-align: middle;
|
|
|
+
|
|
|
+ .menu-collapse-enter,
|
|
|
+ .menu-collapse-leave-active {
|
|
|
+ height: 0 !important;
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-.menu-collapse-enter,
|
|
|
-.menu-collapse-leave-active {
|
|
|
- height: 0 !important;
|
|
|
-}
|
|
|
-.menu-collapse-enter-active,
|
|
|
-.menu-collapse-leave-active {
|
|
|
- transition: height 0.3s ease;
|
|
|
- transform: translate3d(0, 0, 0);
|
|
|
- overflow: hidden;
|
|
|
-}
|
|
|
+ .menu-collapse-enter-active,
|
|
|
+ .menu-collapse-leave-active {
|
|
|
+ transition: height 0.3s ease;
|
|
|
+ transform: translate3d(0, 0, 0);
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
|
|
|
-.menu-zoom-enter,
|
|
|
-.menu-zoom-leave-active {
|
|
|
- transform: scale3d(0.2, 0.2, 1);
|
|
|
- opacity: 0;
|
|
|
-}
|
|
|
-.menu-zoom-enter-active,
|
|
|
-.menu-zoom-leave-active {
|
|
|
- transform-origin: 0 0;
|
|
|
- transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1),
|
|
|
+ .menu-zoom-enter,
|
|
|
+ .menu-zoom-leave-active {
|
|
|
+ transform: scale3d(0.2, 0.2, 1);
|
|
|
+ opacity: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .menu-zoom-enter-active,
|
|
|
+ .menu-zoom-leave-active {
|
|
|
+ transform-origin: 0 0;
|
|
|
+ transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1),
|
|
|
opacity 0.3s cubic-bezier(0.23, 1, 0.32, 1);
|
|
|
- overflow: hidden;
|
|
|
-}
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
</style>
|