improve code & fix inittial submenu active bug (#2225)

pull/2237/head
baiyaaaaa 2017-01-05 23:19:49 +08:00 committed by cinwell.li
parent f95ca3e09d
commit d9d673bcc3
8 changed files with 277 additions and 200 deletions

View File

@ -32,7 +32,7 @@ Top bar NavMenu can be used in a variety of scenarios.
<el-menu-item index="2-2">item two</el-menu-item> <el-menu-item index="2-2">item two</el-menu-item>
<el-menu-item index="2-3">item three</el-menu-item> <el-menu-item index="2-3">item three</el-menu-item>
</el-submenu> </el-submenu>
<el-menu-item index="3">Orders</el-menu-item> <el-menu-item index="3"><a href="https://www.ele.me" target="_blank">Orders</a></el-menu-item>
</el-menu> </el-menu>
<div class="line"></div> <div class="line"></div>
<el-menu default-active="1" class="el-menu-demo" mode="horizontal" @select="handleSelect"> <el-menu default-active="1" class="el-menu-demo" mode="horizontal" @select="handleSelect">
@ -43,7 +43,7 @@ Top bar NavMenu can be used in a variety of scenarios.
<el-menu-item index="2-2">item two</el-menu-item> <el-menu-item index="2-2">item two</el-menu-item>
<el-menu-item index="2-3">item three</el-menu-item> <el-menu-item index="2-3">item three</el-menu-item>
</el-submenu> </el-submenu>
<el-menu-item index="3">Orders </el-menu-item> <el-menu-item index="3"><a href="https://www.ele.me" target="_blank">Orders</a></el-menu-item>
</el-menu> </el-menu>
<script> <script>

View File

@ -53,6 +53,7 @@
适用广泛的基础用法。 适用广泛的基础用法。
::: demo 导航菜单默认为垂直模式,通过 `mode` 属性可以使导航菜单变更为水平模式。另外,在菜单中通过 `submenu` 组件可以生成二级菜单。 ::: demo 导航菜单默认为垂直模式,通过 `mode` 属性可以使导航菜单变更为水平模式。另外,在菜单中通过 `submenu` 组件可以生成二级菜单。
```html ```html
<el-menu theme="dark" default-active="1" class="el-menu-demo" mode="horizontal" @select="handleSelect"> <el-menu theme="dark" default-active="1" class="el-menu-demo" mode="horizontal" @select="handleSelect">
<el-menu-item index="1">处理中心</el-menu-item> <el-menu-item index="1">处理中心</el-menu-item>
@ -62,7 +63,7 @@
<el-menu-item index="2-2">选项2</el-menu-item> <el-menu-item index="2-2">选项2</el-menu-item>
<el-menu-item index="2-3">选项3</el-menu-item> <el-menu-item index="2-3">选项3</el-menu-item>
</el-submenu> </el-submenu>
<el-menu-item index="3">订单管理</el-menu-item> <el-menu-item index="3"><a href="https://www.ele.me" target="_blank">订单管理</a></el-menu-item>
</el-menu> </el-menu>
<div class="line"></div> <div class="line"></div>
<el-menu default-active="1" class="el-menu-demo" mode="horizontal" @select="handleSelect"> <el-menu default-active="1" class="el-menu-demo" mode="horizontal" @select="handleSelect">
@ -73,7 +74,7 @@
<el-menu-item index="2-2">选项2</el-menu-item> <el-menu-item index="2-2">选项2</el-menu-item>
<el-menu-item index="2-3">选项3</el-menu-item> <el-menu-item index="2-3">选项3</el-menu-item>
</el-submenu> </el-submenu>
<el-menu-item index="3">订单管理</el-menu-item> <el-menu-item index="3"><a href="https://www.ele.me" target="_blank">订单管理</a></el-menu-item>
</el-menu> </el-menu>
<script> <script>

View File

@ -11,12 +11,14 @@
</template> </template>
<script> <script>
import Menu from './menu-mixin'; import Menu from './menu-mixin';
import Emitter from 'element-ui/src/mixins/emitter';
module.exports = { module.exports = {
name: 'ElMenuItem', name: 'ElMenuItem',
componentName: 'ElMenuItem', componentName: 'ElMenuItem',
mixins: [Menu], mixins: [Menu, Emitter],
props: { props: {
index: { index: {
@ -34,21 +36,21 @@
}, },
computed: { computed: {
active() { active() {
return this.index === this.rootMenu.activeIndex; return this.index === this.rootMenu.activedIndex;
} }
}, },
methods: { methods: {
handleClick() { handleClick() {
this.rootMenu.handleSelect( this.dispatch('ElMenu', 'item-click', this);
this.index,
this.indexPath,
this.route || this.index,
this
);
} }
}, },
created() { created() {
this.rootMenu.menuItems[this.index] = this; this.parentMenu.addItem(this);
this.rootMenu.addItem(this);
},
beforeDestroy() {
this.parentMenu.removeItem(this);
this.rootMenu.removeItem(this);
} }
}; };
</script> </script>

View File

@ -13,7 +13,20 @@ module.exports = {
}, },
rootMenu() { rootMenu() {
var parent = this.$parent; var parent = this.$parent;
while (parent.$options.componentName !== 'ElMenu') { while (
parent &&
parent.$options.componentName !== 'ElMenu'
) {
parent = parent.$parent;
}
return parent;
},
parentMenu() {
let parent = this.$parent;
while (
parent &&
['ElMenu', 'ElSubmenu'].indexOf(parent.$options.componentName) === -1
) {
parent = parent.$parent; parent = parent.$parent;
} }
return parent; return parent;

View File

@ -41,26 +41,37 @@
}, },
data() { data() {
return { return {
activeIndex: this.defaultActive, activedIndex: this.defaultActive,
openedMenus: this.defaultOpeneds ? this.defaultOpeneds.slice(0) : [], openedMenus: this.defaultOpeneds ? this.defaultOpeneds.slice(0) : [],
menuItems: {}, items: {},
submenus: {} submenus: {}
}; };
}, },
watch: { watch: {
defaultActive(value) { defaultActive(value) {
this.activeIndex = value; const item = this.items[value];
if (!this.menuItems[value]) return; if (!item) return;
let menuItem = this.menuItems[value];
let indexPath = menuItem.indexPath;
this.handleSelect(value, indexPath, null, menuItem); this.activedIndex = item.index;
this.initOpenedMenu();
}, },
defaultOpeneds(value) { defaultOpeneds(value) {
this.openedMenus = value; this.openedMenus = value;
} }
}, },
methods: { methods: {
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) { openMenu(index, indexPath) {
let openedMenus = this.openedMenus; let openedMenus = this.openedMenus;
if (openedMenus.indexOf(index) !== -1) return; if (openedMenus.indexOf(index) !== -1) return;
@ -75,7 +86,8 @@
closeMenu(index, indexPath) { closeMenu(index, indexPath) {
this.openedMenus.splice(this.openedMenus.indexOf(index), 1); this.openedMenus.splice(this.openedMenus.indexOf(index), 1);
}, },
handleSubmenuClick(index, indexPath) { handleSubmenuClick(submenu) {
const { index, indexPath } = submenu;
let isOpened = this.openedMenus.indexOf(index) !== -1; let isOpened = this.openedMenus.indexOf(index) !== -1;
if (isOpened) { if (isOpened) {
@ -86,49 +98,46 @@
this.$emit('open', index, indexPath); this.$emit('open', index, indexPath);
} }
}, },
handleSelect(index, indexPath, route, instance) { handleItemClick(item) {
this.activeIndex = index; let { index, indexPath } = item;
this.$emit('select', index, indexPath, instance); this.activedIndex = item.index;
this.$emit('select', index, indexPath, item);
if (this.mode === 'horizontal') { if (this.mode === 'horizontal') {
this.broadcast('ElSubmenu', 'item-select', [index, indexPath]);
this.openedMenus = []; this.openedMenus = [];
} else {
this.openActiveItemMenus();
} }
if (this.router && route) { if (this.router) {
try { this.routeToItem(item);
this.$router.push(route);
} catch (e) {
console.error(e);
}
} }
}, },
openActiveItemMenus() { //
let index = this.activeIndex; initOpenedMenu() {
// menu const index = this.activedIndex;
if (this.router) { const activeItem = this.items[index];
const userSpecifiedIndexs = Object if (!activeItem || this.mode === 'horizontal') return;
.keys(this.menuItems)
.filter(k => this.menuItems[k].route)
.filter(k => this.menuItems[k].route.path === this.$route.path);
userSpecifiedIndexs.length && (index = this.activeIndex = userSpecifiedIndexs[0]);
}
if (!this.menuItems[index]) return;
if (index && this.mode === 'vertical') {
let indexPath = this.menuItems[index].indexPath;
// let indexPath = activeItem.indexPath;
indexPath.forEach(index => {
let submenu = this.submenus[index]; //
submenu && this.openMenu(index, submenu.indexPath); indexPath.forEach(index => {
}); let submenu = this.submenus[index];
submenu && this.openMenu(index, submenu.indexPath);
});
},
routeToItem(item) {
let route = item.route || item.index;
try {
this.$router.push(route);
} catch (e) {
console.error(e);
} }
} }
}, },
mounted() { mounted() {
this.openActiveItemMenus(); this.initOpenedMenu();
this.$on('item-click', this.handleItemClick);
this.$on('submenu-click', this.handleSubmenuClick);
} }
}; };
</script> </script>

View File

@ -22,13 +22,14 @@
</template> </template>
<script> <script>
import menuMixin from './menu-mixin'; import menuMixin from './menu-mixin';
import Emitter from 'element-ui/src/mixins/emitter';
module.exports = { module.exports = {
name: 'ElSubmenu', name: 'ElSubmenu',
componentName: 'ElSubmenu', componentName: 'ElSubmenu',
mixins: [menuMixin], mixins: [menuMixin, Emitter],
props: { props: {
index: { index: {
@ -39,17 +40,52 @@
data() { data() {
return { return {
timeout: null, timeout: null,
active: false items: {},
submenus: {}
}; };
}, },
computed: { computed: {
opened() { opened() {
return this.rootMenu.openedMenus.indexOf(this.index) !== -1; return this.rootMenu.openedMenus.indexOf(this.index) > -1;
},
active: {
cache: false,
get() {
let isActive = false;
const submenus = this.submenus;
const items = this.items;
Object.keys(items).forEach(index => {
if (items[index].active) {
isActive = true;
}
});
Object.keys(submenus).forEach(index => {
if (submenus[index].active) {
isActive = true;
}
});
return isActive;
}
} }
}, },
methods: { methods: {
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];
},
handleClick() { handleClick() {
this.rootMenu.handleSubmenuClick(this.index, this.indexPath); this.dispatch('ElMenu', 'submenu-click', this);
}, },
handleMouseenter() { handleMouseenter() {
clearTimeout(this.timeout); clearTimeout(this.timeout);
@ -83,12 +119,14 @@
} }
}, },
created() { created() {
this.rootMenu.submenus[this.index] = this; this.parentMenu.addSubmenu(this);
this.rootMenu.addSubmenu(this);
},
beforeDestroy() {
this.parentMenu.removeSubmenu(this);
this.rootMenu.removeSubmenu(this);
}, },
mounted() { mounted() {
this.$on('item-select', (index, indexPath) => {
this.active = indexPath.indexOf(this.index) !== -1;
});
this.initEvents(); this.initEvents();
} }
}; };

View File

@ -59,6 +59,11 @@
box-sizing: border-box; box-sizing: border-box;
border-bottom: 5px solid transparent; border-bottom: 5px solid transparent;
a,
a:hover {
color: inherit;
}
&:hover { &:hover {
background-color: var(--menu-item-hover-fill); background-color: var(--menu-item-hover-fill);
} }

View File

@ -27,133 +27,165 @@ describe('Menu', () => {
}); });
}); });
}); });
it('default active', done => { describe('default active', () => {
vm = createVue({ it('normal active', done => {
template: ` vm = createVue({
<el-menu default-active="2"> template: `
<el-menu-item index="1" ref="item1">处理中心</el-menu-item> <el-menu default-active="2">
<el-menu-item index="2" ref="item2">订单管理</el-menu-item> <el-menu-item index="1" ref="item1">处理中心</el-menu-item>
</el-menu> <el-menu-item index="2" ref="item2">订单管理</el-menu-item>
` </el-menu>
}, true); `
expect(vm.$refs.item2.$el.classList.contains('is-active')).to.be.true; }, true);
vm.$refs.item1.$el.click(); expect(vm.$refs.item2.$el.classList.contains('is-active')).to.be.true;
vm.$nextTick(_ => { vm.$refs.item1.$el.click();
expect(vm.$refs.item1.$el.classList.contains('is-active')).to.be.true;
done();
});
});
it('active watch', done => {
vm = createVue({
template: `
<el-menu :default-active="active">
<el-menu-item index="1" ref="item1">active watch处理中心</el-menu-item>
<el-menu-item index="2" ref="item2">active watch订单管理</el-menu-item>
</el-menu>
`,
data() {
return {
active: '2'
};
}
}, true);
setTimeout(_ => {
vm.active = '1';
vm.$nextTick(_ => { vm.$nextTick(_ => {
expect(vm.$refs.item1.$el.classList.contains('is-active')).to.be.true; expect(vm.$refs.item1.$el.classList.contains('is-active')).to.be.true;
done(); done();
}); });
}, 100);
});
it('default active in submenu', done => {
vm = createVue({
template: `
<el-menu default-active="2-2">
<el-menu-item index="1" ref="item1">处理中心</el-menu-item>
<el-submenu index="2" ref="submenu">
<template slot="title">我的工作台</template>
<el-menu-item index="2-1">选项1</el-menu-item>
<el-menu-item index="2-2" ref="submenuItem2">选项2</el-menu-item>
<el-menu-item index="2-3">选项3</el-menu-item>
</el-submenu>
<el-menu-item index="3">订单管理</el-menu-item>
</el-menu>
`
}, true);
expect(vm.$refs.submenuItem2.$el.classList.contains('is-active')).to.be.true;
// vm.$refs.item1.$el.click();
vm.$nextTick(_ => {
expect(vm.$refs.submenu.$el.classList.contains('is-opened')).to.be.true;
done();
}); });
}); it('dynamic active', done => {
it('submenu', done => { vm = createVue({
vm = createVue({ template: `
template: ` <el-menu :default-active="active">
<el-menu> <el-menu-item index="1" ref="item1">active watch处理中心</el-menu-item>
<el-menu-item index="1" ref="item1">处理中心</el-menu-item> <el-menu-item index="2" ref="item2">active watch订单管理</el-menu-item>
<el-submenu index="2" ref="submenu"> </el-menu>
<template slot="title">我的工作台</template> `,
<el-menu-item index="2-1">选项1</el-menu-item> data() {
<el-menu-item index="2-2" ref="submenuItem2">选项2</el-menu-item> return {
<el-menu-item index="2-3">选项3</el-menu-item> active: '2'
</el-submenu> };
<el-menu-item index="3">订单管理</el-menu-item> }
</el-menu> }, true);
`, setTimeout(_ => {
data() { vm.active = '1';
return {
};
}
}, true);
var submenuItem2 = vm.$refs.submenuItem2;
var submenu = vm.$refs.submenu;
submenu.$el.querySelector('.el-submenu__title').click();
vm.$nextTick(_ => {
expect(submenu.$el.classList.contains('is-opened')).to.be.true;
submenuItem2.$el.click();
vm.$nextTick(_ => {
expect(submenuItem2.$el.classList.contains('is-active')).to.be.true;
submenu.$el.querySelector('.el-submenu__title').click();
vm.$nextTick(_ => { vm.$nextTick(_ => {
expect(submenu.$el.classList.contains('is-opened')).to.not.true; expect(vm.$refs.item1.$el.classList.contains('is-active')).to.be.true;
done(); done();
}); });
}, 100);
});
it('vertical submenu item active', done => {
vm = createVue({
template: `
<div>
<el-menu default-active="2-2">
<el-menu-item index="1" ref="item1">处理中心</el-menu-item>
<el-submenu index="2" ref="submenu">
<template slot="title">我的工作台</template>
<el-menu-item index="2-1">选项1</el-menu-item>
<el-menu-item index="2-2" ref="submenuItem2">选项2</el-menu-item>
<el-menu-item index="2-3">选项3</el-menu-item>
</el-submenu>
<el-menu-item index="3">订单管理</el-menu-item>
</el-menu>
</div>
`
}, true);
expect(vm.$refs.submenuItem2.$el.classList.contains('is-active')).to.be.true;
vm.$nextTick(_ => {
expect(vm.$refs.submenu.$el.classList.contains('is-opened')).to.be.true;
expect(vm.$refs.submenu.$el.classList.contains('is-active')).to.be.true;
done();
});
});
it('horizontal submenu item active', done => {
vm = createVue({
template: `
<div>
<el-menu default-active="2-2">
<el-menu-item index="1" ref="item1">处理中心</el-menu-item>
<el-submenu index="2" ref="submenu">
<template slot="title">我的工作台</template>
<el-menu-item index="2-1">选项1</el-menu-item>
<el-menu-item index="2-2" ref="submenuItem2">选项2</el-menu-item>
<el-menu-item index="2-3">选项3</el-menu-item>
</el-submenu>
<el-menu-item index="3">订单管理</el-menu-item>
</el-menu>
</div>
`
}, true);
expect(vm.$refs.submenuItem2.$el.classList.contains('is-active')).to.be.true;
vm.$nextTick(_ => {
expect(vm.$refs.submenu.$el.classList.contains('is-opened')).to.be.true;
expect(vm.$refs.submenu.$el.classList.contains('is-active')).to.be.true;
done();
}); });
}); });
}); });
it('submenu default opened', done => { describe('submenu', () => {
vm = createVue({ it('toggle', done => {
template: ` vm = createVue({
<el-menu theme="dark" :default-openeds="defaultOpeneds"> template: `
<el-menu-item index="1">default opened处理中心</el-menu-item> <el-menu>
<el-submenu index="2" ref="submenu1"> <el-menu-item index="1" ref="item1">处理中心</el-menu-item>
<template slot="title">default opened我的工作台</template> <el-submenu index="2" ref="submenu">
<el-menu-item index="2-1">选项1</el-menu-item> <template slot="title">我的工作台</template>
<el-menu-item index="2-2" ref="submenu1Item2">选项2</el-menu-item> <el-menu-item index="2-1">选项1</el-menu-item>
<el-menu-item index="2-3">选项3</el-menu-item> <el-menu-item index="2-2" ref="submenuItem2">选项2</el-menu-item>
</el-submenu> <el-menu-item index="2-3">选项3</el-menu-item>
<el-submenu index="3" ref="submenu2"> </el-submenu>
<template slot="title">default opened订单管理</template> <el-menu-item index="3">订单管理</el-menu-item>
<el-menu-item index="3-1">选项1</el-menu-item> </el-menu>
<el-menu-item index="3-2" ref="submenu2Item2">选项2</el-menu-item> `,
<el-menu-item index="3-3">选项3</el-menu-item> data() {
</el-submenu> return {
</el-menu> };
`, }
data() { }, true);
return { var submenuItem2 = vm.$refs.submenuItem2;
defaultOpeneds: ['2', '3'] var submenu = vm.$refs.submenu;
}; submenu.$el.querySelector('.el-submenu__title').click();
} vm.$nextTick(_ => {
}, true); expect(submenu.$el.classList.contains('is-opened')).to.be.true;
expect(vm.$refs.submenu1.$el.classList.contains('is-opened')).to.be.true; submenuItem2.$el.click();
expect(vm.$refs.submenu2.$el.classList.contains('is-opened')).to.be.true; vm.$nextTick(_ => {
vm.defaultOpeneds = ['2']; expect(submenuItem2.$el.classList.contains('is-active')).to.be.true;
vm.$nextTick(_ => { submenu.$el.querySelector('.el-submenu__title').click();
vm.$nextTick(_ => {
expect(submenu.$el.classList.contains('is-opened')).to.not.true;
done();
});
});
});
});
it('default opened', done => {
vm = createVue({
template: `
<el-menu theme="dark" :default-openeds="defaultOpeneds">
<el-menu-item index="1">default opened处理中心</el-menu-item>
<el-submenu index="2" ref="submenu1">
<template slot="title">default opened我的工作台</template>
<el-menu-item index="2-1">选项1</el-menu-item>
<el-menu-item index="2-2" ref="submenu1Item2">选项2</el-menu-item>
<el-menu-item index="2-3">选项3</el-menu-item>
</el-submenu>
<el-submenu index="3" ref="submenu2">
<template slot="title">default opened订单管理</template>
<el-menu-item index="3-1">选项1</el-menu-item>
<el-menu-item index="3-2" ref="submenu2Item2">选项2</el-menu-item>
<el-menu-item index="3-3">选项3</el-menu-item>
</el-submenu>
</el-menu>
`,
data() {
return {
defaultOpeneds: ['2', '3']
};
}
}, true);
expect(vm.$refs.submenu1.$el.classList.contains('is-opened')).to.be.true; expect(vm.$refs.submenu1.$el.classList.contains('is-opened')).to.be.true;
expect(vm.$refs.submenu2.$el.classList.contains('is-opened')).to.not.true; expect(vm.$refs.submenu2.$el.classList.contains('is-opened')).to.be.true;
done(); vm.defaultOpeneds = ['2'];
vm.$nextTick(_ => {
expect(vm.$refs.submenu1.$el.classList.contains('is-opened')).to.be.true;
expect(vm.$refs.submenu2.$el.classList.contains('is-opened')).to.not.true;
done();
});
}); });
}); });
it('theme', () => { it('theme', () => {
@ -195,10 +227,9 @@ describe('Menu', () => {
}; };
} }
}, true); }, true);
vm.$refs.submenu2Item2.$el.click(); vm.$refs.submenu2.$el.querySelector('.el-submenu__title').click();
vm.$nextTick(_ => { vm.$nextTick(_ => {
expect(vm.$refs.submenu1.$el.classList.contains('is-opened')).to.not.true; expect(vm.$refs.submenu1.$el.classList.contains('is-opened')).to.not.true;
expect(vm.$refs.submenu2Item2.$el.classList.contains('is-active')).to.be.true;
done(); done();
}); });
}); });
@ -268,28 +299,6 @@ describe('Menu', () => {
}, 1000); }, 1000);
}, 500); }, 500);
}); });
it('horizontal submenu active', done => {
vm = createVue({
template: `
<el-menu mode="horizontal">
<el-menu-item index="1">处理中心</el-menu-item>
<el-submenu index="2" ref="submenu">
<template slot="title">我的工作台</template>
<el-menu-item index="2-1">选项1</el-menu-item>
<el-menu-item index="2-2" ref="submenuItem2">选项2</el-menu-item>
<el-menu-item index="2-3">选项3</el-menu-item>
</el-submenu>
<el-menu-item index="3">订单管理</el-menu-item>
</el-menu>
`
}, true);
let submenuItem2 = vm.$refs.submenuItem2;
submenuItem2.$el.click();
vm.$nextTick(_ => {
expect(vm.$refs.submenu.$el.classList.contains('is-active')).to.be.true;
done();
});
});
it('menu group', done => { it('menu group', done => {
vm = createVue({ vm = createVue({
template: ` template: `