vben
2021-01-25 0ec1a62e596c363f3f017d6ac3b374a1b5caa7c5
fix(menu): top submenu disappeared problem #214
13个文件已修改
400 ■■■■ 已修改文件
CHANGELOG.zh_CN.md 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
build/config/lessModifyVars.ts 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Menu/src/BasicMenu.vue 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Menu/src/components/BasicMenuItem.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Menu/src/components/BasicSubMenuItem.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Menu/src/components/MenuItemContent.vue 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Menu/src/index.less 292 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/default/menu/useLayoutMenu.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/page/ParentView.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/page/index.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/page/useCache.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yarn.lock 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
CHANGELOG.zh_CN.md
@@ -15,6 +15,7 @@
- 修复 form 表单初始化值为 0 问题
- 修复表格换行问题
- 修复菜单外链不跳转
- 修复菜单顶部显示问题
## 2.0.0-rc.17 (2020-01-18)
build/config/lessModifyVars.ts
@@ -1,7 +1,8 @@
/**
 * less global variable
 */
const primaryColor = '#018ffb';
const primaryColor = '#0084f4';
// const primaryColor = '#018ffb';
// const primaryColor = '#0065cc';
//{
const modifyVars = {
package.json
@@ -64,7 +64,7 @@
    "@typescript-eslint/eslint-plugin": "^4.14.0",
    "@typescript-eslint/parser": "^4.14.0",
    "@vitejs/plugin-legacy": "^1.2.1",
    "@vitejs/plugin-vue": "^1.1.2",
    "@vitejs/plugin-vue": "1.1.0",
    "@vitejs/plugin-vue-jsx": "^1.0.2",
    "@vue/compiler-sfc": "^3.0.5",
    "@vuedx/typecheck": "^0.6.0",
src/components/Menu/src/BasicMenu.vue
@@ -13,13 +13,7 @@
    v-bind="getInlineCollapseOptions"
  >
    <template v-for="item in items" :key="item.path">
      <BasicSubMenuItem
        :item="item"
        :theme="theme"
        :level="1"
        :showTitle="showTitle"
        :isHorizontal="isHorizontal"
      />
      <BasicSubMenuItem :item="item" :theme="theme" :isHorizontal="isHorizontal" />
    </template>
  </Menu>
</template>
@@ -46,6 +40,7 @@
  // import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
  import { listenerLastChangeTab } from '/@/logics/mitt/tabChange';
  import { getAllParentPath } from '/@/router/helper/menuHelper';
  export default defineComponent({
    name: 'BasicMenu',
@@ -96,15 +91,11 @@
          prefixCls,
          `justify-${align}`,
          {
            [`${prefixCls}--hide-title`]: !unref(showTitle),
            [`${prefixCls}--collapsed-show-title`]: props.collapsedShowTitle,
            [`${prefixCls}__second`]: !props.isHorizontal && unref(getSplit),
            [`${prefixCls}__sidebar-hor`]: unref(getIsTopMenu),
          },
        ];
      });
      const showTitle = computed(() => props.collapsedShowTitle && unref(getCollapsed));
      const getInlineCollapseOptions = computed(() => {
        const isInline = props.mode === MenuModeEnum.INLINE;
@@ -135,7 +126,7 @@
          }
        );
      async function handleMenuClick({ key, keyPath }: { key: string; keyPath: string[] }) {
      async function handleMenuClick({ key }: { key: string; keyPath: string[] }) {
        const { beforeClickFn } = props;
        if (beforeClickFn && isFunction(beforeClickFn)) {
          const flag = await beforeClickFn(key);
@@ -144,7 +135,9 @@
        emit('menuClick', key);
        isClickGo.value = true;
        menuState.openKeys = keyPath;
        // const parentPath = await getCurrentParentPath(key);
        // menuState.openKeys = [parentPath];
        menuState.selectedKeys = [key];
      }
@@ -160,7 +153,8 @@
          const parentPath = await getCurrentParentPath(path);
          menuState.selectedKeys = [parentPath];
        } else {
          menuState.selectedKeys = [path];
          const parentPaths = await getAllParentPath(props.items, path);
          menuState.selectedKeys = parentPaths;
        }
      }
