Browse Source

feat: support mobile layout adaptation

vben 4 years ago
parent
commit
c774a6d3a0
35 changed files with 589 additions and 630 deletions
  1. 4 0
      CHANGELOG.zh_CN.md
  2. 1 1
      package.json
  3. 2 4
      src/components/Application/src/search/AppSearch.vue
  4. 119 45
      src/components/Application/src/search/AppSearchModal.vue
  5. 1 1
      src/components/Menu/src/BasicMenu.vue
  6. 6 6
      src/hooks/setting/useMenuSetting.ts
  7. 0 214
      src/layouts/default/header/LayoutHeader.tsx
  8. 0 18
      src/layouts/default/header/LayoutMultipleHeader.less
  9. 0 125
      src/layouts/default/header/LayoutMultipleHeader.tsx
  10. 124 0
      src/layouts/default/header/MultipleHeader.vue
  11. 27 1
      src/layouts/default/header/index.less
  12. 18 13
      src/layouts/default/header/index.vue
  13. 8 10
      src/layouts/default/index.vue
  14. 13 2
      src/layouts/default/menu/index.less
  15. 23 7
      src/layouts/default/menu/index.tsx
  16. 7 5
      src/layouts/default/menu/useLayoutMenu.ts
  17. 67 0
      src/layouts/default/sider/DragBar.vue
  18. 8 7
      src/layouts/default/sider/LayoutSider.tsx
  19. 0 21
      src/layouts/default/sider/index.less
  20. 54 0
      src/layouts/default/sider/index.vue
  21. 23 34
      src/layouts/default/sider/useLayoutSider.tsx
  22. 0 16
      src/layouts/default/useLayoutContext.ts
  23. 1 0
      src/locales/lang/en/component/app.ts
  24. 1 0
      src/locales/lang/zh_CN/component/app.ts
  25. 3 2
      src/logics/mitt/tabChange.ts
  26. 0 1
      src/main.ts
  27. 1 5
      src/router/helper/menuHelper.ts
  28. 7 15
      src/router/helper/routeHelper.ts
  29. 15 24
      src/router/menus/index.ts
  30. 0 1
      src/router/routes/modules/demo/iframe.ts
  31. 0 3
      src/router/types.d.ts
  32. 1 1
      src/settings/projectSetting.ts
  33. 1 1
      src/utils/is.ts
  34. 16 6
      src/views/sys/lock/LockPage.vue
  35. 38 41
      yarn.lock

+ 4 - 0
CHANGELOG.zh_CN.md

@@ -3,17 +3,21 @@
 ### ✨ Features
 
 - 移除左侧菜单搜索,新增顶部菜单搜索功能
+- layout 移动端适配。页面未适配
 
 ### ⚡ Performance Improvements
 
 - 异步引入组件
 - 优化整体结构
+- 替换菜单默认滚动条为滚动组件
+- 菜单性能优化
 
 ### 🎫 Chores
 
 - 返回顶部样式调整,避免遮住其他元素
 - 升级`ant-design-vue`到`2.0.0-rc.5`
 - 刷新按钮布局调整
+- `route.meta` 移除 `externalLink` 属性
 
 ### 🐛 Bug Fixes
 

+ 1 - 1
package.json

@@ -37,7 +37,7 @@
     "sortablejs": "^1.12.0",
     "vditor": "^3.7.2",
     "vue": "^3.0.4",
-    "vue-i18n": "^9.0.0-beta.12",
+    "vue-i18n": "^9.0.0-beta.13",
     "vue-router": "^4.0.1",
     "vue-types": "^3.0.1",
     "vuex": "^4.0.0-rc.2",

+ 2 - 4
src/components/Application/src/search/AppSearch.vue

@@ -1,13 +1,11 @@
 <template>
-  <div :class="prefixCls" v-if="getShowSearch" @click="handleSearch">
+  <div :class="prefixCls" v-if="getShowSearch" @click.stop="handleSearch">
     <Tooltip>
       <template #title> {{ t('component.app.search') }} </template>
       <SearchOutlined />
     </Tooltip>
 
-    <transition name="zoom-fade" mode="out-in">
-      <AppSearchModal @close="handleClose" v-if="showModal" />
-    </transition>
+    <AppSearchModal @close="handleClose" :visible="showModal" />
   </div>
 </template>
 <script lang="ts">

+ 119 - 45
src/components/Application/src/search/AppSearchModal.vue

@@ -1,48 +1,58 @@
 <template>
-  <div :class="prefixCls" @click.stop>
-    <ClickOutSide @clickOutside="handleClose">
-      <div :class="`${prefixCls}-content`">
-        <a-input
-          :class="`${prefixCls}-input`"
-          :placeholder="t('component.app.search')"
-          allow-clear
-          @change="handleSearch"
-        >
-          <template #prefix>
-            <SearchOutlined />
-          </template>
-        </a-input>
-        <div :class="`${prefixCls}-not-data`" v-show="getIsNotData">
-          {{ t('component.app.searchNotData') }}
-        </div>
-        <ul :class="`${prefixCls}-list`" v-show="!getIsNotData" ref="scrollWrap">
-          <li
-            :ref="setRefs(index)"
-            v-for="(item, index) in searchResult"
-            :key="item.path"
-            :data-index="index"
-            @mouseenter="handleMouseenter"
-            @click="handleEnter"
-            :class="[
-              `${prefixCls}-list__item`,
-              {
-                [`${prefixCls}-list__item--active`]: activeIndex === index,
-              },
-            ]"
-          >
-            <div :class="`${prefixCls}-list__item-icon`">
-              <g-icon :icon="item.icon || 'mdi:form-select'" :size="20" />
+  <Teleport to="body">
+    <transition name="zoom-fade" mode="out-in">
+      <div :class="getClass" @click.stop v-if="visible">
+        <ClickOutSide @clickOutside="handleClose">
+          <div :class="`${prefixCls}-content`">
+            <div :class="`${prefixCls}-input__wrapper`">
+              <a-input
+                :class="`${prefixCls}-input`"
+                :placeholder="t('component.app.search')"
+                allow-clear
+                @change="handleSearch"
+              >
+                <template #prefix>
+                  <SearchOutlined />
+                </template>
+              </a-input>
+              <span :class="`${prefixCls}-cancel`" @click="handleClose">{{
+                t('component.app.cancel')
+              }}</span>
             </div>
-            <div :class="`${prefixCls}-list__item-text`">{{ item.name }}</div>
-            <div :class="`${prefixCls}-list__item-enter`">
-              <g-icon icon="ant-design:enter-outlined" :size="20" />
+
+            <div :class="`${prefixCls}-not-data`" v-show="getIsNotData">
+              {{ t('component.app.searchNotData') }}
             </div>
-          </li>
-        </ul>
-        <AppSearchFooter />
+            <ul :class="`${prefixCls}-list`" v-show="!getIsNotData" ref="scrollWrap">
+              <li
+                :ref="setRefs(index)"
+                v-for="(item, index) in searchResult"
+                :key="item.path"
+                :data-index="index"
+                @mouseenter="handleMouseenter"
+                @click="handleEnter"
+                :class="[
+                  `${prefixCls}-list__item`,
+                  {
+                    [`${prefixCls}-list__item--active`]: activeIndex === index,
+                  },
+                ]"
+              >
+                <div :class="`${prefixCls}-list__item-icon`">
+                  <g-icon :icon="item.icon || 'mdi:form-select'" :size="20" />
+                </div>
+                <div :class="`${prefixCls}-list__item-text`">{{ item.name }}</div>
+                <div :class="`${prefixCls}-list__item-enter`">
+                  <g-icon icon="ant-design:enter-outlined" :size="20" />
+                </div>
+              </li>
+            </ul>
+            <AppSearchFooter />
+          </div>
+        </ClickOutSide>
       </div>
-    </ClickOutSide>
-  </div>
+    </transition>
+  </Teleport>
 </template>
 <script lang="ts">
   import { defineComponent, computed, unref, ref } from 'vue';
@@ -54,15 +64,20 @@
   import AppSearchFooter from './AppSearchFooter.vue';
   import { useI18n } from '/@/hooks/web/useI18n';
   import { ClickOutSide } from '/@/components/ClickOutSide';
