Commit c774a6d3a03d9507a9023d600aa9dd9592f52fb3
1 parent
683d1f52
feat: support mobile layout adaptation
Showing
34 changed files
with
583 additions
and
624 deletions
CHANGELOG.zh_CN.md
... | ... | @@ -3,17 +3,21 @@ |
3 | 3 | ### ✨ Features |
4 | 4 | |
5 | 5 | - 移除左侧菜单搜索,新增顶部菜单搜索功能 |
6 | +- layout 移动端适配。页面未适配 | |
6 | 7 | |
7 | 8 | ### ⚡ Performance Improvements |
8 | 9 | |
9 | 10 | - 异步引入组件 |
10 | 11 | - 优化整体结构 |
12 | +- 替换菜单默认滚动条为滚动组件 | |
13 | +- 菜单性能优化 | |
11 | 14 | |
12 | 15 | ### 🎫 Chores |
13 | 16 | |
14 | 17 | - 返回顶部样式调整,避免遮住其他元素 |
15 | 18 | - 升级`ant-design-vue`到`2.0.0-rc.5` |
16 | 19 | - 刷新按钮布局调整 |
20 | +- `route.meta` 移除 `externalLink` 属性 | |
17 | 21 | |
18 | 22 | ### 🐛 Bug Fixes |
19 | 23 | ... | ... |
package.json
src/components/Application/src/search/AppSearch.vue
1 | 1 | <template> |
2 | - <div :class="prefixCls" v-if="getShowSearch" @click="handleSearch"> | |
2 | + <div :class="prefixCls" v-if="getShowSearch" @click.stop="handleSearch"> | |
3 | 3 | <Tooltip> |
4 | 4 | <template #title> {{ t('component.app.search') }} </template> |
5 | 5 | <SearchOutlined /> |
6 | 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 | 9 | </div> |
12 | 10 | </template> |
13 | 11 | <script lang="ts"> | ... | ... |
src/components/Application/src/search/AppSearchModal.vue
1 | 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 | 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 | 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 | 53 | </div> |
44 | - </ClickOutSide> | |
45 | - </div> | |
54 | + </transition> | |
55 | + </Teleport> | |
46 | 56 | </template> |
47 | 57 | <script lang="ts"> |
48 | 58 | import { defineComponent, computed, unref, ref } from 'vue'; |
... | ... | @@ -54,15 +64,20 @@ |
54 | 64 | import AppSearchFooter from './AppSearchFooter.vue'; |
55 | 65 | import { useI18n } from '/@/hooks/web/useI18n'; |
56 | 66 | import { ClickOutSide } from '/@/components/ClickOutSide'; |
67 | + import { useAppInject } from '/@/hooks/web/useAppInject'; | |
57 | 68 | export default defineComponent({ |
58 | 69 | name: 'AppSearchModal', |
59 | 70 | components: { SearchOutlined, ClickOutSide, AppSearchFooter }, |
60 | 71 | emits: ['close'], |
72 | + props: { | |
73 | + visible: Boolean, | |
74 | + }, | |
61 | 75 | setup(_, { emit }) { |
62 | 76 | const scrollWrap = ref<ElRef>(null); |
63 | 77 | const { prefixCls } = useDesign('app-search-modal'); |
64 | 78 | const { t } = useI18n(); |
65 | 79 | const [refs, setRefs] = useRefs(); |
80 | + const { getIsMobile } = useAppInject(); | |
66 | 81 | |
67 | 82 | const { |
68 | 83 | handleSearch, |
... | ... | @@ -77,9 +92,19 @@ |
77 | 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 | 104 | return { |
81 | 105 | t, |
82 | 106 | prefixCls, |
107 | + getClass, | |
83 | 108 | handleSearch, |
84 | 109 | searchResult, |
85 | 110 | activeIndex, |
... | ... | @@ -98,12 +123,12 @@ |
98 | 123 | <style lang="less" scoped> |
99 | 124 | @import (reference) '../../../../design/index.less'; |
100 | 125 | @prefix-cls: ~'@{namespace}-app-search-modal'; |
101 | - | |
126 | + @footer-prefix-cls: ~'@{namespace}-app-search-footer'; | |
102 | 127 | .@{prefix-cls} { |
103 | 128 | position: fixed; |
104 | 129 | top: 0; |
105 | 130 | left: 0; |
106 | - z-index: 100; | |
131 | + z-index: 800; | |
107 | 132 | display: flex; |
108 | 133 | width: 100%; |
109 | 134 | height: 100%; |
... | ... | @@ -113,6 +138,43 @@ |
113 | 138 | justify-content: center; |
114 | 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 | 178 | &-content { |
117 | 179 | position: relative; |
118 | 180 | width: 532px; |
... | ... | @@ -124,10 +186,16 @@ |
124 | 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 | 196 | &-input { |
128 | - width: calc(100% - 28px); | |
197 | + width: 100%; | |
129 | 198 | height: 56px; |
130 | - margin: 14px 14px 0 14px; | |
131 | 199 | font-size: 1.5em; |
132 | 200 | color: #1c1e21; |
133 | 201 | |
... | ... | @@ -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 | 213 | &-not-data { |
140 | 214 | display: flex; |
141 | 215 | width: 100%; | ... | ... |
src/components/Menu/src/BasicMenu.vue
... | ... | @@ -151,7 +151,7 @@ |
151 | 151 | if (props.mode !== MenuModeEnum.HORIZONTAL) { |
152 | 152 | setOpenKeys(path); |
153 | 153 | } |
154 | - if (unref(getIsTopMenu)) { | |
154 | + if (props.isHorizontal && unref(getSplit)) { | |
155 | 155 | const parentPath = await getCurrentParentPath(path); |
156 | 156 | menuState.selectedKeys = [parentPath]; |
157 | 157 | } else { | ... | ... |
src/hooks/setting/useMenuSetting.ts
... | ... | @@ -50,11 +50,7 @@ const getShowTopMenu = computed(() => { |
50 | 50 | }); |
51 | 51 | |
52 | 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 | 54 | return false; |
59 | 55 | } |
60 | 56 | |
... | ... | @@ -79,7 +75,11 @@ const getMiniWidthNumber = computed(() => { |
79 | 75 | }); |
80 | 76 | |
81 | 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 | 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 | 2 | @header-trigger-prefix-cls: ~'@{namespace}-layout-header-trigger'; |
3 | 3 | @header-prefix-cls: ~'@{namespace}-layout-header'; |
4 | 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 | 8 | .@{header-prefix-cls} { |
7 | 9 | display: flex; |
... | ... | @@ -14,6 +16,30 @@ |
14 | 16 | align-items: center; |
15 | 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 | 43 | &--fixed { |
18 | 44 | position: fixed; |
19 | 45 | top: 0; |
... | ... | @@ -78,7 +104,7 @@ |
78 | 104 | |
79 | 105 | &-action { |
80 | 106 | display: flex; |
81 | - min-width: 200px; | |
107 | + min-width: 180px; | |
82 | 108 | padding-right: 12px; |
83 | 109 | align-items: center; |
84 | 110 | ... | ... |
src/layouts/default/header/index.vue
... | ... | @@ -3,17 +3,17 @@ |
3 | 3 | <!-- left start --> |
4 | 4 | <div :class="`${prefixCls}-left`"> |
5 | 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 | 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 | 13 | :theme="getHeaderTheme" |
14 | + :sider="false" | |
16 | 15 | /> |
16 | + <LayoutBreadcrumb v-if="getShowContent && getShowBread" :theme="getHeaderTheme" /> | |
17 | 17 | </div> |
18 | 18 | <!-- left end --> |
19 | 19 | |
... | ... | @@ -30,15 +30,15 @@ |
30 | 30 | |
31 | 31 | <!-- action --> |
32 | 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 | 43 | <UserDropDown :theme="getHeaderTheme" /> |
44 | 44 | |
... | ... | @@ -123,7 +123,11 @@ |
123 | 123 | const theme = unref(getHeaderTheme); |
124 | 124 | return [ |
125 | 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 | 149 | getShowBread, |
146 | 150 | getShowContent, |
147 | 151 | getSplitType, |
152 | + getSplit, | |
148 | 153 | getMenuMode, |
149 | 154 | getShowTopMenu, |
150 | 155 | getShowLocale, | ... | ... |
src/layouts/default/index.vue
1 | 1 | <template> |
2 | 2 | <Layout :class="prefixCls"> |
3 | 3 | <LayoutFeatures /> |
4 | - <LayoutHeader fixed ref="headerRef" v-if="getShowFullHeaderRef" /> | |
4 | + <LayoutHeader fixed v-if="getShowFullHeaderRef" /> | |
5 | 5 | <Layout> |
6 | - <LayoutSideBar v-if="getShowSidebar" /> | |
6 | + <LayoutSideBar v-if="getShowSidebar || getIsMobile" /> | |
7 | 7 | <Layout :class="`${prefixCls}__main`"> |
8 | 8 | <LayoutMultipleHeader /> |
9 | 9 | <LayoutContent /> |
... | ... | @@ -14,21 +14,21 @@ |
14 | 14 | </template> |
15 | 15 | |
16 | 16 | <script lang="ts"> |
17 | - import { defineComponent, ref } from 'vue'; | |
17 | + import { defineComponent } from 'vue'; | |
18 | 18 | import { Layout } from 'ant-design-vue'; |
19 | 19 | import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
20 | 20 | |
21 | 21 | import LayoutHeader from './header/index.vue'; |
22 | 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 | 26 | import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; |
27 | 27 | import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
28 | 28 | import { useDesign } from '/@/hooks/web/useDesign'; |
29 | - import { createLayoutContext } from './useLayoutContext'; | |
30 | 29 | |
31 | 30 | import { registerGlobComp } from '/@/components/registerGlobComp'; |
31 | + import { useAppInject } from '/@/hooks/web/useAppInject'; | |
32 | 32 | |
33 | 33 | export default defineComponent({ |
34 | 34 | name: 'DefaultLayout', |
... | ... | @@ -47,11 +47,9 @@ |
47 | 47 | // default layout It is loaded after login. So it won’t be packaged to the first screen |
48 | 48 | registerGlobComp(); |
49 | 49 | |
50 | - const headerRef = ref<ComponentRef>(null); | |
51 | - | |
52 | 50 | const { prefixCls } = useDesign('default-layout'); |
53 | 51 | |
54 | - createLayoutContext({ fullHeader: headerRef }); | |
52 | + const { getIsMobile } = useAppInject(); | |
55 | 53 | |
56 | 54 | const { getShowFullHeaderRef } = useHeaderSetting(); |
57 | 55 | |
... | ... | @@ -60,8 +58,8 @@ |
60 | 58 | return { |
61 | 59 | getShowFullHeaderRef, |
62 | 60 | getShowSidebar, |
63 | - headerRef, | |
64 | 61 | prefixCls, |
62 | + getIsMobile, | |
65 | 63 | }; |
66 | 64 | }, |
67 | 65 | }); | ... | ... |
src/layouts/default/menu/index.less
1 | 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 | 8 | height: @header-height; |
6 | 9 | padding: 10px 4px 10px 10px; |
7 | 10 | |
... | ... | @@ -10,4 +13,12 @@ |
10 | 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 | 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 | 6 | import { BasicMenu } from '/@/components/Menu'; |
7 | 7 | import { AppLogo } from '/@/components/Application'; |
8 | 8 | |
... | ... | @@ -17,7 +17,8 @@ import { openWindow } from '/@/utils'; |
17 | 17 | import { propTypes } from '/@/utils/propTypes'; |
18 | 18 | import { isUrl } from '/@/utils/is'; |
19 | 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 | 23 | export default defineComponent({ |
23 | 24 | name: 'LayoutMenu', |
... | ... | @@ -50,9 +51,15 @@ export default defineComponent({ |
50 | 51 | } = useMenuSetting(); |
51 | 52 | const { getShowLogo } = useRootSetting(); |
52 | 53 | |
54 | + const { prefixCls } = useDesign('layout-menu'); | |
55 | + | |
53 | 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 | 64 | const getComputedMenuTheme = computed(() => props.theme || unref(getMenuTheme)); |
58 | 65 | |
... | ... | @@ -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 | 90 | * click menu |
74 | 91 | * @param menu |
... | ... | @@ -91,12 +108,12 @@ export default defineComponent({ |
91 | 108 | } |
92 | 109 | |
93 | 110 | function renderHeader() { |
94 | - if (!unref(getIsShowLogo)) return null; | |
111 | + if (!unref(getIsShowLogo) && !unref(getIsMobile)) return null; | |
95 | 112 | |
96 | 113 | return ( |
97 | 114 | <AppLogo |
98 | 115 | showTitle={!unref(getCollapsed)} |
99 | - class={[`layout-menu__logo`, unref(getComputedMenuTheme)]} | |
116 | + class={unref(getLogoClass)} | |
100 | 117 | theme={unref(getComputedMenuTheme)} |
101 | 118 | /> |
102 | 119 | ); |
... | ... | @@ -128,7 +145,6 @@ export default defineComponent({ |
128 | 145 | ) : ( |
129 | 146 | renderMenu() |
130 | 147 | )} |
131 | - ; | |
132 | 148 | </> |
133 | 149 | ); |
134 | 150 | }; | ... | ... |
src/layouts/default/menu/useLayoutMenu.ts
... | ... | @@ -10,11 +10,13 @@ import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
10 | 10 | |
11 | 11 | import { getChildrenMenus, getCurrentParentPath, getMenus, getShallowMenus } from '/@/router/menus'; |
12 | 12 | import { permissionStore } from '/@/store/modules/permission'; |
13 | +import { useAppInject } from '/@/hooks/web/useAppInject'; | |
13 | 14 | |
14 | 15 | export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) { |
15 | 16 | // Menu array |
16 | 17 | const menusRef = ref<Menu[]>([]); |
17 | 18 | const { currentRoute } = useRouter(); |
19 | + const { getIsMobile } = useAppInject(); | |
18 | 20 | const { setMenuSetting, getIsHorizontal, getSplit } = useMenuSetting(); |
19 | 21 | |
20 | 22 | const [throttleHandleSplitLeftMenu] = useThrottle(handleSplitLeftMenu, 50); |
... | ... | @@ -36,7 +38,7 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) { |
36 | 38 | watch( |
37 | 39 | [() => unref(currentRoute).path, () => unref(splitType)], |
38 | 40 | async ([path]: [string, MenuSplitTyeEnum]) => { |
39 | - if (unref(splitNotLeft)) return; | |
41 | + if (unref(splitNotLeft) || unref(getIsMobile)) return; | |
40 | 42 | |
41 | 43 | const parentPath = await getCurrentParentPath(path); |
42 | 44 | parentPath && throttleHandleSplitLeftMenu(parentPath); |
... | ... | @@ -65,24 +67,24 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) { |
65 | 67 | |
66 | 68 | // Handle left menu split |
67 | 69 | async function handleSplitLeftMenu(parentPath: string) { |
68 | - if (unref(getSplitLeft)) return; | |
70 | + if (unref(getSplitLeft) || unref(getIsMobile)) return; | |
69 | 71 | |
70 | 72 | // spilt mode left |
71 | 73 | const children = await getChildrenMenus(parentPath); |
72 | 74 | if (!children) { |
73 | - setMenuSetting({ hidden: false }); | |
75 | + setMenuSetting({ hidden: true }); | |
74 | 76 | menusRef.value = []; |
75 | 77 | return; |
76 | 78 | } |
77 | 79 | |
78 | - setMenuSetting({ hidden: true }); | |
80 | + setMenuSetting({ hidden: false }); | |
79 | 81 | menusRef.value = children; |
80 | 82 | } |
81 | 83 | |
82 | 84 | // get menus |
83 | 85 | async function genMenus() { |
84 | 86 | // normal mode |
85 | - if (unref(normalType)) { | |
87 | + if (unref(normalType) || unref(getIsMobile)) { | |
86 | 88 | menusRef.value = await getMenus(); |
87 | 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 './useLayoutSider'; |
12 | 12 | import { useAppInject } from '/@/hooks/web/useAppInject'; |
13 | 13 | import { useDesign } from '/@/hooks/web/useDesign'; |
14 | 14 | |
15 | +import DragBar from './DragBar.vue'; | |
15 | 16 | export default defineComponent({ |
16 | 17 | name: 'LayoutSideBar', |
17 | 18 | setup() { |
... | ... | @@ -31,11 +32,11 @@ export default defineComponent({ |
31 | 32 | |
32 | 33 | const { prefixCls } = useDesign('layout-sideBar'); |
33 | 34 | |
34 | - const { getTriggerAttr, getTriggerSlot } = useTrigger(); | |
35 | - | |
36 | 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 | 41 | const { getCollapsedWidth, onBreakpointChange, onCollapseChange } = useSiderEvent(); |
41 | 42 | |
... | ... | @@ -48,7 +49,7 @@ export default defineComponent({ |
48 | 49 | }); |
49 | 50 | |
50 | 51 | const showClassSideBarRef = computed(() => { |
51 | - return unref(getSplit) ? unref(getMenuHidden) : true; | |
52 | + return unref(getSplit) ? !unref(getMenuHidden) : true; | |
52 | 53 | }); |
53 | 54 | |
54 | 55 | const getSiderClass = computed(() => { |
... | ... | @@ -57,7 +58,7 @@ export default defineComponent({ |
57 | 58 | { |
58 | 59 | [`${prefixCls}--fixed`]: unref(getMenuFixed), |
59 | 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 | 85 | menuMode={unref(getMode)} |
85 | 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 | 102 | collapsible |
102 | 103 | class={unref(getSiderClass)} |
103 | 104 | width={unref(getMenuWidth)} |
104 | - collapsed={unref(getCollapsed)} | |
105 | + collapsed={unref(getIsMobile) ? false : unref(getCollapsed)} | |
105 | 106 | collapsedWidth={unref(getCollapsedWidth)} |
106 | 107 | theme={unref(getMenuTheme)} |
107 | 108 | onCollapse={onCollapseChange} | ... | ... |
src/layouts/default/sider/index.less
... | ... | @@ -44,27 +44,6 @@ |
44 | 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 | 47 | & .ant-layout-sider-trigger { |
69 | 48 | height: 36px; |
70 | 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 | 42 | /** |
43 | 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 | 48 | const showTrigger = computed(() => { |
49 | 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 | 58 | const getTriggerAttr = computed(() => { |
... | ... | @@ -77,14 +82,7 @@ export function useTrigger() { |
77 | 82 | * @param dragBarRef |
78 | 83 | */ |
79 | 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 | 87 | onMounted(() => { |
90 | 88 | nextTick(() => { |
... | ... | @@ -93,16 +91,6 @@ export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) { |
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 | 94 | function handleMouseMove(ele: HTMLElement, wrap: HTMLElement, clientX: number) { |
107 | 95 | document.onmousemove = function (innerE) { |
108 | 96 | let iT = (ele as any).left + (innerE.clientX - clientX); |
... | ... | @@ -138,21 +126,22 @@ export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) { |
138 | 126 | } |
139 | 127 | |
140 | 128 | function changeWrapWidth() { |
141 | - const ele = unref(dragBarRef) as any; | |
129 | + const ele = unref(dragBarRef)?.$el; | |
130 | + if (!ele) { | |
131 | + return; | |
132 | + } | |
142 | 133 | const side = unref(siderRef); |
143 | - | |
144 | 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
src/locales/lang/zh_CN/component/app.ts
src/logics/mitt/tabChange.ts
... | ... | @@ -13,8 +13,9 @@ const key = Symbol(); |
13 | 13 | let lastChangeTab: RouteLocationNormalized; |
14 | 14 | |
15 | 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 | 21 | export function listenerLastChangeTab( | ... | ... |
src/main.ts
src/router/helper/menuHelper.ts
1 | 1 | import { AppRouteModule } from '/@/router/types.d'; |
2 | 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 | 5 | import { cloneDeep } from 'lodash-es'; |
6 | 6 | import { isUrl } from '/@/utils/is'; |
7 | 7 | |
... | ... | @@ -10,10 +10,6 @@ export function getAllParentPath(treeData: any[], path: string) { |
10 | 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 | 14 | function joinParentPath(list: any, node: any) { |
19 | 15 | let allPaths = getAllParentPath(list, node.path); | ... | ... |
src/router/helper/routeHelper.ts
... | ... | @@ -5,6 +5,10 @@ import { getParentLayout, LAYOUT } from '/@/router/constant'; |
5 | 5 | import dynamicImport from './dynamicImport'; |
6 | 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 | 13 | function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { |
10 | 14 | if (!routes) return; |
... | ... | @@ -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 | 27 | // Turn background objects into routing objects |
28 | 28 | export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]): T[] { |
29 | + LayoutMap.set('LAYOUT', LAYOUT); | |
30 | + | |
29 | 31 | routeList.forEach((route) => { |
30 | 32 | if (route.component) { |
31 | 33 | if ((route.component as string).toUpperCase() === 'LAYOUT') { |
32 | - route.component = getLayoutComp(route.component); | |
34 | + route.component = LayoutMap.get(route.component); | |
33 | 35 | } else { |
34 | 36 | route.children = [cloneDeep(route)]; |
35 | 37 | route.component = LAYOUT; |
... | ... | @@ -46,16 +48,6 @@ export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModul |
46 | 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 | 51 | // Return to the new routing structure, not affected by the original example |
60 | 52 | export function getRoute(route: RouteLocationNormalized): RouteLocationNormalized { |
61 | 53 | if (!route) return route; | ... | ... |
src/router/menus/index.ts
1 | 1 | import type { Menu, MenuModule } from '/@/router/types'; |
2 | 2 | import type { RouteRecordNormalized } from 'vue-router'; |
3 | + | |
3 | 4 | import { appStore } from '/@/store/modules/app'; |
4 | 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 | 7 | import { filter } from '/@/utils/helper/treeHelper'; |
7 | 8 | import router from '/@/router'; |
8 | 9 | import { PermissionModeEnum } from '/@/enums/appEnum'; |
... | ... | @@ -10,6 +11,8 @@ import { pathToRegexp } from 'path-to-regexp'; |
10 | 11 | |
11 | 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 | 16 | const menuModules: MenuModule[] = []; |
14 | 17 | |
15 | 18 | Object.keys(modules).forEach((key) => { |
... | ... | @@ -38,18 +41,9 @@ const staticMenus: Menu[] = []; |
38 | 41 | |
39 | 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 | 48 | export const getMenus = async (): Promise<Menu[]> => { |
55 | 49 | const menus = await getAsyncMenus(); |
... | ... | @@ -61,7 +55,7 @@ export const getMenus = async (): Promise<Menu[]> => { |
61 | 55 | export async function getCurrentParentPath(currentPath: string) { |
62 | 56 | const menus = await getAsyncMenus(); |
63 | 57 | const allParentPath = await getAllParentPath(menus, currentPath); |
64 | - return allParentPath[0]; | |
58 | + return allParentPath?.[0]; | |
65 | 59 | } |
66 | 60 | |
67 | 61 | // 获取1级菜单,删除children |
... | ... | @@ -81,27 +75,24 @@ export async function getChildrenMenus(parentPath: string) { |
81 | 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 | 79 | function basicFilter(routes: RouteRecordNormalized[]) { |
91 | 80 | return (menu: Menu) => { |
92 | 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 | 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 | 98 | if (!matchRoute) return false; | ... | ... |
src/router/routes/modules/demo/iframe.ts
src/router/types.d.ts
src/settings/projectSetting.ts
src/utils/is.ts
... | ... | @@ -50,7 +50,7 @@ export function isRegExp(val: unknown): val is RegExp { |
50 | 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 | 54 | return val && Array.isArray(val); |
55 | 55 | } |
56 | 56 | ... | ... |
src/views/sys/lock/LockPage.vue
... | ... | @@ -221,25 +221,35 @@ |
221 | 221 | font-size: 23em; |
222 | 222 | } |
223 | 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 | 232 | @media (min-width: @screen-xs-max) and (max-width: @screen-sm-max) { |
227 | 233 | font-size: 13em; |
228 | 234 | } |
229 | 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 | 246 | &__footer-date { |
237 | 247 | position: absolute; |
238 | 248 | bottom: 20px; |
239 | - left: 50%; | |
249 | + width: 100%; | |
240 | 250 | font-family: helvetica; |
241 | 251 | color: #bababa; |
242 | - transform: translate(-50%, 0); | |
252 | + text-align: center; | |
243 | 253 | |
244 | 254 | .time { |
245 | 255 | font-size: 50px; | ... | ... |
yarn.lock
... | ... | @@ -1051,43 +1051,43 @@ |
1051 | 1051 | resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.272.tgz#27c7caee9764e0304161261ec08ffc2794944b66" |
1052 | 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 | 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 | 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 | 1092 | "@koa/cors@^3.1.0": |
1093 | 1093 | version "3.1.0" |
... | ... | @@ -8256,16 +8256,13 @@ vue-eslint-parser@^7.3.0: |
8256 | 8256 | esquery "^1.0.1" |
8257 | 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 | 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 | 8266 | "@vue/devtools-api" "^6.0.0-beta.2" |
8270 | 8267 | |
8271 | 8268 | vue-router@^4.0.1: | ... | ... |