Commit 4bce7645256764643e8f82b02940f2666fd6d7c7

Authored by zhigang.li
1 parent 3537176f

make menu support more than 2 levels

examples/routers/menu.vue
1 1 <template>
2 2 <div>
3   - <Menu mode="horizontal" :theme="theme1" active-name="1">
  3 + <Menu :theme="theme1" active-name="1" @on-select="handleSelect" @on-open-change="handleOpen" :open-names="openArr">
4 4 <Menu-item name="1">
5 5 <Icon type="ios-paper"></Icon>
6   - 内容管理
  6 + 一级1
7 7 </Menu-item>
8 8 <Menu-item name="2">
9 9 <Icon type="ios-people"></Icon>
10   - 用户管理
  10 + 一级2
11 11 </Menu-item>
12 12 <Submenu name="3">
13 13 <template slot="title">
14   - <Icon type="stats-bars"></Icon>
15   - 统计分析
  14 + <Icon type="ios-people"></Icon>
  15 + 一级3
16 16 </template>
17   - <Menu-group title="使用">
18   - <Menu-item name="3-1">新增和启动</Menu-item>
19   - <Menu-item name="3-2">活跃分析</Menu-item>
20   - <Menu-item name="3-3">时段分析</Menu-item>
21   - </Menu-group>
22   - <Menu-group title="留存">
23   - <Menu-item name="3-4">用户留存</Menu-item>
24   - <Menu-item name="3-5">流失用户</Menu-item>
25   - </Menu-group>
  17 + <Menu-item name="3-1">二级1</Menu-item>
  18 + <Menu-item name="3-2">二级2</Menu-item>
  19 + <Submenu name="3-3">
  20 + <template slot="title">
  21 + <Icon type="stats-bars"></Icon>
  22 + 二级3
  23 + </template>
  24 + <Menu-item name="3-3-1">三级1</Menu-item>
  25 + <Menu-item name="3-3-2">三级2</Menu-item>
  26 + <Submenu name="3-3-3">
  27 + <template slot="title">
  28 + <Icon type="stats-bars"></Icon>
  29 + 三级3
  30 + </template>
  31 + <Menu-item name="3-3-3-1">四级1</Menu-item>
  32 + <Menu-item name="3-3-3-2">四级2</Menu-item>
  33 + </Submenu>
  34 + </Submenu>
26 35 </Submenu>
27 36 <Menu-item name="4">
28 37 <Icon type="settings"></Icon>
29   - 综合设置
  38 + 一级4
30 39 </Menu-item>
31 40 </Menu>
32 41 <br>
... ... @@ -43,9 +52,20 @@
43 52 export default {
44 53 data () {
45 54 return {
46   - theme1: 'light',
47   - value4: ''
  55 + theme1: 'dark',
  56 + value4: '',
  57 + openArr: ['3', '3-3', '3-3-3']
  58 + };
  59 + },
  60 + methods: {
  61 + handleSelect (name) {
  62 + // console.log(name);
  63 + return name;
  64 + },
  65 + handleOpen (openArr) {
  66 + // console.log(openArr);
  67 + return openArr;
48 68 }
49 69 }
50   - }
  70 + };
