Vuejs渡劫系列三:构建一个包含路由控制、状态管理和权限校验的vue-cli项目

构建一个包含路由控制、状态管理和权限校验的vue-cli项目

Vuejs渡劫系列的第三篇文章,主要通过vue-cli构建容器,并应用vue-router实现前端路由控制,应用vuex实现状态管理,以及通过axios的拦截器进行页面访问的权限控制,读者在阅读后,可以完成一个常规的后台管理应用模型。

vue-cli构建web App,网上通俗的称呼就是 vue全家桶技术栈,已经熟悉掌握整个SPA流程开发的同学,基本上没必要看,但是如果想完整了解vue技术栈的一个流程开发,我相信这篇文章会让你获益良多。

tips: 本文所提供的案例都可以在 https://github.com/WeideMo/vue-admin-template 仓库获取,建议检出后边运行边学习,同时该项目也是一个简单管理后台的template,欢迎 ★star

安装

命令行工具(CLI)

在搭建我们自己的应用之前,我们需要一个容器,而vue-cli脚手架可以快速帮助我们构建这个容器,让我们基于容器去进行快速的开发,因此我们需要全局安装一下:

1
2
# 全局安装 vue-cli
$ npm install --global vue-cli

安装完成后,我们需要创建一个项目,一般我们会使用 webpack,作为模板以方便我们后期的代码构建,简单说就是轻松脚本打包。

1
2
# 创建一个基于 webpack 模板的新项目
$ vue init webpack my-project

此时,你会发现vue帮你创建了一个名叫 my-project的文件,进行项目文件,并安装依赖,这个过程比较久,你可以去逛逛别网站先:

1
2
3
# 安装依赖,走你
$ cd my-project
$ npm install

当安装完依赖之后,你就可以通过以下命令,启动一个 本地开发服务

1
$ npm run dev

当你看到vue的欢迎页面的时候,恭喜你,你成功搭建起你的 容器 了。

开始

忽略繁杂的配置

或许刚进入目录的你会被吓呆,但是小兄弟一定要淡定,因为这个是你开始的第一步,这里我们不用一一了解根目录下的所有文件(如果你真的有兴趣了解,你可以看下我前一篇Vuejs渡劫系列二:最全的vue-cli项目下的配置简析),而现在我们只需要知道的就以下几个就好:

index.html: 单页面的模板页面,后面在打包代码的时候,所有依赖会以这个文件作为模板进行构建后的javascript注入;

src 目录: 里面会根据你在初始化项目时选择的组件去生成的代码,一般包含了 components 目录(放置.vue单文件); router 目录(放置index.js管理前端路由跳转); store 目录(放置应用状态管理的文件); App.vue(单页面的主业务组件); main.js(实现App.vue实例化的逻辑,包括导入vue-router,vuex和其他组件等)

dist 目录: 里面放置的是你执行 npm run build 之后的构建静态资源和index.html页面。

在App.vue中设计页面结构

为了方便新手学习,这里通过构建一个简单的后台管理的模板,通过穿插一些典型的案例,来分解说明 vue-cli 项目下各个组件的用法,首先我自己设计的构图是这样的(设计水平有限,见谅-_-||):

构图

这个设计是很经典的头部和侧栏固定,内容区域自适应的一个管理后台设计,聪明的你,肯定知道通过划分三部分:Header, SiderbarContent 部分,得到的应该的划分:

划分

由于vue单页面应用默认有一个主组件入口 App.vue,此时,我们需要在其template中进行总的页面结构设计,下面是模板中的html代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<section class="container" id="J_container">
<!-- header view start -->
<router-view name="header"></router-view>
<!-- header view end -->
<section class="content">
<!-- sidebar view start -->
<router-view name="sidebar"></router-view>
<!-- sidebar view end -->
<!-- default view start -->
<router-view></router-view>
<!-- default view end -->
</section>
</section>
</template>

观察代码可以发现,我们将整个dom划分为 header, sidebar和一个default 三个部分,分别用了 router-view 标签进行占位,其中两个 router-view 具有名字,还有一个没有名字的 <router-view></router-view> 则代表是默认的view,这里大家可能还不清楚什么是 router-view,不用担心,这里你只需要知道,这个是 vue-router 中定义的一个组件,每个 router-view 最终都会根据路由规则去填充预设的视图。

