fix:完善相关页面

This commit is contained in:
liangdong
2025-12-30 19:08:31 +08:00
parent fa4de6f71f
commit bfb6a1e500
22 changed files with 1886 additions and 23 deletions

View File

@@ -1,34 +1,223 @@
<template>
<div class="mj-layout">
<el-container>
<el-aside :width> 左侧菜单模块 </el-aside>
<el-aside :width="width">
<div class="mj-aside-content">
<!-- 顶部company公司标记 -->
<div class="mj-aside-company"></div>
<div class="mj-aside-menu">
<div class="mj-aside-title" v-show="!isCollapse">{{ topTitle }}</div>
<mjMenus class="mj-aside_menu" :isCollapse="isCollapse" :menuList="sideMenuList" :active-menu="selectedActiveMenu"
@menu-select="handleSideMenuSelect" />
</div>
<!-- 展开收缩左侧菜单按钮 -->
<div class="mj-collapse" @click="showCollapse"></div>
</div>
</el-aside>
<el-container>
<el-header>头部模块</el-header>
<el-header class="mj-header-content">
<mjMenus :menuList="topLevelMenuList" mode="horizontal" :active-menu="selectedTopMenu"
@menu-select="handleTopMenuSelect" />
</el-header>
<el-main>
内容区域模块
<!-- <card-item :list="[1,2,3,4,5,6]"/> -->
<router-view />
</el-main>
</el-container>
</el-container>
</div>
</template>
<script setup lang="ts">
import mjMenus from '@/components/standardMenu/index.vue';
import { useUserStore } from '@/store'
defineOptions({
name: "Layout",
});
const isCollapse = ref(false);
const userStore = useUserStore()
// 响应式断点(小屏阈值,小于此值视为小屏)
const BREAKPOINT = 1024;
// 屏幕宽度
const screenWidth = ref(window.innerWidth);
// 菜单收缩状态:完全根据屏幕大小自动判断
const isCollapse = computed(() => {
return screenWidth.value < BREAKPOINT;
});
const width = computed(() => {
return isCollapse.value ? "60px" : "240px";
return isCollapse.value ? "80px" : "224px";
});
// 监听窗口大小变化
const handleResize = () => {
screenWidth.value = window.innerWidth;
// isCollapse 是计算属性,会自动响应 screenWidth 的变化
};
// 展开收缩菜单(临时切换,窗口大小变化时会自动恢复)
const showCollapse = () => {
if (isCollapse.value) {
screenWidth.value = BREAKPOINT + 1;
} else {
screenWidth.value = BREAKPOINT - 1;
}
};
// 返回菜单数据
const menuList = computed(() => {
return userStore.routes || []
});
// 获取一级菜单数据(不包含 children
const topLevelMenuList = computed(() => {
return menuList.value.map(item => {
const { children, ...rest } = item
return rest
})
})
const topTitle = computed(() => {
return topLevelMenuList.value.find(path => path.path === selectedTopMenu.value)?.meta?.title || '-'
})
// 当前选中的顶部菜单
const selectedTopMenu = ref<string>('')
const selectedActiveMenu = ref<string>('');
// 根据选中的顶部菜单,获取左侧菜单列表
const sideMenuList = computed(() => {
if (!selectedTopMenu.value) {
// 默认选中第一个有 children 的菜单
const firstMenuWithChildren = menuList.value.find(item => item.children && item.children.length > 0)
if (firstMenuWithChildren) {
return (firstMenuWithChildren.children || []).map(child=>{
const fullPath = child.path.startsWith('/')
? child.path
: `${firstMenuWithChildren.path}/${child.path}`
return {
...child,
path: fullPath
}
})
}
return []
}
// 根据选中的顶部菜单路径,找到对应的菜单项
const selectedMenu = menuList.value.find(item => item.path === selectedTopMenu.value)
if (selectedMenu && selectedMenu.children) {
return selectedMenu.children.map(child=>{
const fullPath = child.path.startsWith('/')
? child.path
: `${selectedMenu.path}/${child.path}`
return {
...child,
path: fullPath
}
})
}
return []
})
// 处理顶部菜单选中事件
const handleTopMenuSelect = (menuPath: string) => {
selectedTopMenu.value = menuPath
}
// 左侧菜单选中事件
const handleSideMenuSelect = (menuPath: string) => {
selectedActiveMenu.value = menuPath;
}
// 初始化:默认选中第一个菜单
onMounted(() => {
if (topLevelMenuList.value.length > 0) {
const firstMenu = topLevelMenuList.value[0]
if (firstMenu && firstMenu.path) {
selectedTopMenu.value = firstMenu.path
}
}
// 监听窗口大小变化
window.addEventListener('resize', handleResize);
// 初始化时根据屏幕大小设置菜单状态
handleResize();
});
// 组件卸载时移除监听
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
</script>
<style lang="scss" scoped>
.mj-layout {
height: inherit;
:deep(.el-container) {
height: inherit;
}
:deep(.el-aside) {
transition: width 0.3s;
overflow: hidden;
}
:deep(.el-main) {
--el-main-padding:16px;
background-color: #f8fafc;
}
.mj-aside-content {
position: relative;
height: 100%;
display: flex;
flex-direction: column;
border-right: 1px solid var(--mj-border-color);
.mj-aside-company {
height: var(--mj-menu-header-height);
border-bottom: 1px solid var(--mj-border-color);
flex-shrink: 0;
}
.mj-aside-menu {
flex: 1;
overflow: hidden;
.mj-aside-title {
font-size: 10px;
color: #888;
padding: 10px var(--el-menu-base-level-padding);
transition: opacity 0.3s;
}
}
.mj-collapse {
width: 24px;
height: 24px;
border-radius: 50%;
position: absolute;
right: 0;
top: calc(var(--mj-menu-header-height)/2 - 10px);
cursor: pointer;
z-index: 10;
box-sizing: border-box;
box-shadow: 0 1px 6px #0000001f;
}
}
.mj-header-content {
--el-header-padding: 0;
border-bottom: 1px solid var(--mj-border-color);
}
}
</style>