Commit b9851cf0e930d1b1ad3858aad8041d799835b08e

Authored by chenhaodong
1 parent c39c688c

mew-menu

src/components/mew-menu-group/index.js 0 → 100644
  1 +import MenuGroup from '../mew-menu/menu-group.vue';
  2 +
  3 +export default MenuGroup;
0 4 \ No newline at end of file
... ...
src/components/mew-menu-item/index.js 0 → 100644
  1 +import MenuItem from '../mew-menu/menu-item.vue';
  2 +
  3 +export default MenuItem;
0 4 \ No newline at end of file
... ...
src/components/mew-menu/index.js 0 → 100644
  1 +import Menu from './menu.vue';
  2 +import MenuGroup from './menu-group.vue';
  3 +import MenuItem from './menu-item.vue';
  4 +import Submenu from './submenu.vue';
  5 +
  6 +Menu.Group = MenuGroup;
  7 +Menu.Item = MenuItem;
  8 +Menu.Sub = Submenu;
  9 +
  10 +export default Menu;
0 11 \ No newline at end of file
... ...
src/components/mew-menu/menu-group.vue 0 → 100644
  1 +<template>
  2 + <li :class="[prefixCls + '-item-group']">
  3 + <div :class="[prefixCls + '-item-group-title']" :style="groupStyle">{{ title }}</div>
  4 + <ul><slot></slot></ul>
  5 + </li>
  6 +</template>
  7 +<script>
  8 + import mixin from './mixin';
  9 + const prefixCls = 'ivu-menu';
  10 +
  11 + export default {
  12 + name: 'MenuGroup',
  13 + mixins: [ mixin ],
  14 + props: {
  15 + title: {
  16 + type: String,
  17 + default: ''
  18 + }
  19 + },
  20 + data () {
  21 + return {
  22 + prefixCls: prefixCls
  23 + };
  24 + },
  25 + computed: {
  26 + groupStyle () {
  27 + return this.hasParentSubmenu && this.mode !== 'horizontal' ? {
  28 + paddingLeft: 43 + (this.parentSubmenuNum - 1) * 28 + 'px'
  29 + } : {};
  30 + }
  31 + }
  32 + };
  33 +</script>
... ...
src/components/mew-menu/menu-item.vue 0 → 100644
  1 +<template>
  2 + <a
  3 + v-if="to"
  4 + :href="linkUrl"
  5 + :target="target"
  6 + :class="classes"
  7 + @click.exact="handleClickItem($event, false)"
  8 + @click.ctrl="handleClickItem($event, true)"
  9 + @click.meta="handleClickItem($event, true)"
  10 + :style="itemStyle"><slot></slot></a>
  11 + <li v-else :class="classes" @click.stop="handleClickItem" :style="itemStyle"><slot></slot></li>
  12 +</template>
  13 +<script>
  14 + import Emitter from '../../mixins/emitter';
  15 + import { findComponentUpward } from '../../utils/assist';
  16 + import mixin from './mixin';
  17 + import mixinsLink from '../../mixins/link';
  18 +
  19 + const prefixCls = 'ivu-menu';
  20 +
  21 + export default {
  22 + name: 'MenuItem',
  23 + mixins: [ Emitter, mixin, mixinsLink ],
  24 + props: {
  25 + name: {
  26 + type: [String, Number],
  27 + required: true
  28 + },
  29 + disabled: {
  30 + type: Boolean,
  31 + default: false
  32 + },
  33 + },
  34 + data () {
  35 + return {
  36 + active: false
  37 + };
  38 + },
  39 + computed: {
  40 + classes () {
  41 + return [
  42 + `${prefixCls}-item`,
  43 + {
  44 + [`${prefixCls}-item-active`]: this.active,
  45 + [`${prefixCls}-item-selected`]: this.active,
  46 + [`${prefixCls}-item-disabled`]: this.disabled
  47 + }
  48 + ];
  49 + },
  50 + itemStyle () {
  51 + return this.hasParentSubmenu && this.mode !== 'horizontal' ? {
  52 + paddingLeft: 43 + (this.parentSubmenuNum - 1) * 24 + 'px'
  53 + } : {};
  54 + }
  55 + },
  56 + methods: {
  57 + handleClickItem (event, new_window = false) {
  58 + if (this.disabled) return;
  59 +
  60 + if (new_window || this.target === '_blank') {
  61 + // 如果是 new_window,直接新开窗口就行,无需发送状态
  62 + this.handleCheckClick(event, new_window);
  63 + let parentMenu = findComponentUpward(this, 'Menu');
  64 + if (parentMenu) parentMenu.handleEmitSelectEvent(this.name);
  65 + } else {
  66 + let parent = findComponentUpward(this, 'Submenu');
  67 +
  68 + if (parent) {
  69 + this.dispatch('Submenu', 'on-menu-item-select', this.name);
  70 + } else {
  71 + this.dispatch('Menu', 'on-menu-item-select', this.name);
  72 + }
  73 +
  74 + this.handleCheckClick(event, new_window);
  75 + }
  76 + }
  77 + },
  78 + mounted () {
  79 + this.$on('on-update-active-name', (name) => {
  80 + if (this.name === name) {
  81 + this.active = true;
  82 + this.dispatch('Submenu', 'on-update-active-name', name);
  83 + } else {
  84 + this.active = false;
  85 + }
  86 + });
  87 + }
  88 + };
  89 +</script>
