### Vue.js
## 一、 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'
})
}
## 三、 自定义全局组件(插件)
全局组件(插件):就是指可以在main.js中使用Vue.use()进行全局引入,然后在其他组件中就都可以使用了,如vue-router
import VueRouter from 'vue-router'
Vue.use(VueRouter);
普通组件(插件):每次使用时都要引入,如axios
import axios from 'axios'
## 插件, 全局组件, 局部组件的演示
component-demo
vue init webpack-simple component-demo
### 第一种方式:插件
// 方式一: 插件通常用来为 Vue 添加全局功能 , 插件方式引入组件
新建 components/user , 并添加index.js和 Login.vue
Login.vue
```javascript
<template>
<div id="login">
{{msg}}
</div>
</template>
<script>
export default {
data(){
return {
msg:'用户登陆'
}
}
}
</script>
<style scoped>
#login{
color:red;
font-size:20px;
text-shadow:2px 2px 5px #000;
}
</style>
```
index.js
```javascript
import Login from './Login.vue'
export default {
install:function(Vue){
Vue.component('Login',Login);
}
}
```
在main.js里引入并使用插件
main.js
import Login from '../components/user/index.js'
Vue.use(Login);
在vue页面中可以直接使用 ,如这里在 App.vue中使用
App.vue
```javascript
...
<h2>1. 插件方式引入</h2>
<Login ></Login>
....
```
## 第二种方式 :全局组件方式引入
main.js
在components里添加common文件夹 ,并添加info.vue popwin.vue
简单点写 info.vue popwin.vue 的内容
// 全局组件方式引入
// import commonInfo from '../components/common/info.vue'
// import commonPopwin from '../components/common/popwin.vue'
// 注册为全局组件
// Vue.component('commonInfo',commonInfo);
// Vue.component('commonPopwin',commonPopwin);
在页面中直接使用, 如这里在 App.vue中使用
App.vue
```javascript
...
<h2>2. 全局组件方式引入</h2>
<commonPopwin></commonPopwin>
<commonInfo></commonInfo>
....
```
## 方式三: 如果项目插件比较多的时候,在components在common里新建index.js
index.js代码:
```javascript
import commonInfo from './info.vue'
import commonPopwin from './popwin.vue'
export default {
commonInfo,
commonPopwin
}
```
在main.js引入
main.js代码:
//通过components下common的index.js文件导入组件
// import commonComponents from '../components/common/index.js';
import commonComponents from '../components/common';
Object.keys(commonComponents).forEach((key) => {
Vue.component(key, commonComponents[key])
});
在App.vue的全剧组件的使用方式同 方式二
## 局部组件方式引入
在app.vue页面中 局部引入
//局部引入组件
import myInfo from '../components/common/info.vue'
并在vue实例的选项添加
components:{myInfo}
在template里 应用
```javascript
<h2>3. 局部组件方式引入</h2>
<myInfo ></myInfo>
```