<template>
|
<div :class="`${prefixCls}-dom`" :style="getDomStyle" />
|
|
<div
|
v-click-outside="handleClickOutside"
|
:style="getWrapStyle"
|
:class="[
|
prefixCls,
|
getMenuTheme,
|
{
|
open: openMenu,
|
mini: getCollapsed,
|
},
|
]"
|
v-bind="getMenuEvents"
|
>
|
<AppLogo :showTitle="false" :class="`${prefixCls}-logo`" />
|
<ScrollContainer>
|
<ul :class="`${prefixCls}-module`">
|
<li
|
:class="[
|
`${prefixCls}-module__item `,
|
{
|
[`${prefixCls}-module__item--active`]: item.path === activePath,
|
},
|
]"
|
v-for="item in menuModules"
|
:key="item.path"
|
v-bind="getItemEvents(item)"
|
>
|
<MenuTag :item="item" :showTitle="false" :isHorizontal="false" />
|
<Icon
|
:class="`${prefixCls}-module__icon`"
|
:size="getCollapsed ? 16 : 20"
|
: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`">
|
<BasicMenu
|
:isHorizontal="false"
|
mode="inline"
|
:items="chilrenMenus"
|
:theme="getMenuTheme"
|
mixSider
|
@menuClick="handleMenuClick"
|
/>
|
</ScrollContainer>
|
<div
|
v-show="getShowDragBar && openMenu"
|
:class="`${prefixCls}-drag-bar`"
|
ref="dragBarRef"
|
></div>
|
</div>
|
</div>
|
</template>
|
<script lang="ts">
|
import { defineComponent, onMounted, ref, computed, CSSProperties, unref } from 'vue';
|
import type { Menu } from '/@/router/types';
|
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, SIDE_BAR_MINI_WIDTH } from '/@/enums/appEnum';
|
|
import clickOutside from '/@/directives/clickOutside';
|
|
export default defineComponent({
|
name: 'LayoutMixSider',
|
components: {
|
ScrollContainer,
|
AppLogo,
|
BasicMenu,
|
MenuTag,
|
Icon,
|
},
|
directives: {
|
clickOutside,
|
},
|
setup() {
|
let menuModules = ref<Menu[]>([]);
|
const activePath = ref('');
|
const chilrenMenus = 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();
|
|
useDragLine(sideRef, dragBarRef, true);
|
|
const getMenuStyle = computed(
|
(): CSSProperties => {
|
return {
|
width: unref(openMenu) ? `${unref(getMenuWidth)}px` : 0,
|
left: `${unref(getMixSideWidth)}px`,
|
};
|
}
|
);
|
|
const getIsFixed = computed(() => {
|
mixSideHasChildren.value = unref(chilrenMenus).length > 0;
|
const isFixed = unref(getMixSideFixed) && unref(mixSideHasChildren);
|
if (isFixed) {
|
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: () => {
|
closeMenu();
|
},
|
}
|
: {};
|
});
|
|
const getShowDragBar = computed(() => unref(getCanDrag));
|
|
onMounted(async () => {
|
menuModules.value = await getShallowMenus();
|
});
|
|
listenerLastChangeTab((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 hanldeModuleClick(path: string, hover = false) {
|
const children = await getChildrenMenus(path);
|
|
if (unref(activePath) === path) {
|
if (!hover) {
|
if (!unref(openMenu)) {
|
openMenu.value = true;
|
} else {
|
closeMenu();
|
}
|
}
|
if (!unref(openMenu)) {
|
setActive();
|
}
|
} else {
|
openMenu.value = true;
|
activePath.value = path;
|
}
|
|
if (!children || children.length === 0) {
|
go(path);
|
chilrenMenus.value = [];
|
closeMenu();
|
return;
|
}
|
chilrenMenus.value = children;
|
}
|
|
// Set the currently active menu and submenu
|
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(getIsMixSidebar)) {
|
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;
|
|
if (unref(getMixSideFixed)) {
|
openMenu.value = children.length > 0;
|
}
|
}
|
if (children.length === 0) {
|
chilrenMenus.value = [];
|
}
|
}
|
}
|
}
|
|
function handleMenuClick(path: string) {
|
go(path);
|
}
|
|
function handleClickOutside() {
|
setActive(true);
|
closeMenu();
|
}
|
|
function getItemEvents(item: Menu) {
|
if (unref(getMixSideTrigger) === 'hover') {
|
return {
|
onMouseenter: () => hanldeModuleClick(item.path, true),
|
};
|
}
|
return {
|
onClick: () => hanldeModuleClick(item.path),
|
};
|
}
|
|
function handleFixedMenu() {
|
setMenuSetting({
|
mixSideFixed: !unref(getIsFixed),
|
});
|
}
|
|
// Close menu
|
function closeMenu() {
|
if (!unref(getIsFixed)) {
|
openMenu.value = false;
|
}
|
}
|
|
return {
|
t,
|
prefixCls,
|
menuModules,
|
hanldeModuleClick,
|
activePath,
|
chilrenMenus,
|
getShowDragBar,
|
handleMenuClick,
|
getMenuStyle,
|
handleClickOutside,
|
sideRef,
|
dragBarRef,
|
title,
|
openMenu,
|
getMenuTheme,
|
getItemEvents,
|
getMenuEvents,
|
getDomStyle,
|
handleFixedMenu,
|
getMixSideFixed,
|
getWrapStyle,
|
getCollapsed,
|
};
|
},
|
});
|
</script>
|
<style lang="less">
|
@import (reference) '../../../design/index.less';
|
@prefix-cls: ~'@{namespace}-layout-mix-sider';
|
@tag-prefix-cls: ~'@{namespace}-basic-menu-item-tag';
|
@width: 80px;
|
.@{prefix-cls} {
|
position: fixed;
|
top: 0;
|
left: 0;
|
z-index: @layout-mix-sider-fixed-z-index;
|
height: 100%;
|
overflow: hidden;
|
background: @sider-dark-bg-color;
|
transition: all 0.2s ease 0s;
|
.@{tag-prefix-cls} {
|
position: absolute;
|
top: 6px;
|
right: 2px;
|
}
|
|
&-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: rgba(0, 0, 0, 0.65);
|
|
&--active {
|
color: @primary-color;
|
background: unset;
|
}
|
}
|
}
|
.@{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;
|
|
&.dark {
|
&.open {
|
.@{prefix-cls}-logo {
|
border-bottom: 1px solid @border-color;
|
}
|
|
> .scrollbar {
|
border-right: 1px solid @border-color;
|
}
|
}
|
.@{prefix-cls}-menu-list {
|
background: @sider-dark-bg-color;
|
|
&__title {
|
color: @white;
|
border-bottom: none;
|
border-bottom: 1px solid @border-color;
|
}
|
}
|
}
|
|
> .scrollbar {
|
height: calc(100% - @header-height) !important;
|
}
|
|
&.mini &-module {
|
&__name {
|
display: none;
|
}
|
|
&__icon {
|
margin-bottom: 0;
|
}
|
}
|
|
&-module {
|
position: relative;
|
padding-top: 1px;
|
|
&__item {
|
position: relative;
|
padding: 12px 0;
|
color: rgba(255, 255, 255, 0.65);
|
text-align: center;
|
cursor: pointer;
|
transition: all 0.3s ease;
|
|
&:hover {
|
color: @white;
|
}
|
// &:hover,
|
&--active {
|
font-weight: 700;
|
color: @white;
|
background: @sider-dark-darken-bg-color;
|
|
&::before {
|
position: absolute;
|
top: 0;
|
left: 0;
|
width: 3px;
|
height: 100%;
|
background: @primary-color;
|
content: '';
|
}
|
}
|
}
|
|
&__icon {
|
margin-bottom: 8px;
|
font-size: 24px;
|
}
|
|
&__name {
|
margin-bottom: 0;
|
font-size: 12px;
|
}
|
}
|
|
&-menu-list {
|
position: fixed;
|
top: 0;
|
width: 0;
|
width: 200px;
|
height: calc(100%);
|
background: #fff;
|
transition: width 0.2s;
|
.@{tag-prefix-cls} {
|
position: absolute;
|
top: 10px;
|
right: 30px;
|
|
&--dot {
|
top: 50%;
|
margin-top: -3px;
|
}
|
}
|
|
&__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: rgba(255, 255, 255, 0.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: 0;
|
right: -3px;
|
width: 3px;
|
height: 100%;
|
cursor: ew-resize;
|
background: #f8f8f9;
|
border-top: none;
|
border-bottom: none;
|
box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.15);
|
}
|
}
|
</style>
|