2.vue3+ts+element-plus后台管理界面布局及动态侧边栏实现

本文介绍了如何使用ElementPlus的组件构建网页布局,包括el-container、el-header、el-aside和el-main的运用。同时详细讲解了侧边栏的动态实现,包括el-menu、el-submenu以及菜单项的配置,特别提到了vue3中的注意事项,如组件名唯一性和自定义图标的方法。

1. 界面布局

  利用element-plus的布局组件实现:  <el-container> 整个布局容器,  <el-header> 顶部布局容器,  <el-aside> 侧边布局容器  <el-main> 主要布局容器。  采用组件化思想,将header和aside组件化,方便布局。

<template>
  <div class="common-layout">
    <el-container>

      <CommonAside />

      <el-container>
        <el-header>
          <CommonHeader />
        </el-header>
        <el-main>
          <router-view />
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>
<script lang="ts" setup>
import CommonAside from '../components/CommonAside.vue'
import CommonHeader from '../components/CommonHeader.vue'

</script>
  color: var(--el-text-color-primary);
}</style>

2.侧边栏布局及动态实现技术

侧边栏的布局,利用element-plus的el-menu组件实现。  el-menu组件实现第一级菜单,el-submenu实现第二级菜单。  el-menu-item实现菜单项,el-submenu-item实现二级菜单项。  el-menu-item-group实现菜单组,el-menu-item-group实现菜单组。

      所有菜单内容存储在数组menuData中,通过v-for指令动态渲染。通过noChildren过滤出没有子菜单的菜单数据也就是一级菜单数据。  通过hasChildren过滤出有子菜单的菜单数据也就是二级菜单数据。

  需要说明的是在vue3和elment-plus中: 
 1.el-menu-item和el-submenu-item组件的name属性值必须唯一,否则会报错。 
  index属性也必须唯一,否则后面现实菜单单击一项所有项全部响应。
 2.icon采用组件化设计,不同以前版本通过拼接字符串的方式调用组件(大多过去实现采用这种方法)。具体为:

   <component :is="item.icon" ></component>

   这块实现参考文章:https://blog.youkuaiyun.com/qq_40190624/article/details/125019530 说的较为详细。

ai推荐使用:<el-icon><icon-svg :icon-class="item.icon" /></el-icon>

   需要自定义的图标,可以到iconfont官网下载,然后放到static文件夹下,然后通过import引入。https://blog.youkuaiyun.com/qq_39246667/article/details/124347398 给出具体方法较为复杂.

  3.noChildren()方法最好存储成具体的变量,这样在代码中可以很方便的调用,而不用在v-for中直接调用函数。
<template>
    <!-- <el-radio-group v-model="isCollapse" style="margin-bottom: 20px">
      <el-radio-button :label="false">expand</el-radio-button>
      <el-radio-button :label="true">collapse</el-radio-button>
    </el-radio-group> -->
    <el-menu default-active="2" class="el-menu-vertical-demo" :collapse="isCollapse" @open="handleOpen"
        @close="handleClose">
        <el-menu-item v-for="item in noChildren()" :key="item.name" :index="item.name">
            <component :is="item.icon" style="width: 18px;height: 18px;"></component>
            <template #title>{{item.label}}</template>
        </el-menu-item>
        <el-sub-menu v-for="item in hasChildren()" :key="item.name" :index="item.label">
            <template #title>
                <el-icon>
                    <component :is="item.icon" style="width: 18px;height: 18px;"></component>
                </el-icon>
                <span>{{item.label}}</span>
            </template>
            <el-menu-item-group v-for="subItem in item.children" :key="subItem.name">
            <el-menu-item :index="subItem.name">{{subItem.label}}</el-menu-item>
           </el-menu-item-group>
        </el-sub-menu>
    </el-menu>
</template>
  
<script lang="ts" setup>
import { ref } from 'vue'
import {
    Document,
    Menu as IconMenu,
    Location,
    Setting,
} from '@element-plus/icons-vue'

// 声明一个变量isCollapse,值为false
const isCollapse = ref(false)

