You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
207 lines
6.3 KiB
207 lines
6.3 KiB
<script type="text/jsx">
|
|
/**
|
|
* 顶部菜单,参考了ant design的响应式设计
|
|
*/
|
|
import menuMixin from "@/layout/mixin/menu"
|
|
import {getters as appGetters, mutations as appMutations} from "@/layout/store/app"
|
|
import NavMenu from "@/component/menu/NavMenu"
|
|
import NavMenuItem from "@/component/menu/NavMenu/item"
|
|
import {getActiveMenuByRoute} from "@/layout/util"
|
|
|
|
export default {
|
|
name: "HeadMenu",
|
|
|
|
mixins: [menuMixin],
|
|
|
|
components: {NavMenu, NavMenuItem},
|
|
|
|
props: {
|
|
//是否在只有一个顶部菜单时仍然渲染
|
|
alwaysShow: Boolean,
|
|
|
|
//主题由父组件控制
|
|
theme: String
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
//最后一个不被隐藏的顶部菜单的数组下标
|
|
//为undefined时,说明不需要隐藏菜单,为-1时,说明需要隐藏全部菜单
|
|
lastVisibleIndex: undefined,
|
|
|
|
//用于生成el-submenu的key
|
|
seed: 0
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
navMode: () => appGetters.navMode,
|
|
|
|
menus() {
|
|
if (appGetters.isMobile) return []
|
|
switch (this.navMode) {
|
|
case 'aside':
|
|
return []
|
|
case 'head' :
|
|
return appGetters.menus
|
|
case 'mix':
|
|
return appGetters.menus.map(root => ({...root, children: undefined}))
|
|
}
|
|
},
|
|
|
|
//实际用于渲染的菜单数组
|
|
realMenus() {
|
|
const {lastVisibleIndex, menus} = this
|
|
|
|
//不需要隐藏菜单
|
|
if (lastVisibleIndex === undefined) {
|
|
return menus
|
|
}
|
|
|
|
const fullPath = `_${this.seed}`
|
|
|
|
//隐藏全部菜单
|
|
if (lastVisibleIndex === -1) {
|
|
return [{fullPath, meta: {icon: 'el-icon-menu'}, children: menus}]
|
|
}
|
|
|
|
const visible = menus.slice(0, lastVisibleIndex + 1)
|
|
const hidden = menus.slice(lastVisibleIndex + 1)
|
|
|
|
visible.push({fullPath, meta: {title: '...'}, children: hidden})
|
|
|
|
return visible
|
|
}
|
|
},
|
|
|
|
watch: {
|
|
//路由变化时设置高亮菜单
|
|
$route: {
|
|
immediate: true,
|
|
handler(to) {
|
|
const {path, matched} = to
|
|
//使用/redirect跳转 或 无匹配路由 时跳过
|
|
if (path.startsWith('/redirect') || matched.length === 0) {
|
|
return
|
|
}
|
|
this.setActiveMenu(this.navMode, to)
|
|
}
|
|
},
|
|
|
|
//切换导航模式时重新设置高亮菜单
|
|
navMode: {
|
|
immediate: true,
|
|
handler(mode) {
|
|
this.setActiveMenu(mode)
|
|
}
|
|
},
|
|
|
|
//变动时修改种子,避免组件不更新
|
|
lastVisibleIndex() {
|
|
this.seed++
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
setActiveMenu(navMode = this.navMode, {path, meta, matched} = this.$route) {
|
|
//设置当前激活的顶部菜单
|
|
const rootPath = matched[0].path || '/'
|
|
appMutations.activeRootMenu(rootPath)
|
|
|
|
//只有在混合导航模式下才将当前激活的顶部菜单认为是当前菜单
|
|
if (navMode === 'mix') {
|
|
this.activeMenu = rootPath
|
|
}
|
|
//否则按照路由配置项设置
|
|
else this.activeMenu = getActiveMenuByRoute({path, meta})
|
|
},
|
|
onSelect(index) {
|
|
//混合导航模式下,点击相同菜单不刷新页面
|
|
if (this.navMode === 'mix' && index === appGetters.activeRootMenu) {
|
|
return
|
|
}
|
|
this.actionOnSelectMenu(index)
|
|
},
|
|
|
|
//获取el-menu的dom
|
|
getMenuEl() {
|
|
return this.$el
|
|
},
|
|
//获取初始菜单的总宽度,只在mounted时调用一次
|
|
setChildrenWidth() {
|
|
const ul = this.getMenuEl()
|
|
if (!ul) return
|
|
const menuItemNodes = ul.children
|
|
if (!menuItemNodes || menuItemNodes.length === 0) return
|
|
//'更多'菜单的宽度,由于不考虑自定义,所以直接写死
|
|
this.overflowedIndicatorWidth = 50
|
|
this.menuItemSizes = Array.from(menuItemNodes).map(i => i.getBoundingClientRect().width)
|
|
this.originalTotalWidth = this.menuItemSizes.reduce((acc, cur) => acc + cur, 0)
|
|
},
|
|
|
|
resize() {
|
|
const width = this.getMenuEl().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
|
|
},
|
|
createResizeObserver() {
|
|
//菜单未渲染时,移除之前的observer
|
|
if (!this.getMenuEl()) {
|
|
if (this.resizeObserver) {
|
|
this.resizeObserver.disconnect()
|
|
this.resizeObserver = null
|
|
}
|
|
return
|
|
}
|
|
|
|
//如果已创建observer,则返回
|
|
if (this.resizeObserver) return
|
|
|
|
this.resizeObserver = new ResizeObserver(this.resize)
|
|
this.resizeObserver.observe(this.getMenuEl())
|
|
|
|
this.$once('hook:beforeDestroy', () => {
|
|
this.resizeObserver && this.resizeObserver.disconnect()
|
|
})
|
|
}
|
|
},
|
|
|
|
mounted() {
|
|
this.setChildrenWidth()
|
|
this.createResizeObserver()
|
|
},
|
|
|
|
render() {
|
|
if (this.menus.length <= 0 || this.menus.length === 1 && !this.alwaysShow) {
|
|
return
|
|
}
|
|
|
|
return (
|
|
<nav-menu
|
|
menus={this.realMenus}
|
|
mode="horizontal"
|
|
theme={this.theme}
|
|
default-active={this.activeMenu}
|
|
on-select={this.onSelect}
|
|
/>
|
|
)
|
|
}
|
|
}
|
|
</script>
|
|
|