Commit c774a6d3a03d9507a9023d600aa9dd9592f52fb3

Authored by vben
1 parent 683d1f52

feat: support mobile layout adaptation

CHANGELOG.zh_CN.md
@@ -3,17 +3,21 @@ @@ -3,17 +3,21 @@
3 ### ✨ Features 3 ### ✨ Features
4 4
5 - 移除左侧菜单搜索,新增顶部菜单搜索功能 5 - 移除左侧菜单搜索,新增顶部菜单搜索功能
  6 +- layout 移动端适配。页面未适配
6 7
7 ### ⚡ Performance Improvements 8 ### ⚡ Performance Improvements
8 9
9 - 异步引入组件 10 - 异步引入组件
10 - 优化整体结构 11 - 优化整体结构
  12 +- 替换菜单默认滚动条为滚动组件
  13 +- 菜单性能优化
11 14
12 ### 🎫 Chores 15 ### 🎫 Chores
13 16
14 - 返回顶部样式调整,避免遮住其他元素 17 - 返回顶部样式调整,避免遮住其他元素
15 - 升级`ant-design-vue`到`2.0.0-rc.5` 18 - 升级`ant-design-vue`到`2.0.0-rc.5`
16 - 刷新按钮布局调整 19 - 刷新按钮布局调整
  20 +- `route.meta` 移除 `externalLink` 属性
17 21
18 ### 🐛 Bug Fixes 22 ### 🐛 Bug Fixes
19 23
package.json
@@ -37,7 +37,7 @@ @@ -37,7 +37,7 @@
37 "sortablejs": "^1.12.0", 37 "sortablejs": "^1.12.0",
38 "vditor": "^3.7.2", 38 "vditor": "^3.7.2",
39 "vue": "^3.0.4", 39 "vue": "^3.0.4",
40 - "vue-i18n": "^9.0.0-beta.12", 40 + "vue-i18n": "^9.0.0-beta.13",
41 "vue-router": "^4.0.1", 41 "vue-router": "^4.0.1",
42 "vue-types": "^3.0.1", 42 "vue-types": "^3.0.1",
43 "vuex": "^4.0.0-rc.2", 43 "vuex": "^4.0.0-rc.2",
src/components/Application/src/search/AppSearch.vue
1 <template> 1 <template>
2 - <div :class="prefixCls" v-if="getShowSearch" @click="handleSearch"> 2 + <div :class="prefixCls" v-if="getShowSearch" @click.stop="handleSearch">
3 <Tooltip> 3 <Tooltip>
4 <template #title> {{ t('component.app.search') }} </template> 4 <template #title> {{ t('component.app.search') }} </template>
5 <SearchOutlined /> 5 <SearchOutlined />
6 </Tooltip> 6 </Tooltip>
7 7
8 - <transition name="zoom-fade" mode="out-in">  
9 - <AppSearchModal @close="handleClose" v-if="showModal" />  
10 - </transition> 8 + <AppSearchModal @close="handleClose" :visible="showModal" />
11 </div> 9 </div>
12 </template> 10 </template>
13 <script lang="ts"> 11 <script lang="ts">
src/components/Application/src/search/AppSearchModal.vue
1 <template> 1 <template>
2 - <div :class="prefixCls" @click.stop>  
3 - <ClickOutSide @clickOutside="handleClose">  
4 - <div :class="`${prefixCls}-content`">  
5 - <a-input  
6 - :class="`${prefixCls}-input`"  
7 - :placeholder="t('component.app.search')"  
8 - allow-clear  
9 - @change="handleSearch"  
10 - >  
11 - <template #prefix>  
12 - <SearchOutlined />  
13 - </template>  
14 - </a-input>  
15 - <div :class="`${prefixCls}-not-data`" v-show="getIsNotData">  
16 - {{ t('component.app.searchNotData') }}  
17 - </div>  
18 - <ul :class="`${prefixCls}-list`" v-show="!getIsNotData" ref="scrollWrap">  
19 - <li  
20 - :ref="setRefs(index)"  
21 - v-for="(item, index) in searchResult"  
22 - :key="item.path"  
23 - :data-index="index"  
24 - @mouseenter="handleMouseenter"  
25 - @click="handleEnter"  
26 - :class="[  
27 - `${prefixCls}-list__item`,  
28 - {  
29 - [`${prefixCls}-list__item--active`]: activeIndex === index,  
30 - },  
31 - ]"  
32 - >  
33 - <div :class="`${prefixCls}-list__item-icon`">  
34 - <g-icon :icon="item.icon || 'mdi:form-select'" :size="20" /> 2 + <Teleport to="body">
  3 + <transition name="zoom-fade" mode="out-in">
  4 + <div :class="getClass" @click.stop v-if="visible">
  5 + <ClickOutSide @clickOutside="handleClose">
  6 + <div :class="`${prefixCls}-content`">
  7 + <div :class="`${prefixCls}-input__wrapper`">
  8 + <a-input
  9 + :class="`${prefixCls}-input`"
  10 + :placeholder="t('component.app.search')"
  11 + allow-clear
  12 + @change="handleSearch"
  13 + >
  14 + <template #prefix>
  15 + <SearchOutlined />
  16 + </template>
  17 + </a-input>
  18 + <span :class="`${prefixCls}-cancel`" @click="handleClose">{{
  19 + t('component.app.cancel')
  20 + }}</span>
35 </div> 21 </div>
36 - <div :class="`${prefixCls}-list__item-text`">{{ item.name }}</div>  
37 - <div :class="`${prefixCls}-list__item-enter`">  
38 - <g-icon icon="ant-design:enter-outlined" :size="20" /> 22 +
  23 + <div :class="`${prefixCls}-not-data`" v-show="getIsNotData">
  24 + {{ t('component.app.searchNotData') }}
39 </div> 25 </div>
40 - </li>  
41 - </ul>  
42 - <AppSearchFooter /> 26 + <ul :class="`${prefixCls}-list`" v-show="!getIsNotData" ref="scrollWrap">
  27 + <li
  28 + :ref="setRefs(index)"
  29 + v-for="(item, index) in searchResult"
  30 + :key="item.path"
  31 + :data-index="index"
  32 + @mouseenter="handleMouseenter"
  33 + @click="handleEnter"
  34 + :class="[
  35 + `${prefixCls}-list__item`,
  36 + {
  37 + [`${prefixCls}-list__item--active`]: activeIndex === index,
  38 + },
  39 + ]"
  40 + >
  41 + <div :class="`${prefixCls}-list__item-icon`">
  42 + <g-icon :icon="item.icon || 'mdi:form-select'" :size="20" />
  43 + </div>
  44 + <div :class="`${prefixCls}-list__item-text`">{{ item.name }}</div>
  45 + <div :class="`${prefixCls}-list__item-enter`">
  46 + <g-icon icon="ant-design:enter-outlined" :size="20" />
  47 + </div>
  48 + </li>
  49 + </ul>
  50 + <AppSearchFooter />
  51 + </div>
  52 + </ClickOutSide>
43 </div> 53 </div>
44 - </ClickOutSide>  
45 - </div> 54 + </transition>
  55 + </Teleport>
46 </template> 56 </template>
47 <script lang="ts"> 57 <script lang="ts">
48 import { defineComponent, computed, unref, ref } from 'vue'; 58 import { defineComponent, computed, unref, ref } from 'vue';
@@ -54,15 +64,20 @@ @@ -54,15 +64,20 @@
54 import AppSearchFooter from './AppSearchFooter.vue'; 64 import AppSearchFooter from './AppSearchFooter.vue';
55 import { useI18n } from '/@/hooks/web/useI18n'; 65 import { useI18n } from '/@/hooks/web/useI18n';
56 import { ClickOutSide } from '/@/components/ClickOutSide'; 66 import { ClickOutSide } from '/@/components/ClickOutSide';
  67 + import { useAppInject } from '/@/hooks/web/useAppInject';
