4 changed files with 855 additions and 1 deletions
-
5src/store/modules/common.js
-
44src/views/main-navbar.vue
-
795src/views/main-sidebar-hover.vue
-
12src/views/main.vue
@ -0,0 +1,795 @@ |
|||
<template> |
|||
<aside class="site-sidebar-hover" :class="['site-sidebar--' + sidebarLayoutSkin, { 'site-sidebar--fold': sidebarFold }]"> |
|||
<div class="site-sidebar__inner"> |
|||
<!-- 展开状态的内容 --> |
|||
<template v-if="!sidebarFold"> |
|||
<!-- 搜索框 --> |
|||
<div class="sidebar-search"> |
|||
<el-input |
|||
v-model="search" |
|||
placeholder="搜索" |
|||
@keyup.enter.native="searchMenu1" |
|||
size="small"> |
|||
<i slot="suffix" class="el-icon-search" @click="searchMenu1()"></i> |
|||
</el-input> |
|||
</div> |
|||
|
|||
<!-- 首页 --> |
|||
<div class="sidebar-home-item" @click="$router.push({ name: 'home' })"> |
|||
<icon-svg name="shouye" class="sidebar-menu-icon"></icon-svg> |
|||
<span>{{ pageLanguage.homePage }}</span> |
|||
</div> |
|||
|
|||
<!-- 主菜单列表 --> |
|||
<div class="sidebar-main-menu"> |
|||
<div |
|||
v-for="menu in menuList" |
|||
:key="menu.menuId" |
|||
class="main-menu-item" |
|||
@mouseenter="showSubMenu(menu, $event)" |
|||
@mouseleave="hideSubMenu" |
|||
@click="handleMainMenuClick(menu)"> |
|||
<icon-svg :name="menu.icon || ''" class="sidebar-menu-icon"></icon-svg> |
|||
<span class="menu-title">{{ menu.name }}</span> |
|||
<i v-if="menu.list && menu.list.length > 0" class="el-icon-arrow-right menu-arrow"></i> |
|||
</div> |
|||
|
|||
<!-- 收藏夹 --> |
|||
<div |
|||
v-for="menu in favoriteList" |
|||
:key="'fav-' + menu.menuId" |
|||
class="main-menu-item favorite-menu" |
|||
@mouseenter="showSubMenu(menu, $event)" |
|||
@mouseleave="hideSubMenu" |
|||
@click="handleMainMenuClick(menu)"> |
|||
<icon-svg :name="menu.icon || 'star-on'" class="sidebar-menu-icon"></icon-svg> |
|||
<span class="menu-title">{{ menu.name }}</span> |
|||
<i v-if="menu.list && menu.list.length > 0" class="el-icon-arrow-right menu-arrow"></i> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<!-- 折叠状态的内容 - 只显示图标 --> |
|||
<template v-else> |
|||
<!-- 首页图标 --> |
|||
<div class="sidebar-home-item folded-item" @click="$router.push({ name: 'home' })" :title="pageLanguage.homePage"> |
|||
<icon-svg name="shouye" class="sidebar-menu-icon"></icon-svg> |
|||
</div> |
|||
|
|||
<!-- 主菜单图标列表 --> |
|||
<div class="sidebar-main-menu"> |
|||
<div |
|||
v-for="menu in menuList" |
|||
:key="menu.menuId" |
|||
class="main-menu-item folded-item" |
|||
@mouseenter="showSubMenu(menu, $event)" |
|||
@mouseleave="hideSubMenu" |
|||
@click="handleMainMenuClick(menu)" |
|||
:title="menu.name"> |
|||
<icon-svg :name="menu.icon || ''" class="sidebar-menu-icon"></icon-svg> |
|||
</div> |
|||
|
|||
<!-- 收藏夹图标 --> |
|||
<div |
|||
v-for="menu in favoriteList" |
|||
:key="'fav-' + menu.menuId" |
|||
class="main-menu-item folded-item favorite-menu" |
|||
@mouseenter="showSubMenu(menu, $event)" |
|||
@mouseleave="hideSubMenu" |
|||
@click="handleMainMenuClick(menu)" |
|||
:title="menu.name"> |
|||
<icon-svg :name="menu.icon || 'star-on'" class="sidebar-menu-icon"></icon-svg> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
</div> |
|||
|
|||
<!-- 悬浮子菜单 --> |
|||
<div |
|||
v-if="hoveredMenu && hoveredMenu.list && hoveredMenu.list.length > 0" |
|||
class="hover-submenu" |
|||
:style="submenuStyle" |
|||
@mouseenter="keepSubMenuVisible" |
|||
@mouseleave="hideSubMenu"> |
|||
<div class="submenu-content"> |
|||
<div class="submenu-title">{{ hoveredMenu.name }}</div> |
|||
<div class="submenu-items"> |
|||
<div |
|||
v-for="subItem in hoveredMenu.list" |
|||
:key="subItem.menuId" |
|||
class="submenu-category"> |
|||
<!-- 统一的显示样式:都显示为分类标题 + 子项 --> |
|||
<div class="category-title">{{ subItem.name }}</div> |
|||
<div class="category-items"> |
|||
<!-- 如果有子菜单,递归显示所有子项 --> |
|||
<template v-if="subItem.list && subItem.list.length > 0"> |
|||
<div v-for="grandChild in subItem.list" :key="grandChild.menuId"> |
|||
<!-- 如果孙子菜单还有子菜单,显示为子分类 --> |
|||
<div v-if="grandChild.list && grandChild.list.length > 0" class="sub-category"> |
|||
<div class="sub-category-title">{{ grandChild.name }}</div> |
|||
<div class="sub-category-items"> |
|||
<span |
|||
v-for="greatGrandChild in getAllLeafMenus(grandChild)" |
|||
:key="greatGrandChild.menuId" |
|||
class="submenu-link sub-link" |
|||
@click="gotoRouteHandle(greatGrandChild)"> |
|||
{{ greatGrandChild.name }} |
|||
</span> |
|||
</div> |
|||
</div> |
|||
<!-- 如果孙子菜单没有子菜单,直接显示 --> |
|||
<span |
|||
v-else |
|||
class="submenu-link" |
|||
@click="gotoRouteHandle(grandChild)"> |
|||
{{ grandChild.name }} |
|||
</span> |
|||
</div> |
|||
</template> |
|||
<!-- 如果没有子菜单,将自己作为子项显示 --> |
|||
<template v-else> |
|||
<span |
|||
class="submenu-link" |
|||
@click="gotoRouteHandle(subItem)"> |
|||
{{ subItem.name }} |
|||
</span> |
|||
</template> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</aside> |
|||
</template> |
|||
|
|||
<script> |
|||
import { isURL } from '@/utils/validate' |
|||
import { userFavoriteList } from '@/api/userFavorite.js' |
|||
import { searchFunctionButtonList } from '@/api/sysLanguage.js' |
|||
|
|||
export default { |
|||
name: 'MainSidebarHover', |
|||
data() { |
|||
return { |
|||
dynamicMenuRoutes: [], |
|||
search: '', |
|||
favoriteList: [], |
|||
hoveredMenu: null, |
|||
submenuStyle: {}, |
|||
hideTimer: null, |
|||
list: [], |
|||
pageLanguage: { |
|||
homePage: '首页' |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
sidebarLayoutSkin: { |
|||
get() { |
|||
return this.$store.state.common.sidebarLayoutSkin |
|||
} |
|||
}, |
|||
sidebarFold: { |
|||
get() { |
|||
return this.$store.state.common.sidebarFold |
|||
} |
|||
}, |
|||
menuList: { |
|||
get() { |
|||
return this.$store.state.common.menuList |
|||
}, |
|||
set(val) { |
|||
this.$store.commit('common/updateMenuList', val) |
|||
} |
|||
}, |
|||
menuActiveName: { |
|||
get() { |
|||
return this.$store.state.common.menuActiveName |
|||
}, |
|||
set(val) { |
|||
this.$store.commit('common/updateMenuActiveName', val) |
|||
} |
|||
}, |
|||
mainTabs: { |
|||
get() { |
|||
return this.$store.state.common.mainTabs |
|||
}, |
|||
set(val) { |
|||
this.$store.commit('common/updateMainTabs', val) |
|||
} |
|||
}, |
|||
mainTabsActiveName: { |
|||
get() { |
|||
return this.$store.state.common.mainTabsActiveName |
|||
}, |
|||
set(val) { |
|||
this.$store.commit('common/updateMainTabsActiveName', val) |
|||
} |
|||
} |
|||
}, |
|||
watch: { |
|||
$route: 'routeHandle' |
|||
}, |
|||
created() { |
|||
this.menuList = JSON.parse(sessionStorage.getItem('menuList') || '[]').filter(item => item.menuId != 999) |
|||
this.favoriteList = JSON.parse(sessionStorage.getItem('menuList') || '[]').filter(item => item.menuId == 999) |
|||
this.userFavorites() |
|||
this.dynamicMenuRoutes = JSON.parse(sessionStorage.getItem('dynamicMenuRoutes') || '[]') |
|||
this.routeHandle(this.$route) |
|||
this.getFunctionButtonList() |
|||
}, |
|||
methods: { |
|||
// 获取页面多语言数据 |
|||
getFunctionButtonList() { |
|||
let queryButton = { |
|||
functionId: 'systemInformation', |
|||
tableId: 'systemInformation', |
|||
languageCode: this.$i18n.locale, |
|||
objectId: 'homePage' |
|||
} |
|||
searchFunctionButtonList(queryButton).then(({data}) => { |
|||
if (data.code == 0) { |
|||
this.pageLanguage = data.data |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
// 用户收藏夹 |
|||
userFavorites() { |
|||
let query = { |
|||
userId: this.$store.state.user.id, |
|||
languageCode: this.$i18n.locale |
|||
} |
|||
userFavoriteList(query).then(({data}) => { |
|||
if (data.list && data.list.length > 0 && this.favoriteList.length > 0) { |
|||
this.favoriteList[0].list = data.list |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
// 显示子菜单 |
|||
showSubMenu(menu, event) { |
|||
if (this.hideTimer) { |
|||
clearTimeout(this.hideTimer) |
|||
this.hideTimer = null |
|||
} |
|||
|
|||
if (menu.list && menu.list.length > 0) { |
|||
this.hoveredMenu = menu |
|||
const rect = event.currentTarget.getBoundingClientRect() |
|||
this.submenuStyle = { |
|||
top: rect.top + 'px', |
|||
left: rect.right + 'px', |
|||
display: 'block' |
|||
} |
|||
} |
|||
}, |
|||
|
|||
// 隐藏子菜单 |
|||
hideSubMenu() { |
|||
this.hideTimer = setTimeout(() => { |
|||
this.hoveredMenu = null |
|||
this.submenuStyle = { display: 'none' } |
|||
}, 100) |
|||
}, |
|||
|
|||
// 保持子菜单可见 |
|||
keepSubMenuVisible() { |
|||
if (this.hideTimer) { |
|||
clearTimeout(this.hideTimer) |
|||
this.hideTimer = null |
|||
} |
|||
}, |
|||
|
|||
// 处理主菜单点击 |
|||
handleMainMenuClick(menu) { |
|||
if (!menu.list || menu.list.length === 0) { |
|||
this.gotoRouteHandle(menu) |
|||
} |
|||
}, |
|||
|
|||
// 通过menuId与动态(菜单)路由进行匹配跳转至指定路由 |
|||
gotoRouteHandle(menu) { |
|||
console.log('点击菜单:', menu.name, 'menuId:', menu.menuId) |
|||
console.log('动态路由数量:', this.dynamicMenuRoutes.length) |
|||
|
|||
var route = this.dynamicMenuRoutes.filter(item => item.meta.menuId === menu.menuId) |
|||
console.log('匹配到的路由:', route) |
|||
|
|||
if (route.length >= 1) { |
|||
console.log('跳转到路由:', route[0].name) |
|||
this.$router.push({ name: route[0].name }) |
|||
this.hideSubMenu() |
|||
} else { |
|||
console.warn('未找到对应的路由,menuId:', menu.menuId) |
|||
// 尝试其他跳转方式 |
|||
if (menu.url) { |
|||
console.log('尝试使用URL跳转:', menu.url) |
|||
this.$router.push(menu.url) |
|||
this.hideSubMenu() |
|||
} else { |
|||
this.$message.warning(`菜单 "${menu.name}" 暂无对应页面`) |
|||
} |
|||
} |
|||
}, |
|||
|
|||
// 搜索菜单 |
|||
searchMenu1() { |
|||
if (this.search == '') { |
|||
this.menuList = JSON.parse(sessionStorage.getItem('menuList') || '[]').filter(item => item.menuId != 999) |
|||
} else { |
|||
// 搜索逻辑保持原有实现 |
|||
this.menuList = JSON.parse(sessionStorage.getItem('menuList') || '[]').filter(item => item.menuId != 999) |
|||
|
|||
this.list = this.treeFindPath(this.menuList) |
|||
let list = this.treeFindPath(this.menuList) |
|||
list = this.distinct(list, list) |
|||
|
|||
this.menuList = list.filter(item => item.name.indexOf(this.search) != -1) |
|||
let menuSum = [] |
|||
for (let data of this.menuList) { |
|||
menuSum.push(data) |
|||
this.getParent(data.parentId, menuSum) |
|||
} |
|||
menuSum = Array.from(new Set([...menuSum])) |
|||
|
|||
if (menuSum.length > 0) { |
|||
let menuList = menuSum.filter(item => item.parentId == 0) |
|||
this.menuList = menuList |
|||
this.treeList(menuList, menuSum) |
|||
} |
|||
} |
|||
}, |
|||
|
|||
// 获取父节点 |
|||
getParent(val, sum) { |
|||
if (val == 0) { |
|||
return |
|||
} |
|||
let parent = this.list.filter(item => item.menuId == val) |
|||
if (parent.length > 0) { |
|||
parent[0].list.length = 0 |
|||
sum.push(parent[0]) |
|||
this.getParent(parent[0].parentId, sum) |
|||
} |
|||
}, |
|||
|
|||
// 封装树形结构 |
|||
treeList(menuList, menuSum) { |
|||
for (let m1 of menuList) { |
|||
for (let m2 of menuSum) { |
|||
if (m1.menuId == m2.parentId) { |
|||
m1.list.push(m2) |
|||
this.treeList(m1.list, menuSum) |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
|
|||
// 树结构菜单转换list |
|||
treeFindPath(tree, path = []) { |
|||
if (!tree) return [] |
|||
for (const data of tree) { |
|||
path.push(data) |
|||
this.treeFindPath(data.list, path) |
|||
} |
|||
return path |
|||
}, |
|||
|
|||
// 去重 |
|||
distinct(a, b) { |
|||
return Array.from(new Set([...a, ...b])) |
|||
}, |
|||
|
|||
// 递归获取所有叶子节点菜单(没有子菜单的菜单项) |
|||
getAllLeafMenus(menu) { |
|||
const leafMenus = [] |
|||
|
|||
function collectLeafMenus(menuItem) { |
|||
if (!menuItem.list || menuItem.list.length === 0) { |
|||
// 没有子菜单,是叶子节点 |
|||
leafMenus.push(menuItem) |
|||
} else { |
|||
// 有子菜单,递归处理 |
|||
menuItem.list.forEach(child => { |
|||
collectLeafMenus(child) |
|||
}) |
|||
} |
|||
} |
|||
|
|||
collectLeafMenus(menu) |
|||
return leafMenus |
|||
}, |
|||
|
|||
// 路由操作 |
|||
routeHandle(route) { |
|||
if (route.meta.isTab) { |
|||
var tab = this.mainTabs.filter(item => item.name === route.name)[0] |
|||
if (!tab) { |
|||
if (route.meta.isDynamic) { |
|||
route = this.dynamicMenuRoutes.filter(item => item.name === route.name)[0] |
|||
if (!route) { |
|||
return console.error('未能找到可用标签页!') |
|||
} |
|||
} |
|||
tab = { |
|||
menuId: route.meta.menuId || route.name, |
|||
name: route.name, |
|||
title: route.meta.title, |
|||
type: isURL(route.meta.iframeUrl) ? 'iframe' : 'module', |
|||
iframeUrl: route.meta.iframeUrl || '', |
|||
params: route.params, |
|||
query: route.query |
|||
} |
|||
this.mainTabs = this.mainTabs.concat(tab) |
|||
} |
|||
this.menuActiveName = tab.menuId + '' |
|||
this.mainTabsActiveName = tab.name |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.site-sidebar-hover { |
|||
position: fixed; |
|||
top: 32px; |
|||
left: 0; |
|||
bottom: 0; |
|||
z-index: 1020; |
|||
width: 230px; |
|||
background-color: #263238; |
|||
overflow: hidden; |
|||
transition: width 0.3s ease; |
|||
|
|||
&.site-sidebar--fold { |
|||
width: 64px; |
|||
|
|||
.site-sidebar__inner { |
|||
width: 64px; |
|||
} |
|||
|
|||
// 折叠菜单项样式 |
|||
.main-menu-item.folded-item { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 14px 0; |
|||
transition: all 0.3s; |
|||
|
|||
.sidebar-menu-icon { |
|||
width: 20px !important; |
|||
height: 20px !important; |
|||
font-size: 26px !important; |
|||
line-height: 32px !important; |
|||
display: inline-flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
|
|||
// 针对 svg 图标的控制 |
|||
svg { |
|||
width: 20px; |
|||
height: 20px; |
|||
} |
|||
} |
|||
|
|||
.menu-title { |
|||
display: none; |
|||
} |
|||
} |
|||
|
|||
// 子菜单图标样式(如有) |
|||
.el-submenu__icon-arrow { |
|||
display: none; |
|||
} |
|||
} |
|||
|
|||
|
|||
.site-sidebar__inner { |
|||
position: relative; |
|||
z-index: 1; |
|||
width: 230px; |
|||
height: 100%; |
|||
padding: 10px 0; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
.sidebar-search { |
|||
padding: 0 15px 10px; |
|||
border-bottom: 1px solid #37474f; |
|||
margin-bottom: 10px; |
|||
|
|||
.el-input__inner { |
|||
background: #37474f; |
|||
border: none; |
|||
color: #fff; |
|||
font-size: 12px; |
|||
|
|||
&::placeholder { |
|||
color: #8a979e; |
|||
} |
|||
} |
|||
|
|||
.el-icon-search { |
|||
color: #8a979e; |
|||
cursor: pointer; |
|||
|
|||
&:hover { |
|||
color: #fff; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.sidebar-home-item { |
|||
display: flex; |
|||
align-items: center; |
|||
padding: 12px 20px; |
|||
color: #8a979e; |
|||
cursor: pointer; |
|||
transition: all 0.3s; |
|||
|
|||
&:hover { |
|||
background-color: #37474f; |
|||
color: #fff; |
|||
} |
|||
|
|||
.sidebar-menu-icon { |
|||
width: 20px; |
|||
margin-right: 10px; |
|||
text-align: center; |
|||
font-size: 16px; |
|||
} |
|||
} |
|||
|
|||
.sidebar-main-menu { |
|||
.main-menu-item { |
|||
display: flex; |
|||
align-items: center; |
|||
padding: 14px 20px; |
|||
color: #8a979e; |
|||
cursor: pointer; |
|||
transition: all 0.3s ease; |
|||
position: relative; |
|||
border-radius: 0 25px 25px 0; |
|||
margin: 2px 0; |
|||
|
|||
&::before { |
|||
content: ''; |
|||
position: absolute; |
|||
left: 0; |
|||
top: 0; |
|||
bottom: 0; |
|||
width: 0; |
|||
background: linear-gradient(135deg, #17B3A3, #0BB2D4); |
|||
transition: width 0.3s ease; |
|||
} |
|||
|
|||
&:hover { |
|||
background: linear-gradient(135deg, rgba(23, 179, 163, 0.1), rgba(11, 178, 212, 0.1)); |
|||
color: #fff; |
|||
transform: translateX(5px); |
|||
|
|||
&::before { |
|||
width: 4px; |
|||
} |
|||
|
|||
.menu-arrow { |
|||
transform: translateX(5px); |
|||
opacity: 1; |
|||
} |
|||
} |
|||
|
|||
&.favorite-menu { |
|||
border-top: 1px solid #37474f; |
|||
margin-top: 15px; |
|||
padding-top: 18px; |
|||
|
|||
&::after { |
|||
content: '★'; |
|||
position: absolute; |
|||
right: 35px; |
|||
color: #ffd700; |
|||
font-size: 12px; |
|||
opacity: 0.7; |
|||
} |
|||
} |
|||
|
|||
.sidebar-menu-icon { |
|||
width: 24px; |
|||
margin-right: 12px; |
|||
text-align: center; |
|||
font-size: 18px; |
|||
color: inherit; |
|||
transition: transform 0.3s ease; |
|||
} |
|||
|
|||
.menu-title { |
|||
flex: 1; |
|||
font-size: 14px; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.menu-arrow { |
|||
font-size: 14px; |
|||
opacity: 0.6; |
|||
transition: all 0.3s ease; |
|||
color: #17B3A3; |
|||
} |
|||
|
|||
&:hover .sidebar-menu-icon { |
|||
transform: scale(1.1); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.hover-submenu { |
|||
position: fixed; |
|||
z-index: 1030; |
|||
background: linear-gradient(135deg, #fafbfc 0%, #f8f9fa 100%); |
|||
border: 1px solid #dee2e6; |
|||
border-radius: 8px; |
|||
box-shadow: |
|||
0 8px 32px 0 rgba(0, 0, 0, 0.12), |
|||
0 2px 8px 0 rgba(0, 0, 0, 0.08), |
|||
0 0 0 1px rgba(255, 255, 255, 0.05); |
|||
min-width: 900px; |
|||
max-width: 1200px; |
|||
max-height: 600px; |
|||
overflow-y: auto; |
|||
backdrop-filter: blur(10px); |
|||
-webkit-backdrop-filter: blur(10px); |
|||
|
|||
.submenu-content { |
|||
padding: 25px; |
|||
|
|||
.submenu-title { |
|||
font-size: 18px; |
|||
font-weight: bold; |
|||
color: #303133; |
|||
margin-bottom: 20px; |
|||
padding-bottom: 12px; |
|||
border-bottom: 2px solid #17B3A3; |
|||
position: relative; |
|||
|
|||
&::after { |
|||
content: ''; |
|||
position: absolute; |
|||
bottom: -2px; |
|||
left: 0; |
|||
width: 60px; |
|||
height: 2px; |
|||
background: linear-gradient(90deg, #17B3A3, #0BB2D4); |
|||
} |
|||
} |
|||
|
|||
.submenu-items { |
|||
display: grid; |
|||
grid-template-columns: repeat(4, 1fr); |
|||
gap: 20px; |
|||
|
|||
.submenu-category { |
|||
min-height: 60px; // 确保每个分类都有最小高度 |
|||
|
|||
.category-title { |
|||
font-size: 15px; |
|||
font-weight: 600; |
|||
color: #17B3A3; |
|||
margin-bottom: 12px; |
|||
padding-bottom: 8px; |
|||
border-bottom: 1px solid #e8f4f8; |
|||
position: relative; |
|||
|
|||
&::before { |
|||
content: ''; |
|||
position: absolute; |
|||
left: 0; |
|||
bottom: -1px; |
|||
width: 30px; |
|||
height: 1px; |
|||
background: #17B3A3; |
|||
} |
|||
} |
|||
|
|||
.category-items { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 6px; |
|||
|
|||
.sub-category { |
|||
margin: 8px 0; |
|||
padding-left: 12px; |
|||
border-left: 2px solid #e8f4f8; |
|||
|
|||
.sub-category-title { |
|||
font-size: 13px; |
|||
font-weight: 600; |
|||
color: #606266; |
|||
margin-bottom: 6px; |
|||
padding-bottom: 4px; |
|||
border-bottom: 1px solid #f0f0f0; |
|||
} |
|||
|
|||
.sub-category-items { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 4px; |
|||
} |
|||
} |
|||
|
|||
.submenu-link { |
|||
display: inline-block; |
|||
padding: 8px 12px; |
|||
color: #606266; |
|||
font-size: 13px; |
|||
font-weight: 600; |
|||
cursor: pointer; |
|||
border-radius: 4px; |
|||
transition: all 0.3s ease; |
|||
position: relative; |
|||
|
|||
&:hover { |
|||
background: linear-gradient(135deg, #f0f9ff, #e0f7fa); |
|||
color: #17B3A3; |
|||
transform: translateX(3px); |
|||
box-shadow: 0 2px 8px rgba(23, 179, 163, 0.15); |
|||
} |
|||
|
|||
&::before { |
|||
content: ''; |
|||
position: absolute; |
|||
left: 0; |
|||
top: 50%; |
|||
transform: translateY(-50%); |
|||
width: 0; |
|||
height: 2px; |
|||
background: #17B3A3; |
|||
transition: width 0.3s ease; |
|||
} |
|||
|
|||
&:hover::before { |
|||
width: 3px; |
|||
} |
|||
|
|||
&.sub-link { |
|||
font-size: 12px; |
|||
font-weight: 400; |
|||
padding: 6px 10px; |
|||
margin-left: 8px; |
|||
// background: rgba(23, 179, 163, 0.05); |
|||
border-radius: 3px; |
|||
|
|||
&:hover { |
|||
background: rgba(23, 179, 163, 0.1); |
|||
transform: translateX(2px); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
|||
} |
|||
} |
|||
|
|||
// 添加进入动画 |
|||
animation: slideInRight 0.3s ease-out; |
|||
} |
|||
|
|||
@keyframes slideInRight { |
|||
from { |
|||
opacity: 0; |
|||
transform: translateX(-20px); |
|||
} |
|||
to { |
|||
opacity: 1; |
|||
transform: translateX(0); |
|||
} |
|||
} |
|||
|
|||
// 深色主题样式 |
|||
.site-sidebar--dark { |
|||
background-color: #263238; |
|||
} |
|||
</style> |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue