286 lines
7.3 KiB
Vue
286 lines
7.3 KiB
Vue
<template>
|
||
<div class="mj-layout">
|
||
<el-container>
|
||
<el-aside :width="width">
|
||
<div class="mj-aside-content">
|
||
<!-- 顶部company公司标记 -->
|
||
<div class="mj-aside-logo">
|
||
<img :src="companyLogo" class="mj-company-logo" />
|
||
</div>
|
||
<div class="mj-aside-menu">
|
||
<div class="mj-aside-title" v-show="!isCollapse">
|
||
{{ topTitle }}
|
||
</div>
|
||
<standardMenu
|
||
class="mj-aside_menu"
|
||
:isCollapse="isCollapse"
|
||
:menuList="sideMenuList"
|
||
:active-menu="selectedActiveMenu"
|
||
@menu-select="handleSideMenuSelect"
|
||
/>
|
||
</div>
|
||
<!-- 展开收缩左侧菜单按钮 -->
|
||
<div class="mj-collapse" @click="showCollapse">
|
||
<el-icon><component :is="isCollapse ? 'DArrowRight' : 'DArrowLeft'" /></el-icon>
|
||
</div>
|
||
</div>
|
||
</el-aside>
|
||
<el-container>
|
||
<el-header class="mj-header-content">
|
||
<!-- 左侧的菜单展示 -->
|
||
<standardMenu
|
||
:menuList="topLevelMenuList"
|
||
mode="horizontal"
|
||
:active-menu="selectedTopMenu"
|
||
@menu-select="handleTopMenuSelect"
|
||
/>
|
||
<!-- 右侧用户的内容 -->
|
||
<rightMenuGroup @on-stage-manage="onStageManage" />
|
||
</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 standardMenu from "@/components/standardMenu/index.vue";
|
||
import { useUserStore } from "@/store";
|
||
import rightMenuGroup from './rightMenuGroup.vue';
|
||
import companyLogo from '@/assets/images/logo.png';
|
||
defineOptions({
|
||
name: "Layout",
|
||
});
|
||
|
||
const userStore = useUserStore();
|
||
const router = useRouter();
|
||
|
||
// 响应式断点(小屏阈值,小于此值视为小屏)
|
||
const BREAKPOINT = 1024;
|
||
|
||
// 屏幕宽度
|
||
const screenWidth = ref(window.innerWidth);
|
||
|
||
// 菜单收缩状态:完全根据屏幕大小自动判断
|
||
const isCollapse = computed(() => {
|
||
return screenWidth.value < BREAKPOINT;
|
||
});
|
||
|
||
const width = computed(() => {
|
||
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;
|
||
}).filter(itv=>itv.name !== 'stage');
|
||
});
|
||
|
||
// 获取是管理后台中心的数据内容
|
||
const backTitle = computed(()=>{
|
||
return menuList.value.find(itv=>itv.name === 'stage')?.meta?.title || '-';
|
||
})
|
||
|
||
// 后台管理点击获取列表
|
||
const onStageManage = () =>{
|
||
selectedTopMenu.value = '/stage';
|
||
}
|
||
|
||
const topTitle = computed(() => {
|
||
return (
|
||
topLevelMenuList.value.find((path) => path.path === selectedTopMenu.value)
|
||
?.meta?.title || backTitle.value
|
||
);
|
||
});
|
||
|
||
// 当前选中的顶部菜单
|
||
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(() => {
|
||
|
||
const currentRoutePath = router.currentRoute.value.path;
|
||
const matchedTopMenu = topLevelMenuList.value.find(menu =>
|
||
currentRoutePath.startsWith(`${menu.path}/`) || currentRoutePath === menu.path
|
||
);
|
||
|
||
if (matchedTopMenu && matchedTopMenu.path) {
|
||
selectedTopMenu.value = matchedTopMenu.path;
|
||
} else 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-logo {
|
||
height: var(--mj-menu-header-height);
|
||
line-height: var(--mj-menu-header-height);
|
||
border-bottom: 1px solid var(--mj-border-color);
|
||
flex-shrink: 0;
|
||
.mj-company-logo{
|
||
width: 39px;
|
||
height: 32px;
|
||
display: inline-block;
|
||
vertical-align: middle;
|
||
margin-left: 20px;
|
||
}
|
||
}
|
||
|
||
.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;
|
||
line-height: 24px;
|
||
text-align: center;
|
||
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{
|
||
display: flex;
|
||
justify-content: space-between;
|
||
.mj-standard-menu{
|
||
flex: 1;
|
||
}
|
||
}
|
||
|
||
.mj-header-content {
|
||
--el-header-padding: 0;
|
||
border-bottom: 1px solid var(--mj-border-color);
|
||
}
|
||
}
|
||
</style>
|