Commit 97180e83f5055ebd138acc2a82c981d8a7399371
1 parent
5091a875
feat(layout): added setting. Used to fix the left mixed mode menu
Showing
16 changed files
with
192 additions
and
53 deletions
CHANGELOG.zh_CN.md
@@ -3,6 +3,8 @@ | @@ -3,6 +3,8 @@ | ||
3 | ### ✨ Features | 3 | ### ✨ Features |
4 | 4 | ||
5 | - 新增`mixSideTrigger`配置。用于配置左侧混合模式菜单打开方式。可选`hover`,默认`click` | 5 | - 新增`mixSideTrigger`配置。用于配置左侧混合模式菜单打开方式。可选`hover`,默认`click` |
6 | +- 新增`mixSideFixed`配置。用于固定左侧混合模式菜单 | ||
7 | +- modal 组件新增`height`和`min-height`属性 | ||
6 | 8 | ||
7 | ### 🐛 Bug Fixes | 9 | ### 🐛 Bug Fixes |
8 | 10 |
package.json
@@ -7,7 +7,7 @@ | @@ -7,7 +7,7 @@ | ||
7 | "build": "cross-env vite build --mode=production && esno ./build/script/postBuild.ts", | 7 | "build": "cross-env vite build --mode=production && esno ./build/script/postBuild.ts", |
8 | "build:site": "cross-env SITE=true npm run build ", | 8 | "build:site": "cross-env SITE=true npm run build ", |
9 | "build:no-cache": "yarn clean:cache && npm run build", | 9 | "build:no-cache": "yarn clean:cache && npm run build", |
10 | - "typecheck": "typecheck .", | 10 | + "typecheck": "vuedx-typecheck .", |
11 | "report": "cross-env REPORT=true npm run build ", | 11 | "report": "cross-env REPORT=true npm run build ", |
12 | "preview": "npm run build && esno ./build/script/preview.ts", | 12 | "preview": "npm run build && esno ./build/script/preview.ts", |
13 | "preview:dist": "esno ./build/script/preview.ts", | 13 | "preview:dist": "esno ./build/script/preview.ts", |
src/components/Menu/src/BasicMenu.vue
@@ -125,15 +125,13 @@ | @@ -125,15 +125,13 @@ | ||
125 | } | 125 | } |
126 | }); | 126 | }); |
127 | 127 | ||
128 | - watch( | ||
129 | - () => props.items, | ||
130 | - () => { | ||
131 | - handleMenuChange(); | ||
132 | - } | ||
133 | - // { | ||
134 | - // immediate: true, | ||
135 | - // } | ||
136 | - ); | 128 | + !props.mixSider && |
129 | + watch( | ||
130 | + () => props.items, | ||
131 | + () => { | ||
132 | + handleMenuChange(); | ||
133 | + } | ||
134 | + ); | ||
137 | 135 | ||
138 | async function handleMenuClick({ key, keyPath }: { key: string; keyPath: string[] }) { | 136 | async function handleMenuClick({ key, keyPath }: { key: string; keyPath: string[] }) { |
139 | const { beforeClickFn } = props; | 137 | const { beforeClickFn } = props; |
src/components/Menu/src/useOpenKeys.ts
@@ -8,6 +8,7 @@ import { unref } from 'vue'; | @@ -8,6 +8,7 @@ import { unref } from 'vue'; | ||
8 | import { es6Unique } from '/@/utils'; | 8 | import { es6Unique } from '/@/utils'; |
9 | import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; | 9 | import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
10 | import { getAllParentPath } from '/@/router/helper/menuHelper'; | 10 | import { getAllParentPath } from '/@/router/helper/menuHelper'; |
11 | +import { useTimeoutFn } from '/@/hooks/core/useTimeout'; | ||
11 | 12 | ||
12 | export function useOpenKeys( | 13 | export function useOpenKeys( |
13 | menuState: MenuState, | 14 | menuState: MenuState, |
@@ -15,18 +16,29 @@ export function useOpenKeys( | @@ -15,18 +16,29 @@ export function useOpenKeys( | ||
15 | mode: Ref<MenuModeEnum>, | 16 | mode: Ref<MenuModeEnum>, |
16 | accordion: Ref<boolean> | 17 | accordion: Ref<boolean> |
17 | ) { | 18 | ) { |
18 | - const { getCollapsed, getIsMixSidebar } = useMenuSetting(); | 19 | + const { getCollapsed, getIsMixSidebar, getMixSideFixed } = useMenuSetting(); |
19 | 20 | ||
20 | - function setOpenKeys(path: string) { | 21 | + async function setOpenKeys(path: string) { |
21 | if (mode.value === MenuModeEnum.HORIZONTAL) { | 22 | if (mode.value === MenuModeEnum.HORIZONTAL) { |
22 | return; | 23 | return; |
23 | } | 24 | } |
24 | - const menuList = toRaw(menus.value); | ||
25 | - if (!unref(accordion)) { | ||
26 | - menuState.openKeys = es6Unique([...menuState.openKeys, ...getAllParentPath(menuList, path)]); | ||
27 | - } else { | ||
28 | - menuState.openKeys = getAllParentPath(menuList, path); | ||
29 | - } | 25 | + const native = unref(getIsMixSidebar) && unref(getMixSideFixed); |
26 | + | ||
27 | + useTimeoutFn( | ||
28 | + () => { | ||
29 | + const menuList = toRaw(menus.value); | ||
30 | + if (!unref(accordion)) { | ||
31 | + menuState.openKeys = es6Unique([ | ||
32 | + ...menuState.openKeys, | ||
33 | + ...getAllParentPath(menuList, path), | ||
34 | + ]); | ||
35 | + } else { | ||
36 | + menuState.openKeys = getAllParentPath(menuList, path); | ||
37 | + } | ||
38 | + }, | ||
39 | + 16, | ||
40 | + native | ||
41 | + ); | ||
30 | } | 42 | } |
31 | 43 | ||
32 | const getOpenKeys = computed(() => { | 44 | const getOpenKeys = computed(() => { |
src/components/Table/src/hooks/useDataSource.ts
@@ -218,7 +218,7 @@ export function useDataSource( | @@ -218,7 +218,7 @@ export function useDataSource( | ||
218 | onMounted(() => { | 218 | onMounted(() => { |
219 | useTimeoutFn(() => { | 219 | useTimeoutFn(() => { |
220 | unref(propsRef).immediate && fetch(); | 220 | unref(propsRef).immediate && fetch(); |
221 | - }, 0); | 221 | + }, 16); |
222 | }); | 222 | }); |
223 | 223 | ||
224 | return { | 224 | return { |
src/hooks/core/useTimeout.ts
@@ -3,20 +3,23 @@ import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; | @@ -3,20 +3,23 @@ import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; | ||
3 | 3 | ||
4 | import { isFunction } from '/@/utils/is'; | 4 | import { isFunction } from '/@/utils/is'; |
5 | 5 | ||
6 | -export function useTimeoutFn(handle: Fn<any>, wait: number) { | 6 | +export function useTimeoutFn(handle: Fn<any>, wait: number, native = false) { |
7 | if (!isFunction(handle)) { | 7 | if (!isFunction(handle)) { |
8 | throw new Error('handle is not Function!'); | 8 | throw new Error('handle is not Function!'); |
9 | } | 9 | } |
10 | 10 | ||
11 | const { readyRef, stop, start } = useTimeoutRef(wait); | 11 | const { readyRef, stop, start } = useTimeoutRef(wait); |
12 | - | ||
13 | - watch( | ||
14 | - readyRef, | ||
15 | - (maturity) => { | ||
16 | - maturity && handle(); | ||
17 | - }, | ||
18 | - { immediate: false } | ||
19 | - ); | 12 | + if (native) { |
13 | + handle(); | ||
14 | + } else { | ||
15 | + watch( | ||
16 | + readyRef, | ||
17 | + (maturity) => { | ||
18 | + maturity && handle(); | ||
19 | + }, | ||
20 | + { immediate: false } | ||
21 | + ); | ||
22 | + } | ||
20 | return { readyRef, stop, start }; | 23 | return { readyRef, stop, start }; |
21 | } | 24 | } |
22 | 25 |
src/hooks/setting/useMenuSetting.ts
1 | import type { MenuSetting } from '/@/types/config'; | 1 | import type { MenuSetting } from '/@/types/config'; |
2 | 2 | ||
3 | -import { computed, unref } from 'vue'; | 3 | +import { computed, unref, ref } from 'vue'; |
4 | 4 | ||
5 | import { appStore } from '/@/store/modules/app'; | 5 | import { appStore } from '/@/store/modules/app'; |
6 | 6 | ||
@@ -8,6 +8,8 @@ import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appE | @@ -8,6 +8,8 @@ import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appE | ||
8 | import { MenuModeEnum, MenuTypeEnum, TriggerEnum } from '/@/enums/menuEnum'; | 8 | import { MenuModeEnum, MenuTypeEnum, TriggerEnum } from '/@/enums/menuEnum'; |
9 | import { useFullContent } from '/@/hooks/web/useFullContent'; | 9 | import { useFullContent } from '/@/hooks/web/useFullContent'; |
10 | 10 | ||
11 | +const mixSideHasChildren = ref(false); | ||
12 | + | ||
11 | // Get menu configuration | 13 | // Get menu configuration |
12 | const getMenuSetting = computed(() => appStore.getProjectConfig.menuSetting); | 14 | const getMenuSetting = computed(() => appStore.getProjectConfig.menuSetting); |
13 | 15 | ||
@@ -39,6 +41,8 @@ const getCanDrag = computed(() => unref(getMenuSetting).canDrag); | @@ -39,6 +41,8 @@ const getCanDrag = computed(() => unref(getMenuSetting).canDrag); | ||
39 | 41 | ||
40 | const getAccordion = computed(() => unref(getMenuSetting).accordion); | 42 | const getAccordion = computed(() => unref(getMenuSetting).accordion); |
41 | 43 | ||
44 | +const getMixSideFixed = computed(() => unref(getMenuSetting).mixSideFixed); | ||
45 | + | ||
42 | const getTopMenuAlign = computed(() => unref(getMenuSetting).topMenuAlign); | 46 | const getTopMenuAlign = computed(() => unref(getMenuSetting).topMenuAlign); |
43 | 47 | ||
44 | const getCloseMixSidebarOnChange = computed(() => unref(getMenuSetting).closeMixSidebarOnChange); | 48 | const getCloseMixSidebarOnChange = computed(() => unref(getMenuSetting).closeMixSidebarOnChange); |
@@ -87,7 +91,8 @@ const getCalcContentWidth = computed(() => { | @@ -87,7 +91,8 @@ const getCalcContentWidth = computed(() => { | ||
87 | unref(getIsTopMenu) || !unref(getShowMenu) || (unref(getSplit) && unref(getMenuHidden)) | 91 | unref(getIsTopMenu) || !unref(getShowMenu) || (unref(getSplit) && unref(getMenuHidden)) |
88 | ? 0 | 92 | ? 0 |
89 | : unref(getIsMixSidebar) | 93 | : unref(getIsMixSidebar) |
90 | - ? SIDE_BAR_SHOW_TIT_MINI_WIDTH | 94 | + ? SIDE_BAR_SHOW_TIT_MINI_WIDTH + |
95 | + (unref(getMixSideFixed) && unref(mixSideHasChildren) ? unref(getRealWidth) : 0) | ||
91 | : unref(getRealWidth); | 96 | : unref(getRealWidth); |
92 | 97 | ||
93 | return `calc(100% - ${unref(width)}px)`; | 98 | return `calc(100% - ${unref(width)}px)`; |
@@ -148,5 +153,7 @@ export function useMenuSetting() { | @@ -148,5 +153,7 @@ export function useMenuSetting() { | ||
148 | getIsMixSidebar, | 153 | getIsMixSidebar, |
149 | getCloseMixSidebarOnChange, | 154 | getCloseMixSidebarOnChange, |
150 | getMixSideTrigger, | 155 | getMixSideTrigger, |
156 | + getMixSideFixed, | ||
157 | + mixSideHasChildren, | ||
151 | }; | 158 | }; |
152 | } | 159 | } |
src/layouts/default/setting/SettingDrawer.tsx
@@ -75,6 +75,7 @@ export default defineComponent({ | @@ -75,6 +75,7 @@ export default defineComponent({ | ||
75 | getIsMixSidebar, | 75 | getIsMixSidebar, |
76 | getCloseMixSidebarOnChange, | 76 | getCloseMixSidebarOnChange, |
77 | getMixSideTrigger, | 77 | getMixSideTrigger, |
78 | + getMixSideFixed, | ||
78 | } = useMenuSetting(); | 79 | } = useMenuSetting(); |
79 | 80 | ||
80 | const { | 81 | const { |
@@ -110,6 +111,12 @@ export default defineComponent({ | @@ -110,6 +111,12 @@ export default defineComponent({ | ||
110 | def={unref(getSplit)} | 111 | def={unref(getSplit)} |
111 | disabled={!unref(getShowMenuRef) || unref(getMenuType) !== MenuTypeEnum.MIX} | 112 | disabled={!unref(getShowMenuRef) || unref(getMenuType) !== MenuTypeEnum.MIX} |
112 | /> | 113 | /> |
114 | + <SwitchItem | ||
115 | + title={t('layout.setting.mixSidebarFixed')} | ||
116 | + event={HandlerEnum.MENU_FIXED_MIX_SIDEBAR} | ||
117 | + def={unref(getMixSideFixed)} | ||
118 | + disabled={!unref(getIsMixSidebar)} | ||
119 | + /> | ||
113 | 120 | ||
114 | <SwitchItem | 121 | <SwitchItem |
115 | title={t('layout.setting.closeMixSidebarOnChange')} | 122 | title={t('layout.setting.closeMixSidebarOnChange')} |
src/layouts/default/setting/enum.ts
@@ -27,6 +27,7 @@ export enum HandlerEnum { | @@ -27,6 +27,7 @@ export enum HandlerEnum { | ||
27 | MENU_FIXED, | 27 | MENU_FIXED, |
28 | MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE, | 28 | MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE, |
29 | MENU_TRIGGER_MIX_SIDEBAR, | 29 | MENU_TRIGGER_MIX_SIDEBAR, |
30 | + MENU_FIXED_MIX_SIDEBAR, | ||
30 | 31 | ||
31 | // header | 32 | // header |
32 | HEADER_SHOW, | 33 | HEADER_SHOW, |
src/layouts/default/setting/handler.ts
@@ -70,6 +70,9 @@ export function handler(event: HandlerEnum, value: any): DeepPartial<ProjectConf | @@ -70,6 +70,9 @@ export function handler(event: HandlerEnum, value: any): DeepPartial<ProjectConf | ||
70 | case HandlerEnum.MENU_TRIGGER_MIX_SIDEBAR: | 70 | case HandlerEnum.MENU_TRIGGER_MIX_SIDEBAR: |
71 | return { menuSetting: { mixSideTrigger: value } }; | 71 | return { menuSetting: { mixSideTrigger: value } }; |
72 | 72 | ||
73 | + case HandlerEnum.MENU_FIXED_MIX_SIDEBAR: | ||
74 | + return { menuSetting: { mixSideTrigger: value } }; | ||
75 | + | ||
73 | // ============transition================== | 76 | // ============transition================== |
74 | case HandlerEnum.OPEN_PAGE_LOADING: | 77 | case HandlerEnum.OPEN_PAGE_LOADING: |
75 | appStore.commitPageLoadingState(false); | 78 | appStore.commitPageLoadingState(false); |
src/layouts/default/sider/MixSider.vue
1 | <template> | 1 | <template> |
2 | - <div :class="`${prefixCls}-dom`" /> | 2 | + <div :class="`${prefixCls}-dom`" :style="getDomStyle" /> |
3 | 3 | ||
4 | <div | 4 | <div |
5 | v-click-outside="handleClickOutside" | 5 | v-click-outside="handleClickOutside" |
@@ -27,7 +27,7 @@ | @@ -27,7 +27,7 @@ | ||
27 | v-bind="getItemEvents(item)" | 27 | v-bind="getItemEvents(item)" |
28 | > | 28 | > |
29 | <MenuTag :item="item" :showTitle="false" :isHorizontal="false" /> | 29 | <MenuTag :item="item" :showTitle="false" :isHorizontal="false" /> |
30 | - <g-icon | 30 | + <Icon |
31 | :class="`${prefixCls}-module__icon`" | 31 | :class="`${prefixCls}-module__icon`" |
32 | :size="22" | 32 | :size="22" |
33 | :icon="item.meta && item.meta.icon" | 33 | :icon="item.meta && item.meta.icon" |
@@ -48,6 +48,14 @@ | @@ -48,6 +48,14 @@ | ||
48 | ]" | 48 | ]" |
49 | > | 49 | > |
50 | <span class="text"> {{ title }}</span> | 50 | <span class="text"> {{ title }}</span> |
51 | + <Icon | ||
52 | + :size="16" | ||
53 | + v-if="getMixSideFixed" | ||
54 | + icon="ri:pushpin-2-fill" | ||
55 | + class="pushpin" | ||
56 | + @click="handleFixedMenu" | ||
57 | + /> | ||
58 | + <Icon :size="16" v-else icon="ri:pushpin-2-line" class="pushpin" @click="handleFixedMenu" /> | ||
51 | </div> | 59 | </div> |
52 | <ScrollContainer :class="`${prefixCls}-menu-list__content`"> | 60 | <ScrollContainer :class="`${prefixCls}-menu-list__content`"> |
53 | <BasicMenu | 61 | <BasicMenu |
@@ -70,20 +78,23 @@ | @@ -70,20 +78,23 @@ | ||
70 | <script lang="ts"> | 78 | <script lang="ts"> |
71 | import { defineComponent, onMounted, ref, computed, CSSProperties, unref } from 'vue'; | 79 | import { defineComponent, onMounted, ref, computed, CSSProperties, unref } from 'vue'; |
72 | import type { Menu } from '/@/router/types'; | 80 | import type { Menu } from '/@/router/types'; |
73 | - import type { RouteLocationNormalized } from 'vue-router'; | 81 | + import { RouteLocationNormalized } from 'vue-router'; |
74 | import { useDesign } from '/@/hooks/web/useDesign'; | 82 | import { useDesign } from '/@/hooks/web/useDesign'; |
75 | import { getShallowMenus, getChildrenMenus, getCurrentParentPath } from '/@/router/menus'; | 83 | import { getShallowMenus, getChildrenMenus, getCurrentParentPath } from '/@/router/menus'; |
76 | import { useI18n } from '/@/hooks/web/useI18n'; | 84 | import { useI18n } from '/@/hooks/web/useI18n'; |
77 | import { ScrollContainer } from '/@/components/Container'; | 85 | import { ScrollContainer } from '/@/components/Container'; |
86 | + import Icon from '/@/components/Icon'; | ||
78 | import { AppLogo } from '/@/components/Application'; | 87 | import { AppLogo } from '/@/components/Application'; |
79 | import { useGo } from '/@/hooks/web/usePage'; | 88 | import { useGo } from '/@/hooks/web/usePage'; |
80 | import { BasicMenu, MenuTag } from '/@/components/Menu'; | 89 | import { BasicMenu, MenuTag } from '/@/components/Menu'; |
81 | import { listenerLastChangeTab } from '/@/logics/mitt/tabChange'; | 90 | import { listenerLastChangeTab } from '/@/logics/mitt/tabChange'; |
82 | import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; | 91 | import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
83 | import { useDragLine } from './useLayoutSider'; | 92 | import { useDragLine } from './useLayoutSider'; |
93 | + import { useGlobSetting } from '/@/hooks/setting'; | ||
94 | + | ||
95 | + import { SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum'; | ||
84 | 96 | ||
85 | import clickOutside from '/@/directives/clickOutside'; | 97 | import clickOutside from '/@/directives/clickOutside'; |
86 | - import { useGlobSetting } from '/@/hooks/setting'; | ||
87 | 98 | ||
88 | export default defineComponent({ | 99 | export default defineComponent({ |
89 | name: 'LayoutMixSider', | 100 | name: 'LayoutMixSider', |
@@ -92,6 +103,7 @@ | @@ -92,6 +103,7 @@ | ||
92 | AppLogo, | 103 | AppLogo, |
93 | BasicMenu, | 104 | BasicMenu, |
94 | MenuTag, | 105 | MenuTag, |
106 | + Icon, | ||
95 | }, | 107 | }, |
96 | directives: { | 108 | directives: { |
97 | clickOutside, | 109 | clickOutside, |
@@ -101,6 +113,7 @@ | @@ -101,6 +113,7 @@ | ||
101 | const activePath = ref(''); | 113 | const activePath = ref(''); |
102 | const chilrenMenus = ref<Menu[]>([]); | 114 | const chilrenMenus = ref<Menu[]>([]); |
103 | const openMenu = ref(false); | 115 | const openMenu = ref(false); |
116 | + const isInit = ref(false); | ||
104 | const dragBarRef = ref<ElRef>(null); | 117 | const dragBarRef = ref<ElRef>(null); |
105 | const sideRef = ref<ElRef>(null); | 118 | const sideRef = ref<ElRef>(null); |
106 | const currentRoute = ref<Nullable<RouteLocationNormalized>>(null); | 119 | const currentRoute = ref<Nullable<RouteLocationNormalized>>(null); |
@@ -114,7 +127,12 @@ | @@ -114,7 +127,12 @@ | ||
114 | getCloseMixSidebarOnChange, | 127 | getCloseMixSidebarOnChange, |
115 | getMenuTheme, | 128 | getMenuTheme, |
116 | getMixSideTrigger, | 129 | getMixSideTrigger, |
130 | + getRealWidth, | ||
131 | + getMixSideFixed, | ||
132 | + mixSideHasChildren, | ||
133 | + setMenuSetting, | ||
117 | } = useMenuSetting(); | 134 | } = useMenuSetting(); |
135 | + | ||
118 | const { title } = useGlobSetting(); | 136 | const { title } = useGlobSetting(); |
119 | 137 | ||
120 | useDragLine(sideRef, dragBarRef, true); | 138 | useDragLine(sideRef, dragBarRef, true); |
@@ -127,14 +145,41 @@ | @@ -127,14 +145,41 @@ | ||
127 | } | 145 | } |
128 | ); | 146 | ); |
129 | 147 | ||
148 | + const getIsFixed = computed(() => { | ||
149 | + mixSideHasChildren.value = unref(chilrenMenus).length > 0; | ||
150 | + const isFixed = unref(getMixSideFixed) && unref(mixSideHasChildren); | ||
151 | + if (isFixed) { | ||
152 | + openMenu.value = true; | ||
153 | + } | ||
154 | + return isFixed; | ||
155 | + }); | ||
156 | + | ||
157 | + const getDomStyle = computed( | ||
158 | + (): CSSProperties => { | ||
159 | + const fixedWidth = unref(getIsFixed) ? unref(getRealWidth) : 0; | ||
160 | + const width = `${SIDE_BAR_SHOW_TIT_MINI_WIDTH + fixedWidth}px`; | ||
161 | + return { | ||
162 | + width, | ||
163 | + maxWidth: width, | ||
164 | + minWidth: width, | ||
165 | + flex: `0 0 ${width}`, | ||
166 | + }; | ||
167 | + } | ||
168 | + ); | ||
169 | + | ||
130 | const getMenuEvents = computed(() => { | 170 | const getMenuEvents = computed(() => { |
131 | - return unref(getMixSideTrigger) === 'hover' | ||
132 | - ? { | ||
133 | - onMouseleave: () => { | ||
134 | - openMenu.value = false; | ||
135 | - }, | ||
136 | - } | ||
137 | - : {}; | 171 | + // return unref(getMixSideTrigger) === 'hover' |
172 | + // ? { | ||
173 | + // onMouseleave: () => { | ||
174 | + // closeMenu(); | ||
175 | + // }, | ||
176 | + // } | ||
177 | + // : {}; | ||
178 | + return { | ||
179 | + onMouseleave: () => { | ||
180 | + closeMenu(); | ||
181 | + }, | ||
182 | + }; | ||
138 | }); | 183 | }); |
139 | 184 | ||
140 | const getShowDragBar = computed(() => unref(getCanDrag)); | 185 | const getShowDragBar = computed(() => unref(getCanDrag)); |
@@ -145,9 +190,9 @@ | @@ -145,9 +190,9 @@ | ||
145 | 190 | ||
146 | listenerLastChangeTab((route) => { | 191 | listenerLastChangeTab((route) => { |
147 | currentRoute.value = route; | 192 | currentRoute.value = route; |
148 | - setActive(); | 193 | + setActive(true); |
149 | if (unref(getCloseMixSidebarOnChange)) { | 194 | if (unref(getCloseMixSidebarOnChange)) { |
150 | - openMenu.value = false; | 195 | + closeMenu(); |
151 | } | 196 | } |
152 | }); | 197 | }); |
153 | 198 | ||
@@ -156,7 +201,11 @@ | @@ -156,7 +201,11 @@ | ||
156 | 201 | ||
157 | if (unref(activePath) === path) { | 202 | if (unref(activePath) === path) { |
158 | if (!hover) { | 203 | if (!hover) { |
159 | - openMenu.value = !unref(openMenu); | 204 | + if (!unref(openMenu)) { |
205 | + openMenu.value = true; | ||
206 | + } else { | ||
207 | + closeMenu(); | ||
208 | + } | ||
160 | } | 209 | } |
161 | if (!unref(openMenu)) { | 210 | if (!unref(openMenu)) { |
162 | setActive(); | 211 | setActive(); |
@@ -169,18 +218,32 @@ | @@ -169,18 +218,32 @@ | ||
169 | if (!children || children.length === 0) { | 218 | if (!children || children.length === 0) { |
170 | go(path); | 219 | go(path); |
171 | chilrenMenus.value = []; | 220 | chilrenMenus.value = []; |
172 | - openMenu.value = false; | 221 | + closeMenu(); |
173 | return; | 222 | return; |
174 | } | 223 | } |
175 | chilrenMenus.value = children; | 224 | chilrenMenus.value = children; |
176 | } | 225 | } |
177 | 226 | ||
178 | - async function setActive() { | 227 | + async function setActive(setChildren = false) { |
179 | const path = currentRoute.value?.path; | 228 | const path = currentRoute.value?.path; |
180 | if (!path) return; | 229 | if (!path) return; |
181 | const parentPath = await getCurrentParentPath(path); | 230 | const parentPath = await getCurrentParentPath(path); |
182 | activePath.value = parentPath; | 231 | activePath.value = parentPath; |
183 | // hanldeModuleClick(parentPath); | 232 | // hanldeModuleClick(parentPath); |
233 | + if (unref(getMixSideFixed)) { | ||
234 | + const activeMenu = unref(menuModules).find((item) => item.path === unref(activePath)); | ||
235 | + const p = activeMenu?.path; | ||
236 | + if (p) { | ||
237 | + const children = await getChildrenMenus(p); | ||
238 | + if (setChildren) { | ||
239 | + chilrenMenus.value = children; | ||
240 | + openMenu.value = children.length > 0; | ||
241 | + } | ||
242 | + if (children.length === 0) { | ||
243 | + chilrenMenus.value = []; | ||
244 | + } | ||
245 | + } | ||
246 | + } | ||
184 | } | 247 | } |
185 | 248 | ||
186 | function handleMenuClick(path: string) { | 249 | function handleMenuClick(path: string) { |
@@ -188,7 +251,7 @@ | @@ -188,7 +251,7 @@ | ||
188 | } | 251 | } |
189 | 252 | ||
190 | function handleClickOutside() { | 253 | function handleClickOutside() { |
191 | - openMenu.value = false; | 254 | + closeMenu(); |
192 | setActive(); | 255 | setActive(); |
193 | } | 256 | } |
194 | 257 | ||
@@ -203,6 +266,18 @@ | @@ -203,6 +266,18 @@ | ||
203 | }; | 266 | }; |
204 | } | 267 | } |
205 | 268 | ||
269 | + function handleFixedMenu() { | ||
270 | + setMenuSetting({ | ||
271 | + mixSideFixed: !unref(getIsFixed), | ||
272 | + }); | ||
273 | + } | ||
274 | + | ||
275 | + function closeMenu() { | ||
276 | + if (!unref(getIsFixed)) { | ||
277 | + openMenu.value = false; | ||
278 | + } | ||
279 | + } | ||
280 | + | ||
206 | return { | 281 | return { |
207 | t, | 282 | t, |
208 | prefixCls, | 283 | prefixCls, |
@@ -221,6 +296,9 @@ | @@ -221,6 +296,9 @@ | ||
221 | getMenuTheme, | 296 | getMenuTheme, |
222 | getItemEvents, | 297 | getItemEvents, |
223 | getMenuEvents, | 298 | getMenuEvents, |
299 | + getDomStyle, | ||
300 | + handleFixedMenu, | ||
301 | + getMixSideFixed, | ||
224 | }; | 302 | }; |
225 | }, | 303 | }, |
226 | }); | 304 | }); |
@@ -241,7 +319,7 @@ | @@ -241,7 +319,7 @@ | ||
241 | min-width: @width; | 319 | min-width: @width; |
242 | overflow: hidden; | 320 | overflow: hidden; |
243 | background: @sider-dark-bg-color; | 321 | background: @sider-dark-bg-color; |
244 | - transition: all 0.2s ease 0s; | 322 | + transition: all 0.3s ease 0s; |
245 | flex: 0 0 @width; | 323 | flex: 0 0 @width; |
246 | .@{tag-prefix-cls} { | 324 | .@{tag-prefix-cls} { |
247 | position: absolute; | 325 | position: absolute; |
@@ -293,6 +371,17 @@ | @@ -293,6 +371,17 @@ | ||
293 | } | 371 | } |
294 | } | 372 | } |
295 | } | 373 | } |
374 | + .@{prefix-cls}-menu-list { | ||
375 | + &__title { | ||
376 | + .pushpin { | ||
377 | + color: rgba(0, 0, 0, 0.35); | ||
378 | + | ||
379 | + &:hover { | ||
380 | + color: rgba(0, 0, 0, 0.85); | ||
381 | + } | ||
382 | + } | ||
383 | + } | ||
384 | + } | ||
296 | } | 385 | } |
297 | @border-color: @sider-dark-lighten-1-bg-color; | 386 | @border-color: @sider-dark-lighten-1-bg-color; |
298 | 387 | ||
@@ -388,20 +477,30 @@ | @@ -388,20 +477,30 @@ | ||
388 | &__title { | 477 | &__title { |
389 | display: flex; | 478 | display: flex; |
390 | height: @header-height; | 479 | height: @header-height; |
391 | - margin-left: -6px; | 480 | + // margin-left: -6px; |
392 | font-size: 18px; | 481 | font-size: 18px; |
393 | color: @primary-color; | 482 | color: @primary-color; |
394 | border-bottom: 1px solid rgb(238, 238, 238); | 483 | border-bottom: 1px solid rgb(238, 238, 238); |
395 | opacity: 0; | 484 | opacity: 0; |
396 | transition: unset; | 485 | transition: unset; |
397 | - // justify-content: center; | ||
398 | align-items: center; | 486 | align-items: center; |
399 | - justify-content: start; | 487 | + justify-content: space-between; |
400 | 488 | ||
401 | &.show { | 489 | &.show { |
490 | + min-width: 130px; | ||
402 | opacity: 1; | 491 | opacity: 1; |
403 | transition: all 0.5s ease; | 492 | transition: all 0.5s ease; |
404 | } | 493 | } |
494 | + | ||
495 | + .pushpin { | ||
496 | + margin-right: 6px; | ||
497 | + color: rgba(255, 255, 255, 0.65); | ||
498 | + cursor: pointer; | ||
499 | + | ||
500 | + &:hover { | ||
501 | + color: #fff; | ||
502 | + } | ||
503 | + } | ||
405 | } | 504 | } |
406 | 505 | ||
407 | &__content { | 506 | &__content { |
src/locales/lang/en/layout/setting.ts
@@ -77,4 +77,6 @@ export default { | @@ -77,4 +77,6 @@ export default { | ||
77 | mixSidebarTrigger: 'Mixed menu Trigger', | 77 | mixSidebarTrigger: 'Mixed menu Trigger', |
78 | triggerHover: 'Hover', | 78 | triggerHover: 'Hover', |
79 | triggerClick: 'Click', | 79 | triggerClick: 'Click', |
80 | + | ||
81 | + mixSidebarFixed: 'Fixed expanded menu', | ||
80 | }; | 82 | }; |
src/locales/lang/zh_CN/layout/setting.ts
src/settings/projectSetting.ts
@@ -110,6 +110,8 @@ const setting: ProjectConfig = { | @@ -110,6 +110,8 @@ const setting: ProjectConfig = { | ||
110 | closeMixSidebarOnChange: false, | 110 | closeMixSidebarOnChange: false, |
111 | // Module opening method ‘click’ |'hover' | 111 | // Module opening method ‘click’ |'hover' |
112 | mixSideTrigger: MixSidebarTriggerEnum.CLICK, | 112 | mixSideTrigger: MixSidebarTriggerEnum.CLICK, |
113 | + // Fixed expanded menu | ||
114 | + mixSideFixed: false, | ||
113 | }, | 115 | }, |
114 | 116 | ||
115 | // Multi-label | 117 | // Multi-label |
src/types/config.d.ts
@@ -21,6 +21,7 @@ export interface MenuSetting { | @@ -21,6 +21,7 @@ export interface MenuSetting { | ||
21 | closeMixSidebarOnChange: boolean; | 21 | closeMixSidebarOnChange: boolean; |
22 | collapsedShowTitle: boolean; | 22 | collapsedShowTitle: boolean; |
23 | mixSideTrigger: MixSidebarTriggerEnum; | 23 | mixSideTrigger: MixSidebarTriggerEnum; |
24 | + mixSideFixed: boolean; | ||
24 | } | 25 | } |
25 | 26 | ||
26 | export interface MultiTabsSetting { | 27 | export interface MultiTabsSetting { |
tsconfig.json
1 | { | 1 | { |
2 | "compilerOptions": { | 2 | "compilerOptions": { |
3 | - "target": "es2016", | 3 | + "target": "esnext", |
4 | "module": "esnext", | 4 | "module": "esnext", |
5 | "moduleResolution": "node", | 5 | "moduleResolution": "node", |
6 | "strict": true, | 6 | "strict": true, |
7 | "forceConsistentCasingInFileNames": true, | 7 | "forceConsistentCasingInFileNames": true, |
8 | "allowSyntheticDefaultImports": true, | 8 | "allowSyntheticDefaultImports": true, |
9 | "strictFunctionTypes": false, | 9 | "strictFunctionTypes": false, |
10 | - "jsx": "react", | 10 | + "jsx": "preserve", |
11 | "baseUrl": ".", | 11 | "baseUrl": ".", |
12 | "allowJs": true, | 12 | "allowJs": true, |
13 | "sourceMap": true, | 13 | "sourceMap": true, |