51 71 </script>
... ...
src/components/cascader/cascader.vue
... ... @@ -220,7 +220,7 @@
220 220 }
221 221 getSelections(this.data);
222 222 selections = selections.filter(item => {
223   - return item.label ? item.label.indexOf(this.query) > -1 : false
  223 + return item.label ? item.label.indexOf(this.query) > -1 : false;
224 224 }).map(item => {
225 225 item.display = item.display.replace(new RegExp(this.query, 'g'), `<span>${this.query}</span>`);
226 226 return item;
... ...
src/components/menu/menu-item.vue
1 1 <template>
2   - <li :class="classes" @click.stop="handleClick"><slot></slot></li>
  2 + <li :class="classes" @click.stop="handleClick" :style="itemStyle"><slot></slot></li>
3 3 </template>
4 4 <script>
5 5 import Emitter from '../../mixins/emitter';
  6 + import { findComponentUpward, findComponentsUpward } from '../../utils/assist';
6 7 const prefixCls = 'ivu-menu';
7 8  
8 9 export default {
... ... @@ -33,18 +34,24 @@
33 34 [`${prefixCls}-item-disabled`]: this.disabled
34 35 }
35 36 ];
  37 + },
  38 + parentSubmenuNum () {
  39 + return findComponentsUpward(this, 'Submenu').length;
  40 + },
  41 + itemStyle () {
  42 + return this.hasParentSubmenu ? {
  43 + paddingLeft: 43 + (this.parentSubmenuNum - 1) * 24 + 'px'
  44 + } : {};
  45 + },
  46 + hasParentSubmenu () {
  47 + return findComponentUpward(this, 'Submenu');
36 48 }
37 49 },
38 50 methods: {
39 51 handleClick () {
40 52 if (this.disabled) return;
41 53  
42   - let parent = this.$parent;
43   - let name = parent.$options.name;
44   - while (parent && (!name || name !== 'Submenu')) {
45   - parent = parent.$parent;
46   - if (parent) name = parent.$options.name;
47   - }
  54 + let parent = findComponentUpward(this, 'Submenu');
48 55  
49 56 if (parent) {
50 57 this.dispatch('Submenu', 'on-menu-item-select', this.name);
... ... @@ -57,7 +64,7 @@
57 64 this.$on('on-update-active-name', (name) => {
58 65 if (this.name === name) {
59 66 this.active = true;
60   - this.dispatch('Submenu', 'on-update-active-name', true);
  67 + this.dispatch('Submenu', 'on-update-active-name', name);
61 68 } else {
62 69 this.active = false;
63 70 }
... ...
src/components/menu/submenu.vue
1 1 <template>
2 2 <li :class="classes" @mouseenter="handleMouseenter" @mouseleave="handleMouseleave">
3   - <div :class="[prefixCls + '-submenu-title']" ref="reference" @click="handleClick">
  3 + <div :class="[prefixCls + '-submenu-title']" ref="reference" @click="handleClick" :style="titleStyle">
4 4 <slot name="title"></slot>
5 5 <Icon type="ios-arrow-down" :class="[prefixCls + '-submenu-title-icon']"></Icon>
6 6 </div>
... ... @@ -21,7 +21,7 @@
21 21 import Drop from '../select/dropdown.vue';
22 22 import Icon from '../icon/icon.vue';
23 23 import CollapseTransition from '../base/collapse-transition';
24   - import { getStyle, findComponentUpward } from '../../utils/assist';
  24 + import { getStyle, findComponentUpward, findComponentsUpward, findComponentsDownward } from '../../utils/assist';
25 25 import Emitter from '../../mixins/emitter';
26 26  
27 27 const prefixCls = 'ivu-menu';
... ... @@ -54,9 +54,11 @@
54 54 return [
55 55 `${prefixCls}-submenu`,
56 56 {
57   - [`${prefixCls}-item-active`]: this.active,
  57 + [`${prefixCls}-item-active`]: this.active && !this.hasParentSubmenu,
58 58 [`${prefixCls}-opened`]: this.opened,
59   - [`${prefixCls}-submenu-disabled`]: this.disabled
  59 + [`${prefixCls}-submenu-disabled`]: this.disabled,
  60 + [`${prefixCls}-submenu-has-parent-submenu`]: this.hasParentSubmenu,
  61 + [`${prefixCls}-child-item-active`]: this.active
60 62 }
61 63 ];
62 64 },
... ... @@ -71,6 +73,17 @@
71 73  
72 74 if (this.dropWidth) style.minWidth = `${this.dropWidth}px`;
73 75 return style;
  76 + },
  77 + hasParentSubmenu () {
  78 + return findComponentUpward(this, 'Submenu');
  79 + },
  80 + parentSubmenuNum () {
  81 + return findComponentsUpward(this, 'Submenu').length;
  82 + },
  83 + titleStyle () {
  84 + return this.hasParentSubmenu ? {
  85 + paddingLeft: 43 + (this.parentSubmenuNum - 1) * 24 + 'px'
  86 + } : {};
74 87 }
75 88 },
76 89 methods: {
... ... @@ -131,6 +144,10 @@
131 144 return true;
132 145 });
133 146 this.$on('on-update-active-name', (status) => {
  147 + if (findComponentUpward(this, 'Submenu')) this.dispatch('Submenu', 'on-update-active-name', status);
  148 + if (findComponentsDownward(this, 'Submenu')) findComponentsDownward(this, 'Submenu').forEach(item => {
  149 + item.active = false;
  150 + });
134 151 this.active = status;
135 152 });
136 153 }
... ...
src/locale/lang/hi-IN.js
... ... @@ -48,18 +48,18 @@ const lang = {
48 48 sat: 'शनिवार'
49 49 },
50 50 months: {
51   - m1: 'जनवरी',
52   - m2: 'फरवरी',
53   - m3: 'मार्च',
54   - m4: 'अप्रैल',
55   - m5: 'मई',
56   - m6: 'जून',
57   - m7: 'जुलाई',
58   - m8: 'अगस्त',
59   - m9: 'सितंबर',
60   - m10: 'अक्टूबर',
61   - m11: 'नवंबर',
62   - m12: 'दिसंबर'
  51 + m1: 'जनवरी',
  52 + m2: 'फरवरी',
  53 + m3: 'मार्च',
  54 + m4: 'अप्रैल',
  55 + m5: 'मई',
  56 + m6: 'जून',
  57 + m7: 'जुलाई',
  58 + m8: 'अगस्त',
  59 + m9: 'सितंबर',
  60 + m10: 'अक्टूबर',
  61 + m11: 'नवंबर',
  62 + m12: 'दिसंबर'
63 63 }
64 64 },
65 65 transfer: {
... ...
src/styles/components/menu.less
... ... @@ -160,13 +160,18 @@
160 160 &-submenu-title-icon {
161 161 transition: transform @transition-time @ease-in-out;
162 162 }
163   - &-opened &-submenu-title-icon{
  163 + &-opened > * > &-submenu-title-icon{
164 164 transform: rotate(180deg);
165 165 }
166 166  
167   - &-vertical &-submenu &-item{
168   - padding-left: 43px;
169   - }
  167 + &-vertical &-submenu{
  168 + &-nested{
  169 + padding-left: 20px;
  170 + }
  171 + .@{menu-prefix-cls}-item{
  172 + padding-left: 43px;
  173 + }
  174 + }
170 175 &-vertical &-item-group{
171 176 &-title{
172 177 height: 48px;
... ... @@ -217,7 +222,10 @@
217 222 background: @primary-color !important;
218 223 }
219 224 }
220   - &-dark&-vertical &-item-active &-submenu-title{
  225 + // &-dark&-vertical &-item-active &-submenu-title{
  226 + // color: #fff;
  227 + // }
  228 + &-dark&-vertical &-child-item-active > &-submenu-title{
221 229 color: #fff;
222 230 }
223 231  
... ... @@ -226,6 +234,12 @@
226 234 .@{menu-prefix-cls}-submenu-title{
227 235 background: @menu-dark-title;
228 236 }
  237 +
  238 + .@{menu-prefix-cls}-submenu-has-parent-submenu{
  239 + .@{menu-prefix-cls}-submenu-title{
  240 + background: transparent;
  241 + }
  242 + }
229 243 }
230 244 }
231 245 .select-item(@menu-prefix-cls, @menu-dropdown-item-prefix-cls);
... ...
src/utils/assist.js
... ... @@ -217,6 +217,17 @@ export function findComponentsDownward (context, componentName) {
217 217 }, []);
218 218 }
219 219  
  220 +// Find components upward
  221 +export function findComponentsUpward (context, componentName) {
  222 + let parents = [];
  223 + if (context.$parent) {
  224 + if (context.$parent.$options.name === componentName) parents.push(context.$parent);
  225 + return parents.concat(findComponentsUpward(context.$parent, componentName));
  226 + } else {
  227 + return [];
  228 + }
  229 +}
  230 +
220 231 /* istanbul ignore next */
221 232 const trim = function(string) {
222 233 return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '');
... ...