|
|
|
@ -1,10 +1,16 @@ |
|
|
|
<script type="text/jsx"> |
|
|
|
<script type="text/jsx"> |
|
|
|
import jumpOnSelectMenuMixin from "@/layout/mixin/jumpOnSelectMenu" |
|
|
|
/** |
|
|
|
|
|
|
|
* 简易顶部菜单,参考了ant design的响应式设计 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
import ClickOutside from 'element-ui/lib/utils/clickoutside' |
|
|
|
|
|
|
|
import actionOnSelectMenuMixin from "@/layout/mixin/actionOnSelectMenu" |
|
|
|
|
|
|
|
|
|
|
|
export default { |
|
|
|
export default { |
|
|
|
name: "RootMenu", |
|
|
|
name: "RootMenu", |
|
|
|
|
|
|
|
|
|
|
|
mixins: [jumpOnSelectMenuMixin], |
|
|
|
mixins: [actionOnSelectMenuMixin], |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
directives: {ClickOutside}, |
|
|
|
|
|
|
|
|
|
|
|
props: { |
|
|
|
props: { |
|
|
|
//当前激活菜单的路径 |
|
|
|
//当前激活菜单的路径 |
|
|
|
@ -15,31 +21,165 @@ export default { |
|
|
|
alwaysShow: Boolean |
|
|
|
alwaysShow: Boolean |
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data() { |
|
|
|
|
|
|
|
return { |
|
|
|
|
|
|
|
//最后一个不被隐藏的顶部菜单的数组下标 |
|
|
|
|
|
|
|
//为undefined时,说明不需要隐藏菜单 |
|
|
|
|
|
|
|
//为-1时,说明需要隐藏全部菜单 |
|
|
|
|
|
|
|
lastVisibleIndex: undefined, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//是否显示menu-popover |
|
|
|
|
|
|
|
showPopover: false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
methods: { |
|
|
|
methods: { |
|
|
|
onSelect(menu) { |
|
|
|
onSelect(menu, e) { |
|
|
|
|
|
|
|
e.stopPropagation() |
|
|
|
const path = menu.fullPath |
|
|
|
const path = menu.fullPath |
|
|
|
if (path === this.activeMenu) return |
|
|
|
if (path === this.activeMenu) return |
|
|
|
this.jumpOnSelectMenu(path, false) |
|
|
|
this.showPopover = false |
|
|
|
|
|
|
|
this.actionOnSelectMenu(path, false) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//获取初始菜单的总宽度,只在mounted时调用一次 |
|
|
|
|
|
|
|
setChildrenWidth() { |
|
|
|
|
|
|
|
const ul = this.$el |
|
|
|
|
|
|
|
if (!ul) return |
|
|
|
|
|
|
|
const menuItemNodes = ul.children |
|
|
|
|
|
|
|
if (!menuItemNodes || menuItemNodes.length === 0) return |
|
|
|
|
|
|
|
//'更多'菜单的宽度,由于不考虑自定义,所以直接写死 |
|
|
|
|
|
|
|
this.overflowedIndicatorWidth = 57 |
|
|
|
|
|
|
|
this.menuItemSizes = Array.from(menuItemNodes).map(i => i.getBoundingClientRect().width) |
|
|
|
|
|
|
|
this.originalTotalWidth = this.menuItemSizes.reduce((acc, cur) => acc + cur, 0) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
resize() { |
|
|
|
|
|
|
|
const width = this.$el.getBoundingClientRect().width |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let lastVisibleIndex = undefined |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//如果初始菜单的总宽度超出容器宽度 |
|
|
|
|
|
|
|
if (this.originalTotalWidth > width) { |
|
|
|
|
|
|
|
lastVisibleIndex = -1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//得到满足总宽度不超出容器宽度的最大菜单下标 |
|
|
|
|
|
|
|
let currentSumWidth = 0 |
|
|
|
|
|
|
|
for (const liWidth of this.menuItemSizes) { |
|
|
|
|
|
|
|
currentSumWidth += liWidth |
|
|
|
|
|
|
|
if (currentSumWidth + this.overflowedIndicatorWidth > width) { |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
lastVisibleIndex += 1 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.lastVisibleIndex = lastVisibleIndex |
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
render() { |
|
|
|
renderItemContent(h, menu) { |
|
|
|
//菜单点击时触发select事件,payload为菜单对象 |
|
|
|
const {title, icon} = menu.meta |
|
|
|
const {activeMenu, menu, alwaysShow} = this |
|
|
|
const arr = [] |
|
|
|
if (!Array.isArray(menu) || menu.length <= 1 && !alwaysShow) return |
|
|
|
icon && arr.push(<v-icon icon={icon}/>) |
|
|
|
|
|
|
|
arr.push(title) |
|
|
|
|
|
|
|
return arr |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
renderOverflowedIndicator(h, hideAll) { |
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<ul class="root-menu"> |
|
|
|
<div class="root-menu-item-title"> |
|
|
|
{menu.map(i => { |
|
|
|
{hideAll ? <i class="el-icon-menu"/> : '...'} |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
renderVisibleMenus(h, visibleMenus) { |
|
|
|
|
|
|
|
if (visibleMenus.length <= 0) return |
|
|
|
|
|
|
|
const activeMenu = this.activeMenu |
|
|
|
|
|
|
|
return visibleMenus.map(i => { |
|
|
|
const key = i.fullPath + i.meta.title |
|
|
|
const key = i.fullPath + i.meta.title |
|
|
|
const className = {'root-menu-item': true, 'active': i.fullPath === activeMenu} |
|
|
|
const className = {'root-menu-item': true, 'active': i.fullPath === activeMenu} |
|
|
|
const icon = i.meta.icon |
|
|
|
|
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<li key={key} class={className} on-click={() => this.onSelect(i)}> |
|
|
|
<li key={key} class={className} on-click={e => this.onSelect(i, e)}> |
|
|
|
{icon && <v-icon icon={icon}/>} |
|
|
|
<div class="root-menu-item-title"> |
|
|
|
{i.meta.title} |
|
|
|
{this.renderItemContent(h, i)} |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</li> |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
renderHideMenus(h, hideMenus) { |
|
|
|
|
|
|
|
if (hideMenus.length <= 0) return |
|
|
|
|
|
|
|
const activeMenu = this.activeMenu |
|
|
|
|
|
|
|
let hasActiveChild = false |
|
|
|
|
|
|
|
const children = hideMenus.map(i => { |
|
|
|
|
|
|
|
const className = {'root-sub-menu-item': true, 'active': i.fullPath === activeMenu} |
|
|
|
|
|
|
|
if (className.active) hasActiveChild = true |
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
|
|
<li class={className} on-click={e => this.onSelect(i, e)}> |
|
|
|
|
|
|
|
{this.renderItemContent(h, i)} |
|
|
|
|
|
|
|
</li> |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
const className = { |
|
|
|
|
|
|
|
'root-menu-item': true, |
|
|
|
|
|
|
|
'root-sub-menu': true, |
|
|
|
|
|
|
|
'active': this.showPopover || hasActiveChild |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
|
|
<li |
|
|
|
|
|
|
|
v-click-outside={() => this.showPopover = false} |
|
|
|
|
|
|
|
key="overflowed-indicator" |
|
|
|
|
|
|
|
class={className} |
|
|
|
|
|
|
|
on-click={() => this.showPopover = true} |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
{this.renderOverflowedIndicator(h, hideMenus.length === this.menu.length)} |
|
|
|
|
|
|
|
<transition name="el-zoom-in-top"> |
|
|
|
|
|
|
|
<ul v-show={this.showPopover} class="menu-popover"> |
|
|
|
|
|
|
|
{children} |
|
|
|
|
|
|
|
</ul> |
|
|
|
|
|
|
|
</transition> |
|
|
|
</li> |
|
|
|
</li> |
|
|
|
) |
|
|
|
) |
|
|
|
})} |
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mounted() { |
|
|
|
|
|
|
|
this.setChildrenWidth() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.resizeObserver = new ResizeObserver(() => this.resize()) |
|
|
|
|
|
|
|
this.resizeObserver.observe(this.$el) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.$once('hook:beforeDestroy', () => { |
|
|
|
|
|
|
|
this.resizeObserver && this.resizeObserver.disconnect() |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
render(h) { |
|
|
|
|
|
|
|
const {lastVisibleIndex, menu, alwaysShow} = this |
|
|
|
|
|
|
|
if (!Array.isArray(menu) || menu.length <= 1 && !alwaysShow) { |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let visibleMenus = menu, hideMenus = [] |
|
|
|
|
|
|
|
if (lastVisibleIndex !== undefined) { |
|
|
|
|
|
|
|
//不需要隐藏菜单 |
|
|
|
|
|
|
|
if (lastVisibleIndex === -1) { |
|
|
|
|
|
|
|
visibleMenus = [] |
|
|
|
|
|
|
|
hideMenus = menu |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
//需要隐藏全部菜单 |
|
|
|
|
|
|
|
else if (lastVisibleIndex !== menu.length - 1) { |
|
|
|
|
|
|
|
visibleMenus = menu.slice(0, lastVisibleIndex + 1) |
|
|
|
|
|
|
|
hideMenus = menu.slice(lastVisibleIndex + 1) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
|
|
<ul class="root-menu"> |
|
|
|
|
|
|
|
{this.renderVisibleMenus(h, visibleMenus)} |
|
|
|
|
|
|
|
{this.renderHideMenus(h, hideMenus)} |
|
|
|
</ul> |
|
|
|
</ul> |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
@ -50,6 +190,8 @@ export default { |
|
|
|
@import "~@/asset/style/variables.scss"; |
|
|
|
@import "~@/asset/style/variables.scss"; |
|
|
|
|
|
|
|
|
|
|
|
.root-menu { |
|
|
|
.root-menu { |
|
|
|
|
|
|
|
width: 100%; |
|
|
|
|
|
|
|
flex: 1; |
|
|
|
height: $nav-height; |
|
|
|
height: $nav-height; |
|
|
|
line-height: $nav-height; |
|
|
|
line-height: $nav-height; |
|
|
|
list-style: none; |
|
|
|
list-style: none; |
|
|
|
@ -66,14 +208,51 @@ export default { |
|
|
|
cursor: pointer; |
|
|
|
cursor: pointer; |
|
|
|
transition: all .2s ease-in-out; |
|
|
|
transition: all .2s ease-in-out; |
|
|
|
|
|
|
|
|
|
|
|
> .icon { |
|
|
|
&:hover, &.active { |
|
|
|
margin-right: 6px; |
|
|
|
border-bottom: 2px solid $--color-primary; |
|
|
|
vertical-align: -0.125em; |
|
|
|
|
|
|
|
|
|
|
|
> .root-menu-item-title { |
|
|
|
|
|
|
|
color: $--color-primary; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
&:hover, &.active { |
|
|
|
.root-sub-menu { |
|
|
|
|
|
|
|
position: relative; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> .menu-popover { |
|
|
|
|
|
|
|
position: absolute; |
|
|
|
|
|
|
|
left: -20px; |
|
|
|
|
|
|
|
background-color: #fff; |
|
|
|
|
|
|
|
margin: 5px 0; |
|
|
|
|
|
|
|
padding: 5px 0; |
|
|
|
|
|
|
|
min-width: 100px; |
|
|
|
|
|
|
|
text-align: left; |
|
|
|
|
|
|
|
border-radius: 4px; |
|
|
|
|
|
|
|
box-shadow: 0 1px 6px rgba(0, 0, 0, .2); |
|
|
|
|
|
|
|
z-index: 10; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&-item { |
|
|
|
|
|
|
|
margin: 0; |
|
|
|
|
|
|
|
line-height: normal; |
|
|
|
|
|
|
|
padding: 7px 16px; |
|
|
|
|
|
|
|
color: #515a6e; |
|
|
|
|
|
|
|
white-space: nowrap; |
|
|
|
|
|
|
|
list-style: none; |
|
|
|
|
|
|
|
cursor: pointer; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&.active, &:hover { |
|
|
|
color: $--color-primary; |
|
|
|
color: $--color-primary; |
|
|
|
border-bottom: 2px solid $--color-primary; |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.root-menu-item-title, .root-sub-menu-item { |
|
|
|
|
|
|
|
> .icon { |
|
|
|
|
|
|
|
font-size: 14px; |
|
|
|
|
|
|
|
margin-right: 6px; |
|
|
|
|
|
|
|
vertical-align: -0.125em; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|