1.布局
一个后台的基本布局为
确定之后开始进行搭建。
这里是需要什么就填什么,所以以下步骤暂时没有进排序。
创建src/layout/index.vue 文件
<template>
<!-- 整体页面布局 -->
<el-row class="app-wrapper">
<el-container>
<!-- 侧边栏 -->
<el-aside width="220px">侧边栏</el-aside>
<el-container>
<!-- 顶部 -->
<el-header height="50px">header</el-header>
<!-- 主页面 -->
<el-main>主页面</el-main>
</el-container>
</el-container>
</el-row>
</template>
<script>
export default {
name: 'Layout'
}
</script>
<style lang="scss" scoped>
.app-wrapper {
position: relative;
height: 100%;
width: 100%;
}
</style>
由于样式采用scss写法。所以需要因为对应的Loader支持
//package.json 生产包 加入
"devDependencies": {
...
"node-sass": "^4.12.0",
"sass-loader": "^8.0.2"
}
命令安装
npm install // vscode 终端cmd 快捷 Ctrl + ~ 数字1旁边那个
修改router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Layout from '@/layout'
const routes = [
{
path: '/',
component: Layout,
name: 'Home'
// component: () => import('@/views/home/index')
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
项目跑起来
npm run serve
接下来开始为布局添砖加瓦。
侧边栏(sidebar)
创建src/layout/components/Sidebar.vue
<template>
<div>
侧边栏
</div>
</template>
<script>
export default {
name: 'Sidebar'
}
</script>
src/layout/index.vue 引入
<template>
<!-- 整体页面布局 -->
<el-row class="app-wrapper">
<el-container>
<!-- 侧边栏 -->
<el-aside width="220px">
<sidebar />
</el-aside>
<el-container>
<!-- 顶部 -->
<el-header height="50px">header</el-header>
<!-- 主页面 -->
<el-main>主页面</el-main>
</el-container>
</el-container>
</el-row>
</template>
<script>
import Sidebar from './components/Sidebar.vue'
export default {
name: 'Layout',
components: {
Sidebar
}
}
</script>
开始组建样式。
创建src/styles 文件夹存放项目样式文件
创建index.scss:项目全局样式(通用样式),具有较高的优先级
创建theme.scss:项目颜色样式
index.scss
* {
margin: 0;
padding: 0;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
html {
height: 100%;
box-sizing: border-box;
}
body {
height: 100%;
// 字体抗锯齿,使用后字体看起来会更清晰舒服
font-size: 14px;
color: #58666e;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
text-decoration: none;
}
div:focus {
outline: none;
}
src/main.js 入口文件引入全局样式
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' // 路由
import store from './store' // vuex
// element 组件这里全部引入,也可定制按需引入,如只要button、table等用到的组件
import ElementPlus from 'element-plus'
// 所有组件相关的样式
import 'element-plus/lib/theme-chalk/index.css'
// 全局样式
import '@/styles/index.scss'
// 挂载
createApp(App).use(store).use(router).use(ElementPlus).mount('#app')
theme.scss 项目主题色加入颜色变量控制。维护起来会很方便
// sidebar
// 文字
$menuText:#bfcbd9;
// 背景
$menuBg:#20222a;
// 激活的背景色
$menuActiveBg:#009688;
$subMenuActiveText:#fff;
$menuHover:#263445;
$subMenuBg:#1f2d3d;
$subMenuHover:#001528;
$sideBarWidth: 210px;
:export {
menuText: $menuText;
menuActiveBg: $menuActiveBg;
subMenuActiveText: $subMenuActiveText;
menuBg: $menuBg;
menuHover: $menuHover;
subMenuBg: $subMenuBg;
subMenuHover: $subMenuHover;
sideBarWidth: $sideBarWidth;
}
Sidebar.vue 内容样式填充。
el-*组件的参数含义可以前往官方文档查看
<template>
<el-row class="slidebar">
<el-col class="sidebar-container">
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-openeds="['1','2']"
class="el-menu-vertical-demo"
:router="true"
:background-color="theme.menuBg"
:text-color="theme.menuText"
:active-text-color="theme.suMenuActiveText"
>
<el-submenu index="1" class="hide-tile">
<template #title>
<i class="el-icon-sunny" />
<span>父级菜单</span>
</template>
<el-menu-item index="#">子菜单1</el-menu-item>
<el-menu-item index="#">子菜单2</el-menu-item>
<!-- 菜单进行分组 -->
<el-menu-item-group title="分组名字" class="default-menu">
<el-menu-item index="#">分组里的菜单1</el-menu-item>
<el-menu-item index="#">分组里的菜单2</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="2" class="hide-tile">
<template #title>
<i class="el-icon-sunny" />
<span>多级菜单</span>
</template>
<el-menu-item-group title="分组名称" class="default-menu">
<el-submenu index="2-1">
<template #title>子菜单</template>
<el-menu-item index="2-1-1">2级菜单</el-menu-item>
</el-submenu>
<el-submenu index="2-2">
<template #title>子菜单</template>
<el-menu-item index="2-2-1">2级菜单</el-menu-item>
</el-submenu>
<el-submenu index="2-3">
<template #title>子菜单</template>
<el-menu-item index="2-3-1">2级菜单</el-menu-item>
</el-submenu>
<el-submenu index="2-4">
<template #title>子菜单</template>
<el-menu-item index="2-4-1">2级菜单</el-menu-item>
</el-submenu>
</el-menu-item-group>
</el-submenu>
</el-menu>
</el-scrollbar>
</el-col>
</el-row>
</template>
<script>
import theme from '@/styles/theme.scss'
import { mapGetters } from 'vuex'
export default {
name: 'Sidebar',
computed: {
...mapGetters([
'routes'
]),
theme() {
return theme
}
}
}
</script>
<style lang="scss">
// 这里是个人覆盖的一些样式,喜欢官方的就不用加了
@import '@/styles/theme.scss';
.slidebar { //外套一个slidebar类。声明以下样式仅限slidebar的组件生效
.sidebar-container {
transition: width 0.5s;
width: $sideBarWidth;
background-color: $menuBg;
height: 100%;
position: fixed;
font-size: 0px;
top: 0;
bottom: 0;
left: 0;
z-index: 1001;
overflow: hidden;
.el-menu-item.is-active {
color: $subMenuActiveText !important;;
background-color: $menuActiveBg !important;
}
.horizontal-collapse-transition {
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
}
.scrollbar-wrapper {
overflow-x: hidden !important;
}
.el-scrollbar__bar.is-vertical {
right: 0px;
}
.el-scrollbar {
height: 100%;
}
&.has-logo {
.el-scrollbar {
height: calc(100% - 50px);
}
}
.is-horizontal {
display: none;
}
.svg-icon {
margin-right: 16px;
}
.sub-el-icon {
margin-right: 12px;
margin-left: -2px;
}
.el-menu {
border: none;
height: 100%;
width: 100% !important;
}
// menu hover
.submenu-title-noDropdown,.el-submenu__title {
&:hover {
color: $subMenuActiveText !important;
background-color: $menuHover !important;
}
}
.el-submenu__title i{
color: $subMenuActiveText !important;
}
.is-active>.el-submenu__title {
color: $subMenuActiveText !important;
}
& .nest-menu .el-submenu>.el-submenu__title,
& .el-submenu .el-menu-item {
min-width: $sideBarWidth !important;
background-color: #191a23 !important;
&:hover {
color: $subMenuActiveText !important;
background-color: $subMenuHover !important;
}
}
& .el-submenu .el-menu-item.is-active{
color: $subMenuActiveText !important;;
background-color: $menuActiveBg !important;
}
}
}
</style>
运行 看看 目前的 搭建情况
npm run serve
以上为样式的搭建。暂时没有任何功能
菜单动态化
接下来让菜单栏根据路由进行变化,
创建store/modules/routers.js 创建一个路由模块管理路由信息的变动
import { routes } from '@/router'
const state = {
routers: [] // 路由数组,存放菜单栏的路由数据
}
const mutations = {
SET_ROUTERS: (state, routers) => {
state.routers = routers
}
}
// function getMenuList(routes) { }
const actions = {
getRouters({ commit }) {
return new Promise(resolve => {
// 处理路由信息
// const res = getMenuList(routes)
// 这里由于暂时还没有对 路由进行 任何限制。
// 所以不需要加工过滤某些需要权限才能展示的路由
const res = routes
// 更新状态
commit('SET_ROUTERS', res)
// 返回数据
resolve(res)
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
/store/getters.js 加入 routers
这里可能有些变量会同名。可以改为自己理解的命名
const getters = {
name: state => state.app.name,
routers: state => state.routers.routers
}
export default getters
然后给src/router/index.js 加入两个路由,用于测试
import { createRouter, createWebHistory } from 'vue-router'
import Layout from '@/layout'
export const routes = [ //这里需要导出,vuex那需要获取
{
path: '/',
component: Layout,
name: '主页'
// component: () => import('@/views/home/index')
},
{
path: '/dog',
component: Layout,
name: '狗子世界',
// component: () => import('@/views/home/index')
children: [
{
path: '/erha',
name: '哈士奇',
component: () => import('@/views/home/index')
}
]
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
重构Sidebar.vue 代码。例子内容可以保存
我们这里不需要多级目录。
<template>
<el-row class="slidebar">
<el-col class="sidebar-container">
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
class="el-menu-vertical-demo"
:router="true"
:background-color="theme.menuBg"
:text-color="theme.menuText"
:active-text-color="theme.suMenuActiveText"
>
<div v-for="(item, i) in routers" :key="i">
<el-submenu v-if="item.children" :index="item.path" class="hide-tile">
<template #title>
<i class="el-icon-sunny" />
<span>{{ item.name }}</span>
</template>
<el-menu-item v-for="(children, index) in item.children" :key="index" :index="item.path + '/' +children.path">{{ children.name }}</el-menu-item>
</el-submenu>
<el-menu-item v-else :index="item.path">
<i class="el-icon-menu" />
<template #title>{{ item.name }}</template>
</el-menu-item>
</div>
</el-menu>
</el-scrollbar>
</el-col>
</el-row>
</template>
<script>
import theme from '@/styles/theme.scss'
import { mapGetters } from 'vuex'
export default {
name: 'Sidebar',
computed: {
...mapGetters([
'routers'
]),
theme() {
return theme
}
},
mounted() {
this.$store.dispatch('routers/getRouters')
console.log(this.routers)
}
}
</script>
...省略了样式
附加一些侧边栏功能。
根据路由激活菜单
// 加入 :default-active属性
<el-menu
:default-active="getActive"
class="el-menu-vertical-demo"
:router="true"
:background-color="theme.menuBg"
:text-color="theme.menuText"
:active-text-color="theme.suMenuActiveText"
>
//script
computed: {
...mapGetters([
'routers'
]),
theme() {
return theme
},
getActive() {
// 根据当前路由展示激活菜单。
const route = this.$route
const { meta, path } = route
if (meta.activeMenu) {
return meta.activeMenu
}
return path
}
},
增加抽屉效果
顶部加入按钮。
<el-container>
<!-- 侧边栏 -->
<el-aside :width="open ? '210px' : '0px'">
<sidebar :open="open" />
</el-aside>
<el-container>
<!-- 顶部 -->
<el-header height="50px">
<div class="sidebar-switch" @click="open = !open">
<i :class="open ? 'el-icon-s-fold':'el-icon-s-unfold'" />
</div>
<el-col>header</el-col>
</el-header>
<!-- 主页面 -->
<el-main>主页面</el-main>
</el-container>
</el-container>
...样式
<style lang="scss" scoped>
.app-wrapper {
position: relative;
height: 100%;
width: 100%;
.el-aside{
transition: .5s;
}
.sidebar-switch {
height: 100%;
width: 50px;
line-height: 50px;
cursor: pointer;
i {
font-size: 22px;
}
}
}
</style>
Sidebar.vue
...
<el-col class="sidebar-container" :style="`width:${open ? '210' : '0'}px;`">
...
//script
name: 'Sidebar',
props: {
open:{
type: Boolean,
default: true
}
},
computed: {
...