... ...
src/components/mew-menu/menu.vue 0 → 100644
  1 +<template>
  2 + <ul :class="classes" :style="styles"><slot></slot></ul>
  3 +</template>
  4 +<script>
  5 + import { oneOf, findComponentsDownward, findComponentsUpward } from '../../utils/assist';
  6 + import Emitter from '../../mixins/emitter';
  7 +
  8 + const prefixCls = 'ivu-menu';
  9 +
  10 + export default {
  11 + name: 'Menu',
  12 + mixins: [ Emitter ],
  13 + props: {
  14 + mode: {
  15 + validator (value) {
  16 + return oneOf(value, ['horizontal', 'vertical']);
  17 + },
  18 + default: 'vertical'
  19 + },
  20 + theme: {
  21 + validator (value) {
  22 + return oneOf(value, ['light', 'dark', 'primary']);
  23 + },
  24 + default: 'light'
  25 + },
  26 + activeName: {
  27 + type: [String, Number]
  28 + },
  29 + openNames: {
  30 + type: Array,
  31 + default () {
  32 + return [];
  33 + }
  34 + },
  35 + accordion: {
  36 + type: Boolean,
  37 + default: false
  38 + },
  39 + width: {
  40 + type: String,
  41 + default: '240px'
  42 + }
  43 + },
  44 + data () {
  45 + return {
  46 + currentActiveName: this.activeName,
  47 + openedNames: []
  48 + };
  49 + },
  50 + computed: {
  51 + classes () {
  52 + let theme = this.theme;
  53 + if (this.mode === 'vertical' && this.theme === 'primary') theme = 'light';
  54 +
  55 + return [
  56 + `${prefixCls}`,
  57 + `${prefixCls}-${theme}`,
  58 + {
  59 + [`${prefixCls}-${this.mode}`]: this.mode
  60 + }
  61 + ];
  62 + },
  63 + styles () {
  64 + let style = {};
  65 +
  66 + if (this.mode === 'vertical') style.width = this.width;
  67 +
  68 + return style;
  69 + }
  70 + },
  71 + methods: {
  72 + updateActiveName () {
  73 + if (this.currentActiveName === undefined) {
  74 + this.currentActiveName = -1;
  75 + }
  76 + this.broadcast('Submenu', 'on-update-active-name', false);
  77 + this.broadcast('MenuItem', 'on-update-active-name', this.currentActiveName);
  78 + },
  79 + updateOpenKeys (name) {
  80 + let names = [...this.openedNames];
  81 + const index = names.indexOf(name);
  82 + if (this.accordion) findComponentsDownward(this, 'Submenu').forEach(item => {
  83 + item.opened = false;
  84 + });
  85 + if (index >= 0) {
  86 + let currentSubmenu = null;
  87 + findComponentsDownward(this, 'Submenu').forEach(item => {
  88 + if (item.name === name) {
  89 + currentSubmenu = item;
  90 + item.opened = false;
  91 + }
  92 + });
  93 + findComponentsUpward(currentSubmenu, 'Submenu').forEach(item => {
  94 + item.opened = true;
  95 + });
  96 + findComponentsDownward(currentSubmenu, 'Submenu').forEach(item => {
  97 + item.opened = false;
  98 + });
  99 + } else {
  100 + if (this.accordion) {
  101 + let currentSubmenu = null;
  102 + findComponentsDownward(this, 'Submenu').forEach(item => {
  103 + if (item.name === name) {
  104 + currentSubmenu = item;
  105 + item.opened = true;
  106 + }
  107 + });
  108 + findComponentsUpward(currentSubmenu, 'Submenu').forEach(item => {
  109 + item.opened = true;
  110 + });
  111 + } else {
  112 + findComponentsDownward(this, 'Submenu').forEach(item => {
  113 + if (item.name === name) item.opened = true;
  114 + });
  115 + }
  116 + }
  117 + let openedNames = findComponentsDownward(this, 'Submenu').filter(item => item.opened).map(item => item.name);
  118 + this.openedNames = [...openedNames];
  119 + this.$emit('on-open-change', openedNames);
  120 + },
  121 + updateOpened () {
  122 + const items = findComponentsDownward(this, 'Submenu');
  123 +
  124 + if (items.length) {
  125 + items.forEach(item => {
  126 + if (this.openedNames.indexOf(item.name) > -1) item.opened = true;
  127 + else item.opened = false;
  128 + });
  129 + }
  130 + },
  131 + handleEmitSelectEvent (name) {
  132 + this.$emit('on-select', name);
  133 + }
  134 + },
  135 + mounted () {
  136 + this.openedNames = [...this.openNames];
  137 + this.updateOpened();
  138 + this.$nextTick(() => this.updateActiveName());
  139 + this.$on('on-menu-item-select', (name) => {
  140 + this.currentActiveName = name;
  141 + this.$emit('on-select', name);
  142 + });
  143 + },
  144 + watch: {
  145 + openNames (names) {
  146 + this.openedNames = names;
  147 + },
  148 + activeName (val) {
  149 + this.currentActiveName = val;
  150 + },
  151 + currentActiveName () {
  152 + this.updateActiveName();
  153 + }
  154 + }
  155 + };
  156 +</script>
... ...
src/components/mew-menu/mixin.js 0 → 100644
  1 +import { findComponentUpward, findComponentsUpward } from '../../utils/assist';
  2 +export default {
  3 + data () {
  4 + return {
  5 + menu: findComponentUpward(this, 'Menu')
  6 + };
  7 + },
  8 + computed: {
  9 + hasParentSubmenu () {
  10 + return !!findComponentUpward(this, 'Submenu');
  11 + },
  12 + parentSubmenuNum () {
  13 + return findComponentsUpward(this, 'Submenu').length;
  14 + },
  15 + mode () {
  16 + return this.menu.mode;
  17 + }
  18 + }
  19 +};
... ...
src/components/mew-menu/submenu.vue 0 → 100644
  1 +<template>
  2 + <li :class="classes" @mouseenter="handleMouseenter" @mouseleave="handleMouseleave">
  3 + <div :class="[prefixCls + '-submenu-title']" ref="reference" @click.stop="handleClick" :style="titleStyle">
  4 + <slot name="title"></slot>
  5 + <Icon :type="arrowType" :custom="customArrowType" :size="arrowSize" :class="[prefixCls + '-submenu-title-icon']" />
  6 + </div>
  7 + <collapse-transition v-if="mode === 'vertical'">
  8 + <ul :class="[prefixCls]" v-show="opened"><slot></slot></ul>
  9 + </collapse-transition>
  10 + <transition name="slide-up" v-else>
  11 + <Drop
  12 + v-show="opened"
  13 + placement="bottom"
  14 + ref="drop"
  15 + :style="dropStyle"><ul :class="[prefixCls + '-drop-list']"><slot></slot></ul>
  16 + </Drop>
  17 + </transition>
  18 + </li>
  19 +</template>
  20 +<script>
  21 + import Drop from '../select/dropdown.vue';
  22 + import Icon from '../icon/icon.vue';
  23 + import CollapseTransition from '../base/collapse-transition';
  24 + import { getStyle, findComponentUpward, findComponentsDownward } from '../../utils/assist';
  25 + import Emitter from '../../mixins/emitter';
  26 + import mixin from './mixin';
  27 +
  28 + const prefixCls = 'ivu-menu';
  29 +
  30 + export default {
  31 + name: 'Submenu',
  32 + mixins: [ Emitter, mixin ],
  33 + components: { Icon, Drop, CollapseTransition },
  34 + props: {
  35 + name: {
  36 + type: [String, Number],
  37 + required: true
  38 + },
  39 + disabled: {
  40 + type: Boolean,
  41 + default: false
  42 + }
  43 + },
  44 + data () {
  45 + return {
  46 + prefixCls: prefixCls,
  47 + active: false,
  48 + opened: false,
  49 + dropWidth: parseFloat(getStyle(this.$el, 'width'))
  50 + };
  51 + },
  52 + computed: {
  53 + classes () {
  54 + return [
  55 + `${prefixCls}-submenu`,
  56 + {
  57 + [`${prefixCls}-item-active`]: this.active && !this.hasParentSubmenu,
  58 + [`${prefixCls}-opened`]: this.opened,
  59 + [`${prefixCls}-submenu-disabled`]: this.disabled,
  60 + [`${prefixCls}-submenu-has-parent-submenu`]: this.hasParentSubmenu,
  61 + [`${prefixCls}-child-item-active`]: this.active
  62 + }
  63 + ];
  64 + },
  65 + accordion () {
  66 + return this.menu.accordion;
  67 + },
  68 + dropStyle () {
  69 + let style = {};
  70 +
  71 + if (this.dropWidth) style.minWidth = `${this.dropWidth}px`;
  72 + return style;
  73 + },
  74 + titleStyle () {
  75 + return this.hasParentSubmenu && this.mode !== 'horizontal' ? {
  76 + paddingLeft: 43 + (this.parentSubmenuNum - 1) * 24 + 'px'
  77 + } : {};
  78 + },
  79 + // 3.4.0, global setting customArrow 有值时,arrow 赋值空
  80 + arrowType () {
  81 + let type = 'ios-arrow-down';
  82 +
  83 + if (this.$IVIEW) {
  84 + if (this.$IVIEW.menu.customArrow) {
  85 + type = '';
  86 + } else if (this.$IVIEW.menu.arrow) {
  87 + type = this.$IVIEW.menu.arrow;
  88 + }
  89 + }
  90 + return type;
  91 + },
  92 + // 3.4.0, global setting
  93 + customArrowType () {
  94 + let type = '';
  95 +
  96 + if (this.$IVIEW) {
  97 + if (this.$IVIEW.menu.customArrow) {
  98 + type = this.$IVIEW.menu.customArrow;
  99 + }
  100 + }
  101 + return type;
  102 + },
  103 + // 3.4.0, global setting
  104 + arrowSize () {
  105 + let size = '';
  106 +
  107 + if (this.$IVIEW) {
  108 + if (this.$IVIEW.menu.arrowSize) {
  109 + size = this.$IVIEW.menu.arrowSize;
  110 + }
  111 + }
  112 + return size;
  113 + }
  114 + },
  115 + methods: {
  116 + handleMouseenter () {
  117 + if (this.disabled) return;
  118 + if (this.mode === 'vertical') return;
  119 +
  120 + clearTimeout(this.timeout);
  121 + this.timeout = setTimeout(() => {
  122 + this.menu.updateOpenKeys(this.name);
  123 + this.opened = true;
  124 + }, 250);
  125 + },
  126 + handleMouseleave () {
  127 + if (this.disabled) return;
  128 + if (this.mode === 'vertical') return;
  129 +
  130 + clearTimeout(this.timeout);
  131 + this.timeout = setTimeout(() => {
  132 + this.menu.updateOpenKeys(this.name);
  133 + this.opened = false;
  134 + }, 150);
  135 + },
  136 + handleClick () {
  137 + if (this.disabled) return;
  138 + if (this.mode === 'horizontal') return;
  139 + const opened = this.opened;
  140 + if (this.accordion) {
  141 + this.$parent.$children.forEach(item => {
  142 + if (item.$options.name === 'Submenu') item.opened = false;
  143 + });
  144 + }
  145 + this.opened = !opened;
  146 + this.menu.updateOpenKeys(this.name);
  147 + }
  148 + },
  149 + watch: {
  150 + mode (val) {
  151 + if (val === 'horizontal') {
  152 + this.$refs.drop.update();
  153 + }
  154 + },
  155 + opened (val) {
  156 + if (this.mode === 'vertical') return;
  157 + if (val) {
  158 + // set drop a width to fixed when menu has fixed position
  159 + this.dropWidth = parseFloat(getStyle(this.$el, 'width'));
  160 + this.$refs.drop.update();
  161 + } else {
  162 + this.$refs.drop.destroy();
  163 + }
  164 + }
  165 + },
  166 + mounted () {
  167 + this.$on('on-menu-item-select', (name) => {
  168 + if (this.mode === 'horizontal') this.opened = false;
  169 + this.dispatch('Menu', 'on-menu-item-select', name);
  170 + return true;
  171 + });
  172 + this.$on('on-update-active-name', (status) => {
  173 + if (findComponentUpward(this, 'Submenu')) this.dispatch('Submenu', 'on-update-active-name', status);
  174 + if (findComponentsDownward(this, 'Submenu')) findComponentsDownward(this, 'Submenu').forEach(item => {
  175 + item.active = false;
  176 + });
  177 + this.active = status;
  178 + });
  179 + }
  180 + };
  181 +</script>
... ...
src/components/mew-submenu/index.js 0 → 100644
  1 +import Submenu from '../mew-menu/submenu.vue';
  2 +
  3 +export default Submenu;
0 4 \ No newline at end of file
... ...
src/index.js
... ... @@ -36,6 +36,7 @@ import Menu from &#39;./components/menu&#39;;
36 36 import Message from './components/message';
37 37 import MewMap from './components/mew-map';
38 38 import MewMapSelector from './components/mew-map-selector';
  39 +import MewMenu from './components/mew-menu';
39 40 import Modal from './components/modal';
40 41 import Notice from './components/notice';
41 42 import Page from './components/page';
... ... @@ -113,6 +114,9 @@ const components = {
113 114 Message,
114 115 MewMap,
115 116 MewMapSelector,
  117 + MewMenu,
  118 + MewMenuGroup: MewMenu.Group,
  119 + MewMenuItem: MewMenu.Item,
116 120 Modal,
117 121 Notice,
118 122 Option: Option,
... ...