const menuData = [
    {
        path: "/",
        name: "home",
        label: "首页",
        icon: 'Menu',
        url: "Home/Home",
    },
    {
        path: "/mall",
        name: "mall",
        label: "商品管理",
        icon: "Document",
        url: "MallManage/MallManage",
    },
    {
        path: "/user",
        name: "user",
        label: "用户管理",
        icon: "Setting",
        url: "UserManage/UserManage",
    },
    {
        label: "其他",
        icon: "location",
        children: [
            {
                path: "/page1",
                name: "page1",
                label: "首页",
                icon: "Setting",
                url: "Other/PageOne",
            },
            {
                path: "/page2",
                name: "page2",
                label: "首页",
                icon: "Setting",
                url: "Other/PageTwo",
            },
        ],
    },
]
// 声明一个函数handleOpen,参数为key和keyPath,用于接收参数
const handleOpen = (key: string, keyPath: string[]) => {
    console.log(key, keyPath)
}
// 声明一个函数handleClose,参数为key和keyPath,用于接收参数
const handleClose = (key: string, keyPath: string[]) => {
    console.log(key, keyPath)
}
const noChildren = () => { return menuData.filter((item) => !item.children) }
// 过滤出没有子菜单的菜单数据

const hasChildren = () => { return menuData.filter((item) => item.children) }
// 过滤出有子菜单的菜单数据

</script>
  
<style>
.el-menu-vertical-demo:not(.el-menu--collapse) {
    width: 200px;
    min-height: 400px;
}
</style>
  

注意:index 项目必须唯一 :index="item.label"  item.label选项各菜单都有且不同,子菜单项标题没有name,所以item.name报错。