使用 vue-router 声明路由规则

从这一部分开始,我们真正开始接触 vue-router 组件,作为vue全家桶里不可以或缺的成员之一,那到底其有什么神奇之处呢?

前面一节,我们提前对管理后台的视图进行了划分,分为了header,siderbar以及default视图,此时我们就需要定义一个前端的路由规则,能让路由在页面切换的时候,能够根据路由规则去将对应的组件放到对应的 router-view视图中(没错,就是将大象放到冰箱一样的简单,♪(・ω・)ノ)。

在开始之前,我有必要先把几个东西先讲清楚:首先说下什么是 路由:所谓路由,就是当前浏览器显示的url链接,好像如果我们有一个跳转按钮,他能让我们跳到 http://localhost:8090/compare这个路由,那么我们可以通过 <router-link></router-link> 元素进行定义,具体如下

1
<router-link to="/compare">跳转compare页面</router-link>

<router-link></router-link> 元素就像一把钥匙, to 属性告诉了这把钥匙可以打开门的地址,当我们跳转的时候,浏览器上的地址就会改变成 http://localhost:8090/compare

此时路由已经发生改变,正如前文说道的,我们需要一个路由规则,而这个规则就在src/router/index.js 中定义,这里我先把一个完整的配置发出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 引入依赖包和依赖组件
import Vue from 'vue'
import Router from 'vue-router'
// @被解析为src路径下,在webpack.base.conf.js定义
import Header from '@/components/Header'
import Sidebar from '@/components/Sidebar'
import compare from '@/components/compare/compare'
// 通过下面的方式定义一个异步组件,打包的时候不会加载到总的依赖包中,后续通过异步js的方式获取
const Error404 = resolve => require(['@/components/error/404'], resolve);
// 引用vue-router
Vue.use(Router)
// 定义路由实例
const router = new Router({
// 定义history方式,可以让单页面拥有后退和前进的浏览器能力
mode: 'history',
// 定义路由规则,具体如下
routes: [
// 默认路由:/
{
path: '/',
redirect: '/compare',
components: {
default: "",
sidebar: Sidebar,
header: Header
}
},
//未授权
{
path: '/error/404',
name: 'error404',
components: {
default: Error404,
sidebar: "",
header: ""
},
meta: {
module: 'error'
}
}
]
})
// 导出路由实例
export default router;

这里把上述代码,转化为以下流程段,分段解析:

引入vue框架和vue-router

通过import关键字,将vuevue-router 引入,并通过 vue的use方法,使用上 vue-router组件,这样的好处是,后续可以通过vue实例 this.$route 获取路由实例;

声明组件,以供路由使用

通过import的方式,将 @/components 下的组件导入,并声明为组件变量,组件变量为后续路由表的配置提供使用;

配置路由表,导出实例

通过 new Router() 进行实例化,并传入配置数组,即每个包含 path, name, components 键值对的数组,实例化,就可以将 router实例通过 export default 导出。

使用 vuex 集中管理状态

在使用 vuex之前,首先要清楚认识什么是 “状态管理”,简单笼统的说,vuex是为了解决多个视图依赖的状态的一个管理组件,意在解决多个组件共享同一块的状态数据,能让各个组件之间能正确共享数据而出现的解决方案,因此,如果你的业务仅仅是一两个组件构成的一个页面,完全没有必要引入 vuex

因此这里可以给点指引:如果你想做一个逻辑复杂的SPA项目,里面夹杂着千丝万缕共享的状态数据时,我建议你使用, 如果你要做的只是一个简单的营销活动页面时,你完全没有必要引入 vuex

vuex官网上也给出了很详细的教程,但是要快速入门还是需要一点时间,我这里简单的抽象了一下,以自己的理解让大家在项目中轻松使用 vuex

首先给出一幅图:

划分

vuex大致可以分为三个部分,读取状态写入状态 以及 模块,简单的理解就是:读,写,分类,以下详细的解析下三部分的关系:

读 (State, Getter)
state, 你可以理解它就是一个状态的存放区域,就像你简单使用vue时,都需要定义一个 data对象作为渲染所需的数据集合,state的定义,我们一般在 cli脚手架的 src/store/index.js中定义:

