vu-element-admin框架开发

前端起步
查看 vue-element-admin 源码

中文文档

vue-element-admin 实现了登录模块,包括 token 校验、网络请求等,可以简化我们的开发工作

修改与删除
删除 src/views 下的源码,保留:
dashboard:首页
error-page:异常页面
login:登录
redirect:重定向
theme:主题(暂时不删)
删除 src/router/modules 文件夹
删除 src/vendor 文件夹
(删不删都行 根据自己的需求来)
1
2
3
4
5
6
7
8
9
修改:src/router/index.js

import Vue from ‘vue’
import Router from ‘vue-router’

Vue.use(Router)

/* Layout */
import Layout from ‘@/layout’

/**

  • constantRoutes
  • a base page that does not have permission requirements
  • all roles can be accessed
    /
    export const constantRoutes = [
    {
    path: ‘/redirect’,
    component: Layout,
    hidden: true,
    children: [
    {
    path: '/redirect/:path
    ’,
    component: () => import(’@/views/redirect/index’)
    }
    ]
    },
    {
    path: ‘/login’,
    component: () => import(’@/views/login/index’),
    hidden: true
    },
    {
    path: ‘/auth-redirect’,
    component: () => import(’@/views/login/auth-redirect’),
    hidden: true
    },
    {
    path: ‘/404’,
    component: () => import(’@/views/error-page/404’),
    hidden: true
    },
    {
    path: ‘/401’,
    component: () => import(’@/views/error-page/401’),
    hidden: true
    },
    {
    path: ‘/’,
    component: Layout,
    redirect: ‘/dashboard’,
    children: [
    {
    path: ‘dashboard’,
    component: () => import(’@/views/dashboard/index’),
    name: ‘Dashboard’,
    meta: { title: ‘Dashboard’, icon: ‘dashboard’, affix: true }
    }
    ]
    }
    ]

/**

  • asyncRoutes
  • the routes that need to be dynamically loaded based on user roles
    /
    export const asyncRoutes = [
    {
    path: ‘/error’,
    component: Layout,
    redirect: ‘noRedirect’,
    name: ‘ErrorPages’,
    meta: {
    title: ‘Error Pages’,
    icon: ‘404’
    },
    children: [
    {
    path: ‘401’,
    component: () => import(’@/views/error-page/401’),
    name: ‘Page401’,
    meta: { title: ‘401’, noCache: true }
    },
    {
    path: ‘404’,
    component: () => import(’@/views/error-page/404’),
    name: ‘Page404’,
    meta: { title: ‘404’, noCache: true }
    }
    ]
    },
    {
    path: ‘/theme’,
    component: Layout,
    children: [
    {
    path: ‘index’,
    component: () => import(’@/views/theme/index’),
    name: ‘Theme’,
    meta: { title: ‘Theme’, icon: ‘theme’ }
    }
    ]
    },
    // 404 page must be placed at the end !!!
    { path: '
    ’, redirect: ‘/404’, hidden: true }
    ]

const createRouter = () => new Router({
// mode: ‘history’, // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})

const router = createRouter()

// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}

export default router

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
项目配置
通过 src/settings.js 进行全局配置:

title:站点标题,进入某个页面后,格式为:
页面标题 - 站点标题
1
showSettings:是否显示右侧悬浮配置按钮
tagsView:是否显示页面标签功能条
fixedHeader:是否将头部布局固定
sidebarLogo:菜单栏中是否显示LOGO
errorLog:默认显示错误日志的环境
源码调试
如果需要进行源码调试,需要修改 vue.config.js:

config
// https://webpack.js.org/configuration/devtool/#development
.when(process.env.NODE_ENV === ‘development’,
config => config.devtool(‘cheap-source-map’)
)
1
2
3
4
5
将 cheap-source-map 改为 source-map,如果希望提升构建速度可以改为 eval

通常建议开发时保持 eval 配置,以增加构建速度,当出现需要源码调试排查问题时改为 source-map

logo修改
目录:vue-element-admin/src/layout/components/Sidebar/Logo.vue

data() {
return {
title: ‘交控智慧公司’,
logo: ‘http://qny.tuchuang.mqllin.cn/FjGOwzNvGm77x5vGqtho7vjSeyMP’
}
}
1
2
3
4
5
6
7
8
项目结构
api:接口请求

assets:静态资源

components:通用组件

directive:自定义指令

filters:自定义过滤器

icons:图标组件

layout:布局组件

router:路由配置

store:状态管理

styles:自定义样式

utils:通用工具方法

auth.js:token 存取
permission.js:权限检查
request.js:axios 请求封装
index.js:工具方法
views:页面

dashboard:首页
error-page:错误页面
login:登陆
redirect:重定向
theme:主题
permission.js:登录认证和路由跳转

settings.js:全局配置

main.js:全局入口文件

App.vue:全局入口组件

Vue-element-admin02
路由
新增页面
//asyncRoutes用于角色权限访问的目录
export const asyncRoutes = [
{
path: ‘/book’,
component: Layout,
meta: { title: ‘图书管理’, icon: ‘documentation’}
},

]

//公共页面,不分权限
export const asyncRoutes = [

]

const createRouter = () => new Router({
// mode: ‘history’, // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
多级路由
{
path: ‘/book’,
component: Layout,
redirect: ‘/book/create’,
name: ‘图书管理’,
meta: { title: ‘图书管理’, icon: ‘documentation’ },
children: [
{
path: ‘/book/create’,
component: () => import(’@/views/book/create’),
name: ‘上传图书’,
meta: { title: ‘上传图书’, icon: ‘edit’ }
},
{
path: ‘/book/create’,
component: () => import(’@/views/book/create’),
name: ‘图书查询’,
meta: { title: ‘图书查询’, icon: ‘edit’ }
}
]
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
带角色权限的多级路由
{
path: ‘/book’,
component: Layout,
redirect: ‘/book/create’,
name: ‘图书管理’,
meta: { title: ‘图书管理’, icon: ‘documentation’ },
children: [
{
path: ‘/book/create’,
component: () => import(’@/views/book/create’),
name: ‘上传图书’,
meta: { title: ‘上传图书’, icon: ‘edit’,roles:[‘admin] }
},
{
path: ‘/book/create’,
component: () => import(’@/views/book/create’),
name: ‘图书查询’,
meta: { title: ‘图书查询’, icon: ‘edit’,roles:[‘editor’] }
}
]
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
路由和权限校验
思考:你现在参与的项目中是如何处理路由和权限映射的?

路由处理逻辑分析
路由处理逻辑图如下:

路由场景分析
中后台路由常见的常见如下:

已获取 Token:
访问 /login:重定向到 /
访问 /login?redirect=/xxx:重定向到 /xxx
访问 /login 以外的路由:直接访问 /xxx
未获取 Token:
访问 /login:直接访问 /login
访问 /login 以外的路由:如访问 /dashboard,实际访问路径为 /login?redirect=%2Fdashboard,登录后会直接重定向 /dashboard
路由逻辑源码
第一步,main.js 中加载了全局路由守卫

import ‘./permission’ // permission control
1
第二步,permission 定义了全局路由守卫

router.beforeEach(async(to, from, next) => {
// 启动进度条
NProgress.start()

// 修改页面标题
document.title = getPageTitle(to.meta.title)

// 从 Cookie 获取 Token
const hasToken = getToken()

// 判断 Token 是否存在
if (hasToken) {
// 如果当前路径为 login 则直接重定向至首页
if (to.path === ‘/login’) {
next({ path: ‘/’ })
NProgress.done()
} else {
// 判断用户的角色是否存在
const hasRoles = store.getters.roles && store.getters.roles.length > 0
// 如果用户角色存在,则直接访问
if (hasRoles) {
next()
} else {
try {
// 异步获取用户的角色
const { roles } = await store.dispatch(‘user/getInfo’)
// 根据用户角色,动态生成路由
const accessRoutes = await store.dispatch(‘permission/generateRoutes’, roles)
// 调用 router.addRoutes 动态添加路由
router.addRoutes(accessRoutes)
// 使用 replace 访问路由,不会在 history 中留下记录
next({ …to, replace: true })
} catch (error) {
// 移除 Token 数据
await store.dispatch(‘user/resetToken’)
// 显示错误提示
Message.error(error || ‘Has Error’)
// 重定向至登录页面
next(/login?redirect=${to.path})
NProgress.done()
}
}
}
} else {
// 如果访问的 URL 在白名单中,则直接访问
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
// 如果访问的 URL 不在白名单中,则直接重定向到登录页面,并将访问的 URL 添加到 redirect 参数中
next(/login?redirect=${to.path})
NProgress.done()
}
}
})

router.afterEach(() => {
// 停止进度条
NProgress.done()
})
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
动态路由分析
动态路由流程图
动态路由生成逻辑如下图:

动态路由源码分析
生成动态路由的源码位于 src/store/modules/permission.js 中的 generateRoutes 方法,源码如下:

import { asyncRoutes, constantRoutes } from ‘@/router’

generateRoutes({ commit }, roles) {
// 返回 Promise 对象
return new Promise(resolve => {
let accessedRoutes
if (roles.includes(‘admin’)) {
// 如果角色中包含 admin,则直接跳过判断,直接将 asyncRoutes 全部返回
accessedRoutes = asyncRoutes || []
} else {
// 如果角色中没有包含 admin,则调用 filterAsyncRoutes 过滤路由
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
// 将路由保存到 vuex 中
commit(‘SET_ROUTES’, accessedRoutes)
resolve(accessedRoutes)
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SET_ROUTES 方法源码如下:

SET_ROUTES: (state, routes) => {
// 将 routes 保存到 state 中的 addRoutes
state.addRoutes = routes
// 将 routes 集成到 src/router/index.js 的 constantRoutes 中
state.routes = constantRoutes.concat(routes)
}
1
2
3
4
5
6
路由过滤的方法 filterAsyncRoutes 源码如下:

/**

  • @params routes - 异步加载的路由
  • @params roles - 用户的角色,数组形式
    */
    export function filterAsyncRoutes(routes, roles) {
    const res = []

// 遍历全部路由
routes.forEach(route => {
// 对路由进行浅拷贝,注意 children 不会拷贝,因为不需要对 children 进行判断,所以可以使用浅拷贝
const tmp = { …route }
// 检查用户角色是否具备访问路由的权限
if (hasPermission(roles, tmp)) {
// 当路由具有访问权限时,判断路由是否具备 children 属性
if (tmp.children) {
// 当路由包含 children 时,对 children 迭代调用 filterAsyncRoutes 方法
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
// 当路由具有访问权限时,将 tmp 保存到 res 中
res.push(tmp)
}
})

return res
}
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
检查权限方法 hasPermission 源码如下:

function hasPermission(roles, route) {
// 检查路由是否包含 meta 和 meta.roles 属性
if (route.meta && route.meta.roles) {
// 判断 route.meta.roles 中是否包含用户角色 roles 中的任何一个权限,如果包含则返回 true,否则为 false
return roles.some(role => route.meta.roles.includes(role))
} else {
// 如果路由没有 meta 或 meta.roles 属性,则视为该路由不需要进行权限控制,所有用户对该路由都具有访问权限
return true
}
}
1
2
3
4
5
6
7
8
9
10
相关库分析
Slim progress bars for Ajax’y applications. Inspired by Google, YouTube, and Medium.

NProgress.start();
NProgress.done();
1
2
showSpinner 可以控制右侧的环形进度条是否显示

NProgress.configure({ showSpinner: false })
1
路由总结
关于路由处理
vue-element-admin 对所有访问的路由进行拦截;
访问路由时会从 Cookie 中获取 Token,判断 Token 是否存在:
如果 Token 存在,将根据用户角色生成动态路由,然后访问路由,生成对应的页面组件。这里有一个特例,即用户访问 /login 时会重定向至 / 路由;
如果 Token 不存在,则会判断路由是否在白名单中,如果在白名单中将直接访问,否则说明该路由需要登录才能访问,此时会将路由生成一个 redirect 参数传入 login 组件,实际访问的路由为:/login?redirect=/xxx。
关于动态路由和权限校验
vue-element-admin 将路由分为:constantRoutes 和 asyncRoutes

用户登录系统时,会动态生成路由,其中 constantRoutes 必然包含,asyncRoutes 会进行过滤;

asyncRoutes 过滤的逻辑是看路由下是否包含 meta 和 meta.roles 属性,如果没有该属性,所以这是一个通用路由,不需要进行权限校验;如果包含 roles 属性则会判断用户的角色是否命中路由中的任意一个权限,如果命中,则将路由保存下来,如果未命中,则直接将该路由舍弃;

asyncRoutes 处理完毕后,会和 constantRoutes 合并为一个新的路由对象,并保存到 vuex 的 permission/routes 中;

用户登录系统后,侧边栏会从 vuex 中获取 state.permission.routes,根据该路由动态渲染用户菜单。

Vue-element-admin03
axios用法分析
request 库使用了 axios 的手动实例化方法 create 来封装请求,要理解其中的用法,我们需要首先学习 axios 库的用法

查看 axios 官网

基本案例
import axios from ‘axios’

export default {
data() {
return {
tableData: []
}
},
created() {
const url = ‘http://192.168.15.5:9555/BusService/Require_RouteStatData/?’
axios.get(url, {
params: {
RouteID: 122
},
headers: {
token: ‘abcd’
}
}).then(response => {
console.log(response.data[0].SegmentList)
this.tableData = response.data[0].SegmentList
}).catch(err => {
console.log(err)
})
}
}
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
这样改动可以实现我们的需求,但是有两个问题:

每个需要传入 token 的请求都需要添加 headers 对象,会造成大量重复代码
每个请求都需要手动定义异常处理,而异常处理的逻辑大多是一致的,如果将其封装成通用的异常处理方法,那么每个请求都要调用一遍
axios.create 示例
下面我们使用 axios.create 对整个请求进行重构:

created() {
const url = ‘/Require_RouteStatData/’
const request = axios.create({
baseURL: ‘http://192.168.15.5:9555/BusService’,
params: {
RouteID: 122
},
timeout: 5000
})

request({
  url,
  method: 'get'
}).then(res => {
  this.tableData = res.data[0].SegmentList
})

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
首先我们通过 axios.create 生成一个函数,该函数是 axios 实例,通过执行该方法完成请求,它与直接调用 axios.get 区别如下:

需要传入 url 参数,axios.get 方法的第一个参数是 url
需要传入 method 参数,axios.get 方法已经表示发起 get 请求
axios 请求拦截器
上述代码完成了基本请求的功能,下面我们需要为 http 请求的 headers 中添加 token,同时进行白名单校验,如 /login 不需要添加 token,并实现异步捕获和自定义处理:

const whiteUrl = [ ‘/login’, ‘/book/home/v2’ ]
const url = ‘/book/home/v2’
const request = axios.create({
baseURL: ‘https://test.youbaobao.xyz:18081’,
timeout: 5000
})
request.interceptors.request.use(
config => {
// throw new Error(‘error…’)
const url = config.url.replace(config.baseURL, ‘’)
if (whiteUrl.some(wl => url === wl)) {
return config
}
config.headers[‘token’] = ‘abcd’
return config
},
error => {
return Promise.reject(error)
}
)
request({
url,
method: ‘get’,
params: {
openId: ‘1234’
}
}).catch(err => {
console.log(err)
})
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
这里核心是调用了 request.interceptors.request.use 方法,即 axios 的请求拦截器,该方法需要传入两个参数,第一个参数是拦截器方法,包含一个 config 参数,我们可以在这个方法中修改 config 并且进行回传,第二个参数是异常处理方法,我们可以使用 Promise.reject(error) 将异常返回给用户进行处理,所以我们在 request 请求后可以通过 catch 捕获异常进行自定义处理

axios 响应拦截器
下面我们进一步增强 axios 功能,我们在实际开发中除了需要保障 http statusCode 为 200,还需要保证业务代码正确,上述案例中,我定义了 error_code 为 0 时,表示业务返回正常,如果返回值不为 0 则说明业务处理出错,此时我们通过 request.interceptors.response.use 方法定义响应拦截器,它仍然需要2个参数,与请求拦截器类似,注意第二个参数主要处理 statusCode 非 200 的异常请求,源码如下:

const whiteUrl = [ ‘/login’, ‘/book/home/v2’ ]
const url = ‘/book/home/v2’
const request = axios.create({
baseURL: ‘https://test.youbaobao.xyz:18081’,
timeout: 5000
})
request.interceptors.request.use(
config => {
const url = config.url.replace(config.baseURL, ‘’)
if (whiteUrl.some(wl => url === wl)) {
return config
}
config.headers[‘token’] = ‘abcd’
return config
},
error => {
return Promise.reject(error)
}
)

request.interceptors.response.use(
response => {
const res = response.data
if (res.error_code != 0) {
alert(res.msg)
return Promise.reject(new Error(res.msg))
} else {
return res
}
},
error => {
return Promise.reject(error)
}
)

request({
url,
method: ‘get’,
params: {
openId: ‘1234’
}
}).then(response => {
console.log(response)
}).catch(err => {
console.log(err)
})

你可能感兴趣的:(vu-element-admin框架开发)