This commit is contained in:
2026-01-26 13:05:36 +08:00
parent 2a9cb82fef
commit 8b0e5a5642
8 changed files with 382 additions and 43 deletions

View File

@@ -8,7 +8,7 @@ export default {
} }
for (const [key, component] of Object.entries(ElementPlusIconsVue)) { for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(`${key}`, component) app.component(`ElIcon${key}`, component)
} }
}, },
} }

View File

@@ -16,7 +16,7 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useI18n } from '@/hooks/useI18n' // import { useI18n } from '@/hooks/useI18n'
defineOptions({ defineOptions({
name: 'LayoutBreadcrumb', name: 'LayoutBreadcrumb',
@@ -24,7 +24,7 @@ defineOptions({
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const { t } = useI18n() // const { t } = useI18n()
// 面包屑列表 // 面包屑列表
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
@@ -37,12 +37,12 @@ const breadcrumbs = computed(() => {
} }
// 如果第一个不是首页,添加首页 // 如果第一个不是首页,添加首页
if (first.path !== '/') { // if (first.path !== '/') {
matched.unshift({ // matched.unshift({
path: '/', // path: '/',
meta: { title: t('menu.home') || '首页' }, // meta: { title: '首页' },
}) // })
} // }
return matched return matched
}) })
@@ -60,19 +60,34 @@ const handleLink = (item) => {
<style lang="scss" scoped> <style lang="scss" scoped>
.el-breadcrumb { .el-breadcrumb {
display: inline-block; display: inline-flex;
line-height: 60px; align-items: center;
font-size: 14px; font-size: 14px;
line-height: 1;
:deep(.el-breadcrumb__item) {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
.el-breadcrumb__inner {
font-weight: 400;
transition: all 0.3s;
}
&:last-child .el-breadcrumb__inner {
font-weight: 500;
}
}
.no-redirect { .no-redirect {
color: var(--el-text-color-regular); color: var(--el-text-color-primary);
cursor: text; cursor: text;
font-weight: 500;
} }
.redirect { .redirect {
color: var(--el-text-color-primary); color: var(--el-text-color-regular);
cursor: pointer; cursor: pointer;
transition: color 0.3s; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover { &:hover {
color: var(--el-color-primary); color: var(--el-color-primary);
@@ -82,7 +97,7 @@ const handleLink = (item) => {
.breadcrumb-enter-active, .breadcrumb-enter-active,
.breadcrumb-leave-active { .breadcrumb-leave-active {
transition: all 0.3s; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
} }
.breadcrumb-enter-from, .breadcrumb-enter-from,

View File

@@ -46,4 +46,54 @@ const hasChildren = (menu) => {
} }
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped>
// 子菜单项样式
:deep(.el-sub-menu) {
.el-sub-menu__title {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
background-color: var(--el-fill-color-light) !important;
}
}
// 子菜单内容区
.el-menu {
background-color: var(--el-bg-color);
}
// 菜单项样式
.el-menu-item {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
background-color: var(--el-fill-color-light) !important;
}
&.is-active {
background-color: var(--el-color-primary-light-9) !important;
color: var(--el-color-primary) !important;
font-weight: 500;
}
}
}
// 图标样式优化
:deep(.el-icon) {
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
// 箭头图标动画
:deep(.el-sub-menu__icon-arrow) {
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
.el-sub-menu.is-opened > .el-sub-menu__title & {
transform: rotateZ(180deg);
}
}
// 菜单文本样式
:deep(span) {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
</style>

View File

@@ -94,5 +94,59 @@ const handleSelect = (index) => {
<style lang="scss" scoped> <style lang="scss" scoped>
.el-menu { .el-menu {
border-right: none; border-right: none;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
// 菜单项样式
:deep(.el-menu-item) {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
background-color: var(--el-fill-color-light) !important;
}
&.is-active {
background-color: var(--el-color-primary-light-9) !important;
color: var(--el-color-primary) !important;
font-weight: 500;
}
}
// 子菜单样式
:deep(.el-sub-menu__title) {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
background-color: var(--el-fill-color-light) !important;
}
.el-sub-menu__icon-arrow {
transition: transform 0.3s;
}
}
:deep(.el-sub-menu.is-opened > .el-sub-menu__title .el-sub-menu__icon-arrow) {
transform: rotateZ(180deg);
}
// 图标样式
:deep(.el-icon) {
width: 18px;
height: 18px;
font-size: 18px;
vertical-align: middle;
margin-right: 8px;
}
// 折叠状态
&.el-menu--collapse {
:deep(.el-menu-item),
:deep(.el-sub-menu__title) {
padding: 0 20px;
}
:deep(.el-icon) {
margin-right: 0;
}
}
} }
</style> </style>

View File

@@ -274,15 +274,25 @@ onMounted(() => {
border-radius: 6px 0 0 6px; border-radius: 6px 0 0 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
z-index: 9999; z-index: 9999;
transition: all 0.3s; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
user-select: none;
.el-icon { .el-icon {
font-size: 20px; font-size: 20px;
transition: transform 0.3s;
} }
&:hover { &:hover {
width: 56px; width: 56px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
.el-icon {
transform: rotate(90deg);
}
}
&:active {
transform: translateY(-50%) scale(0.95);
} }
} }

View File

@@ -59,9 +59,17 @@ const left = ref(0)
const selectedTag = ref(null) const selectedTag = ref(null)
const affixTags = ref([]) const affixTags = ref([])
// 可见的标签 // 可见的标签(去重)
const visibleTags = computed(() => { const visibleTags = computed(() => {
return layoutStore.viewTags || [] const tags = layoutStore.viewTags || []
// 使用 Map 根据 fullPath 去重,保留最后出现的
const tagMap = new Map()
tags.forEach((tag) => {
if (tag.fullPath) {
tagMap.set(tag.fullPath, tag)
}
})
return Array.from(tagMap.values())
}) })
// 判断是否激活 // 判断是否激活
@@ -223,6 +231,8 @@ onMounted(() => {
background: var(--el-fill-color-blank); background: var(--el-fill-color-blank);
border-bottom: 1px solid var(--el-border-color-light); border-bottom: 1px solid var(--el-border-color-light);
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
position: relative;
z-index: 9;
.tags-view-wrapper { .tags-view-wrapper {
white-space: nowrap; white-space: nowrap;
@@ -233,6 +243,20 @@ onMounted(() => {
:deep(.el-scrollbar__wrap) { :deep(.el-scrollbar__wrap) {
height: 34px; height: 34px;
overflow-x: auto; overflow-x: auto;
scrollbar-width: thin;
&::-webkit-scrollbar {
height: 4px;
}
&::-webkit-scrollbar-thumb {
background-color: var(--el-border-color-darker);
border-radius: 2px;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
} }
:deep(.el-scrollbar__bar.is-horizontal) { :deep(.el-scrollbar__bar.is-horizontal) {
@@ -241,20 +265,22 @@ onMounted(() => {
} }
.tags-view-item { .tags-view-item {
display: inline-block; display: inline-flex;
position: relative; position: relative;
align-items: center;
cursor: pointer; cursor: pointer;
height: 26px; height: 26px;
line-height: 26px; line-height: 26px;
border: 1px solid var(--el-border-color-light); border: 1px solid var(--el-border-color-light);
color: var(--el-text-color-regular); color: var(--el-text-color-regular);
background: var(--el-fill-color-blank); background: var(--el-fill-color-blank);
padding: 0 8px; padding: 0 12px;
font-size: 12px; font-size: 12px;
margin-left: 5px; margin-left: 5px;
margin-top: 4px; margin-top: 4px;
border-radius: 2px; border-radius: 3px;
transition: all 0.3s; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
user-select: none;
&:first-of-type { &:first-of-type {
margin-left: 10px; margin-left: 10px;
@@ -267,40 +293,54 @@ onMounted(() => {
&:hover { &:hover {
background-color: var(--el-fill-color-light); background-color: var(--el-fill-color-light);
color: var(--el-color-primary); color: var(--el-color-primary);
border-color: var(--el-color-primary-light-7);
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
} }
&.active { &.active {
background-color: var(--el-color-primary); background-color: var(--el-color-primary);
border-color: var(--el-color-primary); border-color: var(--el-color-primary);
color: #fff; color: #fff;
font-weight: 500;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(var(--el-color-primary-rgb), 0.3);
&::before { &::before {
content: ''; content: '';
background: #fff; background: #fff;
display: inline-block; display: inline-block;
width: 8px; width: 6px;
height: 8px; height: 6px;
border-radius: 50%; border-radius: 50%;
position: relative; position: relative;
margin-right: 2px; margin-right: 6px;
flex-shrink: 0;
} }
} }
.el-icon-close { .el-icon-close {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%; border-radius: 50%;
text-align: center; text-align: center;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transform-origin: 100% 50%; transform-origin: 100% 50%;
font-size: 12px; font-size: 12px;
margin-left: 5px; margin-left: 6px;
width: 14px; width: 16px;
height: 14px; height: 16px;
line-height: 14px; line-height: 16px;
vertical-align: middle; vertical-align: middle;
flex-shrink: 0;
opacity: 0.7;
&:hover { &:hover {
background-color: #fff; background-color: #fff;
color: var(--el-color-danger); color: var(--el-color-danger);
opacity: 1;
transform: rotate(90deg);
} }
} }
} }

View File

@@ -219,16 +219,18 @@ onUnmounted(() => {
align-items: center; align-items: center;
gap: 8px; gap: 8px;
cursor: pointer; cursor: pointer;
padding: 4px 8px; padding: 6px 12px;
border-radius: 4px; border-radius: 6px;
transition: background-color 0.3s; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover { &:hover {
background-color: var(--el-fill-color-light); background-color: var(--el-fill-color-light);
transform: translateY(-1px);
} }
.username { .username {
font-size: 14px; font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary); color: var(--el-text-color-primary);
max-width: 100px; max-width: 100px;
overflow: hidden; overflow: hidden;
@@ -239,10 +241,11 @@ onUnmounted(() => {
.el-icon-caret-bottom { .el-icon-caret-bottom {
font-size: 12px; font-size: 12px;
color: var(--el-text-color-regular); color: var(--el-text-color-regular);
transition: transform 0.3s; transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
.avatar-wrapper:hover & { .avatar-wrapper:hover & {
transform: rotate(180deg); transform: rotate(180deg);
color: var(--el-color-primary);
} }
} }
} }
@@ -254,25 +257,55 @@ onUnmounted(() => {
width: 36px; width: 36px;
height: 36px; height: 36px;
cursor: pointer; cursor: pointer;
border-radius: 4px; border-radius: 6px;
transition: background-color 0.3s; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
user-select: none;
.el-icon { .el-icon {
font-size: 18px; font-size: 18px;
color: var(--el-text-color-regular); color: var(--el-text-color-regular);
transition: all 0.3s;
} }
&:hover { &:hover {
background-color: var(--el-fill-color-light); background-color: var(--el-fill-color-light);
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
.el-icon { .el-icon {
color: var(--el-color-primary); color: var(--el-color-primary);
} }
} }
&:active {
transform: translateY(0);
}
:deep(.el-badge__content) { :deep(.el-badge__content) {
transform: translateY(-8px) translateX(8px); transform: translateY(-8px) translateX(8px);
border: 2px solid var(--el-bg-color);
}
:deep(.el-badge__content.is-fixed) {
transform: translateY(-8px) translateX(8px);
} }
} }
} }
// 下拉菜单样式优化
:deep(.el-dropdown-menu__item) {
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s;
.el-icon {
font-size: 16px;
}
&:hover {
background-color: var(--el-fill-color-light);
color: var(--el-color-primary);
}
}
</style> </style>

View File

@@ -5,8 +5,7 @@
<!-- 一级菜单栏 (窄侧边栏) --> <!-- 一级菜单栏 (窄侧边栏) -->
<el-aside width="60px" class="first-sidebar"> <el-aside width="60px" class="first-sidebar">
<div class="logo-mini"> <div class="logo-mini">
<img v-if="logo" :src="logo" alt="logo" /> <img :src="logo" alt="logo" />
<span v-else class="logo-text-mini">{{ logoText }}</span>
</div> </div>
<div class="menu-list"> <div class="menu-list">
<div v-for="menu in parentMenus" :key="menu.path" class="menu-item" :class="{ active: selectedParentMenu?.path === menu.path }" @click="handleParentMenuClick(menu)"> <div v-for="menu in parentMenus" :key="menu.path" class="menu-item" :class="{ active: selectedParentMenu?.path === menu.path }" @click="handleParentMenuClick(menu)">
@@ -20,9 +19,9 @@
<!-- 二级菜单栏 --> <!-- 二级菜单栏 -->
<el-aside :width="sidebarCollapsed ? '60px' : '220px'" class="second-sidebar"> <el-aside :width="sidebarCollapsed ? '60px' : '220px'" class="second-sidebar">
<div v-if="!sidebarCollapsed" class="logo"> <div v-if="!sidebarCollapsed" class="logo logo-only">
<img v-if="logo" :src="logo" alt="logo" /> <img v-if="logo" :src="logo" alt="logo" />
<span class="logo-text">{{ logoText }}</span> <span class="logo-text hidden">{{ logoText }}</span>
</div> </div>
<el-scrollbar class="menu-scrollbar"> <el-scrollbar class="menu-scrollbar">
<menu-component /> <menu-component />
@@ -34,7 +33,7 @@
<el-header class="app-header"> <el-header class="app-header">
<div class="header-left"> <div class="header-left">
<el-icon class="collapse-icon" @click="toggleSidebar"> <el-icon class="collapse-icon" @click="toggleSidebar">
<component :is="sidebarCollapsed ? 'Expand' : 'Fold'" /> <component :is="sidebarCollapsed ? 'ElIconExpand' : 'ElIconFold'" />
</el-icon> </el-icon>
<breadcrumb v-if="showBreadcrumb" /> <breadcrumb v-if="showBreadcrumb" />
</div> </div>
@@ -69,7 +68,7 @@
<el-header class="app-header"> <el-header class="app-header">
<div class="header-left"> <div class="header-left">
<el-icon class="collapse-icon" @click="toggleSidebar"> <el-icon class="collapse-icon" @click="toggleSidebar">
<component :is="sidebarCollapsed ? 'Expand' : 'Fold'" /> <component :is="sidebarCollapsed ? 'ElIconExpand' : 'ElIconFold'" />
</el-icon> </el-icon>
<breadcrumb v-if="showBreadcrumb" /> <breadcrumb v-if="showBreadcrumb" />
</div> </div>
@@ -101,11 +100,11 @@
<menu-component mode="horizontal" /> <menu-component mode="horizontal" />
</div> </div>
<div class="header-right"> <div class="header-right">
<tags v-if="showTags" />
<userbar /> <userbar />
</div> </div>
</el-header> </el-header>
<el-main class="app-main"> <el-main class="app-main">
<tags v-if="showTags" />
<router-view v-slot="{ Component, route }"> <router-view v-slot="{ Component, route }">
<keep-alive :include="cachedViews"> <keep-alive :include="cachedViews">
<component v-if="!route.meta.link" :is="Component" :key="refreshKey" /> <component v-if="!route.meta.link" :is="Component" :key="refreshKey" />
@@ -272,6 +271,18 @@ watch(
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
// 双栏布局只显示logo隐藏名称
&.logo-only .logo-text.hidden {
display: none;
}
&.logo-only {
justify-content: center;
img {
margin: 0;
}
}
} }
.logo-mini { .logo-mini {
@@ -418,4 +429,130 @@ watch(
display: none !important; display: none !important;
} }
} }
// 响应式设计
@media screen and (max-width: 768px) {
// 小屏幕优化
.layout-default {
.first-sidebar {
width: 50px !important;
.menu-item {
height: 45px;
.el-icon {
font-size: 18px;
}
.menu-text {
font-size: 10px;
}
}
.logo-mini img {
height: 28px;
width: 28px;
}
}
.second-sidebar {
position: fixed;
left: 50px;
top: 0;
bottom: 0;
z-index: 1000;
background: var(--el-bg-color);
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
}
}
.layout-menu {
.menu-sidebar {
position: fixed;
left: 0;
top: 0;
bottom: 0;
z-index: 1000;
background: var(--el-bg-color);
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
}
}
// Header 优化
.el-header {
padding: 0 12px;
.header-left,
.header-right {
gap: 8px;
}
.collapse-icon {
font-size: 18px;
}
:deep(.breadcrumb) {
display: none;
}
}
// Tags 优化
.tags-view-item {
padding: 0 8px;
font-size: 11px;
}
}
@media screen and (max-width: 480px) {
// 超小屏幕优化
.layout-default {
.first-sidebar {
width: 45px !important;
}
.second-sidebar {
left: 45px;
}
}
.layout-menu {
.menu-sidebar {
width: 200px !important;
}
}
// Header 优化
.el-header {
padding: 0 8px;
.header-left,
.header-right {
gap: 6px;
}
.icon-btn {
width: 32px;
height: 32px;
}
.avatar-wrapper {
padding: 4px 8px;
.username {
display: none;
}
}
}
// Logo 优化
.logo {
img {
height: 28px;
}
.logo-text {
font-size: 16px;
}
}
}
</style> </style>