@@ -172,7 +166,6 @@
        getMenuClass,
        handleOpenChange,
        getOpenKeys,
        showTitle,
        ...toRefs(menuState),
      };
    },
src/components/Menu/src/components/BasicMenuItem.vue
@@ -1,10 +1,11 @@
<template>
  <MenuItem :class="getLevelClass">
  <MenuItem>
    <!-- <MenuItem :class="getLevelClass"> -->
    <MenuItemContent v-bind="$props" :item="item" />
  </MenuItem>
</template>
<script lang="ts">
  import { defineComponent, computed } from 'vue';
  import { defineComponent } from 'vue';
  import { Menu } from 'ant-design-vue';
  import { useDesign } from '/@/hooks/web/useDesign';
  import { itemProps } from '../props';
@@ -14,18 +15,19 @@
    name: 'BasicMenuItem',
    components: { MenuItem: Menu.Item, MenuItemContent },
    props: itemProps,
    setup(props) {
    setup() // props
    {
      const { prefixCls } = useDesign('basic-menu-item');
      const getLevelClass = computed(() => {
        const { level, theme } = props;
      // const getLevelClass = computed(() => {
      //   const { level, theme } = props;
        const levelCls = [`${prefixCls}__level${level}`, theme];
        return levelCls;
      });
      //   const levelCls = [`${prefixCls}__level${level}`, theme];
      //   return levelCls;
      // });
      return {
        prefixCls,
        getLevelClass,
        // getLevelClass,
      };
    },
  });
src/components/Menu/src/components/BasicSubMenuItem.vue
@@ -2,17 +2,15 @@
  <BasicMenuItem v-if="!menuHasChildren(item) && getShowMenu" v-bind="$props" />
  <SubMenu
    v-if="menuHasChildren(item) && getShowMenu"
    :class="[`${prefixCls}__level${level}`, theme]"
    :class="[theme]"
    popupClassName="app-top-menu-popup"
  >
    <template #title>
      <MenuItemContent v-bind="$props" :item="item" />
    </template>
    <!-- <template #expandIcon="{ key }">
      <ExpandIcon :key="key" />
    </template> -->
    <template v-for="childrenItem in item.children || []" :key="childrenItem.path">
      <BasicSubMenuItem v-bind="$props" :item="childrenItem" :level="level + 1" />
      <BasicSubMenuItem v-bind="$props" :item="childrenItem" />
    </template>
  </SubMenu>