<think>根据用户的问题,用户希望使用Vue3、TypeScript和Element Plus实现一个带有侧边导航菜单和右侧内容区域的布局。这是一个典型的后台管理系统布局,通常包含侧边栏(菜单)、顶部导航栏(Header)和主内容区域(Main)。 我将参考Element Plus的官方文档和常见的布局模式,提供以下步骤: 1. 安装Element Plus及其图标库 2. 创建布局组件:Layout(包含侧边栏、头部、主内容区域) 3. 使用Element Plus的Container布局容器(el-container, el-aside, el-header, el-main) 4. 实现侧边栏菜单(使用el-menu组件) 5. 实现路由切换,使右侧内容区域根据路由变化 注意:由于用户使用TypeScript,我们需要为组件添加类型声明。 下面是具体实现: ### 1. 安装Element Plus 如果尚未安装,请先安装Element Plus和它的图标库: ```bash npm install element-plus @element-plus/icons-vue ``` ### 2. 在main.ts中引入Element Plus ```typescript import { createApp } from &#39;vue&#39; import App from &#39;./App.vue&#39; import ElementPlus from &#39;element-plus&#39; import &#39;element-plus/dist/index.css&#39; const app = createApp(App) app.use(ElementPlus) app.mount(&#39;#app&#39;) ``` ### 3. 创建布局组件 在src目录下创建layout文件夹,然后创建以下组件: - Layout.vue布局容器) - Sidebar.vue侧边栏- Header.vue(顶部导航栏) - Main.vue(主内容区域,这里我们直接使用路由视图) #### Layout.vue ```vue <template> <el-container class="layout-container"> <!-- 侧边栏 --> <el-aside :width="sidebarWidth"> <Sidebar :collapse="isCollapse" /> </el-aside> <el-container> <!-- 头部 --> <el-header> <Header @toggle-collapse="toggleCollapse" :collapse="isCollapse" /> </el-header> <!-- 主内容区域 --> <el-main> <router-view></router-view> </el-main> </el-container> </el-container> </template> <script setup lang="ts"> import { ref, computed } from &#39;vue&#39; import Sidebar from &#39;./Sidebar.vue&#39; import Header from &#39;./Header.vue&#39; const isCollapse = ref(false) const toggleCollapse = () => { isCollapse.value = !isCollapse.value } // 根据折叠状态设置侧边栏宽度 const sidebarWidth = computed(() => isCollapse.value ? &#39;64px&#39; : &#39;200px&#39;) </script> <style scoped> .layout-container { height: 100vh; } .el-header { background-color: #fff; border-bottom: 1px solid #eee; display: flex; align-items: center; } .el-aside { background-color: #304156; transition: width 0.3s; } .el-main { padding: 20px; } </style> ``` #### Sidebar.vue ```vue <template> <el-menu :default-active="activeMenu" class="el-menu-vertical" background-color="#304156" text-color="#bfcbd9" active-text-color="#409EFF" :collapse="collapse" router > <el-sub-menu index="1"> <template #title> <el-icon><location /></el-icon> <span>导航一</span> </template> <el-menu-item index="/dashboard">首页</el-menu-item> <el-menu-item index="/table">表格</el-menu-item> </el-sub-menu> <el-menu-item index="2"> <el-icon><icon-menu /></el-icon> <span>导航二</span> </el-menu-item> </el-menu> </template> <script setup lang="ts"> import { computed } from &#39;vue&#39; import { useRoute } from &#39;vue-router&#39; import { Location, Menu as IconMenu } from &#39;@element-plus/icons-vue&#39; defineProps<{ collapse: boolean }>() const route = useRoute() const activeMenu = computed(() => route.path) </script> <style> .el-menu-vertical:not(.el-menu--collapse) { width: 200px; } </style> ``` #### Header.vue ```vue <template> <div class="header"> <el-icon @click="toggleCollapse" style="cursor: pointer;"> <Expand v-if="collapse" /> <Fold v-else /> </el-icon> <!-- 其他头部内容,如面包屑、用户信息等 --> </div> </template> <script setup lang="ts"> import { Expand, Fold } from &#39;@element-plus/icons-vue&#39; defineProps<{ collapse: boolean }>() const emit = defineEmits([&#39;toggle-collapse&#39;]) const toggleCollapse = () => { emit(&#39;toggle-collapse&#39;) } </script> <style scoped> .header { display: flex; align-items: center; } </style> ``` ### 4. 配置路由 在router/index.ts中配置路由,确保路由与菜单的index匹配。例如: ```typescript import { createRouter, createWebHistory, RouteRecordRaw } from &#39;vue-router&#39; const routes: Array<RouteRecordRaw> = [ { path: &#39;/&#39;, component: () => import(&#39;@/views/Dashboard.vue&#39;), meta: { title: &#39;首页&#39; } }, { path: &#39;/dashboard&#39;, name: &#39;Dashboard&#39;, component: () => import(&#39;@/views/Dashboard.vue&#39;), meta: { title: &#39;首页&#39; } }, { path: &#39;/table&#39;, name: &#39;Table&#39;, component: () => import(&#39;@/views/Table.vue&#39;), meta: { title: &#39;表格&#39; } } ] const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default router ``` ### 5. 在App.vue中使用布局组件 ```vue <template> <Layout /> </template> <script setup lang="ts"> import Layout from &#39;@/layout/Layout.vue&#39; </script> ``` 这样,一个基本的侧边栏导航和右侧视图布局就完成了。用户可以通过点击侧边栏的菜单项来切换右侧内容区域显示的路由组件。 ### 注意事项 1. 侧边栏的折叠状态通过一个响应式变量`isCollapse`控制,点击头部菜单的折叠按钮时切换。 2. 侧边栏菜单的`router`属性设置为true,这样菜单项的index值会被当作路由路径进行跳转。 3. 使用`<router-view>`来显示路由对应的组件。 ### 优化点 - 可以根据实际需求调整样式 - 侧边栏菜单项可以通过路由配置动态生成 - 添加权限控制,根据用户权限动态生成菜单 希望这个实现能够满足你的需求。如果有任何问题,请随时提问。 §§相关问题§§ 1. 如何在Vue3+TypeScript项目中动态生成侧边栏菜单? 2. 如何实现Element Plus侧边栏菜单的权限控制? 3.Vue3中,如何优化大型项目的路由配置?</think>以下是一个使用 Vue 3、TypeScript 和 Element Plus 实现侧边栏导航和右侧视图布局的完整方案: ### 1. 安装依赖 ```bash npm install element-plus @element-plus/icons-vue npm install vue-router@4 ``` ### 2. 项目结构 ``` src/ ├── layout/ │ ├── Layout.vue # 主布局组件 │ ├── Sidebar.vue # 侧边栏组件 │ └── Header.vue # 顶部导航栏组件 ├── views/ # 页面视图组件 ├── router/ # 路由配置 │ └── index.ts └── App.vue ``` ### 3. 路由配置 (`router/index.ts`) ```typescript import { createRouter, createWebHistory, RouteRecordRaw } from &#39;vue-router&#39; const routes: RouteRecordRaw[] = [ { path: &#39;/&#39;, component: () => import(&#39;@/layout/Layout.vue&#39;), children: [ { path: &#39;/dashboard&#39;, name: &#39;Dashboard&#39;, component: () => import(&#39;@/views/Dashboard.vue&#39;) }, { path: &#39;/user&#39;, name: &#39;User&#39;, component: () => import(&#39;@/views/User.vue&#39;) } ] } ] const router = createRouter({ history: createWebHistory(), routes }) export default router ``` ### 4.布局组件 (`layout/Layout.vue`) ```vue <template> <el-container class="h-screen"> <!-- 侧边栏 --> <el-aside :width="sidebarWidth"> <Sidebar :collapse="isCollapse" /> </el-aside> <el-container> <!-- 顶部导航栏 --> <el-header height="50px"> <Header @toggle-collapse="toggleCollapse" :collapse="isCollapse" /> </el-header> <!-- 主内容区 --> <el-main> <router-view /> </el-main> </el-container> </el-container> </template> <script setup lang="ts"> import { ref, computed } from &#39;vue&#39; import Sidebar from &#39;./Sidebar.vue&#39; import Header from &#39;./Header.vue&#39; const isCollapse = ref(false) const toggleCollapse = () => { isCollapse.value = !isCollapse.value } const sidebarWidth = computed(() => isCollapse.value ? &#39;64px&#39; : &#39;200px&#39;) </script> <style scoped> .el-header { border-bottom: 1px solid #eee; display: flex; align-items: center; } .el-aside { background-color: #304156; transition: width 0.3s; } </style> ``` ### 5. 侧边栏组件 (`layout/Sidebar.vue`) ```vue <template> <el-menu :default-active="activeMenu" class="el-menu-vertical" background-color="#304156" text-color="#bfcbd9" active-text-color="#409EFF" :collapse="collapse" router > <el-sub-menu index="1"> <template #title> <el-icon><location /></el-icon> <span>导航一</span> </template> <el-menu-item index="/dashboard">仪表盘</el-menu-item> </el-sub-menu> <el-sub-menu index="2"> <template #title> <el-icon><user /></el-icon> <span>用户管理</span> </template> <el-menu-item index="/user">用户列表</el-menu-item> </el-sub-menu> </el-menu> </template> <script setup lang="ts"> import { computed } from &#39;vue&#39; import { useRoute } from &#39;vue-router&#39; import { Location, User } from &#39;@element-plus/icons-vue&#39; defineProps<{ collapse: boolean }>() const route = useRoute() const activeMenu = computed(() => route.path) </script> <style> .el-menu-vertical:not(.el-menu--collapse) { width: 200px; } </style> ``` ### 6. 顶部导航栏组件 (`layout/Header.vue`) ```vue <template> <div class="header"> <el-icon @click="toggleCollapse" class="collapse-icon"> <Expand v-if="collapse" /> <Fold v-else /> </el-icon> <div class="header-right"> <el-dropdown> <span class="user-info"> <el-avatar size="small" /> <span>管理员</span> </span> </el-dropdown> </div> </div> </template> <script setup lang="ts"> import { Expand, Fold } from &#39;@element-plus/icons-vue&#39; defineProps<{ collapse: boolean }>() const emit = defineEmits([&#39;toggle-collapse&#39;]) const toggleCollapse = () => { emit(&#39;toggle-collapse&#39;) } </script> <style scoped> .header { display: flex; justify-content: space-between; align-items: center; width: 100%; } .collapse-icon { font-size: 20px; cursor: pointer; } .user-info { display: flex; align-items: center; gap: 8px; cursor: pointer; } </style> ``` ### 7. 视图组件示例 (`views/Dashboard.vue`) ```vue <template> <div class="dashboard"> <h1>仪表盘</h1> <el-card> <p>欢迎使用管理系统</p> </el-card> </div> </template> <script setup lang="ts"> // 业务逻辑代码 </script> ``` ### 8. 在 `main.ts` 中全局引入 ```typescript import { createApp } from &#39;vue&#39; import App from &#39;./App.vue&#39; import router from &#39;./router&#39; import ElementPlus from &#39;element-plus&#39; import &#39;element-plus/dist/index.css&#39; import * as ElementPlusIconsVue from &#39;@element-plus/icons-vue&#39; const app = createApp(App) // 注册所有Element图标 for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } app.use(router) app.use(ElementPlus) app.mount(&#39;#app&#39;) ``` ### 关键功能说明 1. **响应式折叠**: - 点击顶部导航栏的折叠图标可切换侧边栏宽度 - 使用 `computed` 属性动态计算宽度:`$sidebarWidth = isCollapse ? &#39;64px&#39; : &#39;200px&#39;` 2. **路由集成**: - 侧边栏菜单的 `router` 属性自动处理导航 - `activeMenu` 根据当前路由高亮菜单项 3. **布局结构**: ```mermaid graph TD A[el-container] --> B[el-aside 侧边栏] A --> C[el-container] C --> D[el-header 顶部栏] C --> E[el-main 主内容区] ``` 4. **样式优化**: - 使用 `height: 100vh` 确保全屏高度 - 添加过渡动画使折叠更平滑 - 深色侧边栏搭配浅色内容区 这个布局实现了典型的后台管理系统结构,侧边栏导航包含可折叠菜单,右侧内容区域根据路由动态加载组件[^3][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值