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.
325 lines
9.1 KiB
325 lines
9.1 KiB
<script type="text/jsx"> |
|
import emitter from 'element-ui/src/mixins/emitter'; |
|
import Migrating from 'element-ui/src/mixins/migrating'; |
|
import Menubar from 'element-ui/src/utils/menu/aria-menubar'; |
|
import { addClass, removeClass, hasClass } from 'element-ui/src/utils/dom'; |
|
|
|
export default { |
|
name: 'ElMenu', |
|
|
|
render (h) { |
|
const component = ( |
|
<ul |
|
role="menubar" |
|
key={ +this.collapse } |
|
style={{ backgroundColor: this.backgroundColor || '' }} |
|
class={{ |
|
'el-menu--horizontal': this.mode === 'horizontal', |
|
'el-menu--collapse': this.collapse, |
|
"el-menu": true |
|
}} |
|
> |
|
{ this.$slots.default } |
|
</ul> |
|
); |
|
|
|
if (this.collapseTransition) { |
|
return ( |
|
<el-menu-collapse-transition> |
|
{ component } |
|
</el-menu-collapse-transition> |
|
); |
|
} else { |
|
return component; |
|
} |
|
}, |
|
|
|
componentName: 'ElMenu', |
|
|
|
mixins: [emitter, Migrating], |
|
|
|
provide() { |
|
return { |
|
rootMenu: this |
|
}; |
|
}, |
|
|
|
components: { |
|
'el-menu-collapse-transition': { |
|
functional: true, |
|
render(createElement, context) { |
|
const data = { |
|
props: { |
|
mode: 'out-in' |
|
}, |
|
on: { |
|
beforeEnter(el) { |
|
el.style.opacity = 0.2; |
|
}, |
|
|
|
enter(el) { |
|
addClass(el, 'el-opacity-transition'); |
|
el.style.opacity = 1; |
|
}, |
|
|
|
afterEnter(el) { |
|
removeClass(el, 'el-opacity-transition'); |
|
el.style.opacity = ''; |
|
}, |
|
|
|
beforeLeave(el) { |
|
if (!el.dataset) el.dataset = {}; |
|
|
|
if (hasClass(el, 'el-menu--collapse')) { |
|
removeClass(el, 'el-menu--collapse'); |
|
el.dataset.oldOverflow = el.style.overflow; |
|
el.dataset.scrollWidth = el.clientWidth; |
|
addClass(el, 'el-menu--collapse'); |
|
} else { |
|
addClass(el, 'el-menu--collapse'); |
|
el.dataset.oldOverflow = el.style.overflow; |
|
el.dataset.scrollWidth = el.clientWidth; |
|
removeClass(el, 'el-menu--collapse'); |
|
} |
|
|
|
el.style.width = el.scrollWidth + 'px'; |
|
el.style.overflow = 'hidden'; |
|
}, |
|
|
|
leave(el) { |
|
addClass(el, 'horizontal-collapse-transition'); |
|
el.style.width = el.dataset.scrollWidth + 'px'; |
|
} |
|
} |
|
}; |
|
return createElement('transition', data, context.children); |
|
} |
|
} |
|
}, |
|
|
|
props: { |
|
mode: { |
|
type: String, |
|
default: 'vertical' |
|
}, |
|
defaultActive: { |
|
type: String, |
|
default: '' |
|
}, |
|
defaultOpeneds: Array, |
|
uniqueOpened: Boolean, |
|
router: Boolean, |
|
menuTrigger: { |
|
type: String, |
|
default: 'hover' |
|
}, |
|
collapse: Boolean, |
|
backgroundColor: String, |
|
textColor: String, |
|
activeTextColor: String, |
|
collapseTransition: { |
|
type: Boolean, |
|
default: true |
|
} |
|
}, |
|
data() { |
|
return { |
|
activeIndex: this.defaultActive, |
|
openedMenus: (this.defaultOpeneds && !this.collapse) ? this.defaultOpeneds.slice(0) : [], |
|
items: {}, |
|
submenus: {} |
|
}; |
|
}, |
|
computed: { |
|
hoverBackground() { |
|
return this.backgroundColor ? this.mixColor(this.backgroundColor, 0.2) : ''; |
|
}, |
|
isMenuPopup() { |
|
return this.mode === 'horizontal' || (this.mode === 'vertical' && this.collapse); |
|
} |
|
}, |
|
watch: { |
|
defaultActive(value){ |
|
if(!this.items[value]){ |
|
this.activeIndex = null |
|
} |
|
this.updateActiveIndex(value) |
|
}, |
|
|
|
defaultOpeneds(value) { |
|
if (!this.collapse) { |
|
this.openedMenus = value; |
|
} |
|
}, |
|
|
|
collapse(value) { |
|
if (value) this.openedMenus = []; |
|
this.broadcast('ElSubmenu', 'toggle-collapse', value); |
|
} |
|
}, |
|
methods: { |
|
updateActiveIndex(val) { |
|
const item = this.items[val] || this.items[this.activeIndex] || this.items[this.defaultActive]; |
|
if (item) { |
|
this.activeIndex = item.index; |
|
this.initOpenedMenu(); |
|
} else { |
|
this.activeIndex = null; |
|
} |
|
}, |
|
|
|
getMigratingConfig() { |
|
return { |
|
props: { |
|
'theme': 'theme is removed.' |
|
} |
|
}; |
|
}, |
|
getColorChannels(color) { |
|
color = color.replace('#', ''); |
|
if (/^[0-9a-fA-F]{3}$/.test(color)) { |
|
color = color.split(''); |
|
for (let i = 2; i >= 0; i--) { |
|
color.splice(i, 0, color[i]); |
|
} |
|
color = color.join(''); |
|
} |
|
if (/^[0-9a-fA-F]{6}$/.test(color)) { |
|
return { |
|
red: parseInt(color.slice(0, 2), 16), |
|
green: parseInt(color.slice(2, 4), 16), |
|
blue: parseInt(color.slice(4, 6), 16) |
|
}; |
|
} else { |
|
return { |
|
red: 255, |
|
green: 255, |
|
blue: 255 |
|
}; |
|
} |
|
}, |
|
mixColor(color, percent) { |
|
let { red, green, blue } = this.getColorChannels(color); |
|
if (percent > 0) { // shade given color |
|
red *= 1 - percent; |
|
green *= 1 - percent; |
|
blue *= 1 - percent; |
|
} else { // tint given color |
|
red += (255 - red) * percent; |
|
green += (255 - green) * percent; |
|
blue += (255 - blue) * percent; |
|
} |
|
return `rgb(${ Math.round(red) }, ${ Math.round(green) }, ${ Math.round(blue) })`; |
|
}, |
|
addItem(item) { |
|
this.$set(this.items, item.index, item); |
|
}, |
|
removeItem(item) { |
|
delete this.items[item.index]; |
|
}, |
|
addSubmenu(item) { |
|
this.$set(this.submenus, item.index, item); |
|
}, |
|
removeSubmenu(item) { |
|
delete this.submenus[item.index]; |
|
}, |
|
openMenu(index, indexPath) { |
|
let openedMenus = this.openedMenus; |
|
if (openedMenus.indexOf(index) !== -1) return; |
|
// 将不在该菜单路径下的其余菜单收起 |
|
// collapse all menu that are not under current menu item |
|
if (this.uniqueOpened) { |
|
this.openedMenus = openedMenus.filter(index => { |
|
return indexPath.indexOf(index) !== -1; |
|
}); |
|
} |
|
this.openedMenus.push(index); |
|
}, |
|
closeMenu(index) { |
|
const i = this.openedMenus.indexOf(index); |
|
if (i !== -1) { |
|
this.openedMenus.splice(i, 1); |
|
} |
|
}, |
|
handleSubmenuClick(submenu) { |
|
const { index, indexPath } = submenu; |
|
let isOpened = this.openedMenus.indexOf(index) !== -1; |
|
|
|
if (isOpened) { |
|
this.closeMenu(index); |
|
this.$emit('close', index, indexPath); |
|
} else { |
|
this.openMenu(index, indexPath); |
|
this.$emit('open', index, indexPath); |
|
} |
|
}, |
|
handleItemClick(item) { |
|
const { index, indexPath } = item; |
|
const oldActiveIndex = this.activeIndex; |
|
const hasIndex = item.index !== null; |
|
|
|
if (hasIndex) { |
|
this.activeIndex = item.index; |
|
} |
|
|
|
this.$emit('select', index, indexPath, item); |
|
|
|
if (this.mode === 'horizontal' || this.collapse) { |
|
this.openedMenus = []; |
|
} |
|
|
|
if (this.router && hasIndex) { |
|
this.routeToItem(item, (error) => { |
|
this.activeIndex = oldActiveIndex; |
|
if (error) { |
|
// vue-router 3.1.0+ push/replace cause NavigationDuplicated error |
|
// https://github.com/ElemeFE/element/issues/17044 |
|
if (error.name === 'NavigationDuplicated') return |
|
console.error(error) |
|
} |
|
}); |
|
} |
|
}, |
|
// 初始化展开菜单 |
|
// initialize opened menu |
|
initOpenedMenu() { |
|
const index = this.activeIndex; |
|
const activeItem = this.items[index]; |
|
if (!activeItem || this.mode === 'horizontal' || this.collapse) return; |
|
|
|
let indexPath = activeItem.indexPath; |
|
|
|
// 展开该菜单项的路径上所有子菜单 |
|
// expand all submenus of the menu item |
|
indexPath.forEach(index => { |
|
let submenu = this.submenus[index]; |
|
submenu && this.openMenu(index, submenu.indexPath); |
|
}); |
|
}, |
|
routeToItem(item, onError) { |
|
let route = item.route || item.index; |
|
try { |
|
this.$router.push(route, () => {}, onError); |
|
} catch (e) { |
|
console.error(e); |
|
} |
|
}, |
|
open(index) { |
|
const { indexPath } = this.submenus[index.toString()]; |
|
indexPath.forEach(i => this.openMenu(i, indexPath)); |
|
}, |
|
close(index) { |
|
this.closeMenu(index); |
|
} |
|
}, |
|
mounted() { |
|
this.initOpenedMenu(); |
|
this.$on('item-click', this.handleItemClick); |
|
this.$on('submenu-click', this.handleSubmenuClick); |
|
if (this.mode === 'horizontal') { |
|
new Menubar(this.$el); // eslint-disable-line |
|
} |
|
this.$watch('items', this.updateActiveIndex); |
|
} |
|
}; |
|
</script>
|
|
|