Bladeren bron

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

baiyaaaaa 8 jaren geleden
bovenliggende
commit
d9d673bcc3

+ 2 - 2
examples/docs/en-US/menu.md

@@ -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-3">item three</el-menu-item>
   </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>
 <div class="line"></div>
 <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-3">item three</el-menu-item>
   </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>
 
 <script>

+ 3 - 2
examples/docs/zh-CN/menu.md

@@ -53,6 +53,7 @@
 适用广泛的基础用法。
 
 ::: demo 导航菜单默认为垂直模式,通过 `mode` 属性可以使导航菜单变更为水平模式。另外,在菜单中通过 `submenu` 组件可以生成二级菜单。
+
 ```html
 <el-menu theme="dark" default-active="1" class="el-menu-demo" mode="horizontal" @select="handleSelect">
   <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-3">选项3</el-menu-item>
   </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>
 <div class="line"></div>
 <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-3">选项3</el-menu-item>
   </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>
 
 <script>

+ 11 - 9
packages/menu/src/menu-item.vue

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

+ 14 - 1
packages/menu/src/menu-mixin.js

@@ -13,7 +13,20 @@ module.exports = {
     },
     rootMenu() {
       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;
       }
       return parent;

+ 48 - 39
packages/menu/src/menu.vue

@@ -41,26 +41,37 @@
     },
     data() {
       return {
-        activeIndex: this.defaultActive,
+        activedIndex: this.defaultActive,
         openedMenus: this.defaultOpeneds ? this.defaultOpeneds.slice(0) : [],
-        menuItems: {},
+        items: {},
         submenus: {}
       };
     },
     watch: {
       defaultActive(value) {
-        this.activeIndex = value;
-        if (!this.menuItems[value]) return;
-        let menuItem = this.menuItems[value];
-        let indexPath = menuItem.indexPath;
+        const item = this.items[value];
+        if (!item) return;
 
-        this.handleSelect(value, indexPath, null, menuItem);
+        this.activedIndex = item.index;
+        this.initOpenedMenu();
       },
       defaultOpeneds(value) {
         this.openedMenus = value;
       }
     },
     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) {
         let openedMenus = this.openedMenus;
         if (openedMenus.indexOf(index) !== -1) return;
@@ -75,7 +86,8 @@
       closeMenu(index, indexPath) {
         this.openedMenus.splice(this.openedMenus.indexOf(index), 1);
       },
-      handleSubmenuClick(index, indexPath) {
+      handleSubmenuClick(submenu) {
+        const { index, indexPath } = submenu;
         let isOpened = this.openedMenus.indexOf(index) !== -1;
 
         if (isOpened) {
@@ -86,49 +98,46 @@
           this.$emit('open', index, indexPath);
         }
       },
-      handleSelect(index, indexPath, route, instance) {
-        this.activeIndex = index;
-        this.$emit('select', index, indexPath, instance);
+      handleItemClick(item) {
+        let { index, indexPath } = item;
+        this.activedIndex = item.index;
+        this.$emit('select', index, indexPath, item);
 
         if (this.mode === 'horizontal') {
-          this.broadcast('ElSubmenu', 'item-select', [index, indexPath]);
           this.openedMenus = [];
-        } else {
-          this.openActiveItemMenus();
         }
 
-        if (this.router && route) {
-          try {
-            this.$router.push(route);
-          } catch (e) {
-            console.error(e);
-          }
-        }
-      },
-      openActiveItemMenus() {
-        let index = this.activeIndex;
-        // 选中用户指定的路由对应的menu
         if (this.router) {
-          const userSpecifiedIndexs = Object
-                                       .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]);
+          this.routeToItem(item);
         }
-        if (!this.menuItems[index]) return;
-        if (index && this.mode === 'vertical') {
-          let indexPath = this.menuItems[index].indexPath;
+      },
+      // 初始化展开菜单
+      initOpenedMenu() {
+        const index = this.activedIndex;
+        const activeItem = this.items[index];
+        if (!activeItem || this.mode === 'horizontal') return;
 
-          // 展开该菜单项的路径上所有子菜单
-          indexPath.forEach(index => {
-            let submenu = this.submenus[index];
-            submenu && this.openMenu(index, submenu.indexPath);
-          });
+        let indexPath = activeItem.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() {
-      this.openActiveItemMenus();
+      this.initOpenedMenu();
+      this.$on('item-click', this.handleItemClick);
+      this.$on('submenu-click', this.handleSubmenuClick);
     }
   };
 </script>

+ 46 - 8
packages/menu/src/submenu.vue

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

+ 5 - 0
packages/theme-default/src/menu.css

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

+ 148 - 139
test/unit/specs/menu.spec.js

@@ -27,133 +27,165 @@ describe('Menu', () => {
       });
     });
   });
-  it('default active', done => {
-    vm = createVue({
-      template: `
-        <el-menu default-active="2">
-          <el-menu-item index="1" ref="item1">处理中心</el-menu-item>
-          <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;
-    vm.$refs.item1.$el.click();
-    vm.$nextTick(_ => {
-      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';
+  describe('default active', () => {
+    it('normal active', done => {
+      vm = createVue({
+        template: `
+          <el-menu default-active="2">
+            <el-menu-item index="1" ref="item1">处理中心</el-menu-item>
+            <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;
+      vm.$refs.item1.$el.click();
       vm.$nextTick(_ => {
         expect(vm.$refs.item1.$el.classList.contains('is-active')).to.be.true;
         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('submenu', done => {
-    vm = createVue({
-      template: `
-        <el-menu>
-          <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>
-      `,
-      data() {
-        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();
+    it('dynamic active', 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(_ => {
-          expect(submenu.$el.classList.contains('is-opened')).to.not.true;
+          expect(vm.$refs.item1.$el.classList.contains('is-active')).to.be.true;
           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 => {
-    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.submenu2.$el.classList.contains('is-opened')).to.be.true;
-    vm.defaultOpeneds = ['2'];
-    vm.$nextTick(_ => {
+  describe('submenu', () => {
+    it('toggle', done => {
+      vm = createVue({
+        template: `
+          <el-menu>
+            <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>
+        `,
+        data() {
+          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(_ => {
+            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.submenu2.$el.classList.contains('is-opened')).to.not.true;
-      done();
+      expect(vm.$refs.submenu2.$el.classList.contains('is-opened')).to.be.true;
+      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', () => {
@@ -195,10 +227,9 @@ describe('Menu', () => {
         };
       }
     }, true);
-    vm.$refs.submenu2Item2.$el.click();
+    vm.$refs.submenu2.$el.querySelector('.el-submenu__title').click();
     vm.$nextTick(_ => {
       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();
     });
   });
@@ -268,28 +299,6 @@ describe('Menu', () => {
       }, 1000);
     }, 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 => {
     vm = createVue({
       template: `