Ver Fonte

menu fixed

baiyaaaaa há 9 anos atrás
pai
commit
6a558dbe48

+ 38 - 20
examples/docs/menu.md

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

+ 1 - 6
packages/menu/src/menu-item-group.vue

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

+ 19 - 14
packages/menu/src/menu-item.vue

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

+ 16 - 18
packages/menu/src/menu.vue

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

+ 88 - 22
packages/menu/src/submenu.vue

@@ -16,25 +16,62 @@
     },
     data() {
       return {
-        opened: false
+        opened: false,
+        timeout: null,
+        active: false
       };
     },
     computed: {
       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;
+      },
+      rootMenu() {
+        var parent = this.$parent;
+        while (parent.$options._componentTag !== 'el-menu') {
+          parent = parent.$parent;
+        }
+        return parent;
       },
-      activeIndex() {
-        return this.$parent.activeIndex;
+      mode() {
+        return this.rootMenu.mode;
       }
     },
     methods: {
       handleClick() {
-        if (!this.opened) {
-          this.dispatch('menu', 'expand-menu', [this.index, this.indexPath]);
-          this.opened = true;
-        } else {
-          this.dispatch('menu', 'collapse-menu', [this.index, this.indexPath]);
-          this.opened = false;
+        if (this.mode === 'vertical') {
+          if (!this.opened) {
+            this.dispatch('menu', 'expand-menu', [this.index, this.indexPath]);
+            this.opened = true;
+          } else {
+            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.$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>
-
-<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>

+ 132 - 82
packages/theme-default/src/menu.css

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