1
2
3
4
5
6
//states
const state = {
user: {
name:"草莓"
}
}

通过上述代码,我们就可以在 state 下,定义了一个公共的user对象,对象下还有一个name的属性值,这样只要一个user的公共状态就注入到了state状态树中;

getter,它是state中派生出的一些状态,简单解析就是vue中的 computed 的类似用法,如上述我们在state中定义了一个user的name,如果我们需要派生出一个 nickname,就可以在其基础上增加计算,这里不详尽说。

写 (Mutation, Action)
既然有了读,那肯定少不了写,因此针对状态值的写入,就产生了 mutationaction:
沿用上述的代码,我们如果想对这个公共的用户名进行修改的时候,我们就可以使用到 mutation:

1
2
3
4
5
6
7
// mutations
const mutations = {
//修改用户名称
setUserName (state, name){
state.user.name = name;
}
}

定义了mutation之后,我们可以在各个组建中,通过下面的方式调用写状态的方法:

1
2
//calll
this.$store.commit('setUserName ','莫莫大神');

这里的 mutation的只能用于一些同步的操作,就像上面的例子,如果你的某个改变状态的操作需要访问服务器,或者需要耗时的一些IO操作,这个时候,我们就不能直接用 mutation 而要定义一个 action 去分发:

1
2
3
4
5
6
7
8
9
10
const actions = {
//异步操作
setUserNameAsync({ commit }){
setTimeout(()=>{
commit('setUserName')
},3000)
}
}
//call
store.dispatch('setUserNameAsync')

actionmutation 相似,不过有两点不同,一个是可以定义异步的操作;第二个是通过 dispatch 关键字去触发。

模块 (Module)
因为考虑到状态可能会存在很多的情况,为了给状态分类,我们可以定义不同的文件去存储不同的状态到 user.js,rights等,最后通过 import 到总的 index.js中,方便管理,以下给出一个经典的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// user.js
const state = {
sider,
user: {
name:"莫莫童鞋"
}
}
//此处省略 getters,mutaions,actions的定义
export default {
state,
getters,
actions,
mutations
}
//index.js
import Vue from 'vue'
import Vuex from 'vuex'
import * as actions from './actions'
import * as getters from './getters'
import user from './modules/user'
//rights 为类似 user 的另外一个模块
import rights from './modules/rights'
//使用vuex
Vue.use(Vuex)
//实例化
export default new Vuex.Store({
actions,
getters,
modules: {
user,
rights
}
})

至此,vuex里划分的三大部分都已经描述清楚,此时我们可以在各个组件中通过 this.$store 去调用实例下上述的各个方法。

页面请求校验

由于单页面区别于多页面的请求响应方式,页面切换时我们往往需要对用户是否有访问即将到达页面的权限去做校验,针对MPA(多页面)应用的时候,每次访问页面时都会与服务器通信,因此在请求页面的时候,后端可以轻松handle用户的请求校验,然而在针对SPA(单页面)应用的时候,页面的请求是通过异步加载 javascript分块 的方式去渲染,因此就不存在请求页面的timing,因此这里针对需要对页面作校验的场景,提供两种通用简洁的解决方案:

全局路由守护校验

如果你的项目使用了 vue-router控制路由的话,那么你可以通过全局的路由去守护校验,这里列出关键代码:

1
2
3
4
5
6
7
8
9
10
11
const router = new VueRouter({ … })
router.beforeEach((to, from, next) => {
// do some verify
Vue.prototype.$axios.get(params).then(d=>{
//verify successly
next();
}).then(()=>{
//verify failed
next('error/404');
})
})

基于HTTP的拦截守护

如果你的项目使用了 vue-resourceaxios 等HTTP解决方案,通过HTTP的请求拦截器,可以在每次响应用户请求时,在拦截器中做处理,关键代码如下(代码以axios方案为例):

1
2
3
4
5
6
7
Vue.prototype.$axios.interceptors.response.use((response)=> {
//do some verify for response
return response;
}, (error)=> {
//error handler
return Promise.reject(error);
});

总结

至此,我们已经完成了 vue渡劫三,顺利完成了在 vue-cli 项目中引入路由控制,状态管理以及在SPA项目中增加一些权限校验的技巧,后续会有更多vue的实战技巧,希望继续关注。