</template>
@@ -26,7 +24,6 @@
  import BasicMenuItem from './BasicMenuItem.vue';
  import MenuItemContent from './MenuItemContent.vue';
  // import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
  export default defineComponent({
    name: 'BasicSubMenuItem',
    isSubMenu: true,
@@ -35,7 +32,6 @@
      SubMenu: Menu.SubMenu,
      MenuItem: Menu.Item,
      MenuItemContent,
      // ExpandIcon: createAsyncComponent(() => import('./ExpandIcon.vue')),
    },
    props: itemProps,
    setup(props) {
src/components/Menu/src/components/MenuItemContent.vue
@@ -1,10 +1,7 @@
<template>
  <span :class="`${prefixCls}-wrapper`">
    <Icon v-if="getIcon" :icon="getIcon" :size="18" :class="`${prefixCls}-wrapper__icon`" />
    <span :class="getNameClass">
      {{ getI18nName }}
      <MenuItemTag v-bind="$props" />
    </span>
    {{ getI18nName }}
  </span>
</template>
<script lang="ts">
@@ -14,25 +11,21 @@
  import { useI18n } from '/@/hooks/web/useI18n';
  import { useDesign } from '/@/hooks/web/useDesign';
  import { contentProps } from '../props';
  import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
  const { t } = useI18n();
  export default defineComponent({
    name: 'MenuItemContent',
    components: { Icon, MenuItemTag: createAsyncComponent(() => import('./MenuItemTag.vue')) },
    components: {
      Icon,
    },
    props: contentProps,
    setup(props) {
      const { prefixCls } = useDesign('basic-menu-item-content');
      const getI18nName = computed(() => t(props.item?.name));
      const getIcon = computed(() => props.item?.icon);
      const getNameClass = computed(() => {
        const { showTitle } = props;
        return { [`${prefixCls}--show-title`]: showTitle, [`${prefixCls}__name`]: !showTitle };
      });
      return {
        prefixCls,
        getNameClass,
        getI18nName,
        getIcon,
      };
src/components/Menu/src/index.less
@@ -1,128 +1,11 @@
@basic-menu-prefix-cls: ~'@{namespace}-basic-menu';
@basic-menu-content-prefix-cls: ~'@{namespace}-basic-menu-item-content';
@basic-menu-tag-prefix-cls: ~'@{namespace}-basic-menu-item-tag';
.active-style() {
  color: @white;
  // background: @primary-color !important;
  background: linear-gradient(
    118deg,
    rgba(@primary-color, 0.8),
    rgba(@primary-color, 1)
  ) !important;
}
.active-menu-style() {
  .ant-menu-item-selected,
  .ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected {
    .active-style();
  }
.app-top-menu-popup {
  min-width: 150px;
}
.@{basic-menu-prefix-cls} {
  width: 100%;
  // &__expand-icon {
  //   position: absolute;
  //   top: calc(50% - 6px);
  //   right: 16px;
  //   width: 10px;
  //   transform-origin: none;
  //   opacity: 0.45;
  //   span[role='img'] {
  //     margin-right: 0;
  //     font-size: 11px;
  //   }
  //   &--collapsed {
  //     opacity: 0;
  //   }
  // }
  //  collapsed show title start
  .@{basic-menu-content-prefix-cls}--show-title {
    max-width: unset !important;
    opacity: 1 !important;
  }
  &--hide-title {
    &.ant-menu-inline-collapsed > .ant-menu-item,
    &.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item,
    &.ant-menu-inline-collapsed
      > .ant-menu-item-group
      > .ant-menu-item-group-list
      > .ant-menu-submenu
      > .ant-menu-submenu-title,
    &.ant-menu-inline-collapsed .ant-menu-submenu-title {
      padding-right: 16px !important;
      padding-left: 16px !important;
    }
  }
  &--collapsed-show-title.ant-menu-inline-collapsed {
    .@{basic-menu-prefix-cls}-item__level1 {
      padding: 2px 0;
      justify-content: center !important;
      &.ant-menu-item {
        display: flex;
        align-items: center;
        height: 60px !important;
        margin-top: 0 !important;
        margin-bottom: 0 !important;
        line-height: 60px !important;
        > span {
          margin-top: 10px;
        }
      }
    }
    & > li[role='menuitem']:not(.ant-menu-submenu),
    & > li > .ant-menu-submenu-title {
      display: flex;
      margin-top: 10px;
      font-size: 12px;
      flex-direction: column;
      align-items: center;
      line-height: 24px;
    }
    & > li > .ant-menu-submenu-title {
      line-height: 24px;
    }
    .@{basic-menu-content-prefix-cls}-wrapper {
      display: flex;
      justify-content: center;
      align-items: center;
      flex-direction: column;
      .@{basic-menu-content-prefix-cls}--show-title {
        line-height: 30px;
      }
    }
  }
  &.ant-menu-inline-collapsed > .ant-menu-item,
  &.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item,
  &.ant-menu-inline-collapsed
    > .ant-menu-item-group
    > .ant-menu-item-group-list
    > .ant-menu-submenu
    > .ant-menu-submenu-title,
  &.ant-menu-inline-collapsed .ant-menu-submenu-title {
    padding-right: 16px !important;
    padding-left: 16px !important;
  }
  .@{basic-menu-content-prefix-cls}-wrapper {
    width: 100%;
    margin-top: 4px;
    &__icon {
      vertical-align: text-top;
    }
  }
  .ant-menu-item {
    transition: unset;
@@ -179,117 +62,6 @@
    }
  }
  &.ant-menu-dark:not(.@{basic-menu-prefix-cls}__sidebar-hor):not(.@{basic-menu-prefix-cls}__second) {
    // Reset menu item row height
    .ant-menu-item:not(.@{basic-menu-prefix-cls}-item__level1),
    .ant-menu-sub.ant-menu-inline > .ant-menu-item,
    .ant-menu-sub.ant-menu-inline > .ant-menu-submenu > .ant-menu-submenu-title {
      height: @app-menu-item-height;
      margin: 0;
      line-height: @app-menu-item-height;
    }
    .ant-menu-item.@{basic-menu-prefix-cls}-item__level1 {
      height: @app-menu-item-height;
      line-height: @app-menu-item-height;
    }
  }
  // 层级样式
  &.ant-menu-dark:not(.@{basic-menu-prefix-cls}__sidebar-hor) {
    overflow: hidden;
    background: @sider-dark-bg-color;
    .active-menu-style();
    .ant-menu-item.ant-menu-item-selected.@{basic-menu-prefix-cls}-menu-item__level1,
    .ant-menu-submenu-selected.@{basic-menu-prefix-cls}-menu-item__level1 {
      color: @white;
    }
    .@{basic-menu-prefix-cls}-item__level1 {
      background-color: @sider-dark-bg-color;
      > .ant-menu-sub > li {
        background-color: @sider-dark-lighten-1-bg-color;
      }
    }
    .@{basic-menu-prefix-cls}-item__level2:not(.ant-menu-item-selected),
    .ant-menu-sub {
      background-color: @sider-dark-lighten-1-bg-color;
    }
    .@{basic-menu-prefix-cls}-item__level3:not(.ant-menu-item-selected) {
      background-color: @sider-dark-lighten-2-bg-color;
      .ant-menu-item {
        background-color: @sider-dark-lighten-2-bg-color;
      }
    }
    .ant-menu-submenu-title {
      display: flex;
      height: @app-menu-item-height;
      // margin: 0;
      align-items: center;
    }
    &.ant-menu-inline-collapsed {
      .ant-menu-submenu-selected,
      .ant-menu-item-selected {
        position: relative;
        font-weight: 500;
        color: @white;
        background: @sider-dark-darken-bg-color !important;
        &::before {
          position: absolute;
          top: 0;
          left: 0;
          width: 3px;
          height: 100%;
          background: @primary-color;
          content: '';
        }
      }
    }
  }
  &.ant-menu-light:not(.@{basic-menu-prefix-cls}__sidebar-hor) {
    // overflow: hidden;
    border-right: none;
    .ant-menu-item.ant-menu-item-selected.@{basic-menu-prefix-cls}-menu-item__level1,
    .ant-menu-submenu-selected.@{basic-menu-prefix-cls}-menu-item__level1 {
      color: @primary-color;
    }
  }
  &.@{basic-menu-prefix-cls}__second.ant-menu-inline-collapsed:not(.@{basic-menu-prefix-cls}__sidebar-hor) {
    // Reset menu item row height
    .@{basic-menu-prefix-cls}-item__level1 {
      display: flex;
      height: @app-menu-item-height * 1.4;
      padding: 6px 0 !important;
      margin: 0;
      font-size: 12px;
      line-height: @app-menu-item-height;
      align-items: center;
      text-align: center;
      > div {
        padding: 6px 0 !important;
        font-size: 12px;
      }
      .@{basic-menu-content-prefix-cls}__name {
        display: inline-block;
        width: 50%;
        overflow: hidden;
      }
    }
  }
  .ant-menu-submenu,
  .ant-menu-submenu-inline {
    transition: unset;
@@ -298,65 +70,5 @@
  .ant-menu-inline.ant-menu-sub {
    box-shadow: unset !important;
    transition: unset;
  }
}
.ant-menu-dark {
  &.ant-menu-submenu-popup {
    > ul {
      background: @sider-dark-bg-color;
    }
    .active-menu-style();
  }
}
// collapsed show title end
.ant-menu-item,
.ant-menu-submenu-title {
  > .@{basic-menu-content-prefix-cls}__name {
    width: 100%;
    .@{basic-menu-tag-prefix-cls} {
      float: right;
      margin-top: @app-menu-item-height / 2;
      transform: translate(0%, -50%);
    }
  }
}
.@{basic-menu-tag-prefix-cls} {
  position: absolute;
  top: calc(50% - 8px);
  right: 30px;
  display: inline-block;
  padding: 2px 4px;
  margin-right: 4px;
  font-size: 12px;
  line-height: 14px;
  color: #fff;
  border-radius: 2px;
  &--dot {
    top: calc(50% - 4px);
    width: 6px;
    height: 6px;
    padding: 0;
    border-radius: 50%;
  }
  &--primary {
    background: @primary-color;
  }
  &--error {
    background: @error-color;
  }
  &--success {
    background: @success-color;
  }
  &--warn {
    background: @warning-color;
  }
}
src/layouts/default/menu/useLayoutMenu.ts
@@ -72,9 +72,6 @@
  // Handle left menu split
  async function handleSplitLeftMenu(parentPath: string) {
    console.log('======================');
    console.log(unref(getSplitLeft));
    console.log('======================');
    if (unref(getSplitLeft) || unref(getIsMobile)) return;
    // spilt mode left
src/layouts/page/ParentView.vue
@@ -19,9 +19,9 @@
          appear
        >
          <keep-alive v-if="openCache" :include="getCaches">
            <component :is="Component" :key="route.fullPath" />
            <component :is="Component" v-bind="getKey(Component, route)" />
          </keep-alive>
          <component v-else :is="Component" :key="route.fullPath" />
          <component v-else :is="Component" v-bind="getKey(Component, route)" />
        </transition>
      </template>
    </router-view>
@@ -34,7 +34,7 @@
  import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
  import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
  import { useCache } from './useCache';
  import { useCache, getKey } from './useCache';
  import { getTransitionName } from './transition';
  export default defineComponent({
@@ -56,6 +56,7 @@
        openCache,
        getEnableTransition,
        getTransitionName,
        getKey,
      };
    },
  });
src/layouts/page/index.vue
@@ -27,9 +27,6 @@
</template>
<script lang="ts">
  import type { FunctionalComponent } from 'vue';
  import type { RouteLocation } from 'vue-router';
  import { computed, defineComponent, unref } from 'vue';
  import FrameLayout from '/@/layouts/iframe/index.vue';
@@ -37,7 +34,7 @@
  import { useRootSetting } from '/@/hooks/setting/useRootSetting';
  import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
  import { useCache } from './useCache';
  import { useCache, getKey } from './useCache';
  import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
  import { getTransitionName } from './transition';
@@ -53,10 +50,6 @@
      const { getBasicTransition, getEnableTransition } = useTransitionSetting();
      const openCache = computed(() => unref(getOpenKeepAlive) && unref(getShowMultipleTab));
      function getKey(component: FunctionalComponent & { type: Indexable }, route: RouteLocation) {
        return !!component?.type.parentView ? {} : { key: route.fullPath };
      }
      return {
        getTransitionName,
src/layouts/page/useCache.ts
@@ -1,3 +1,5 @@
import type { FunctionalComponent } from 'vue';
import type { RouteLocation } from 'vue-router';
import { computed, ref, unref } from 'vue';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { tryTsxEmit } from '/@/utils/helper/vueHelper';
@@ -6,6 +8,11 @@
import { useRouter } from 'vue-router';
const ParentLayoutName = 'ParentLayout';
export function getKey(component: FunctionalComponent & { type: Indexable }, route: RouteLocation) {
  return !!component?.type.parentView ? {} : { key: route.fullPath };
}
export function useCache(isPage: boolean) {
  const name = ref('');
  const { currentRoute } = useRouter();
yarn.lock
@@ -1768,10 +1768,10 @@
    "@vue/babel-plugin-jsx" "^1.0.1"
    hash-sum "^2.0.0"
"@vitejs/plugin-vue@^1.1.2":
  version "1.1.2"
  resolved "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.1.2.tgz#64d1f0e0739675f5717015ffb4d861c53af8fe60"
  integrity sha512-a5ORYuPsiAO4Kb2blA/x63mDiBQBxEJkbjhVtiv5IP/I7fGfpwXPPGHx9LHD4MedpXp8icngJYMKO0hOwahtmQ==
"@vitejs/plugin-vue@1.1.0":
  version "1.1.0"
  resolved "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.1.0.tgz#8ae0b11388897b07259c9e5198c0e3fb5e4b37d9"
  integrity sha512-ExlAt3nb3PB31jV9AgRZSMoGd+aQRU53fc/seghV8/l0JCzaX2mqlgpG8iytWkRxbBPgtAx4TpCPdiVKnTFT/A==
"@vue/babel-helper-vue-transform-on@^1.0.2":
  version "1.0.2"