feat(layout): 菜单重构布局并调整样式

- 修改了 AppMain组件的最小高度和样式
- 更新了 Sidebar 组件的背景色和内边距
-调整了 Logo 组件的宽度
- 重构了 layout 目录的结构
- 优化了路由配置
This commit is contained in:
du.meimei
2025-04-22 20:39:54 +08:00
parent 5637496340
commit 57300be56f
12 changed files with 460 additions and 48 deletions

View File

@@ -349,7 +349,8 @@ h3 {
#app .sidebar-container {
transition: width 0.28s;
width: 210px !important;
background-color: #304156;
background-color: #f0f4fa;
padding: 10px;
height: 100%;
position: fixed;
font-size: 0px;

View File

@@ -1,8 +1,7 @@
#app {
.main-container {
min-height: 100%;
transition: margin-left .28s;
transition: margin-left 0.28s;
margin-left: $sideBarWidth;
position: relative;
}
@@ -10,10 +9,11 @@
.sidebar-container {
transition: width 0.28s;
width: $sideBarWidth !important;
background-color: $menuBg;
background-color: #f0f4fa;
padding: 10px;
height: 100%;
position: fixed;
font-size: 0px;
font-size: 12px;
top: 0;
bottom: 0;
left: 0;
@@ -71,11 +71,11 @@
}
}
.is-active>.el-submenu__title {
.is-active > .el-submenu__title {
color: $subMenuActiveText !important;
}
& .nest-menu .el-submenu>.el-submenu__title,
& .nest-menu .el-submenu > .el-submenu__title,
& .el-submenu .el-menu-item {
min-width: $sideBarWidth !important;
background-color: $subMenuBg !important;
@@ -111,7 +111,7 @@
.el-submenu {
overflow: hidden;
&>.el-submenu__title {
& > .el-submenu__title {
padding: 0 !important;
.svg-icon {
@@ -126,8 +126,8 @@
.el-menu--collapse {
.el-submenu {
&>.el-submenu__title {
&>span {
& > .el-submenu__title {
& > span {
height: 0;
width: 0;
overflow: hidden;
@@ -150,7 +150,7 @@
}
.sidebar-container {
transition: transform .28s;
transition: transform 0.28s;
width: $sideBarWidth !important;
}
@@ -164,7 +164,6 @@
}
.withoutAnimation {
.main-container,
.sidebar-container {
transition: none;
@@ -174,13 +173,13 @@
// when menu collapsed
.el-menu--vertical {
&>.el-menu {
& > .el-menu {
.svg-icon {
margin-right: 16px;
}
}
.nest-menu .el-submenu>.el-submenu__title,
.nest-menu .el-submenu > .el-submenu__title,
.el-menu-item {
&:hover {
// you can use $subMenuHover
@@ -189,7 +188,7 @@
}
// the scroll bar appears when the subMenu is too long
>.el-menu--popup {
> .el-menu--popup {
max-height: 100vh;
overflow-y: auto;

View File

@@ -18,24 +18,7 @@ $width-list: 5 8 10 20 30 40 50 60 65 70 80 86 100 110 120 140 150 155 160 180 1
// 内外边距列表
$distance-list: 0 1 2 5 6 8 9 10 12 15 20 25 30 35 40 45 50 60 80 86 90 100 120 140;
$distance-class-list: m,
mv,
mh,
mt,
ml,
mr,
mb,
p,
pv,
ph,
pt,
pl,
pr,
pb,
top,
left,
right,
bottom;
$distance-class-list: m, mv, mh, mt, ml, mr, mb, p, pv, ph, pt, pl, pr, pb, top, left, right, bottom;
//圆角弧度
$radius: 1 2 3 4 5 6 7 8 9 10 12 13 14 15 18 20 50 100;
@@ -54,7 +37,7 @@ $menuHover: #263445;
$subMenuBg: #1f2d3d;
$subMenuHover: #001528;
$sideBarWidth: 210px;
$sideBarWidth: 270px;
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass

View File

@@ -1,4 +1,4 @@
import layout from '@/views/app/layout/index.vue'
import layout from '@/views/app/layout/layout.vue'
import redirect from '@/views/app/redirect/index.vue'
export default [
@@ -135,7 +135,7 @@ export default [
}
}
]
},
}
]
},
{

View File

@@ -20,13 +20,13 @@ export default {
<style scoped>
.app-main {
/*50 = navbar */
min-height: calc(100vh - 50px);
min-height: calc(100vh - 250px);
width: 100%;
position: relative;
overflow: hidden;
}
.fixed-header + .app-main {
padding-top: 50px;
//padding-top: 50px;
}
</style>

View File

@@ -44,7 +44,7 @@ export default {
.sidebar-logo-container {
position: relative;
width: 100%;
width: 60px;
height: 60px;
line-height: 50px;
/*background: #2b2f3a;*/

View File

@@ -0,0 +1,335 @@
<template>
<div :class="{ 'has-logo': showLogo }" class="sidebar-container">
<div class="sidebar-layout">
<!-- Left side - Primary menu -->
<div class="primary-menu">
<logo class="logo" v-if="showLogo" :collapse="isCollapse" />
<div
v-for="route in menuList"
:key="route.path"
class="primary-menu-item"
:class="{ active: activeParent === route.path }"
@click="selectParentMenu(route)"
>
<svg t="1745323232253" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4148" width="24" height="24">
<path
d="M523.73504 319.29344h-204.8c-16.896 0-30.72-13.824-30.72-30.72s13.824-30.72 30.72-30.72h204.8c16.896 0 30.72 13.824 30.72 30.72s-13.824 30.72-30.72 30.72zM605.65504 452.41344h-286.72c-16.896 0-30.72-13.824-30.72-30.72s13.824-30.72 30.72-30.72h286.72c16.896 0 30.72 13.824 30.72 30.72s-13.824 30.72-30.72 30.72z"
fill="#FFFFFF"
p-id="4149"
></path>
<path
d="M706.56 870.4H317.44c-67.584 0-122.88-55.296-122.88-122.88V276.48c0-67.584 55.296-122.88 122.88-122.88h389.12c67.584 0 122.88 55.296 122.88 122.88v471.04c0 67.584-55.296 122.88-122.88 122.88z"
fill="#3889FF"
p-id="4150"
></path>
<path
d="M358.4 153.6h61.44v716.8h-61.44zM545.20832 153.6h61.44v215.04h-61.44zM653.81376 153.6h61.44v215.04h-61.44z"
fill="#FFFFFF"
p-id="4151"
></path>
<path
d="M673.67936 312.32l-21.71904-21.71904a30.72 30.72 0 0 0-43.44832 0L586.78272 312.32l-32.58368 32.58368a30.72 30.72 0 1 0 43.44832 43.44832l10.86464-10.86464a30.72 30.72 0 0 1 43.44832 0l10.86464 10.86464a30.72 30.72 0 1 0 43.44832-43.44832L673.67936 312.32z"
fill="#FFFFFF"
p-id="4152"
></path>
</svg>
<!-- <svg-icon v-if="route.meta && route.meta.icon" :icon-class="route.meta.icon" />-->
<span class="menu-title">{{ route.meta ? route.meta.title : '' }}</span>
</div>
</div>
<!-- Right side - Submenu -->
<div class="submenu-container" v-if="activeParent && currentSubmenu.length">
<!-- <div class="submenu-header">-->
<!-- <span>{{ currentParentTitle }}</span>-->
<!-- </div>-->
<el-scrollbar wrap-class="scrollbar-wrapper">
<div class="submenu-list">
<pre style="font-size: 12px; padding: 10px; display: none;">{{ JSON.stringify(currentSubmenu, null, 2) }}</pre>
<div
v-for="subItem in currentSubmenu"
:key="subItem.path"
class="submenu-item"
:class="{ active: activeMenu === subItem.path }"
@click="navigateTo(subItem.path)"
>
<!-- <svg-icon v-if="subItem.meta && subItem.meta.icon" :icon-class="subItem.meta.icon" />-->
<span>{{ subItem.meta ? subItem.meta.title : subItem.name || 'Unnamed Item' }}</span>
</div>
</div>
</el-scrollbar>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import Logo from './Logo'
import variables from '@/assets/sass/variables.scss'
export default {
components: { Logo },
computed: {
...mapGetters(['sidebar', 'sidebarList']),
routes() {
return this.$router.options.routes
},
activeMenu() {
const route = this.$route
const { meta, path } = route
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
showLogo() {
return this.$store.state.settings.sidebarLogo
},
variables() {
return variables
},
isCollapse() {
return !this.sidebar.opened
},
currentSubmenu() {
if (!this.activeParent) return []
const parent = this.menuList.find(item => item.path === this.activeParent)
console.log('Parent found:', parent)
console.log('Children:', parent ? parent.children : 'No children')
return parent ? parent.children || [] : []
},
currentParentTitle() {
if (!this.activeParent) return ''
const parent = this.menuList.find(item => item.path === this.activeParent)
return parent && parent.meta ? parent.meta.title : ''
}
},
data() {
return {
menuList: [],
activeParent: null
}
},
methods: {
...mapActions({
setSidebarList: 'app/setSidebarList'
}),
formatList(menu, state, parentPath = null) {
menu.map(item => {
if (parentPath) {
item.path = '/' + parentPath + '/' + item.url
} else {
item.path = item.url
}
if (item.otherInfo1 == 0) {
item.name = item.menuName
item.alwaysShow = state
item.meta = {
title: item.menuName,
icon: item.img
}
if (item.children != null) {
this.formatList(item.children, undefined, item.path)
} else {
item.children = []
}
} else {
item.children = []
}
})
return menu
},
selectParentMenu(route) {
this.activeParent = route.path
// If this parent has children, don't navigate
if (route.children && route.children.length > 0) {
// Optionally navigate to the first child
// this.navigateTo(route.children[0].path)
} else {
// If no children, navigate to the parent route
this.navigateTo(route.path)
}
},
navigateTo(path) {
if (path && path !== this.$route.path) {
this.$router.push(path)
}
},
findParentForCurrentRoute() {
const currentPath = this.$route.path
// Try to find the parent menu that contains the current route
for (const parent of this.menuList) {
// Check if current route is the parent itself
if (parent.path === currentPath) {
this.activeParent = parent.path
return
}
// Check if current route is one of the children
if (parent.children && parent.children.length) {
const childMatch = parent.children.find(child => child.path === currentPath || currentPath.startsWith(child.path + '/'))
if (childMatch) {
this.activeParent = parent.path
return
}
}
}
// If no match found, default to first menu item if available
if (this.menuList.length > 0 && !this.activeParent) {
this.activeParent = this.menuList[0].path
}
}
},
created() {
if (sessionStorage.token !== 'MockToken') {
// 获取路由数据
let data = this.$store.state.app.sidebarList
// 只获取顶级路由,不需要硬编码路径
// 通过检查路由结构来确定顶级菜单
const topLevelRoutes = data.filter(route => {
// 如果路由有 meta 和 children则认为它是顶级菜单
return route.meta && route.path && !route.hidden
})
this.menuList = topLevelRoutes
} else {
// 从路由配置中获取顶级路由
const topLevelRoutes = this.routes.filter(route => {
// 只显示有 meta 和 children 的路由,并且不是隐藏的
return route.meta && route.children && !route.hidden
})
this.menuList = topLevelRoutes
}
// 根据当前路由设置活动的父菜单
this.$nextTick(() => {
this.findParentForCurrentRoute()
})
},
watch: {
$route() {
// Update active parent when route changes
this.findParentForCurrentRoute()
}
}
}
</script>
<style scoped lang="scss">
.sidebar-container {
height: 100%;
background-color: #fff;
border-right: 1px solid #e6e6e6;
overflow: hidden;
display: flex;
flex-direction: column;
}
.sidebar-layout {
display: flex;
flex: 1;
overflow: hidden;
background-color: #fff;
border-radius: 8px;
}
.primary-menu {
width: 95px;
height: 100%;
border-right: 1px solid #ebeef2;
overflow-y: auto;
background-color: #fff;
padding: 0 10px;
text-align: center;
.logo {
margin-bottom: 90px;
}
}
.primary-menu-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 58px;
width: 68px;
padding: 10px 0;
cursor: pointer;
color: #606266;
margin-top: 10px;
&:hover,
&.active {
color: #000;
font-weight: bold;
background-color: #f8f8fa;
}
.svg-icon {
font-size: 24px;
margin-bottom: 8px;
}
.menu-title {
font-size: 14px;
text-align: center;
}
}
.submenu-container {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
padding: 23px 10px;
}
.submenu-header {
height: 50px;
line-height: 50px;
padding: 0 20px;
font-size: 16px;
font-weight: bold;
border-bottom: 1px solid #f0f0f0;
}
.submenu-list {
padding: 10px 0;
}
.submenu-item {
height: 40px;
line-height: 40px;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #000;
margin-bottom: 5px;
border-radius: 4px;
font-size: 14px;
&:hover,
&.active {
color: #000;
font-weight: bold;
background-color: #f8f8fa;
}
.svg-icon {
margin-right: 10px;
}
}
.scrollbar-wrapper {
height: 100%;
}
</style>

View File

@@ -1,3 +1,3 @@
export { default as Navbar } from './Navbar'
export { default as Sidebar } from './Sidebar'
export { default as Sidebar } from './Sidebar/Sidebar'
export { default as AppMain } from './AppMain'

View File

@@ -0,0 +1,97 @@
<template>
<div class="app-wrapper">
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
<sidebar class="sidebar-container" />
<div class="main-container">
<app-main />
</div>
<!--<app-main v-else />-->
</div>
</template>
<script>
import { AppMain } from './components'
import Sidebar from './components/Sidebar/Sidebar.vue'
import ResizeMixin from './mixin/ResizeHandler'
export default {
name: 'Layout',
components: {
// Navbar,
Sidebar,
AppMain
},
data() {
return {
type: process.env.VUE_APP_FLAG
}
},
mixins: [ResizeMixin],
computed: {
sidebar() {
return this.$store.state.app.sidebar
},
device() {
return this.$store.state.app.device
},
fixedHeader() {
return this.$store.state.settings.fixedHeader
},
classObj() {
return {
hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === 'mobile'
}
}
},
methods: {
handleClickOutside() {
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
}
}
}
</script>
<style lang="scss" scoped>
@import '~@/assets/sass/mixin.scss';
@import '~@/assets/sass/variables.scss';
.app-wrapper {
@include clearfix;
position: relative;
height: 100%;
width: 100%;
&.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}
.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$sideBarWidth});
transition: width 0.28s;
}
.hideSidebar .fixed-header {
width: calc(100% - 54px);
}
.mobile .fixed-header {
width: 100%;
}
</style>

View File

@@ -1,5 +1,4 @@
<script>
export default {
name: 'dify',
data() {
@@ -13,9 +12,7 @@ export default {
</script>
<template>
<div class="container">
</div>
<div class="container"></div>
</template>
<style lang="scss" scoped></style>
<style lang="scss" scoped></style>

View File

@@ -74,4 +74,4 @@ export default {
<el-empty v-else />
</el-card>
</div>
</template>
</template>

View File

@@ -38,7 +38,7 @@ module.exports = {
target: 'http://localhost:3000',
changeOrigin: true,
onProxyRes: (proxyRes, req, res) => {
delete proxyRes.headers['x-frame-options'];
delete proxyRes.headers['x-frame-options']
},
pathRewrite: {
// '^/app': '/'
@@ -49,7 +49,7 @@ module.exports = {
target: 'http://localhost:3000',
changeOrigin: true,
onProxyRes: (proxyRes, req, res) => {
delete proxyRes.headers['x-frame-options'];
delete proxyRes.headers['x-frame-options']
},
pathRewrite: {
// '^/_next': '/'