## 一、 addRoutes权限控制
场景: 对登陆成功后的用户可能会有不同的身份权限, 看到的系统菜单以及功能不一样, 这个时候需要用到 动态路由的方式来处理
路由结构: |--- initRoutes 默认是可以看到的路由,是所有用户都可以看到的路由菜单 |--- asyncRouetes 需要登陆后确认权限才能看到的路由
### 1.1 初始路由initRoutes
var initRoutes=[ { path:'/home', component:Home, // 路由元信息 meta: { title: '首页' } }, { path:'/user', component:User, meta: { title: '用户' }, children:[ { path:'login', component:Login }, { path:'regist/:username/:password', component:Regist } ] }, { path:'*', redirect:'/home', hidden: true //隐藏不需要渲染到页面上的路由 } ];
### 1.2 动态路由 asyncRouetes
//需要登陆后确认权限才能看到的路由 var asyncRouetes = [ { path:'/finance', component:Finance, meta: { title: '财务信息', roles: ['admin'] } }, { path:'/news', component:News, meta: { title: '新闻中心', roles: ['admin','guest'] } } ];
### 1.3 默认在vueRouters 实例化的时候, 只是传入初始的路由
const routerAll=new VueRouter({ routes:initRoutes, linkActiveClass:'active', //更新活动链接的class类名,默认的激活的 class linkExactActiveClass:'active-extact', //精确激活的 class mode: "hash", //默认
});
在vue实例化的时候进行挂载
### 1.4 进行登录处理,获取token和用户信息
localStorage.setItem('token','XXXXXXXXXXX'); localStorage.setItem('userRole','admin'); //submain, guest
### 1.5 添加全局路由守卫
// 导航守卫 -- 全局前置守卫 routerAll.beforeEach((to,from,next)=>{ //这里可以获取登陆后的权限, 如果是admin var auth = localStorage.getItem('userRole'); asyncRouetes = asyncRouetes.filter((item,index)=>{ return item.meta.roles.includes(auth) }) //将新路由添加到路由中, 如果不加组件component不能正确渲染 routerAll.addRoutes(asyncRouetes); //为了正确渲染导航,将对应的新的路由添加到routerAll中 routerAll.options.routes = [...initRoutes,...asyncRouetes]; next(); })
## 二、 权限控制在项目中的实际使用
### 2.1 配置必要的动态路由文件
在router文件夹下添加 dynamic.js 在src/pages文件夹下添加 finance.vue, staffs.vue作为测试 var asyncRouetes = [ { path:'/finance', component:()=>import('../pages/finance.vue'), meta: { title: '财务信息', roles: ['admin'] } }, { path:'/staffs', component:()=>import('../pages/staffs.vue'), meta: { title: '员工信息', roles: ['admin','guest'] } } ]; export default asyncRouetes;
### 2.2 登录成功以后需要获取toekn以及用户信息 localStorage.setItem('token','XXXXXXXXXXX'); localStorage.setItem('userRole','admin'); //submain, guest
### 2.3 在入口文件main.js进行 导航守卫
引入文件: import asyncRouetes from './router/dynamic.js'; 全局前置守卫配置:
注意:路由守卫的时候是针对vueRouter实例对象
```javascript VueRouter.beforeEach((to,from,next)=>{ //如果自定义了标题就取标题,否则拿全局标题 window.document.title = to.meta.title?to.meta.title:'测试系统';
//这里可以获取登陆后的权限 var UserToken = localStorage.getItem('token'); var userRole = localStorage.getItem('userRole');
//判断是否存在token if(UserToken && userRole){ //已登录 var asyncRouteMenu = asyncRouetes.filter((item,index)=>{ return item.meta.roles.includes(userRole) })
//将新路由添加到路由中, 如果不加组件component不能正确渲染 VueRouter.addRoutes(asyncRouteMenu); //为了正确渲染导航,将对应的新的路由添加到VueRouter中 var initRoutes = VueRouter.options.routes; VueRouter.options.routes = [...initRoutes,...asyncRouteMenu]; next();
} else { //是否处于登陆页面 if(to.path=='/login'){ //如果是登录页面路径,就直接next() next(); } else { //不然就跳转到登录; next('/login'); } } }) ```
### 2.3 如何处理菜单的显示 在App.vue里 ,原来的菜单部分
<router-link to="/login" tag='li'>登陆</router-link> <router-link to="/home?name=laney" tag='li'>主页</router-link> <router-link to="/news" tag='li'>新闻</router-link>
需要修改为动态的, 因为这里所有的路由 不是写死的, 需要从路由实例this.$router.options里获取
可以在计算器属性里设置 ```javascript computed:{ getMyRoutes(){ var thisData = this.$router.options.routes; thisData = thisData.filter((option,index)=>{ return !option.hidden }) return thisData; } }, ```
然后把 原来菜单部分 改成如下:
<router-link tag='li' v-for="(item,index) in getMyRoutes" :key="index" :to="item.path">{{item.meta.title}} </router-link>
### 2.4 判断不要重复的添加动态路由
现在看似成功了, 但是可以优化下,不需要重复的对动态路由进行合并,如果已经添加 直接next就行
var allPaths = []; asyncRouetes.forEach((option)=>{ allPaths.push(option.path); })
.....
//需要判断下是否已经添加过动态路由,不要重复添加 // 方式: 判断默认和路由和 读取的路由是否一致 var isHAS = initRoutes.some((option)=>{ return allPaths.includes(option.path) }); if(isHAS){ next(); return; }
### 2.5 添加注销功能 在App.vue里 <button type="button" @click="logOut()">注销</button> methods:{ ... logOut(){ localStorage.clear(); this.$router.push({ path:'/login' }) } }
### 2.6 处理注销 与登录 后的动态菜单的实时更新
更新路由实例上 的 options后 ,VueRouter.options.routes 如果才能在App.vue页面里的菜单也能实时的更新
2.6.1. 借助中心控制vue实例 , 利用eventEmitter 2.6.2. 利用vuex
#### 2.6.1. 借助中心控制vue实例 , 利用eventEmitter window.EventEmitter = new Vue();
在main.js的全局前置守卫添加 进行路由的 发布 EventEmitter.$emit('allOption',VueRouter.options.routes);
在App.vue里进行数据的订阅监听
去掉计算器属性getMyRoutes, 在data变量里添加一个 变量getMyRoutes
在生命周期函数 mounted里添加 window.EventEmitter.$on('allOption',(res)=>{ this.getMyRoutes = res; })
## 二、 自定义指令的讲解
### 2.1 复习之前学的指令钩子函数
/** * 自定义全局指令 * 注:使用指令时必须在指名名称前加前缀v-,即v-指令名称 */ //钩子函数的参数 el,binding Vue.directive('hello',{ bind(el,binding){ //常用!! // console.log(el); //指令所绑定的元素,DOM对象 el.style.color='red'; console.log(binding); //name
console.log('bind:指令第一次绑定到元素上时调用,只调用一次,可执行初始化操作'); }, inserted(el,binding){ console.log(el) // binding.arg:传给指令的参数 console.log('inserted:被绑定元素插入到DOM中时调用'); }, update(el,binding){ console.log(el) console.log('update:被绑定元素所在模板更新时调用,模板还没更新完成'); }, componentUpdated(el,binding){ console.log(el) console.log('componentUpdated:被绑定元素所在模板完成一次更新周期时调用'); }, unbind(el,binding){ console.log('unbind:指令与元素解绑时调用,只调用一次'); } });
### 2.2 封装弹框的函数以及拖拽的功能
```javascript function initPopHtml(title,html){ var popOut = document.getElementById('popOut') ; if(popOut) { return; } var popOut = document.createElement('div'); popOut.id='popOut'; popOut.className = 'pop-out';
var innerText = ` <div class="heaad-title">${title}</div> <div class="pop-inner">${html}</div> <div class="footer"><button type="button" class="btn-close" id="btnClose">关闭</button></div>`; popOut.innerHTML = innerText;
document.querySelector('body').appendChild(popOut); toMoveDrag(popOut); document.getElementById('btnClose').addEventListener('click',()=>{ popOut.remove(); }); }
function toMoveDrag(boxDom){ var moveFlag = false; var dis={}; boxDom.querySelector('.heaad-title').onmousedown = function(e){ moveFlag=true; // 计算出鼠标落下点与边界的距离 dis.x = e.clientX - boxDom.offsetLeft; dis.y = e.clientY - boxDom.offsetTop; } document.onmousemove = function(e){ if (!moveFlag) { return; }; console.log(e.clientX); // 根据拖拽距离设置当前拖拽元素的位置 boxDom.style.left = (e.clientX - dis.x) + 'px'; boxDom.style.top = (e.clientY - dis.y) + 'px'; }; document.onmouseup = function(e){ moveFlag=false; }
} ```
### 2.3 指令的编写
```javascript
directives:{ popwin:{ bind(el,binding){ el.onclick = function(){ // binding.value :列表的数据 var data = binding.value; var listUl = []; listUl.push(`<ul class='user-list'>`) data.forEach((item,index)=>{ console.log(item); listUl.push(`<li><span>姓名:${item.name}</span><span>年龄:${item.age}</span></li>`) }) listUl.push(`</ul>`); initPopHtml(el.innerText,`<div class="content">${listUl.join('')}</div>`); } }, inserted(el,binding){ }, update(){ }, componentUpdated(){ } } }
```
### 2.4 使用指令 <button type="button" v-popwin="teacherlist">教师信息列表</button> <button type="button" v-popwin="studentlist">学生信息列表</button>
因为需要传递数据, 所以需要在data 里添加 变量teacherlist,studentlist
data(){ return { studentlist:[{ name:'song', age:'10' },{ name:'hong', age:'8' }], teacherlist:[{ name:'孙老师', age:'45' },{ name:'刘老师', age:'30' }] } },
### 2.5 如果需要改变学生 以及 教师的信息 <button type="button" @click="changeData()">添加学生信息列表</button> changeData(){ this.studentlist.push({ name:'alice', age:'10' }) }
## 三、 Vuex
### 1. 简介 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 简单来说,用来集中管理数据,类似于React中的Redux,都是基于Flux的前端状态管理框架
store里面有4个核心内容, State、 是存数据用的 Getters、 有点类似计算属性,进行简单的逻辑计算 Mutations、 是一些简单的方法,又来改变state Actions 、 Actions是较为复杂的方法,并不能直接改变状态,而是提交mutations,可以包含任意异步操作。
官方的意图: 1. 不要直接修改state 2. 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation ### 2. 基本用法 创建项目 vue init webpack-simple vuex-demo
#### 2.1 安装vuex npm install vuex -S 或者 yarn add vuex
#### 2.2 创建store.js文件,在main.js中导入并配置store选项 Vuex的核心是Store(仓库),相当于是一个容器,一个store实例中包含以下属性的方法: state 定义属性(状态、数据) getters 用来获取属性 actions 定义方法(动作) commit 提交变化,修改数据的唯一方式就是显式的提交mutations mutations 定义变化 注:不能直接修改数据,必须显式提交变化,目的是为了追踪到状态的变化
store.js ```javascript import Vue from 'vue' import Vuex from 'vuex'
Vue.use(Vuex); //定义属性(数据) var state={ count:6 }
//创建store对象 const store=new Vuex.Store({ state }) //导出store对象 export default store; ```
main.js ```javascript
import store from './store.js' //导入store对象
new Vue({ store, //配置store选项,指定为store对象,会自动将store对象注入到所有子组件中, // 在子组件中通过this.$store访问该store对象 el: '#app', render: h => h(App) })
```
在App.vue中添加:
获取数据: ```javascript <h1>{{ msg }}</h1> <h2>获取store仓库中的所有数据state</h2> {{$store.state}}
<h2>从计算属性中获取:</h2> {{getCount}} <br/> 基数还是偶数: {{isEvenOrOdd}}
computed:{ getCount:function(){ return this.$store.state.count }, isEvenOrOdd(){ return this.$store.state.count%2==0?'偶数':'奇数'; } }
```
如果有多个页面 获取 state里的数据,进行了一些简单的业务逻辑处理,可以借助store里的getters
从store的getters获取 {{$store.getters.isEvenOrOdd}} ```javascript
//定义getters var getters={ count(state){ return state.count; }, isEvenOrOdd(state){ return state.count%2==0?'偶数':'奇数'; } } ```
const store=new Vuex.Store({ state, getters })
#### 2.4 编辑App.vue 在子组件中访问store对象的两种方式: 方式1:直接通过this.$store访问 方式2:通过计算属性(相对于state数据变换后的) 方式3:通过在store里定义getters,相当于把计算属性里的计算方式拿到getters里封装,然后其他页面直接获取 方式4: 通过辅助函数mapState : 获取state 方式5: 通过辅助函数mapGetters :获取getters
#### 2.5 如何改变数据
在子组件中改变store对象里state的两种方式: 方式一:通过commit一个mutations 方式二:通过dispatch一个mutations 方式三:通过mapMutations、mapActions访问,vuex提供了两个方法: mapMutations 获取mapMutations mapActions 获取mapActions
##### 2.5.1 通过commit一个mutations
在store.js里添加
//定义mutations,处理状态(数据)的改变 const mutations={ // 没有参数的情况 // increment(state){ // state.count++; // },
// 有参数的情况 increment(state,payload){ state.count +=payload.data; },
decrement(state,payload){ state.count -=payload.data; // state.count--; } }
const store=new Vuex.Store({ state, getters, mutations })
在app.vue里 改变state
<button @click="increment">增加</button> <button @click="decrement">减小</button> <button @click="incrementAsync">增加</button>
methods:{ increment(){ this.$store.dispatch({ type:'increment', data:6 }) }, decrement(){ this.$store.dispatch({ type:'decrement', data:5 }) }, incrementAsync(){ this.$store.dispatch({ type:'incrementAsync', data:5 }) } }, mounted(){ // 改变数据 // 1. 直接改state .不推荐 this.$store.state.count = 80; //官方不推荐
// 2.通过仓库管理员mutation - 用来修改数据 this.$store.commit('increment',{ data:6 }) this.$store.commit({ type:'increment', data:5 })
} ##### 2.5.1 通过dispatch一个mutations //定义actions,要执行的操作,如流程判断、异步请求等 const actions = {
// increment(context,payload){//包含:commit、dispatch、state // console.log(context); // context.commit('increment',payload) // }, increment({commit},payload){ commit('increment',payload); //提交一个名为increment的变化,名称可自定义,可以认为是类型名 }, decrement({commit,state},payload){ if(state.count>10){ commit('decrement',payload); } }, incrementAsync({commit,state},payload){ //异步操作 var p=new Promise((resolve,reject) => { setTimeout(() => { resolve(); },3000); });
p.then(() => { commit('increment',payload); }).catch(() => { console.log('异步操作'); }); } }
const store=new Vuex.Store({ state, getters, mutations, actions })
在app.vue页面
// 3. 通过仓库总监 actions 发送请求 // this.$store.dispatch('increment',{ // data:6 // }); this.$store.dispatch({ type:'increment', data:60 })
### 3. 分模块组织Vuex
|-src |-store |-index.js |-getters.js |-actions.js |-mutations.js |-modules //分为多个模块,每个模块都可以拥有自己的state、getters、actions、mutations |-user.js |-cart.js |-goods.js |....