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.

537 lines
12 KiB

2 years ago
<!-- eslint-disable vue/no-side-effects-in-computed-properties -->
<script lang="ts" setup>
2 years ago
import type { CSSProperties } from 'vue'
import { computed, onMounted, ref, unref, watch } from 'vue'
2 years ago
import type { RouteLocationNormalized } from 'vue-router'
import { onClickOutside } from '@vueuse/core'
import LayoutTrigger from '../trigger/index.vue'
import { useDragLine } from './useLayoutSider'
import type { Menu } from '@/router/types'
2 years ago
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 { 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 { getChildrenMenus, getCurrentParentPath, getShallowMenus } from '@/router/menus'
import { listenerRouteChange } from '@/logics/mitt/routeChange'
const wrap = ref(null)
const 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)
2 years ago
const getMixSideWidth = computed(() => {
return unref(getCollapsed) ? SIDE_BAR_MINI_WIDTH : SIDE_BAR_SHOW_TIT_MINI_WIDTH
})
const getMenuStyle = computed((): CSSProperties => {
return {
width: unref(openMenu) ? `${unref(getMenuWidth)}px` : 0,
left: `${unref(getMixSideWidth)}px`,
}
})
2 years ago
const getIsFixed = computed(() => {
mixSideHasChildren.value = unref(childrenMenus).length > 0
const isFixed = unref(getMixSideFixed) && unref(mixSideHasChildren)
if (isFixed)
openMenu.value = true
2 years ago
return isFixed
})
2 years ago
const getDomStyle = computed((): CSSProperties => {
const fixedWidth = unref(getIsFixed) ? unref(getRealWidth) : 0
const width = `${unref(getMixSideWidth) + fixedWidth}px`
return getWrapCommonStyle(width)
})
2 years ago
const getWrapStyle = computed((): CSSProperties => {
const width = `${unref(getMixSideWidth)}px`
return getWrapCommonStyle(width)
})
2 years ago
const getMenuEvents = computed(() => {
return !unref(getMixSideFixed)
? {
onMouseleave: () => {
setActive(true)
closeMenu()
},
}
: {}
})
2 years ago
const getShowDragBar = computed(() => unref(getCanDrag))
2 years ago
onMounted(async () => {
menuModules.value = await getShallowMenus()
})
2 years ago
// Menu changes
watch(
[() => permissionStore.getLastBuildMenuTime, () => permissionStore.getBackMenuList],
async () => {
menuModules.value = await getShallowMenus()
},
{
immediate: true,
},
)
2 years ago
listenerRouteChange((route) => {
currentRoute.value = route
setActive(true)
if (unref(getCloseMixSidebarOnChange))
closeMenu()
})
2 years ago
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))
2 years ago
openMenu.value = true
else
2 years ago
closeMenu()
}
else {
if (!unref(openMenu))
openMenu.value = true
2 years ago
}
if (!unref(openMenu))
setActive()
}
else {
openMenu.value = true
activePath.value = path
}
if (!children || children.length === 0) {
if (!hover)
2 years ago
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
2 years ago
}
if (children.length === 0)
childrenMenus.value = []
2 years ago
}
}
}
2 years ago
function handleMenuClick(path: string) {
go(path)
}
2 years ago
function handleClickOutside() {
setActive(true)
closeMenu()
}
2 years ago
function getItemEvents(item: Menu) {
if (unref(getMixSideTrigger) === 'hover') {
2 years ago
return {
onMouseenter: () => handleModuleClick(item.path, true),
onClick: async () => {
const children = await getChildrenMenus(item.path)
if (item.path && (!children || children.length === 0))
go(item.path)
},
2 years ago
}
}
return {
onClick: () => handleModuleClick(item.path),
}
}
function handleFixedMenu() {
setMenuSetting({
mixSideFixed: !unref(getIsFixed),
})
}
// Close menu
function closeMenu() {
if (!unref(getIsFixed))
openMenu.value = false
}
onClickOutside(wrap, () => {
handleClickOutside()
2 years ago
})
</script>
<template>
<div :class="`${prefixCls}-dom`" :style="getDomStyle" />
<div
ref="wrap"
:style="getWrapStyle"
:class="[
prefixCls,
getMenuTheme,
{
open: openMenu,
mini: getCollapsed,
},
]"
v-bind="getMenuEvents"
>
<AppLogo :show-title="false" :class="`${prefixCls}-logo`" />
<LayoutTrigger :class="`${prefixCls}-trigger`" />
<ScrollContainer>
<ul :class="`${prefixCls}-module`">
<li
v-for="item in menuModules"
v-bind="getItemEvents(item)"
:key="item.path"
:class="[
`${prefixCls}-module__item `,
{
[`${prefixCls}-module__item--active`]: item.path === activePath,
},
]"
>
<SimpleMenuTag :item="item" collapse-parent 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 ref="sideRef" :class="`${prefixCls}-menu-list`" :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" mix-sider @menu-click="handleMenuClick" />
</ScrollContainer>
<div v-show="getShowDragBar && openMenu" ref="dragBarRef" :class="`${prefixCls}-drag-bar`" />
</div>
</div>
</template>
2 years ago
<style lang="less">
@prefix-cls: ~'@{namespace}-layout-mix-sider';
@width: 80px;
2 years ago
2 years ago
.@{prefix-cls} {
position: fixed;
top: 0;
left: 0;
2 years ago
z-index: @layout-mix-sider-fixed-z-index;
2 years ago
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;
justify-content: center;
2 years ago
height: @header-height;
padding-left: 0 !important;
img {
width: @logo-width;
height: @logo-width;
}
}
&.light {
.@{prefix-cls}-logo {
border-bottom: 1px solid rgb(238 238 238);
}
&.open {
2 years ago
>.scrollbar {
2 years ago
border-right: 1px solid rgb(238 238 238);
}
}
.@{prefix-cls}-module {
&__item {
font-weight: normal;
color: rgb(0 0 0 / 65%);
&--active {
background-color: unset;
}
}
}
2 years ago
2 years ago
.@{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%);
}
}
}
}
}
&.dark {
&.open {
2 years ago
.@{prefix-cls}-logo {
border-bottom: 1px solid var(--sider-dark-lighten-bg-color);
}
2 years ago
2 years ago
>.scrollbar {
2 years ago
border-right: 1px solid var(--sider-dark-lighten-bg-color);
2 years ago
}
}
2 years ago
2 years ago
.@{prefix-cls}-menu-list {
2 years ago
background-color: var(--sider-dark-bg-color);
2 years ago
&__title {
color: @white;
border-bottom: none;
2 years ago
border-bottom: 1px solid var(--sider-dark-lighten-bg-color);
2 years ago
}
}
}
2 years ago
>.scrollbar {
2 years ago
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;
}
2 years ago
2 years ago
// &: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%;
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%;
height: 36px;
2 years ago
font-size: 14px;
line-height: 36px;
2 years ago
color: rgb(255 255 255 / 65%);
text-align: center;
cursor: pointer;
background-color: @trigger-dark-bg-color;
}
&.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;
align-items: center;
justify-content: space-between;
2 years ago
height: @header-height;
// margin-left: -6px;
font-size: 18px;
border-bottom: 1px solid rgb(238 238 238);
opacity: 0;
2 years ago
transition: unset;
&.show {
min-width: 130px;
opacity: 1;
2 years ago
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>