Commit 97180e83f5055ebd138acc2a82c981d8a7399371

Authored by vben
1 parent 5091a875

feat(layout): added setting. Used to fix the left mixed mode menu

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 &#39;/@/utils/helper/vueHelper&#39;; @@ -3,20 +3,23 @@ import { tryOnUnmounted } from &#39;/@/utils/helper/vueHelper&#39;;
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 &#39;/@/enums/appE @@ -8,6 +8,8 @@ import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from &#39;/@/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(() =&gt; unref(getMenuSetting).canDrag); @@ -39,6 +41,8 @@ const getCanDrag = computed(() =&gt; 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(() =&gt; { @@ -87,7 +91,8 @@ const getCalcContentWidth = computed(() =&gt; {
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&lt;ProjectConf @@ -70,6 +70,9 @@ export function handler(event: HandlerEnum, value: any): DeepPartial&lt;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
@@ -76,4 +76,6 @@ export default { @@ -76,4 +76,6 @@ export default {
76 mixSidebarTrigger: '混合菜单触发方式', 76 mixSidebarTrigger: '混合菜单触发方式',
77 triggerHover: '悬停', 77 triggerHover: '悬停',
78 triggerClick: '点击', 78 triggerClick: '点击',
  79 +
  80 + mixSidebarFixed: '固定展开菜单',
79 }; 81 };
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,