源码地址:https://github.com/18291907191/gwc_manage

Vue实现狗尾草博客后台管理系统第三章

本章节,咱们开发管理系统侧边栏及面包屑功能。

先上一张效果图

样式呢,作者前端初审,关于设计上毫无美感可言,大家可根据自己情况设计更好看的哦~

侧边栏

这里我们借助element的aslide侧边栏,直接使用。

在components>commons下新建Aslide.vue,Header.vue组件。分别作为我们的侧边栏和头部组件。

Aslide内容,我们直接使用el-menu及相关侧边栏组件。不过要仔细阅读以下官方文档,否则使用会比较费劲。

collapse 是否水平折叠收起菜单(仅在 mode 为 vertical 时可用) boolean false

default-active default-active string

default-openeds 当前打开的 sub-menu 的 index 的数组 Array

unique-opened 是否只保持一个子菜单的展开 boolean false

router 是否使用 vue-router 的模式,启用该模式会在激活导航时以 index 作为 path 进行路由跳转 boolean false

以上就是主要的属性,我们要仔细阅读加以理解。

这里的侧边栏的话,因为我们需要注意的是

  1. 如果当前展开菜单为2级的某一菜单,那么在页面刷新后和浏览器回退后,也依然要展开。

  2. 不同的角色登录后,所拥有的权限是不同的。这里我么可以做成较为简单的,前端处理,控制某些菜单显示来实现,当然。后期如果有时间,后端也是需要对接口做权限校验的!

那么,我么开始吧~

首先,我们可以复制elementui的代码过来,直接放到Aslide.vue文件中,然后引用,都是没有问题的。

下来我们就要开始改造了。

因为要做权限的管理,我们这里要控制菜单的显示,所以这里,我们不再页面中写死,这里给提供两种解决方案:

  • 在static中配置静态的menu.json文件,将我们的菜单栏加以不同的角色进行配置,然后在页面中根据登录后的权限,进行动态控制显示对应角色的菜单栏。

  • 将菜单栏放到store中管理。getters直接解构取值获得并使用。(这里之所以放在store中,是因为后面如果后端配合使用权限控制,那么我们就需要后端返回菜单栏信息,并格式化转换为我们的路由信息。实现动态路由的使用~),当然,因为是自己的管理平台,MD还是懒~

这里,我们先一起采用store的方式来存储menu.json文件吧

大家先按照如图所示补全目录。

我们,将menu文件存储在store>modules>aslide.js文件中:

/**
* @description 侧边栏状态库
* @author chaizhiyang
*/
const aslide = {
state: {
isCollapse: false,
menuList: [
{
"text": "概况",
"path": "",
"icon": "el-icon-c-scale-to-original",
"itemGroup": [
{
"text": "概况数据",
"path": "/index"
}
]
},
{
"text": "菜单",
"path": "menu",
"icon": "el-icon-s-operation",
"itemGroup": [
{
"text": "菜单列表",
"path": "/menu_list"
}
]
},
{
"text": "文章",
"path": "article",
"icon": "el-icon-document",
"itemGroup": [
{
"text": "文章列表",
"path": "/article_list"
},
{
"text": "详情",
"path": "/article_detail"
}
]
}
]
},
mutations: {
changeCollapse(state) {
state.isCollapse = state.isCollapse == false ? true : false
},
}
} export default aslide

  

除了,菜单信息外,后面所涉及的header中控制菜单的展开折叠的方法,我们也一并放置在状态中进行管理。

getters.js文件如下

const getters = {
isCollapse: state => state.aslide.isCollapse,
menuList: state => state.aslide.menuList,
} export default getters;

  

简单说就是为了后期mapGetters的使用,方便我们去取state中的数据,使用更加方便~

index.js文件:

/**
* @description vuex主入口文件
* @author chaizhiyang
*/
import Vue from 'vue'
import Vuex from 'vuex'
import aslide from './modules/aslide'
import getters from './getters'
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
aslide,
},
getters
}) export default store;

  store文件基本的配置也就算是完成了,下来我们需要在main.js中引入

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router/permission'
import store from './store';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css'
import Utils from './utils';
import './assets/styles/index.css'; Vue.config.productionTip = false
Vue.use(ElementUI);
Vue.use(Utils);
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})

  侧边栏的配置已经好了,但是还没有使用。下来我们补全一些其他的页面信息。

内容可以随便写成标志性的内容,这都不是重点。

重点是Aslide.vue文件中的引用:

<template>
<div class="menu">
<el-menu
class="el-menu-admin"
:default-active="active"
:default-openeds="openeds"
:unique-opened="true"
:router="true"
:collapse="isCollapse"
ref="menuchild">
<!-- 菜单栏包含单个选项 -->
<el-menu-item
v-for="(item, pindex) in menuList"
:key="+new Date() + pindex"
:index="item.path"
v-if="!item.itemGroup">
<i :class="item.icon"></i>
<span slot="title">{{item.text}}</span>
</el-menu-item>
<!-- 菜单栏包含多个选项 -->
<el-submenu
v-for="(item, pindex) in menuList"
:key="pindex"
:index="item.path">
<template slot="title">
<i :class="item.icon"></i>
<span>{{item.text}}</span>
</template>
<!-- 菜单栏只有二级菜单 -->
<el-menu-item
v-for="(subitem, subindex) in item.itemGroup"
:key="subindex"
:route="subitem.path"
:index="subitem.path"
v-if="!subitem.items"
>{{subitem.text}}</el-menu-item>
<!-- 菜单栏有三级菜单 -->
<el-submenu
v-for="(subitem, subindex) in item.itemGroup"
:key="subindex"
:index="subitem.path"
v-if="subitem.items">
<!-- 第三项分组标题 -->
<template slot="title">{{subitem.text}}</template>
<!-- 第三项分组的items -->
<el-menu-item
v-for="(s_subitem, s_subindex) in subitem.items"
:key="s_subindex"
:route="s_subitem.path"
:index="s_subitem.path"
>{{s_subitem.text}}</el-menu-item>
</el-submenu>
</el-submenu>
</el-menu>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
data() {
return {
openeds: [],
}
},
watch: {
// 监听路由变化
$route(to, from) {
this.setMenulist(to);
}
},
computed: {
...mapGetters([
'menuList','isCollapse'
])
},
created() {
this.setMenulist(this.$route);
},
methods: {
// 设置菜单栏
setMenulist(route) {
let _this = this;
if (route.matched[0].path != "") {
// 多页面菜单栏
this.openeds = [route.matched[0].path];
this.active = route.fullPath.split("?")[0]; //携带参数时,只匹配"?"前的路径
} else if (route.matched[1].path != "") {
// 单页面菜单栏
this.openeds = [route.matched[0].path];
this.active = route.fullPath.split("?")[0]; //携带参数时,只匹配"?"前的路径
} else {
this.$nextTick(() => {
_this.active = "";
_this.openeds = [""];
_this.$refs.menuchild.close(_this.active);
});
}
}
}
}
</script>
<style lang="less" scoped>
.menu {
height: 100%;
.el-menu {
height: 100%;
border: 0;
}
.el-menu-vertical-demo {
color: #303133;
}
.el-menu-item {
box-sizing: border-box;
border-left: 5px solid transparent;
}
.el-menu-item.is-active {
border-left: 5px solid #409EFF;
}
.el-menu-admin:not(.el-menu--collapse) {
width: 145px;
max-height: 400px;
}
}
</style>

  

  1. 之所以要写watch监听,是因为上面我们说到过的页面刷新后,也依然要保持菜单栏的点击和展开状态。当然也可以使用本地缓存区实现,不过就有点小题大做了。

  2. 另外,这里之所以不惜消耗性能的去循环的时候去判断,是因为我们可能有单个的一级菜单。这个时候他是不需要展开的,所以种种状态我们都需要去做判断。

  3. 具体的实现思路:

active要求为字符串,且:router="true"这个属性的开关直接控制了是否将index作为路由进行跳转。

这里给大家提供两种实现思路:

第一种:我们可以给给个菜单配置单独的下标,我们可以写死,比如:'1','1-1','1-2','2','2-1','2-2',采用这种方式去标记,去区别。(这种方式的使用,我们需要将router设置为false,否则话跳转到1-1.。。根本不是我们想要的。)。

第二种::router="true"。设置为true后,下标就会作为路由进行跳转。我们就需要将下标设置为路由的路径。

当然两种方法的区别就是,一个是写死的下标。一个是路径作为下标。都要求我们在配置json文件的时候主要需要的参数。

Next,下来我们就要去Layout布局组件中引入我们的侧边栏啦

Layout:

<template>
<el-container>
<el-header>
<adminHeader />
</el-header>
<el-container>
<el-aside>
<adminAslide />
</el-aside>
<el-container class="loading-area">
<el-main>
<adminCrumbs />
<keep-alive>
<router-view v-if="this.$route.meta.isAlive"></router-view>
</keep-alive>
<router-view v-if="!this.$route.meta.isAlive"></router-view>
</el-main>
<el-footer>Footer</el-footer>
</el-container>
</el-container>
</el-container>
</template>
<script>
import adminHeader from './Header.vue';
import adminAslide from './Aslide.vue';
import adminCrumbs from './Crumbs.vue';
export default {
components: {
adminHeader,
adminAslide,
adminCrumbs
},
}
</script> <style lang="less" scoped>
.el-container {
width: 100%;
height: 100%;
}
.el-header, .el-footer {
background-color: #B3C0D1;
color: #333;
line-height: 60px;
}
.el-header {
padding: 0!important;
}
.el-aside {
// background-color: #D3DCE6;
width: auto!important;
color: #333;
text-align: left;
overflow: hidden;
// line-height: 200px;
} .el-main {
background-color: #E9EEF3;
color: #333;
text-align: center;
line-height: 160px;
}
</style>

  

需要注意的是:这里引入的时候没有用Header代表头部组件和Aslide代表侧边栏组件,是因为这些组件在原生的h5中含有相同的标签,难免造成混淆。作者曾经在使用MpVue开发小程序的过程中,就因为没有区别,所以报了一个error,让我头疼了好久~

大家可以暂时先把上面的面包屑和header引入先关掉,这里不是还没配置嘛。不关闭的话,会报错哦。

Next,非常重要的一个环节。侧边栏我们已经配置好了,我们要对路由进行配置。不过这里。我们需要先将我们原来设置的登录拦截给管理。

在路由中设置auth为false

meta: {
auth: false,
isAlive: true,
title: '文章列表'
}

  

接着,我们只需要按照刚才创建的文件的目录去补全路由:

router/index.js

import Vue from 'vue'
import Router from 'vue-router'
// import HelloWorld from '@/components/HelloWorld'
Vue.use(Router) const _import = file => () => import('@/pages/' + file + '.vue');
const _import_ = file => () => import('@/components/' + file + '.vue'); const asyncRouterMap = []; const constantRouterMap = [
{
path: '/login',
name: 'login',
component: _import('login/index'),
},
{
path: '/',
name: '概况',
component: _import_('commons/Layout'),
redirect: '/index',
children: [
{
path: '/index',
name: '总览',
component: _import('home/index'),
meta: {
isAlive: true,
auth: false,
title: '概况数据'
}
}
]
},
{
path: 'menu',
name: "菜单",
component: _import_('commons/Layout'),
redirect: '/menu_list',
children: [
{
path: '/menu_list',
name: '列表',
component: _import('menu/index'),
meta: {
auth: false,
isAlive: true,
title: '菜单列表'
}
},
]
},
{
path: 'article',
name: '文章',
component: _import_('commons/Layout'),
redirect: '/article_list',
children: [
{
path: '/article_list',
name: '列表',
component: _import('article/index'),
meta: {
auth: false,
isAlive: true,
title: '文章列表'
}
},
{
path: '/article_detail',
name: '详情',
component: _import('article/detail'),
meta: {
auth: false,
isAlive: true,
title: '文章详情'
},
}
]
},
{
path: '/404',
name: '404',
component: _import('error/index'),
meta: {
title: "请求页面未找到",
auth: false
},
},
{
path: '*',
meta: {
title: "请求页面未找到",
auth: false
},
redirect: '/404'
}
]; const router = new Router({
mode: 'history',
routes: constantRouterMap,
linkActiveClass: "router-link-active",
}); export default router

  

这里,我们新增了404路由和通配符。在匹配不到路由时,就会跳转到404页面,当然我们也需要在pages中创建error文件 pages>error>index.vue

细心的同学会发现路由我也都配置了name。这个name就是配置面包屑而准备哒。需要值得注意的是,路由中name的配置,不能有相同项,虽然不影响使用不会报错,但是控制台会出现一个warn告诉我们避免相同的name。

嘿嘿嘿~下来我们就可以配置我们的BreadCrumbs了。

BreadCrumbs配置

BreadCrumbs面包屑导航 什么事面包屑导航呢?

可以理解为当前路由信息的导航提示,并随着路由的改变而改变。

elemnt-ui面包屑组件的使用:

一个数组,里头有很多对象,对象为路由的信息。如果有路径就是可以跳转,如果没有就不能通过面包屑挑战。

eg:

[{ path:'/',name:"主页"

},{ name:"标签"

}]

这里的最终显示效果就为: 主页 > 标签

主页是可以点击的。标签页则不可以点击。

知道了组件需要什么我们就好整理数据啦。

这里我们实现的思路为:

使用路由的this.$route.matched来实现

matched可以返回一个数组,该数组汇总含有当前路由的所有parent信息。

我们定义的name和path也都有。我们就只需要在路由变化的时候去改变传给面包屑的数组即可。

在components>commons>Crumbs.vue文件

上菜Crumbs.vue:

<template>
<div class="crumbs">
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item
v-for="(item,index) in crumbsList"
:key="+new Date() + index"
:to="item.redirect?item.redirect:item.path">
{{item.name}}
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script>
export default {
data() {
return {
crumbsList: []
}
},
watch: {
$route() {
this.getCrumbs();
}
},
methods: {
getCrumbs() {
this.crumbsList = this.$route.matched;
}
},
created() {
this.getCrumbs();
}
}
</script>
<style lang="less" scoped> </style>

  

这面包屑配置就ok啦。当然,menu,router和面包屑三者有一个有问题都会造成问题。所以还是挺复杂的。面包屑组件写好了。我们就在layout中将面包屑打开即可。

Header,菜单栏的收缩

我们因为已经在store中配置好了collapse所以下来要实现按钮控制收缩,我们就需要调用store方法即可.

直接上Header.vue码:

<template>
<div class="header df">
<div class="logo df">
<i class="el-icon-menu" @click="handleChangeCollapse"></i>logo</div>
</div>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
handleChangeCollapse() {
this.$store.commit('changeCollapse');
}
},
created() {
},
}
</script>
<style lang="less" scoped>
.header {
height: 100%;
.logo {
width: 145px;
height: 100%;
cursor: pointer;
font-size: 30px;
}
}
</style>

第二章的内容就完成了,在开发完成后记得推送到仓库哦!  

git add .
git commit -m "菜单栏,面包屑"
git push origin master

  

总结

信心的你,或许发现了。我在元素便利的时候key给的是这样的+new Date() + index;

学到了么?这样写的话,不会造成index重复造成的error。

另外以上所有内容中用到的图标都是element-ui自带的图标。

下一章

  1. iconfont的仓库配置和引入

  2. 页面开发~

Vue实战狗尾草博客后台管理系统第三章的更多相关文章

  1. Vue实战狗尾草博客后台管理系统第七章

    Vue实战狗尾草博客后台管理平台第七章 本章内容为借助模块化来阐述Vuex的进阶使用. 在复杂项目的架构中,对于数据的处理是一个非常头疼的问题.处理不当,不仅对维护增加相当的工作负担,也给开发增加巨大 ...

  2. Vue实战狗尾草博客管理平台第六章

    Vue实现狗尾草博客后台管理系统第六章 本章节内容 文章列表 文章详情 草稿箱 文章发布. 本章节内容呢,开发的很是随意哈,因为多数就是element-ui的使用,熟悉的童鞋,是可以很快完成本章节的内 ...

  3. Vue实战狗尾草博客管理平台第四章

    本章主要内容如下: 填补上期的坑. iconfont仓库的关联,引入. 开发登录页面 填坑 上期中我们功能都已正常使用.但不知道有没有小伙伴测试过error页面,当访问地址不存在时,路由是否能正常挑战 ...

  4. Vue实战狗尾草博客管理平台第五章

    本章主要内容如下: 静态资源服务器的配置.学会如何使用静态资源服务器引入静态资源.并给大家推荐一个免费可使用的oss服务器~ 页面的开发由于近期做出的更改较大.就放在下一篇中. 静态资源服务器 静态资 ...

  5. Vue实战狗尾草博客管理系统第一章

    Vue实战狗尾草博客后台管理系统第一章 这里准备采用的技术栈为:vue全家桶+element-ui 这里因为是后台管理系统,没有做SSR的必要.所以这里就采用前后端分离来昨晚这个项目~ 项目搭建 vu ...

  6. Vue实战狗尾草博客管理系统第二章

    伙伴们出来啦,探讨各问题,关于项目中大量的表单,大家是怎么处理的? 本章主要内容如下:底层布局,路由配置,github仓库推送关联. 关联GitHub仓库 关联建立在github已创建账号的基础上 登 ...

  7. 使用react全家桶制作博客后台管理系统

    前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基于react全家桶(React.React-r ...

  8. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  9. 基于Laravel开发博客应用系列 —— 构建博客后台管理系统

    一个完整的博客应用不能没有后台管理系统.所以在本节中我们将继续完善博客应用 —— 开发后台管理系统. 1.创建路由 在上一节十分钟创建博客项目中,已经设置过了 app/Http/routes.php, ...

随机推荐

  1. SpringBoot+MyBatisPlus+ElementUI一步一步搭建前后端分离的项目(附代码下载)

    场景 一步一步教你在IEDA中快速搭建SpringBoot项目: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/87688277 ...

  2. Winform中实现ZedGraph滚轮缩放后自动重新加载数据

    场景 Winforn中设置ZedGraph曲线图的属性.坐标轴属性.刻度属性: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/10 ...

  3. 松软科技web课堂:JavaScript 事件

    HTML 事件是发生在 HTML 元素上的“事情”. 当在 HTML 页面中使用 JavaScript 时,JavaScript 能够“应对”这些事件. HTML 事件 HTML 事件可以是浏览器或用 ...

  4. Cesium区分单击【LEFT_CLICK】和双击事件【LEFT_DOUBLE_CLICK】

    问题描述 在cesium中,用户鼠标左键双击视图或Entity时,实际触发的是两次click和一次dbclick事件,非常影响代码设计,本文记录了如何区分单击[LEFT_CLICK]和双击事件[LEF ...

  5. xip.io

    gg gg "" "" ,gg, ,gg gg gg,gggg, gg ,ggggg, ""8b,dP" 88 I8P" ...

  6. go语言设计模式之adapter

    adapter.go package adapter import ( "fmt" ) type LegacyPrinter interface { Print(s string) ...

  7. OSPF和ACL的应用

    1.创建拓扑图 2.配置基本网络 3.配置OSPF (1)在R1上配置 (2)在R2上配置 (3)在R3上配置 (4)在IT上配置 4.配置ACL (1)在R3上配置    (2)在R2上配置 (3) ...

  8. (day58)十、Cookie、Session、Token、Django中间件

    目录 一.Cookie (一)由来 (二)什么是Cookie (三)Django中操作Cookie (1)设置Cookie (2)获取Cookie (3)删除Cookie 二.Session (一)由 ...

  9. 【2019.8.20 NOIP模拟赛 T2】小B的树(tree)(树形DP)

    树形\(DP\) 考虑设\(f_{i,j,k}\)表示在\(i\)的子树内,从\(i\)向下的最长链长度为\(j\),\(i\)子树内直径长度为\(k\)的概率. 然后我们就能发现这个东西直接转移是几 ...

  10. 推荐书单(网课)-人生/编程/Python/机器学习-130本

    目录 总计(130本) 一.在读 二.将读 三.已读 非专业书单(77本) 四.已读 专业书单(53本) 五.已看网课(8个) 六.在看网课 一个人如果抱着义务的意识去读书,便不了解读书的艺术.--林 ...