Merge pull request #6 from ElemeFE/menu-fixed

Menu fixed
pull/11/head
杨奕 2016-09-05 00:34:56 -05:00 committed by GitHub
commit 0080558807
8 changed files with 307 additions and 175 deletions

View File

@ -60,15 +60,25 @@
::: demo ::: demo
```html ```html
<el-menu theme="dark" default-active="1" class="el-menu-demo" @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>
<el-menu-item index="2">我的工作台</el-menu-item> <el-submenu index="2">
<template slot="title">我的工作台</template>
<el-menu-item index="2-1">选项1</el-menu-item>
<el-menu-item index="2-2">选项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-item index="3">订单管理</el-menu-item>
</el-menu> </el-menu>
<div class="line"></div> <div class="line"></div>
<el-menu default-active="1" class="el-menu-demo" @select="handleselect"> <el-menu 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>
<el-menu-item index="2">我的工作台</el-menu-item> <el-submenu index="2">
<template slot="title">我的工作台</template>
<el-menu-item index="2-1">选项1</el-menu-item>
<el-menu-item index="2-2">选项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-item index="3">订单管理</el-menu-item>
</el-menu> </el-menu>
``` ```
@ -83,12 +93,16 @@
<el-row class="tac"> <el-row class="tac">
<el-col :span="8"> <el-col :span="8">
<h5>带 icon</h5> <h5>带 icon</h5>
<el-menu mode="vertical" default-active="2" class="el-menu-vertical-demo" @open="handleopen" @close="handleclose"> <el-menu default-active="2" class="el-menu-vertical-demo" @open="handleopen" @close="handleclose">
<el-submenu index="1"> <el-submenu index="1">
<template slot="title"><i class="el-icon-message"></i>导航一</template> <template slot="title"><i class="el-icon-message"></i>导航一</template>
<el-menu-item index="1-1">选项1</el-menu-item> <el-menu-item-group title="分组一">
<el-menu-item index="1-2">选项2</el-menu-item> <el-menu-item index="1-1">选项1</el-menu-item>
<el-menu-item index="1-3">选项3</el-menu-item> <el-menu-item index="1-2">选项2</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="分组2">
<el-menu-item index="1-3">选项3</el-menu-item>
</el-menu-item-group>
</el-submenu> </el-submenu>
<el-menu-item index="2"><i class="el-icon-menu"></i>导航二</el-menu-item> <el-menu-item index="2"><i class="el-icon-menu"></i>导航二</el-menu-item>
<el-menu-item index="3"><i class="el-icon-setting"></i>导航三</el-menu-item> <el-menu-item index="3"><i class="el-icon-setting"></i>导航三</el-menu-item>
@ -96,12 +110,16 @@
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<h5>不带 icon</h5> <h5>不带 icon</h5>
<el-menu mode="vertical" default-active="2" class="el-menu-vertical-demo" @open="handleopen" @close="handleclose"> <el-menu default-active="2" class="el-menu-vertical-demo" @open="handleopen" @close="handleclose" theme="dark">
<el-submenu index="1"> <el-submenu index="1">
<template slot="title">导航一</template> <template slot="title">导航一</template>
<el-menu-item index="1-1">选项1</el-menu-item> <el-menu-item-group title="分组一">
<el-menu-item index="1-2">选项2</el-menu-item> <el-menu-item index="1-1">选项1</el-menu-item>
<el-menu-item index="1-3">选项3</el-menu-item> <el-menu-item index="1-2">选项2</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="分组2">
<el-menu-item index="1-3">选项3</el-menu-item>
</el-menu-item-group>
</el-submenu> </el-submenu>
<el-menu-item index="2">导航二</el-menu-item> <el-menu-item index="2">导航二</el-menu-item>
<el-menu-item index="3">导航三</el-menu-item> <el-menu-item index="3">导航三</el-menu-item>
@ -137,29 +155,29 @@
### Menu Attribute ### Menu Attribute
| 参数 | 说明 | 类型 | 可选值 | 默认值 | | 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---------- |-------- |---------- |------------- |-------- | |---------- |-------- |---------- |------------- |-------- |
| mode | 模式 | string | horizontal,vertical | horizontal | | mode | 模式 | string | horizontal,vertical | vertical |
| theme | 主题色 | string | light,dark | light | | theme | 主题色 | string | light,dark | light |
| default-active | 当前激活菜单的 key | string | — | — | | default-active | 当前激活菜单的 index | string | — | — |
| default-openeds | 当前打开的submenu的 key 数组 | Array | — | — | | default-openeds | 当前打开的submenu的 key 数组 | Array | — | — |
| unique-opend | 是否只保持一个子菜单的展开 | boolean | — | false | | unique-opend | 是否只保持一个子菜单的展开 | boolean | — | false |
| router | 是否使用 vue-router 的模式,启用该模式会在激活导航时以 key 作为 path 进行路由跳转 | boolean | — | false | | router | 是否使用 vue-router 的模式,启用该模式会在激活导航时以 index 作为 path 进行路由跳转 | boolean | — | false |
### Menu Events ### Menu Events
| 事件名称 | 说明 | 回调参数 | | 事件名称 | 说明 | 回调参数 |
|---------- |-------- |---------- | |---------- |-------- |---------- |
| select | 菜单激活回调 | key: 选中菜单项的 keyPath: 选中菜单项的 key path | | select | 菜单激活回调 | index: 选中菜单项的 indexPath: 选中菜单项的 index path |
| open | SubMenu 展开的回调 | key: 打开的 subMenu 的 key keyPath: 打开的 subMenu 的 key path | | open | SubMenu 展开的回调 | index: 打开的 subMenu 的 index indexPath: 打开的 subMenu 的 index path |
| close | SubMenu 收起的回调 | key: 收起的 subMenu 的 key keyPath: 收起的 subMenu 的 key path | | close | SubMenu 收起的回调 | index: 收起的 subMenu 的 index indexPath: 收起的 subMenu 的 index path |
### SubMenu Attribute ### SubMenu Attribute
| 参数 | 说明 | 类型 | 可选值 | 默认值 | | 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---------- |-------- |---------- |------------- |-------- | |---------- |-------- |---------- |------------- |-------- |
| key | 唯一标志 | string | — | — | | index | 唯一标志 | string | — | — |
### Menu-Item Attribute ### Menu-Item Attribute
| 参数 | 说明 | 类型 | 可选值 | 默认值 | | 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---------- |-------- |---------- |------------- |-------- | |---------- |-------- |---------- |------------- |-------- |
| key | 唯一标志 | string | — | — | | index | 唯一标志 | string | — | — |
### Menu-Group Attribute ### Menu-Group Attribute
| 参数 | 说明 | 类型 | 可选值 | 默认值 | | 参数 | 说明 | 类型 | 可选值 | 默认值 |

