从0到1基于vue搭建后台管理项目
本文主要内容是如何从0到1基于vue搭建一个后台管理项目的前端工程。示例的代码地址:https://github.com/tyuqing/vue-admin1 创建项目使用vue-cli3快速创建项目,具体的创建方法vue-cli官网上有说明。创建时大家可根据自己项目的需求选择合适的模块,我使用的配置如下图所示。其中选中了Babel, Router, Vuex, CSS Pre-proce...
本文主要内容是如何从0到1基于vue搭建一个后台管理项目的前端工程。示例的代码地址:https://github.com/tyuqing/vue-admin
1 创建项目
使用vue-cli3快速创建项目,具体的创建方法vue-cli官网上有说明。创建时大家可根据自己项目的需求选择合适的模块,我使用的配置如下图所示。
其中选中了Babel, Router, Vuex, CSS Pre-processors, Linter, Unit。 CSS Pre-processors选的是sass,eslint的规则选的是Airbnb,并且只在commit时校验并修复。这样一个基本的vue项目就创建成功了。
此时目录结构如下:
此时,可引入基础组件库,我们的基础组件库使用的是iview。大家也可以使用饿了么的elementUI
2 ESlint
eslint的规范有:airbnb、standard和prettier等。前面有提到过我们使用的eslint规则是airbnb。
eslint的自动修复方式有:
- 每次保存时lint代码。该方式是通过 eslint-loader实现的。
- git commit时lint代码
git commit时lint代码是在通过git钩子在commit时调用lint的命令(即下面配置中的lint-staged
,lint-staged
执行的命令为vue-cli-service lint
)来完成eslint的检查和修复,主要配置是在package.json中:
// package.json
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.{js,vue}": [
"vue-cli-service lint",
"git add"
]
}
以上配置示例是通过vue cli3搭建的项目的eslint自动检查和修复配置,主要是使用“yorkie”和“lint-staged”这两个npm包来实现的。(其中yorkie在@vue/cli-service中有引用,所以项目中引用@vue/cli-service即可),大家还可以通过“husky”和“lint-staged”来实现。
3 接口层
我们的接口请求的http库使用的axios。一般在业务上,当接口返回数据异常时,不同的异常前端需要通过不同的方式进行提示,如Message或Modal方式的提示。所以我们需要结合业务对axios进行封装,对异常进行统一处理,若分散到各个组件将会使代码变得冗余和难以维护。
3.1 axios封装
我们接口的响应体统一结构如下
{"code":200,"msg":"OK","data":{}}
我们是通过判断响应体中code来判断是否异常的,新建文件src/api/base.js
对axios进行封装,代码如下:
// src/api/base.js
import axios from 'axios';
import { Message, Modal } from 'iview';
const request = axios.create({
// 统一设置超时时间
timeout: 20000,
// 返回数据类型
responseType: 'json',
});
/**
* 接口异常的统一处理 判断响应体中的code进行不同的异常提示
*/
request.interceptors.response.use(
(response) => {
const res = response.data;
if (res.code === 200) {
// 正确时的处理
return res;
} else if (/* 条件 */) {
Message.error(res.message); // Message方式的提示
} else if (/* 条件 */) {
Modal.error({ title: '提示', content: res.message }); // modal方式的提示
}
return Promise.reject(new Error(res.message || 'Error'));
},
(error) => {
// 请求异常时的处理
if (error.message) {
Message.error(error.message);
}
return Promise.reject(error);
},
);
export default request;
对axios的封装中,我们首先传入了一些简单的配置创建了一个axios的实例(request
);然后,调用了axios的API(interceptors.response.use
)对请求的响应进行了统一拦截,通过判断响应体中的code进行不同的处理:
- 正常时
正常时返回res(响应体),这里我们没有返回变量response,因为response中包含了http状态等一系列信息,在base.js已针对这些信息进行了统一处理,而在组件或页面中并不需要这些信息,所以请求正常时直接返回响应体 - 响应体异常
响应体中code异常时,不同的code通过不同的方式进行异常提示,后返回一个rejected Promise - HTTP状态码异常
当HTTP状态码异常等,提示后返回一个rejected Promise
封装后,一次接口请求的时序图如下所示
3.2 接口的定义
以获取用户信息的接口为例,文件src/api/user.js
中引入base.js
中的request
,然后提供getUserInfo方法
/* src/api/user.js */
import request from './base.js';
export function getUserInfo(params) {
return request.get('/api/user/info', { params });
}
3.3 接口的使用
前面已经提到过,接口成功会返回响应体,失败被抛出一个Error,所以在组件中调用接口时,直接在then进行业务成功的逻辑,无需进行是否成功的判断,这样代码逻辑上会清晰很多。以在组件中使用获取用户信息接口为例示例如下。接口异常在前面提到的base.js
已经统一处理了,所以catch和finally相对会使用的比较少。
<script>
import { getUserInfo } from '@/api/user';
export default {
//......
methods: {
getUserInfo() {
getUserInfo()
.then(() => {
// 成功后的处理
})
.catch(() => {
// 异常后的处理
})
.finally(function() {
// promise完成后的处理
});
},
},
};
</script>
3.4 接口的mock和代理
在本地启动工程开发时,若后端接口未开发好,接口可以走本地的mock配置文件;若后端接口开发好了,接口可以代理到测试环境上。其中mock的功能可通过插件http-mockjs来实现,github地址:https://github.com/brizer/http-mocker
4 全局变量
使用vuex来进行全局变量共享的管理。以用户信息为例,讲解全局变量的存储。新建文件src/store/modules/user.js
,用于获取并存储用户信息,代码示例如下:
// src/store/modules/user.js
import { getInfo } from '@/api/user';
import router, { resetRouter } from '@/router';
const state = {
role: '',
email: '',
// 其他用户信息...
};
const mutations = {
SET_ROLE: (state, role) => {
state.role = role;
},
SET_EMAIL: (state, email) => {
state.email = email;
},
// 其他用户信息...
};
const actions = {
// 获取用户信息
getInfo({ commit }) {
return getInfo()
.then(response => {
const { data } = response;
const { role, email } = data;
commit('SET_ROLE', role);
commit('SET_EMAIL', email);
// 其他用户信息...
return data;
})
.catch(error => {
reject(error);
});
},
};
export default {
namespaced: true,
state,
mutations,
actions,
};
因为用户信息在工程中可能经常被使用,可以将用户信息配置在getter中,步骤如下,新建文件src/store/getters.js
,使任何组件能更快速方便的访问到用户信息。
// src/store/getters.js,
const getters = {
role: state => state.user.role,
email: state => state.user.email,
// 其他用户信息...
};
export default getters;
将使用vue-cli3生成的src/store.js
文件,改为src/store/index.js
,如下所示,使全局变量分模块进行管理
// src/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import getters from './getters';
Vue.use(Vuex);
const modulesFiles = require.context('./modules', true, /\.js$/);
// 不需要 `import app from './modules/app'`
// 自动载入文件夹./modules中的内容
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
// set './app.js' => 'app'
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1');
const value = modulesFiles(modulePath);
modules[moduleName] = value.default;
return modules;
}, {});
const store = new Vuex.Store({
modules,
getters,
});
export default store;
该文件会自动加载src/store/modules/
文件夹下的所有模块,即变量modules,在新建了模块后无需再手动引入。
在组件中调用store的模块的action的示例如下:
export default {
methods: {
demo() {
this.$store.dispatch('product/getInfo', productId); // 其中product为模块
}
}
}
在组件中使用store的getter的数据的示例如下:
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters(['email']) // 即前面提到的用户信息的email
}
}
在组件中使用store的模块的数据的示例如下:
import { mapState } from 'vuex';
export default {
computed: {
...mapState({
productInfo: state => state.product.productInfo, // 其中product为模块
}),
}
5 页面
5.1 布局
通常后台管理系统的布局如下图所示,包含菜单栏、工具栏和主体内容等。其中,左侧菜单栏和顶部工具栏是固定不变的,于是我们需要一个包含上述两部分内容的公用的布局模块,然后主体内容根据路由的变化而变化。
新建如下图所示中红框中的文件
其中src/layout/index.vue
是一个左-上下结构布局入口组件,代码如下:
// src/layout/index.vue
<template>
<div class="g-wrap">
<Sidebar class="g-left" />
<div class="g-right">
<div class="g-right_top">
<Navbar />
</div>
<section class="g-app-main">
<router-view />
</section>
</div>
</div>
</template>
<script>
import { Navbar, Sidebar } from './components/index';
export default {
name: 'Layout',
components: { Navbar, Sidebar },
computed: {},
methods: {},
};
</script>
5.2 侧边菜单栏
后台管理系统会需要在侧边展示导航栏,其中的菜单列表我们可以根据src/router/index.js
(路由配置文件)中配置的路由来生成,这样就省去了还要写一遍菜单列表配置的麻烦。
5.3 面包屑
一般我们会需要展示网站的层级结构,告知用户当前所在位置,这时,我们会用到面包屑组件。同样,该层级我们可以根据路由对象的属性来生成。路由对象属性$route.matched包含当前路由的所有嵌套路径片段的路由记录,可以基于该数组进行处理得到需要展示的层级结构
6 路由
路由可能会有访问权限,所以路由表分为两个:一个是存放不需要权限的公共页面,如登录页、首页等;一个是存放需要权限的页面,如通过用户角色判断或需要登录等。前者在实例化vue时就挂载,后者在请求到用户角色后再通过router.addRoutes方法来动态挂载。
6.1 常量路由
无需权限的常量路由表示例如下:
// src/router/index.js
import Vue from 'vue';
import Router from 'vue-router';
import Layout from '@/layout/index.vue';
Vue.use(Router);
// 无权限校验的路由
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index.vue'),
hidden: true,
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index.vue'),
name: 'dashboard',
meta: { title: '首页' },
}
],
}
]
export default new Router({
routes: constantRoutes
});
6.2 动态路由
给需要权限的动态路由的meta标签配置roles属性,角色的校验是通过router.beforeEach
方法在路由变更时,首次获取了用户信息后,判断路由的meta.roles
来校验访问的权限,若没有配置meta.roles则不需要校验角色
示例如下:
// src/router/modules/demo.js
import Layout from '@/layout/index.vue';
const demoRouter = {
path: '/demo',
component: Layout,
redirect: '/demo/list',
name: 'demo',
meta: { title: '示例列表' },
hidden: true,
children: [
{
path: 'list',
component: () => import('@/views/demo/list.vue'),
name: 'demo-list',
meta: { title: '示例列表', breadcrumb: false },
},
{
path: 'edit/:id',
component: () => import('@/views/demo/edit.vue'),
name: 'demo-edit',
meta: { title: '示例编辑', activeMenu: 'demo-list', roles: ['admin', 'editor'] },
hidden: true,
},
],
};
export default demoRouter;
// src/router/index.js
import demoRouter from './modules/demo';
export const asyncRouterMap = [ demoRouter ];
注意事项:404页面要放在最后加载,否则所有页面都会跳转到404。
7 权限的校验
权限的校验分为
- 校验用户是否登录
- 校验用户角色有权访问的页面。
角色的校验和动态路由的挂载是写在项目的入口文件main.js中的,也可以提成单独的文件在main.js中引用。以下为示例:
// main.js
import store from '@/store/index';
const WHITE_LIST = ['/login'];
router.beforeEach((to, from, next) => {
if (WHITE_LIST.indexOf(to.path) !== -1) {
// 白名单页面不需要登录
next();
} else {
if (!store.getters.role) { // 判断是否获取了用户信息
store.dispatch('user/getInfo').then(response => { // 获取用户信息
const role = response.data.role;
// 生成可访问的路由表
const accessRoutes = generateRoutes(role)
// 动态添加可访问路由表
router.addRoutes(accessRoutes)
next({ ...to, replace: true })
}).catch(() => {
next(`/login`)
});
} else {
next() //当已经登录过,说明已经通过角色生成了可访问的路由表,若无权限会跳转到404页面
}
}
});
在每次路由变更时执行的逻辑如下图所示:
流程如下:
- 判断目标路由是否在权限白名单中(如是否访问的是登录页),在则next();不在则执行步骤2
- 判断是否已经获取过用户信息 获取过则next();未获取过则执行步骤3
- 调用vuex的action(该action是调用接口)获取用户信息 后通过用户信息动态加载剩余路由。然后
next({ ...to, replace: true })
这样会使该路由变更的逻辑重新执行一遍,以确保所有路由加载成功。
未登录跳转到登录页的功能写在前面所提到的“axios封装”中。这样确保每个接口被调用时,若未登录都会跳转到登录页。并且在权限校验的方法中,首次进入时会调用用户信息接口这样来确认是否登录。
8 总结
最后搭建出项目的整体架构如下图所示:
更多推荐
所有评论(0)