vue3:十一、主页面布局(实现基本左侧菜单+右侧内容效果)
一、实现效果
二、初始搭建
1、官网参考
Container 布局容器 | Element Plus
实现如下的效果
首先实现左侧导航,右侧内容效果
基本框架代码
2、视图层
①基本框架
<template>
<el-container class="layout-container-demo" style="height: 100vh">
<el-aside width="200px">Aside</el-aside>
<el-container>
<el-header>Header</el-header>
<el-main>Main</el-main>
<el-footer>Footer</el-footer>
</el-container>
</el-container>
</template>
②Footer编写
底部一般是写入版权信息之类的信息
<!-- 底部信息 -->
<el-footer class="flex flex-center">
<span>@2025-2030 wen</span>
</el-footer>
③Main编写
Main是核心内容,是起到点击左侧菜单,将内容展示到右侧内容
这里使用<RouterView /> 调出路由中的定义的页面,由于右侧内容比较丰富,所以需要采用滚动条模式<el-scrollbar>...</el-scrollbar>
<!-- 右侧内容 -->
<el-main>
<el-scrollbar>
<RouterView />
</el-scrollbar>
</el-main>
④header编写
header主要是头信息,这里的内容可以参考官网给的例子,暂时直接复制
代码如下
<el-header style="text-align: right; font-size: 12px">
<div class="toolbar">
<el-dropdown>
<el-icon style="margin-right: 8px; margin-top: 1px">
<setting />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>View</el-dropdown-item>
<el-dropdown-item>Add</el-dropdown-item>
<el-dropdown-item>Delete</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span>Tom</span>
</div>
</el-header>
⑤aside编写
三级菜单示例
在侧边栏容器aside中
- 首先为内容区域添加滚动条
- 定义一个菜单容器
el-menu
,
设置默认菜单展开项1,2项目
:default-openeds="['1', '2']"
- 遍历一级标题,首先用
template去循环,可能涉及到不存在子项的菜单,就要用不同的标签。
template循环
<template v-for="(item, index) in menu" :key="index">:遍历menu
数组,item
是当前菜单项,index
是当前索引。:key="index"
: 为每个菜单项设置唯一的key
- 一级菜单含有二级子菜单:<el-sub-menu v-if="item.children && item.children.length > 0" :index="`${index + 1}`">:使用可展开的子菜单容器
el-sub-menu,判断如果存在子菜单是展示,并设置菜单项
索引从1开始
- 定义二级子菜单的标题:
<template #title> 一级菜单的标题部分
<el-icon v-if="item.icon"> 标题的图标定义标签
<component :is="item.icon" /> 图标引入,采用动态加载图标组件
</el-icon>
{{ item.name }} 标题的名称
</template>
- 遍历二级标题,仍然用
template去循环(存在三级菜单),
<template v-for="(secondmenu, secondindex) in item.children" :key="secondindex">:遍历二级菜单(也就是一级菜单的子项children),索引是secondindex- 二级菜单含有三级菜单:<el-sub-menu v-if="secondmenu.children && secondmenu.children.length > 0" :index="`${index + 1}-${secondindex + 1}`">,可展开的子菜单容器
el-sub-menu,设置菜单项的当前索引为"一级菜单索引-二级菜单索引"(例如1-1,1-2,2-1...)
- 定义二级子菜单的标题:
<template #title> 二级菜单的标题部分
{{ secondmenu.name }} 二级菜单名称
</template>
- 遍历三级标题,直接使用普通的菜单项el-menu-item(因为三级标题是最底层的标题,无需展开,所以使用普通的即可),<el-menu-item v-for="(thirdMenu, thirdIndex) in secondmenu.children" :key="thirdIndex" :index="`${index + 1}-${secondindex + 1}-${thirdIndex + 1}`" @click="handClick(thirdMenu.path)">,循环二级菜单下的子菜单secondmenu.children,索引为thirdIndex,当前索引为
"一级菜单索引-二级菜单索引-三级索引"(例如1-1-1,1-1-2,1-2-1...),使用单击事件进行页面跳转,跳转的内容为该项定义的path字段
三级菜单标题定义:
{{ thirdMenu.name }}
- 定义二级子菜单的标题:
- 二级菜单不含三级菜单:使用普通菜单项el-menu-item,<el-menu-item v-else :index="`${index + 1}-${secondindex + 1}`" @click=" handClick ( secondmenu .path)">
,不含子项就表明可以通过点击事件进入到对应的内容
二级菜单标题定义:
{{ secondmenu.name }}
- 二级菜单含有三级菜单:<el-sub-menu v-if="secondmenu.children && secondmenu.children.length > 0" :index="`${index + 1}-${secondindex + 1}`">,可展开的子菜单容器
- 定义二级子菜单的标题:
- 一级菜单不含二级菜单:使用普通菜单项el-menu-item,<el-menu-item v-else :index="`${index + 1}`" @click="handClick(item.path)">
,同理不含二级菜单的一级菜单就进行标题展示(这里是一级菜单,所以需要带图标)
一级菜单标题定义
<el-icon v-if="item.icon"> 标题图标标签
<component :is="item.icon" /> 图标展示
</el-icon>
{{ item.name }} 标题名称
- 一级菜单含有二级子菜单:<el-sub-menu v-if="item.children && item.children.length > 0" :index="`${index + 1}`">:使用可展开的子菜单容器
代码:
<template>
<el-container class="layout-container-demo" style="height: 100vh">
<el-aside width="200px">
<el-scrollbar>
<!-- default-openeds:默认展开菜单 -->
<el-menu :default-openeds="['1', '2']">
<!-- 遍历一级菜单 -->
<template v-for="(item, index) in menu" :key="index">
<!-- 如果一级菜单有子菜单,渲染 el-sub-menu -->
<el-sub-menu v-if="item.children && item.children.length > 0" :index="`${index + 1}`">
<template #title>
<el-icon v-if="item.icon">
<component :is="item.icon" />
</el-icon>
{{ item.name }}
</template>
<!-- 遍历二级菜单 -->
<template v-for="(secondmenu, secondindex) in item.children" :key="secondindex">
<!-- 如果二级菜单有子菜单,渲染 el-sub-menu -->
<el-sub-menu v-if="secondmenu.children && secondmenu.children.length > 0"
:index="`${index + 1}-${secondindex + 1}`">
<template #title>
{{ secondmenu.name }}
</template>
<!-- 遍历三级菜单 -->
<el-menu-item v-for="(thirdMenu, thirdIndex) in secondmenu.children" :key="thirdIndex"
:index="`${index + 1}-${secondindex + 1}-${thirdIndex + 1}`" @click="handClick(thirdMenu.path)">
{{ thirdMenu.name }}
</el-menu-item>
</el-sub-menu>
<!-- 如果二级菜单没有子菜单,渲染 el-menu-item -->
<el-menu-item v-else :index="`${index + 1}-${secondindex + 1}`" @click="handClick(secondmenu.path)">
{{ secondmenu.name }}
</el-menu-item>
</template>
</el-sub-menu>
<!-- 如果一级菜单没有子菜单,渲染 el-menu-item -->
<el-menu-item v-else :index="`${index + 1}`" @click="handClick(item.path)">
<el-icon v-if="item.icon">
<component :is="item.icon" />
</el-icon>
{{ item.name }}
</el-menu-item>
</template>
</el-menu>
</el-scrollbar>
</el-aside>
<el-container>
<el-header style="text-align: right; font-size: 12px">
<div class="toolbar">
<el-dropdown>
<el-icon style="margin-right: 8px; margin-top: 1px">
<setting />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>View</el-dropdown-item>
<el-dropdown-item>Add</el-dropdown-item>
<el-dropdown-item>Delete</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span>Tom</span>
</div>
</el-header>
<!-- 右侧内容 -->
<el-main>
<el-scrollbar>
<RouterView />
</el-scrollbar>
</el-main>
<!-- 底部信息 -->
<el-footer class="flex flex-center">
<span>@2025-2030 wen</span>
</el-footer>
</el-container>
</el-container>
</template>
二级菜单示例
<template>
<el-container class="layout-container-demo" style="height: 100vh">
<el-aside width="200px">
<el-scrollbar>
<!-- default-openeds:默认展开菜单 -->
<el-menu :default-openeds="['1', '2']">
<!-- 遍历一级菜单 -->
<template v-for="(item, index) in menu" :key="index">
<!-- 如果一级菜单有子菜单,渲染 el-sub-menu -->
<el-sub-menu v-if="item.children && item.children.length > 0" :index="`${index + 1}`">
<template #title>
<el-icon v-if="item.icon">
<component :is="item.icon" />
</el-icon>
{{ item.name }}
</template>
<!-- 遍历二级菜单 -->
<el-menu-item v-for="(secondMenu, secondIndex) in item.children" :key="secondIndex"
:index="`${index + 1}-${secondIndex + 1}`" @click="handClick(secondMenu.path)">
{{ secondMenu.name }}
</el-menu-item>
</el-sub-menu>
<!-- 如果一级菜单没有子菜单,渲染 el-menu-item -->
<el-menu-item v-else :index="`${index + 1}`" @click="handClick(item.path)">
<el-icon v-if="item.icon">
<component :is="item.icon" />
</el-icon>
{{ item.name }}
</el-menu-item>
</template>
</el-menu>
</el-scrollbar>
</el-aside>
<el-container>
<el-header style="text-align: right; font-size: 12px">
<div class="toolbar">
<el-dropdown>
<el-icon style="margin-right: 8px; margin-top: 1px">
<setting />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>View</el-dropdown-item>
<el-dropdown-item>Add</el-dropdown-item>
<el-dropdown-item>Delete</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span>Tom</span>
</div>
</el-header>
<!-- 右侧内容 -->
<el-main>
<el-scrollbar>
<RouterView />
</el-scrollbar>
</el-main>
<!-- 底部信息 -->
<el-footer class="flex flex-center">
<span>@2025-2030 wen</span>
</el-footer>
</el-container>
</el-container>
</template>
3、逻辑层
①引入方法
引入vue的reactive方法 ,vue-router路由的RouterView, useRouter方法
import { reactive } from 'vue'
import { RouterView, useRouter } from 'vue-router'
②注册图标
这里在视图层使用的<component :is="item.icon" /> 图标引入,采用动态加载图标组件,其中的图标都是需要先注册的,具体在main.js中进行
③定义菜单
这里写了三级菜单和二级菜单两个例子,根据需求选择需要的菜单层
三级菜单
定义对象的数组,每个菜单用对象表示,包含的项有name:标题,icon:图标,path:路径,这里先用现有的路径作为例子
// 菜单
const menu = reactive([
{
name: 'Navigator One',
icon: "message",
path: '/about',
},
{
name: 'Navigator Two',
icon: "message",
children: [
{
name: 'Option 1',
path: '/about',
},
{
name: 'Option 2',
},
{
name: 'Option 3',
},
{
name: 'Option 4',
children: [
{
name: 'Option 4-1',
path: '/home',
},
]
},
]
},
]);
二级菜单
// 菜单
const menu = reactive([
{
name: 'Navigator One',
icon: "message",
path: '/about',
},
{
name: 'Navigator Two',
icon: "message",
children: [
{
name: 'Option 1',
path: '/about',
},
{
name: 'Option 2',
},
{
name: 'Option 3',
},
{
name: 'Option 4',
},
]
},
]);
④标题点击事件
定义路由
const router = useRouter();
点击跳转事件
通过视图层中点击事件的参数,可获取路径,再通过路由的事件进行跳转
// 点击菜单
const handClick = (item) => {
router.push(item)
}
4、样式层
这里采用官网提供的示例
这里有个变量var(--el-color-primary-light-8),需要在base.js中进行定义,默认都是蓝色
三、完整代码
这里以二级菜单为例
1、layout代码
src/layout/index.vue
<template>
<el-container class="layout-container-demo" style="height: 100vh">
<el-aside width="200px">
<el-scrollbar>
<!-- default-openeds:默认展开菜单 -->
<el-menu :default-openeds="['1', '2']">
<!-- 遍历一级菜单 -->
<template v-for="(item, index) in menu" :key="index">
<!-- 如果一级菜单有子菜单,渲染 el-sub-menu -->
<el-sub-menu v-if="item.children && item.children.length > 0" :index="`${index + 1}`">
<template #title>
<el-icon v-if="item.icon">
<component :is="item.icon" />
</el-icon>
{{ item.name }}
</template>
<!-- 遍历二级菜单 -->
<el-menu-item v-for="(secondmenu, secondindex) in item.children" :key="secondindex"
:index="`${index + 1}-${secondindex + 1}`" @click="handClick(secondmenu.path)">
{{ secondmenu.name }}
</el-menu-item>
</el-sub-menu>
<!-- 如果一级菜单没有子菜单,渲染 el-menu-item -->
<el-menu-item v-else :index="`${index + 1}`" @click="handClick(item.path)">
<el-icon v-if="item.icon">
<component :is="item.icon" />
</el-icon>
{{ item.name }}
</el-menu-item>
</template>
</el-menu>
</el-scrollbar>
</el-aside>
<el-container>
<el-header style="text-align: right; font-size: 12px">
<div class="toolbar">
<el-dropdown>
<el-icon style="margin-right: 8px; margin-top: 1px">
<setting />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>View</el-dropdown-item>
<el-dropdown-item>Add</el-dropdown-item>
<el-dropdown-item>Delete</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span>Tom</span>
</div>
</el-header>
<!-- 右侧内容 -->
<el-main>
<el-scrollbar>
<RouterView />
</el-scrollbar>
</el-main>
<!-- 底部信息 -->
<el-footer class="flex flex-center">
<span>@2025-2030 wen</span>
</el-footer>
</el-container>
</el-container>
</template>
<script setup>
import { reactive } from 'vue'
import { RouterView, useRouter } from 'vue-router'
// 菜单
const menu = reactive([
{
name: 'Navigator One',
icon: "message",
path: '/about',
},
{
name: 'Navigator Two',
icon: "message",
children: [
{
name: 'Option 1',
path: '/home',
},
{
name: 'Option 2',
},
{
name: 'Option 3',
},
{
name: 'Option 4',
},
]
},
]);
//路由定义
const router = useRouter();
// 点击菜单
const handClick = (item) => {
router.push(item)
}
</script>
<style scoped>
.layout-container-demo .el-header {
position: relative;
background-color: var(--el-color-primary-light-7);
color: var(--el-text-color-primary);
}
.layout-container-demo .el-aside {
color: var(--el-text-color-primary);
background: var(--el-color-primary-light-8);
}
.layout-container-demo .el-menu {
border-right: none;
}
.layout-container-demo .el-main {
padding: 0;
}
.layout-container-demo .toolbar {
display: inline-flex;
align-items: center;
justify-content: center;
height: 100%;
right: 20px;
}
</style>
2、全局样式代码
src/assets/base.js
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
--el-color-primary: #646cff;
--el-button-hover-bg-color: #868bf7;
--el-color-primary-light-3: #868bf7;
--el-color-primary-light-5:#b4b7ef;
--el-color-primary-light-9:#e5e6f7;
--el-color-primary-light-7:#c6c8fa;
--el-color-primary-light-8:#d9e3ff;
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
margin: 0;
}
3、全局的js代码
src/main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import './assets/main.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)
//注册全部图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(createPinia())
app.use(router)
app.use(ElementPlus)
app.mount('#app')