+  import { useAppInject } from '/@/hooks/web/useAppInject';
   export default defineComponent({
     name: 'AppSearchModal',
     components: { SearchOutlined, ClickOutSide, AppSearchFooter },
     emits: ['close'],
+    props: {
+      visible: Boolean,
+    },
     setup(_, { emit }) {
       const scrollWrap = ref<ElRef>(null);
       const { prefixCls } = useDesign('app-search-modal');
       const { t } = useI18n();
       const [refs, setRefs] = useRefs();
+      const { getIsMobile } = useAppInject();
 
       const {
         handleSearch,
@@ -77,9 +92,19 @@
         return !keyword || unref(searchResult).length === 0;
       });
 
+      const getClass = computed(() => {
+        return [
+          prefixCls,
+          {
+            [`${prefixCls}--mobile`]: unref(getIsMobile),
+          },
+        ];
+      });
+
       return {
         t,
         prefixCls,
+        getClass,
         handleSearch,
         searchResult,
         activeIndex,
@@ -98,12 +123,12 @@
 <style lang="less" scoped>
   @import (reference) '../../../../design/index.less';
   @prefix-cls: ~'@{namespace}-app-search-modal';
-
+  @footer-prefix-cls: ~'@{namespace}-app-search-footer';
   .@{prefix-cls} {
     position: fixed;
     top: 0;
     left: 0;
-    z-index: 100;
+    z-index: 800;
     display: flex;
     width: 100%;
     height: 100%;
@@ -113,6 +138,43 @@
     justify-content: center;
     // backdrop-filter: blur(2px);
 
+    &--mobile {
+      padding: 0;
+
+      > div {
+        width: 100%;
+      }
+
+      .@{prefix-cls}-input {
+        width: calc(100% - 38px);
+      }
+
+      .@{prefix-cls}-cancel {
+        display: inline-block;
+      }
+
+      .@{prefix-cls}-content {
+        width: 100%;
+        height: 100%;
+        border-radius: 0;
+      }
+
+      .@{footer-prefix-cls} {
+        display: none;
+      }
+
+      .@{prefix-cls}-list {
+        height: calc(100% - 80px);
+        max-height: unset;
+
+        &__item {
+          &-enter {
+            opacity: 0 !important;
+          }
+        }
+      }
+    }
+
     &-content {
       position: relative;
       width: 532px;
@@ -124,10 +186,16 @@
       flex-direction: column;
     }
 
+    &-input__wrapper {
+      display: flex;
+      padding: 14px 14px 0 14px;
+      justify-content: space-between;
+      align-items: center;
+    }
+
     &-input {
-      width: calc(100% - 28px);
+      width: 100%;
       height: 56px;
-      margin: 14px 14px 0 14px;
       font-size: 1.5em;
       color: #1c1e21;
 
@@ -136,6 +204,12 @@
       }
     }
 
+    &-cancel {
+      display: none;
+      font-size: 1em;
+      color: #666;
+    }
+
     &-not-data {
       display: flex;
       width: 100%;

+ 1 - 1
src/components/Menu/src/BasicMenu.vue

@@ -151,7 +151,7 @@
         if (props.mode !== MenuModeEnum.HORIZONTAL) {
           setOpenKeys(path);
         }
-        if (unref(getIsTopMenu)) {
+        if (props.isHorizontal && unref(getSplit)) {
           const parentPath = await getCurrentParentPath(path);
           menuState.selectedKeys = [parentPath];
         } else {

+ 6 - 6
src/hooks/setting/useMenuSetting.ts

@@ -50,11 +50,7 @@ const getShowTopMenu = computed(() => {
 });
 
 const getShowHeaderTrigger = computed(() => {
-  if (
-    unref(getMenuType) === MenuTypeEnum.TOP_MENU ||
-    !unref(getShowMenu) ||
-    !unref(getMenuHidden)
-  ) {
+  if (unref(getMenuType) === MenuTypeEnum.TOP_MENU || !unref(getShowMenu) || unref(getMenuHidden)) {
     return false;
   }
 
@@ -79,7 +75,11 @@ const getMiniWidthNumber = computed(() => {
 });
 
 const getCalcContentWidth = computed(() => {
-  const width = unref(getIsTopMenu) || !unref(getShowMenu) ? 0 : unref(getRealWidth);
+  const width =
+    unref(getIsTopMenu) || !unref(getShowMenu) || (unref(getSplit) && unref(getMenuHidden))
+      ? 0
+      : unref(getRealWidth);
+
   return `calc(100% - ${unref(width)}px)`;
 });
 

+ 0 - 214
src/layouts/default/header/LayoutHeader.tsx

@@ -1,214 +0,0 @@
-import './index.less';
-
-import type { FunctionalComponent } from 'vue';
-import type { Component } from '/@/components/types';
-
-import { defineComponent, unref, computed } from 'vue';
-
-import { Layout, Tooltip, Badge } from 'ant-design-vue';
-import { AppLogo } from '/@/components/Application';
-import LayoutMenu from '../menu';
-import LockAction from './actions/LockAction';
-import LayoutTrigger from '../trigger/index.vue';
-import NoticeAction from './notice/NoticeActionItem.vue';
-import { LockOutlined, BugOutlined } from '@ant-design/icons-vue';
-
-import { AppSearch } from '/@/components/Application';
-import { useModal } from '/@/components/Modal';
-
-import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
-import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
-import { useRootSetting } from '/@/hooks/setting/useRootSetting';
-import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
-
-import { useRouter } from 'vue-router';
-
-import { errorStore } from '/@/store/modules/error';
-
-import { PageEnum } from '/@/enums/pageEnum';
-import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
-import { AppLocalePicker } from '/@/components/Application';
-import { useI18n } from '/@/hooks/web/useI18n';
-import { propTypes } from '/@/utils/propTypes';
-
-import { UserDropDown, LayoutBreadcrumb, FullScreen } from './components';
-import { useAppInject } from '/@/hooks/web/useAppInject';
-import { useDesign } from '../../../hooks/web/useDesign';
-interface TooltipItemProps {
-  title: string;
-}
-
-const TooltipItem: FunctionalComponent<TooltipItemProps> = (props, { slots }) => {
-  return (
-    <Tooltip>
-      {{
-        title: () => props.title,
-        default: () => slots.default?.(),
-      }}
-    </Tooltip>
-  );
-};
-
-export default defineComponent({
-  name: 'LayoutHeader',
-  props: {
-    fixed: propTypes.bool,
-  },
-  setup(props) {
-    const { t } = useI18n();
-    const { prefixCls } = useDesign('layout-header');
-    const { getShowTopMenu, getShowHeaderTrigger, getSplit } = useMenuSetting();
-    const { getShowLocale } = useLocaleSetting();
-    const { getUseErrorHandle } = useRootSetting();
-
-    const {
-      getHeaderTheme,
-      getUseLockPage,
-      getShowFullScreen,
-      getShowNotice,
-      getShowContent,
-      getShowBread,
-      getShowHeaderLogo,
-    } = useHeaderSetting();
-
-    const { push } = useRouter();
-    const [register, { openModal }] = useModal();
-    const { getIsMobile } = useAppInject();
-
-    const headerClass = computed(() => {
-      const theme = unref(getHeaderTheme);
-      return theme ? `${prefixCls}__header--${theme}` : '';
-    });
-
-    const getSplitType = computed(() => {
-      return unref(getSplit) ? MenuSplitTyeEnum.TOP : MenuSplitTyeEnum.NONE;
-    });
-
-    const getMenuMode = computed(() => {
-      return unref(getSplit) ? MenuModeEnum.HORIZONTAL : null;
-    });
-
-    function handleToErrorList() {
-      push(PageEnum.ERROR_LOG_PAGE).then(() => {
-        errorStore.commitErrorListCountState(0);
-      });
-    }
-
-    function handleLockPage() {
-      openModal(true);
-    }
-
-    function renderHeaderLeft() {
-      return (
-        <>
-          {unref(getShowContent) && (
-            <div class={`${prefixCls}__left`}>
-              {unref(getShowHeaderTrigger) && (
-                <LayoutTrigger theme={unref(getHeaderTheme)} sider={false} />
-              )}
-              {unref(getShowBread) && !unref(getIsMobile) && (
-                <LayoutBreadcrumb theme={unref(getHeaderTheme)} />
-              )}
-            </div>
-          )}
-        </>
-      );
-    }
-
-    function renderHeaderContent() {
-      return (
-        <div class={`${prefixCls}__content`}>
-          {unref(getShowTopMenu) && !unref(getIsMobile) && (
-            <div class={[`${prefixCls}__menu `]}>
-              {/* <div class={[`layout-header__menu `]}> */}
-              <LayoutMenu
-                isHorizontal={true}
-                // class={`justify-${unref(getTopMenuAlign)}`}
-                theme={unref(getHeaderTheme)}
-                splitType={unref(getSplitType)}
-                menuMode={unref(getMenuMode)}
-              />
-            </div>
-          )}
-        </div>
-      );
-    }
-
-    function renderActionDefault(Comp: Component | any, event: Fn) {
-      return (
-        <div class={`${prefixCls}__action-item`} onClick={event}>
-          <Comp class={`${prefixCls}__action-icon`} />
-        </div>
-      );
-    }
-
-    function renderAction() {
-      return (
-        <div class={`${prefixCls}__action`}>
-          {!unref(getIsMobile) && <AppSearch class={`${prefixCls}__action-item`} />}
-
-          {unref(getUseErrorHandle) && !unref(getIsMobile) && (
-            <TooltipItem title={t('layout.header.tooltipErrorLog')}>
-              {() => (
-                <Badge
-                  count={errorStore.getErrorListCountState}
-                  offset={[0, 10]}
-                  dot
-                  overflowCount={99}
-                >
-                  {() => renderActionDefault(BugOutlined, handleToErrorList)}
-                </Badge>
-              )}
-            </TooltipItem>
-          )}
-
-          {unref(getUseLockPage) && !unref(getIsMobile) && (
-            <TooltipItem title={t('layout.header.tooltipLock')}>
-              {() => renderActionDefault(LockOutlined, handleLockPage)}
-            </TooltipItem>
-          )}
-
-          {unref(getShowNotice) && !unref(getIsMobile) && (
-            <TooltipItem title={t('layout.header.tooltipNotify')}>
-              {() => <NoticeAction />}
-            </TooltipItem>
-          )}
-
-          {unref(getShowFullScreen) && !unref(getIsMobile) && <FullScreen />}
-
-          <UserDropDown theme={unref(getHeaderTheme)} />
-
-          {unref(getShowLocale) && (
-            <AppLocalePicker
-              reload={true}
-              showText={false}
-              class={`${prefixCls}__action-item locale`}
-            />
-          )}
-        </div>
-      );
-    }
-
-    function renderHeaderDefault() {
-      return (
-        <>
-          {unref(getShowHeaderLogo) && (
-            <AppLogo class={`${prefixCls}__logo`} theme={unref(getHeaderTheme)} />
-          )}
-          {renderHeaderLeft()}
-          {renderHeaderContent()}
-          {renderAction()}
-          <LockAction onRegister={register} />
-        </>
-      );
-    }
-
-    return () => {
-      return (
-        <Layout.Header class={[prefixCls, unref(headerClass), { fixed: props.fixed }]}>
-          {() => renderHeaderDefault()}
-        </Layout.Header>
-      );
-    };
-  },
-});

+ 0 - 18
src/layouts/default/header/LayoutMultipleHeader.less

@@ -1,18 +0,0 @@
-@import (reference) '../../../design/index.less';
-
-.multiple-tab-header {
-  margin-left: 1px;
-  transition: width 0.2s;
-  flex: 0 0 auto;
-
-  &.dark {
-    margin-left: 0;
-  }
-
-  &.fixed {
-    position: fixed;
-    top: 0;
-    z-index: @multiple-tab-fixed-z-index;
-    width: 100%;
-  }
-}

+ 0 - 125
src/layouts/default/header/LayoutMultipleHeader.tsx

@@ -1,125 +0,0 @@
-import './LayoutMultipleHeader.less';
-
-import { defineComponent, unref, computed, ref, watch, nextTick, CSSProperties } from 'vue';
-
-import LayoutHeader from './index.vue';
-import MultipleTabs from '../tabs/index.vue';
-
-import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
-import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
-import { useFullContent } from '/@/hooks/web/useFullContent';
-import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
-import { useLayoutContext } from '../useLayoutContext';
-import { useAppInject } from '/@/hooks/web/useAppInject';
-
-export default defineComponent({
-  name: 'LayoutMultipleHeader',
-  setup() {
-    const placeholderHeightRef = ref(0);
-    const fullHeaderHeightRef = ref(0);
-    const headerElRef = ref<ComponentRef>(null);
-    const tabElRef = ref<ComponentRef>(null);
-
-    const injectValue = useLayoutContext();
-
-    const { getCalcContentWidth, getSplit } = useMenuSetting();
-    const { getIsMobile } = useAppInject();
-    const {
-      getFixed,
-      getShowInsetHeaderRef,
-      getShowFullHeaderRef,
-      getShowHeader,
-      getUnFixedAndFull,
-      getHeaderTheme,
-    } = useHeaderSetting();
-
-    const { getFullContent } = useFullContent();
-
-    const { getShowMultipleTab } = useMultipleTabSetting();
-
-    const getShowTabs = computed(() => {
-      return unref(getShowMultipleTab) && !unref(getFullContent);
-    });
-
-    const getPlaceholderDomStyle = computed(
-      (): CSSProperties => {
-        return {
-          height: `${unref(placeholderHeightRef)}px`,
-        };
-      }
-    );
-
-    const getIsShowPlaceholderDom = computed(() => {
-      return unref(getFixed) || unref(getShowFullHeaderRef);
-    });
-
-    const getWrapStyle = computed(
-      (): CSSProperties => {
-        const style: CSSProperties = {};
-        if (unref(getFixed)) {
-          style.width = unref(getIsMobile) ? '100%' : unref(getCalcContentWidth);
-        }
-        if (unref(getShowFullHeaderRef)) {
-          style.top = `${unref(fullHeaderHeightRef)}px`;
-        }
-        return style;
-      }
-    );
-
-    const getIsFixed = computed(() => {
-      return unref(getFixed) || unref(getShowFullHeaderRef);
-    });
-
-    watch(
-      () => [
-        unref(getFixed),
-        unref(getShowFullHeaderRef),
-        unref(getShowHeader),
-        unref(getShowMultipleTab),
-      ],
-      () => {
-        if (unref(getUnFixedAndFull)) return;
-        nextTick(() => {
-          const headerEl = unref(headerElRef)?.$el;
-          const tabEl = unref(tabElRef)?.$el;
-          const fullHeaderEl = unref(injectValue.fullHeader)?.$el;
-
-          let height = 0;
-          if (headerEl && !unref(getShowFullHeaderRef) && !unref(getSplit)) {
-            height += headerEl.offsetHeight;
-          }
-
-          if (tabEl) {
-            height += tabEl.offsetHeight;
-          }
-
-          if (fullHeaderEl && unref(getShowFullHeaderRef)) {
-            const fullHeaderHeight = fullHeaderEl.offsetHeight;
-            height += fullHeaderHeight;
-            fullHeaderHeightRef.value = fullHeaderHeight;
-          }
-
-          placeholderHeightRef.value = height;
-        });
-      },
-      {
-        immediate: true,
-      }
-    );
-
-    return () => {
-      return (
-        <>
-          {unref(getIsShowPlaceholderDom) && <div style={unref(getPlaceholderDomStyle)} />}
-          <div
-            style={unref(getWrapStyle)}
-            class={['multiple-tab-header', unref(getHeaderTheme), { fixed: unref(getIsFixed) }]}
-          >
-            {unref(getShowInsetHeaderRef) && <LayoutHeader ref={headerElRef} />}
-            {unref(getShowTabs) && <MultipleTabs ref={tabElRef} />}
-          </div>
-        </>
-      );
-    };
-  },
-});

+ 124 - 0
src/layouts/default/header/MultipleHeader.vue

@@ -0,0 +1,124 @@
+<template>
+  <div :style="getPlaceholderDomStyle" v-if="getIsShowPlaceholderDom" />
+  <div :style="getWrapStyle" :class="getClass">
+    <LayoutHeader v-if="getShowInsetHeaderRef" />
+    <MultipleTabs v-if="getShowTabs" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, unref, computed, CSSProperties } from 'vue';
+
+  import LayoutHeader from './index.vue';
+  import MultipleTabs from '../tabs/index.vue';
+
+  import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
+  import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
+  import { useFullContent } from '/@/hooks/web/useFullContent';
+  import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
+  import { useAppInject } from '/@/hooks/web/useAppInject';
+  import { useDesign } from '/@/hooks/web/useDesign';
+
+  const HEADER_HEIGHT = 48;
+
+  const TABS_HEIGHT = 32;
+  export default defineComponent({
+    name: 'LayoutMultipleHeader',
+    components: { LayoutHeader, MultipleTabs },
+    setup() {
+      const { prefixCls } = useDesign('layout-multiple-header');
+
+      const { getCalcContentWidth, getSplit } = useMenuSetting();
+      const { getIsMobile } = useAppInject();
+      const {
+        getFixed,
+        getShowInsetHeaderRef,
+        getShowFullHeaderRef,
+        getHeaderTheme,
+      } = useHeaderSetting();
+
+      const { getFullContent } = useFullContent();
+
+      const { getShowMultipleTab } = useMultipleTabSetting();
+
+      const getShowTabs = computed(() => {
+        return unref(getShowMultipleTab) && !unref(getFullContent);
+      });
+
+      const getIsShowPlaceholderDom = computed(() => {
+        return unref(getFixed) || unref(getShowFullHeaderRef);
+      });
+
+      const getWrapStyle = computed(
+        (): CSSProperties => {
+          const style: CSSProperties = {};
+          if (unref(getFixed)) {
+            style.width = unref(getIsMobile) ? '100%' : unref(getCalcContentWidth);
+          }
+          if (unref(getShowFullHeaderRef)) {
+            style.top = `${HEADER_HEIGHT}px`;
+          }
+          return style;
+        }
+      );
+
+      const getIsFixed = computed(() => {
+        return unref(getFixed) || unref(getShowFullHeaderRef);
+      });
+
+      const getPlaceholderDomStyle = computed(
+        (): CSSProperties => {
+          let height = 0;
+          if (unref(getShowFullHeaderRef) || !unref(getSplit)) {
+            height += HEADER_HEIGHT;
+          }
+          if (unref(getShowMultipleTab)) {
+            height += TABS_HEIGHT;
+          }
+          return {
+            height: `${height}px`,
+          };
+        }
+      );
+
+      const getClass = computed(() => {
+        return [
+          prefixCls,
+          `${prefixCls}--${unref(getHeaderTheme)}`,
+          { [`${prefixCls}--fixed`]: unref(getIsFixed) },
+        ];
+      });
+
+      return {
+        getClass,
+        prefixCls,
+        getPlaceholderDomStyle,
+        getIsFixed,
+        getWrapStyle,
+        getIsShowPlaceholderDom,
+        getShowTabs,
+        getShowInsetHeaderRef,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  @import (reference) '../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-layout-multiple-header';
+
+  .@{prefix-cls} {
+    margin-left: 1px;
+    transition: width 0.2s;
+    flex: 0 0 auto;
+
+    &--dark {
+      margin-left: 0;
+    }
+
+    &--fixed {
+      position: fixed;
+      top: 0;
+      z-index: @multiple-tab-fixed-z-index;
+      width: 100%;
+    }
+  }
+</style>

+ 27 - 1
src/layouts/default/header/index.less

@@ -2,6 +2,8 @@
 @header-trigger-prefix-cls: ~'@{namespace}-layout-header-trigger';
 @header-prefix-cls: ~'@{namespace}-layout-header';
 @locale-prefix-cls: ~'@{namespace}-app-locale-picker';
+@breadcrumb-prefix-cls: ~'@{namespace}-layout-breadcrumb';
+@logo-prefix-cls: ~'@{namespace}-app-logo';
 
 .@{header-prefix-cls} {
   display: flex;
@@ -14,6 +16,30 @@
   align-items: center;
   justify-content: space-between;
 
+  &--mobile {
+    .@{breadcrumb-prefix-cls},
+    .error-action,
+    .notify-item,
+    .fullscreen-item {
+      display: none;
+    }
+
+    .@{logo-prefix-cls} {
+      min-width: unset;
+      padding-right: 0;
+
+      &__title {
+        display: none;
+      }
+    }
+    .@{header-trigger-prefix-cls} {
+      padding: 0 4px 0 8px !important;
+    }
+    .@{header-prefix-cls}-action {
+      padding-right: 4px;
+    }
+  }
+
   &--fixed {
     position: fixed;
     top: 0;
@@ -78,7 +104,7 @@
 
   &-action {
     display: flex;
-    min-width: 200px;
+    min-width: 180px;
     padding-right: 12px;
     align-items: center;
 

+ 18 - 13
src/layouts/default/header/index.vue

@@ -3,17 +3,17 @@
     <!-- left start -->
     <div :class="`${prefixCls}-left`">
       <!-- logo -->
-      <AppLogo v-if="getShowHeaderLogo" :class="`${prefixCls}-logo`" :theme="getHeaderTheme" />
-
-      <LayoutTrigger
-        v-if="getShowContent && getShowHeaderTrigger"
+      <AppLogo
+        v-if="getShowHeaderLogo || getIsMobile"
+        :class="`${prefixCls}-logo`"
         :theme="getHeaderTheme"
-        :sider="false"
       />
-      <LayoutBreadcrumb
-        v-if="getShowContent && getShowBread && !getIsMobile"
+      <LayoutTrigger
+        v-if="(getShowContent && getShowHeaderTrigger && !getSplit) || getIsMobile"
         :theme="getHeaderTheme"
+        :sider="false"
       />
+      <LayoutBreadcrumb v-if="getShowContent && getShowBread" :theme="getHeaderTheme" />
     </div>
     <!-- left end -->
 
@@ -30,15 +30,15 @@
 
     <!-- action  -->
     <div :class="`${prefixCls}-action`">
-      <AppSearch v-if="!getIsMobile" :class="`${prefixCls}-action__item`" />
+      <AppSearch :class="`${prefixCls}-action__item `" />
 
-      <ErrorAction v-if="getUseErrorHandle && !getIsMobile" :class="`${prefixCls}-action__item`" />
+      <ErrorAction v-if="getUseErrorHandle" :class="`${prefixCls}-action__item error-action`" />
 
-      <LockItem v-if="getUseLockPage && !getIsMobile" :class="`${prefixCls}-action__item`" />
+      <LockItem v-if="getUseLockPage" :class="`${prefixCls}-action__item lock-item`" />
 
-      <Notify v-if="getShowNotice && !getIsMobile" :class="`${prefixCls}-action__item`" />
+      <Notify v-if="getShowNotice" :class="`${prefixCls}-action__item notify-item`" />
 
-      <FullScreen v-if="getShowFullScreen && !getIsMobile" :class="`${prefixCls}-action__item`" />
+      <FullScreen v-if="getShowFullScreen" :class="`${prefixCls}-action__item fullscreen-item`" />
 
       <UserDropDown :theme="getHeaderTheme" />
 
@@ -123,7 +123,11 @@
         const theme = unref(getHeaderTheme);
         return [
           prefixCls,
-          { [`${prefixCls}--fixed`]: props.fixed, [`${prefixCls}--${theme}`]: theme },
+          {
+            [`${prefixCls}--fixed`]: props.fixed,
+            [`${prefixCls}--mobile`]: unref(getIsMobile),
+            [`${prefixCls}--${theme}`]: theme,
+          },
         ];
       });
 
@@ -145,6 +149,7 @@
         getShowBread,
         getShowContent,
         getSplitType,
+        getSplit,
         getMenuMode,
         getShowTopMenu,
         getShowLocale,

+ 8 - 10
src/layouts/default/index.vue

@@ -1,9 +1,9 @@
 <template>
   <Layout :class="prefixCls">
     <LayoutFeatures />
-    <LayoutHeader fixed ref="headerRef" v-if="getShowFullHeaderRef" />
+    <LayoutHeader fixed v-if="getShowFullHeaderRef" />
     <Layout>
-      <LayoutSideBar v-if="getShowSidebar" />
+      <LayoutSideBar v-if="getShowSidebar || getIsMobile" />
       <Layout :class="`${prefixCls}__main`">
         <LayoutMultipleHeader />
         <LayoutContent />
@@ -14,21 +14,21 @@
 </template>
 
 <script lang="ts">
-  import { defineComponent, ref } from 'vue';
+  import { defineComponent } from 'vue';
   import { Layout } from 'ant-design-vue';
   import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
 
   import LayoutHeader from './header/index.vue';
   import LayoutContent from './content/index.vue';
-  import LayoutSideBar from './sider';
-  import LayoutMultipleHeader from './header/LayoutMultipleHeader';
+  import LayoutSideBar from './sider/index.vue';
+  import LayoutMultipleHeader from './header/MultipleHeader.vue';
 
   import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
   import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
   import { useDesign } from '/@/hooks/web/useDesign';
-  import { createLayoutContext } from './useLayoutContext';
 
   import { registerGlobComp } from '/@/components/registerGlobComp';
+  import { useAppInject } from '/@/hooks/web/useAppInject';
 
   export default defineComponent({
     name: 'DefaultLayout',
@@ -47,11 +47,9 @@
       // default layout It is loaded after login. So it won’t be packaged to the first screen
       registerGlobComp();
 
-      const headerRef = ref<ComponentRef>(null);
-
       const { prefixCls } = useDesign('default-layout');
 
-      createLayoutContext({ fullHeader: headerRef });
+      const { getIsMobile } = useAppInject();
 
       const { getShowFullHeaderRef } = useHeaderSetting();
 
@@ -60,8 +58,8 @@
       return {
         getShowFullHeaderRef,
         getShowSidebar,
-        headerRef,
         prefixCls,
+        getIsMobile,
       };
     },
   });

+ 13 - 2
src/layouts/default/menu/index.less

@@ -1,7 +1,10 @@
 @import (reference) '../../../design/index.less';
 
-.layout-menu {
-  &__logo {
+@prefix-cls: ~'@{namespace}-layout-menu';
+@logo-prefix-cls: ~'@{namespace}-app-logo';
+
+.@{prefix-cls} {
+  &-logo {
     height: @header-height;
     padding: 10px 4px 10px 10px;
 
@@ -10,4 +13,12 @@
       height: @logo-width;
     }
   }
+
+  &--mobile {
+    .@{logo-prefix-cls} {
+      &__title {
+        opacity: 1;
+      }
+    }
+  }
 }

+ 23 - 7
src/layouts/default/menu/index.tsx

@@ -1,8 +1,8 @@
 import './index.less';
 
-import { PropType, toRef } from 'vue';
+import type { PropType, CSSProperties } from 'vue';
 
-import { computed, defineComponent, unref } from 'vue';
+import { computed, defineComponent, unref, toRef } from 'vue';
 import { BasicMenu } from '/@/components/Menu';
 import { AppLogo } from '/@/components/Application';
 
@@ -17,7 +17,8 @@ import { openWindow } from '/@/utils';
 import { propTypes } from '/@/utils/propTypes';
 import { isUrl } from '/@/utils/is';
 import { useRootSetting } from '/@/hooks/setting/useRootSetting';
-import { CSSProperties } from 'vue';
+import { useAppInject } from '/@/hooks/web/useAppInject';
+import { useDesign } from '/@/hooks/web/useDesign';
 
 export default defineComponent({
   name: 'LayoutMenu',
@@ -50,9 +51,15 @@ export default defineComponent({
     } = useMenuSetting();
     const { getShowLogo } = useRootSetting();
 
+    const { prefixCls } = useDesign('layout-menu');
+
     const { menusRef } = useSplitMenu(toRef(props, 'splitType'));
 
-    const getComputedMenuMode = computed(() => props.menuMode || unref(getMenuMode));
+    const { getIsMobile } = useAppInject();
+
+    const getComputedMenuMode = computed(() =>
+      unref(getIsMobile) ? MenuModeEnum.INLINE : props.menuMode || unref(getMenuMode)
+    );
 
     const getComputedMenuTheme = computed(() => props.theme || unref(getMenuTheme));
 
@@ -69,6 +76,16 @@ export default defineComponent({
         };
       }
     );
+
+    const getLogoClass = computed(() => {
+      return [
+        `${prefixCls}-logo`,
+        unref(getComputedMenuTheme),
+        {
+          [`${prefixCls}--mobile`]: unref(getIsMobile),
+        },
+      ];
+    });
     /**
      * click menu
      * @param menu
@@ -91,12 +108,12 @@ export default defineComponent({
     }
 
     function renderHeader() {
-      if (!unref(getIsShowLogo)) return null;
+      if (!unref(getIsShowLogo) && !unref(getIsMobile)) return null;
 
       return (
         <AppLogo
           showTitle={!unref(getCollapsed)}
-          class={[`layout-menu__logo`, unref(getComputedMenuTheme)]}
+          class={unref(getLogoClass)}
           theme={unref(getComputedMenuTheme)}
         />
       );
@@ -128,7 +145,6 @@ export default defineComponent({
           ) : (
             renderMenu()
           )}
-          ;
         </>
       );
     };

+ 7 - 5
src/layouts/default/menu/useLayoutMenu.ts

@@ -10,11 +10,13 @@ import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
 
 import { getChildrenMenus, getCurrentParentPath, getMenus, getShallowMenus } from '/@/router/menus';
 import { permissionStore } from '/@/store/modules/permission';
+import { useAppInject } from '/@/hooks/web/useAppInject';
 
 export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
   // Menu array
   const menusRef = ref<Menu[]>([]);
   const { currentRoute } = useRouter();
+  const { getIsMobile } = useAppInject();
   const { setMenuSetting, getIsHorizontal, getSplit } = useMenuSetting();
 
   const [throttleHandleSplitLeftMenu] = useThrottle(handleSplitLeftMenu, 50);
@@ -36,7 +38,7 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
   watch(
     [() => unref(currentRoute).path, () => unref(splitType)],
     async ([path]: [string, MenuSplitTyeEnum]) => {
-      if (unref(splitNotLeft)) return;
+      if (unref(splitNotLeft) || unref(getIsMobile)) return;
 
       const parentPath = await getCurrentParentPath(path);
       parentPath && throttleHandleSplitLeftMenu(parentPath);
@@ -65,24 +67,24 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
 
   // Handle left menu split
   async function handleSplitLeftMenu(parentPath: string) {
-    if (unref(getSplitLeft)) return;
+    if (unref(getSplitLeft) || unref(getIsMobile)) return;
 
     // spilt mode left
     const children = await getChildrenMenus(parentPath);
     if (!children) {
-      setMenuSetting({ hidden: false });
+      setMenuSetting({ hidden: true });
       menusRef.value = [];
       return;
     }
 
-    setMenuSetting({ hidden: true });
+    setMenuSetting({ hidden: false });
     menusRef.value = children;
   }
 
   // get menus
   async function genMenus() {
     // normal mode
-    if (unref(normalType)) {
+    if (unref(normalType) || unref(getIsMobile)) {
       menusRef.value = await getMenus();
       return;
     }

+ 67 - 0
src/layouts/default/sider/DragBar.vue

@@ -0,0 +1,67 @@
+<template>
+  <div :class="getClass" :style="getDragBarStyle" />
+</template>
+<script lang="ts">
+  import { defineComponent, computed, unref } from 'vue';
+
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
+
+  export default defineComponent({
+    name: 'DargBar',
+    props: {
+      mobile: Boolean,
+    },
+    setup(props) {
+      const { getMiniWidthNumber, getCollapsed, getCanDrag } = useMenuSetting();
+
+      const { prefixCls } = useDesign('darg-bar');
+      const getDragBarStyle = computed(() => {
+        if (unref(getCollapsed)) {
+          return { left: `${unref(getMiniWidthNumber)}px` };
+        }
+        return {};
+      });
+
+      const getClass = computed(() => {
+        return [
+          prefixCls,
+          {
+            [`${prefixCls}--hide`]: !unref(getCanDrag) || props.mobile,
+          },
+        ];
+      });
+
+      return {
+        prefixCls,
+        getDragBarStyle,
+        getClass,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  @import (reference) '../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-darg-bar';
+
+  .@{prefix-cls} {
+    position: absolute;
+    top: 0;
+    right: -2px;
+    z-index: @side-drag-z-index;
+    width: 2px;
+    height: 100%;
+    cursor: col-resize;
+    border-top: none;
+    border-bottom: none;
+
+    &--hide {
+      display: none;
+    }
+
+    &:hover {
+      background: @primary-color;
+      box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.15);
+    }
+  }
+</style>

+ 8 - 7
src/layouts/default/sider/index.tsx → src/layouts/default/sider/LayoutSider.tsx

@@ -12,6 +12,7 @@ import { useTrigger, useDragLine, useSiderEvent } from './useLayoutSider';
 import { useAppInject } from '/@/hooks/web/useAppInject';
 import { useDesign } from '/@/hooks/web/useDesign';
 
+import DragBar from './DragBar.vue';
 export default defineComponent({
   name: 'LayoutSideBar',
   setup() {
@@ -31,11 +32,11 @@ export default defineComponent({
 
     const { prefixCls } = useDesign('layout-sideBar');
 
-    const { getTriggerAttr, getTriggerSlot } = useTrigger();
-
     const { getIsMobile } = useAppInject();
 
-    const { renderDragLine } = useDragLine(sideRef, dragBarRef);
+    const { getTriggerAttr, getTriggerSlot } = useTrigger(getIsMobile);
+
+    useDragLine(sideRef, dragBarRef);
 
     const { getCollapsedWidth, onBreakpointChange, onCollapseChange } = useSiderEvent();
 
@@ -48,7 +49,7 @@ export default defineComponent({
     });
 
     const showClassSideBarRef = computed(() => {
-      return unref(getSplit) ? unref(getMenuHidden) : true;
+      return unref(getSplit) ? !unref(getMenuHidden) : true;
     });
 
     const getSiderClass = computed(() => {
@@ -57,7 +58,7 @@ export default defineComponent({
         {
           [`${prefixCls}--fixed`]: unref(getMenuFixed),
           hidden: !unref(showClassSideBarRef),
-          [`${prefixCls}--mix`]: unref(getIsMixMode),
+          [`${prefixCls}--mix`]: unref(getIsMixMode) && !unref(getIsMobile),
         },
       ];
     });
@@ -84,7 +85,7 @@ export default defineComponent({
             menuMode={unref(getMode)}
             splitType={unref(getSplitType)}
           />
-          {renderDragLine()}
+          <DragBar ref={dragBarRef} />
         </>
       );
     }
@@ -101,7 +102,7 @@ export default defineComponent({
             collapsible
             class={unref(getSiderClass)}
             width={unref(getMenuWidth)}
-            collapsed={unref(getCollapsed)}
+            collapsed={unref(getIsMobile) ? false : unref(getCollapsed)}
             collapsedWidth={unref(getCollapsedWidth)}
             theme={unref(getMenuTheme)}
             onCollapse={onCollapseChange}

+ 0 - 21
src/layouts/default/sider/index.less

@@ -44,27 +44,6 @@
     z-index: 10;
   }
 
-  &__darg-bar {
-    position: absolute;
-    top: 0;
-    right: -2px;
-    z-index: @side-drag-z-index;
-    width: 2px;
-    height: 100%;
-    cursor: col-resize;
-    border-top: none;
-    border-bottom: none;
-
-    &.hide {
-      display: none;
-    }
-
-    &:hover {
-      background: @primary-color;
-      box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.15);
-    }
-  }
-
   & .ant-layout-sider-trigger {
     height: 36px;
     line-height: 36px;

+ 54 - 0
src/layouts/default/sider/index.vue

@@ -0,0 +1,54 @@
+<template>
+  <Drawer
+    v-if="getIsMobile"
+    placement="left"
+    :class="prefixCls"
+    :width="getMenuWidth"
+    :getContainer="null"
+    :visible="!getCollapsed"
+    @close="handleClose"
+  >
+    <Sider />
+  </Drawer>
+  <Sider v-else />
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+
+  import Sider from './LayoutSider';
+  import { Drawer } from 'ant-design-vue';
+  import { useAppInject } from '/@/hooks/web/useAppInject';
+  import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
+  import { useDesign } from '/@/hooks/web/useDesign';
+  export default defineComponent({
+    name: 'SiderWrapper',
+    components: { Sider, Drawer },
+    setup() {
+      const { prefixCls } = useDesign('layout-sider-wrapper');
+      const { getIsMobile } = useAppInject();
+      const { setMenuSetting, getCollapsed, getMenuWidth } = useMenuSetting();
+
+      function handleClose() {
+        setMenuSetting({
+          collapsed: true,
+        });
+      }
+
+      return { prefixCls, getIsMobile, getCollapsed, handleClose, getMenuWidth };
+    },
+  });
+</script>
+<style lang="less">
+  @import (reference) '../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-layout-sider-wrapper';
+  .@{prefix-cls} {
+    .ant-drawer-body {
+      height: 100vh;
+      padding: 0;
+    }
+
+    .ant-drawer-header-no-title {
+      display: none;
+    }
+  }
+</style>

+ 23 - 34
src/layouts/default/sider/useLayoutSider.tsx

@@ -42,12 +42,17 @@ export function useSiderEvent() {
 /**
  * Handle related operations of menu folding
  */
-export function useTrigger() {
-  const { getTrigger } = useMenuSetting();
+export function useTrigger(getIsMobile: Ref<boolean>) {
+  const { getTrigger, getSplit } = useMenuSetting();
 
   const showTrigger = computed(() => {
     const trigger = unref(getTrigger);
-    return trigger !== TriggerEnum.NONE && trigger === TriggerEnum.FOOTER;
+
+    return (
+      trigger !== TriggerEnum.NONE &&
+      !unref(getIsMobile) &&
+      (trigger === TriggerEnum.FOOTER || unref(getSplit))
+    );
   });
 
   const getTriggerAttr = computed(() => {
@@ -77,14 +82,7 @@ export function useTrigger() {
  * @param dragBarRef
  */
 export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) {
-  const { getMiniWidthNumber, getCollapsed, setMenuSetting, getCanDrag } = useMenuSetting();
-
-  const getDragBarStyle = computed(() => {
-    if (unref(getCollapsed)) {
-      return { left: `${unref(getMiniWidthNumber)}px` };
-    }
-    return {};
-  });
+  const { getMiniWidthNumber, getCollapsed, setMenuSetting } = useMenuSetting();
 
   onMounted(() => {
     nextTick(() => {
@@ -93,16 +91,6 @@ export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) {
     });
   });
 
-  function renderDragLine() {
-    return (
-      <div
-        class={[`layout-sidebar__darg-bar`, { hide: !unref(getCanDrag) }]}
-        style={unref(getDragBarStyle)}
-        ref={dragBarRef}
-      />
-    );
-  }
-
   function handleMouseMove(ele: HTMLElement, wrap: HTMLElement, clientX: number) {
     document.onmousemove = function (innerE) {
       let iT = (ele as any).left + (innerE.clientX - clientX);
@@ -138,21 +126,22 @@ export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) {
   }
 
   function changeWrapWidth() {
-    const ele = unref(dragBarRef) as any;
+    const ele = unref(dragBarRef)?.$el;
+    if (!ele) {
+      return;
+    }
     const side = unref(siderRef);
-
     const wrap = (side || {}).$el;
-    ele &&
-      (ele.onmousedown = (e: any) => {
-        wrap.style.transition = 'unset';
-        const clientX = e?.clientX;
-        ele.left = ele.offsetLeft;
-        handleMouseMove(ele, wrap, clientX);
-        removeMouseup(ele);
-        ele.setCapture?.();
-        return false;
-      });
+    ele.onmousedown = (e: any) => {
+      wrap.style.transition = 'unset';
+      const clientX = e?.clientX;
+      ele.left = ele.offsetLeft;
+      handleMouseMove(ele, wrap, clientX);
+      removeMouseup(ele);
+      ele.setCapture?.();
+      return false;
+    };
   }
 
-  return { renderDragLine };
+  return {};
 }

+ 0 - 16
src/layouts/default/useLayoutContext.ts

@@ -1,16 +0,0 @@
-import { InjectionKey, Ref } from 'vue';
-import { createContext, useContext } from '/@/hooks/core/useContext';
-
-export interface LayoutContextProps {
-  fullHeader: Ref<ComponentRef>;
-}
-
-const key: InjectionKey<LayoutContextProps> = Symbol();
-
-export function createLayoutContext(context: LayoutContextProps) {
-  return createContext<LayoutContextProps>(context, key);
-}
-
-export function useLayoutContext() {
-  return useContext<LayoutContextProps>(key);
-}

+ 1 - 0
src/locales/lang/en/component/app.ts

@@ -1,5 +1,6 @@
 export default {
   search: 'Search',
+  cancel: 'Cancel',
   searchNotData: 'No search results yet',
   toSearch: 'to search',
   toNavigate: 'to navigate',

+ 1 - 0
src/locales/lang/zh_CN/component/app.ts

@@ -1,5 +1,6 @@
 export default {
   search: '搜索',
+  cancel: '取消',
   searchNotData: '暂无搜索结果',
   toSearch: '确认',
   toNavigate: '切换',

+ 3 - 2
src/logics/mitt/tabChange.ts

@@ -13,8 +13,9 @@ const key = Symbol();
 let lastChangeTab: RouteLocationNormalized;
 
 export function setLastChangeTab(lastChangeRoute: RouteLocationNormalized) {
-  mitt.emit(key, getRoute(lastChangeRoute));
-  lastChangeTab = getRoute(lastChangeRoute);
+  const r = getRoute(lastChangeRoute);
+  mitt.emit(key, r);
+  lastChangeTab = r;
 }
 
 export function listenerLastChangeTab(

+ 0 - 1
src/main.ts

@@ -51,6 +51,5 @@ if (isDevMode()) {
 if (isProdMode() && isUseMock()) {
   setupProdMockServer();
 }
-
 // Used to share app instances in other modules
 setApp(app);

+ 1 - 5
src/router/helper/menuHelper.ts

@@ -1,7 +1,7 @@
 import { AppRouteModule } from '/@/router/types.d';
 import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types';
 
-import { findPath, forEach, treeMap, treeToList } from '/@/utils/helper/treeHelper';
+import { findPath, forEach, treeMap } from '/@/utils/helper/treeHelper';
 import { cloneDeep } from 'lodash-es';
 import { isUrl } from '/@/utils/is';
 
@@ -10,10 +10,6 @@ export function getAllParentPath(treeData: any[], path: string) {
   return (menuList || []).map((item) => item.path);
 }
 
-export function flatMenus(menus: Menu[]) {
-  return treeToList(menus);
-}
-
 // 拼接父级路径
 function joinParentPath(list: any, node: any) {
   let allPaths = getAllParentPath(list, node.path);

+ 7 - 15
src/router/helper/routeHelper.ts

@@ -5,6 +5,10 @@ import { getParentLayout, LAYOUT } from '/@/router/constant';
 import dynamicImport from './dynamicImport';
 import { cloneDeep } from 'lodash-es';
 
+export type LayoutMapKey = 'LAYOUT';
+
+const LayoutMap = new Map<LayoutMapKey, () => Promise<typeof import('*.vue')>>();
+
 // 动态引入
 function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
   if (!routes) return;
@@ -20,16 +24,14 @@ function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
   });
 }
 
-function getLayoutComp(comp: string) {
-  return comp === 'LAYOUT' ? LAYOUT : '';
-}
-
 // Turn background objects into routing objects
 export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]): T[] {
+  LayoutMap.set('LAYOUT', LAYOUT);
+
   routeList.forEach((route) => {
     if (route.component) {
       if ((route.component as string).toUpperCase() === 'LAYOUT') {
-        route.component = getLayoutComp(route.component);
+        route.component = LayoutMap.get(route.component);
       } else {
         route.children = [cloneDeep(route)];
         route.component = LAYOUT;
@@ -46,16 +48,6 @@ export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModul
   return (routeList as unknown) as T[];
 }
 
-export function getParams(data: any = {}) {
-  const { params = {} } = data;
-  let ret = '';
-  Object.keys(params).forEach((key) => {
-    const p = params[key];
-    ret += `/${p}`;
-  });
-  return ret;
-}
-
 // Return to the new routing structure, not affected by the original example
 export function getRoute(route: RouteLocationNormalized): RouteLocationNormalized {
   if (!route) return route;

+ 15 - 24
src/router/menus/index.ts

@@ -1,8 +1,9 @@
 import type { Menu, MenuModule } from '/@/router/types';
 import type { RouteRecordNormalized } from 'vue-router';
+
 import { appStore } from '/@/store/modules/app';
 import { permissionStore } from '/@/store/modules/permission';
-import { transformMenuModule, flatMenus, getAllParentPath } from '/@/router/helper/menuHelper';
+import { transformMenuModule, getAllParentPath } from '/@/router/helper/menuHelper';
 import { filter } from '/@/utils/helper/treeHelper';
 import router from '/@/router';
 import { PermissionModeEnum } from '/@/enums/appEnum';
@@ -10,6 +11,8 @@ import { pathToRegexp } from 'path-to-regexp';
 
 import modules from 'globby!/@/router/menus/modules/**/*.@(ts)';
 
+const reg = /(((https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
+
 const menuModules: MenuModule[] = [];
 
 Object.keys(modules).forEach((key) => {
@@ -38,18 +41,9 @@ const staticMenus: Menu[] = [];
 
 async function getAsyncMenus() {
   // 前端角色控制菜单 直接取菜单文件
-  if (!isBackMode()) {
-    return staticMenus;
-  }
-  return permissionStore.getBackMenuListState;
+  return !isBackMode() ? staticMenus : permissionStore.getBackMenuListState;
 }
 
-// 获取深层扁平化菜单
-export const getFlatMenus = async (): Promise<Menu[]> => {
-  const menus = await getAsyncMenus();
-  return flatMenus(menus);
-};
-
 // 获取菜单 树级
 export const getMenus = async (): Promise<Menu[]> => {
   const menus = await getAsyncMenus();
@@ -61,7 +55,7 @@ export const getMenus = async (): Promise<Menu[]> => {
 export async function getCurrentParentPath(currentPath: string) {
   const menus = await getAsyncMenus();
   const allParentPath = await getAllParentPath(menus, currentPath);
-  return allParentPath[0];
+  return allParentPath?.[0];
 }
 
 // 获取1级菜单,删除children
@@ -81,27 +75,24 @@ export async function getChildrenMenus(parentPath: string) {
   return parent.children;
 }
 
-// 扁平化children
-export async function getFlatChildrenMenus(children: Menu[]) {
-  return flatMenus(children);
-}
-
 // 通用过滤方法
 function basicFilter(routes: RouteRecordNormalized[]) {
   return (menu: Menu) => {
     const matchRoute = routes.find((route) => {
-      if (route.meta.externalLink) {
+      const match = route.path.match(reg)?.[0];
+      if (match && match === menu.path) {
         return true;
       }
 
-      if (route.meta) {
-        if (route.meta.carryParam) {
-          return pathToRegexp(route.path).test(menu.path);
-        }
-        if (route.meta.ignoreAuth) return true;
+      if (route.meta?.carryParam) {
+        return pathToRegexp(route.path).test(menu.path);
       }
+      const isSame = route.path === menu.path;
+      if (!isSame) return false;
+
+      if (route.meta?.ignoreAuth) return true;
 
-      return route.path === menu.path;
+      return isSame || pathToRegexp(route.path).test(menu.path);
     });
 
     if (!matchRoute) return false;

+ 0 - 1
src/router/routes/modules/demo/iframe.ts

@@ -38,7 +38,6 @@ const iframe: AppRouteModule = {
       name: 'DocExternal',
       component: IFrame,
       meta: {
-        externalLink: true,
         title: t('routes.demo.iframe.docExternal'),
       },
     },

+ 0 - 3
src/router/types.d.ts

@@ -15,9 +15,6 @@ export interface RouteMeta {
   // icon on tab
   icon?: string;
   // Jump address
-  frameSrc?: string;
-  // Outer link jump address
-  externalLink?: boolean;
 
   // current page transition
   transitionName?: string;

+ 1 - 1
src/settings/projectSetting.ts

@@ -89,7 +89,7 @@ const setting: ProjectConfig = {
     // Whether to show no dom
     show: true,
     // Whether to show dom
-    hidden: true,
+    hidden: false,
     // Menu width
     menuWidth: 210,
     // Menu mode

+ 1 - 1
src/utils/is.ts

@@ -50,7 +50,7 @@ export function isRegExp(val: unknown): val is RegExp {
   return is(val, 'RegExp');
 }
 
-export function isArray(val: unknown): val is Array<any> {
+export function isArray(val: any): val is Array<any> {
   return val && Array.isArray(val);
 }
 

+ 16 - 6
src/views/sys/lock/LockPage.vue

@@ -221,25 +221,35 @@
         font-size: 23em;
       }
       @media (min-width: @screen-sm-max) and (max-width: @screen-md-max) {
-        font-size: 19em;
+        height: 50%;
+        font-size: 12em;
+        border-radius: 10px;
+
+        .meridiem {
+          font-size: 20px;
+        }
       }
       @media (min-width: @screen-xs-max) and (max-width: @screen-sm-max) {
         font-size: 13em;
       }
       @media (max-width: @screen-xs) {
-        height: 50%;
-        font-size: 6em;
-        border-radius: 20px;
+        height: 30%;
+        font-size: 5em;
+        border-radius: 10px;
+
+        .meridiem {
+          font-size: 14px;
+        }
       }
     }
 
     &__footer-date {
       position: absolute;
       bottom: 20px;
-      left: 50%;
+      width: 100%;
       font-family: helvetica;
       color: #bababa;
-      transform: translate(-50%, 0);
+      text-align: center;
 
       .time {
         font-size: 50px;

+ 38 - 41
yarn.lock

@@ -1051,43 +1051,43 @@
   resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.272.tgz#27c7caee9764e0304161261ec08ffc2794944b66"
   integrity sha512-FyiTc7UiXJ5cDfk09lv70sYOSi5uLyK+a0LnF1KgWmofkikL06p98ksNRN7stmHryOYarSy75xgi6MbgAwtltQ==
 
-"@intlify/core@9.0.0-beta.12":
-  version "9.0.0-beta.12"
-  resolved "https://registry.npmjs.org/@intlify/core/-/core-9.0.0-beta.12.tgz#f7d2d09060b8e00ae37157e00a0daa1c86290802"
-  integrity sha512-0wdOS9d0ZEvGkbNIdaxEHQQOfAIuhv1Q8CSpNImThh8ZDD+5Sa38wTerHBO0/Rk0HfHUP/hjPqbxxRqITmSo1g==
-  dependencies:
-    "@intlify/message-compiler" "9.0.0-beta.12"
-    "@intlify/message-resolver" "9.0.0-beta.12"
-    "@intlify/runtime" "9.0.0-beta.12"
-    "@intlify/shared" "9.0.0-beta.12"
-
-"@intlify/message-compiler@9.0.0-beta.12":
-  version "9.0.0-beta.12"
-  resolved "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.0.0-beta.12.tgz#836a49cfd057ecb2c536680cc01aa16693211891"
-  integrity sha512-EMzBDBIsFvWV9w0tRAHzn2BD1C7nkJkXYwDWinROmoL6C4jgKUgon+9Uxp7lV0H1E+7hUfhGj6zHdtJrwFhH+g==
-  dependencies:
-    "@intlify/message-resolver" "9.0.0-beta.12"
-    "@intlify/shared" "9.0.0-beta.12"
+"@intlify/core-base@9.0.0-beta.13":
+  version "9.0.0-beta.13"
+  resolved "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.0.0-beta.13.tgz#fb6bc278209cb7bef44853a42160fedb0560c3f8"
+  integrity sha512-ukImWV+QvRmNZtCTLrSW391z46eMuBheCMPZh801nM3v0Dosfu2PtWO5/z8Q9Bsom4Q+PNQ5eBtOQj2yCAhVEA==
+  dependencies:
+    "@intlify/message-compiler" "9.0.0-beta.13"
+    "@intlify/message-resolver" "9.0.0-beta.13"
+    "@intlify/runtime" "9.0.0-beta.13"
+    "@intlify/shared" "9.0.0-beta.13"
+
+"@intlify/message-compiler@9.0.0-beta.13":
+  version "9.0.0-beta.13"
+  resolved "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.0.0-beta.13.tgz#3b8ddcb2be3f80b28c6e4f6028c0b3ec4e709849"
+  integrity sha512-1z7716InFM8FdTAz64wqZvFuT4wL7WKF63v+vUEW4s9FLoL0U+xIccor9P5XHAvvG1gPMH/Zxd0deg/ULZ1Mcg==
+  dependencies:
+    "@intlify/message-resolver" "9.0.0-beta.13"
+    "@intlify/shared" "9.0.0-beta.13"
     source-map "0.6.1"
 
-"@intlify/message-resolver@9.0.0-beta.12":
-  version "9.0.0-beta.12"
-  resolved "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.0.0-beta.12.tgz#98cf346f5da0fdf3408ba132c24841295a4e02db"
-  integrity sha512-i8bmWzhiBH59YED3SXqvdUfwecl7OUPOU/8yvfdhg2rXuZ4e2chCPnLpPafXz6bi88HcRsWF4aRGlpwDVDYadg==
+"@intlify/message-resolver@9.0.0-beta.13":
+  version "9.0.0-beta.13"
+  resolved "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.0.0-beta.13.tgz#ae6de0bf0e54093160442d465e719bf03fd0f146"
+  integrity sha512-mR1eSpRtB4jh11TpQTUyzjEwqZ6D30mJYREEfSrl5YKfUKwDQrulrOaIO8T5gVQG2m09vfxJHVrgfJ2hR8z/0Q==
 
-"@intlify/runtime@9.0.0-beta.12":
-  version "9.0.0-beta.12"
-  resolved "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.0.0-beta.12.tgz#647a62a326d92690569798ef046d29e8daa25c96"
-  integrity sha512-4ucZHqk/VGhrQEgu9xU5tE/sJTNfqKBhQtaXyEgYHchL9PvLoS1HFwPjABHvWjo3aVcv4d2cGtUPBwH4oLROKA==
+"@intlify/runtime@9.0.0-beta.13":
+  version "9.0.0-beta.13"
+  resolved "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.0.0-beta.13.tgz#8deff103ee6982c6d531314e9f965b90768d8a27"
+  integrity sha512-hcb3sg75SokuzNDG8IC6PJmwjsS/xdgevd99UNG1zKb7s5qFFb90ApvPDpiH0+R9TMQe11fZqg5dyrVBKqAV4A==
   dependencies:
-    "@intlify/message-compiler" "9.0.0-beta.12"
-    "@intlify/message-resolver" "9.0.0-beta.12"
-    "@intlify/shared" "9.0.0-beta.12"
+    "@intlify/message-compiler" "9.0.0-beta.13"
+    "@intlify/message-resolver" "9.0.0-beta.13"
+    "@intlify/shared" "9.0.0-beta.13"
 
-"@intlify/shared@9.0.0-beta.12":
-  version "9.0.0-beta.12"
-  resolved "https://registry.npmjs.org/@intlify/shared/-/shared-9.0.0-beta.12.tgz#e939575bc4047411b9fc65347779f5b3173c1130"
-  integrity sha512-XtHAzQ2KBcdN0Khc7ZDCo5GnKQK4Vv0GKD1BplCWntpA2d5XqjdDpFuKumvbiOjPvYtuCFnksJU0OgJiCWG+KQ==
+"@intlify/shared@9.0.0-beta.13":
+  version "9.0.0-beta.13"
+  resolved "https://registry.npmjs.org/@intlify/shared/-/shared-9.0.0-beta.13.tgz#2d93d695f19fd699ea8b336066f9d6dfc185f094"
+  integrity sha512-/rqC3YEGHs3uu3XSsF1zdBKJb+on34Yn8Z58K3YxJsFxKPHa8mH73EUtN79hTZWh6Js4zEa/WsCgZCM62b8eJA==
 
 "@koa/cors@^3.1.0":
   version "3.1.0"
@@ -8256,16 +8256,13 @@ vue-eslint-parser@^7.3.0:
     esquery "^1.0.1"
     lodash "^4.17.15"
 
-vue-i18n@^9.0.0-beta.12:
-  version "9.0.0-beta.12"
-  resolved "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.0.0-beta.12.tgz#f6e2fc1cc366b8f16aa4754642931e937ebde303"
-  integrity sha512-hDnr+GsIGCIKRtZsdDczkhqyzbpLuPgEkH5bQyMzrKTLelXipLvIVmUCAsSjyR7xMHDCwP6AwVTIZwk6ENXkwg==
+vue-i18n@^9.0.0-beta.13:
+  version "9.0.0-beta.13"
+  resolved "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.0.0-beta.13.tgz#89cf5dd1566025f441132231d15ed621ef70ba96"
+  integrity sha512-ZN6r5ITODu9NYAAbe1IGVUkNeamuleaXTLn5NMn/YZQ+5NSjDjysyVZVLkVOEOIw6bT2tLveyjsWlAZBVtfcPw==
   dependencies:
-    "@intlify/core" "9.0.0-beta.12"
-    "@intlify/message-compiler" "9.0.0-beta.12"
-    "@intlify/message-resolver" "9.0.0-beta.12"
-    "@intlify/runtime" "9.0.0-beta.12"
-    "@intlify/shared" "9.0.0-beta.12"
+    "@intlify/core-base" "9.0.0-beta.13"
+    "@intlify/shared" "9.0.0-beta.13"
     "@vue/devtools-api" "^6.0.0-beta.2"
 
 vue-router@^4.0.1: