You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
573 lines
13 KiB
573 lines
13 KiB
<template> |
|
<div :class="`${prefixCls}-dom`" :style="getDomStyle"></div> |
|
<div |
|
v-click-outside="handleClickOutside" |
|
:style="getWrapStyle" |
|
:class="[ |
|
prefixCls, |
|
getMenuTheme, |
|
{ |
|
open: openMenu, |
|
mini: getCollapsed |
|
} |
|
]" |
|
v-bind="getMenuEvents" |
|
> |
|
<AppLogo :showTitle="false" :class="`${prefixCls}-logo`" /> |
|
|
|
<LayoutTrigger :class="`${prefixCls}-trigger`" /> |
|
|
|
<ScrollContainer> |
|
<ul :class="`${prefixCls}-module`"> |
|
<li |
|
:class="[ |
|
`${prefixCls}-module__item `, |
|
{ |
|
[`${prefixCls}-module__item--active`]: item.path === activePath |
|
} |
|
]" |
|
v-bind="getItemEvents(item)" |
|
v-for="item in menuModules" |
|
:key="item.path" |
|
> |
|
<SimpleMenuTag :item="item" collapseParent dot /> |
|
<Icon :class="`${prefixCls}-module__icon`" :size="getCollapsed ? 16 : 20" :icon="item.icon || (item.meta && item.meta.icon)" /> |
|
<p :class="`${prefixCls}-module__name`"> |
|
{{ t(item.name) }} |
|
</p> |
|
</li> |
|
</ul> |
|
</ScrollContainer> |
|
|
|
<div :class="`${prefixCls}-menu-list`" ref="sideRef" :style="getMenuStyle"> |
|
<div |
|
v-show="openMenu" |
|
:class="[ |
|
`${prefixCls}-menu-list__title`, |
|
{ |
|
show: openMenu |
|
} |
|
]" |
|
> |
|
<span class="text"> {{ title }}</span> |
|
<Icon :size="16" :icon="getMixSideFixed ? 'ri:pushpin-2-fill' : 'ri:pushpin-2-line'" class="pushpin" @click="handleFixedMenu" /> |
|
</div> |
|
<ScrollContainer :class="`${prefixCls}-menu-list__content`"> |
|
<SimpleMenu :items="childrenMenus" :theme="getMenuTheme" mixSider @menu-click="handleMenuClick" /> |
|
</ScrollContainer> |
|
<div v-show="getShowDragBar && openMenu" :class="`${prefixCls}-drag-bar`" ref="dragBarRef"></div> |
|
</div> |
|
</div> |
|
</template> |
|
<script lang="ts"> |
|
import type { Menu } from '@/router/types' |
|
import type { CSSProperties } from 'vue' |
|
import { computed, defineComponent, onMounted, ref, unref, watch } from 'vue' |
|
import type { RouteLocationNormalized } from 'vue-router' |
|
import { ScrollContainer } from '@/components/Container' |
|
import { SimpleMenu, SimpleMenuTag } from '@/components/SimpleMenu' |
|
import { Icon } from '@/components/Icon' |
|
import { AppLogo } from '@/components/Application' |
|
import { useMenuSetting } from '@/hooks/setting/useMenuSetting' |
|
import { usePermissionStore } from '@/store/modules/permission' |
|
import { useDragLine } from './useLayoutSider' |
|
import { useGlobSetting } from '@/hooks/setting' |
|
import { useDesign } from '@/hooks/web/useDesign' |
|
import { useI18n } from '@/hooks/web/useI18n' |
|
import { useGo } from '@/hooks/web/usePage' |
|
import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '@/enums/appEnum' |
|
import clickOutside from '@/directives/clickOutside' |
|
import { getChildrenMenus, getCurrentParentPath, getShallowMenus } from '@/router/menus' |
|
import { listenerRouteChange } from '@/logics/mitt/routeChange' |
|
import LayoutTrigger from '../trigger/index.vue' |
|
|
|
export default defineComponent({ |
|
name: 'LayoutMixSider', |
|
components: { |
|
ScrollContainer, |
|
AppLogo, |
|
SimpleMenu, |
|
Icon, |
|
LayoutTrigger, |
|
SimpleMenuTag |
|
}, |
|
directives: { |
|
clickOutside |
|
}, |
|
setup() { |
|
let menuModules = ref<Menu[]>([]) |
|
const activePath = ref('') |
|
const childrenMenus = ref<Menu[]>([]) |
|
const openMenu = ref(false) |
|
const dragBarRef = ref<ElRef>(null) |
|
const sideRef = ref<ElRef>(null) |
|
const currentRoute = ref<Nullable<RouteLocationNormalized>>(null) |
|
|
|
const { prefixCls } = useDesign('layout-mix-sider') |
|
const go = useGo() |
|
const { t } = useI18n() |
|
const { |
|
getMenuWidth, |
|
getCanDrag, |
|
getCloseMixSidebarOnChange, |
|
getMenuTheme, |
|
getMixSideTrigger, |
|
getRealWidth, |
|
getMixSideFixed, |
|
mixSideHasChildren, |
|
setMenuSetting, |
|
getIsMixSidebar, |
|
getCollapsed |
|
} = useMenuSetting() |
|
|
|
const { title } = useGlobSetting() |
|
const permissionStore = usePermissionStore() |
|
|
|
useDragLine(sideRef, dragBarRef, true) |
|
|
|
const getMenuStyle = computed((): CSSProperties => { |
|
return { |
|
width: unref(openMenu) ? `${unref(getMenuWidth)}px` : 0, |
|
left: `${unref(getMixSideWidth)}px` |
|
} |
|
}) |
|
|
|
const getIsFixed = computed(() => { |
|
/* eslint-disable-next-line */ |
|
mixSideHasChildren.value = unref(childrenMenus).length > 0 |
|
const isFixed = unref(getMixSideFixed) && unref(mixSideHasChildren) |
|
if (isFixed) { |
|
/* eslint-disable-next-line */ |
|
openMenu.value = true |
|
} |
|
return isFixed |
|
}) |
|
|
|
const getMixSideWidth = computed(() => { |
|
return unref(getCollapsed) ? SIDE_BAR_MINI_WIDTH : SIDE_BAR_SHOW_TIT_MINI_WIDTH |
|
}) |
|
|
|
const getDomStyle = computed((): CSSProperties => { |
|
const fixedWidth = unref(getIsFixed) ? unref(getRealWidth) : 0 |
|
const width = `${unref(getMixSideWidth) + fixedWidth}px` |
|
return getWrapCommonStyle(width) |
|
}) |
|
|
|
const getWrapStyle = computed((): CSSProperties => { |
|
const width = `${unref(getMixSideWidth)}px` |
|
return getWrapCommonStyle(width) |
|
}) |
|
|
|
const getMenuEvents = computed(() => { |
|
return !unref(getMixSideFixed) |
|
? { |
|
onMouseleave: () => { |
|
setActive(true) |
|
closeMenu() |
|
} |
|
} |
|
: {} |
|
}) |
|
|
|
const getShowDragBar = computed(() => unref(getCanDrag)) |
|
|
|
onMounted(async () => { |
|
menuModules.value = await getShallowMenus() |
|
}) |
|
|
|
// Menu changes |
|
watch( |
|
[() => permissionStore.getLastBuildMenuTime, () => permissionStore.getBackMenuList], |
|
async () => { |
|
menuModules.value = await getShallowMenus() |
|
}, |
|
{ |
|
immediate: true |
|
} |
|
) |
|
|
|
listenerRouteChange((route) => { |
|
currentRoute.value = route |
|
setActive(true) |
|
if (unref(getCloseMixSidebarOnChange)) { |
|
closeMenu() |
|
} |
|
}) |
|
|
|
function getWrapCommonStyle(width: string): CSSProperties { |
|
return { |
|
width, |
|
maxWidth: width, |
|
minWidth: width, |
|
flex: `0 0 ${width}` |
|
} |
|
} |
|
|
|
// Process module menu click |
|
async function handleModuleClick(path: string, hover = false) { |
|
const children = await getChildrenMenus(path) |
|
if (unref(activePath) === path) { |
|
if (!hover) { |
|
if (!unref(openMenu)) { |
|
openMenu.value = true |
|
} else { |
|
closeMenu() |
|
} |
|
} else { |
|
if (!unref(openMenu)) { |
|
openMenu.value = true |
|
} |
|
} |
|
if (!unref(openMenu)) { |
|
setActive() |
|
} |
|
} else { |
|
openMenu.value = true |
|
activePath.value = path |
|
} |
|
|
|
if (!children || children.length === 0) { |
|
if (!hover) go(path) |
|
childrenMenus.value = [] |
|
closeMenu() |
|
return |
|
} |
|
childrenMenus.value = children |
|
} |
|
|
|
// Set the currently active menu and submenu |
|
async function setActive(setChildren = false) { |
|
const path = currentRoute.value?.path |
|
if (!path) return |
|
activePath.value = await getCurrentParentPath(path) |
|
// hanldeModuleClick(parentPath); |
|
if (unref(getIsMixSidebar)) { |
|
const activeMenu = unref(menuModules).find((item) => item.path === unref(activePath)) |
|
const p = activeMenu?.path |
|
if (p) { |
|
const children = await getChildrenMenus(p) |
|
if (setChildren) { |
|
childrenMenus.value = children |
|
|
|
if (unref(getMixSideFixed)) { |
|
openMenu.value = children.length > 0 |
|
} |
|
} |
|
if (children.length === 0) { |
|
childrenMenus.value = [] |
|
} |
|
} |
|
} |
|
} |
|
|
|
function handleMenuClick(path: string) { |
|
go(path) |
|
} |
|
|
|
function handleClickOutside() { |
|
setActive(true) |
|
closeMenu() |
|
} |
|
|
|
function getItemEvents(item: Menu) { |
|
if (unref(getMixSideTrigger) === 'hover') { |
|
return { |
|
onMouseenter: () => handleModuleClick(item.path, true), |
|
onClick: async () => { |
|
const children = await getChildrenMenus(item.path) |
|
if (item.path && (!children || children.length === 0)) go(item.path) |
|
} |
|
} |
|
} |
|
return { |
|
onClick: () => handleModuleClick(item.path) |
|
} |
|
} |
|
|
|
function handleFixedMenu() { |
|
setMenuSetting({ |
|
mixSideFixed: !unref(getIsFixed) |
|
}) |
|
} |
|
|
|
// Close menu |
|
function closeMenu() { |
|
if (!unref(getIsFixed)) { |
|
openMenu.value = false |
|
} |
|
} |
|
|
|
return { |
|
t, |
|
prefixCls, |
|
menuModules, |
|
handleModuleClick: handleModuleClick, |
|
activePath, |
|
childrenMenus: childrenMenus, |
|
getShowDragBar, |
|
handleMenuClick, |
|
getMenuStyle, |
|
handleClickOutside, |
|
sideRef, |
|
dragBarRef, |
|
title, |
|
openMenu, |
|
getMenuTheme, |
|
getItemEvents, |
|
getMenuEvents, |
|
getDomStyle, |
|
handleFixedMenu, |
|
getMixSideFixed, |
|
getWrapStyle, |
|
getCollapsed |
|
} |
|
} |
|
}) |
|
</script> |
|
<style lang="less"> |
|
@prefix-cls: ~'@{namespace}-layout-mix-sider'; |
|
@width: 80px; |
|
.@{prefix-cls} { |
|
position: fixed; |
|
top: 0; |
|
left: 0; |
|
z-index: @layout-mix-sider-fixed-z-index; |
|
height: 100%; |
|
overflow: hidden; |
|
background-color: @sider-dark-bg-color; |
|
transition: all 0.2s ease 0s; |
|
|
|
&-dom { |
|
height: 100%; |
|
overflow: hidden; |
|
transition: all 0.2s ease 0s; |
|
} |
|
|
|
&-logo { |
|
display: flex; |
|
height: @header-height; |
|
padding-left: 0 !important; |
|
justify-content: center; |
|
|
|
img { |
|
width: @logo-width; |
|
height: @logo-width; |
|
} |
|
} |
|
|
|
&.light { |
|
.@{prefix-cls}-logo { |
|
border-bottom: 1px solid rgb(238 238 238); |
|
} |
|
|
|
&.open { |
|
> .scrollbar { |
|
border-right: 1px solid rgb(238 238 238); |
|
} |
|
} |
|
|
|
.@{prefix-cls}-module { |
|
&__item { |
|
font-weight: normal; |
|
color: rgb(0 0 0 / 65%); |
|
|
|
&--active { |
|
color: @primary-color; |
|
background-color: unset; |
|
} |
|
} |
|
} |
|
.@{prefix-cls}-menu-list { |
|
&__content { |
|
box-shadow: 0 0 4px 0 rgb(0 0 0 / 10%); |
|
} |
|
|
|
&__title { |
|
.pushpin { |
|
color: rgb(0 0 0 / 35%); |
|
|
|
&:hover { |
|
color: rgb(0 0 0 / 85%); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
@border-color: @sider-dark-lighten-bg-color; |
|
|
|
&.dark { |
|
&.open { |
|
// .@{prefix-cls}-logo { |
|
// border-bottom: 1px solid @border-color; |
|
// } |
|
|
|
> .scrollbar { |
|
border-right: 1px solid @border-color; |
|
} |
|
} |
|
.@{prefix-cls}-menu-list { |
|
background-color: @sider-dark-bg-color; |
|
|
|
&__title { |
|
color: @white; |
|
border-bottom: none; |
|
border-bottom: 1px solid @border-color; |
|
} |
|
} |
|
} |
|
|
|
> .scrollbar { |
|
height: calc(100% - @header-height - 38px); |
|
} |
|
|
|
&.mini &-module { |
|
&__name { |
|
display: none; |
|
} |
|
|
|
&__icon { |
|
margin-bottom: 0; |
|
} |
|
} |
|
|
|
&-module { |
|
position: relative; |
|
padding-top: 1px; |
|
|
|
&__item { |
|
position: relative; |
|
padding: 12px 0; |
|
color: rgb(255 255 255 / 65%); |
|
text-align: center; |
|
cursor: pointer; |
|
transition: all 0.3s ease; |
|
|
|
&:hover { |
|
color: @white; |
|
} |
|
// &:hover, |
|
&--active { |
|
font-weight: 700; |
|
color: @white; |
|
background-color: @sider-dark-darken-bg-color; |
|
|
|
&::before { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 3px; |
|
height: 100%; |
|
background-color: @primary-color; |
|
content: ''; |
|
} |
|
} |
|
} |
|
|
|
&__icon { |
|
margin-bottom: 8px; |
|
font-size: 24px; |
|
transition: all 0.2s; |
|
} |
|
|
|
&__name { |
|
margin-bottom: 0; |
|
font-size: 12px; |
|
transition: all 0.2s; |
|
} |
|
} |
|
|
|
&-trigger { |
|
position: absolute; |
|
bottom: 0; |
|
left: 0; |
|
width: 100%; |
|
font-size: 14px; |
|
color: rgb(255 255 255 / 65%); |
|
text-align: center; |
|
cursor: pointer; |
|
background-color: @trigger-dark-bg-color; |
|
height: 36px; |
|
line-height: 36px; |
|
} |
|
|
|
&.light &-trigger { |
|
color: rgb(0 0 0 / 65%); |
|
background-color: #fff; |
|
border-top: 1px solid #eee; |
|
} |
|
|
|
&-menu-list { |
|
position: fixed; |
|
top: 0; |
|
width: 200px; |
|
height: calc(100%); |
|
background-color: #fff; |
|
transition: all 0.2s; |
|
|
|
&__title { |
|
display: flex; |
|
height: @header-height; |
|
// margin-left: -6px; |
|
font-size: 18px; |
|
color: @primary-color; |
|
border-bottom: 1px solid rgb(238 238 238); |
|
opacity: 0; |
|
transition: unset; |
|
align-items: center; |
|
justify-content: space-between; |
|
|
|
&.show { |
|
min-width: 130px; |
|
opacity: 1; |
|
transition: all 0.5s ease; |
|
} |
|
|
|
.pushpin { |
|
margin-right: 6px; |
|
color: rgb(255 255 255 / 65%); |
|
cursor: pointer; |
|
|
|
&:hover { |
|
color: #fff; |
|
} |
|
} |
|
} |
|
|
|
&__content { |
|
height: calc(100% - @header-height) !important; |
|
|
|
.scrollbar__wrap { |
|
height: 100%; |
|
overflow-x: hidden; |
|
} |
|
|
|
.scrollbar__bar.is-horizontal { |
|
display: none; |
|
} |
|
|
|
.ant-menu { |
|
height: 100%; |
|
} |
|
|
|
.ant-menu-inline, |
|
.ant-menu-vertical, |
|
.ant-menu-vertical-left { |
|
border-right: 1px solid transparent; |
|
} |
|
} |
|
} |
|
|
|
&-drag-bar { |
|
position: absolute; |
|
top: 50px; |
|
right: -1px; |
|
width: 1px; |
|
height: calc(100% - 50px); |
|
cursor: ew-resize; |
|
background-color: #f8f8f9; |
|
border-top: none; |
|
border-bottom: none; |
|
box-shadow: 0 0 4px 0 rgb(28 36 56 / 15%); |
|
} |
|
} |
|
</style>
|
|
|