From 97180e83f5055ebd138acc2a82c981d8a7399371 Mon Sep 17 00:00:00 2001 From: vben Date: Sun, 3 Jan 2021 10:42:19 +0800 Subject: [PATCH] feat(layout): added setting. Used to fix the left mixed mode menu --- CHANGELOG.zh_CN.md | 2 + package.json | 2 +- src/components/Menu/src/BasicMenu.vue | 16 +- src/components/Menu/src/useOpenKeys.ts | 28 +++- .../Table/src/hooks/useDataSource.ts | 2 +- src/hooks/core/useTimeout.ts | 21 +-- src/hooks/setting/useMenuSetting.ts | 11 +- src/layouts/default/setting/SettingDrawer.tsx | 7 + src/layouts/default/setting/enum.ts | 1 + src/layouts/default/setting/handler.ts | 3 + src/layouts/default/sider/MixSider.vue | 141 +++++++++++++++--- src/locales/lang/en/layout/setting.ts | 2 + src/locales/lang/zh_CN/layout/setting.ts | 2 + src/settings/projectSetting.ts | 2 + src/types/config.d.ts | 1 + tsconfig.json | 4 +- 16 files changed, 192 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.zh_CN.md b/CHANGELOG.zh_CN.md index 9d54b9d0..855e3471 100644 --- a/CHANGELOG.zh_CN.md +++ b/CHANGELOG.zh_CN.md @@ -3,6 +3,8 @@ ### ✨ Features - 新增`mixSideTrigger`配置。用于配置左侧混合模式菜单打开方式。可选`hover`,默认`click` +- 新增`mixSideFixed`配置。用于固定左侧混合模式菜单 +- modal 组件新增`height`和`min-height`属性 ### 🐛 Bug Fixes diff --git a/package.json b/package.json index f2c3c22e..d3ec170a 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "build": "cross-env vite build --mode=production && esno ./build/script/postBuild.ts", "build:site": "cross-env SITE=true npm run build ", "build:no-cache": "yarn clean:cache && npm run build", - "typecheck": "typecheck .", + "typecheck": "vuedx-typecheck .", "report": "cross-env REPORT=true npm run build ", "preview": "npm run build && esno ./build/script/preview.ts", "preview:dist": "esno ./build/script/preview.ts", diff --git a/src/components/Menu/src/BasicMenu.vue b/src/components/Menu/src/BasicMenu.vue index 468ffcb1..5729c15e 100644 --- a/src/components/Menu/src/BasicMenu.vue +++ b/src/components/Menu/src/BasicMenu.vue @@ -125,15 +125,13 @@ } }); - watch( - () => props.items, - () => { - handleMenuChange(); - } - // { - // immediate: true, - // } - ); + !props.mixSider && + watch( + () => props.items, + () => { + handleMenuChange(); + } + ); async function handleMenuClick({ key, keyPath }: { key: string; keyPath: string[] }) { const { beforeClickFn } = props; diff --git a/src/components/Menu/src/useOpenKeys.ts b/src/components/Menu/src/useOpenKeys.ts index a9c56423..813d8d70 100644 --- a/src/components/Menu/src/useOpenKeys.ts +++ b/src/components/Menu/src/useOpenKeys.ts @@ -8,6 +8,7 @@ import { unref } from 'vue'; import { es6Unique } from '/@/utils'; import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; import { getAllParentPath } from '/@/router/helper/menuHelper'; +import { useTimeoutFn } from '/@/hooks/core/useTimeout'; export function useOpenKeys( menuState: MenuState, @@ -15,18 +16,29 @@ export function useOpenKeys( mode: Ref, accordion: Ref ) { - const { getCollapsed, getIsMixSidebar } = useMenuSetting(); + const { getCollapsed, getIsMixSidebar, getMixSideFixed } = useMenuSetting(); - function setOpenKeys(path: string) { + async function setOpenKeys(path: string) { if (mode.value === MenuModeEnum.HORIZONTAL) { return; } - const menuList = toRaw(menus.value); - if (!unref(accordion)) { - menuState.openKeys = es6Unique([...menuState.openKeys, ...getAllParentPath(menuList, path)]); - } else { - menuState.openKeys = getAllParentPath(menuList, path); - } + const native = unref(getIsMixSidebar) && unref(getMixSideFixed); + + useTimeoutFn( + () => { + const menuList = toRaw(menus.value); + if (!unref(accordion)) { + menuState.openKeys = es6Unique([ + ...menuState.openKeys, + ...getAllParentPath(menuList, path), + ]); + } else { + menuState.openKeys = getAllParentPath(menuList, path); + } + }, + 16, + native + ); } const getOpenKeys = computed(() => { diff --git a/src/components/Table/src/hooks/useDataSource.ts b/src/components/Table/src/hooks/useDataSource.ts index ddf446e1..435d234e 100644 --- a/src/components/Table/src/hooks/useDataSource.ts +++ b/src/components/Table/src/hooks/useDataSource.ts @@ -218,7 +218,7 @@ export function useDataSource( onMounted(() => { useTimeoutFn(() => { unref(propsRef).immediate && fetch(); - }, 0); + }, 16); }); return { diff --git a/src/hooks/core/useTimeout.ts b/src/hooks/core/useTimeout.ts index 119cbe7e..8bb0cee6 100644 --- a/src/hooks/core/useTimeout.ts +++ b/src/hooks/core/useTimeout.ts @@ -3,20 +3,23 @@ import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; import { isFunction } from '/@/utils/is'; -export function useTimeoutFn(handle: Fn, wait: number) { +export function useTimeoutFn(handle: Fn, wait: number, native = false) { if (!isFunction(handle)) { throw new Error('handle is not Function!'); } const { readyRef, stop, start } = useTimeoutRef(wait); - - watch( - readyRef, - (maturity) => { - maturity && handle(); - }, - { immediate: false } - ); + if (native) { + handle(); + } else { + watch( + readyRef, + (maturity) => { + maturity && handle(); + }, + { immediate: false } + ); + } return { readyRef, stop, start }; } diff --git a/src/hooks/setting/useMenuSetting.ts b/src/hooks/setting/useMenuSetting.ts index 566b6a22..2edcafb2 100644 --- a/src/hooks/setting/useMenuSetting.ts +++ b/src/hooks/setting/useMenuSetting.ts @@ -1,6 +1,6 @@ import type { MenuSetting } from '/@/types/config'; -import { computed, unref } from 'vue'; +import { computed, unref, ref } from 'vue'; import { appStore } from '/@/store/modules/app'; @@ -8,6 +8,8 @@ import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appE import { MenuModeEnum, MenuTypeEnum, TriggerEnum } from '/@/enums/menuEnum'; import { useFullContent } from '/@/hooks/web/useFullContent'; +const mixSideHasChildren = ref(false); + // Get menu configuration const getMenuSetting = computed(() => appStore.getProjectConfig.menuSetting); @@ -39,6 +41,8 @@ const getCanDrag = computed(() => unref(getMenuSetting).canDrag); const getAccordion = computed(() => unref(getMenuSetting).accordion); +const getMixSideFixed = computed(() => unref(getMenuSetting).mixSideFixed); + const getTopMenuAlign = computed(() => unref(getMenuSetting).topMenuAlign); const getCloseMixSidebarOnChange = computed(() => unref(getMenuSetting).closeMixSidebarOnChange); @@ -87,7 +91,8 @@ const getCalcContentWidth = computed(() => { unref(getIsTopMenu) || !unref(getShowMenu) || (unref(getSplit) && unref(getMenuHidden)) ? 0 : unref(getIsMixSidebar) - ? SIDE_BAR_SHOW_TIT_MINI_WIDTH + ? SIDE_BAR_SHOW_TIT_MINI_WIDTH + + (unref(getMixSideFixed) && unref(mixSideHasChildren) ? unref(getRealWidth) : 0) : unref(getRealWidth); return `calc(100% - ${unref(width)}px)`; @@ -148,5 +153,7 @@ export function useMenuSetting() { getIsMixSidebar, getCloseMixSidebarOnChange, getMixSideTrigger, + getMixSideFixed, + mixSideHasChildren, }; } diff --git a/src/layouts/default/setting/SettingDrawer.tsx b/src/layouts/default/setting/SettingDrawer.tsx index cd4b68b0..6b8f0133 100644 --- a/src/layouts/default/setting/SettingDrawer.tsx +++ b/src/layouts/default/setting/SettingDrawer.tsx @@ -75,6 +75,7 @@ export default defineComponent({ getIsMixSidebar, getCloseMixSidebarOnChange, getMixSideTrigger, + getMixSideFixed, } = useMenuSetting(); const { @@ -110,6 +111,12 @@ export default defineComponent({ def={unref(getSplit)} disabled={!unref(getShowMenuRef) || unref(getMenuType) !== MenuTypeEnum.MIX} /> + -
+
- {{ title }} + +
import { defineComponent, onMounted, ref, computed, CSSProperties, unref } from 'vue'; import type { Menu } from '/@/router/types'; - import type { RouteLocationNormalized } from 'vue-router'; + import { RouteLocationNormalized } from 'vue-router'; import { useDesign } from '/@/hooks/web/useDesign'; import { getShallowMenus, getChildrenMenus, getCurrentParentPath } from '/@/router/menus'; import { useI18n } from '/@/hooks/web/useI18n'; import { ScrollContainer } from '/@/components/Container'; + import Icon from '/@/components/Icon'; import { AppLogo } from '/@/components/Application'; import { useGo } from '/@/hooks/web/usePage'; import { BasicMenu, MenuTag } from '/@/components/Menu'; import { listenerLastChangeTab } from '/@/logics/mitt/tabChange'; import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; import { useDragLine } from './useLayoutSider'; + import { useGlobSetting } from '/@/hooks/setting'; + + import { SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum'; import clickOutside from '/@/directives/clickOutside'; - import { useGlobSetting } from '/@/hooks/setting'; export default defineComponent({ name: 'LayoutMixSider', @@ -92,6 +103,7 @@ AppLogo, BasicMenu, MenuTag, + Icon, }, directives: { clickOutside, @@ -101,6 +113,7 @@ const activePath = ref(''); const chilrenMenus = ref([]); const openMenu = ref(false); + const isInit = ref(false); const dragBarRef = ref(null); const sideRef = ref(null); const currentRoute = ref>(null); @@ -114,7 +127,12 @@ getCloseMixSidebarOnChange, getMenuTheme, getMixSideTrigger, + getRealWidth, + getMixSideFixed, + mixSideHasChildren, + setMenuSetting, } = useMenuSetting(); + const { title } = useGlobSetting(); useDragLine(sideRef, dragBarRef, true); @@ -127,14 +145,41 @@ } ); + const getIsFixed = computed(() => { + mixSideHasChildren.value = unref(chilrenMenus).length > 0; + const isFixed = unref(getMixSideFixed) && unref(mixSideHasChildren); + if (isFixed) { + openMenu.value = true; + } + return isFixed; + }); + + const getDomStyle = computed( + (): CSSProperties => { + const fixedWidth = unref(getIsFixed) ? unref(getRealWidth) : 0; + const width = `${SIDE_BAR_SHOW_TIT_MINI_WIDTH + fixedWidth}px`; + return { + width, + maxWidth: width, + minWidth: width, + flex: `0 0 ${width}`, + }; + } + ); + const getMenuEvents = computed(() => { - return unref(getMixSideTrigger) === 'hover' - ? { - onMouseleave: () => { - openMenu.value = false; - }, - } - : {}; + // return unref(getMixSideTrigger) === 'hover' + // ? { + // onMouseleave: () => { + // closeMenu(); + // }, + // } + // : {}; + return { + onMouseleave: () => { + closeMenu(); + }, + }; }); const getShowDragBar = computed(() => unref(getCanDrag)); @@ -145,9 +190,9 @@ listenerLastChangeTab((route) => { currentRoute.value = route; - setActive(); + setActive(true); if (unref(getCloseMixSidebarOnChange)) { - openMenu.value = false; + closeMenu(); } }); @@ -156,7 +201,11 @@ if (unref(activePath) === path) { if (!hover) { - openMenu.value = !unref(openMenu); + if (!unref(openMenu)) { + openMenu.value = true; + } else { + closeMenu(); + } } if (!unref(openMenu)) { setActive(); @@ -169,18 +218,32 @@ if (!children || children.length === 0) { go(path); chilrenMenus.value = []; - openMenu.value = false; + closeMenu(); return; } chilrenMenus.value = children; } - async function setActive() { + async function setActive(setChildren = false) { const path = currentRoute.value?.path; if (!path) return; const parentPath = await getCurrentParentPath(path); activePath.value = parentPath; // hanldeModuleClick(parentPath); + if (unref(getMixSideFixed)) { + const activeMenu = unref(menuModules).find((item) => item.path === unref(activePath)); + const p = activeMenu?.path; + if (p) { + const children = await getChildrenMenus(p); + if (setChildren) { + chilrenMenus.value = children; + openMenu.value = children.length > 0; + } + if (children.length === 0) { + chilrenMenus.value = []; + } + } + } } function handleMenuClick(path: string) { @@ -188,7 +251,7 @@ } function handleClickOutside() { - openMenu.value = false; + closeMenu(); setActive(); } @@ -203,6 +266,18 @@ }; } + function handleFixedMenu() { + setMenuSetting({ + mixSideFixed: !unref(getIsFixed), + }); + } + + function closeMenu() { + if (!unref(getIsFixed)) { + openMenu.value = false; + } + } + return { t, prefixCls, @@ -221,6 +296,9 @@ getMenuTheme, getItemEvents, getMenuEvents, + getDomStyle, + handleFixedMenu, + getMixSideFixed, }; }, }); @@ -241,7 +319,7 @@ min-width: @width; overflow: hidden; background: @sider-dark-bg-color; - transition: all 0.2s ease 0s; + transition: all 0.3s ease 0s; flex: 0 0 @width; .@{tag-prefix-cls} { position: absolute; @@ -293,6 +371,17 @@ } } } + .@{prefix-cls}-menu-list { + &__title { + .pushpin { + color: rgba(0, 0, 0, 0.35); + + &:hover { + color: rgba(0, 0, 0, 0.85); + } + } + } + } } @border-color: @sider-dark-lighten-1-bg-color; @@ -388,20 +477,30 @@ &__title { display: flex; height: @header-height; - margin-left: -6px; + // margin-left: -6px; font-size: 18px; color: @primary-color; border-bottom: 1px solid rgb(238, 238, 238); opacity: 0; transition: unset; - // justify-content: center; align-items: center; - justify-content: start; + justify-content: space-between; &.show { + min-width: 130px; opacity: 1; transition: all 0.5s ease; } + + .pushpin { + margin-right: 6px; + color: rgba(255, 255, 255, 0.65); + cursor: pointer; + + &:hover { + color: #fff; + } + } } &__content { diff --git a/src/locales/lang/en/layout/setting.ts b/src/locales/lang/en/layout/setting.ts index 8d06aaaf..771e9e5f 100644 --- a/src/locales/lang/en/layout/setting.ts +++ b/src/locales/lang/en/layout/setting.ts @@ -77,4 +77,6 @@ export default { mixSidebarTrigger: 'Mixed menu Trigger', triggerHover: 'Hover', triggerClick: 'Click', + + mixSidebarFixed: 'Fixed expanded menu', }; diff --git a/src/locales/lang/zh_CN/layout/setting.ts b/src/locales/lang/zh_CN/layout/setting.ts index 18fc675c..3b2e847c 100644 --- a/src/locales/lang/zh_CN/layout/setting.ts +++ b/src/locales/lang/zh_CN/layout/setting.ts @@ -76,4 +76,6 @@ export default { mixSidebarTrigger: '混合菜单触发方式', triggerHover: '悬停', triggerClick: '点击', + + mixSidebarFixed: '固定展开菜单', }; diff --git a/src/settings/projectSetting.ts b/src/settings/projectSetting.ts index 13ed7a01..06422313 100644 --- a/src/settings/projectSetting.ts +++ b/src/settings/projectSetting.ts @@ -110,6 +110,8 @@ const setting: ProjectConfig = { closeMixSidebarOnChange: false, // Module opening method ‘click’ |'hover' mixSideTrigger: MixSidebarTriggerEnum.CLICK, + // Fixed expanded menu + mixSideFixed: false, }, // Multi-label diff --git a/src/types/config.d.ts b/src/types/config.d.ts index 68292938..f7c11e3e 100644 --- a/src/types/config.d.ts +++ b/src/types/config.d.ts @@ -21,6 +21,7 @@ export interface MenuSetting { closeMixSidebarOnChange: boolean; collapsedShowTitle: boolean; mixSideTrigger: MixSidebarTriggerEnum; + mixSideFixed: boolean; } export interface MultiTabsSetting { diff --git a/tsconfig.json b/tsconfig.json index 6dae0d79..2f3de4d3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,13 +1,13 @@ { "compilerOptions": { - "target": "es2016", + "target": "esnext", "module": "esnext", "moduleResolution": "node", "strict": true, "forceConsistentCasingInFileNames": true, "allowSyntheticDefaultImports": true, "strictFunctionTypes": false, - "jsx": "react", + "jsx": "preserve", "baseUrl": ".", "allowJs": true, "sourceMap": true,