View File

@ -30,9 +30,9 @@
type="checkbox" type="checkbox"
:disabled="disabled"> :disabled="disabled">
</span> </span>
<span class="el-checkbox__label"> <span class="el-checkbox__label" v-if="$slots.default || label">
<slot></slot> <slot></slot>
<template v-if="!$slots || !$slots.default">{{label}}</template> <template v-if="!$slots.default">{{label}}</template>
</span> </span>
</label> </label>
</template> </template>

View File

@ -12,14 +12,9 @@
}, },
data() { data() {
return { return {
paddingLeft: 15 paddingLeft: 20
}; };
}, },
computed: {
activeIndex() {
return this.$parent.activeIndex;
}
},
methods: { methods: {
initPadding() { initPadding() {
var parent = this.$parent; var parent = this.$parent;

View File

@ -1,13 +1,9 @@
<script> <script>
import emitter from 'main/mixins/emitter';
module.exports = { module.exports = {
name: 'el-menu-item', name: 'el-menu-item',
componentName: 'menu-item', componentName: 'menu-item',
mixins: [emitter],
props: { props: {
index: { index: {
type: String, type: String,
@ -20,21 +16,33 @@
}, },
computed: { computed: {
indexPath() { indexPath() {
return this.$parent.indexPath ? this.$parent.indexPath.concat(this.index) : [this.index]; var path = [this.index];
var parent = this.$parent;
while (parent.$options._componentTag !== 'el-menu') {
if (parent.index) {
path.unshift(parent.index);
}
parent = parent.$parent;
}
return path;
}, },
activeIndex() { rootMenu() {
return this.$parent.activeIndex; var parent = this.$parent;
while (parent.$options._componentTag !== 'el-menu') {
parent = parent.$parent;
}
return parent;
}, },
active() { active() {
return this.index === this.activeIndex; return this.index === this.rootMenu.activeIndex;
} }
}, },
methods: { methods: {
handleClick() { handleClick() {
if (!this.active) { this.rootMenu.handleSelect(this.index, this.indexPath);
this.dispatch('menu', 'select-key', [this.index, this.indexPath]);
}
} }
},
mounted() {
} }
}; };
</script> </script>
@ -47,8 +55,5 @@
'is-disabled': disabled 'is-disabled': disabled
}"> }">
<slot></slot> <slot></slot>
<transition name="fade" mode="out-in">
<span class="el-menu-item__bar" v-if="active"></span>
</transition>
</li> </li>
</template> </template>

View File

@ -1,7 +1,7 @@
<template> <template>
<ul class="el-menu" <ul class="el-menu"
:class="{ :class="{
'el-menu--vertical': mode === 'vertical', 'el-menu--horizontal': mode === 'horizontal',
'el-menu--dark': theme === 'dark' 'el-menu--dark': theme === 'dark'
}" }"
> >
@ -21,7 +21,7 @@
props: { props: {
mode: { mode: {
type: String, type: String,
default: '' default: 'vertical'
}, },
defaultActive: { defaultActive: {
type: String, type: String,
@ -43,31 +43,30 @@
data() { data() {
return { return {
activeIndex: this.defaultActive, activeIndex: this.defaultActive,
openedMenus: this.defaultOpeneds openedMenus: this.defaultOpeneds.slice(0)
}; };
}, },
methods: { methods: {
handleMenuExpand(key, keyPath) { handleMenuExpand(index, indexPath) {
this.openedMenus.push(key);
if (this.uniqueOpend) { if (this.uniqueOpend) {
this.broadcast('submenu', 'close-menu', keyPath); this.broadcast('submenu', 'close-menu', indexPath);
this.openedMenus = this.openedMenus.filter((key) => { this.openedMenus = this.openedMenus.filter((index) => {
return keyPath.indexOf(key) !== -1; return indexPath.indexOf(index) !== -1;
}); });
} }
this.$emit('open', key, keyPath); this.$emit('open', index, indexPath);
}, },
handleMenuCollapse(key, keyPath) { handleMenuCollapse(index, indexPath) {
this.openedMenus.splice(this.openedMenus.indexOf(key), 1); this.openedMenus.splice(this.openedMenus.indexOf(index), 1);
this.$emit('close', key, keyPath); this.$emit('close', index, indexPath);
}, },
handleSelect(key, keyPath) { handleSelect(index, indexPath) {
this.activeIndex = key; this.activeIndex = index;
this.$emit('select', key, keyPath); this.$emit('select', index, indexPath);
this.broadcast('submenu', 'select', [index, indexPath]);
if (this.router) { if (this.router) {
this.$router.push(key); this.$router.push(index);
} }
} }
}, },
@ -75,7 +74,6 @@
this.broadcast('submenu', 'open-menu', this.openedMenus); this.broadcast('submenu', 'open-menu', this.openedMenus);
this.$on('expand-menu', this.handleMenuExpand); this.$on('expand-menu', this.handleMenuExpand);
this.$on('collapse-menu', this.handleMenuCollapse); this.$on('collapse-menu', this.handleMenuCollapse);
this.$on('select-key', this.handleSelect);
} }
}; };
</script> </script>

View File

@ -16,25 +16,62 @@
}, },
data() { data() {
return { return {
opened: false opened: false,
timeout: null,
active: false
}; };
}, },
computed: { computed: {
indexPath() { indexPath() {
return this.$parent.indexPath ? this.$parent.indexPath.concat(this.index) : [this.index]; var path = [this.index];
var parent = this.$parent;
while (parent.$options._componentTag !== 'el-menu') {
if (parent.index) {
path.unshift(parent.index);
}
parent = parent.$parent;
}
return path;
}, },
activeIndex() { rootMenu() {
return this.$parent.activeIndex; var parent = this.$parent;
while (parent.$options._componentTag !== 'el-menu') {
parent = parent.$parent;
}
return parent;
},
mode() {
return this.rootMenu.mode;
} }
}, },
methods: { methods: {
handleClick() { handleClick() {
if (!this.opened) { if (this.mode === 'vertical') {
this.dispatch('menu', 'expand-menu', [this.index, this.indexPath]); if (!this.opened) {
this.opened = true; this.dispatch('menu', 'expand-menu', [this.index, this.indexPath]);
} else { this.opened = true;
this.dispatch('menu', 'collapse-menu', [this.index, this.indexPath]); } else {
this.opened = false; this.dispatch('menu', 'collapse-menu', [this.index, this.indexPath]);
this.opened = false;
}
}
},
handleMouseenter() {
if (this.mode === 'horizontal') {
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.dispatch('menu', 'expand-menu', [this.index, this.indexPath]);
this.opened = true;
}, 300);
}
},
handleMouseleave() {
if (this.mode === 'horizontal') {
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.dispatch('menu', 'collapse-menu', [this.index, this.indexPath]);
this.opened = false;
}, 300);
} }
} }
}, },
@ -49,18 +86,47 @@
this.opened = true; this.opened = true;
} }
}); });
this.$on('select', (index, indexPath) => {
if (this.mode === 'horizontal') {
this.active = indexPath.indexOf(this.index) !== -1;
this.opened = false;
}
});
},
render(h) {
var submenu;
if (this.mode === 'horizontal') {
submenu = (
<transition name="md-fade-bottom">
{this.opened ? <ul class="el-menu">{this.$slots.default}</ul> : null }
</transition>
);
} else {
submenu = (
this.opened ? <ul class="el-menu">{this.$slots.default}</ul> : null
);
}
return (
<li
class={{ 'el-submenu': true, 'is-active': this.active, 'is-opened': this.opened}}
on-mouseenter={this.handleMouseenter}
on-mouseleave={this.handleMouseleave}
>
<div class="el-submenu__title" on-click={this.handleClick}>
{this.$slots.title}
<i class={{
'el-submenu__icon-arrow': true,
'el-icon-arrow-down': this.mode === 'vertical',
'el-icon-caret-bottom': this.mode === 'horizontal'
}}>
</i>
</div>
{submenu}
</li>
);
} }
}; };
</script> </script>
<template>
<li class="el-submenu" :class="{'is-opened': opened}">
<div class="el-menu-item el-submenu__title" @click="handleClick">
<slot name="title"></slot>
<i class="el-submenu__icon-arrow el-icon-arrow-up"></i>
</div>
<ul class="el-menu" v-show="opened">
<slot></slot>
</ul>
</li>
</template>