57 export default defineComponent({ 68 export default defineComponent({
58 name: 'AppSearchModal', 69 name: 'AppSearchModal',
59 components: { SearchOutlined, ClickOutSide, AppSearchFooter }, 70 components: { SearchOutlined, ClickOutSide, AppSearchFooter },
60 emits: ['close'], 71 emits: ['close'],
  72 + props: {
  73 + visible: Boolean,
  74 + },
61 setup(_, { emit }) { 75 setup(_, { emit }) {
62 const scrollWrap = ref<ElRef>(null); 76 const scrollWrap = ref<ElRef>(null);
63 const { prefixCls } = useDesign('app-search-modal'); 77 const { prefixCls } = useDesign('app-search-modal');
64 const { t } = useI18n(); 78 const { t } = useI18n();
65 const [refs, setRefs] = useRefs(); 79 const [refs, setRefs] = useRefs();
  80 + const { getIsMobile } = useAppInject();
66 81
67 const { 82 const {
68 handleSearch, 83 handleSearch,
@@ -77,9 +92,19 @@ @@ -77,9 +92,19 @@
77 return !keyword || unref(searchResult).length === 0; 92 return !keyword || unref(searchResult).length === 0;
78 }); 93 });
79 94
  95 + const getClass = computed(() => {
  96 + return [
  97 + prefixCls,
  98 + {
  99 + [`${prefixCls}--mobile`]: unref(getIsMobile),
  100 + },
  101 + ];
  102 + });
  103 +
80 return { 104 return {
81 t, 105 t,
82 prefixCls, 106 prefixCls,
  107 + getClass,
83 handleSearch, 108 handleSearch,
84 searchResult, 109 searchResult,
85 activeIndex, 110 activeIndex,
@@ -98,12 +123,12 @@ @@ -98,12 +123,12 @@
98 <style lang="less" scoped> 123 <style lang="less" scoped>
99 @import (reference) '../../../../design/index.less'; 124 @import (reference) '../../../../design/index.less';
100 @prefix-cls: ~'@{namespace}-app-search-modal'; 125 @prefix-cls: ~'@{namespace}-app-search-modal';
101 - 126 + @footer-prefix-cls: ~'@{namespace}-app-search-footer';
102 .@{prefix-cls} { 127 .@{prefix-cls} {
103 position: fixed; 128 position: fixed;
104 top: 0; 129 top: 0;
105 left: 0; 130 left: 0;
106 - z-index: 100; 131 + z-index: 800;
107 display: flex; 132 display: flex;
108 width: 100%; 133 width: 100%;
109 height: 100%; 134 height: 100%;
@@ -113,6 +138,43 @@ @@ -113,6 +138,43 @@
113 justify-content: center; 138 justify-content: center;
114 // backdrop-filter: blur(2px); 139 // backdrop-filter: blur(2px);
115 140
  141 + &--mobile {
  142 + padding: 0;
  143 +
  144 + > div {
  145 + width: 100%;
  146 + }
  147 +
  148 + .@{prefix-cls}-input {
  149 + width: calc(100% - 38px);
  150 + }
  151 +
  152 + .@{prefix-cls}-cancel {
  153 + display: inline-block;
  154 + }
  155 +
  156 + .@{prefix-cls}-content {
  157 + width: 100%;
  158 + height: 100%;
  159 + border-radius: 0;
  160 + }
  161 +
  162 + .@{footer-prefix-cls} {
  163 + display: none;
  164 + }
  165 +
  166 + .@{prefix-cls}-list {
  167 + height: calc(100% - 80px);
  168 + max-height: unset;
  169 +
  170 + &__item {
  171 + &-enter {
  172 + opacity: 0 !important;
  173 + }
  174 + }
  175 + }
  176 + }
  177 +
116 &-content { 178 &-content {
117 position: relative; 179 position: relative;
118 width: 532px; 180 width: 532px;
@@ -124,10 +186,16 @@ @@ -124,10 +186,16 @@
124 flex-direction: column; 186 flex-direction: column;
125 } 187 }
126 188
  189 + &-input__wrapper {
  190 + display: flex;
  191 + padding: 14px 14px 0 14px;
  192 + justify-content: space-between;
  193 + align-items: center;
  194 + }
  195 +
127 &-input { 196 &-input {
128 - width: calc(100% - 28px); 197 + width: 100%;
129 height: 56px; 198 height: 56px;
130 - margin: 14px 14px 0 14px;  
131 font-size: 1.5em; 199 font-size: 1.5em;
132 color: #1c1e21; 200 color: #1c1e21;
133 201
@@ -136,6 +204,12 @@ @@ -136,6 +204,12 @@
136 } 204 }
137 } 205 }
138 206
  207 + &-cancel {
  208 + display: none;
  209 + font-size: 1em;
  210 + color: #666;
  211 + }
  212 +
139 &-not-data { 213 &-not-data {
140 display: flex; 214 display: flex;
141 width: 100%; 215 width: 100%;
src/components/Menu/src/BasicMenu.vue
@@ -151,7 +151,7 @@ @@ -151,7 +151,7 @@
151 if (props.mode !== MenuModeEnum.HORIZONTAL) { 151 if (props.mode !== MenuModeEnum.HORIZONTAL) {
152 setOpenKeys(path); 152 setOpenKeys(path);
153 } 153 }
154 - if (unref(getIsTopMenu)) { 154 + if (props.isHorizontal && unref(getSplit)) {
155 const parentPath = await getCurrentParentPath(path); 155 const parentPath = await getCurrentParentPath(path);
156 menuState.selectedKeys = [parentPath]; 156 menuState.selectedKeys = [parentPath];
157 } else { 157 } else {
src/hooks/setting/useMenuSetting.ts
@@ -50,11 +50,7 @@ const getShowTopMenu = computed(() =&gt; { @@ -50,11 +50,7 @@ const getShowTopMenu = computed(() =&gt; {
50 }); 50 });
51 51
52 const getShowHeaderTrigger = computed(() => { 52 const getShowHeaderTrigger = computed(() => {
53 - if (  
54 - unref(getMenuType) === MenuTypeEnum.TOP_MENU ||  
55 - !unref(getShowMenu) ||  
56 - !unref(getMenuHidden)  
57 - ) { 53 + if (unref(getMenuType) === MenuTypeEnum.TOP_MENU || !unref(getShowMenu) || unref(getMenuHidden)) {
58 return false; 54 return false;
59 } 55 }
60 56
@@ -79,7 +75,11 @@ const getMiniWidthNumber = computed(() =&gt; { @@ -79,7 +75,11 @@ const getMiniWidthNumber = computed(() =&gt; {
79 }); 75 });
80 76
81 const getCalcContentWidth = computed(() => { 77 const getCalcContentWidth = computed(() => {
82 - const width = unref(getIsTopMenu) || !unref(getShowMenu) ? 0 : unref(getRealWidth); 78 + const width =
  79 + unref(getIsTopMenu) || !unref(getShowMenu) || (unref(getSplit) && unref(getMenuHidden))
  80 + ? 0
  81 + : unref(getRealWidth);
  82 +
83 return `calc(100% - ${unref(width)}px)`; 83 return `calc(100% - ${unref(width)}px)`;
84 }); 84 });
85 85
src/layouts/default/header/LayoutHeader.tsx deleted 100644 → 0
1 -import './index.less';  
2 -  
3 -import type { FunctionalComponent } from 'vue';  
4 -import type { Component } from '/@/components/types';  
5 -  
6 -import { defineComponent, unref, computed } from 'vue';  
7 -  
8 -import { Layout, Tooltip, Badge } from 'ant-design-vue';  
9 -import { AppLogo } from '/@/components/Application';  
10 -import LayoutMenu from '../menu';  
11 -import LockAction from './actions/LockAction';  
12 -import LayoutTrigger from '../trigger/index.vue';  
13 -import NoticeAction from './notice/NoticeActionItem.vue';  
14 -import { LockOutlined, BugOutlined } from '@ant-design/icons-vue';  
15 -  
16 -import { AppSearch } from '/@/components/Application';  
17 -import { useModal } from '/@/components/Modal';  
18 -  
19 -import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';  
20 -import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';  
21 -import { useRootSetting } from '/@/hooks/setting/useRootSetting';  
22 -import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';  
23 -  
24 -import { useRouter } from 'vue-router';  
25 -  
26 -import { errorStore } from '/@/store/modules/error';  
27 -  
28 -import { PageEnum } from '/@/enums/pageEnum';  
29 -import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';  
30 -import { AppLocalePicker } from '/@/components/Application';  
31 -import { useI18n } from '/@/hooks/web/useI18n';  
32 -import { propTypes } from '/@/utils/propTypes';  
33 -  
34 -import { UserDropDown, LayoutBreadcrumb, FullScreen } from './components';  
35 -import { useAppInject } from '/@/hooks/web/useAppInject';  
36 -import { useDesign } from '../../../hooks/web/useDesign';  
37 -interface TooltipItemProps {  
38 - title: string;  
39 -}  
40 -  
41 -const TooltipItem: FunctionalComponent<TooltipItemProps> = (props, { slots }) => {  
42 - return (  
43 - <Tooltip>  
44 - {{  
45 - title: () => props.title,  
46 - default: () => slots.default?.(),  
47 - }}  
48 - </Tooltip>  
49 - );  
50 -};  
51 -  
52 -export default defineComponent({  
53 - name: 'LayoutHeader',  
54 - props: {  
55 - fixed: propTypes.bool,  
56 - },  
57 - setup(props) {  
58 - const { t } = useI18n();  
59 - const { prefixCls } = useDesign('layout-header');  
60 - const { getShowTopMenu, getShowHeaderTrigger, getSplit } = useMenuSetting();  
61 - const { getShowLocale } = useLocaleSetting();  
62 - const { getUseErrorHandle } = useRootSetting();  
63 -  
64 - const {  
65 - getHeaderTheme,  
66 - getUseLockPage,  
67 - getShowFullScreen,  
68 - getShowNotice,  
69 - getShowContent,  
70 - getShowBread,  
71 - getShowHeaderLogo,  
72 - } = useHeaderSetting();  
73 -  
74 - const { push } = useRouter();  
75 - const [register, { openModal }] = useModal();  
76 - const { getIsMobile } = useAppInject();  
77 -  
78 - const headerClass = computed(() => {  
79 - const theme = unref(getHeaderTheme);  
80 - return theme ? `${prefixCls}__header--${theme}` : '';  
81 - });  
82 -  
83 - const getSplitType = computed(() => {  
84 - return unref(getSplit) ? MenuSplitTyeEnum.TOP : MenuSplitTyeEnum.NONE;  
85 - });  
86 -  
87 - const getMenuMode = computed(() => {  
88 - return unref(getSplit) ? MenuModeEnum.HORIZONTAL : null;  
89 - });  
90 -  
91 - function handleToErrorList() {  
92 - push(PageEnum.ERROR_LOG_PAGE).then(() => {  
93 - errorStore.commitErrorListCountState(0);  
94 - });  
95 - }  
96 -  
97 - function handleLockPage() {  
98 - openModal(true);  
99 - }  
100 -  
101 - function renderHeaderLeft() {  
102 - return (  
103 - <>  
104 - {unref(getShowContent) && (  
105 - <div class={`${prefixCls}__left`}>  
106 - {unref(getShowHeaderTrigger) && (  
107 - <LayoutTrigger theme={unref(getHeaderTheme)} sider={false} />  
108 - )}  
109 - {unref(getShowBread) && !unref(getIsMobile) && (  
110 - <LayoutBreadcrumb theme={unref(getHeaderTheme)} />  
111 - )}  
112 - </div>  
113 - )}  
114 - </>  
115 - );  
116 - }  
117 -  
118 - function renderHeaderContent() {  
119 - return (  
120 - <div class={`${prefixCls}__content`}>  
121 - {unref(getShowTopMenu) && !unref(getIsMobile) && (  
122 - <div class={[`${prefixCls}__menu `]}>  
123 - {/* <div class={[`layout-header__menu `]}> */}  
124 - <LayoutMenu  
125 - isHorizontal={true}  
126 - // class={`justify-${unref(getTopMenuAlign)}`}  
127 - theme={unref(getHeaderTheme)}  
128 - splitType={unref(getSplitType)}  
129 - menuMode={unref(getMenuMode)}  
130 - />  
131 - </div>  
132 - )}  
133 - </div>  
134 - );  
135 - }  
136 -  
137 - function renderActionDefault(Comp: Component | any, event: Fn) {  
138 - return (  
139 - <div class={`${prefixCls}__action-item`} onClick={event}>  
140 - <Comp class={`${prefixCls}__action-icon`} />  
141 - </div>  
142 - );  
143 - }  
144 -  
145 - function renderAction() {  
146 - return (  
147 - <div class={`${prefixCls}__action`}>  
148 - {!unref(getIsMobile) && <AppSearch class={`${prefixCls}__action-item`} />}  
149 -  
150 - {unref(getUseErrorHandle) && !unref(getIsMobile) && (  
151 - <TooltipItem title={t('layout.header.tooltipErrorLog')}>  
152 - {() => (  
153 - <Badge  
154 - count={errorStore.getErrorListCountState}  
155 - offset={[0, 10]}  
156 - dot  
157 - overflowCount={99}  
158 - >  
159 - {() => renderActionDefault(BugOutlined, handleToErrorList)}  
160 - </Badge>  
161 - )}  
162 - </TooltipItem>  
163 - )}  
164 -  
165 - {unref(getUseLockPage) && !unref(getIsMobile) && (  
166 - <TooltipItem title={t('layout.header.tooltipLock')}>  
167 - {() => renderActionDefault(LockOutlined, handleLockPage)}  
168 - </TooltipItem>  
169 - )}  
170 -  
171 - {unref(getShowNotice) && !unref(getIsMobile) && (  
172 - <TooltipItem title={t('layout.header.tooltipNotify')}>  
173 - {() => <NoticeAction />}  
174 - </TooltipItem>  
175 - )}  
176 -  
177 - {unref(getShowFullScreen) && !unref(getIsMobile) && <FullScreen />}  
178 -  
179 - <UserDropDown theme={unref(getHeaderTheme)} />  
180 -  
181 - {unref(getShowLocale) && (  
182 - <AppLocalePicker  
183 - reload={true}  
184 - showText={false}  
185 - class={`${prefixCls}__action-item locale`}  
186 - />  
187 - )}  
188 - </div>  
189 - );  
190 - }  
191 -  
192 - function renderHeaderDefault() {  
193 - return (  
194 - <>  
195 - {unref(getShowHeaderLogo) && (  
196 - <AppLogo class={`${prefixCls}__logo`} theme={unref(getHeaderTheme)} />  
197 - )}  
198 - {renderHeaderLeft()}  
199 - {renderHeaderContent()}  
200 - {renderAction()}  
201 - <LockAction onRegister={register} />  
202 - </>  
203 - );  
204 - }  
205 -  
206 - return () => {  
207 - return (  
208 - <Layout.Header class={[prefixCls, unref(headerClass), { fixed: props.fixed }]}>  
209 - {() => renderHeaderDefault()}  
210 - </Layout.Header>  
211 - );  
212 - };  
213 - },  
214 -});  
src/layouts/default/header/LayoutMultipleHeader.less deleted 100644 → 0
1 -@import (reference) '../../../design/index.less';  
2 -  
3 -.multiple-tab-header {  
4 - margin-left: 1px;  
5 - transition: width 0.2s;  
6 - flex: 0 0 auto;  
7 -  
8 - &.dark {  
9 - margin-left: 0;  
10 - }  
11 -  
12 - &.fixed {  
13 - position: fixed;  
14 - top: 0;  
15 - z-index: @multiple-tab-fixed-z-index;  
16 - width: 100%;  
17 - }  
18 -}  
src/layouts/default/header/LayoutMultipleHeader.tsx renamed to src/layouts/default/header/MultipleHeader.vue
1 -import './LayoutMultipleHeader.less';  
2 -  
3 -import { defineComponent, unref, computed, ref, watch, nextTick, CSSProperties } from 'vue';  
4 -  
5 -import LayoutHeader from './index.vue';  
6 -import MultipleTabs from '../tabs/index.vue';  
7 -  
8 -import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';  
9 -import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';  
10 -import { useFullContent } from '/@/hooks/web/useFullContent';  
11 -import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';  
12 -import { useLayoutContext } from '../useLayoutContext';  
13 -import { useAppInject } from '/@/hooks/web/useAppInject';  
14 -  
15 -export default defineComponent({  
16 - name: 'LayoutMultipleHeader',  
17 - setup() {  
18 - const placeholderHeightRef = ref(0);  
19 - const fullHeaderHeightRef = ref(0);  
20 - const headerElRef = ref<ComponentRef>(null);  
21 - const tabElRef = ref<ComponentRef>(null);  
22 -  
23 - const injectValue = useLayoutContext();  
24 -  
25 - const { getCalcContentWidth, getSplit } = useMenuSetting();  
26 - const { getIsMobile } = useAppInject();  
27 - const {  
28 - getFixed,  
29 - getShowInsetHeaderRef,  
30 - getShowFullHeaderRef,  
31 - getShowHeader,  
32 - getUnFixedAndFull,  
33 - getHeaderTheme,  
34 - } = useHeaderSetting();  
35 -  
36 - const { getFullContent } = useFullContent();  
37 -  
38 - const { getShowMultipleTab } = useMultipleTabSetting();  
39 -  
40 - const getShowTabs = computed(() => {  
41 - return unref(getShowMultipleTab) && !unref(getFullContent);  
42 - });  
43 -  
44 - const getPlaceholderDomStyle = computed(  
45 - (): CSSProperties => {  
46 - return {  
47 - height: `${unref(placeholderHeightRef)}px`,  
48 - };  
49 - }  
50 - );  
51 -  
52 - const getIsShowPlaceholderDom = computed(() => {  
53 - return unref(getFixed) || unref(getShowFullHeaderRef);  
54 - });  
55 -  
56 - const getWrapStyle = computed(  
57 - (): CSSProperties => {  
58 - const style: CSSProperties = {};  
59 - if (unref(getFixed)) {  
60 - style.width = unref(getIsMobile) ? '100%' : unref(getCalcContentWidth);  
61 - }  
62 - if (unref(getShowFullHeaderRef)) {  
63 - style.top = `${unref(fullHeaderHeightRef)}px`; 1 +<template>
  2 + <div :style="getPlaceholderDomStyle" v-if="getIsShowPlaceholderDom" />
  3 + <div :style="getWrapStyle" :class="getClass">
  4 + <LayoutHeader v-if="getShowInsetHeaderRef" />
  5 + <MultipleTabs v-if="getShowTabs" />
  6 + </div>
  7 +</template>
  8 +<script lang="ts">
  9 + import { defineComponent, unref, computed, CSSProperties } from 'vue';
  10 +
  11 + import LayoutHeader from './index.vue';
  12 + import MultipleTabs from '../tabs/index.vue';
  13 +
  14 + import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
  15 + import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
  16 + import { useFullContent } from '/@/hooks/web/useFullContent';
  17 + import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
  18 + import { useAppInject } from '/@/hooks/web/useAppInject';
  19 + import { useDesign } from '/@/hooks/web/useDesign';
  20 +
  21 + const HEADER_HEIGHT = 48;
  22 +
  23 + const TABS_HEIGHT = 32;
  24 + export default defineComponent({
  25 + name: 'LayoutMultipleHeader',
  26 + components: { LayoutHeader, MultipleTabs },
  27 + setup() {
  28 + const { prefixCls } = useDesign('layout-multiple-header');
  29 +
  30 + const { getCalcContentWidth, getSplit } = useMenuSetting();
  31 + const { getIsMobile } = useAppInject();
  32 + const {
  33 + getFixed,
  34 + getShowInsetHeaderRef,
  35 + getShowFullHeaderRef,
  36 + getHeaderTheme,
  37 + } = useHeaderSetting();
  38 +
  39 + const { getFullContent } = useFullContent();
  40 +
  41 + const { getShowMultipleTab } = useMultipleTabSetting();
  42 +
  43 + const getShowTabs = computed(() => {
  44 + return unref(getShowMultipleTab) && !unref(getFullContent);
  45 + });
  46 +
  47 + const getIsShowPlaceholderDom = computed(() => {
  48 + return unref(getFixed) || unref(getShowFullHeaderRef);
  49 + });
  50 +
  51 + const getWrapStyle = computed(
  52 + (): CSSProperties => {
  53 + const style: CSSProperties = {};
  54 + if (unref(getFixed)) {
  55 + style.width = unref(getIsMobile) ? '100%' : unref(getCalcContentWidth);
  56 + }
  57 + if (unref(getShowFullHeaderRef)) {
  58 + style.top = `${HEADER_HEIGHT}px`;
  59 + }
  60 + return style;
64 } 61 }
65 - return style;  
66 - }  
67 - );  
68 -  
69 - const getIsFixed = computed(() => {  
70 - return unref(getFixed) || unref(getShowFullHeaderRef);  
71 - });  
72 -  
73 - watch(  
74 - () => [  
75 - unref(getFixed),  
76 - unref(getShowFullHeaderRef),  
77 - unref(getShowHeader),  
78 - unref(getShowMultipleTab),  
79 - ],  
80 - () => {  
81 - if (unref(getUnFixedAndFull)) return;  
82 - nextTick(() => {  
83 - const headerEl = unref(headerElRef)?.$el;  
84 - const tabEl = unref(tabElRef)?.$el;  
85 - const fullHeaderEl = unref(injectValue.fullHeader)?.$el; 62 + );
86 63
87 - let height = 0;  
88 - if (headerEl && !unref(getShowFullHeaderRef) && !unref(getSplit)) {  
89 - height += headerEl.offsetHeight;  
90 - } 64 + const getIsFixed = computed(() => {
  65 + return unref(getFixed) || unref(getShowFullHeaderRef);
  66 + });
91 67
92 - if (tabEl) {  
93 - height += tabEl.offsetHeight; 68 + const getPlaceholderDomStyle = computed(
  69 + (): CSSProperties => {
  70 + let height = 0;
  71 + if (unref(getShowFullHeaderRef) || !unref(getSplit)) {
  72 + height += HEADER_HEIGHT;
94 } 73 }
95 -  
96 - if (fullHeaderEl && unref(getShowFullHeaderRef)) {  
97 - const fullHeaderHeight = fullHeaderEl.offsetHeight;  
98 - height += fullHeaderHeight;  
99 - fullHeaderHeightRef.value = fullHeaderHeight; 74 + if (unref(getShowMultipleTab)) {
  75 + height += TABS_HEIGHT;
100 } 76 }
101 -  
102 - placeholderHeightRef.value = height;  
103 - });  
104 - },  
105 - {  
106 - immediate: true,  
107 - }  
108 - );  
109 -  
110 - return () => {  
111 - return (  
112 - <>  
113 - {unref(getIsShowPlaceholderDom) && <div style={unref(getPlaceholderDomStyle)} />}  
114 - <div  
115 - style={unref(getWrapStyle)}  
116 - class={['multiple-tab-header', unref(getHeaderTheme), { fixed: unref(getIsFixed) }]}  
117 - >  
118 - {unref(getShowInsetHeaderRef) && <LayoutHeader ref={headerElRef} />}  
119 - {unref(getShowTabs) && <MultipleTabs ref={tabElRef} />}  
120 - </div>  
121 - </> 77 + return {
  78 + height: `${height}px`,
  79 + };
  80 + }
122 ); 81 );
123 - };  
124 - },  
125 -}); 82 +
  83 + const getClass = computed(() => {
  84 + return [
  85 + prefixCls,
  86 + `${prefixCls}--${unref(getHeaderTheme)}`,
  87 + { [`${prefixCls}--fixed`]: unref(getIsFixed) },
  88 + ];
  89 + });
  90 +
  91 + return {
  92 + getClass,
  93 + prefixCls,
  94 + getPlaceholderDomStyle,
  95 + getIsFixed,
  96 + getWrapStyle,
  97 + getIsShowPlaceholderDom,
  98 + getShowTabs,
  99 + getShowInsetHeaderRef,
  100 + };
  101 + },
  102 + });
  103 +</script>
  104 +<style lang="less" scoped>
  105 + @import (reference) '../../../design/index.less';
  106 + @prefix-cls: ~'@{namespace}-layout-multiple-header';
  107 +
  108 + .@{prefix-cls} {
  109 + margin-left: 1px;
  110 + transition: width 0.2s;
  111 + flex: 0 0 auto;
  112 +
  113 + &--dark {
  114 + margin-left: 0;
  115 + }
  116 +
  117 + &--fixed {
  118 + position: fixed;
  119 + top: 0;
  120 + z-index: @multiple-tab-fixed-z-index;
  121 + width: 100%;
  122 + }
  123 + }
  124 +</style>
src/layouts/default/header/index.less
@@ -2,6 +2,8 @@ @@ -2,6 +2,8 @@
2 @header-trigger-prefix-cls: ~'@{namespace}-layout-header-trigger'; 2 @header-trigger-prefix-cls: ~'@{namespace}-layout-header-trigger';
3 @header-prefix-cls: ~'@{namespace}-layout-header'; 3 @header-prefix-cls: ~'@{namespace}-layout-header';
4 @locale-prefix-cls: ~'@{namespace}-app-locale-picker'; 4 @locale-prefix-cls: ~'@{namespace}-app-locale-picker';
  5 +@breadcrumb-prefix-cls: ~'@{namespace}-layout-breadcrumb';
  6 +@logo-prefix-cls: ~'@{namespace}-app-logo';
5 7
6 .@{header-prefix-cls} { 8 .@{header-prefix-cls} {
7 display: flex; 9 display: flex;
@@ -14,6 +16,30 @@ @@ -14,6 +16,30 @@
14 align-items: center; 16 align-items: center;
15 justify-content: space-between; 17 justify-content: space-between;
16 18
  19 + &--mobile {
  20 + .@{breadcrumb-prefix-cls},
  21 + .error-action,
  22 + .notify-item,
  23 + .fullscreen-item {
  24 + display: none;
  25 + }
  26 +
  27 + .@{logo-prefix-cls} {
  28 + min-width: unset;
  29 + padding-right: 0;
  30 +
  31 + &__title {
  32 + display: none;
  33 + }
  34 + }
  35 + .@{header-trigger-prefix-cls} {
  36 + padding: 0 4px 0 8px !important;
  37 + }
  38 + .@{header-prefix-cls}-action {
  39 + padding-right: 4px;
  40 + }
  41 + }
  42 +
17 &--fixed { 43 &--fixed {
18 position: fixed; 44 position: fixed;
19 top: 0; 45 top: 0;
@@ -78,7 +104,7 @@ @@ -78,7 +104,7 @@
78 104
79 &-action { 105 &-action {
80 display: flex; 106 display: flex;
81 - min-width: 200px; 107 + min-width: 180px;
82 padding-right: 12px; 108 padding-right: 12px;
83 align-items: center; 109 align-items: center;
84 110
src/layouts/default/header/index.vue
@@ -3,17 +3,17 @@ @@ -3,17 +3,17 @@
3 <!-- left start --> 3 <!-- left start -->
4 <div :class="`${prefixCls}-left`"> 4 <div :class="`${prefixCls}-left`">
5 <!-- logo --> 5 <!-- logo -->
6 - <AppLogo v-if="getShowHeaderLogo" :class="`${prefixCls}-logo`" :theme="getHeaderTheme" />  
7 -  
8 - <LayoutTrigger  
9 - v-if="getShowContent && getShowHeaderTrigger" 6 + <AppLogo
  7 + v-if="getShowHeaderLogo || getIsMobile"
  8 + :class="`${prefixCls}-logo`"
10 :theme="getHeaderTheme" 9 :theme="getHeaderTheme"
11 - :sider="false"  
12 /> 10 />
13 - <LayoutBreadcrumb  
14 - v-if="getShowContent && getShowBread && !getIsMobile" 11 + <LayoutTrigger
  12 + v-if="(getShowContent && getShowHeaderTrigger && !getSplit) || getIsMobile"
15 :theme="getHeaderTheme" 13 :theme="getHeaderTheme"
  14 + :sider="false"
16 /> 15 />
  16 + <LayoutBreadcrumb v-if="getShowContent && getShowBread" :theme="getHeaderTheme" />
17 </div> 17 </div>
18 <!-- left end --> 18 <!-- left end -->
19 19
@@ -30,15 +30,15 @@ @@ -30,15 +30,15 @@
30 30
31 <!-- action --> 31 <!-- action -->
32 <div :class="`${prefixCls}-action`"> 32 <div :class="`${prefixCls}-action`">
33 - <AppSearch v-if="!getIsMobile" :class="`${prefixCls}-action__item`" /> 33 + <AppSearch :class="`${prefixCls}-action__item `" />
34 34
35 - <ErrorAction v-if="getUseErrorHandle && !getIsMobile" :class="`${prefixCls}-action__item`" /> 35 + <ErrorAction v-if="getUseErrorHandle" :class="`${prefixCls}-action__item error-action`" />
36 36
37 - <LockItem v-if="getUseLockPage && !getIsMobile" :class="`${prefixCls}-action__item`" /> 37 + <LockItem v-if="getUseLockPage" :class="`${prefixCls}-action__item lock-item`" />
38 38
39 - <Notify v-if="getShowNotice && !getIsMobile" :class="`${prefixCls}-action__item`" /> 39 + <Notify v-if="getShowNotice" :class="`${prefixCls}-action__item notify-item`" />
40 40
41 - <FullScreen v-if="getShowFullScreen && !getIsMobile" :class="`${prefixCls}-action__item`" /> 41 + <FullScreen v-if="getShowFullScreen" :class="`${prefixCls}-action__item fullscreen-item`" />
42 42
43 <UserDropDown :theme="getHeaderTheme" /> 43 <UserDropDown :theme="getHeaderTheme" />
44 44
@@ -123,7 +123,11 @@ @@ -123,7 +123,11 @@
123 const theme = unref(getHeaderTheme); 123 const theme = unref(getHeaderTheme);
124 return [ 124 return [
125 prefixCls, 125 prefixCls,
126 - { [`${prefixCls}--fixed`]: props.fixed, [`${prefixCls}--${theme}`]: theme }, 126 + {
  127 + [`${prefixCls}--fixed`]: props.fixed,
  128 + [`${prefixCls}--mobile`]: unref(getIsMobile),
  129 + [`${prefixCls}--${theme}`]: theme,
  130 + },
127 ]; 131 ];
128 }); 132 });
129 133
@@ -145,6 +149,7 @@ @@ -145,6 +149,7 @@
145 getShowBread, 149 getShowBread,
146 getShowContent, 150 getShowContent,
147 getSplitType, 151 getSplitType,
  152 + getSplit,
148 getMenuMode, 153 getMenuMode,
149 getShowTopMenu, 154 getShowTopMenu,
150 getShowLocale, 155 getShowLocale,
src/layouts/default/index.vue
1 <template> 1 <template>
2 <Layout :class="prefixCls"> 2 <Layout :class="prefixCls">
3 <LayoutFeatures /> 3 <LayoutFeatures />
4 - <LayoutHeader fixed ref="headerRef" v-if="getShowFullHeaderRef" /> 4 + <LayoutHeader fixed v-if="getShowFullHeaderRef" />
5 <Layout> 5 <Layout>
6 - <LayoutSideBar v-if="getShowSidebar" /> 6 + <LayoutSideBar v-if="getShowSidebar || getIsMobile" />
7 <Layout :class="`${prefixCls}__main`"> 7 <Layout :class="`${prefixCls}__main`">
8 <LayoutMultipleHeader /> 8 <LayoutMultipleHeader />
9 <LayoutContent /> 9 <LayoutContent />
@@ -14,21 +14,21 @@ @@ -14,21 +14,21 @@
14 </template> 14 </template>
15 15
16 <script lang="ts"> 16 <script lang="ts">
17 - import { defineComponent, ref } from 'vue'; 17 + import { defineComponent } from 'vue';
18 import { Layout } from 'ant-design-vue'; 18 import { Layout } from 'ant-design-vue';
19 import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; 19 import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
20 20
21 import LayoutHeader from './header/index.vue'; 21 import LayoutHeader from './header/index.vue';
22 import LayoutContent from './content/index.vue'; 22 import LayoutContent from './content/index.vue';
23 - import LayoutSideBar from './sider';  
24 - import LayoutMultipleHeader from './header/LayoutMultipleHeader'; 23 + import LayoutSideBar from './sider/index.vue';
  24 + import LayoutMultipleHeader from './header/MultipleHeader.vue';
25 25
26 import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; 26 import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
27 import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; 27 import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
28 import { useDesign } from '/@/hooks/web/useDesign'; 28 import { useDesign } from '/@/hooks/web/useDesign';
29 - import { createLayoutContext } from './useLayoutContext';  
30 29
31 import { registerGlobComp } from '/@/components/registerGlobComp'; 30 import { registerGlobComp } from '/@/components/registerGlobComp';
  31 + import { useAppInject } from '/@/hooks/web/useAppInject';
32 32
33 export default defineComponent({ 33 export default defineComponent({
34 name: 'DefaultLayout', 34 name: 'DefaultLayout',
@@ -47,11 +47,9 @@ @@ -47,11 +47,9 @@
47 // default layout It is loaded after login. So it won’t be packaged to the first screen 47 // default layout It is loaded after login. So it won’t be packaged to the first screen
48 registerGlobComp(); 48 registerGlobComp();
49 49
50 - const headerRef = ref<ComponentRef>(null);  
51 -  
52 const { prefixCls } = useDesign('default-layout'); 50 const { prefixCls } = useDesign('default-layout');
53 51
54 - createLayoutContext({ fullHeader: headerRef }); 52 + const { getIsMobile } = useAppInject();
55 53
56 const { getShowFullHeaderRef } = useHeaderSetting(); 54 const { getShowFullHeaderRef } = useHeaderSetting();
57 55
@@ -60,8 +58,8 @@ @@ -60,8 +58,8 @@
60 return { 58 return {
61 getShowFullHeaderRef, 59 getShowFullHeaderRef,
62 getShowSidebar, 60 getShowSidebar,
63 - headerRef,  
64 prefixCls, 61 prefixCls,
  62 + getIsMobile,
65 }; 63 };
66 }, 64 },
67 }); 65 });
src/layouts/default/menu/index.less
1 @import (reference) '../../../design/index.less'; 1 @import (reference) '../../../design/index.less';
2 2
3 -.layout-menu {  
4 - &__logo { 3 +@prefix-cls: ~'@{namespace}-layout-menu';
  4 +@logo-prefix-cls: ~'@{namespace}-app-logo';
  5 +
  6 +.@{prefix-cls} {
  7 + &-logo {
5 height: @header-height; 8 height: @header-height;
6 padding: 10px 4px 10px 10px; 9 padding: 10px 4px 10px 10px;
7 10
@@ -10,4 +13,12 @@ @@ -10,4 +13,12 @@
10 height: @logo-width; 13 height: @logo-width;
11 } 14 }
12 } 15 }
  16 +
  17 + &--mobile {
  18 + .@{logo-prefix-cls} {
  19 + &__title {
  20 + opacity: 1;
  21 + }
  22 + }
  23 + }
13 } 24 }
src/layouts/default/menu/index.tsx
1 import './index.less'; 1 import './index.less';
2 2
3 -import { PropType, toRef } from 'vue'; 3 +import type { PropType, CSSProperties } from 'vue';
4 4
5 -import { computed, defineComponent, unref } from 'vue'; 5 +import { computed, defineComponent, unref, toRef } from 'vue';
6 import { BasicMenu } from '/@/components/Menu'; 6 import { BasicMenu } from '/@/components/Menu';
7 import { AppLogo } from '/@/components/Application'; 7 import { AppLogo } from '/@/components/Application';
8 8
@@ -17,7 +17,8 @@ import { openWindow } from &#39;/@/utils&#39;; @@ -17,7 +17,8 @@ import { openWindow } from &#39;/@/utils&#39;;
17 import { propTypes } from '/@/utils/propTypes'; 17 import { propTypes } from '/@/utils/propTypes';
18 import { isUrl } from '/@/utils/is'; 18 import { isUrl } from '/@/utils/is';
19 import { useRootSetting } from '/@/hooks/setting/useRootSetting'; 19 import { useRootSetting } from '/@/hooks/setting/useRootSetting';
20 -import { CSSProperties } from 'vue'; 20 +import { useAppInject } from '/@/hooks/web/useAppInject';
  21 +import { useDesign } from '/@/hooks/web/useDesign';
21 22
22 export default defineComponent({ 23 export default defineComponent({
23 name: 'LayoutMenu', 24 name: 'LayoutMenu',
@@ -50,9 +51,15 @@ export default defineComponent({ @@ -50,9 +51,15 @@ export default defineComponent({
50 } = useMenuSetting(); 51 } = useMenuSetting();
51 const { getShowLogo } = useRootSetting(); 52 const { getShowLogo } = useRootSetting();
52 53
  54 + const { prefixCls } = useDesign('layout-menu');
  55 +
53 const { menusRef } = useSplitMenu(toRef(props, 'splitType')); 56 const { menusRef } = useSplitMenu(toRef(props, 'splitType'));
54 57
55 - const getComputedMenuMode = computed(() => props.menuMode || unref(getMenuMode)); 58 + const { getIsMobile } = useAppInject();
  59 +
  60 + const getComputedMenuMode = computed(() =>
  61 + unref(getIsMobile) ? MenuModeEnum.INLINE : props.menuMode || unref(getMenuMode)
  62 + );
56 63
57 const getComputedMenuTheme = computed(() => props.theme || unref(getMenuTheme)); 64 const getComputedMenuTheme = computed(() => props.theme || unref(getMenuTheme));
58 65
@@ -69,6 +76,16 @@ export default defineComponent({ @@ -69,6 +76,16 @@ export default defineComponent({
69 }; 76 };
70 } 77 }
71 ); 78 );
  79 +
  80 + const getLogoClass = computed(() => {
  81 + return [
  82 + `${prefixCls}-logo`,
  83 + unref(getComputedMenuTheme),
  84 + {
  85 + [`${prefixCls}--mobile`]: unref(getIsMobile),
  86 + },
  87 + ];
  88 + });
72 /** 89 /**
73 * click menu 90 * click menu
74 * @param menu 91 * @param menu
@@ -91,12 +108,12 @@ export default defineComponent({ @@ -91,12 +108,12 @@ export default defineComponent({
91 } 108 }
92 109
93 function renderHeader() { 110 function renderHeader() {
94 - if (!unref(getIsShowLogo)) return null; 111 + if (!unref(getIsShowLogo) && !unref(getIsMobile)) return null;
95 112
96 return ( 113 return (
97 <AppLogo 114 <AppLogo
98 showTitle={!unref(getCollapsed)} 115 showTitle={!unref(getCollapsed)}
99 - class={[`layout-menu__logo`, unref(getComputedMenuTheme)]} 116 + class={unref(getLogoClass)}
100 theme={unref(getComputedMenuTheme)} 117 theme={unref(getComputedMenuTheme)}
101 /> 118 />
102 ); 119 );
@@ -128,7 +145,6 @@ export default defineComponent({ @@ -128,7 +145,6 @@ export default defineComponent({
128 ) : ( 145 ) : (
129 renderMenu() 146 renderMenu()
130 )} 147 )}
131 - ;  
132 </> 148 </>
133 ); 149 );
134 }; 150 };
src/layouts/default/menu/useLayoutMenu.ts
@@ -10,11 +10,13 @@ import { useMenuSetting } from &#39;/@/hooks/setting/useMenuSetting&#39;; @@ -10,11 +10,13 @@ import { useMenuSetting } from &#39;/@/hooks/setting/useMenuSetting&#39;;
10 10
11 import { getChildrenMenus, getCurrentParentPath, getMenus, getShallowMenus } from '/@/router/menus'; 11 import { getChildrenMenus, getCurrentParentPath, getMenus, getShallowMenus } from '/@/router/menus';
12 import { permissionStore } from '/@/store/modules/permission'; 12 import { permissionStore } from '/@/store/modules/permission';
  13 +import { useAppInject } from '/@/hooks/web/useAppInject';
13 14
14 export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) { 15 export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
15 // Menu array 16 // Menu array
16 const menusRef = ref<Menu[]>([]); 17 const menusRef = ref<Menu[]>([]);
17 const { currentRoute } = useRouter(); 18 const { currentRoute } = useRouter();
  19 + const { getIsMobile } = useAppInject();
18 const { setMenuSetting, getIsHorizontal, getSplit } = useMenuSetting(); 20 const { setMenuSetting, getIsHorizontal, getSplit } = useMenuSetting();
19 21
20 const [throttleHandleSplitLeftMenu] = useThrottle(handleSplitLeftMenu, 50); 22 const [throttleHandleSplitLeftMenu] = useThrottle(handleSplitLeftMenu, 50);
@@ -36,7 +38,7 @@ export function useSplitMenu(splitType: Ref&lt;MenuSplitTyeEnum&gt;) { @@ -36,7 +38,7 @@ export function useSplitMenu(splitType: Ref&lt;MenuSplitTyeEnum&gt;) {
36 watch( 38 watch(
37 [() => unref(currentRoute).path, () => unref(splitType)], 39 [() => unref(currentRoute).path, () => unref(splitType)],
38 async ([path]: [string, MenuSplitTyeEnum]) => { 40 async ([path]: [string, MenuSplitTyeEnum]) => {
39 - if (unref(splitNotLeft)) return; 41 + if (unref(splitNotLeft) || unref(getIsMobile)) return;
40 42
41 const parentPath = await getCurrentParentPath(path); 43 const parentPath = await getCurrentParentPath(path);
42 parentPath && throttleHandleSplitLeftMenu(parentPath); 44 parentPath && throttleHandleSplitLeftMenu(parentPath);
@@ -65,24 +67,24 @@ export function useSplitMenu(splitType: Ref&lt;MenuSplitTyeEnum&gt;) { @@ -65,24 +67,24 @@ export function useSplitMenu(splitType: Ref&lt;MenuSplitTyeEnum&gt;) {
65 67
66 // Handle left menu split 68 // Handle left menu split
67 async function handleSplitLeftMenu(parentPath: string) { 69 async function handleSplitLeftMenu(parentPath: string) {
68 - if (unref(getSplitLeft)) return; 70 + if (unref(getSplitLeft) || unref(getIsMobile)) return;
69 71
70 // spilt mode left 72 // spilt mode left
71 const children = await getChildrenMenus(parentPath); 73 const children = await getChildrenMenus(parentPath);
72 if (!children) { 74 if (!children) {
73 - setMenuSetting({ hidden: false }); 75 + setMenuSetting({ hidden: true });
74 menusRef.value = []; 76 menusRef.value = [];
75 return; 77 return;
76 } 78 }
77 79
78 - setMenuSetting({ hidden: true }); 80 + setMenuSetting({ hidden: false });
79 menusRef.value = children; 81 menusRef.value = children;
80 } 82 }
81 83
82 // get menus 84 // get menus
83 async function genMenus() { 85 async function genMenus() {
84 // normal mode 86 // normal mode
85 - if (unref(normalType)) { 87 + if (unref(normalType) || unref(getIsMobile)) {
86 menusRef.value = await getMenus(); 88 menusRef.value = await getMenus();
87 return; 89 return;
88 } 90 }
src/layouts/default/sider/DragBar.vue 0 → 100644
  1 +<template>
  2 + <div :class="getClass" :style="getDragBarStyle" />
  3 +</template>
  4 +<script lang="ts">
  5 + import { defineComponent, computed, unref } from 'vue';
  6 +
  7 + import { useDesign } from '/@/hooks/web/useDesign';
  8 + import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
  9 +
  10 + export default defineComponent({
  11 + name: 'DargBar',
  12 + props: {
  13 + mobile: Boolean,
  14 + },
  15 + setup(props) {
  16 + const { getMiniWidthNumber, getCollapsed, getCanDrag } = useMenuSetting();
  17 +
  18 + const { prefixCls } = useDesign('darg-bar');
  19 + const getDragBarStyle = computed(() => {
  20 + if (unref(getCollapsed)) {
  21 + return { left: `${unref(getMiniWidthNumber)}px` };
  22 + }
  23 + return {};
  24 + });
  25 +
  26 + const getClass = computed(() => {
  27 + return [
  28 + prefixCls,
  29 + {
  30 + [`${prefixCls}--hide`]: !unref(getCanDrag) || props.mobile,
  31 + },
  32 + ];
  33 + });
  34 +
  35 + return {
  36 + prefixCls,
  37 + getDragBarStyle,
  38 + getClass,
  39 + };
  40 + },
  41 + });
  42 +</script>
  43 +<style lang="less" scoped>
  44 + @import (reference) '../../../design/index.less';
  45 + @prefix-cls: ~'@{namespace}-darg-bar';
  46 +
  47 + .@{prefix-cls} {
  48 + position: absolute;
  49 + top: 0;
  50 + right: -2px;
  51 + z-index: @side-drag-z-index;
  52 + width: 2px;
  53 + height: 100%;
  54 + cursor: col-resize;
  55 + border-top: none;
  56 + border-bottom: none;
  57 +
  58 + &--hide {
  59 + display: none;
  60 + }
  61 +
  62 + &:hover {
  63 + background: @primary-color;
  64 + box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.15);
  65 + }
  66 + }
  67 +</style>
src/layouts/default/sider/index.tsx renamed to src/layouts/default/sider/LayoutSider.tsx
@@ -12,6 +12,7 @@ import { useTrigger, useDragLine, useSiderEvent } from &#39;./useLayoutSider&#39;; @@ -12,6 +12,7 @@ import { useTrigger, useDragLine, useSiderEvent } from &#39;./useLayoutSider&#39;;
12 import { useAppInject } from '/@/hooks/web/useAppInject'; 12 import { useAppInject } from '/@/hooks/web/useAppInject';
13 import { useDesign } from '/@/hooks/web/useDesign'; 13 import { useDesign } from '/@/hooks/web/useDesign';
14 14
  15 +import DragBar from './DragBar.vue';
15 export default defineComponent({ 16 export default defineComponent({
16 name: 'LayoutSideBar', 17 name: 'LayoutSideBar',
17 setup() { 18 setup() {
@@ -31,11 +32,11 @@ export default defineComponent({ @@ -31,11 +32,11 @@ export default defineComponent({
31 32
32 const { prefixCls } = useDesign('layout-sideBar'); 33 const { prefixCls } = useDesign('layout-sideBar');
33 34
34 - const { getTriggerAttr, getTriggerSlot } = useTrigger();  
35 -  
36 const { getIsMobile } = useAppInject(); 35 const { getIsMobile } = useAppInject();
37 36
38 - const { renderDragLine } = useDragLine(sideRef, dragBarRef); 37 + const { getTriggerAttr, getTriggerSlot } = useTrigger(getIsMobile);
  38 +
  39 + useDragLine(sideRef, dragBarRef);
39 40
40 const { getCollapsedWidth, onBreakpointChange, onCollapseChange } = useSiderEvent(); 41 const { getCollapsedWidth, onBreakpointChange, onCollapseChange } = useSiderEvent();
41 42
@@ -48,7 +49,7 @@ export default defineComponent({ @@ -48,7 +49,7 @@ export default defineComponent({
48 }); 49 });
49 50
50 const showClassSideBarRef = computed(() => { 51 const showClassSideBarRef = computed(() => {
51 - return unref(getSplit) ? unref(getMenuHidden) : true; 52 + return unref(getSplit) ? !unref(getMenuHidden) : true;
52 }); 53 });
53 54
54 const getSiderClass = computed(() => { 55 const getSiderClass = computed(() => {
@@ -57,7 +58,7 @@ export default defineComponent({ @@ -57,7 +58,7 @@ export default defineComponent({
57 { 58 {
58 [`${prefixCls}--fixed`]: unref(getMenuFixed), 59 [`${prefixCls}--fixed`]: unref(getMenuFixed),
59 hidden: !unref(showClassSideBarRef), 60 hidden: !unref(showClassSideBarRef),
60 - [`${prefixCls}--mix`]: unref(getIsMixMode), 61 + [`${prefixCls}--mix`]: unref(getIsMixMode) && !unref(getIsMobile),
61 }, 62 },
62 ]; 63 ];
63 }); 64 });
@@ -84,7 +85,7 @@ export default defineComponent({ @@ -84,7 +85,7 @@ export default defineComponent({
84 menuMode={unref(getMode)} 85 menuMode={unref(getMode)}
85 splitType={unref(getSplitType)} 86 splitType={unref(getSplitType)}
86 /> 87 />
87 - {renderDragLine()} 88 + <DragBar ref={dragBarRef} />
88 </> 89 </>
89 ); 90 );
90 } 91 }
@@ -101,7 +102,7 @@ export default defineComponent({ @@ -101,7 +102,7 @@ export default defineComponent({
101 collapsible 102 collapsible
102 class={unref(getSiderClass)} 103 class={unref(getSiderClass)}
103 width={unref(getMenuWidth)} 104 width={unref(getMenuWidth)}
104 - collapsed={unref(getCollapsed)} 105 + collapsed={unref(getIsMobile) ? false : unref(getCollapsed)}
105 collapsedWidth={unref(getCollapsedWidth)} 106 collapsedWidth={unref(getCollapsedWidth)}
106 theme={unref(getMenuTheme)} 107 theme={unref(getMenuTheme)}
107 onCollapse={onCollapseChange} 108 onCollapse={onCollapseChange}
src/layouts/default/sider/index.less
@@ -44,27 +44,6 @@ @@ -44,27 +44,6 @@
44 z-index: 10; 44 z-index: 10;
45 } 45 }
46 46
47 - &__darg-bar {  
48 - position: absolute;  
49 - top: 0;  
50 - right: -2px;  
51 - z-index: @side-drag-z-index;  
52 - width: 2px;  
53 - height: 100%;  
54 - cursor: col-resize;  
55 - border-top: none;  
56 - border-bottom: none;  
57 -  
58 - &.hide {  
59 - display: none;  
60 - }  
61 -  
62 - &:hover {  
63 - background: @primary-color;  
64 - box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.15);  
65 - }  
66 - }  
67 -  
68 & .ant-layout-sider-trigger { 47 & .ant-layout-sider-trigger {
69 height: 36px; 48 height: 36px;
70 line-height: 36px; 49 line-height: 36px;
src/layouts/default/sider/index.vue 0 → 100644
  1 +<template>
  2 + <Drawer
  3 + v-if="getIsMobile"
  4 + placement="left"
  5 + :class="prefixCls"
  6 + :width="getMenuWidth"
  7 + :getContainer="null"
  8 + :visible="!getCollapsed"
  9 + @close="handleClose"
  10 + >
  11 + <Sider />
  12 + </Drawer>
  13 + <Sider v-else />
  14 +</template>
  15 +<script lang="ts">
  16 + import { defineComponent } from 'vue';
  17 +
  18 + import Sider from './LayoutSider';
  19 + import { Drawer } from 'ant-design-vue';
  20 + import { useAppInject } from '/@/hooks/web/useAppInject';
  21 + import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
  22 + import { useDesign } from '/@/hooks/web/useDesign';
  23 + export default defineComponent({
  24 + name: 'SiderWrapper',
  25 + components: { Sider, Drawer },
  26 + setup() {
  27 + const { prefixCls } = useDesign('layout-sider-wrapper');
  28 + const { getIsMobile } = useAppInject();
  29 + const { setMenuSetting, getCollapsed, getMenuWidth } = useMenuSetting();
  30 +
  31 + function handleClose() {
  32 + setMenuSetting({
  33 + collapsed: true,
  34 + });
  35 + }
  36 +
  37 + return { prefixCls, getIsMobile, getCollapsed, handleClose, getMenuWidth };
  38 + },
  39 + });
  40 +</script>
  41 +<style lang="less">
  42 + @import (reference) '../../../design/index.less';
  43 + @prefix-cls: ~'@{namespace}-layout-sider-wrapper';
  44 + .@{prefix-cls} {
  45 + .ant-drawer-body {
  46 + height: 100vh;
  47 + padding: 0;
  48 + }
  49 +
  50 + .ant-drawer-header-no-title {
  51 + display: none;
  52 + }
  53 + }
  54 +</style>
src/layouts/default/sider/useLayoutSider.tsx
@@ -42,12 +42,17 @@ export function useSiderEvent() { @@ -42,12 +42,17 @@ export function useSiderEvent() {
42 /** 42 /**
43 * Handle related operations of menu folding 43 * Handle related operations of menu folding
44 */ 44 */
45 -export function useTrigger() {  
46 - const { getTrigger } = useMenuSetting(); 45 +export function useTrigger(getIsMobile: Ref<boolean>) {
  46 + const { getTrigger, getSplit } = useMenuSetting();
47 47
48 const showTrigger = computed(() => { 48 const showTrigger = computed(() => {
49 const trigger = unref(getTrigger); 49 const trigger = unref(getTrigger);
50 - return trigger !== TriggerEnum.NONE && trigger === TriggerEnum.FOOTER; 50 +
  51 + return (
  52 + trigger !== TriggerEnum.NONE &&
  53 + !unref(getIsMobile) &&
  54 + (trigger === TriggerEnum.FOOTER || unref(getSplit))
  55 + );
51 }); 56 });
52 57
53 const getTriggerAttr = computed(() => { 58 const getTriggerAttr = computed(() => {
@@ -77,14 +82,7 @@ export function useTrigger() { @@ -77,14 +82,7 @@ export function useTrigger() {
77 * @param dragBarRef 82 * @param dragBarRef
78 */ 83 */
79 export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) { 84 export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) {
80 - const { getMiniWidthNumber, getCollapsed, setMenuSetting, getCanDrag } = useMenuSetting();  
81 -  
82 - const getDragBarStyle = computed(() => {  
83 - if (unref(getCollapsed)) {  
84 - return { left: `${unref(getMiniWidthNumber)}px` };  
85 - }  
86 - return {};  
87 - }); 85 + const { getMiniWidthNumber, getCollapsed, setMenuSetting } = useMenuSetting();
88 86
89 onMounted(() => { 87 onMounted(() => {
90 nextTick(() => { 88 nextTick(() => {
@@ -93,16 +91,6 @@ export function useDragLine(siderRef: Ref&lt;any&gt;, dragBarRef: Ref&lt;any&gt;) { @@ -93,16 +91,6 @@ export function useDragLine(siderRef: Ref&lt;any&gt;, dragBarRef: Ref&lt;any&gt;) {
93 }); 91 });
94 }); 92 });
95 93
96 - function renderDragLine() {  
97 - return (  
98 - <div  
99 - class={[`layout-sidebar__darg-bar`, { hide: !unref(getCanDrag) }]}  
100 - style={unref(getDragBarStyle)}  
101 - ref={dragBarRef}  
102 - />  
103 - );  
104 - }  
105 -  
106 function handleMouseMove(ele: HTMLElement, wrap: HTMLElement, clientX: number) { 94 function handleMouseMove(ele: HTMLElement, wrap: HTMLElement, clientX: number) {
107 document.onmousemove = function (innerE) { 95 document.onmousemove = function (innerE) {
108 let iT = (ele as any).left + (innerE.clientX - clientX); 96 let iT = (ele as any).left + (innerE.clientX - clientX);
@@ -138,21 +126,22 @@ export function useDragLine(siderRef: Ref&lt;any&gt;, dragBarRef: Ref&lt;any&gt;) { @@ -138,21 +126,22 @@ export function useDragLine(siderRef: Ref&lt;any&gt;, dragBarRef: Ref&lt;any&gt;) {
138 } 126 }
139 127
140 function changeWrapWidth() { 128 function changeWrapWidth() {
141 - const ele = unref(dragBarRef) as any; 129 + const ele = unref(dragBarRef)?.$el;
  130 + if (!ele) {
  131 + return;
  132 + }
142 const side = unref(siderRef); 133 const side = unref(siderRef);
143 -  
144 const wrap = (side || {}).$el; 134 const wrap = (side || {}).$el;
145 - ele &&  
146 - (ele.onmousedown = (e: any) => {  
147 - wrap.style.transition = 'unset';  
148 - const clientX = e?.clientX;  
149 - ele.left = ele.offsetLeft;  
150 - handleMouseMove(ele, wrap, clientX);  
151 - removeMouseup(ele);  
152 - ele.setCapture?.();  
153 - return false;  
154 - }); 135 + ele.onmousedown = (e: any) => {
  136 + wrap.style.transition = 'unset';
  137 + const clientX = e?.clientX;
  138 + ele.left = ele.offsetLeft;
  139 + handleMouseMove(ele, wrap, clientX);
  140 + removeMouseup(ele);
  141 + ele.setCapture?.();
  142 + return false;
  143 + };
155 } 144 }
156 145
157 - return { renderDragLine }; 146 + return {};
158 } 147 }
src/layouts/default/useLayoutContext.ts deleted 100644 → 0
1 -import { InjectionKey, Ref } from 'vue';  
2 -import { createContext, useContext } from '/@/hooks/core/useContext';  
3 -  
4 -export interface LayoutContextProps {  
5 - fullHeader: Ref<ComponentRef>;  
6 -}  
7 -  
8 -const key: InjectionKey<LayoutContextProps> = Symbol();  
9 -  
10 -export function createLayoutContext(context: LayoutContextProps) {  
11 - return createContext<LayoutContextProps>(context, key);  
12 -}  
13 -  
14 -export function useLayoutContext() {  
15 - return useContext<LayoutContextProps>(key);  
16 -}  
src/locales/lang/en/component/app.ts
1 export default { 1 export default {
2 search: 'Search', 2 search: 'Search',
  3 + cancel: 'Cancel',
3 searchNotData: 'No search results yet', 4 searchNotData: 'No search results yet',
4 toSearch: 'to search', 5 toSearch: 'to search',
5 toNavigate: 'to navigate', 6 toNavigate: 'to navigate',
src/locales/lang/zh_CN/component/app.ts
1 export default { 1 export default {
2 search: '搜索', 2 search: '搜索',
  3 + cancel: '取消',
3 searchNotData: '暂无搜索结果', 4 searchNotData: '暂无搜索结果',
4 toSearch: '确认', 5 toSearch: '确认',
5 toNavigate: '切换', 6 toNavigate: '切换',
src/logics/mitt/tabChange.ts
@@ -13,8 +13,9 @@ const key = Symbol(); @@ -13,8 +13,9 @@ const key = Symbol();
13 let lastChangeTab: RouteLocationNormalized; 13 let lastChangeTab: RouteLocationNormalized;
14 14
15 export function setLastChangeTab(lastChangeRoute: RouteLocationNormalized) { 15 export function setLastChangeTab(lastChangeRoute: RouteLocationNormalized) {
16 - mitt.emit(key, getRoute(lastChangeRoute));  
17 - lastChangeTab = getRoute(lastChangeRoute); 16 + const r = getRoute(lastChangeRoute);
  17 + mitt.emit(key, r);
  18 + lastChangeTab = r;
18 } 19 }
19 20
20 export function listenerLastChangeTab( 21 export function listenerLastChangeTab(
src/main.ts
@@ -51,6 +51,5 @@ if (isDevMode()) { @@ -51,6 +51,5 @@ if (isDevMode()) {
51 if (isProdMode() && isUseMock()) { 51 if (isProdMode() && isUseMock()) {
52 setupProdMockServer(); 52 setupProdMockServer();
53 } 53 }
54 -  
55 // Used to share app instances in other modules 54 // Used to share app instances in other modules
56 setApp(app); 55 setApp(app);
src/router/helper/menuHelper.ts
1 import { AppRouteModule } from '/@/router/types.d'; 1 import { AppRouteModule } from '/@/router/types.d';
2 import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types'; 2 import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types';
3 3
4 -import { findPath, forEach, treeMap, treeToList } from '/@/utils/helper/treeHelper'; 4 +import { findPath, forEach, treeMap } from '/@/utils/helper/treeHelper';
5 import { cloneDeep } from 'lodash-es'; 5 import { cloneDeep } from 'lodash-es';
6 import { isUrl } from '/@/utils/is'; 6 import { isUrl } from '/@/utils/is';
7 7
@@ -10,10 +10,6 @@ export function getAllParentPath(treeData: any[], path: string) { @@ -10,10 +10,6 @@ export function getAllParentPath(treeData: any[], path: string) {
10 return (menuList || []).map((item) => item.path); 10 return (menuList || []).map((item) => item.path);
11 } 11 }
12 12
13 -export function flatMenus(menus: Menu[]) {  
14 - return treeToList(menus);  
15 -}  
16 -  
17 // 拼接父级路径 13 // 拼接父级路径
18 function joinParentPath(list: any, node: any) { 14 function joinParentPath(list: any, node: any) {
19 let allPaths = getAllParentPath(list, node.path); 15 let allPaths = getAllParentPath(list, node.path);
src/router/helper/routeHelper.ts
@@ -5,6 +5,10 @@ import { getParentLayout, LAYOUT } from &#39;/@/router/constant&#39;; @@ -5,6 +5,10 @@ import { getParentLayout, LAYOUT } from &#39;/@/router/constant&#39;;
5 import dynamicImport from './dynamicImport'; 5 import dynamicImport from './dynamicImport';
6 import { cloneDeep } from 'lodash-es'; 6 import { cloneDeep } from 'lodash-es';
7 7
  8 +export type LayoutMapKey = 'LAYOUT';
  9 +
  10 +const LayoutMap = new Map<LayoutMapKey, () => Promise<typeof import('*.vue')>>();
  11 +
8 // 动态引入 12 // 动态引入
9 function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { 13 function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
10 if (!routes) return; 14 if (!routes) return;
@@ -20,16 +24,14 @@ function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { @@ -20,16 +24,14 @@ function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
20 }); 24 });
21 } 25 }
22 26
23 -function getLayoutComp(comp: string) {  
24 - return comp === 'LAYOUT' ? LAYOUT : '';  
25 -}  
26 -  
27 // Turn background objects into routing objects 27 // Turn background objects into routing objects
28 export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]): T[] { 28 export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]): T[] {
  29 + LayoutMap.set('LAYOUT', LAYOUT);
  30 +
29 routeList.forEach((route) => { 31 routeList.forEach((route) => {
30 if (route.component) { 32 if (route.component) {
31 if ((route.component as string).toUpperCase() === 'LAYOUT') { 33 if ((route.component as string).toUpperCase() === 'LAYOUT') {
32 - route.component = getLayoutComp(route.component); 34 + route.component = LayoutMap.get(route.component);
33 } else { 35 } else {
34 route.children = [cloneDeep(route)]; 36 route.children = [cloneDeep(route)];
35 route.component = LAYOUT; 37 route.component = LAYOUT;
@@ -46,16 +48,6 @@ export function transformObjToRoute&lt;T = AppRouteModule&gt;(routeList: AppRouteModul @@ -46,16 +48,6 @@ export function transformObjToRoute&lt;T = AppRouteModule&gt;(routeList: AppRouteModul
46 return (routeList as unknown) as T[]; 48 return (routeList as unknown) as T[];
47 } 49 }
48 50
49 -export function getParams(data: any = {}) {  
50 - const { params = {} } = data;  
51 - let ret = '';  
52 - Object.keys(params).forEach((key) => {  
53 - const p = params[key];  
54 - ret += `/${p}`;  
55 - });  
56 - return ret;  
57 -}  
58 -  
59 // Return to the new routing structure, not affected by the original example 51 // Return to the new routing structure, not affected by the original example
60 export function getRoute(route: RouteLocationNormalized): RouteLocationNormalized { 52 export function getRoute(route: RouteLocationNormalized): RouteLocationNormalized {
61 if (!route) return route; 53 if (!route) return route;
src/router/menus/index.ts
1 import type { Menu, MenuModule } from '/@/router/types'; 1 import type { Menu, MenuModule } from '/@/router/types';
2 import type { RouteRecordNormalized } from 'vue-router'; 2 import type { RouteRecordNormalized } from 'vue-router';
  3 +
3 import { appStore } from '/@/store/modules/app'; 4 import { appStore } from '/@/store/modules/app';
4 import { permissionStore } from '/@/store/modules/permission'; 5 import { permissionStore } from '/@/store/modules/permission';
5 -import { transformMenuModule, flatMenus, getAllParentPath } from '/@/router/helper/menuHelper'; 6 +import { transformMenuModule, getAllParentPath } from '/@/router/helper/menuHelper';
6 import { filter } from '/@/utils/helper/treeHelper'; 7 import { filter } from '/@/utils/helper/treeHelper';
7 import router from '/@/router'; 8 import router from '/@/router';
8 import { PermissionModeEnum } from '/@/enums/appEnum'; 9 import { PermissionModeEnum } from '/@/enums/appEnum';
@@ -10,6 +11,8 @@ import { pathToRegexp } from &#39;path-to-regexp&#39;; @@ -10,6 +11,8 @@ import { pathToRegexp } from &#39;path-to-regexp&#39;;
10 11
11 import modules from 'globby!/@/router/menus/modules/**/*.@(ts)'; 12 import modules from 'globby!/@/router/menus/modules/**/*.@(ts)';
12 13
  14 +const reg = /(((https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
  15 +
13 const menuModules: MenuModule[] = []; 16 const menuModules: MenuModule[] = [];
14 17
15 Object.keys(modules).forEach((key) => { 18 Object.keys(modules).forEach((key) => {
@@ -38,18 +41,9 @@ const staticMenus: Menu[] = []; @@ -38,18 +41,9 @@ const staticMenus: Menu[] = [];
38 41
39 async function getAsyncMenus() { 42 async function getAsyncMenus() {
40 // 前端角色控制菜单 直接取菜单文件 43 // 前端角色控制菜单 直接取菜单文件
41 - if (!isBackMode()) {  
42 - return staticMenus;  
43 - }  
44 - return permissionStore.getBackMenuListState; 44 + return !isBackMode() ? staticMenus : permissionStore.getBackMenuListState;
45 } 45 }
46 46
47 -// 获取深层扁平化菜单  
48 -export const getFlatMenus = async (): Promise<Menu[]> => {  
49 - const menus = await getAsyncMenus();  
50 - return flatMenus(menus);  
51 -};  
52 -  
53 // 获取菜单 树级 47 // 获取菜单 树级
54 export const getMenus = async (): Promise<Menu[]> => { 48 export const getMenus = async (): Promise<Menu[]> => {
55 const menus = await getAsyncMenus(); 49 const menus = await getAsyncMenus();
@@ -61,7 +55,7 @@ export const getMenus = async (): Promise&lt;Menu[]&gt; =&gt; { @@ -61,7 +55,7 @@ export const getMenus = async (): Promise&lt;Menu[]&gt; =&gt; {
61 export async function getCurrentParentPath(currentPath: string) { 55 export async function getCurrentParentPath(currentPath: string) {
62 const menus = await getAsyncMenus(); 56 const menus = await getAsyncMenus();
63 const allParentPath = await getAllParentPath(menus, currentPath); 57 const allParentPath = await getAllParentPath(menus, currentPath);
64 - return allParentPath[0]; 58 + return allParentPath?.[0];
65 } 59 }
66 60
67 // 获取1级菜单,删除children 61 // 获取1级菜单,删除children
@@ -81,27 +75,24 @@ export async function getChildrenMenus(parentPath: string) { @@ -81,27 +75,24 @@ export async function getChildrenMenus(parentPath: string) {
81 return parent.children; 75 return parent.children;
82 } 76 }
83 77
84 -// 扁平化children  
85 -export async function getFlatChildrenMenus(children: Menu[]) {  
86 - return flatMenus(children);  
87 -}  
88 -  
89 // 通用过滤方法 78 // 通用过滤方法
90 function basicFilter(routes: RouteRecordNormalized[]) { 79 function basicFilter(routes: RouteRecordNormalized[]) {
91 return (menu: Menu) => { 80 return (menu: Menu) => {
92 const matchRoute = routes.find((route) => { 81 const matchRoute = routes.find((route) => {
93 - if (route.meta.externalLink) { 82 + const match = route.path.match(reg)?.[0];
  83 + if (match && match === menu.path) {
94 return true; 84 return true;
95 } 85 }
96 86
97 - if (route.meta) {  
98 - if (route.meta.carryParam) {  
99 - return pathToRegexp(route.path).test(menu.path);  
100 - }  
101 - if (route.meta.ignoreAuth) return true; 87 + if (route.meta?.carryParam) {
  88 + return pathToRegexp(route.path).test(menu.path);
102 } 89 }
  90 + const isSame = route.path === menu.path;
  91 + if (!isSame) return false;
  92 +
  93 + if (route.meta?.ignoreAuth) return true;
103 94
104 - return route.path === menu.path; 95 + return isSame || pathToRegexp(route.path).test(menu.path);
105 }); 96 });
106 97
107 if (!matchRoute) return false; 98 if (!matchRoute) return false;
src/router/routes/modules/demo/iframe.ts
@@ -38,7 +38,6 @@ const iframe: AppRouteModule = { @@ -38,7 +38,6 @@ const iframe: AppRouteModule = {
38 name: 'DocExternal', 38 name: 'DocExternal',
39 component: IFrame, 39 component: IFrame,
40 meta: { 40 meta: {
41 - externalLink: true,  
42 title: t('routes.demo.iframe.docExternal'), 41 title: t('routes.demo.iframe.docExternal'),
43 }, 42 },
44 }, 43 },
src/router/types.d.ts
@@ -15,9 +15,6 @@ export interface RouteMeta { @@ -15,9 +15,6 @@ export interface RouteMeta {
15 // icon on tab 15 // icon on tab
16 icon?: string; 16 icon?: string;
17 // Jump address 17 // Jump address
18 - frameSrc?: string;  
19 - // Outer link jump address  
20 - externalLink?: boolean;  
21 18
22 // current page transition 19 // current page transition
23 transitionName?: string; 20 transitionName?: string;
src/settings/projectSetting.ts
@@ -89,7 +89,7 @@ const setting: ProjectConfig = { @@ -89,7 +89,7 @@ const setting: ProjectConfig = {
89 // Whether to show no dom 89 // Whether to show no dom
90 show: true, 90 show: true,
91 // Whether to show dom 91 // Whether to show dom
92 - hidden: true, 92 + hidden: false,
93 // Menu width 93 // Menu width
94 menuWidth: 210, 94 menuWidth: 210,
95 // Menu mode 95 // Menu mode
src/utils/is.ts
@@ -50,7 +50,7 @@ export function isRegExp(val: unknown): val is RegExp { @@ -50,7 +50,7 @@ export function isRegExp(val: unknown): val is RegExp {
50 return is(val, 'RegExp'); 50 return is(val, 'RegExp');
51 } 51 }
52 52
53 -export function isArray(val: unknown): val is Array<any> { 53 +export function isArray(val: any): val is Array<any> {
54 return val && Array.isArray(val); 54 return val && Array.isArray(val);
55 } 55 }
56 56
src/views/sys/lock/LockPage.vue
@@ -221,25 +221,35 @@ @@ -221,25 +221,35 @@
221 font-size: 23em; 221 font-size: 23em;
222 } 222 }
223 @media (min-width: @screen-sm-max) and (max-width: @screen-md-max) { 223 @media (min-width: @screen-sm-max) and (max-width: @screen-md-max) {
224 - font-size: 19em; 224 + height: 50%;
  225 + font-size: 12em;
  226 + border-radius: 10px;
  227 +
  228 + .meridiem {
  229 + font-size: 20px;
  230 + }
225 } 231 }
226 @media (min-width: @screen-xs-max) and (max-width: @screen-sm-max) { 232 @media (min-width: @screen-xs-max) and (max-width: @screen-sm-max) {
227 font-size: 13em; 233 font-size: 13em;
228 } 234 }
229 @media (max-width: @screen-xs) { 235 @media (max-width: @screen-xs) {
230 - height: 50%;  
231 - font-size: 6em;  
232 - border-radius: 20px; 236 + height: 30%;
  237 + font-size: 5em;
  238 + border-radius: 10px;
  239 +
  240 + .meridiem {
  241 + font-size: 14px;
  242 + }
233 } 243 }
234 } 244 }
235 245
236 &__footer-date { 246 &__footer-date {
237 position: absolute; 247 position: absolute;
238 bottom: 20px; 248 bottom: 20px;
239 - left: 50%; 249 + width: 100%;
240 font-family: helvetica; 250 font-family: helvetica;
241 color: #bababa; 251 color: #bababa;
242 - transform: translate(-50%, 0); 252 + text-align: center;
243 253
244 .time { 254 .time {
245 font-size: 50px; 255 font-size: 50px;
yarn.lock
@@ -1051,43 +1051,43 @@ @@ -1051,43 +1051,43 @@
1051 resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.272.tgz#27c7caee9764e0304161261ec08ffc2794944b66" 1051 resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.272.tgz#27c7caee9764e0304161261ec08ffc2794944b66"
1052 integrity sha512-FyiTc7UiXJ5cDfk09lv70sYOSi5uLyK+a0LnF1KgWmofkikL06p98ksNRN7stmHryOYarSy75xgi6MbgAwtltQ== 1052 integrity sha512-FyiTc7UiXJ5cDfk09lv70sYOSi5uLyK+a0LnF1KgWmofkikL06p98ksNRN7stmHryOYarSy75xgi6MbgAwtltQ==
1053 1053
1054 -"@intlify/core@9.0.0-beta.12":  
1055 - version "9.0.0-beta.12"  
1056 - resolved "https://registry.npmjs.org/@intlify/core/-/core-9.0.0-beta.12.tgz#f7d2d09060b8e00ae37157e00a0daa1c86290802"  
1057 - integrity sha512-0wdOS9d0ZEvGkbNIdaxEHQQOfAIuhv1Q8CSpNImThh8ZDD+5Sa38wTerHBO0/Rk0HfHUP/hjPqbxxRqITmSo1g==  
1058 - dependencies:  
1059 - "@intlify/message-compiler" "9.0.0-beta.12"  
1060 - "@intlify/message-resolver" "9.0.0-beta.12"  
1061 - "@intlify/runtime" "9.0.0-beta.12"  
1062 - "@intlify/shared" "9.0.0-beta.12"  
1063 -  
1064 -"@intlify/message-compiler@9.0.0-beta.12":  
1065 - version "9.0.0-beta.12"  
1066 - resolved "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.0.0-beta.12.tgz#836a49cfd057ecb2c536680cc01aa16693211891"  
1067 - integrity sha512-EMzBDBIsFvWV9w0tRAHzn2BD1C7nkJkXYwDWinROmoL6C4jgKUgon+9Uxp7lV0H1E+7hUfhGj6zHdtJrwFhH+g==  
1068 - dependencies:  
1069 - "@intlify/message-resolver" "9.0.0-beta.12"  
1070 - "@intlify/shared" "9.0.0-beta.12" 1054 +"@intlify/core-base@9.0.0-beta.13":
  1055 + version "9.0.0-beta.13"
  1056 + resolved "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.0.0-beta.13.tgz#fb6bc278209cb7bef44853a42160fedb0560c3f8"
  1057 + integrity sha512-ukImWV+QvRmNZtCTLrSW391z46eMuBheCMPZh801nM3v0Dosfu2PtWO5/z8Q9Bsom4Q+PNQ5eBtOQj2yCAhVEA==
  1058 + dependencies:
  1059 + "@intlify/message-compiler" "9.0.0-beta.13"
  1060 + "@intlify/message-resolver" "9.0.0-beta.13"
  1061 + "@intlify/runtime" "9.0.0-beta.13"
  1062 + "@intlify/shared" "9.0.0-beta.13"
  1063 +
  1064 +"@intlify/message-compiler@9.0.0-beta.13":
  1065 + version "9.0.0-beta.13"
  1066 + resolved "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.0.0-beta.13.tgz#3b8ddcb2be3f80b28c6e4f6028c0b3ec4e709849"
  1067 + integrity sha512-1z7716InFM8FdTAz64wqZvFuT4wL7WKF63v+vUEW4s9FLoL0U+xIccor9P5XHAvvG1gPMH/Zxd0deg/ULZ1Mcg==
  1068 + dependencies:
  1069 + "@intlify/message-resolver" "9.0.0-beta.13"
  1070 + "@intlify/shared" "9.0.0-beta.13"
1071 source-map "0.6.1" 1071 source-map "0.6.1"
1072 1072
1073 -"@intlify/message-resolver@9.0.0-beta.12":  
1074 - version "9.0.0-beta.12"  
1075 - resolved "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.0.0-beta.12.tgz#98cf346f5da0fdf3408ba132c24841295a4e02db"  
1076 - integrity sha512-i8bmWzhiBH59YED3SXqvdUfwecl7OUPOU/8yvfdhg2rXuZ4e2chCPnLpPafXz6bi88HcRsWF4aRGlpwDVDYadg== 1073 +"@intlify/message-resolver@9.0.0-beta.13":
  1074 + version "9.0.0-beta.13"
  1075 + resolved "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.0.0-beta.13.tgz#ae6de0bf0e54093160442d465e719bf03fd0f146"
  1076 + integrity sha512-mR1eSpRtB4jh11TpQTUyzjEwqZ6D30mJYREEfSrl5YKfUKwDQrulrOaIO8T5gVQG2m09vfxJHVrgfJ2hR8z/0Q==
1077 1077
1078 -"@intlify/runtime@9.0.0-beta.12":  
1079 - version "9.0.0-beta.12"  
1080 - resolved "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.0.0-beta.12.tgz#647a62a326d92690569798ef046d29e8daa25c96"  
1081 - integrity sha512-4ucZHqk/VGhrQEgu9xU5tE/sJTNfqKBhQtaXyEgYHchL9PvLoS1HFwPjABHvWjo3aVcv4d2cGtUPBwH4oLROKA== 1078 +"@intlify/runtime@9.0.0-beta.13":
  1079 + version "9.0.0-beta.13"
  1080 + resolved "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.0.0-beta.13.tgz#8deff103ee6982c6d531314e9f965b90768d8a27"
  1081 + integrity sha512-hcb3sg75SokuzNDG8IC6PJmwjsS/xdgevd99UNG1zKb7s5qFFb90ApvPDpiH0+R9TMQe11fZqg5dyrVBKqAV4A==
1082 dependencies: 1082 dependencies:
1083 - "@intlify/message-compiler" "9.0.0-beta.12"  
1084 - "@intlify/message-resolver" "9.0.0-beta.12"  
1085 - "@intlify/shared" "9.0.0-beta.12" 1083 + "@intlify/message-compiler" "9.0.0-beta.13"
  1084 + "@intlify/message-resolver" "9.0.0-beta.13"
  1085 + "@intlify/shared" "9.0.0-beta.13"
1086 1086
1087 -"@intlify/shared@9.0.0-beta.12":  
1088 - version "9.0.0-beta.12"  
1089 - resolved "https://registry.npmjs.org/@intlify/shared/-/shared-9.0.0-beta.12.tgz#e939575bc4047411b9fc65347779f5b3173c1130"  
1090 - integrity sha512-XtHAzQ2KBcdN0Khc7ZDCo5GnKQK4Vv0GKD1BplCWntpA2d5XqjdDpFuKumvbiOjPvYtuCFnksJU0OgJiCWG+KQ== 1087 +"@intlify/shared@9.0.0-beta.13":
  1088 + version "9.0.0-beta.13"
  1089 + resolved "https://registry.npmjs.org/@intlify/shared/-/shared-9.0.0-beta.13.tgz#2d93d695f19fd699ea8b336066f9d6dfc185f094"
  1090 + integrity sha512-/rqC3YEGHs3uu3XSsF1zdBKJb+on34Yn8Z58K3YxJsFxKPHa8mH73EUtN79hTZWh6Js4zEa/WsCgZCM62b8eJA==
1091 1091
1092 "@koa/cors@^3.1.0": 1092 "@koa/cors@^3.1.0":
1093 version "3.1.0" 1093 version "3.1.0"
@@ -8256,16 +8256,13 @@ vue-eslint-parser@^7.3.0: @@ -8256,16 +8256,13 @@ vue-eslint-parser@^7.3.0:
8256 esquery "^1.0.1" 8256 esquery "^1.0.1"
8257 lodash "^4.17.15" 8257 lodash "^4.17.15"
8258 8258
8259 -vue-i18n@^9.0.0-beta.12:  
8260 - version "9.0.0-beta.12"  
8261 - resolved "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.0.0-beta.12.tgz#f6e2fc1cc366b8f16aa4754642931e937ebde303"  
8262 - integrity sha512-hDnr+GsIGCIKRtZsdDczkhqyzbpLuPgEkH5bQyMzrKTLelXipLvIVmUCAsSjyR7xMHDCwP6AwVTIZwk6ENXkwg== 8259 +vue-i18n@^9.0.0-beta.13:
  8260 + version "9.0.0-beta.13"
  8261 + resolved "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.0.0-beta.13.tgz#89cf5dd1566025f441132231d15ed621ef70ba96"
  8262 + integrity sha512-ZN6r5ITODu9NYAAbe1IGVUkNeamuleaXTLn5NMn/YZQ+5NSjDjysyVZVLkVOEOIw6bT2tLveyjsWlAZBVtfcPw==
8263 dependencies: 8263 dependencies:
8264 - "@intlify/core" "9.0.0-beta.12"  
8265 - "@intlify/message-compiler" "9.0.0-beta.12"  
8266 - "@intlify/message-resolver" "9.0.0-beta.12"  
8267 - "@intlify/runtime" "9.0.0-beta.12"  
8268 - "@intlify/shared" "9.0.0-beta.12" 8264 + "@intlify/core-base" "9.0.0-beta.13"
  8265 + "@intlify/shared" "9.0.0-beta.13"
8269 "@vue/devtools-api" "^6.0.0-beta.2" 8266 "@vue/devtools-api" "^6.0.0-beta.2"
8270 8267
8271 vue-router@^4.0.1: 8268 vue-router@^4.0.1: