一、路由管理器说明
1.route:译为路由,可以理解为单个路由或者某一个路由。
2.routes:路由集合,可以理解为多个route的集合。
3.router:路由器,可以理解为路由集合的管理者。例如,当我们在页面上点击某个按钮之后,router就会去routes中查找route,即路由管理者会去路由集合中寻找路由。
二、路由依赖
在package.json文件中添加路由依赖
"dependencies": {
"axios": "^0.18.0",
"vue": "^2.5.2",
"vue-router": "^3.0.1",
"element-ui": "^2.3.7",
"js-cookie": "^2.2.0",
"nprogress": "^0.2.0",
"vuex": "^3.0.1",
"echarts": "^4.2.0-rc.2",
"normalize.css": "^8.0.0",
"v-charts": "^1.19.0",
"v-distpicker": "^1.0.20"
},
三、路由引入
在main.js中引入路由
import Vue from 'vue'
import App from './App'
import router from './router'
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
四、注册全局组件方式
如果需要把某一js文件注册为全局使用,则可以通过如下方式
import tools from '@/utils/tools' // 在main.js中引入文件
Vue.prototype.$tools=tools; //注册为全局变量
this.$tools.openNewWindowOut(this.backLink);
五、监听某个变量的变化
示例:在我们通过ajax获取某个url请求地址,通过该url打开弹出式窗口或被拦截,原因:
当时用window.open()打开新浏览器窗口,如果这个url是通过ajax从后台获取,则会被浏览器拦截,因为浏览器监测到非用户行为打开新的浏览器窗口,认为这可能是广告或者不安全的操作;
怎么避免浏览器的这种拦截行为?
已说明这种拦截的情况是因为非用户操作产生的,因为可通过确认框由用户确认后打开,基于这种方式,我们可事先在vue页面绑定某个变量,当用户触发操作之后,把通过ajax获取的url变量赋给绑定的变量,在监测这个事先绑定的变量值的变化,具体实现如下:
data(){
backUrl:'',
backLink:'',
},
methods:{
// 用户点击按钮
backClick(){
if(this.backUrl){
this.backLink = this.backUrl;
}else{
this.$message("请稍后,正在上架中....");
}
},
},
watch:{
// 方法名和变量名相同
backLink(){
this.$tools.openNewWindowOut(this.backLink);
this.backLink = null; //置空的原因:允许打开多个
}
}
六、路由守卫
import router from './router'
// 顶部进度条
import NProgress from 'nprogress'
// token文件导入
import {getToken} from '@/utils/auth'
import store from './store'
import { Message } from 'element-ui'
// 不授权就可以访问的页面
const whitePageList = ['/login']
/**
* 路由守卫
* 在路由守卫中,只有next()是放行,其它的如:next('/login'),next(to),next({...to,replace:true})都不是放行,而是中断当前导航,执行新的导航
* next('/login')说明:
* 此含义并不是直接去/login路由,而是中断(中断指的是:不会执行router.afterEach(()=>{})),而是又进入一层路由守卫,这是to.path已经是新的路由,
* 才执行next()放行。
* 重点:在addRoutes()动态添加路由之后第一次访问被添加的路由,会出现白屏?
* 这是因为刚刚addRoutes()完就立即访问,此时addRoutes()还没有执行结束,从而找不到刚添加的路由导致白屏,此时需要重新访问路由才可以。
* 如何解决这种问题:
* 使用next({..to,replace:true})来确保addRoutes()动态添加的路由已经完全被加载结束,
* replace:true 告诉Vue,本次操作之后,不能通过浏览器后退按钮,返回前一个路由,
* 因此也可以直接写为:next(...to),作用为:如果参数to不能找到对应的路由之后,就再执行router.beforeEach(),直到能够找到对应的路由为止;这就意外着,
* 如果能够找到对应的路由,那么addRoutes()就执行结束了,接下来会执行一次正确的路由,因此需要合适的设置next(),否则会进入死循环。
*/
router.beforeEach((to,from,next)=>{
NProgress.start(); // 开始NProgress
// 获取token
if(getToken()){
if(to.path === '/login'){
next({path:'/'});
NProgress.done(); // 结束NProgress
}else{
// 如果用户角色列表长度为0,则获取用户信息,动态生成路由列表
if(store.getters.roles.length === 0){
store.dispatch('GetInfo').then(res=>{
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(error=>{
// 如果拉取用户信息出错,则登出重新登录
store.dispatch('FedLogOut').then(res=>{
Message.error(err || 'Verification failed, please login again');
next('/');
});
})
}else{
next();
}
}
}else{
// 如果没有登录,又不在白名单则直接放开
if(whitePageList.indexOf(to.path) != -1){
next();
}else{
next('/login');
NProgress.done(); // 结束NProgress
}
}
})
// 路由之后
router.afterEach(()=>{
NProgress.done(); // 结束NProgress
})
七、拦截请求和响应axios
import axios from 'axios'
import {Message,MessageBox } from 'element-ui'
import store from "../store";
import {getToken} from "./auth";
/**
* axios介绍:
* 简单的讲,就是发送get和set请求的
* 拦截请求和响应
* 转换请求和响应数据
* 自动转换json等
*/
//创建axios实例
const service = axios.create({
baseURL:process.env.BASE_API,
timeout:15000 // 请求超时时间
})
// request请求拦截器
service.interceptors.request.use(config=>{
// 在发送请求之前,判断token是否存在,如果存在,则把token添加到请求头header中
if(store.getters.token){
config.headers.Authorization = getToken();
}
return config;
},error=>{
console.log(error);
/**
* Promise:异步操作对象序列化
* Promise是一个对象,用于表示异步操作的成功或失败的结果,一般有三种状态:
* pending:初始状态,既不代表成功,也不代表失败,
* fulfilled:意外着操作成功完成,也可以称为resolved状态
* rejected:意外着操作失败
* pending状态的Peomise对象,可能触发fulfilled状态,并传递一个值给相应的状态处理方法;也有可能触发rejected状态并给相应的状态处理方法传递失败的值,
* 当出现上述任意一种状态时,Promise对象的then方法绑定的事件处理方法(handler)就会被调用(then方法的参数有两个:onfulfilled和onrejected,
* 并且都为Function类型,事件处理方法会根据不同的状态调用不同的方法)。
*
*/
Promise.reject(error);
})
// response拦截器
service.interceptors.response.use(response=>{
const res = response.data;
/**
* 判断返回值状态,如果为200-正常响应,401-未登录异常,否则为错误或异常响应
*/
if(res.code !== 200){
Message({
message:res.message, // 错误响应的信息
type:"error", // 提示信息类型
duration:3 * 1000 //错误信息展示时间,单位为毫秒
});
if (res.code === 401 ){
MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录',
'确定登出',
{
confirmButtonText:'重新登录',
cancelButtonClass:'取消',
type:"error"
}).then(()=>{ // then作用:当then之前的语句被执行之后,再异步执行then内部的程序,可防止数据初始化失败造成的页面无法加载. 可多个连续使用
// 发送登出请求,重新载入当前页面
location.reload();
})
}
return Promise.reject('error');
}else{
return res;
}
},error=>{
console.log('err'+error);
Message({
message:error.message, // 错误响应的信息
type:"error", // 提示信息类型
duration:3 * 1000 //错误信息展示时间,单位为毫秒
});
})
export default service
八、多个组件共享某个状态Vuex
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import user from "./modules/user"
import permission from "./modules/permission";
/**
* Vuex介绍:
* 重点:多个组件共享某个状态
* Vuex是专门为vue.js开发的状态管理模式,它将集中存储管理应用中组件的状态,并用一定的规则保证状态朝着可预测的方向变化。
* store :仓库 state:状态
* VUE组件从store中读取状态,如果store中的状态发生变化,则相关的组件的状态将高效的更新。
* 不能直接改变store中的state(状态),改变的方式只能是显式的提交(commit),方便跟踪某个状态的变化
* 触发状态变更方式:store.commit('increment')
*
*
* this.$store.dispatch('isLogin', true):异步操作,数据提交至 actions ,可用于向后台提交数据
* this.$store.commit('loginStatus', 1):同步操作,数据提交至 mutations ,可用于登录成功后读取用户信息写到缓存里
* 虽然提交的方式不同,但都是传值个vuex的mutation,进而改变state
*/
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {user,permission},
getters
})
export default store
/**
* Vuex中Getters的说明:
* store(容器)中的state派生状态的管理
* Getters可以认为是store的计算属性,其返回值会根据依赖缓存,并且只有依赖值发生变化后才会重新计算
* 对外暴露的公共接口为:store.getter
*/
const getters = {
sideBar: state => state.app.sideBar,
// 设备
device:state => state.app.device,
// 客户端缓存的用户名
token:state => state.user.token,
// 用户头像
avatar: state => state.user.avatar,
// 用户名
name: state => state.user.name,
// 登录用户所属角色
roles: state => state.user.roles,
// 动态路由设置
addRouters: state => state.permission.addRouters,
routers: state => state.permission.routers
}
export default getters
import {getToken,setToken,removeToken} from "../../utils/auth"
import {login,loginOut,getInfo} from "../../api/login";
/**
* Vuex中各参数说明
* state:
* 单一状态树,此状态树包含整个应用层级会话的状态,
* mutations:
* 更改store容器中的状态的唯一方式就是提交mutation(突变),每一个mutation都有一个type(类型)和handler(回调函数),这个handler就是我们
* 实际进行state更改的地方,并且会接收state作为第一个参数,特别注意的是,每个mutation必须是同步函数,其原理为:在回调函数中进行的状态更新是不了追踪的。
* actions:
* 前面说明,为了所有的应用层级别的会话状态的更新都是可追踪的,所以引入了只能进行同步操作的mutations,但是在vuex中,也引入了能进行一步操作的actions,
* Actions包含任何的异步操作,虽然提交的是Mutation,但却不是直接更改状态。
*
*/
const user = {
state:{
// 本地token
token:getToken(),
// 用户名
name:'',
// 头像
avatar:'',
// 所属角色组
roles:[]
},
mutations:{
SET_TOKEN:(state,token)=>{
state.token = token;
},
SET_NAME:(state,name)=>{
state.name = name;
},
SET_AVATAR:(state,avatar)=>{
state.avatar = avatar;
},
SET_ROLES:(state,roles)=>{
state.roles = roles;
}
},
actions:{
// 登录
Login({commit},userInfo){
const userName = userInfo.userName.trim();
return new Promise((resolve,reject)=>{
login(userName, userInfo.passWord).then(response=>{
const data = response.data;
const tokenHeader = data.tokenHead + data.token;
setToken(tokenHeader);
// 提交突变状态
commit('SET_TOKEN',tokenHeader);
resolve();
}).catch(error=>{
reject(error);
})
})
},
// 获取用户信息
GetInfo({commit},state){
return new Promise((resolve,reject)=>{
getInfo().then(response=>{
const data = response.data;
if(data.roles && data.roles.length > 0){
commit('SET_ROLES',data.roles);
}else{
reject('getInfo: roles must be a non-null array !');
}
commit('SET_NAME',data.username);
commit('SET_AVATAR',data.icon);
resolve(response)
}).catch(error=>{
reject(error);
})
});
},
// 后台登出
LogOut({commit},state){
return new Promise((resolve, reject) => {
loginOut().then(response=>{
const data = response.data;
// 突变会话级别的变量
commit('SET_TOKEN','');
commit('SET_ROLES',[]);
// 删除本地token
removeToken();
// 成功响应
resolve();
}).catch(error=>{
reject(error)
})
});
},
// 前端登出
FedLogOut({commit},state){
return new Promise((resolve, reject) => {
commit('SET_TOKEN', '')
removeToken()
resolve()
});
}
}
}
export default user
九、vue中svg的组件
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg组件
// 全局注册
Vue.component('svg-icon', SvgIcon)
const requireAll = requireContext => requireContext.keys().map(requireContext)
/* require.context("./test", false, /.test.js$/);
这行代码就会去 test 文件夹(不包含子目录) 下面的找所有文件名以 .test.js 结尾的文件能被 require 的文件。
更直白的说就是 我们可以通过正则匹配引入相应的文件模块。
require.context有三个参数:
directory:说明需要检索的目录
useSubdirectories:是否检索子目录
regExp: 匹配文件的正则表达式
*/
const req = require.context('./svg', false, /\.svg$/)
requireAll(req)
<template>
<svg :class="svgClass" aria-hidden="true">
<use :xlink:href="iconName"></use>
</svg>
</template>
<script>
export default {
name: 'svg-icon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String
}
},
computed: {
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
}
}
}
</script>
<style scoped>
.svg-icon {
width: 1.2em;
height: 1.2em;
vertical-align: -0.18em;
fill: currentColor;
overflow: hidden;
}
</style>
十、passive和prevent区别
W3C规定,当一个from元素中只有一个输入框时,在该输入框中按下回车键该表单应该提交,如果希望阻止这一默认的行为,
则需要在<el-from>标签上加上@submit.native.prevent.
native:把一个vue组件转化为一个普通的HTML标签,并且该修饰符对普通HTML标签是没有任何作用的。
prevent:阻止某些标签默认拥有的事件,如a[href="#"],button[type="submit"],这类标签会在冒泡结束后自动执行默认事件,
passive:通俗点说就是每次事件产生,浏览器都会去查询一下是否有preventDefault阻止该次事件的默认动作。我们加上passive就是为了告诉浏览器,
不用查询了,我们没用preventDefault阻止默认动作。
** passive和prevent冲突,不能同时绑定在一个监听器上