View File

@ -1,108 +1,141 @@
@charset "UTF-8"; @charset "UTF-8";
@import "./common/var.css"; @import "./common/var.css";
@define-extend menu-item {
height: 56px;
line-height: 56px;
font-size: 14px;
color: var(--menu-item-color);
padding: 0 20px;
cursor: pointer;
position: relative;
transition: border-color .3s, background-color .3s, color .3s;
box-sizing: border-box;
}
@component-namespace el { @component-namespace el {
@b menu { @b menu {
height: 60px;
border-radius: 2px; border-radius: 2px;
line-height: 60px;
list-style: none; list-style: none;
position: relative; position: relative;
margin: 0; margin: 0;
padding-left: 0; padding-left: 0;
background-color: #fff;
background-color: var(--menu-item-fill); background-color: var(--menu-item-fill);
@utils-clearfix;
& li { & li {
list-style: none; list-style: none;
} }
@m vertical {
height: auto;
& .el-menu-item {
float: none;
height: 56px;
line-height: 56px;
margin: 0;
padding-left: 20px;
cursor: pointer;
position: relative;
&:hover {
background-color: var(--menu-item-hover-fill);
}
& .el-menu-item__bar {
display: none;
}
}
& .el-menu-item.is-active {
color: var(--color-primary);
}
}
@m dark { @m dark {
background-color: var(--dark-menu-item-fill); background-color: var(--dark-menu-item-fill);
& .el-menu-item, & .el-menu-item,
& .el-submenu__title { & .el-submenu__title {
color: #c0ccda; color: #c0ccda;
}
& .el-menu-item {
background-color: var(--dark-menu-item-fill);
}
&.el-menu--vertical {
& .el-menu-item,
& .el-submenu__title {
&:hover {
background-color: var(--dark-menu-item-hover-fill);
}
&.is-active {
background-color: #5e6d82;
color: #fff;
}
}
}
& .el-submenu .el-menu {
background-color: var(--dark-submenu-item-fill);
}
& .el-submenu .el-menu-item:not(.el-submenu__title) {
background-color: transparent;
color: #99a9bf;
&:hover { &:hover {
background-color: var(--dark-menu-item-hover-fill); background-color: #475669;
} }
&.is-active { }
background-color: #5e6d82;
color: #fff; & .el-submenu .el-menu {
background-color: #1f2f3d;
& .el-menu-item:hover {
background-color: #475669;
}
}
}
@m horizontal {
& .el-menu-item {
float: left;
height: 60px;
line-height: 60px;
margin: 0;
cursor: pointer;
position: relative;
box-sizing: border-box;
border-bottom: 5px solid transparent;
&:hover {
background-color: var(--menu-item-hover-fill);
}
}
& .el-submenu {
float: left;
position: relative;
> .el-menu {
position: absolute;
top: 65px;
left: 0;
border:1px solid #d3dce6;
padding: 5px 0;
background-color: #fff;
z-index: 1;
width: 100%;
box-shadow: 0px 2px 4px 0px rgba(0,0,0,0.12), 0px 0px 6px 0px rgba(0,0,0,0.04);
}
& .el-submenu__title {
height: 60px;
line-height: 60px;
border-bottom: 5px solid transparent;
}
& .el-menu-item {
background-color: #fff;
float: none;
height: 36px;
line-height: 36px;
padding: 0 10px;
}
& .el-submenu__icon-arrow {
position: static;
vertical-align: middle;
margin-left: 5px;
color: #99a9bf;
margin-top: -3px;
}
}
& .el-menu-item:hover,
& .el-submenu__title:hover {
background-color: var(--menu-item-fill);
}
& > .el-menu-item:hover,
& > .el-submenu:hover .el-submenu__title,
& > .el-submenu.is-active .el-submenu__title {
border-bottom: 5px solid var(--color-primary);
}
&.el-menu--dark {
& .el-menu-item:hover,
& .el-submenu__title:hover {
background-color: var(--dark-menu-item-fill);
}
& .el-submenu {
.el-menu-item,
.el-submenu-title {
color: #475669;
&:hover {
background-color: #d3dce6;
}
}
.el-menu-item.is-active {
color: var(--color-primary);
}
} }
} }
} }
} }
@b menu-item { @b menu-item {
font-size: 14px; @extend menu-item;
color: var(--menu-item-color);
float: left;
height: 100%;
padding: 0 20px;
cursor: pointer;
position: relative;
transition: var(--md-fade-transition);
transform-origin: center center;
background-color: var(--menu-item-fill);
@e bar {
content: '';
width: 100%;
height: 5px;
background-color: var(--color-primary);
bottom: 0;
left: 0;
position: absolute;
display: block;
}
& [class^="el-icon-"] { & [class^="el-icon-"] {
vertical-align: baseline; vertical-align: baseline;
margin-right: 10px; margin-right: 10px;
@ -113,40 +146,57 @@
&:last-child { &:last-child {
margin-right: 0; margin-right: 0;
} }
&:hover {
background-color: #d3dce6;
}
@when active {
color: var(--color-primary);
}
} }
@b submenu { @b submenu {
& .el-menu { @e title {
height: auto; position: relative;
background-color: var(--submenu-item-fill); @extend menu-item;
}
& .el-menu-item:not(.el-submenu__title) {
padding-left: 46px;
background-color: transparent;
&:hover { &:hover {
background-color: var(--menu-item-hover-fill); background-color: #d3dce6;
} }
} }
@e title [class^="el-icon-"] { & .el-menu {
vertical-align: baseline; background-color: #e5e9f2;
margin-right: 10px; }
& .el-menu-item {
height: 50px;
line-height: 50px;
padding: 0 45px;
&:hover {
background-color: #d3dce6;
}
} }
@e icon-arrow { @e icon-arrow {
position: absolute; position: absolute;
margin: 0;
top: 50%; top: 50%;
right: 20px; right: 20px;
margin-top: -8px; margin-top: -7px;
transform: rotateZ(180deg);
font-size: 12px;
transition: transform .3s; transition: transform .3s;
font-size: 12px;
}
@when active {
.el-submenu__title {
border-bottom-color: var(--color-primary);
}
} }
@when opened { @when opened {
& .el-submenu__icon-arrow { .el-submenu__icon-arrow {
transform: rotateZ(0); transform: rotateZ(180deg);
} }
} }
[class^="el-icon-"] {
vertical-align: baseline;
margin-right: 10px;
}
} }
@b menu-item-group { @b menu-item-group {
@ -157,7 +207,7 @@
padding-top: 15px; padding-top: 15px;
line-height: normal; line-height: normal;
font-size: 14px; font-size: 14px;
padding-left: 25px; padding-left: 20px;
color: #99a9bf; color: #99a9bf;
} }
} }

View File

@ -21,7 +21,7 @@
vertical-align: middle; vertical-align: middle;
} }
& > * { & > * {
margin-right: 8px; margin-right: 3px;
} }
&:hover { &:hover {
background: #e5e9f2; background: #e5e9f2;