当前位置: 首页 > news >正文

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-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 }}  标题名称

​​​​代码:

<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')

相关文章:

  • 万亿级数据量的OceanBase应用从JVM到协议栈立体化改造实现性能调优
  • 对比学习(Contrastive Learning)初接触
  • 通过仿真确定抗积分饱和策略的最佳系数
  • 《TCP/IP网络编程》学习笔记 | Chapter 20:Windows 中的线程同步
  • JVM垃圾回收笔记01-垃圾回收算法
  • ffmpeg介绍(一)——解封装
  • 如何让低于1B参数的小型语言模型实现 100% 的准确率
  • SQLMesh SCD-2 时间维度实战:餐饮菜单价格演化追踪
  • JAVA 之「优先队列」:大顶堆与小顶堆的实现与应用
  • aws(学习笔记第三十四课) dockerized-app with asg-alb
  • claude-3-7-sonnet-20250219 支持深度思考,流式输出
  • css基础-浮动
  • 诊断过拟合的方法及解决方法
  • 同一个局域网的话 如何访问另一台电脑的ip
  • Transformers x SwanLab:可视化NLP模型训练(2025最新版)
  • DeepSeek本地搭建
  • yaffs
  • 连通图(并查集)
  • DFS刷题
  • 人工智能 - DeepSeek 和 Manus 的区别和应用场景
  • 中美是否计划讨论美方以芬太尼为由对华征收的特别关税?外交部回应
  • 习近平出席中拉论坛第四届部长级会议开幕式并发表主旨讲话
  • 回望乡土:对媒介化社会的反思
  • 王毅人民日报撰文:共商发展振兴,共建中拉命运共同体
  • 食用城市|食饭识人
  • 甩掉“肥胖刺客”,科学减重指南来了