Skip to content

Commit 8acd3a0

Browse files
committed
feat(layouts): ✨ sidebar
- Sidebar text overflows to display abbreviations and displays tootips - Adjust the style to optimize the expansion and contraction animation
1 parent 8b98a5b commit 8acd3a0

File tree

11 files changed

+214
-285
lines changed

11 files changed

+214
-285
lines changed

.env

Whitespace-only changes.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"dayjs": "^1.11.9",
4949
"docx-preview": "^0.1.18",
5050
"echarts": "^5.4.3",
51-
"element-plus": "^2.3.12",
51+
"element-plus": "^2.4.1",
5252
"es6-promise": "^4.2.8",
5353
"i18next": "^23.5.0",
5454
"intro.js": "^7.2.0",

pnpm-lock.yaml

+5-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/SvgIcon/index.vue

+8-11
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
</script>
2727

2828
<template>
29-
<el-icon v-if="isELIcon" :class="className">
29+
<el-icon v-if="isELIcon" :class="`svg-icon ${className}`">
3030
<component :is="name" />
3131
</el-icon>
3232
<i v-else class="svg-icon" :class="className">
@@ -37,24 +37,21 @@
3737
</template>
3838

3939
<style lang="scss" scoped>
40-
.el-icon {
40+
.svg-icon {
41+
display: inline-flex;
42+
align-items: center;
43+
justify-content: center;
44+
flex-shrink: 0;
4145
width: 1em;
4246
height: 1em;
43-
margin: 0;
44-
// 取父级的宽高
4547
font-size: 1em;
4648
color: currentcolor;
47-
}
48-
49-
.svg-icon {
50-
display: block;
51-
height: 1em;
52-
line-height: 1em;
53-
font-size: 1em;
49+
margin: 0;
5450
5551
.svg {
5652
width: 1em;
5753
height: 1em;
54+
color: currentcolor;
5855
5956
use {
6057
fill: currentcolor;
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script setup lang="ts">
2+
import { ref } from 'vue';
23
import SvgIcon from '@/components/SvgIcon/index.vue';
34
import type { localeTitle } from '@/router/type';
45
import { translateI18n } from '@/hooks/web/useI18n';
@@ -8,24 +9,64 @@
89
icon?: string;
910
title?: string | localeTitle;
1011
className?: string;
12+
collapse?: boolean;
13+
mode?: 'vertical' | 'horizontal';
1114
}>(),
1215
{
13-
icon: 'hello',
16+
icon: '',
1417
title: '',
1518
className: '',
19+
collapse: false,
20+
mode: 'vertical',
1621
},
1722
);
23+
24+
const showTextTooltip = ref<Boolean | null>(null);
25+
const sidebarItemTextRef = ref<HTMLDivElement>();
26+
27+
const onTextMove = () => {
28+
if (showTextTooltip.value !== null || props.mode === 'horizontal') return;
29+
const sidebarItemTextDom = sidebarItemTextRef.value?.children?.[0];
30+
showTextTooltip.value = sidebarItemTextDom
31+
? sidebarItemTextDom.scrollWidth > sidebarItemTextDom.clientWidth
32+
: false;
33+
};
1834
</script>
1935

2036
<template>
21-
<SvgIcon :class-name="props.className" :name="props.icon" />
22-
<span v-if="props.title">{{ translateI18n(props.title) }}</span>
37+
<SvgIcon v-if="props.icon" :class-name="props.className" :name="props.icon" />
38+
<div
39+
ref="sidebarItemTextRef"
40+
class="menu-item-text"
41+
:class="[
42+
!props.icon && 'menu-item-text-only',
43+
props.mode === 'vertical' && 'sidebar-menu-item-text',
44+
]"
45+
@mouseover="onTextMove"
46+
>
47+
<el-tooltip
48+
:content="translateI18n(props.title)"
49+
:disabled="!showTextTooltip || props.collapse"
50+
placement="top"
51+
>
52+
<el-text truncated>
53+
{{ translateI18n(props.title) }}
54+
</el-text>
55+
</el-tooltip>
56+
</div>
2357
</template>
2458

2559
<style lang="scss" scoped>
26-
.sub-el-icon {
27-
/* color: currentColor; */
28-
width: 1em;
29-
height: 1em;
60+
.sidebar-menu-item-text {
61+
width: 0;
62+
flex: 1;
63+
}
64+
65+
.menu-item-text {
66+
color: currentcolor;
67+
68+
.el-text {
69+
color: currentcolor;
70+
}
3071
}
3172
</style>

src/layouts/page-layouts/components/Sidebar/MinSidebar.vue

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
class-name="menu-item-svg"
5252
:icon="menusRoute.meta && menusRoute.meta.icon"
5353
:title="menusRoute.meta?.title"
54+
mode="horizontal"
5455
/>
5556
</el-menu-item>
5657
</AppLink>

src/layouts/page-layouts/components/Sidebar/SidebarItem.vue

+63-32
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import Item from './Item.vue';
77
import AppLink from './Link.vue';
88
import type { AppRouteRecordRaw } from '@/router/type';
9+
import { translateI18n } from '@/hooks/web/useI18n';
910
1011
const props = defineProps({
1112
// route object
@@ -21,21 +22,32 @@
2122
type: String,
2223
default: '',
2324
},
25+
level: {
26+
type: Number,
27+
default: 0,
28+
},
29+
collapse: {
30+
type: Boolean,
31+
default: false,
32+
},
33+
mode: {
34+
type: String as PropType<'vertical' | 'horizontal'>,
35+
default: 'vertical',
36+
},
2437
});
25-
const onlyOneChild = ref<any>({});
26-
const hasOneShowingChild = (
27-
children: Array<AppRouteRecordRaw> = [],
28-
parent: AppRouteRecordRaw,
29-
) => {
30-
const showingChildren = children.filter((item: AppRouteRecordRaw) => {
31-
if (item.meta?.hideSidebar) {
32-
return false;
33-
} else {
34-
// Temp set(will be used if only has one showing child)
35-
onlyOneChild.value = item;
36-
return true;
37-
}
38-
});
38+
39+
const onlyOneChild = ref<Partial<AppRouteRecordRaw & { noShowingChildren: boolean }>>({});
40+
const hasOneShowingChild = (parent: AppRouteRecordRaw) => {
41+
const showingChildren =
42+
parent.children?.filter((item: AppRouteRecordRaw) => {
43+
if (item.meta?.hideSidebar) {
44+
return false;
45+
} else {
46+
// Temp set(will be used if only has one showing child)
47+
onlyOneChild.value = item;
48+
return true;
49+
}
50+
}) ?? [];
3951
4052
// When there is only one child router, the child router is displayed by default
4153
if (showingChildren.length === 1) {
@@ -65,44 +77,63 @@
6577
<div v-if="!item.meta?.hideSidebar">
6678
<template
6779
v-if="
68-
hasOneShowingChild(item.children, item) &&
80+
hasOneShowingChild(item) &&
6981
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
7082
!item.meta?.alwaysShow
7183
"
7284
>
73-
<AppLink v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
74-
<el-menu-item
75-
:index="resolvePath(onlyOneChild.path)"
76-
:class="{ 'submenu-title-noDropdown': !isNest }"
77-
>
78-
<Item
79-
class-name="menu-item-svg"
80-
:icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"
81-
:title="onlyOneChild.meta.title || (item.meta && item.meta.title)"
82-
/>
83-
</el-menu-item>
84-
</AppLink>
85+
<el-tooltip
86+
class="box-item"
87+
:disabled="props.level > 0 || !props.collapse"
88+
:content="translateI18n(onlyOneChild.meta?.title)"
89+
placement="right"
90+
>
91+
<AppLink v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild?.path ?? '')">
92+
<el-menu-item
93+
:index="resolvePath(onlyOneChild?.path ?? '')"
94+
:class="{
95+
'submenu-title-noDropdown': !isNest,
96+
'one-level-menu-item': props.level === 0,
97+
}"
98+
>
99+
<Item
100+
class-name="menu-item-svg"
101+
:icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"
102+
:title="onlyOneChild.meta.title || (item.meta && item.meta.title)"
103+
:collapse="props.level === 0 && props.collapse"
104+
:mode="mode"
105+
/>
106+
</el-menu-item>
107+
</AppLink>
108+
</el-tooltip>
85109
</template>
86110

87-
<el-sub-menu v-else :index="resolvePath(item.path)" teleported>
111+
<el-sub-menu
112+
v-else
113+
:index="resolvePath(item.path)"
114+
:class="{ 'one-level-sub-menu': props.level === 0 }"
115+
teleported
116+
>
88117
<template #title>
89118
<Item
90119
v-if="item.meta"
91120
class-name="sub-menu-svg"
92121
:icon="item.meta && item.meta.icon"
93122
:title="item.meta.title"
123+
:collapse="props.level === 0 && props.collapse"
124+
:mode="mode"
94125
/>
95126
</template>
96127
<sidebar-item
97-
v-for="child in item.children"
98-
:key="child.path"
128+
v-for="(child, index) in item.children"
129+
:key="child.path + index"
99130
:is-nest="true"
100131
:item="child"
101132
:base-path="resolvePath(child.path)"
102133
class="nest-menu"
134+
:level="props.level + 1"
135+
:collapse="props.collapse"
103136
/>
104137
</el-sub-menu>
105138
</div>
106139
</template>
107-
108-
<style lang="scss" scoped></style>

src/layouts/page-layouts/components/Sidebar/index.vue

+8-1
Original file line numberDiff line numberDiff line change
@@ -56,22 +56,29 @@
5656
}
5757
return path;
5858
});
59+
60+
const collapse = computed(() =>
61+
appConfig.value.sidebarMode === 'horizontal' ? false : appConfig.value.collapseMenu,
62+
);
5963
</script>
6064

6165
<template>
6266
<el-scrollbar wrap-class="scrollbar-wrapper">
6367
<el-menu
6468
:default-active="activeMenyu"
6569
:unique-opened="true"
66-
:collapse="appConfig.sidebarMode === 'horizontal' ? false : appConfig.collapseMenu"
70+
:collapse="collapse"
6771
:mode="mode"
72+
:collapse-transition="true"
6873
>
6974
<SidebarItem
7075
v-for="menuRoute in menuData"
7176
:key="menuRoute.path"
7277
:item="menuRoute"
7378
:is-nest="false"
7479
:base-path="menuRoute.path"
80+
:collapse="collapse"
81+
:mode="mode"
7582
/>
7683
</el-menu>
7784
</el-scrollbar>

0 commit comments

Comments
 (0)