了解vuex和vue ls
vuex:
用于前端存储数据,Vuex是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间数据的共享。它通过在内存中存储数据来实现数据的存储和共享。 Vuex支持存储任意类型的数据,包括对象、数组等。Vuex中的状态存储是响应式的,当Vue组件从store中读取状态时,若store中的状态发生变化,相应的组件也会得到更新。而全局对象的状态存储是静态的,无法自动更新组件中的状态
网页刷新可能会导致Vuex状态丢失的问题
Vue-ls 是 Vue 的一个插件,用于操作 Local Storage(本地存储)、Session Storage(会话存储)、Memory(内存存储)
//npm版本安装
npm install vue-ls --save
npm install vuex --save
//yarn版本安装
npm install -g yarn //已安装可以省略
yarn install //初始化
yarn add vuex//安装vuex
yarn add vue-ls
了解vuex的使用
有篇大佬博文写的非常好推荐去看https://blog.csdn.net/qq_30769437/article/details/126202906
main.js中
import Vue from 'vue'
import Storage from 'vue-ls';
import router from './routers/index'
Vue.use(Storage);//使用vue-ls插件
//省略其他
new Vue({
router:router,
store ,
created: bootstrap,
render: h => h(App),
}).$mount('#app')
//举例vuex的用法 user.js
import Vue from 'vue'
import { login, getInfo, logout } from '@/api/login'
import { postAction, getAction, deleteAction, putAction, downFilePost, downFileTimeOut } from "@/api/manage"
import moment from 'moment';
import { ACCESS_TOKEN } from '@/store/mutation-types'
import {resetRouter} from '@/routers/index';
const publicKey = process.env.VUE_APP_LOGIN_PUBLIC_KEY;
//声明了一个存储对象
const user = {
state: {
token: '',
name: '',
roles: [],
permissions:[],
address:'',
},
//这是一个对象赋值的方法 通过 commit('SET_NAME', '') 这种调用
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name ) => {
state.name = name
},
SET_ROLES: (state, roles) => {
state.roles = roles
},
SET_PERMISSIONS:(state, permissions) =>{
state.permissions = permissions;
},
SET_ADDRESS:(state, address) =>{
state.address = address;
}
},
//响应外部vue组件的动作处理数据
// 通过外部vue中的方法调用
//...mapActions(['Login', 'Logout']),
//store.dispatch('store.dispatch','')
actions: {
// 登录
Login ({ commit }, userInfo) {
return new Promise((resolve, reject) => {
var loginUr='/bookstore/auth/login'
console.log("param:userInfo",userInfo);
postAction(loginUr, userInfo)
.then(res => {
if (res.code == 200) {
var result=res.result;
Vue.ls.set(ACCESS_TOKEN, result.token)
commit('SET_NAME', result.name)
commit('SET_ADDRESS', result.address)
resolve(res)
} else {
reject(res)
}
}).catch(error => {
reject(error)
})
})
},
// 登出
Logout ({ commit, state }) {
return new Promise((resolve) => {
var loginUr='/bookstore/auth/logout'
getAction(loginUr, {})
.then(res => {
resolve()
}).catch(error => {
reject(error)
}).finally(() => {
commit('SET_TOKEN', '')
commit('SET_NAME', '')
commit('SET_ADDRESS', '')
commit('SET_ROUTERS', [])
// commit('SET_PERMISSIONS',[])
Vue.ls.remove(ACCESS_TOKEN)
resetRouter()
})
})
},
GetInfo({ commit, state }) {
return new Promise((resolve, reject) => {
var loginUr='/bookstore/auth/info'
getAction(loginUr, {})
.then(res => {
if (res.code == 200) {
var result=res.result;
resolve(result)
} else {
reject(res)
}
}).catch(error => {
reject(error)
})
})
}
}
}
export default user
store目录index.js
import Vue from 'vue'
import Vuex from 'vuex'
import { constantRouterMap } from '@/config/router.config'//定义的初始路由
import { generatorDynamicRouter } from '@/routers/generator-routers'//
import user from './modules/user'
import permission from './modules/async-router'
import getters from './getters'
//必须先使用否则会报错
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
user,
},
state: {},
mutations: {},
actions: {},
getters
})
getter.js
//作用就是为了方便
//import store from './store'
//通过store.getters.addRouters可以直接获取state.permission.addRouters
const getters = {
token: state => state.user.token,
name: state => state.user.name,
userInfo: state => state.user.info,
addRouters: state => state.permission.addRouters,
routers: state => state.permission.routers,
currentPrescriptionId: state => state.doctorStation.currentPrescriptionId
}
export default getters
Login.vue中调用
import 'moment/locale/zh-cn';
import locale from 'ant-design-vue/es/date-picker/locale/zh_CN';
import { postAction, getAction, deleteAction, putAction, downFilePost, downFileTimeOut } from "@/api/manage"
import moment from 'moment';
import { mapState } from 'vuex'
import { mapActions } from 'vuex'
export default {
name: 'Login',
data () {
return {
labelCol: {
xs: { span: 4, offset: 2 },
sm: { span: 4, offset: 2 }
},
wrapperCol: {
xs: { span: 10 },
sm: { span: 10 }
},
url: {
login: '/auth/login',
nvl: '/auth/nvl'
},
userForm: {
pass: '',
name: '',
},
rules: {
pass: [
{ required: true, message: '密码不能为空', trigger: 'blur' },
{ min: 6, max: 12, message: '密码长度必须是6-12位', trigger: 'blur' }
],
name: [
{ required: true, message: '账号不能为空', trigger: 'change' },
{ min: 3, message: '账号长度必须大于3位', trigger: 'blur' }],
},
}
},
mounted: function () {
},
computed: {
//固定写法获取state中的数据
...mapState({
// 动态主路由
mainMenu1 (state) {
return state.menuRouters
}
}),
},
methods: {
//vuex 中定义好的方法
...mapActions(['Login', 'Logout']),//
//登录成功
loginSuccess (res) {
console.log("登录完毕即将跳转")
this.$router.push({ path: '/home' })
},
submitForm (formName) {
this.$refs[formName].validate(valid => {
if (valid) {
var param = {
code: this.userForm.name,
password: this.userForm.pass
}
const { Login } = this
//调用登录
Login(param)
.then((res) => this.loginSuccess(res))
.catch(err => {
this.$message.warn("登录失败:" + err.remark);
})
.finally(() => {
})
} else {
this.$message.warn("校验失败!请输入正确的格式再登录!");
return false;
}
});
},
},
}
}
通过以上可以简单学会vuex的使用,这是动态路由的前提
动态路由的原理是
2每次路由跳转会通过permission.js进行判断和加载,若vuex中无用户路由数据则请求后端,
根据后端返回的用户的菜单地址进行处理拼装成路由格式,存储到vuex中,并添加到路由里面,若vuex中有用户路由数据,直接放行
permission.js
import router from './routers'
import store from './store'
import { ACCESS_TOKEN } from '@/store/mutation-types'
const whiteList = ['login', 'register'] // 白名单
const defaultRoutePath = '/home'
//遍历循环
router.beforeEach((to, from, next) => {
console.log('start!!!',to,from)
//判断是否登录成功获取登录信息 这里可以用token判断
if(Vue.ls.get(ACCESS_TOKEN)){
if (to.path === '/login') {
next({ path: defaultRoutePath })
//NProgress.done() 浏览器进度条
} else {
console.log('addRouters',store.getters.addRouters)
if(store.getters.addRouters.length === 0){
//这里会请求后端和组装路由
store.dispatch('GenerateRoutes', '').then(() => {
//讲路由添加进去
var newRoutes=store.getters.addRouters;
// console.log('newRoutes',newRoutes)
for (let index = 0; index < newRoutes.length; index++) {
const element = newRoutes[index];
//将路由添加进去
router.addRoute(element);
}
//url编码
const redirect = decodeURIComponent(from.query.redirect || to.path)
if (to.path === redirect) {
// console.log('去redirect',redirect)
//中断当前导航,执行新的导航
next({ ...to, replace: true })
} else {
// 跳转到目的路由
//console.log('去home',to.path)
next({ path: defaultRoutePath })
}
}) .catch(() => {
console.log('错误请重新登录')
store.dispatch('Logout').then(() => {
next({ path: '/login'})
})
})
}else{
//放行
next()
}
}
}else{`在这里插入代码片`
if (whiteList.includes(to.name)) {
// 在免登录白名单,直接进入
next()
} else {
console.log('认证失败重新登录')
next({ path: '/login' })
}
}
})
router.afterEach(() => {
})
async-router.js
/**
* 向后端请求用户的菜单,动态生成路由
*/
import { constantRouterMap } from '@/config/router.config'
import { generatorDynamicRouter } from '@/routers/generator-routers'//
const permission = {
state: {
routers: constantRouterMap,
addRouters: []
},
mutations: {
SET_ROUTERS: (state, routers) => {
state.addRouters = routers
//state.routers = constantRouterMap.concat(routers)
console.log('state.addRouters', state.addRouters )
console.log('state.routers', state.routers )
}
},
actions: {
GenerateRoutes ({ commit }, data) {
//入口
console.log('GenerateRoutes:');
return new Promise(resolve => {
const { token } = data
generatorDynamicRouter(token).then(routers => {
console.log('获取返回的routers:',routers);
commit('SET_ROUTERS', routers)
resolve()
})
})
}
}
}
export default permission
constantRouterMap
export const constantRouterMap = [
{
path: '/login',
name: 'login',
component: () => import('../views/bookstore/Login.vue')
},
{
path: '/register',
name: 'register',
component: () => import('../views/bookstore/Register.vue')
},
// {
// path: '/',//必须有
// component: () => import('../layouts/MainLayout.vue'),//一般是layout布局
// // redirect: '/home',//跳转到
// children:[
// {
// path: 'home',//必须没有才会拼接
// name: 'home',
// component: () => import('../views/bookstore/Home.vue')
// },
// {
// path: 'order',
// name: 'order',
// component: () => import('../views/bookstore/Order.vue')
// },
// {
// path: 'books',
// name: 'books',
// component: () => import('../views/bookstore/Books.vue')
// },
// {
// path: 'car',
// name: 'car',
// component: () => import('../views/bookstore/ShoppingCar.vue')
// },
// {
// path: 'adminBook',
// name: 'adminBook',
// component: () => import('../views/bookstore/AdminBook.vue')
// },
// {
// path: 'adminOrder',
// name: 'adminOrder',
// component: () => import('../views/bookstore/AdminOrder.vue')
// },
// ]
// }
]
routers目录下的generator-routers.js
import { BlankLayout } from '@/layouts/BlankLayout.vue'
import { MainLayout } from '@/layouts/MainLayout.vue'
import { postAction, getAction, deleteAction, putAction, downFilePost, downFileTimeOut } from "@/api/manage"
// 前端路由表
const constantRouterComponents = {
// 基础页面 layout 必须引入
MainLayout: MainLayout,
BlankLayout: BlankLayout,
'Home':() =>import('../views/bookstore/Home.vue'),
'Order':() => import('../views/bookstore/Order.vue'),
'Book':() => import('../views/bookstore/Books.vue'),
'ShoppingCar':() => import('../views/bookstore/ShoppingCar.vue'),
'AdminBook':() => import('../views/bookstore/AdminBook.vue'),
'AdminOrder':() => import('../views/bookstore/AdminOrder.vue'),
}
// 菜单的根级菜单
const rootRouter = {
path: '/',//必须有
component: () => import('../layouts/MainLayout.vue'),//一般是layout布局
// redirect: '/home',//登录后默认首页改这里
children:[]
}
/**
* 动态生成菜单
* @param token
*/
export const generatorDynamicRouter =(token) => {
return new Promise((resolve, reject) => {
//查询用户的菜单
var getNal='/bookstore/auth/nvl'
getAction(getNal, {})
.then(res => {
if (res.code == 200) {
const menuNav = []
const childrenNav = generator(res.result)
rootRouter.children = childrenNav
menuNav.push(rootRouter)
resolve(menuNav)
} else {
reject(res.remark)
}
}).catch(err => {
reject(err)
})
})
}
/**
* 格式化树形结构数据 生成 vue-router 层级路由表
*
* @param routerMap
* @returns {*}
*/
export const generator = (routerMap) => {
return routerMap.map(item => {
const currentRouter = {
path: item.path,
name: item.name,
component: (constantRouterComponents[item.component || item.key]) || (() => import(`../views/bookstore/${item.component}`)),
isVisible: item.isVisible,
title:item.title,
icon:item.icon
}
return currentRouter
})
}
/**
* 数组转树形结构
* @param list 源数组
* @param tree 树
* @param parentId 父ID
*/
// const listToTree = (list, tree, parentId) => {
// list.forEach(item => {
// // 判断是否为父级菜单
// if (item.parentId === parentId) {
// const child = {
// ...item,
// key: item.key || item.name,
// children: []
// }
// if (item.isHome) {
// rootRouter.redirect = item.path
// }
// // 迭代 list, 找到当前菜单相符合的所有子菜单
// listToTree(list, child.children, item.id)
// // 删掉不存在 children 值的属性
// if (child.children.length <= 0) {
// delete child.children
// }
// // 加入到树中
// tree.push(child)
// }
// })
// }
MainLayout.vue 动态菜单需要用layout布局实现,路由放行后会加载layout页面并生成加公共动态菜单栏
<template>
<a-layout class="ant-layout">
<!-- 头部导航栏-->
<a-layout-header style="background: #fff">
<div class="logo" />
<a-menu
theme="light"
mode="horizontal"
:open-keys="openKeys"
@click="menuClick"
>
<a-menu-item key="1" disabled>你好,{{ name }}</a-menu-item>
<template v-for="item in routers">
<a-menu-item :key="`${item.path}`">
<a-space>
<a-icon :type="`${item.icon}`" />
<router-link
:to="{
path: `${item.path}`,
}"
>
{{ item.title }}
</router-link>
</a-space>
</a-menu-item>
</template>
<a-menu-item key="2">退出登录</a-menu-item>
</a-menu>
</a-layout-header>
<a-layout-content>
<RouterView />
</a-layout-content>
</a-layout>
</template>
<script >
import { RouterLink, RouterView } from "vue-router";
import { mapState, mapActions } from 'vuex'
export default {
name: 'MainLayout',
data () {
return {
isCollapsed: false,
onlyOneChild: null,
openKeys: [''],
menus: [],
routers: [],
menusRoot: [],
url: {
getUserInfo: '/bookstore/sysUser/getUserInfo'
},
sysUser: {
name: '',
code: '',
}
}
},
computed: {
//获取vuex中的数据
...mapState({
mainMenu: state => state.permission.addRouters,
name: state => state.user.name,
}),
},
watch: {
collapsed: {
handler: function (val) {
this.isCollapsed = val
}
}
},
created () {
this.menusRoot = this.mainMenu[0].children;
this.routers = this.menusRoot.filter(item => {
if (item.isVisible === true) {
return item;
}
});
console.log('routers', this.routers)
console.log('name', this.name)
},
methods: {
...mapActions(['Login', 'Logout']),//
// 点击菜单触发事件
menuClick ({ item, key, keyPath }) {
if (key == 2) {
const { Logout } = this
Logout()
.then((res) => this.$router.push({ path: '/login' })
.catch(err => {
this.$message.warn("退出失败:" + err.remark);
})
.finally(() => {
})
)
}
},
}
}
</script>
<style >
</style>
效果图
后端返回数据,模拟从数据库获取普通用户和管理员路由
@Override
public List<Map> getNvl(SysUser sysUser) {
List<Map> list=new ArrayList<>();
Map map1=new HashMap();
map1.put("name","home");
map1.put("component","Home");
map1.put("icon","book");
map1.put("path","home");
map1.put("title","首页");
map1.put("isVisible",true);
Map map2=new HashMap();
map2.put("name","books");
map2.put("component","Books");
map2.put("icon","filter");
map2.put("path","books");
map2.put("title","图书分类");
map2.put("isVisible",false);
Map map3=new HashMap();
map3.put("name","car");
map3.put("component","ShoppingCar");
map3.put("icon","shopping-cart");
map3.put("path","car");
map3.put("title","购物车");
map3.put("isVisible",true);
Map map4=new HashMap();
map4.put("name","order");
map4.put("component","Order");
map4.put("icon","shop");
map4.put("path","order");
map4.put("title","我的订单");
map4.put("isVisible",true);
//管理员可见
if (sysUser.getIsAdmin()==1){
Map map5=new HashMap();
map5.put("name","adminBook");
map5.put("component","AdminBook");
map5.put("icon","folder");
map5.put("path","adminBook");
map5.put("isVisible",true);
map5.put("title","图书管理");
Map map6=new HashMap();
map6.put("name","adminOrder");
map6.put("component","AdminOrder");
map6.put("icon","dollar");
map6.put("path","adminOrder");
map6.put("title","订单管理");
map6.put("isVisible",true);
list.add(map5);
list.add(map6);
}
list.add(map1);
list.add(map2);
list.add(map3);
list.add(map4);
return list;
}
前端效果: 打印出存储的路由信息