目录
上两篇文章:
【VUE】demo01-VUE做后台管理系统页面实例-创建基本环境+页面布局
【VUE】demo02-VUE后台管理系统-axios接口测试+router动态路由
工具:Visual Studio Code + Vue + Vue cil2 + Vuex + Vue Router + ElementUI + axios
项目代码地址:macrozheng_mall学习: 学习macrozheng的mall项目,进行学习记录与代码拆解 - Gitee.com
1.8加入vuex
第一步先添加 vuex 的实例 store,在 src 文件夹中创建一个文件 store/index.js ,我们还需要添加 module ,再在 store 文件夹中创建两个文件 modules/permission.js(用来存储动态路由的)、modules/user.js(用来存储用户信息的),最后为了使用方便添加一个 store/getter.js 文件,是将我们 modules 里的所有 state 添加到一个 getter 对象,方便在不同页面获取 state 值,如下:
代码:
//-----------store/index.js--------------------------
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import permission from './modules/permission'
import getters from './getters'
Vue.use(Vuex)
//在这里创建 store 对象,并添加模块
const store = new Vuex.Store({
modules: {
user, //这里加入 user 的 module 对象,对应的就是 user.js 里的
permission //这里加入 permission 的 module 对象,对应的就是 permission.js 里的
},
getters //这里加入 getter 对象,对应的就是 getter.js 里的
})
export default store
//-----------store/modules/user.js--------------------------这里我们去掉了头像等不重要的内容,请酌情去除
import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
const user = {
state: {
token: getToken(),
name: '',
roles: []
},
//同步
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
},
SET_ROLES: (state, roles) => {
state.roles = roles
}
},
//异步
actions: {
// 登录,这里就可以代替我们在 login.vue 中点击登录的逻辑。
Login({ commit }, userInfo) {
const username = userInfo.username.trim()
return new Promise((resolve, reject) => {
login(username, userInfo.password).then(response => {
const data = response.data
const tokenStr = data.tokenHead+data.token
setToken(tokenStr)
commit('SET_TOKEN', tokenStr)
resolve()
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息,这里就可以代替我们在 1.7的 permission.js 中的 router 前置过滤器中 getInfo() 获取用户信息的逻辑
GetInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo().then(response => {
const data = response.data
if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
commit('SET_ROLES', data.roles) //在 router 前置过滤器中获取并判断是否已添加动态路由
} else {
reject('getInfo: roles must be a non-null array !')
}
commit('SET_NAME', data.username)
resolve(response)
}).catch(error => {
reject(error)
})
})
},
// 登出
LogOut({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
removeToken()
resolve()
}).catch(error => {
reject(error)
})
})
},
// 前端 登出
FedLogOut({ commit }) {
return new Promise(resolve => {
commit('SET_TOKEN', '')
removeToken()
resolve()
})
}
}
}
export default user
//-----------store/modules/permission.js--------------------------
import { asyncRouterMap, constantRouterMap } from '@/router/index';
//判断是否有权限访问该菜单
function hasPermission(menus, route) {
...
}
//根据路由名称获取菜单
function getMenu(name, menus) {
...
}
//对菜单进行排序
function sortRouters(accessedRouters) {
...
}
//降序比较函数
function compare(p){
...
}
const permission = {
state: {
routers: constantRouterMap, //总路由信息,先默认为静态路由
addRouters: [] //添加的路由
},
mutations: { //同步,安全
SET_ROUTERS: (state, routers) => {
state.addRouters = routers;
state.routers = constantRouterMap.concat(routers);
}
},
actions: { //异步,不安全
// 获取动态路由,这里就可以代替我们在 1.7的 permission.js 中的 router 前置过滤器中 getInfo().then 获取动态路由的逻辑
GenerateRoutes({ commit }, data) {
return new Promise(resolve => {
const { menus } = data;
const { username } = data;
const accessedRouters = asyncRouterMap.filter(v => {
//admin帐号直接返回所有菜单
// if(username==='admin') return true;
if (hasPermission(menus, v)) {
if (v.children && v.children.length > 0) {
v.children = v.children.filter(child => {
if (hasPermission(menus, child)) {
return child
}
return false;
});
return v
} else {
return v
}
}
return false;
});
//对菜单进行排序
sortRouters(accessedRouters);
commit('SET_ROUTERS', accessedRouters); //最终都要调用同步方法设置 vuex 的 data 属性。
resolve();
})
}
}
};
export default permission;
//-----------store/getter.js--------------------------
//里面的 state 值都是 module 里面的 state 对应的,
const getters = {
//例如: token 是我们通过 $store.getters.token 使用的, state.user.token 对应的就是 user.js 里面的 token 值
token: state => state.user.token,
name: state => state.user.name,
roles: state => state.user.roles,
addRouters: state => state.permission.addRouters,
routers: state => state.permission.routers
}
export default getters
第二步 ,将 store 添加到 main.js 里面
//---------main.js-------------
...
import store from './store' //添加进去
...
new Vue({
el: '#app',
router,store, //添加进去
components: { App },
template: '<App/>'
})
之后我们在其余页面就可以通过下面的方式获取存入的 state 数据:
方式一:--------store对象------------
import store from './store' //添加进去
store.getters.***
方式二:--------getter对象-----------
{{routes}}
import { mapGetters } from "vuex";
export default {
//通过几个数据的变化,来影响一个数据,
computed: {
...mapGetters(["routers"]), //routers是 getter 的变量名
routes() {
return this.routers;
},
},
};
如果我们要添加或者设置 state 数据,就通过 store.dispatch('[actions名]').then (异步)来调用方法,然后在这个 action 里面通过commit('[mutations名]') (同步)来修改 state 变量。
添加完毕,我们实现一下 router 动态路由测试一下吧!
1.9使用vuex实现router动态路由
第一步,先获取到路由信息,也就是先修改router的前置过滤器,打开 src/permission.js 文件,将原来 if(!getHaveAuth()){ ...}这里的内容换成调用 store 的action 的内容,
//----src/permission.js-----------
//去掉 function hasPermission,getMenu,sortRouters,compare方法,这些直接在 store 的 permission 中使用
import store from './store' //使用前先引入
router.beforeEach((to, from, next) => {
... //将原来 21行 - 61行 的内容换成下面的内容
//这里需要加个判断哈,如果添加了当前用户的路由就不需要再次添加了,否则程序会警告重复路由。
//getHaveAuth()这个也是保存到了 cookie 中,我直接写在了 auth.js 里面
if (store.getters.roles.length === 0) { //roles 是在 store 的 GetInfo action 里面加入到 cookie 的。
store.dispatch('GetInfo').then(res => { // 拉取用户信息,将 menu username 等传给 GenerateRoutes action
let menus = res.data.menus;
let username = res.data.username;
store.dispatch('GenerateRoutes', { menus, username }).then(() => { // 生成可访问的路由表
router.addRoutes(store.getters.addRouters); // 动态添加可访问路由表
next({ ...to, replace: true })
})
}).catch((err) => { //如果上面的失败了,就需要退出操作,清除 token 值,防止登陆成功但没有权限
store.dispatch('FedLogOut').then(() => {
Message.error(err || 'Verification failed, please login again')
next({ path: '/' })
})
})
} else {
next()
}
...
}
第二步,修改登录时的 token 保存,在 login.js 文件中的 handleLogin() 方法进行修改
//-----login.js-----------
//这里引入cookie通用方法来保存 username ,passwd 。注意password最好不要保存在前端!!!
import {setSupport,getSupport,setCookie,getCookie} from '@/utils/support';
//登陆核心
handleLogin() {
this.$refs.loginForm.validate((valid) => {
if (valid) {
this.loading = true;
//将 110 - 122 行的代码替换成下面的
this.$store.dispatch('Login', this.loginForm).then(() => {
//异步调用成功之后会执行 then
this.loading = false;
setCookie("username",this.loginForm.username,15);
setCookie("password",this.loginForm.password,15);
this.$router.push({path: '/home'})
}).catch(() => {
this.loading = false
})
} else {
console.log("参数验证不合法!");
return false;
}
});
第三步,修改左侧导航栏 sidebar 的内容,这里需要从 store 中获取数值
//------sidebar/index.vue------------
<template>
<el-menu
mode="vertical"
:show-timeout="200"
:default-active="$route.path"
background-color="#304156"
text-color="#bfcbd9"
active-text-color="#409EFF"
>
<sidebar-item :routes="routes"></sidebar-item>
</el-menu>
</template>
<script>
import SidebarItem from "./SidebarItem";
import { mapGetters } from "vuex";
export default {
components: { SidebarItem },
//通过几个数据的变化,来影响一个数据,如果使用 data ,那store改变时就无法影响这里了
computed: {
...mapGetters(["routers"]),
routes() {
// return this.$router.options.routes
return this.routers;
},
},
//注意不能用下面这种方式!!!
// data(){
// return{
// ...mapGetters(["routers"]),
// routes:this.routers
// }
// }
};
</script>
最后,我们测试一下,访问 http://localhost:8080/#/home 接口,直接跳转到 login 路由,输入用户名密码,跳转到 /home 页面,动态导航栏添加成功!!!
1.10修改动态面包屑
这里我们也添加一下面包屑吧,具体的按照什么格式展示看业务了,这里我们按照 mall 系统提供的导航栏展示,不过哪一个页面最前端都显示首页,之后跟着一级导航栏和二级导航,二级导航这里还显示其余的页面的内容,也就是 router 中一级导航的 child 页面,
例如:首页/权限/用户列表 ,是二级导航用户列表的面包屑,而他的其余页面 新增用户页面 的面包屑就是 首页/权限/添加用户。
第一步,在 src/component 文件夹中添加 Breadcrumb/index.vue 文件,里面封装 elementUI 的面包屑的组件,先获取当前路由的信息,然后将我们需要展示的路由信息拼接在一起:
//-------breadcrumb/index.vue-------------------------
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb">
<!-- 当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中 -->
<!-- 这里是拿到当前访问的路由信息,然后获取到他的父级路由信息(如果有的话),最后将首页拼到最前面,后面跟着父级和自己的name -->
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path" v-if="item.meta.title">
<span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">{{item.meta.title}}</span>
<router-link v-else :to="item.redirect||item.path">{{item.meta.title}}</router-link>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script>
export default {
created() {
this.getBreadcrumb()
},
data() {
return {
levelList: null
}
},
//1.watch擅长处理的场景:一个数据影响多个数据(修改主数据的时候,方法中影响相应的数据)
// 2.computed擅长处理的场景:一个数据受多个数据影响(修改某数据的时候,若有主数据,会影响主数据的)
watch: {
$route() {
this.getBreadcrumb()
}
},
methods: {
//拿到当前路由页面的信息,如果不是首页,就将首页的信息添加到第一个
getBreadcrumb() {
let matched = this.$route.matched.filter(item => item.name)
const first = matched[0]
if (first && first.name !== 'home') {
matched = [{ path: '/home', meta: { title: '首页' }}].concat(matched)
}
this.levelList = matched
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 10px;
.no-redirect {
color: #97a8be;
cursor: text;
}
}
</style>
第二步,在 Navbar.vue 中引入面包屑组件
//-------Navbar.vue---------------------
<template>
<el-menu class="navbar" mode="horizontal">
<breadcrumb></breadcrumb>
</el-menu>
</template>
<script>
import Breadcrumb from '@/components/Breadcrumb'
export default {
components: {
Breadcrumb,
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.navbar {
height: 50px;
line-height: 50px;
border-radius: 0px !important;
}
</style>
添加完毕,我们直接打开就会发现顶部的面包屑
okkkk,到目前为止,所有的大类环境都已经添加完毕,自己对 vue 和相关组件的使用也相对的熟悉了一些,之后就是拿项目练手啦,加油吧少年~~~///(^v^)\\\~~~。