Commit 4bce7645256764643e8f82b02940f2666fd6d7c7

Authored by zhigang.li
1 parent 3537176f

make menu support more than 2 levels

examples/routers/menu.vue
1 <template> 1 <template>
2 <div> 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 <Menu-item name="1"> 4 <Menu-item name="1">
5 <Icon type="ios-paper"></Icon> 5 <Icon type="ios-paper"></Icon>
6 - 内容管理 6 + 一级1
7 </Menu-item> 7 </Menu-item>
8 <Menu-item name="2"> 8 <Menu-item name="2">
9 <Icon type="ios-people"></Icon> 9 <Icon type="ios-people"></Icon>
10 - 用户管理 10 + 一级2
11 </Menu-item> 11 </Menu-item>
12 <Submenu name="3"> 12 <Submenu name="3">
13 <template slot="title"> 13 <template slot="title">
14 - <Icon type="stats-bars"></Icon>  
15 - 统计分析 14 + <Icon type="ios-people"></Icon>
  15 + 一级3
16 </template> 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 </Submenu> 35 </Submenu>
27 <Menu-item name="4"> 36 <Menu-item name="4">
28 <Icon type="settings"></Icon> 37 <Icon type="settings"></Icon>
29 - 综合设置 38 + 一级4
30 </Menu-item> 39 </Menu-item>
31 </Menu> 40 </Menu>
32 <br> 41 <br>
@@ -43,9 +52,20 @@ @@ -43,9 +52,20 @@
43 export default { 52 export default {
44 data () { 53 data () {
45 return { 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 </script> 71 </script>
src/components/cascader/cascader.vue
@@ -220,7 +220,7 @@ @@ -220,7 +220,7 @@
220 } 220 }
221 getSelections(this.data); 221 getSelections(this.data);
222 selections = selections.filter(item => { 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 }).map(item => { 224 }).map(item => {
225 item.display = item.display.replace(new RegExp(this.query, 'g'), `<span>${this.query}</span>`); 225 item.display = item.display.replace(new RegExp(this.query, 'g'), `<span>${this.query}</span>`);
226 return item; 226 return item;
src/components/menu/menu-item.vue
1 <template> 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 </template> 3 </template>
4 <script> 4 <script>
5 import Emitter from '../../mixins/emitter'; 5 import Emitter from '../../mixins/emitter';
  6 + import { findComponentUpward, findComponentsUpward } from '../../utils/assist';
6 const prefixCls = 'ivu-menu'; 7 const prefixCls = 'ivu-menu';
7 8
8 export default { 9 export default {
@@ -33,18 +34,24 @@ @@ -33,18 +34,24 @@
33 [`${prefixCls}-item-disabled`]: this.disabled 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 methods: { 50 methods: {
39 handleClick () { 51 handleClick () {
40 if (this.disabled) return; 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 if (parent) { 56 if (parent) {
50 this.dispatch('Submenu', 'on-menu-item-select', this.name); 57 this.dispatch('Submenu', 'on-menu-item-select', this.name);
@@ -57,7 +64,7 @@ @@ -57,7 +64,7 @@
57 this.$on('on-update-active-name', (name) => { 64 this.$on('on-update-active-name', (name) => {
58 if (this.name === name) { 65 if (this.name === name) {
59 this.active = true; 66 this.active = true;
60 - this.dispatch('Submenu', 'on-update-active-name', true); 67 + this.dispatch('Submenu', 'on-update-active-name', name);
61 } else { 68 } else {
62 this.active = false; 69 this.active = false;
63 } 70 }
src/components/menu/submenu.vue
1 <template> 1 <template>
2 <li :class="classes" @mouseenter="handleMouseenter" @mouseleave="handleMouseleave"> 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 <slot name="title"></slot> 4 <slot name="title"></slot>
5 <Icon type="ios-arrow-down" :class="[prefixCls + '-submenu-title-icon']"></Icon> 5 <Icon type="ios-arrow-down" :class="[prefixCls + '-submenu-title-icon']"></Icon>
6 </div> 6 </div>
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 import Drop from '../select/dropdown.vue'; 21 import Drop from '../select/dropdown.vue';
22 import Icon from '../icon/icon.vue'; 22 import Icon from '../icon/icon.vue';
23 import CollapseTransition from '../base/collapse-transition'; 23 import CollapseTransition from '../base/collapse-transition';
24 - import { getStyle, findComponentUpward } from '../../utils/assist'; 24 + import { getStyle, findComponentUpward, findComponentsUpward, findComponentsDownward } from '../../utils/assist';
25 import Emitter from '../../mixins/emitter'; 25 import Emitter from '../../mixins/emitter';
26 26
27 const prefixCls = 'ivu-menu'; 27 const prefixCls = 'ivu-menu';
@@ -54,9 +54,11 @@ @@ -54,9 +54,11 @@
54 return [ 54 return [
55 `${prefixCls}-submenu`, 55 `${prefixCls}-submenu`,
56 { 56 {
57 - [`${prefixCls}-item-active`]: this.active, 57 + [`${prefixCls}-item-active`]: this.active && !this.hasParentSubmenu,
58 [`${prefixCls}-opened`]: this.opened, 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,6 +73,17 @@
71 73
72 if (this.dropWidth) style.minWidth = `${this.dropWidth}px`; 74 if (this.dropWidth) style.minWidth = `${this.dropWidth}px`;
73 return style; 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 methods: { 89 methods: {
@@ -131,6 +144,10 @@ @@ -131,6 +144,10 @@
131 return true; 144 return true;
132 }); 145 });
133 this.$on('on-update-active-name', (status) => { 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 this.active = status; 151 this.active = status;
135 }); 152 });
136 } 153 }
src/locale/lang/hi-IN.js
@@ -48,18 +48,18 @@ const lang = { @@ -48,18 +48,18 @@ const lang = {
48 sat: 'शनिवार' 48 sat: 'शनिवार'
49 }, 49 },
50 months: { 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 transfer: { 65 transfer: {
src/styles/components/menu.less
@@ -160,13 +160,18 @@ @@ -160,13 +160,18 @@
160 &-submenu-title-icon { 160 &-submenu-title-icon {
161 transition: transform @transition-time @ease-in-out; 161 transition: transform @transition-time @ease-in-out;
162 } 162 }
163 - &-opened &-submenu-title-icon{ 163 + &-opened > * > &-submenu-title-icon{
164 transform: rotate(180deg); 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 &-vertical &-item-group{ 175 &-vertical &-item-group{
171 &-title{ 176 &-title{
172 height: 48px; 177 height: 48px;
@@ -217,7 +222,10 @@ @@ -217,7 +222,10 @@
217 background: @primary-color !important; 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 color: #fff; 229 color: #fff;
222 } 230 }
223 231
@@ -226,6 +234,12 @@ @@ -226,6 +234,12 @@
226 .@{menu-prefix-cls}-submenu-title{ 234 .@{menu-prefix-cls}-submenu-title{
227 background: @menu-dark-title; 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 .select-item(@menu-prefix-cls, @menu-dropdown-item-prefix-cls); 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,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 /* istanbul ignore next */ 231 /* istanbul ignore next */
221 const trim = function(string) { 232 const trim = function(string) {
222 return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, ''); 233 return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '');