Merge pull request #59 from zhitan-cloud/jiayu1.0
Jiayu1.0
| | |
| | | |
| | | |
| | | .main-container { |
| | | background-color: #110f2e !important; |
| | | height: 100%; |
| | | transition: margin-left 0.28s; |
| | | margin-left: $base-sidebar-width; |
| | |
| | | transition: width 0.28s; |
| | | width: $base-sidebar-width !important; |
| | | background-color: $base-menu-background; |
| | | height: 100%; |
| | | height: calc(100% - 60px) !important; |
| | | position: fixed; |
| | | top: 60px; |
| | | bottom: 0; |
| | |
| | | transition: width 0.28s; |
| | | width: $base-sidebar-width !important; |
| | | background-color: $base-menu-background; |
| | | height: 100%; |
| | | height: calc(100% - 60px) !important; |
| | | position: fixed; |
| | | top: 60px; |
| | | bottom: 0; |
| | |
| | | setTimeout(updateScrollButtons, 300); |
| | | } |
| | | |
| | | /** |
| | | * 查找最深层的子菜单(叶子节点) |
| | | * 递归查找第一个没有children的子菜单 |
| | | */ |
| | | function findDeepestLeafMenu(route) { |
| | | if (!route) return null; |
| | | |
| | | // 如果没有子菜单或子菜单为空,则返回当前路由 |
| | | if (!route.children || route.children.length === 0) { |
| | | return route; |
| | | } |
| | | |
| | | // 找到第一个非隐藏的子菜单 |
| | | const firstVisibleChild = route.children.find(child => !child.hidden); |
| | | if (!firstVisibleChild) { |
| | | return route; // 如果所有子菜单都是隐藏的,返回当前路由 |
| | | } |
| | | |
| | | // 递归查找这个子菜单的最深层子菜单 |
| | | return findDeepestLeafMenu(firstVisibleChild); |
| | | } |
| | | |
| | | function handleSelect(key, keyPath) { |
| | | currentIndex.value = key; |
| | | const route = routers.value.find(item => item.path === key); |
| | |
| | | return; |
| | | } |
| | | |
| | | if (key === '/index' || key === '/') { |
| | | // 首页时显示折叠的侧边栏,而不是隐藏 |
| | | router.push({ path: key }); |
| | | appStore.showCollapsedSidebar(); |
| | | return; |
| | | } |
| | | |
| | | |
| | | // 检查是否有子路由 |
| | | if (route && route.children && route.children.length > 0) { |
| | | // 有子路由,显示侧边栏 |
| | | activeRoutes(key); |
| | | const firstChild = route.children[0]; |
| | | const path = firstChild.path.startsWith('/') ? firstChild.path : `${key}/${firstChild.path}`; |
| | | if (firstChild.query) { |
| | | router.push({ path, query: firstChild.query }); |
| | | |
| | | // 按照正确的路径构建层级,这里是特殊处理 |
| | | let targetPath = key; // 从当前点击的菜单路径开始 |
| | | let targetQuery = null; |
| | | let currentNode = route; |
| | | let pathSegments = []; |
| | | |
| | | // 当前路径是第一段 |
| | | pathSegments.push(currentNode.path); |
| | | |
| | | // 逐层添加子路径 |
| | | while (currentNode.children && currentNode.children.length > 0) { |
| | | const firstChild = currentNode.children.find(child => !child.hidden); |
| | | if (!firstChild) break; |
| | | |
| | | // 跳过ParentView类型的中间节点,直接使用其子节点的path |
| | | if (firstChild.component === 'ParentView' || firstChild.component.name === 'ParentView') { |
| | | currentNode = firstChild; |
| | | pathSegments.push(firstChild.path); |
| | | continue; |
| | | } |
| | | |
| | | // 普通节点处理 |
| | | currentNode = firstChild; |
| | | // 如果路径不是以/开头,则添加到路径片段中 |
| | | if (!firstChild.path.startsWith('/')) { |
| | | pathSegments.push(firstChild.path); |
| | | } else { |
| | | // 如果是绝对路径,则替换之前所有路径 |
| | | pathSegments = [firstChild.path]; |
| | | } |
| | | |
| | | targetQuery = firstChild.query; |
| | | |
| | | // 如果到达叶子节点(没有子节点),则结束查找 |
| | | if (!firstChild.children || firstChild.children.length === 0) { |
| | | break; |
| | | } |
| | | } |
| | | |
| | | // 构建最终路径 |
| | | if (pathSegments.length > 0) { |
| | | // 如果第一段不是以/开头,添加/ |
| | | if (!pathSegments[0].startsWith('/')) { |
| | | pathSegments[0] = '/' + pathSegments[0]; |
| | | } |
| | | |
| | | // 组合路径 - 把数组中所有路径拼接起来,如果某段包含完整路径(以/开头)则从该段重新开始 |
| | | targetPath = pathSegments.reduce((fullPath, segment, index) => { |
| | | if (segment.startsWith('/')) { |
| | | return segment; |
| | | } else if (index === 0) { |
| | | return segment; |
| | | } else { |
| | | return `${fullPath}/${segment}`; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | // 导航到目标路由 |
| | | if (targetQuery) { |
| | | router.push({ path: targetPath, query: targetQuery }); |
| | | } else { |
| | | router.push({ path }); |
| | | router.push({ path: targetPath }); |
| | | } |
| | | } else { |
| | | // 没有子路由,隐藏侧边栏 |
| | |
| | | |
| | | function activeRoutes(key) { |
| | | let routes = []; |
| | | if (key === '/index' || key === '/') { |
| | | // 首页时显示折叠的侧边栏,而不是隐藏 |
| | | appStore.showCollapsedSidebar(); |
| | | return []; |
| | | } |
| | | |
| | | |
| | | // 查找匹配的路由 |
| | | if (childrenMenus.value && childrenMenus.value.length > 0) { |
| | |
| | | background: #110f2e; |
| | | padding: 14px; |
| | | box-sizing: border-box; |
| | | padding-top: 8px; |
| | | } |
| | | |
| | | .fixed-header + .app-main { |
| | |
| | | } |
| | | |
| | | .fixed-header + .app-main { |
| | | padding-top: 130px; |
| | | padding-top: 108px; /* 60px(navbar) + 34px(tagsview) + 14px(内边距) */ |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | .fixed-header + .app-main { |
| | | padding-top: 130px; |
| | | padding-top: 108px; /* 60px(navbar) + 34px(tagsview) + 14px(内边距) */ |
| | | } |
| | | } |
| | | } |
| | |
| | | </app-link> |
| | | </template> |
| | | |
| | | <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported> |
| | | <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported @click="handleSubMenuClick"> |
| | | <template v-if="item.meta" #title> |
| | | <svg-icon :icon-class="item.meta && item.meta.icon" /> |
| | | <span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title }}</span> |
| | |
| | | import { isExternal } from '@/utils/validate' |
| | | import AppLink from './Link' |
| | | import { getNormalPath } from '@/utils/ruoyi' |
| | | import { useRouter } from 'vue-router' |
| | | |
| | | const router = useRouter(); |
| | | |
| | | const props = defineProps({ |
| | | // route object |
| | |
| | | |
| | | const onlyOneChild = ref({}); |
| | | |
| | | /** |
| | | * 查找最深层的子菜单(叶子节点) |
| | | * 递归查找第一个没有children的子菜单 |
| | | */ |
| | | function findDeepestLeafMenu(route) { |
| | | if (!route) return null; |
| | | |
| | | // 如果没有子菜单或子菜单为空,则返回当前路由 |
| | | if (!route.children || route.children.length === 0) { |
| | | return route; |
| | | } |
| | | |
| | | // 找到第一个非隐藏的子菜单 |
| | | const firstVisibleChild = route.children.find(child => !child.hidden); |
| | | if (!firstVisibleChild) { |
| | | return route; // 如果所有子菜单都是隐藏的,返回当前路由 |
| | | } |
| | | |
| | | // 递归查找这个子菜单的最深层子菜单 |
| | | return findDeepestLeafMenu(firstVisibleChild); |
| | | } |
| | | |
| | | // 处理子菜单点击 |
| | | function handleSubMenuClick(e) { |
| | | // 阻止事件冒泡 |
| | | e.stopPropagation(); |
| | | |
| | | // 如果点击的是子菜单标题,则自动导航到最深层的子菜单 |
| | | if (e.target.closest('.el-sub-menu__title')) { |
| | | // 按照正确的路径构建层级 |
| | | let currentNode = props.item; |
| | | let pathSegments = []; |
| | | |
| | | // 首先添加当前节点的路径 |
| | | if (currentNode.path) { |
| | | pathSegments.push(currentNode.path); |
| | | } |
| | | |
| | | // 逐层添加子路径 |
| | | while (currentNode.children && currentNode.children.length > 0) { |
| | | const firstChild = currentNode.children.find(child => !child.hidden); |
| | | if (!firstChild) break; |
| | | |
| | | // 跳过ParentView类型的中间节点 |
| | | if (firstChild.component === 'ParentView' || firstChild.component.name === 'ParentView') { |
| | | currentNode = firstChild; |
| | | pathSegments.push(firstChild.path); |
| | | continue; |
| | | } |
| | | |
| | | // 普通节点处理 |
| | | currentNode = firstChild; |
| | | // 如果路径不是以/开头,则添加到路径片段中 |
| | | if (!firstChild.path.startsWith('/')) { |
| | | pathSegments.push(firstChild.path); |
| | | } else { |
| | | // 如果是绝对路径,则替换之前所有路径 |
| | | pathSegments = [firstChild.path]; |
| | | } |
| | | |
| | | // 如果到达叶子节点,则结束查找 |
| | | if (!firstChild.children || firstChild.children.length === 0) { |
| | | break; |
| | | } |
| | | } |
| | | |
| | | // 构建最终路径 |
| | | if (pathSegments.length > 0) { |
| | | // 如果第一段不是以/开头,添加/ |
| | | if (!pathSegments[0].startsWith('/')) { |
| | | pathSegments[0] = '/' + pathSegments[0]; |
| | | } |
| | | |
| | | // 组合路径 |
| | | const targetPath = pathSegments.reduce((fullPath, segment, index) => { |
| | | if (segment.startsWith('/')) { |
| | | return segment; |
| | | } else if (index === 0) { |
| | | return segment; |
| | | } else { |
| | | return `${fullPath}/${segment}`; |
| | | } |
| | | }); |
| | | |
| | | // 导航到目标路由,如果有查询参数则添加 |
| | | if (currentNode.query) { |
| | | router.push({ path: targetPath, query: currentNode.query }); |
| | | } else { |
| | | router.push({ path: targetPath }); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | function hasOneShowingChild(children = [], parent) { |
| | | if (!children) { |
| | | children = []; |
| | |
| | | }) |
| | | |
| | | // When there is only one child router, the child router is displayed by default |
| | | if (showingChildren.length === 1) { |
| | | if (showingChildren.length === 1 && !showingChildren[0].children) { |
| | | return true |
| | | } |
| | | |
| | | // If the single child also has children, don't treat it as a single showing child |
| | | if (showingChildren.length === 1 && showingChildren[0].children && showingChildren[0].children.length > 0) { |
| | | return false |
| | | } |
| | | |
| | | // Show parent if there are no child router to display |
| | | if (showingChildren.length === 0) { |
| | | onlyOneChild.value = { ...parent, path: '', noShowingChildren: true } |
| | |
| | | class="sidebar-container-wrapper" |
| | | > |
| | | <el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper" view-class="scrollbar-view"> |
| | | <!-- 首页时不显示任何菜单项 --> |
| | | <!-- 始终显示菜单项,不再根据路径判断 --> |
| | | <el-menu |
| | | v-if="!isHomePage" |
| | | :default-active="activeMenu" |
| | | :collapse="isCollapse" |
| | | :background-color="'transparent'" |
| | |
| | | mode="vertical" |
| | | class="custom-menu" |
| | | > |
| | | <sidebar-item |
| | | v-for="(route, index) in sidebarRouters" |
| | | :key="route.path + index" |
| | | :item="route" |
| | | :base-path="route.path" |
| | | /> |
| | | <!-- 当前是首页看板子路由时,渲染专用路由 --> |
| | | <template v-if="isIndexSubRoute"> |
| | | <sidebar-item |
| | | v-for="(route, index) in indexPageRouters" |
| | | :key="route.path + index" |
| | | :item="route" |
| | | :base-path="route.path" |
| | | /> |
| | | </template> |
| | | <template v-else> |
| | | <sidebar-item |
| | | v-for="(route, index) in sidebarRouters" |
| | | :key="route.path + index" |
| | | :item="route" |
| | | :base-path="route.path" |
| | | /> |
| | | </template> |
| | | </el-menu> |
| | | <!-- 首页时的空白区域 --> |
| | | <div v-else class="home-empty-menu"></div> |
| | | </el-scrollbar> |
| | | |
| | | <!-- 底部用户区域 --> |
| | |
| | | |
| | | const sidebarRouters = computed(() => permissionStore.sidebarRouters) |
| | | |
| | | // 判断当前是否为首页 |
| | | const isHomePage = computed(() => { |
| | | return route.path === '/index' || route.path === '/' || route.fullPath.startsWith('/index') |
| | | // 判断当前是否为首页子路由(/index/index) |
| | | const isIndexSubRoute = computed(() => { |
| | | return route.path === '/index/index' |
| | | }) |
| | | |
| | | // 首页专用路由,只有首页一个菜单项 |
| | | const homePageRouters = computed(() => { |
| | | // 从原始路由中筛选出首页路由 |
| | | const homeRoute = sidebarRouters.value.find(route => { |
| | | return route.children && route.children.find(child => child.path === '/index') |
| | | }) |
| | | |
| | | return homeRoute ? [homeRoute] : [] |
| | | // 判断当前是否为主首页路由(/index或/) |
| | | const isMainIndexRoute = computed(() => { |
| | | return route.path === '/index' || route.path === '/' |
| | | }) |
| | | |
| | | // 首页专用路由,首页看板相关菜单 |
| | | const indexPageRouters = computed(() => { |
| | | // 查找name为Index的路由 |
| | | const indexRoute = sidebarRouters.value.find(route => route.name === 'Index') |
| | | return indexRoute ? [indexRoute] : [] |
| | | }) |
| | | |
| | | const showLogo = computed(() => settingsStore.sidebarLogo) |
| | |
| | | } |
| | | |
| | | :deep(.scrollbar-wrapper) { |
| | | height: calc(100% - 290px) !important; |
| | | height: calc(100% - 220px) !important; |
| | | overflow-x: hidden !important; |
| | | } |
| | | |
| | | :deep(.scrollbar-view) { |
| | | height: 100%; |
| | | padding-bottom: 20px; |
| | | } |
| | | |
| | | :deep(.el-scrollbar__bar.is-vertical) { |
| | |
| | | &.is-active { |
| | | background-color: #3883FA !important; |
| | | color: #fff !important; |
| | | font-weight: bold; |
| | | position: relative; |
| | | box-shadow: 0 2px 8px rgba(56, 131, 250, 0.5); |
| | | |
| | | // 左侧指示条 |
| | | |
| | | } |
| | | |
| | | &:hover { |
| | |
| | | } |
| | | } |
| | | |
| | | &.is-active { |
| | | > .el-sub-menu__title { |
| | | color: #3883FA !important; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | |
| | | .el-menu-item { |
| | | padding-left: 45px !important; |
| | | min-width: auto !important; |
| | |
| | | height: 38px !important; |
| | | line-height: 38px !important; |
| | | } |
| | | |
| | | // Add styling for deeply nested submenus (level 3+) |
| | | .el-sub-menu { |
| | | .el-menu-item { |
| | | padding-left: 65px !important; |
| | | |
| | | &.is-active { |
| | | padding-left: 65px !important; |
| | | } |
| | | } |
| | | |
| | | // Level 4 |
| | | .el-menu { |
| | | .el-menu-item { |
| | | padding-left: 85px !important; |
| | | |
| | | &.is-active { |
| | | padding-left: 85px !important; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | // 底部用户区域样式 |
| | | .sidebar-footer { |
| | | position: absolute; |
| | | bottom: 72px; |
| | | bottom: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | border-top: 1px solid rgba(255, 255, 255, 0.1); |
| | |
| | | } |
| | | } |
| | | |
| | | // 添加深色模式专用样式 |
| | | .theme-dark { |
| | | :deep(.custom-menu) { |
| | | // Override Element Plus menu styles for dark theme |
| | | .el-menu-item { |
| | | &.is-active { |
| | | background-color: #4e77f8 !important; |
| | | color: #ffffff !important; |
| | | font-weight: bold; |
| | | box-shadow: 0 2px 10px rgba(78, 119, 248, 0.6); |
| | | position: relative; |
| | | |
| | | // 左侧指示条 |
| | | |
| | | } |
| | | |
| | | &:hover { |
| | | background-color: rgba(78, 119, 248, 0.2) !important; |
| | | } |
| | | } |
| | | |
| | | .el-sub-menu { |
| | | &.is-active { |
| | | > .el-sub-menu__title { |
| | | color: #4e77f8 !important; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | |
| | | .el-sub-menu__title { |
| | | &:hover { |
| | | background-color: rgba(78, 119, 248, 0.2) !important; |
| | | } |
| | | } |
| | | |
| | | // 嵌套子菜单样式 |
| | | .el-menu { |
| | | .el-menu-item { |
| | | &.is-active { |
| | | background-color: #4e77f8 !important; |
| | | color: #ffffff !important; |
| | | } |
| | | } |
| | | |
| | | .el-sub-menu { |
| | | &.is-active { |
| | | > .el-sub-menu__title { |
| | | color: #4e77f8 !important; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Add global style to override Element Plus defaults |
| | | :global(.el-menu--vertical .el-menu-item), |
| | | :global(.el-menu--vertical .el-sub-menu__title) { |
| | |
| | | outline: none; |
| | | } |
| | | } |
| | | |
| | | // 深色模式下折叠菜单的样式 |
| | | .theme-dark { |
| | | :deep(.custom-menu.el-menu--collapse) { |
| | | .el-menu-item, .el-sub-menu__title { |
| | | &.is-active { |
| | | background-color: #4e77f8 !important; |
| | | color: #ffffff !important; |
| | | box-shadow: 0 2px 8px rgba(78, 119, 248, 0.6); |
| | | } |
| | | |
| | | &:hover { |
| | | background-color: rgba(78, 119, 248, 0.2) !important; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | bottom: 0px; |
| | | } |
| | | :deep(.el-scrollbar__wrap) { |
| | | height: 39px; |
| | | height: 34px !important; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .themeDark { |
| | | .tags-view-container { |
| | | height: 52px; |
| | | width: 100%; |
| | | background: #1a235d; |
| | | // border-bottom: 1px solid #d8dce5; |
| | | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04); |
| | | .tags-view-wrapper { |
| | | .tags-view-item { |
| | | display: inline-block; |
| | | position: relative; |
| | | cursor: pointer; |
| | | height: 32px; |
| | | line-height: 30px; |
| | | border: 1px solid #5278f5; |
| | | color: #fff; |
| | | // background: #3271eb; |
| | | padding: 0 12px; |
| | | font-size: 14px; |
| | | margin-left: 6px; |
| | | margin-top: 10px; |
| | | border-radius: 4px; |
| | | font-family: OPPOSans, OPPOSans; |
| | | &:first-of-type { |
| | | margin-left: 16px; |
| | | } |
| | | &:last-of-type { |
| | | margin-right: 15px; |
| | | } |
| | | &.active { |
| | | background-color: #4e77f8 !important; |
| | | color: #fff; |
| | | border-color: #4e77f8 !important; |
| | | &::before { |
| | | content: ""; |
| | | background: #fff; |
| | | display: inline-block; |
| | | width: 8px; |
| | | height: 8px; |
| | | border-radius: 50%; |
| | | position: relative; |
| | | margin-right: 5px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .contextmenu { |
| | | margin: 0; |
| | | .tags-view-container { |
| | | height: 34px; |
| | | width: calc(100% - 42px); |
| | | margin-top: 10px; |
| | | margin-left: 14px; |
| | | box-sizing: border-box; |
| | | // 添加水平内边距与app-main的内边距一致 |
| | | background: #0A3465; |
| | | |
| | | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04); |
| | | .tags-view-wrapper { |
| | | .tags-view-item { |
| | | display: inline-block; |
| | | position: relative; |
| | | cursor: pointer; |
| | | height: 26px; |
| | | line-height: 26px; |
| | | border: 1px solid #d8dce5; |
| | | color: #495060; |
| | | background: #fff; |
| | | z-index: 3000; |
| | | position: absolute; |
| | | list-style-type: none; |
| | | padding: 5px 0; |
| | | border-radius: 4px; |
| | | padding: 0 8px; |
| | | font-size: 12px; |
| | | font-weight: 400; |
| | | color: #333; |
| | | box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3); |
| | | li { |
| | | margin: 0; |
| | | padding: 7px 16px; |
| | | cursor: pointer; |
| | | &:hover { |
| | | background: #eee; |
| | | } |
| | | margin-left: 5px; |
| | | margin-top: 4px; |
| | | &:first-of-type { |
| | | margin-left: 5px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .themeLight { |
| | | .tags-view-container { |
| | | height: 52px; |
| | | width: 100%; |
| | | background: #fff; |
| | | // border-bottom: 1px solid #d8dce5; |
| | | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04); |
| | | .tags-view-wrapper { |
| | | .tags-view-item { |
| | | display: inline-block; |
| | | position: relative; |
| | | cursor: pointer; |
| | | height: 32px; |
| | | line-height: 30px; |
| | | border: 1px solid #d8dce5; |
| | | color: #495060; |
| | | background: #fff; |
| | | padding: 0 12px; |
| | | font-size: 14px; |
| | | margin-left: 6px; |
| | | margin-top: 10px; |
| | | border-radius: 4px; |
| | | font-family: OPPOSans, OPPOSans; |
| | | &:first-of-type { |
| | | margin-left: 16px; |
| | | } |
| | | &:last-of-type { |
| | | margin-right: 15px; |
| | | } |
| | | &.active { |
| | | background-color: #42b983; |
| | | color: #fff; |
| | | border-color: #42b983; |
| | | &::before { |
| | | content: ""; |
| | | background: #fff; |
| | | display: inline-block; |
| | | width: 8px; |
| | | height: 8px; |
| | | border-radius: 50%; |
| | | position: relative; |
| | | margin-right: 5px; |
| | | } |
| | | &:last-of-type { |
| | | margin-right: 5px; |
| | | } |
| | | &.active { |
| | | background-color: #42b983; |
| | | color: #fff; |
| | | border-color: #42b983; |
| | | &::before { |
| | | content: ""; |
| | | background: #fff; |
| | | display: inline-block; |
| | | width: 8px; |
| | | height: 8px; |
| | | border-radius: 50%; |
| | | position: relative; |
| | | margin-right: 2px; |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | .scroll-container .el-scrollbar__wrap { |
| | | height: 50px !important; |
| | | height: 34px !important; |
| | | } |
| | | </style> |
| | |
| | | |
| | | // 监听路由变化,处理首页的侧边栏显示 |
| | | watchEffect(() => { |
| | | // 检查是否是首页路由 |
| | | if (route.path === '/index' || route.path === '/') { |
| | | // 检查是否是首页路由,但排除/index/index子路由 |
| | | if ((route.path === '/index' || route.path === '/') && route.path !== '/index/index') { |
| | | // 首页路由,确保侧边栏不隐藏,但状态是折叠的 |
| | | appStore.showCollapsedSidebar() |
| | | appStore.toggleSideBarHide(false) // 改为不隐藏侧边栏 |
| | | } else if (route.meta && route.meta.showSidebar === false) { |
| | | // 如果路由明确指定隐藏侧边栏 |
| | | appStore.toggleSideBarHide(true) |
| | | } else { |
| | | // 其他路由,确保侧边栏可见 |
| | | appStore.toggleSideBarHide(false) |
| | | } |
| | | }) |
| | | |
| | | // 组件挂载时,确保首页侧边栏状态正确 |
| | | onMounted(() => { |
| | | // 如果当前是首页,确保侧边栏是折叠的而不是隐藏的 |
| | | if (route.path === '/index' || route.path === '/') { |
| | | appStore.showCollapsedSidebar() |
| | | // 如果当前是首页子页面,只确保侧边栏不被隐藏,但保持折叠/展开状态不变 |
| | | if (route.path === '/index/index') { |
| | | // 只设置不隐藏侧边栏,但不改变其展开/折叠状态 |
| | | appStore.toggleSideBarHide(false) |
| | | // 不再强制设置opened为true,保持用户之前的设置 |
| | | } |
| | | }) |
| | | |
| | |
| | | top: 60px; |
| | | right: 0; |
| | | z-index: 9; |
| | | width: 100%; |
| | | width: calc(100% - #{$base-sidebar-width}); |
| | | transition: width 0.28s; |
| | | display: flex; |
| | | flex-direction: column; |
| | | box-sizing: border-box; |
| | | padding: 0; |
| | | } |
| | | |
| | | .hideSidebar .fixed-header { |
| | | width: calc(100% - 54px); |
| | | } |
| | | |
| | | .sidebarHide .fixed-header { |
| | | width: 100%; |
| | | } |
| | | |
| | |
| | | |
| | | const whiteList = ['/login', '/register', '/energy'] |
| | | |
| | | /** |
| | | * 查找最深层的子菜单并构建完整路径 |
| | | */ |
| | | function findDeepestPath(route) { |
| | | if (!route) return { path: null, query: null }; |
| | | |
| | | // 首先添加当前节点的路径 |
| | | let currentNode = route; |
| | | let pathSegments = []; |
| | | |
| | | if (currentNode.path) { |
| | | pathSegments.push(currentNode.path); |
| | | } |
| | | |
| | | // 逐层添加子路径 |
| | | while (currentNode.children && currentNode.children.length > 0) { |
| | | const firstChild = currentNode.children.find(child => !child.hidden); |
| | | if (!firstChild) break; |
| | | |
| | | // 跳过ParentView类型的中间节点 |
| | | if (firstChild.component === 'ParentView' || |
| | | (typeof firstChild.component === 'object' && firstChild.component.name === 'ParentView')) { |
| | | currentNode = firstChild; |
| | | pathSegments.push(firstChild.path); |
| | | continue; |
| | | } |
| | | |
| | | // 普通节点处理 |
| | | currentNode = firstChild; |
| | | // 如果路径不是以/开头,则添加到路径片段中 |
| | | if (!firstChild.path.startsWith('/')) { |
| | | pathSegments.push(firstChild.path); |
| | | } else { |
| | | // 如果是绝对路径,则替换之前所有路径 |
| | | pathSegments = [firstChild.path]; |
| | | } |
| | | |
| | | // 如果到达叶子节点,则结束查找 |
| | | if (!firstChild.children || firstChild.children.length === 0) { |
| | | break; |
| | | } |
| | | } |
| | | |
| | | // 构建最终路径 |
| | | let targetPath = ''; |
| | | if (pathSegments.length > 0) { |
| | | // 如果第一段不是以/开头,添加/ |
| | | if (!pathSegments[0].startsWith('/')) { |
| | | pathSegments[0] = '/' + pathSegments[0]; |
| | | } |
| | | |
| | | // 组合路径 |
| | | targetPath = pathSegments.reduce((fullPath, segment, index) => { |
| | | if (segment.startsWith('/')) { |
| | | return segment; |
| | | } else if (index === 0) { |
| | | return segment; |
| | | } else { |
| | | return `${fullPath}/${segment}`; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | return { |
| | | path: targetPath, |
| | | query: currentNode.query |
| | | }; |
| | | } |
| | | |
| | | router.beforeEach((to, from, next) => { |
| | | NProgress.start() |
| | | if (getToken()) { |
| | |
| | | if (topMenus.length > 0) { |
| | | // 跳转到第一个菜单 |
| | | const firstMenu = topMenus[0] |
| | | if (firstMenu.children && firstMenu.children.length > 0) { |
| | | // 有子菜单,跳转到第一个子菜单 |
| | | |
| | | // 查找最深层的子菜单并构建路径 |
| | | const { path, query } = findDeepestPath(firstMenu); |
| | | |
| | | if (path) { |
| | | // 有最深层子菜单,跳转到该菜单 |
| | | if (query) { |
| | | next({ path, query, replace: true }); |
| | | } else { |
| | | next({ path, replace: true }); |
| | | } |
| | | return; |
| | | } else if (firstMenu.children && firstMenu.children.length > 0) { |
| | | // 使用原有逻辑 |
| | | const firstChild = firstMenu.children[0] |
| | | const path = firstMenu.path.endsWith('/') ? firstMenu.path + firstChild.path : `${firstMenu.path}/${firstChild.path}` |
| | | next({ path: path, replace: true }) |
| | | const childPath = firstMenu.path.endsWith('/') ? firstMenu.path + firstChild.path : `${firstMenu.path}/${firstChild.path}` |
| | | next({ path: childPath, replace: true }) |
| | | return |
| | | } else { |
| | | // 没有子菜单,直接跳转 |
| | |
| | | if (topMenus.length > 0) { |
| | | // 跳转到第一个菜单 |
| | | const firstMenu = topMenus[0] |
| | | if (firstMenu.children && firstMenu.children.length > 0) { |
| | | // 有子菜单,跳转到第一个子菜单 |
| | | |
| | | // 查找最深层的子菜单并构建路径 |
| | | const { path, query } = findDeepestPath(firstMenu); |
| | | |
| | | if (path) { |
| | | // 有最深层子菜单,跳转到该菜单 |
| | | if (query) { |
| | | next({ path, query, replace: true }); |
| | | } else { |
| | | next({ path, replace: true }); |
| | | } |
| | | return; |
| | | } else if (firstMenu.children && firstMenu.children.length > 0) { |
| | | // 使用原有逻辑 |
| | | const firstChild = firstMenu.children[0] |
| | | const path = firstMenu.path.endsWith('/') ? firstMenu.path + firstChild.path : `${firstMenu.path}/${firstChild.path}` |
| | | next({ path: path, replace: true }) |
| | | const childPath = firstMenu.path.endsWith('/') ? firstMenu.path + firstChild.path : `${firstMenu.path}/${firstChild.path}` |
| | | next({ path: childPath, replace: true }) |
| | | return |
| | | } else { |
| | | // 没有子菜单,直接跳转 |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 自动处理带有重定向的路由 |
| | | if (to.matched.length > 0 && to.matched[0].path === to.path) { |
| | | const currentRouteConfig = router.getRoutes().find(r => r.path === to.path); |
| | | |
| | | if (currentRouteConfig && currentRouteConfig.children && currentRouteConfig.children.length > 0) { |
| | | // 有子路由,自动导航到最深层子菜单 |
| | | const { path, query } = findDeepestPath(currentRouteConfig); |
| | | |
| | | if (path && path !== to.path) { |
| | | if (query) { |
| | | next({ path, query, replace: true }); |
| | | } else { |
| | | next({ path, replace: true }); |
| | | } |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | |
| | | next() |
| | | } |
| | | } |
| | |
| | | path: '/index', |
| | | component: () => import('@/views/index'), |
| | | name: 'Index', |
| | | meta: { title: '首页', icon: 'dashboard', affix: true, showSidebar: true, breadcrumb: false }, |
| | | beforeEnter: (to, from, next) => { |
| | | // 获取app store并设置侧边栏为折叠状态 |
| | | const appStore = useAppStore() |
| | | appStore.showCollapsedSidebar() |
| | | next() |
| | | } |
| | | meta: { title: '首页', icon: 'dashboard', affix: true, showSidebar: true, breadcrumb: false } |
| | | } |
| | | ] |
| | | }, |
| | |
| | | }) |
| | | |
| | | // 遍历后台传来的路由字符串,转换为组件对象 |
| | | function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { |
| | | function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false, parentRoute = null) { |
| | | return asyncRouterMap.filter(route => { |
| | | // 不再过滤掉首页看板相关路由 |
| | | /* |
| | | // 过滤掉首页看板相关路由 |
| | | if (route.name === 'Index' && route.meta && route.meta.title === '首页看板') { |
| | | return false; |
| | | } |
| | | |
| | | // 如果是首页看板的子菜单,也过滤掉 |
| | | if (route.path === '/index' || route.path === 'index' || |
| | | (route.meta && route.meta.title === '首页看板')) { |
| | | return false; |
| | | } |
| | | */ |
| | | |
| | | // 设置父路由引用 |
| | | if (parentRoute) { |
| | | route.parent = parentRoute; |
| | | } |
| | | |
| | | if (type && route.children) { |
| | | route.children = filterChildren(route.children) |
| | | } |
| | |
| | | } |
| | | } |
| | | if (route.children != null && route.children && route.children.length) { |
| | | route.children = filterAsyncRouter(route.children, route, type) |
| | | // 将当前路由作为父路由传递给子路由 |
| | | route.children = filterAsyncRouter(route.children, route, type, route) |
| | | } else { |
| | | delete route['children'] |
| | | delete route['redirect'] |
| | |
| | | if (el.children && el.children.length) { |
| | | if (el.component === 'ParentView' && !lastRouter) { |
| | | el.children.forEach(c => { |
| | | // 设置父路由引用 |
| | | c.parent = el; |
| | | |
| | | c.path = el.path + '/' + c.path |
| | | if (c.children && c.children.length) { |
| | | children = children.concat(filterChildren(c.children, c)) |
| | |
| | | } |
| | | } |
| | | if (lastRouter) { |
| | | // 设置父路由引用 |
| | | el.parent = lastRouter; |
| | | |
| | | el.path = lastRouter.path + '/' + el.path |
| | | if (el.children && el.children.length) { |
| | | children = children.concat(filterChildren(el.children, el)) |