• element menu源码


    src/menu.vue

    <script type="text/jsx">
      import emitter from 'element-ui/src/mixins/emitter';
      import Migrating from 'element-ui/src/mixins/migrating';
      import Menubar from 'element-ui/src/utils/menu/aria-menubar';
      import { addClass, removeClass, hasClass } from 'element-ui/src/utils/dom';
    
      export default {
        name: 'ElMenu',
    
        render (h) {
          const component = (
            <ul
              role="menubar"
              key={ +this.collapse }
              style={{ backgroundColor: this.backgroundColor || '' }}
              class={{
                'el-menu--horizontal': this.mode === 'horizontal',
                'el-menu--collapse': this.collapse,
                "el-menu": true
              }}
            >
              { this.$slots.default }
            </ul>
          );
    
          if (this.collapseTransition) {
            return (
              <el-menu-collapse-transition>
                { component }
              </el-menu-collapse-transition>
            );
          } else {
            return component;
          }
        },
    
        componentName: 'ElMenu',
    
        mixins: [emitter, Migrating],
        // 向子孙级注入自身
        provide() {
          return {
            rootMenu: this
          };
        },
    
        components: {
          // 折叠动画组件
          'el-menu-collapse-transition': {
            // 一个 函数化组件 就像这样:标记组件为 functional, 这意味它是无状态(没有 data),无实例(没有 this 上下文)。
            functional: true,
            render(createElement, context) {
              const data = {
                props: {
                  mode: 'out-in'
                },
                on: {
                  // 进入前
                  beforeEnter(el) {
                    el.style.opacity = 0.2;
                  },
                  // 进入中
                  enter(el) {
                    addClass(el, 'el-opacity-transition');
                    el.style.opacity = 1;
                  },
                  // 进入后
                  afterEnter(el) {
                    removeClass(el, 'el-opacity-transition');
                    el.style.opacity = '';
                  },
                  // 离开前
                  beforeLeave(el) {
                    if (!el.dataset) el.dataset = {};
    
                    if (hasClass(el, 'el-menu--collapse')) {
                      removeClass(el, 'el-menu--collapse');
                      // 记录状态
                      el.dataset.oldOverflow = el.style.overflow;
                      el.dataset.scrollWidth = el.clientWidth;
                      addClass(el, 'el-menu--collapse');
                    } else {
                      addClass(el, 'el-menu--collapse');
                      el.dataset.oldOverflow = el.style.overflow;
                      el.dataset.scrollWidth = el.clientWidth;
                      removeClass(el, 'el-menu--collapse');
                    }
    
                    el.style.width = el.scrollWidth + 'px';
                    el.style.overflow = 'hidden';
                  },
    
                  leave(el) {
                    addClass(el, 'horizontal-collapse-transition');
                    el.style.width = el.dataset.scrollWidth + 'px';
                  }
                }
              };
              return createElement('transition', data, context.children);
            }
          }
        },
    
        props: {
          // mode    模式    string    horizontal / vertical    vertical
          mode: {
            type: String,
            default: 'vertical'
          },
          // 当前激活菜单的 index
          defaultActive: {
            type: String,
            default: ''
          },
          // 当前打开的 sub-menu 的 index 的数组
          defaultOpeneds: Array,
          // 是否只保持一个子菜单的展开
          uniqueOpened: Boolean,
          // 是否使用 vue-router 的模式,启用该模式会在激活导航时以 index 作为 path 进行路由跳转
          router: Boolean,
          // 子菜单打开的触发方式(只在 mode 为 horizontal 时有效)
          menuTrigger: {
            type: String,
            default: 'hover'
          },
          // 是否水平折叠收起菜单(仅在 mode 为 vertical 时可用
          collapse: Boolean,
          // 菜单的背景色(仅支持 hex 格式)
          backgroundColor: String,
          // 菜单的文字颜色(仅支持 hex 格式)
          textColor: String,
          // 当前激活菜单的文字颜色(仅支持 hex 格式)
          activeTextColor: String,
          // 是否开启折叠动画
          collapseTransition: {
            type: Boolean,
            default: true
          }
        },
        data() {
          return {
            activeIndex: this.defaultActive,//当前激活的菜单index
            openedMenus: (this.defaultOpeneds && !this.collapse) ? this.defaultOpeneds.slice(0) : [],//默认展开的菜单
            items: {},
            submenus: {}
          };
        },
        computed: {
          // 菜单hover时颜色
          hoverBackground() {
            return this.backgroundColor ? this.mixColor(this.backgroundColor, 0.2) : '';
          },
          // 是否展示hover状态下的下拉框
          isMenuPopup() {
            // 如果是水平或者垂直方向并且折叠返回true
            return this.mode === 'horizontal' || (this.mode === 'vertical' && this.collapse);
          }
        },
        watch: {
          // 监听激活菜单
          defaultActive(value){
            if(!this.items[value]){
              this.activeIndex = null
            }
            this.updateActiveIndex(value)
          },
    
          defaultOpeneds(value) {
            if (!this.collapse) {
              this.openedMenus = value;
            }
          },
    
          collapse(value) {
            if (value) this.openedMenus = [];
            this.broadcast('ElSubmenu', 'toggle-collapse', value);
          }
        },
        methods: {
          // 更新激活的菜单index
          updateActiveIndex(val) {
            const item = this.items[val] || this.items[this.activeIndex] || this.items[this.defaultActive];
            if (item) {
              this.activeIndex = item.index;
              this.initOpenedMenu();
            } else {
              this.activeIndex = null;
            }
          },
    
          getMigratingConfig() {
            return {
              props: {
                'theme': 'theme is removed.'
              }
            };
          },
          // 获取颜色通道
          getColorChannels(color) {
            // 去掉颜色前面#号
            color = color.replace('#', '');
            // 如果是3位简写
            if (/^[0-9a-fA-F]{3}$/.test(color)) {
              // 转成数组
              color = color.split('');
              for (let i = 2; i >= 0; i--) {
              // 用法:array.splice(start,deleteCount,item...)
              // 解释:splice方法从array中移除一个或多个数组,并用新的item替换它们。参数start是从数组array中移除元素的开始位置。
              // 参数deleteCount是要移除的元素的个数。
                color.splice(i, 0, color[i]);
              }
              // 转变为6位长度颜色值
              color = color.join('');
            }
            if (/^[0-9a-fA-F]{6}$/.test(color)) {
              return {
                // 转变为16进制数值
                red: parseInt(color.slice(0, 2), 16),
                green: parseInt(color.slice(2, 4), 16),
                blue: parseInt(color.slice(4, 6), 16)
              };
            } else {
              // 否则为白色
              return {
                red: 255,
                green: 255,
                blue: 255
              };
            }
          },
          // 获取混合后的颜色
          mixColor(color, percent) {
            let { red, green, blue } = this.getColorChannels(color);
            if (percent > 0) { // shade given color
              red *= 1 - percent;
              green *= 1 - percent;
              blue *= 1 - percent;
            } else { // tint given color
              red += (255 - red) * percent;
              green += (255 - green) * percent;
              blue += (255 - blue) * percent;
            }
            return `rgb(${ Math.round(red) }, ${ Math.round(green) }, ${ Math.round(blue) })`;
          },
          // 添加菜单
          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;
            // 将不在该菜单路径下的其余菜单收起
            // collapse all menu that are not under current menu item
            // 如果设置了只保留一个子菜单展开
            if (this.uniqueOpened) {
              this.openedMenus = openedMenus.filter(index => {
                return indexPath.indexOf(index) !== -1;
              });
            }
            this.openedMenus.push(index);
          },
          // 关闭菜单
          closeMenu(index) {
            const i = this.openedMenus.indexOf(index);
            if (i !== -1) {
              this.openedMenus.splice(i, 1);
            }
          },
          // 子菜单点击事件
          handleSubmenuClick(submenu) {
            const { index, indexPath } = submenu;
            let isOpened = this.openedMenus.indexOf(index) !== -1;
            // 如果打开就关闭,否则就打开
            if (isOpened) {
              this.closeMenu(index);
              this.$emit('close', index, indexPath);
            } else {
              this.openMenu(index, indexPath);
              this.$emit('open', index, indexPath);
            }
          },
          handleItemClick(item) {
            const { index, indexPath } = item;
            const oldActiveIndex = this.activeIndex;
            const hasIndex = item.index !== null;
    
            if (hasIndex) {
              // 点击某个菜单,设置为激活状态
              this.activeIndex = item.index;
            }
            // 触发select事件
            this.$emit('select', index, indexPath, item);
            // 如果是水平或者折叠,清空打开的菜单
            if (this.mode === 'horizontal' || this.collapse) {
              this.openedMenus = [];
            }
            // 如果设置了vue-router 的模式并且有index
            if (this.router && hasIndex) {
              this.routeToItem(item, (error) => {
                this.activeIndex = oldActiveIndex;
                if (error) console.error(error);
              });
            }
          },
          // 初始化展开菜单
          // initialize opened menu
          initOpenedMenu() {
            const index = this.activeIndex;
            const activeItem = this.items[index];
            if (!activeItem || this.mode === 'horizontal' || this.collapse) return;
            // 保存路由跳转路径
            let indexPath = activeItem.indexPath;
    
            // 展开该菜单项的路径上所有子菜单
            // expand all submenus of the menu item
            indexPath.forEach(index => {
              let submenu = this.submenus[index];
              submenu && this.openMenu(index, submenu.indexPath);
            });
          },
          // 跳转
          routeToItem(item, onError) {
            let route = item.route || item.index;
            try {
              this.$router.push(route, () => {}, onError);
            } catch (e) {
              console.error(e);
            }
          },
          // sub-menu 展开的回调    index: 打开的 sub-menu 的 index, indexPath: 打开的 sub-menu 的 index path
          open(index) {
            const { indexPath } = this.submenus[index.toString()];
            indexPath.forEach(i => this.openMenu(i, indexPath));
          },
          // sub-menu 收起的回调    index: 收起的 sub-menu 的 index, indexPath: 收起的 sub-menu 的 index path
          close(index) {
            this.closeMenu(index);
          }
        },
        mounted() {
          this.initOpenedMenu();
          // 接收item-click事件,handleItemClick调用
          this.$on('item-click', this.handleItemClick);
          // 接收submenu-click事件,触发handleSubmenuClick调用
          this.$on('submenu-click', this.handleSubmenuClick);
          if (this.mode === 'horizontal') {
            new Menubar(this.$el); // eslint-disable-line
          }
          this.$watch('items', this.updateActiveIndex);
        }
      };
    </script>

    src/submenu.vue

    <script>
      import ElCollapseTransition from 'element-ui/src/transitions/collapse-transition';
      import menuMixin from './menu-mixin';
      import Emitter from 'element-ui/src/mixins/emitter';
      import Popper from 'element-ui/src/utils/vue-popper';
    
      const poperMixins = {
        props: {
          transformOrigin: {
            type: [Boolean, String],
            default: false
          },
          offset: Popper.props.offset,
          boundariesPadding: Popper.props.boundariesPadding,
          popperOptions: Popper.props.popperOptions
        },
        data: Popper.data,
        methods: Popper.methods,
        beforeDestroy: Popper.beforeDestroy,
        deactivated: Popper.deactivated // keep-alive组件停用时调用
      };
    
      export default {
        name: 'ElSubmenu',
    
        componentName: 'ElSubmenu',
    
        mixins: [menuMixin, Emitter, poperMixins],
    
        components: { ElCollapseTransition },
    
        props: {
          // 唯一标志    string/null    —    null
          index: {
            type: String,
            required: true
          },
          // 展开 sub-menu 的延时    number    —    300
          showTimeout: {
            type: Number,
            default: 300
          },
          // 收起 sub-menu 的延时    number    —    300
          hideTimeout: {
            type: Number,
            default: 300
          },
          // 弹出菜单的自定义类名
          popperClass: String,
          // 是否禁用
          disabled: Boolean,
          // 是否将弹出菜单插入至 body 元素。在菜单的定位出现问题时,可尝试修改该属性    boolean    —    一级子菜单:true / 非一级子菜单:false
          popperAppendToBody: {
            type: Boolean,
            default: undefined
          }
        },
    
        data() {
          return {
            popperJS: null,
            timeout: null,
            items: {},
            submenus: {},
            mouseInChild: false
          };
        },
        watch: {
          // 是否打开
          opened(val) {
            if (this.isMenuPopup) {
              this.$nextTick(_ => {
                this.updatePopper();
              });
            }
          }
        },
        computed: {
          // popper option
          appendToBody() {
            return this.popperAppendToBody === undefined
              ? this.isFirstLevel
              : this.popperAppendToBody;
          },
          menuTransitionName() {
            return this.rootMenu.collapse ? 'el-zoom-in-left' : 'el-zoom-in-top';
          },
          // 打开
          opened() {
            return this.rootMenu.openedMenus.indexOf(this.index) > -1;
          },
          active() {
            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;
          },
          // 鼠标移入的背景色
          hoverBackground() {
            return this.rootMenu.hoverBackground;
          },
          // 菜单的背景色(仅支持 hex 格式)
          backgroundColor() {
            return this.rootMenu.backgroundColor || '';
          },
          // 当前激活菜单的文字颜色(仅支持 hex 格式)
          activeTextColor() {
            return this.rootMenu.activeTextColor || '';
          },
          // 菜单的文字颜色(仅支持 hex 格式)
          textColor() {
            return this.rootMenu.textColor || '';
          },
          // 模式
          mode() {
            return this.rootMenu.mode;
          },
          // 是否展示下拉框
          isMenuPopup() {
            return this.rootMenu.isMenuPopup;
          },
          // title样式
          titleStyle() {
            if (this.mode !== 'horizontal') {
              return {
                color: this.textColor
              };
            }
            return {
              borderBottomColor: this.active
                ? (this.rootMenu.activeTextColor ? this.activeTextColor : '')
                : 'transparent',
              color: this.active
                ? this.activeTextColor
                : this.textColor
            };
          },
          // 是否是第一级
          isFirstLevel() {
            let isFirstLevel = true;
            let parent = this.$parent;
            while (parent && parent !== this.rootMenu) {
              if (['ElSubmenu', 'ElMenuItemGroup'].indexOf(parent.$options.componentName) > -1) {
                isFirstLevel = false;
                break;
              } else {
                parent = parent.$parent;
              }
            }
            return isFirstLevel;
          }
        },
        methods: {
          // 开关折叠
          handleCollapseToggle(value) {
            // 如果为true
            if (value) {
              this.initPopper();
            } else {
              this.doDestroy();
            }
          },
          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() {
            const { rootMenu, disabled } = this;
            if (
              (rootMenu.menuTrigger === 'hover' && rootMenu.mode === 'horizontal') ||
              (rootMenu.collapse && rootMenu.mode === 'vertical') ||
              disabled
            ) {
              return;
            }
            this.dispatch('ElMenu', 'submenu-click', this);
          },
          // 鼠标移入
          handleMouseenter(event, showTimeout = this.showTimeout) {
    
            if (!('ActiveXObject' in window) && event.type === 'focus' && !event.relatedTarget) {
              return;
            }
            const { rootMenu, disabled } = this;
            if (
              (rootMenu.menuTrigger === 'click' && rootMenu.mode === 'horizontal') ||
              (!rootMenu.collapse && rootMenu.mode === 'vertical') ||
              disabled
            ) {
              return;
            }
            // 寻找父级,在父组件触发
            this.dispatch('ElSubmenu', 'mouse-enter-child');
            clearTimeout(this.timeout);
            this.timeout = setTimeout(() => {
              this.rootMenu.openMenu(this.index, this.indexPath);
            }, showTimeout);
          },
          // 鼠标移出
          handleMouseleave() {
            const {rootMenu} = this;
            if (
              (rootMenu.menuTrigger === 'click' && rootMenu.mode === 'horizontal') ||
              (!rootMenu.collapse && rootMenu.mode === 'vertical')
            ) {
              return;
            }
            this.dispatch('ElSubmenu', 'mouse-leave-child');
            clearTimeout(this.timeout);
            this.timeout = setTimeout(() => {
              !this.mouseInChild && this.rootMenu.closeMenu(this.index);
            }, this.hideTimeout);
          },
          // 鼠标移入子菜单,背景色设置为父组件鼠标移入背景色
          handleTitleMouseenter() {
            if (this.mode === 'horizontal' && !this.rootMenu.backgroundColor) return;
            const title = this.$refs['submenu-title'];
            title && (title.style.backgroundColor = this.rootMenu.hoverBackground);
          },
          // 同上
          handleTitleMouseleave() {
            if (this.mode === 'horizontal' && !this.rootMenu.backgroundColor) return;
            const title = this.$refs['submenu-title'];
            title && (title.style.backgroundColor = this.rootMenu.backgroundColor || '');
          },
          // 更新位置
          updatePlacement() {
            // 如果是水平方向且是一级为底部,否则为右侧
            this.currentPlacement = this.mode === 'horizontal' && this.isFirstLevel
              ? 'bottom-start'
              : 'right-start';
          },
          // 初始化poper
          initPopper() {
            this.referenceElm = this.$el;
            this.popperElm = this.$refs.menu;
            this.updatePlacement();
          }
        },
        created() {
          // 接收toggle-collapse订阅,触发handleCollapseToggle
          this.$on('toggle-collapse', this.handleCollapseToggle);
          // 接收mouse-enter-child订阅,鼠标进入
          this.$on('mouse-enter-child', () => {
            this.mouseInChild = true;
            clearTimeout(this.timeout);
          });
          // 鼠标离开
          this.$on('mouse-leave-child', () => {
            this.mouseInChild = false;
            clearTimeout(this.timeout);
          });
        },
        mounted() {
          // 初始化,添加
          this.parentMenu.addSubmenu(this);
          this.rootMenu.addSubmenu(this);
          this.initPopper();
        },
        // 销毁前
        beforeDestroy() {
          // 移除
          this.parentMenu.removeSubmenu(this);
          this.rootMenu.removeSubmenu(this);
        },
        render(h) {
          const {
            active,
            opened,
            paddingStyle,
            titleStyle,
            backgroundColor,
            rootMenu,
            currentPlacement,
            menuTransitionName,
            mode,
            disabled,
            popperClass,
            $slots,
            isFirstLevel
          } = this;
    
          const popupMenu = (
            <transition name={menuTransitionName}>
              <div
                ref="menu"
                v-show={opened}
                class={[`el-menu--${mode}`, popperClass]}
                on-mouseenter={($event) => this.handleMouseenter($event, 100)}
                on-mouseleave={this.handleMouseleave}
                on-focus={($event) => this.handleMouseenter($event, 100)}>
                <ul
                  role="menu"
                  class={['el-menu el-menu--popup', `el-menu--popup-${currentPlacement}`]}
                  style={{ backgroundColor: rootMenu.backgroundColor || '' }}>
                  {$slots.default}
                </ul>
              </div>
            </transition>
          );
    
          const inlineMenu = (
            <el-collapse-transition>
              <ul
                role="menu"
                class="el-menu el-menu--inline"
                v-show={opened}
                style={{ backgroundColor: rootMenu.backgroundColor || '' }}>
                {$slots.default}
              </ul>
            </el-collapse-transition>
          );
    
          const submenuTitleIcon = (
            rootMenu.mode === 'horizontal' && isFirstLevel ||
            rootMenu.mode === 'vertical' && !rootMenu.collapse
          ) ? 'el-icon-arrow-down' : 'el-icon-arrow-right';
    
          return (
            <li
              class={{
                'el-submenu': true,
                'is-active': active,
                'is-opened': opened,
                'is-disabled': disabled
              }}
              role="menuitem"
              aria-haspopup="true"
              aria-expanded={opened}
              on-mouseenter={this.handleMouseenter}
              on-mouseleave={this.handleMouseleave}
              on-focus={this.handleMouseenter}
            >
              <div
                class="el-submenu__title"
                ref="submenu-title"
                on-click={this.handleClick}
                on-mouseenter={this.handleTitleMouseenter}
                on-mouseleave={this.handleTitleMouseleave}
                style={[paddingStyle, titleStyle, { backgroundColor }]}
              >
                {$slots.title}
                <i class={[ 'el-submenu__icon-arrow', submenuTitleIcon ]}></i>
              </div>
              {this.isMenuPopup ? popupMenu : inlineMenu}
            </li>
          );
        }
      };
    </script>

    src/menu-item-group

    <template>
      <li class="el-menu-item-group">
        <div class="el-menu-item-group__title" :style="{paddingLeft: levelPadding + 'px'}">
          <template v-if="!$slots.title">{{title}}</template>
          <slot v-else name="title"></slot>
        </div>
        <ul>
          <slot></slot>
        </ul>
      </li>
    </template>
    <script>
      export default {
        name: 'ElMenuItemGroup',
    
        componentName: 'ElMenuItemGroup',
    
        inject: ['rootMenu'],
        props: {
          title: {
            type: String
          }
        },
        data() {
          return {
            paddingLeft: 20
          };
        },
        computed: {
          levelPadding() {
            let padding = 20;
            let parent = this.$parent;
            if (this.rootMenu.collapse) return 20;
            while (parent && parent.$options.componentName !== 'ElMenu') {
              // 每层submenu + 20
              if (parent.$options.componentName === 'ElSubmenu') {
                padding += 20;
              }
              parent = parent.$parent;
            }
            return padding;
          }
        }
      };
    </script>

    src/menu-item.vue

    <template>
      <li class="el-menu-item"
        role="menuitem"
        tabindex="-1"
        :style="[paddingStyle, itemStyle, { backgroundColor }]"
        :class="{
          'is-active': active,
          'is-disabled': disabled
        }"
        @click="handleClick"
        @mouseenter="onMouseEnter"
        @focus="onMouseEnter"
        @blur="onMouseLeave"
        @mouseleave="onMouseLeave"
      >
        <el-tooltip
          v-if="parentMenu.$options.componentName === 'ElMenu' && rootMenu.collapse && $slots.title"
          effect="dark"
          placement="right">
          <div slot="content"><slot name="title"></slot></div>
          <div style="position: absolute;left: 0;top: 0;height: 100%; 100%;display: inline-block;box-sizing: border-box;padding: 0 20px;">
            <slot></slot>
          </div>
        </el-tooltip>
        <template v-else>
          <slot></slot>
          <slot name="title"></slot>
        </template>
      </li>
    </template>
    <script>
      import Menu from './menu-mixin';
      import ElTooltip from 'element-ui/packages/tooltip';
      import Emitter from 'element-ui/src/mixins/emitter';
    
      export default {
        name: 'ElMenuItem',
    
        componentName: 'ElMenuItem',
    
        mixins: [Menu, Emitter],
    
        components: { ElTooltip },
    
        props: {
          index: {
            default: null,
            validator: val => typeof val === 'string' || val === null
          },
          route: [String, Object],
          disabled: Boolean
        },
        computed: {
          // 是否激活
          active() {
            return this.index === this.rootMenu.activeIndex;
          },
          // hove背景色
          hoverBackground() {
            return this.rootMenu.hoverBackground;
          },
          // 菜单背景色
          backgroundColor() {
            return this.rootMenu.backgroundColor || '';
          },
          // 激活文字背景色
          activeTextColor() {
            return this.rootMenu.activeTextColor || '';
          },
          // 文字颜色
          textColor() {
            return this.rootMenu.textColor || '';
          },
          // 模式
          mode() {
            return this.rootMenu.mode;
          },
          // item样式
          itemStyle() {
            const style = {
              color: this.active ? this.activeTextColor : this.textColor
            };
            if (this.mode === 'horizontal' && !this.isNested) {
              style.borderBottomColor = this.active
                ? (this.rootMenu.activeTextColor ? this.activeTextColor : '')
                : 'transparent';
            }
            return style;
          },
          isNested() {
            return this.parentMenu !== this.rootMenu;
          }
        },
        methods: {
          // 鼠标移入
          onMouseEnter() {
            if (this.mode === 'horizontal' && !this.rootMenu.backgroundColor) return;
            this.$el.style.backgroundColor = this.hoverBackground;
          },
          // 鼠标移出
          onMouseLeave() {
            if (this.mode === 'horizontal' && !this.rootMenu.backgroundColor) return;
            this.$el.style.backgroundColor = this.backgroundColor;
          },
          // 点击
          handleClick() {
            if (!this.disabled) {
              this.dispatch('ElMenu', 'item-click', this);
              this.$emit('click', this);
            }
          }
        },
        mounted() {
          this.parentMenu.addItem(this);
          this.rootMenu.addItem(this);
        },
        beforeDestroy() {
          this.parentMenu.removeItem(this);
          this.rootMenu.removeItem(this);
        }
      };
    </script>

    src/menu-mixin.js

    // mixis混入
    export default {
      // 接收
      inject: ['rootMenu'],
      computed: {
        // 路径
        indexPath () {
          const path = [this.index];
          let parent = this.$parent;
          while (parent.$options.componentName !== 'ElMenu') {
            if (parent.index) {
              path.unshift(parent.index);
            }
            parent = parent.$parent;
          }
          return path;
        },
        // 获取elMenu或者elSubmenu
        parentMenu () {
          let parent = this.$parent;
          while (
            parent &&
            ['ElMenu', 'ElSubmenu'].indexOf(parent.$options.componentName) === -1
          ) {
            parent = parent.$parent;
          }
          return parent;
        },
        // 样式
        paddingStyle () {
          // 不是垂直返回
          if (this.rootMenu.mode !== 'vertical') return {};
    
          let padding = 20;
          let parent = this.$parent;
    
          if (this.rootMenu.collapse) {
            padding = 20;
          } else {
            while (parent && parent.$options.componentName !== 'ElMenu') {
              // 每次遇到ElSubmenu增加20
              if (parent.$options.componentName === 'ElSubmenu') {
                padding += 20;
              }
              parent = parent.$parent;
            }
          }
          return { paddingLeft: padding + 'px' };
        }
      }
    };
  • 相关阅读:
    107. Binary Tree Level Order Traversal II
    103. Binary Tree Zigzag Level Order Traversal
    102. Binary Tree Level Order Traversal
    690. Employee Importance
    1723. Find Minimum Time to Finish All Jobs
    LeetCode 329 矩阵中最长增长路径
    7.2 物理内存管理
    LeetCode 面试题 特定深度节点链表
    LeetCode 100 相同的树
    npm安装包命令详解,dependencies与devDependencies实际区别
  • 原文地址:https://www.cnblogs.com/wsk1576025821/p/10969174.html
Copyright © 2020-2023  润新知