79 changed files with 1381 additions and 1184 deletions
@ -1,7 +1,8 @@ |
|||
import AppLocalPicker from './src/AppLocalPicker.vue'; |
|||
import AppFooterToolbar from './src/AppFooterToolbar.vue'; |
|||
import AppPageFooter from './src/AppPageFooter.vue'; |
|||
import AppLogo from './src/AppLogo.vue'; |
|||
import { withInstall } from '../util'; |
|||
|
|||
export { AppLocalPicker, AppFooterToolbar }; |
|||
export { AppLocalPicker, AppPageFooter, AppLogo }; |
|||
|
|||
export default withInstall(AppLocalPicker, AppFooterToolbar); |
|||
export default withInstall(AppLocalPicker, AppPageFooter, AppLogo); |
|||
|
|||
@ -1,60 +0,0 @@ |
|||
<template> |
|||
<div class="app-footer" :style="{ width: getWidth }"> |
|||
<div class="app-footer__left"> |
|||
<slot name="left" /> |
|||
</div> |
|||
<div class="app-footer__right"> |
|||
<slot name="right" /> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent, computed, unref } from 'vue'; |
|||
|
|||
import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum'; |
|||
|
|||
import { appStore } from '/@/store/modules/app'; |
|||
import { menuStore } from '/@/store/modules/menu'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'AppFooterToolbar', |
|||
setup() { |
|||
const getMiniWidth = computed(() => { |
|||
const { |
|||
menuSetting: { collapsedShowTitle }, |
|||
} = appStore.getProjectConfig; |
|||
return collapsedShowTitle ? SIDE_BAR_SHOW_TIT_MINI_WIDTH : SIDE_BAR_MINI_WIDTH; |
|||
}); |
|||
|
|||
const getWidth = computed(() => { |
|||
const { getCollapsedState, getMenuWidthState } = menuStore; |
|||
const width = getCollapsedState ? unref(getMiniWidth) : getMenuWidthState; |
|||
return `calc(100% - ${width}px)`; |
|||
}); |
|||
|
|||
return { getWidth }; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less" scoped> |
|||
.app-footer { |
|||
position: fixed; |
|||
right: 0; |
|||
bottom: 0; |
|||
z-index: 99; |
|||
display: flex; |
|||
width: 100%; |
|||
align-items: center; |
|||
padding: 0 24px; |
|||
line-height: 44px; |
|||
background: #fff; |
|||
border-top: 1px solid #f0f0f0; |
|||
box-shadow: 0 -6px 16px -8px rgba(0, 0, 0, 0.08), 0 -9px 28px 0 rgba(0, 0, 0, 0.05), |
|||
0 -12px 48px 16px rgba(0, 0, 0, 0.03); |
|||
transition: width 0.3s; |
|||
|
|||
&__left { |
|||
flex: 1 1; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,72 @@ |
|||
<template> |
|||
<div class="app-logo anticon" :class="theme" @click="handleGoHome"> |
|||
<img src="/@/assets/images/logo.png" /> |
|||
<div class="app-logo__title ml-2 ellipsis">{{ globSetting.title }}</div> |
|||
</div> |
|||
</template> |
|||
<script lang="ts"> |
|||
import type { PropType } from 'vue'; |
|||
import { defineComponent } from 'vue'; |
|||
|
|||
import { useGlobSetting } from '/@/hooks/setting'; |
|||
import { useGo } from '/@/hooks/web/usePage'; |
|||
|
|||
import { PageEnum } from '/@/enums/pageEnum'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'AppLogo', |
|||
props: { |
|||
/** |
|||
* The theme of the current parent component |
|||
*/ |
|||
theme: { |
|||
type: String as PropType<string>, |
|||
}, |
|||
}, |
|||
setup() { |
|||
const globSetting = useGlobSetting(); |
|||
const go = useGo(); |
|||
|
|||
function handleGoHome(): void { |
|||
go(PageEnum.BASE_HOME); |
|||
} |
|||
|
|||
return { |
|||
handleGoHome, |
|||
globSetting, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less" scoped> |
|||
@import (reference) '../../../design/index.less'; |
|||
|
|||
.app-logo { |
|||
display: flex; |
|||
align-items: center; |
|||
padding-left: 16px; |
|||
cursor: pointer; |
|||
|
|||
&.light { |
|||
border-bottom: 1px solid @border-color-base; |
|||
} |
|||
|
|||
&.light &__title { |
|||
color: @primary-color; |
|||
} |
|||
|
|||
&.dark &__title { |
|||
color: @white; |
|||
} |
|||
|
|||
&__title { |
|||
font-size: 18px; |
|||
font-weight: 700; |
|||
opacity: 0; |
|||
transition: all 0.5s; |
|||
.respond-to(medium,{ |
|||
opacity: 1; |
|||
}); |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,46 @@ |
|||
<template> |
|||
<div class="app-footer" :style="{ width: getCalcContentWidth }"> |
|||
<div class="app-footer__left"> |
|||
<slot name="left" /> |
|||
</div> |
|||
<div class="app-footer__right"> |
|||
<slot name="right" /> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent } from 'vue'; |
|||
|
|||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'AppFooterToolbar', |
|||
setup() { |
|||
const { getCalcContentWidth } = useMenuSetting(); |
|||
|
|||
return { getCalcContentWidth }; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less" scoped> |
|||
.app-footer { |
|||
position: fixed; |
|||
right: 0; |
|||
bottom: 0; |
|||
z-index: 99; |
|||
display: flex; |
|||
width: 100%; |
|||
align-items: center; |
|||
padding: 0 24px; |
|||
line-height: 44px; |
|||
background: #fff; |
|||
border-top: 1px solid #f0f0f0; |
|||
box-shadow: 0 -6px 16px -8px rgba(0, 0, 0, 0.08), 0 -9px 28px 0 rgba(0, 0, 0, 0.05), |
|||
0 -12px 48px 16px rgba(0, 0, 0, 0.03); |
|||
transition: width 0.4s; |
|||
|
|||
&__left { |
|||
flex: 1 1; |
|||
} |
|||
} |
|||
</style> |
|||
@ -1 +1,5 @@ |
|||
export { default as BasicMenu } from './src/BasicMenu'; |
|||
import BasicMenu from './src/BasicMenu'; |
|||
import { withInstall } from '../util'; |
|||
|
|||
export default withInstall(BasicMenu); |
|||
export { BasicMenu }; |
|||
|
|||
@ -1,5 +1,5 @@ |
|||
import type { Menu as MenuType } from '/@/router/types'; |
|||
import type { MenuState } from './types'; |
|||
import type { MenuState } from '../types'; |
|||
import type { Ref } from 'vue'; |
|||
|
|||
import { isString } from '/@/utils/is'; |
|||
@ -0,0 +1,3 @@ |
|||
import { defineComponent } from 'vue'; |
|||
|
|||
export type Component = ReturnType<typeof defineComponent>; |
|||
@ -0,0 +1,20 @@ |
|||
import { customRef } from 'vue'; |
|||
|
|||
export function useDebouncedRef<T = any>(value: T, delay = 100) { |
|||
let timeout: TimeoutHandle; |
|||
return customRef((track, trigger) => { |
|||
return { |
|||
get() { |
|||
track(); |
|||
return value; |
|||
}, |
|||
set(newValue: T) { |
|||
clearTimeout(timeout); |
|||
timeout = setTimeout(() => { |
|||
value = newValue; |
|||
trigger(); |
|||
}, delay); |
|||
}, |
|||
}; |
|||
}); |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
import type { HeaderSetting } from '/@/types/config'; |
|||
|
|||
import { computed, unref } from 'vue'; |
|||
|
|||
import { appStore } from '/@/store/modules/app'; |
|||
|
|||
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; |
|||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
|||
import { useRootSetting } from '/@/hooks/setting/useRootSetting'; |
|||
|
|||
import { MenuModeEnum } from '/@/enums/menuEnum'; |
|||
|
|||
export function useHeaderSetting() { |
|||
const { getShow: getShowMultipleTab } = useMultipleTabSetting(); |
|||
const { getMode, getSplit, getShowHeaderTrigger, getIsSidebarType } = useMenuSetting(); |
|||
const { getShowBreadCrumb, getShowLogo } = useRootSetting(); |
|||
|
|||
// Get header configuration
|
|||
const getHeaderSetting = computed(() => appStore.getProjectConfig.headerSetting); |
|||
|
|||
const getShowDoc = computed(() => unref(getHeaderSetting).showDoc); |
|||
|
|||
const getTheme = computed(() => unref(getHeaderSetting).theme); |
|||
|
|||
const getShowRedo = computed(() => unref(getHeaderSetting).showRedo && unref(getShowMultipleTab)); |
|||
|
|||
const getUseLockPage = computed(() => unref(getHeaderSetting).useLockPage); |
|||
|
|||
const getShowFullScreen = computed(() => unref(getHeaderSetting).showFullScreen); |
|||
|
|||
const getShowNotice = computed(() => unref(getHeaderSetting).showNotice); |
|||
|
|||
const getShowBread = computed(() => { |
|||
return ( |
|||
unref(getMode) !== MenuModeEnum.HORIZONTAL && unref(getShowBreadCrumb) && !unref(getSplit) |
|||
); |
|||
}); |
|||
|
|||
const getShowHeaderLogo = computed(() => { |
|||
return unref(getShowLogo) && !unref(getIsSidebarType); |
|||
}); |
|||
|
|||
const getShowContent = computed(() => { |
|||
return unref(getShowBread) || unref(getShowHeaderTrigger); |
|||
}); |
|||
|
|||
// Set header configuration
|
|||
function setHeaderSetting(headerSetting: Partial<HeaderSetting>): void { |
|||
appStore.commitProjectConfigState({ headerSetting }); |
|||
} |
|||
|
|||
return { |
|||
setHeaderSetting, |
|||
|
|||
getHeaderSetting, |
|||
|
|||
getShowDoc, |
|||
getTheme, |
|||
getShowRedo, |
|||
getUseLockPage, |
|||
getShowFullScreen, |
|||
getShowNotice, |
|||
getShowBread, |
|||
getShowContent, |
|||
getShowHeaderLogo, |
|||
}; |
|||
} |
|||
@ -0,0 +1,120 @@ |
|||
import type { MenuSetting } from '/@/types/config'; |
|||
|
|||
import { computed, unref } from 'vue'; |
|||
|
|||
import { appStore } from '/@/store/modules/app'; |
|||
|
|||
import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum'; |
|||
import { MenuModeEnum, MenuTypeEnum, TriggerEnum } from '/@/enums/menuEnum'; |
|||
|
|||
export function useMenuSetting() { |
|||
// Get menu configuration
|
|||
const getMenuSetting = computed(() => appStore.getProjectConfig.menuSetting); |
|||
|
|||
const getMiniWidth = computed(() => unref(getMenuSetting).menuWidth); |
|||
|
|||
const getCollapsed = computed(() => unref(getMenuSetting).collapsed); |
|||
|
|||
const getType = computed(() => unref(getMenuSetting).type); |
|||
|
|||
const getMode = computed(() => unref(getMenuSetting).mode); |
|||
|
|||
const getShow = computed(() => unref(getMenuSetting).show); |
|||
|
|||
const getMenuWidth = computed(() => unref(getMenuSetting).menuWidth); |
|||
|
|||
const getTrigger = computed(() => unref(getMenuSetting).trigger); |
|||
|
|||
const getTheme = computed(() => unref(getMenuSetting).theme); |
|||
|
|||
const getSplit = computed(() => unref(getMenuSetting).split); |
|||
|
|||
const getHasDrag = computed(() => unref(getMenuSetting).hasDrag); |
|||
|
|||
const getAccordion = computed(() => unref(getMenuSetting).accordion); |
|||
|
|||
const getCollapsedShowTitle = computed(() => unref(getMenuSetting).collapsedShowTitle); |
|||
|
|||
const getCollapsedShowSearch = computed(() => unref(getMenuSetting).collapsedShowSearch); |
|||
|
|||
const getTopMenuAlign = computed(() => unref(getMenuSetting).topMenuAlign); |
|||
|
|||
const getIsSidebarType = computed(() => unref(getType) === MenuTypeEnum.SIDEBAR); |
|||
|
|||
const getShowTopMenu = computed(() => { |
|||
return unref(getMode) === MenuModeEnum.HORIZONTAL || unref(getSplit); |
|||
}); |
|||
|
|||
const getShowHeaderTrigger = computed(() => { |
|||
if ( |
|||
unref(getType) === MenuTypeEnum.TOP_MENU || |
|||
!unref(getShow) || |
|||
!unref(getMenuSetting).hidden |
|||
) { |
|||
return false; |
|||
} |
|||
|
|||
return unref(getTrigger) === TriggerEnum.HEADER; |
|||
}); |
|||
|
|||
const getShowSearch = computed(() => { |
|||
return ( |
|||
unref(getMenuSetting).showSearch && |
|||
!(unref(getType) === MenuTypeEnum.MIX && unref(getMode) === MenuModeEnum.HORIZONTAL) |
|||
); |
|||
}); |
|||
|
|||
const getIsHorizontal = computed(() => { |
|||
return unref(getMode) === MenuModeEnum.HORIZONTAL; |
|||
}); |
|||
|
|||
const getMiniWidthNumber = computed(() => { |
|||
const { collapsedShowTitle } = unref(getMenuSetting); |
|||
return collapsedShowTitle ? SIDE_BAR_SHOW_TIT_MINI_WIDTH : SIDE_BAR_MINI_WIDTH; |
|||
}); |
|||
|
|||
const getCalcContentWidth = computed(() => { |
|||
const width = unref(getCollapsed) ? unref(getMiniWidthNumber) : unref(getMiniWidth); |
|||
return `calc(100% - ${width}px)`; |
|||
}); |
|||
|
|||
// Set menu configuration
|
|||
function setMenuSetting(menuSetting: Partial<MenuSetting>): void { |
|||
appStore.commitProjectConfigState({ menuSetting }); |
|||
} |
|||
|
|||
function toggleCollapsed() { |
|||
setMenuSetting({ |
|||
collapsed: !unref(getCollapsed), |
|||
}); |
|||
} |
|||
|
|||
return { |
|||
setMenuSetting, |
|||
|
|||
toggleCollapsed, |
|||
|
|||
getMenuSetting, |
|||
getMiniWidth, |
|||
getType, |
|||
getMode, |
|||
getShow, |
|||
getCollapsed, |
|||
getMiniWidthNumber, |
|||
getCalcContentWidth, |
|||
getMenuWidth, |
|||
getTrigger, |
|||
getSplit, |
|||
getTheme, |
|||
getHasDrag, |
|||
getIsHorizontal, |
|||
getShowSearch, |
|||
getCollapsedShowTitle, |
|||
getCollapsedShowSearch, |
|||
getIsSidebarType, |
|||
getAccordion, |
|||
getShowTopMenu, |
|||
getShowHeaderTrigger, |
|||
getTopMenuAlign, |
|||
}; |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
import type { MultiTabsSetting } from '/@/types/config'; |
|||
|
|||
import { computed, unref } from 'vue'; |
|||
|
|||
import { appStore } from '/@/store/modules/app'; |
|||
|
|||
export function useMultipleTabSetting() { |
|||
const getMultipleTabSetting = computed(() => appStore.getProjectConfig.multiTabsSetting); |
|||
|
|||
const getMax = computed(() => unref(getMultipleTabSetting).max); |
|||
|
|||
const getShow = computed(() => unref(getMultipleTabSetting).show); |
|||
|
|||
function setMultipleTabSetting(multiTabsSetting: Partial<MultiTabsSetting>) { |
|||
appStore.commitProjectConfigState({ multiTabsSetting }); |
|||
} |
|||
|
|||
return { |
|||
setMultipleTabSetting, |
|||
|
|||
getMultipleTabSetting, |
|||
getMax, |
|||
getShow, |
|||
}; |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
import type { ProjectConfig } from '/@/types/config'; |
|||
|
|||
import { computed, unref } from 'vue'; |
|||
|
|||
import { appStore } from '/@/store/modules/app'; |
|||
|
|||
type RootSetting = Omit< |
|||
ProjectConfig, |
|||
'locale' | 'headerSetting' | 'menuSetting' | 'multiTabsSetting' |
|||
>; |
|||
export function useRootSetting() { |
|||
const getRootSetting = computed((): RootSetting => appStore.getProjectConfig); |
|||
|
|||
const getOpenPageLoading = computed(() => unref(getRootSetting).openPageLoading); |
|||
|
|||
const getOpenRouterTransition = computed(() => unref(getRootSetting).openRouterTransition); |
|||
|
|||
const getOpenKeepAlive = computed(() => unref(getRootSetting).openKeepAlive); |
|||
|
|||
const getRouterTransition = computed(() => unref(getRootSetting).routerTransition); |
|||
|
|||
const getCanEmbedIFramePage = computed(() => unref(getRootSetting).canEmbedIFramePage); |
|||
|
|||
const getPermissionMode = computed(() => unref(getRootSetting).permissionMode); |
|||
|
|||
const getShowLogo = computed(() => unref(getRootSetting).showLogo); |
|||
|
|||
const getUseErrorHandle = computed(() => unref(getRootSetting).useErrorHandle); |
|||
|
|||
const getShowBreadCrumb = computed(() => unref(getRootSetting).showBreadCrumb); |
|||
|
|||
const getShowBreadCrumbIcon = computed(() => unref(getRootSetting).showBreadCrumbIcon); |
|||
|
|||
function setRootSetting(setting: RootSetting) { |
|||
appStore.commitProjectConfigState(setting); |
|||
} |
|||
|
|||
return { |
|||
setRootSetting, |
|||
|
|||
getRootSetting, |
|||
getOpenPageLoading, |
|||
getOpenRouterTransition, |
|||
getOpenKeepAlive, |
|||
getRouterTransition, |
|||
getCanEmbedIFramePage, |
|||
getPermissionMode, |
|||
getShowLogo, |
|||
getUseErrorHandle, |
|||
getShowBreadCrumb, |
|||
getShowBreadCrumbIcon, |
|||
}; |
|||
} |
|||
@ -1,20 +0,0 @@ |
|||
import { computed } from 'vue'; |
|||
import { appStore } from '/@/store/modules/app'; |
|||
|
|||
export function useSideBar() { |
|||
const currentCollapsedRef = computed(() => { |
|||
return appStore.getProjectConfig.menuSetting.collapsed; |
|||
}); |
|||
const changeCollapsed = (collapsed: boolean) => { |
|||
appStore.commitProjectConfigState({ |
|||
menuSetting: { |
|||
collapsed: collapsed, |
|||
}, |
|||
}); |
|||
}; |
|||
return { |
|||
openSider: changeCollapsed(false), |
|||
closeSider: changeCollapsed(true), |
|||
currentCollapsedRef, |
|||
}; |
|||
} |
|||
@ -1,214 +0,0 @@ |
|||
import { computed, defineComponent, nextTick, onMounted, ref, unref } from 'vue'; |
|||
|
|||
import { Layout } from 'ant-design-vue'; |
|||
import LayoutTrigger from './LayoutTrigger'; |
|||
import LayoutMenu from '/@/layouts/default/menu/LayoutMenu'; |
|||
|
|||
import { menuStore } from '/@/store/modules/menu'; |
|||
import { appStore } from '/@/store/modules/app'; |
|||
|
|||
import { MenuModeEnum, MenuSplitTyeEnum, TriggerEnum } from '/@/enums/menuEnum'; |
|||
import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum'; |
|||
|
|||
import { useDebounce } from '/@/hooks/core/useDebounce'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'DefaultLayoutSideBar', |
|||
setup() { |
|||
const initRef = ref(false); |
|||
const brokenRef = ref(false); |
|||
const collapseRef = ref(true); |
|||
const dragBarRef = ref<Nullable<HTMLDivElement>>(null); |
|||
const sideRef = ref<any>(null); |
|||
|
|||
const getProjectConfigRef = computed(() => { |
|||
return appStore.getProjectConfig; |
|||
}); |
|||
|
|||
const getMiniWidth = computed(() => { |
|||
const { |
|||
menuSetting: { collapsedShowTitle }, |
|||
} = unref(getProjectConfigRef); |
|||
return collapsedShowTitle ? SIDE_BAR_SHOW_TIT_MINI_WIDTH : SIDE_BAR_MINI_WIDTH; |
|||
}); |
|||
|
|||
function onCollapseChange(val: boolean) { |
|||
if (initRef.value) { |
|||
collapseRef.value = val; |
|||
menuStore.commitCollapsedState(val); |
|||
} else { |
|||
const collapsed = appStore.getProjectConfig.menuSetting.collapsed; |
|||
!collapsed && menuStore.commitCollapsedState(val); |
|||
} |
|||
initRef.value = true; |
|||
} |
|||
|
|||
// Menu area drag and drop-mouse movement
|
|||
function handleMouseMove(ele: any, wrap: any, clientX: number) { |
|||
document.onmousemove = function (innerE) { |
|||
let iT = ele.left + ((innerE || event).clientX - clientX); |
|||
innerE = innerE || window.event; |
|||
// let tarnameb = innerE.target || innerE.srcElement;
|
|||
const maxT = 600; |
|||
const minT = unref(getMiniWidth); |
|||
iT < 0 && (iT = 0); |
|||
iT > maxT && (iT = maxT); |
|||
iT < minT && (iT = minT); |
|||
ele.style.left = wrap.style.width = iT + 'px'; |
|||
return false; |
|||
}; |
|||
} |
|||
|
|||
// 菜单区域拖拽 - 鼠标松开
|
|||
function removeMouseup(ele: any) { |
|||
const wrap = unref(sideRef).$el; |
|||
document.onmouseup = function () { |
|||
document.onmousemove = null; |
|||
document.onmouseup = null; |
|||
const width = parseInt(wrap.style.width); |
|||
menuStore.commitDragStartState(false); |
|||
if (!menuStore.getCollapsedState) { |
|||
if (width > unref(getMiniWidth) + 20) { |
|||
setMenuWidth(width); |
|||
} else { |
|||
menuStore.commitCollapsedState(true); |
|||
} |
|||
} else { |
|||
if (width > unref(getMiniWidth)) { |
|||
setMenuWidth(width); |
|||
menuStore.commitCollapsedState(false); |
|||
} |
|||
} |
|||
|
|||
ele.releaseCapture && ele.releaseCapture(); |
|||
}; |
|||
} |
|||
|
|||
function setMenuWidth(width: number) { |
|||
appStore.commitProjectConfigState({ |
|||
menuSetting: { |
|||
menuWidth: width, |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
function changeWrapWidth() { |
|||
const ele = unref(dragBarRef) as any; |
|||
const side = unref(sideRef); |
|||
|
|||
const wrap = (side || {}).$el; |
|||
ele && |
|||
(ele.onmousedown = (e: any) => { |
|||
menuStore.commitDragStartState(true); |
|||
wrap.style.transition = 'unset'; |
|||
const clientX = (e || event).clientX; |
|||
ele.left = ele.offsetLeft; |
|||
handleMouseMove(ele, wrap, clientX); |
|||
removeMouseup(ele); |
|||
ele.setCapture && ele.setCapture(); |
|||
return false; |
|||
}); |
|||
} |
|||
function handleBreakpoint(broken: boolean) { |
|||
brokenRef.value = broken; |
|||
} |
|||
|
|||
const getDragBarStyle = computed(() => { |
|||
if (menuStore.getCollapsedState) { |
|||
return { left: `${unref(getMiniWidth)}px` }; |
|||
} |
|||
return {}; |
|||
}); |
|||
|
|||
const getCollapsedWidth = computed(() => { |
|||
return unref(brokenRef) ? 0 : unref(getMiniWidth); |
|||
}); |
|||
|
|||
const showTrigger = computed(() => { |
|||
const { |
|||
menuSetting: { trigger }, |
|||
} = unref(getProjectConfigRef); |
|||
return trigger !== TriggerEnum.NONE && trigger === TriggerEnum.FOOTER; |
|||
}); |
|||
|
|||
onMounted(() => { |
|||
nextTick(() => { |
|||
const [exec] = useDebounce(changeWrapWidth, 20); |
|||
exec(); |
|||
}); |
|||
}); |
|||
|
|||
function handleSiderClick(e: ChangeEvent) { |
|||
if (!e || !e.target || e.target.className !== 'basic-menu__content') return; |
|||
|
|||
const { collapsed, show } = appStore.getProjectConfig.menuSetting; |
|||
if (!collapsed || !show) return; |
|||
appStore.commitProjectConfigState({ |
|||
menuSetting: { |
|||
collapsed: false, |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
function renderDragLine() { |
|||
const { menuSetting: { hasDrag = true } = {} } = unref(getProjectConfigRef); |
|||
return ( |
|||
<div |
|||
class={[`layout-sidebar__dargbar`, !hasDrag ? 'hide' : '']} |
|||
style={unref(getDragBarStyle)} |
|||
ref={dragBarRef} |
|||
/> |
|||
); |
|||
} |
|||
|
|||
return () => { |
|||
const { |
|||
menuSetting: { theme, split: splitMenu }, |
|||
} = unref(getProjectConfigRef); |
|||
const { getCollapsedState, getMenuWidthState } = menuStore; |
|||
|
|||
const triggerDom = unref(showTrigger) |
|||
? { |
|||
trigger: () => <LayoutTrigger />, |
|||
} |
|||
: {}; |
|||
|
|||
const triggerAttr = unref(showTrigger) |
|||
? {} |
|||
: { |
|||
trigger: null, |
|||
}; |
|||
|
|||
return ( |
|||
<Layout.Sider |
|||
onClick={handleSiderClick} |
|||
onCollapse={onCollapseChange} |
|||
breakpoint="md" |
|||
width={getMenuWidthState} |
|||
collapsed={getCollapsedState} |
|||
collapsible |
|||
collapsedWidth={unref(getCollapsedWidth)} |
|||
theme={theme} |
|||
class="layout-sidebar" |
|||
ref={sideRef} |
|||
onBreakpoint={handleBreakpoint} |
|||
{...triggerAttr} |
|||
> |
|||
{{ |
|||
...triggerDom, |
|||
default: () => ( |
|||
<> |
|||
<LayoutMenu |
|||
theme={theme} |
|||
menuMode={splitMenu ? MenuModeEnum.INLINE : null} |
|||
splitType={splitMenu ? MenuSplitTyeEnum.LEFT : MenuSplitTyeEnum.NONE} |
|||
/> |
|||
{renderDragLine()} |
|||
</> |
|||
), |
|||
}} |
|||
</Layout.Sider> |
|||
); |
|||
}; |
|||
}, |
|||
}); |
|||
@ -0,0 +1,113 @@ |
|||
import type { Menu } from '/@/router/types'; |
|||
import type { Ref } from 'vue'; |
|||
|
|||
import { watch, unref, ref, computed } from 'vue'; |
|||
import { useRouter } from 'vue-router'; |
|||
|
|||
import { MenuSplitTyeEnum } from '/@/enums/menuEnum'; |
|||
import { useThrottle } from '/@/hooks/core/useThrottle'; |
|||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
|||
|
|||
import { |
|||
getChildrenMenus, |
|||
getCurrentParentPath, |
|||
getFlatChildrenMenus, |
|||
getFlatMenus, |
|||
getMenus, |
|||
getShallowMenus, |
|||
} from '/@/router/menus'; |
|||
import { permissionStore } from '/@/store/modules/permission'; |
|||
|
|||
export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) { |
|||
// Menu array
|
|||
const menusRef = ref<Menu[]>([]); |
|||
// flat menu array
|
|||
const flatMenusRef = ref<Menu[]>([]); |
|||
|
|||
const { currentRoute } = useRouter(); |
|||
|
|||
const { setMenuSetting, getIsHorizontal, getSplit } = useMenuSetting(); |
|||
|
|||
const [throttleHandleSplitLeftMenu] = useThrottle(handleSplitLeftMenu, 50); |
|||
|
|||
const splitNotLeft = computed( |
|||
() => unref(splitType) !== MenuSplitTyeEnum.LEFT && !unref(getIsHorizontal) |
|||
); |
|||
|
|||
const splitLeft = computed(() => !unref(getSplit) || unref(splitType) !== MenuSplitTyeEnum.LEFT); |
|||
|
|||
const spiltTop = computed(() => unref(splitType) === MenuSplitTyeEnum.TOP); |
|||
|
|||
const normalType = computed(() => { |
|||
return unref(splitType) === MenuSplitTyeEnum.NONE || !unref(getSplit); |
|||
}); |
|||
|
|||
watch( |
|||
[() => unref(currentRoute).path, () => unref(splitType)], |
|||
async ([path]: [string, MenuSplitTyeEnum]) => { |
|||
if (unref(splitNotLeft)) return; |
|||
|
|||
const parentPath = await getCurrentParentPath(path); |
|||
parentPath && throttleHandleSplitLeftMenu(parentPath); |
|||
}, |
|||
{ |
|||
immediate: true, |
|||
} |
|||
); |
|||
|
|||
// Menu changes
|
|||
watch( |
|||
[() => permissionStore.getLastBuildMenuTimeState, () => permissionStore.getBackMenuListState], |
|||
() => { |
|||
genMenus(); |
|||
}, |
|||
{ |
|||
immediate: true, |
|||
} |
|||
); |
|||
|
|||
// split Menu changes
|
|||
watch([() => getSplit.value], () => { |
|||
if (unref(splitNotLeft)) return; |
|||
genMenus(); |
|||
}); |
|||
|
|||
// Handle left menu split
|
|||
async function handleSplitLeftMenu(parentPath: string) { |
|||
if (unref(splitLeft)) return; |
|||
|
|||
// spilt mode left
|
|||
const children = await getChildrenMenus(parentPath); |
|||
if (!children) { |
|||
setMenuSetting({ hidden: false }); |
|||
flatMenusRef.value = []; |
|||
menusRef.value = []; |
|||
return; |
|||
} |
|||
|
|||
const flatChildren = await getFlatChildrenMenus(children); |
|||
setMenuSetting({ hidden: true }); |
|||
flatMenusRef.value = flatChildren; |
|||
menusRef.value = children; |
|||
} |
|||
|
|||
// get menus
|
|||
async function genMenus() { |
|||
// normal mode
|
|||
if (unref(normalType)) { |
|||
flatMenusRef.value = await getFlatMenus(); |
|||
menusRef.value = await getMenus(); |
|||
return; |
|||
} |
|||
|
|||
// split-top
|
|||
if (unref(spiltTop)) { |
|||
const shallowMenus = await getShallowMenus(); |
|||
|
|||
flatMenusRef.value = shallowMenus; |
|||
menusRef.value = shallowMenus; |
|||
return; |
|||
} |
|||
} |
|||
return { flatMenusRef, menusRef }; |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
import './index.less'; |
|||
|
|||
import { computed, defineComponent, ref, unref } from 'vue'; |
|||
|
|||
import { Layout } from 'ant-design-vue'; |
|||
import LayoutMenu from '/@/layouts/default/menu/LayoutMenu'; |
|||
|
|||
import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum'; |
|||
|
|||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
|||
import { useTrigger, useDragLine, useSiderEvent } from './useLayoutSider'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'LayoutSideBar', |
|||
setup() { |
|||
const dragBarRef = ref<Nullable<HTMLDivElement>>(null); |
|||
const sideRef = ref<Nullable<HTMLDivElement>>(null); |
|||
|
|||
const { getCollapsed, getMenuWidth, getSplit, getTheme } = useMenuSetting(); |
|||
|
|||
const { getTriggerAttr, getTriggerSlot } = useTrigger(); |
|||
|
|||
const { renderDragLine } = useDragLine(sideRef, dragBarRef); |
|||
|
|||
const { |
|||
getCollapsedWidth, |
|||
onBreakpointChange, |
|||
onCollapseChange, |
|||
onSiderClick, |
|||
} = useSiderEvent(); |
|||
|
|||
const getMode = computed(() => { |
|||
return unref(getSplit) ? MenuModeEnum.INLINE : null; |
|||
}); |
|||
|
|||
const getSplitType = computed(() => { |
|||
return unref(getSplit) ? MenuSplitTyeEnum.LEFT : MenuSplitTyeEnum.NONE; |
|||
}); |
|||
|
|||
function renderDefault() { |
|||
return ( |
|||
<> |
|||
<LayoutMenu |
|||
theme={unref(getTheme)} |
|||
menuMode={unref(getMode)} |
|||
splitType={unref(getSplitType)} |
|||
/> |
|||
{renderDragLine()} |
|||
</> |
|||
); |
|||
} |
|||
|
|||
return () => { |
|||
return ( |
|||
<Layout.Sider |
|||
ref={sideRef} |
|||
class="layout-sidebar" |
|||
breakpoint="md" |
|||
collapsible |
|||
width={unref(getMenuWidth)} |
|||
collapsed={unref(getCollapsed)} |
|||
collapsedWidth={unref(getCollapsedWidth)} |
|||
theme={unref(getTheme)} |
|||
onClick={onSiderClick} |
|||
onCollapse={onCollapseChange} |
|||
onBreakpoint={onBreakpointChange} |
|||
{...unref(getTriggerAttr)} |
|||
> |
|||
{{ |
|||
...unref(getTriggerSlot), |
|||
default: () => renderDefault(), |
|||
}} |
|||
</Layout.Sider> |
|||
); |
|||
}; |
|||
}, |
|||
}); |
|||
@ -0,0 +1,44 @@ |
|||
@import (reference) '../../../design/index.less'; |
|||
|
|||
.layout-sidebar { |
|||
background-size: 100% 100%; |
|||
|
|||
&.ant-layout-sider-dark { |
|||
background: @sider-dark-bg-color; |
|||
} |
|||
|
|||
&:not(.ant-layout-sider-dark) { |
|||
border-right: 1px solid @border-color-light; |
|||
} |
|||
|
|||
.ant-layout-sider-zero-width-trigger { |
|||
top: 40%; |
|||
z-index: 10; |
|||
} |
|||
|
|||
&__darg-bar { |
|||
position: absolute; |
|||
top: 0; |
|||
right: -2px; |
|||
z-index: @side-drag-z-index; |
|||
width: 2px; |
|||
height: 100%; |
|||
cursor: col-resize; |
|||
border-top: none; |
|||
border-bottom: none; |
|||
|
|||
&.hide { |
|||
display: none; |
|||
} |
|||
|
|||
&:hover { |
|||
background: @primary-color; |
|||
box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.15); |
|||
} |
|||
} |
|||
} |
|||
|
|||
.ant-layout-sider-trigger { |
|||
height: 36px; |
|||
line-height: 36px; |
|||
} |
|||
@ -0,0 +1,163 @@ |
|||
import type { Ref } from 'vue'; |
|||
|
|||
import { computed, unref, onMounted, nextTick, ref } from 'vue'; |
|||
import LayoutTrigger from '/@/layouts/default/LayoutTrigger'; |
|||
|
|||
import { TriggerEnum } from '/@/enums/menuEnum'; |
|||
|
|||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
|||
import { useDebounce } from '/@/hooks/core/useDebounce'; |
|||
|
|||
/** |
|||
* Handle related operations of menu events |
|||
*/ |
|||
export function useSiderEvent() { |
|||
const initRef = ref(false); |
|||
const brokenRef = ref(false); |
|||
const collapseRef = ref(true); |
|||
|
|||
const { setMenuSetting, getCollapsed, getMiniWidthNumber, getShow } = useMenuSetting(); |
|||
|
|||
const getCollapsedWidth = computed(() => { |
|||
return unref(brokenRef) ? 0 : unref(getMiniWidthNumber); |
|||
}); |
|||
|
|||
function onCollapseChange(val: boolean) { |
|||
if (initRef.value) { |
|||
collapseRef.value = val; |
|||
setMenuSetting({ collapsed: val }); |
|||
} else { |
|||
!unref(getCollapsed) && setMenuSetting({ collapsed: val }); |
|||
} |
|||
initRef.value = true; |
|||
} |
|||
|
|||
function onBreakpointChange(broken: boolean) { |
|||
brokenRef.value = broken; |
|||
} |
|||
|
|||
function onSiderClick(e: ChangeEvent) { |
|||
if (!e || !e.target || e.target.className !== 'basic-menu__content') return; |
|||
if (!unref(getCollapsed) || !unref(getShow)) return; |
|||
setMenuSetting({ collapsed: false }); |
|||
} |
|||
return { getCollapsedWidth, onCollapseChange, onBreakpointChange, onSiderClick }; |
|||
} |
|||
|
|||
/** |
|||
* Handle related operations of menu folding |
|||
*/ |
|||
export function useTrigger() { |
|||
const { getTrigger } = useMenuSetting(); |
|||
|
|||
const showTrigger = computed(() => { |
|||
const trigger = unref(getTrigger); |
|||
return trigger !== TriggerEnum.NONE && trigger === TriggerEnum.FOOTER; |
|||
}); |
|||
|
|||
const getTriggerAttr = computed(() => { |
|||
if (unref(showTrigger)) { |
|||
return {}; |
|||
} |
|||
return { |
|||
trigger: null, |
|||
}; |
|||
}); |
|||
|
|||
const getTriggerSlot = computed(() => { |
|||
if (unref(showTrigger)) { |
|||
return { |
|||
trigger: () => <LayoutTrigger />, |
|||
}; |
|||
} |
|||
return {}; |
|||
}); |
|||
|
|||
return { getTriggerAttr, getTriggerSlot }; |
|||
} |
|||
|
|||
/** |
|||
* Handle menu drag and drop related operations |
|||
* @param siderRef |
|||
* @param dragBarRef |
|||
*/ |
|||
export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) { |
|||
const { getMiniWidthNumber, getCollapsed, setMenuSetting, getHasDrag } = useMenuSetting(); |
|||
|
|||
const getDragBarStyle = computed(() => { |
|||
if (unref(getCollapsed)) { |
|||
return { left: `${unref(getMiniWidthNumber)}px` }; |
|||
} |
|||
return {}; |
|||
}); |
|||
|
|||
onMounted(() => { |
|||
nextTick(() => { |
|||
const [exec] = useDebounce(changeWrapWidth, 20); |
|||
exec(); |
|||
}); |
|||
}); |
|||
|
|||
function renderDragLine() { |
|||
return ( |
|||
<div |
|||
class={[`layout-sidebar__darg-bar`, !unref(getHasDrag) ? 'hide' : '']} |
|||
style={unref(getDragBarStyle)} |
|||
ref={dragBarRef} |
|||
/> |
|||
); |
|||
} |
|||
|
|||
function handleMouseMove(ele: HTMLElement, wrap: HTMLElement, clientX: number) { |
|||
document.onmousemove = function (innerE) { |
|||
let iT = (ele as any).left + (innerE.clientX - clientX); |
|||
innerE = innerE || window.event; |
|||
const maxT = 600; |
|||
const minT = unref(getMiniWidthNumber); |
|||
iT < 0 && (iT = 0); |
|||
iT > maxT && (iT = maxT); |
|||
iT < minT && (iT = minT); |
|||
ele.style.left = wrap.style.width = iT + 'px'; |
|||
return false; |
|||
}; |
|||
} |
|||
|
|||
// Drag and drop in the menu area-release the mouse
|
|||
function removeMouseup(ele: any) { |
|||
const wrap = unref(siderRef).$el; |
|||
document.onmouseup = function () { |
|||
document.onmousemove = null; |
|||
document.onmouseup = null; |
|||
const width = parseInt(wrap.style.width); |
|||
const miniWidth = unref(getMiniWidthNumber); |
|||
|
|||
if (!unref(getCollapsed)) { |
|||
width > miniWidth + 20 |
|||
? setMenuSetting({ menuWidth: width }) |
|||
: setMenuSetting({ collapsed: true }); |
|||
} else { |
|||
width > miniWidth && setMenuSetting({ collapsed: false, menuWidth: width }); |
|||
} |
|||
ele.releaseCapture?.(); |
|||
}; |
|||
} |
|||
|
|||
function changeWrapWidth() { |
|||
const ele = unref(dragBarRef) as any; |
|||
const side = unref(siderRef); |
|||
|
|||
const wrap = (side || {}).$el; |
|||
ele && |
|||
(ele.onmousedown = (e: any) => { |
|||
wrap.style.transition = 'unset'; |
|||
const clientX = e?.clientX; |
|||
ele.left = ele.offsetLeft; |
|||
handleMouseMove(ele, wrap, clientX); |
|||
removeMouseup(ele); |
|||
ele.setCapture?.(); |
|||
return false; |
|||
}); |
|||
} |
|||
|
|||
return { renderDragLine }; |
|||
} |
|||
@ -1,105 +0,0 @@ |
|||
<template> |
|||
<div class="app-logo anticon" :class="theme" @click="handleGoHome" :style="wrapStyle"> |
|||
<img src="/@/assets/images/logo.png" /> |
|||
<div v-if="show" class="logo-title ml-2 ellipsis">{{ globSetting.title }}</div> |
|||
</div> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { computed, defineComponent, PropType, ref, watch } from 'vue'; |
|||
// hooks |
|||
import { useGlobSetting } from '/@/settings/use'; |
|||
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; |
|||
import { useGo } from '/@/hooks/web/usePage'; |
|||
|
|||
import { PageEnum } from '/@/enums/pageEnum'; |
|||
import { MenuTypeEnum } from '/@/enums/menuEnum'; |
|||
|
|||
import { menuStore } from '/@/store/modules/menu'; |
|||
import { appStore } from '/@/store/modules/app'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'Logo', |
|||
props: { |
|||
showTitle: { |
|||
type: Boolean as PropType<boolean>, |
|||
default: true, |
|||
}, |
|||
theme: { |
|||
type: String, |
|||
}, |
|||
}, |
|||
setup(props) { |
|||
const showRef = ref<boolean>(!!props.showTitle); |
|||
const globSetting = useGlobSetting(); |
|||
const go = useGo(); |
|||
|
|||
function handleGoHome() { |
|||
go(PageEnum.BASE_HOME); |
|||
} |
|||
|
|||
watch( |
|||
() => props.showTitle, |
|||
(show: boolean) => { |
|||
if (show) { |
|||
useTimeoutFn(() => { |
|||
showRef.value = show; |
|||
}, 280); |
|||
} else { |
|||
showRef.value = show; |
|||
} |
|||
} |
|||
); |
|||
|
|||
const wrapStyle = computed(() => { |
|||
const { getCollapsedState } = menuStore; |
|||
const { |
|||
menuSetting: { menuWidth, type }, |
|||
} = appStore.getProjectConfig; |
|||
const miniWidth = { minWidth: `${menuWidth}px` }; |
|||
if (type !== MenuTypeEnum.SIDEBAR) { |
|||
return miniWidth; |
|||
} |
|||
return getCollapsedState ? {} : miniWidth; |
|||
}); |
|||
|
|||
return { |
|||
handleGoHome, |
|||
globSetting, |
|||
show: showRef, |
|||
wrapStyle, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less" scoped> |
|||
@import (reference) '../../design/index.less'; |
|||
|
|||
.app-logo { |
|||
display: flex; |
|||
align-items: center; |
|||
padding-left: 16px; |
|||
cursor: pointer; |
|||
// justify-content: center; |
|||
&.light { |
|||
border-bottom: 1px solid @border-color-base; |
|||
} |
|||
|
|||
.logo-title { |
|||
font-size: 18px; |
|||
font-weight: 700; |
|||
opacity: 0; |
|||
transition: all 0.5s; |
|||
.respond-to(medium,{ |
|||
opacity: 1; |
|||
}); |
|||
} |
|||
|
|||
// &.dark .logo-title { |
|||
// font-weight: 400; |
|||
// } |
|||
|
|||
&.light .logo-title { |
|||
color: @primary-color; |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,67 +0,0 @@ |
|||
import store from '/@/store'; |
|||
import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper'; |
|||
import { VuexModule, Module, getModule, Mutation } from 'vuex-module-decorators'; |
|||
|
|||
import { appStore } from '/@/store/modules/app'; |
|||
|
|||
const NAME = 'menu'; |
|||
hotModuleUnregisterModule(NAME); |
|||
@Module({ namespaced: true, name: NAME, dynamic: true, store }) |
|||
class Menu extends VuexModule { |
|||
// 是否开始拖拽
|
|||
private dragStartState = false; |
|||
|
|||
private currentTopSplitMenuPathState = ''; |
|||
|
|||
/** |
|||
* @description: 获取窗口名称 |
|||
*/ |
|||
get getCollapsedState() { |
|||
return appStore.getProjectConfig.menuSetting.collapsed; |
|||
} |
|||
|
|||
get getCurrentTopSplitMenuPathState() { |
|||
return this.currentTopSplitMenuPathState; |
|||
} |
|||
|
|||
get getDragStartState() { |
|||
return this.dragStartState; |
|||
} |
|||
|
|||
get getMenuWidthState() { |
|||
return appStore.getProjectConfig.menuSetting.menuWidth; |
|||
} |
|||
|
|||
@Mutation |
|||
commitDragStartState(dragStart: boolean): void { |
|||
this.dragStartState = dragStart; |
|||
} |
|||
|
|||
@Mutation |
|||
commitCurrentTopSplitMenuPathState(path: string): void { |
|||
this.currentTopSplitMenuPathState = path; |
|||
} |
|||
|
|||
// 改变菜单展开状态
|
|||
@Mutation |
|||
commitCollapsedState(collapsed: boolean): void { |
|||
// this.collapsedState = collapsed;
|
|||
appStore.commitProjectConfigState({ |
|||
menuSetting: { |
|||
collapsed: collapsed, |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
@Mutation |
|||
commitMenuWidthState(menuWidth: number): void { |
|||
// this.menuWidthState = menuWidth;
|
|||
appStore.commitProjectConfigState({ |
|||
menuSetting: { |
|||
menuWidth: menuWidth, |
|||
}, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
export const menuStore = getModule<Menu>(Menu); |
|||
Loading…
Reference in new issue