一、首先先建立文件main.vue,构建主体
1.选择合适的模板element-plus,直接复制
2.编写相应的样式
<template>
<div class="main">
<el-container class="main-content">
<el-aside> aside </el-aside>
<el-container class="page">
<el-header class="page-header"> header </el-header>
<el-main class="page-content"> main </el-main>
</el-container>
</el-container>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue"
export default defineComponent({})
</script>
<style scoped lang="less">
.main {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.main-content,
.page {
height: 100%;
}
.page-content {
height: calc(100%-48px);
}
.page-info {
background-color: #fff;
border-radius: 5px;
}
.el-header.el-footer {
display: inline;
color: #333;
text-align: center;
align-items: center;
}
.el-header {
height: 48px !important;
}
.el-aside {
overflow-x: hidden;
overflow-y: auto;
line-height: 200px;
text-align: left;
cursor: pointer;
background-color: #001529;
transition: width 0.3s linear;
scrollbar-width: none; /* firefox */
-ms-overflow-style: none; /* IE 10+ */
&::-webkit-scrollbar {
display: none;
}
}
.el-main {
color: #333;
text-align: center;
background-color: #f0f2f5;
}
</style>
二、利用组件化思想,将菜单栏,头部和中间内容分别写成三个文件
1.先建立nav-header和nav-menu文件夹,然后在里面编写主要代码,然后在index,ts里面导出组件
2.然后在main.vue里面对应的aside和header的位置引用上面创建的相应组件
3.在nav-menu里面写菜单导航
(1)先去找合适的element-plus样式复制
(2)给每个菜单栏一个点击事件,跳转到对应的路由页面
nav-menu.vue
4.在nav-header.vue设计一个折叠的按钮。可以实现点击折叠菜单栏
(1)先定义一个按钮,并且给按钮一个点击事件,在点击事件里面负责将foldChange事件传给父组件
(2)然后将点击事件传给引用他的父组件main.vue
(3)然后父组件里面将接受到的值赋给另一个新变量isCollapse,然后 将这个新的变量值传给子组件nav-menu,同时isCollapse也会响应式改变,从而改变el-aside的宽度
(4)子组件nav-menu在菜单栏el-menu(这个是el-menu的一个属性)接收新变量值,然后实现折叠效果
三、nav-header的面包屑和个人信息组件的设计
1.首先是先定义好面包屑和个人信息的位置和相应的样式
<template>
<div class="nav-header">
<button @click="handleFoldclick" class="fole-menu">折叠</button>
<div class="content">
<div>面包屑</div>
<div>个人信息</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from "vue"
// import { Expand } from '@element-plus/icons-vue'
export default defineComponent({
emits: ["foldChange"],
setup(props, { emit }) {
const isFold = ref(false)
const handleFoldclick = () => {
isFold.value = !isFold.value
emit("foldChange", isFold.value)
}
return {
isFold,
handleFoldclick
}
}
})
</script>
<style scoped>
.nav-header {
display: flex;
width: 100%;
text-align: center;
margin-top: 10px;
}
.fold-menu {
width: 30px;
height: 20px;
cursor: pointer;
margin: 10px;
}
.content {
/* 自己内部flex布局,两端 */
display: flex;
justify-content: space-between;
/* 在大的nav-header里面flex */
flex: 1;
padding: 0 20px;
}
</style>
2.可以将个人信息单独抽离出来当一个组件user-info.vue,然后再在nav-header对应的个人信息的位置引用user-info组件
user-info.vue
<template>
<div class="user-info">
<el-dropdown>
<span class="el-dropdown-link">
Dropdown List
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>Action 1</el-dropdown-item>
<el-dropdown-item>Action 2</el-dropdown-item>
<el-dropdown-item>Action 3</el-dropdown-item>
<el-dropdown-item disabled>Action 4</el-dropdown-item>
<el-dropdown-item divided>Action 5</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<script>
import { defineComponent, computed } from "vue"
export default defineComponent({})
</script>
<style scoped></style>
nav-header.vue
<template>
<div class="nav-header">
<button @click="handleFoldclick" class="fole-menu">折叠</button>
<div class="content">
<div>面包屑</div>
<user-info></user-info>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from "vue"
import UserInfo from "./user-info.vue"
export default defineComponent({
components: {
UserInfo
},
emits: ["foldChange"],
setup(props, { emit }) {
const isFold = ref(false)
const handleFoldclick = () => {
isFold.value = !isFold.value
emit("foldChange", isFold.value)
}
return {
isFold,
handleFoldclick
}
}
})
</script>
<style scoped>
.nav-header {
display: flex;
width: 100%;
text-align: center;
margin-top: 10px;
}
.fold-menu {
width: 30px;
height: 20px;
cursor: pointer;
margin: 10px;
}
.content {
/* 自己内部flex布局,两端 */
display: flex;
justify-content: space-between;
/* 在大的nav-header里面flex */
flex: 1;
padding: 0 20px;
}
</style>
3.将登录后的用户名显示在个人信息头像旁边
(1)如果直接把登录后存储在vuex里面的loginInfo直接拿出来赋值给当前user-info里面的变量,虽然可以实现显示用户名,但是会出现一个bug,刷新之后获取不到当前登录的用户信息
(2)如果发生这样的情况,需要将实时将存储在localCache里面的loginInfo拿出来赋值
四、面包屑的设计
1.由于这里的菜单导航不是从接口获取到的,是自定义的,所以这里需要拿到当前的路由的meta里面的title进行匹配显示
2.重新创建一个组件文件breadcrumb.vue,在这个文件里面去使用element-plus里面的面包屑组件
3.这里遍历自定义变量list,然后再 根据当前的路由路经去显示对应的页面
breadcrumb.vue
<template>
<div class="nav-breadcrumb">
<el-breadcrumb separator>
<el-breadcrumb-item v-for="item in list" :key="item.path">
<router-link :to="{ path: item.path }">
{{ item.meta.title }}
</router-link>
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from "vue"
import { IBreadcrumb } from "../types"
import { useRouter } from "vue-router"
export default defineComponent({
// props: {
// breadcrumbs: {
// type: Array as PropType<IBreadcrumb[]>,
// default: () => []
// }
// }
setup() {
const router = useRouter()
let list = computed(() => {
return router.currentRoute.value.matched
})
return {
list
}
}
})
</script>
nav-menu.vue
这里也解决了当刷新页面时,导航栏和右侧页面不匹配的问题,这里主要是修改index和default-active
<template>
<div class="nav-menu">
<div class="logo">
<img class="img" src="~@/assets/img/logo.svg" alt="logo" />
<span class="title" v-if="!collapse">Vue3+TS</span>
</div>
<div>
<el-menu
:default-active="activeIndex"
class="el-menu-vertical"
:collapse="collapse"
>
<el-menu-item index="/main/system" @click="handleChartClick">
<el-icon>
<House />
</el-icon>
<span>系统首页</span>
</el-menu-item>
<el-sub-menu index="/main/">
<template #title>
<el-icon>
<User />
</el-icon>
<span>商品管理</span>
</template>
<el-menu-item index="/main/category" @click="handleUserClick">
<el-icon>
<Menu />
</el-icon>
商品中心
</el-menu-item>
<el-menu-item index="/main/goods" @click="handleAdminUserClick">
<el-icon>
<Menu />
</el-icon>
商品信息
</el-menu-item>
</el-sub-menu>
<el-menu-item index="/main/order" @click="handleQuestionClick">
<el-icon>
<Document />
</el-icon>
<span>订单管理</span>
</el-menu-item>
</el-menu>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, ref } from "vue"
// import { useStore } from "@/store"
import { useRouter, useRoute } from "vue-router"
// 使用 document.documentElement.style 实现换肤功能
document.documentElement.style.setProperty(`--el-menu-bg-color`, "transparent")
export default defineComponent({
props: {
collapse: {
type: Boolean,
default: false
}
},
setup() {
const router = useRouter()
const activeIndex = ref("")
// 因为这次是匹配的子路由 所以不需要做处理
activeIndex.value = router.currentRoute.value.path
console.log(activeIndex.value)
const handleUserClick = () => {
router.push({ path: "/main/category" })
}
const handleAdminUserClick = () => {
router.push({ path: "/main/goods" })
}
const handleQuestionClick = () => {
router.push({ path: "/main/order" })
}
const handleChartClick = () => {
router.push({ path: "/main/system" })
}
return {
// handleHomeClick,
handleUserClick,
handleAdminUserClick,
handleQuestionClick,
handleChartClick,
activeIndex
// handleQuestionClick2
}
}
})
</script>
<style scoped lang="less">
.nav-menu {
height: 100%;
.logo {
display: flex;
height: 28px;
padding: 12px 10px 8px 10px;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
img {
height: 100%;
margin: 0 10px;
}
.title {
font-size: 16px;
font-weight: 700;
color: white;
}
}
// 目录
.el-sub-menu {
// 二级菜单 ( 默认背景 )
.el-menu-item {
padding-left: 50px !important;
background-color: transparent !important;
color: #ccc;
}
}
.el-menu {
border-right: none;
}
.el-menu-item {
color: #ccc;
}
.el-sub-menu {
.el-menu-item {
padding-left: 50px !important;
/* background-color: transparent !important; */
color: #ccc;
}
}
::v-deep .el-sub-menu__title {
background-color: transparent !important;
color: #ccc;
}
// hover 高亮
.el-menu-item:hover {
color: #20a0ff !important; // 菜单
background-color: #2c394c !important;
}
.el-menu-item.is-active {
color: #20a0ff !important;
background-color: #2c394c !important;
}
.el-menu-vertical:not(.el-menu--collapse) {
width: 100%;
height: 100%;
// height: calc(100% - 48px);
}
a {
text-decoration: none;
}
</style>
nav-header.vue
<template>
<div class="nav-header">
<button @click="handleFoldclick" class="fole-menu">折叠</button>
<div class="content">
<hy-breadcrumb></hy-breadcrumb>
<user-info></user-info>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from "vue"
import UserInfo from "./user-info.vue"
import HyBreadcrumb, { IBreadcrumb } from "@/base-ui/breadcrumb"
export default defineComponent({
components: {
UserInfo,
HyBreadcrumb
},
emits: ["foldChange"],
setup(props, { emit }) {
const isFold = ref(false)
const handleFoldclick = () => {
isFold.value = !isFold.value
emit("foldChange", isFold.value)
}
return {
isFold,
handleFoldclick
}
}
})
</script>
<style scoped>
.nav-header {
display: flex;
width: 100%;
text-align: center;
margin-top: 10px;
}
.fold-menu {
width: 30px;
height: 20px;
cursor: pointer;
margin: 10px;
}
.content {
/* 自己内部flex布局,两端 */
display: flex;
justify-content: space-between;
/* 在大的nav-header里面flex */
flex: 1;
padding: 0 20px;
align-items: center;
text-align: center;
}
</style>