项目类一:中后台管理系统类
中后台系统类多为内部人员使用。采用Vue+elementUI开发。
技术选型的理由有以下几点:
- 此类项目多为表单、列表、数据看板,vue生态有成熟的解决方案。
- 产品设计风格与elementUI设计风格相近。
- 团队成员更擅长使用Vue开发后台管理类项目,可以快速开发,节省成本。
一般的中后台管理系统实现的功能有以下几类:
- 模块化开发:功能、组件、路由、vuex的实现。
- 权限控制:请求权限,动态路由,按钮权限的实现。
- 数据看板:Echart的使用。
- 项目mock:后端未提供API时,模拟数据。
- 多环境配置:dev(开发)、sit(测试)、stage(预发)prod(生产)
- 国际化:i18n的配置。
模块化开发
模块化开发可以解决命名冲突、文件依赖的问题,方便多人协作开发,提升开发效率,方便后期维护。
- 公共组件抽离出来可以复用。
- 页面、路由、vuex(namescaped)根据模块划分。
权限控制
请求权限的实现
- 登录拿到token,存入cookie
- 每一个请求,都会在请求header里面携带用户的token
- 后端会根据该token来验证用户是否有权限执行该操作
动态路由的实现
- 创建vue实例的时候挂载vue-router,登录页和不用权限的页面
- 通过token获取用户的role和有权限的api列表perms[\'GET /aaa\',\'POST /bbb\',...]
- 算出其相应有权限的路由,通过router.addRoutes动态挂载路由
- 使用vuex管理路由表,根据vuex中可访问的路由渲染侧边栏组件
// 用户登录拿到的perms为[\'GET /admin/goods/list\', ...]
let userInfo = {
role: \'operator\',
perms: [\'GET /admin/goods/list\']
}
// 路由中的权限在meta中的perms中设置,该页面用到的的api
let childRoute = {
path: \'list\',
component: () => import(\'@/views/goods/list\'),
name: \'goodsList\',
meta: {
perms: [\'GET /admin/goods/list\', \'POST /admin/goods/delete\'],
title: \'商品列表\',
noCache: true
}
}
// 在 vuex 中进行管理,算出其相应有权限的路由
const permission = {
state: {
routers: constantRouterMap,
addRouters: []
},
mutations: {
SET_ROUTERS: (state, routers) => {
state.addRouters = routers
state.routers = constantRouterMap.concat(routers)
}
},
actions: {
GenerateRoutes({ commit }, data) {
return new Promise(resolve => {
const { perms } = data
let accessedRouters
if (perms.includes(\'*\')) {
accessedRouters = asyncRouterMap
} else {
accessedRouters = filterAsyncRouter(asyncRouterMap, perms)
}
commit(\'SET_ROUTERS\', accessedRouters)
resolve()
})
}
}
}
// 在router.beforeEach中判断在第一次拉取权限时使用addRoutes添加可访问路由表
router.beforeEach((to, from, next) => {
// 判断当前用户是否已拉取完user_info信息
if (getToken() && store.getters.perms.length === 0) {
// 拉取user_info
store.dispatch(\'GetUserInfo\').then(res => {
// note: perms must be a array! such as: [\'GET /aaa\',\'POST /bbb\']
const perms = res.data.data.perms
// 根据perms权限生成可访问的路由表
store.dispatch(\'GenerateRoutes\', { perms }).then(() => {
// 动态添加可访问路由表
router.addRoutes(store.getters.addRouters)
// hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
next({ ...to, replace: true })
})
})
}
})
按钮权限的实现
- 通过比较该按钮请求的api是否在该用户的有权限的api列表perms中
- 注册permission指令,如果权限不符合,就移除该节点
<el-button v-permission="[\'POST /admin/goods/delete\']" type="danger" @click="handleDelete">删除</el-button>
const permission = {
inserted(el, binding, vnode) {
const { value } = binding
const perms = store.getters && store.getters.perms
if (value && value instanceof Array && value.length > 0) {
const permissions = value
var hasPermission = false
if (perms.indexOf(\'*\') >= 0) {
hasPermission = true
} else {
hasPermission = perms.some(perm => {
return permissions.includes(perm)
})
}
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error(`need perms! Like v-permission="[\'GET /aaa\',\'POST /bbb\']"`)
}
}
}
const install = function(Vue) {
Vue.directive(\'permission\', permission)
}
if (window.Vue) {
window[\'permission\'] = permission
Vue.use(install)
}
permission.install = install
export default permission
Echart的使用
- 按需引入ECharts
// 引入 ECharts 主模块
var echarts = require(\'echarts/lib/echarts\');
// 引入柱状图
require(\'echarts/lib/chart/bar\');
// 引入提示框和标题组件
require(\'echarts/lib/component/tooltip\');
require(\'echarts/lib/component/title\');
- 在vue中的mounted生命周期里声明初始化ECharts。(ECharts初始化必须绑定dom。)
mounted() {
this.initCharts();
},
methods: {
initCharts() {
this.chart = echarts.init(this.$el);
this.setOptions();
},
setOptions() {
this.chart.setOption({
title: {
text: \'ECharts 入门示例\'
},
tooltip: {},
xAxis: {
data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"]
},
yAxis: {},
series: [{
name: \'销量\',
type: \'bar\',
data: [5, 20, 36, 10, 10, 20]
}]
})
}
}
- 数据变化时,通过watch触发setOptions
项目mock
项目mock 模拟产生一些虚拟的数据,可以让前端在后端接口还没有开发出来时独立开发,加快开发速度。
- 使用 promise 和 setTimeout 模拟数据
- 使用 mockjs,mockjs可以拦截ajax请求,返回设定好的数据。
- 使用例如easy-mock/doc Docker等
多环境配置:dev(开发)、sit(测试)、stage(预发)prod(生产)
{
"scripts": {
"dev": "cross-env BABEL_ENV=development webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"build:stage": "cross-env NODE_ENV=production env_config=stage node build/build.js",
"build:prod": "cross-env NODE_ENV=production env_config=prod node build/build.js",
"lint": "eslint --ext .js,.vue src",
"test": "npm run lint",
"precommit": "lint-staged"
},
}
在config文件夹下的prod.env.js配置如下
module.exports = {
NODE_ENV: \'"production"\',
ENV_CONFIG: \'"prod"\',
BASE_API: \'"http://xxx/admin"\'
}
在代码中即可用process.env.env_config使用判断啦
国际化:i18n的配置。
- 安装
npm install vue-i18n -D - 在src下新建文件i18n.js
import Vue from \'vue\'
import VueI18n from \'vue-i18n\'
Vue.use(VueI18n)
export const i18n = new VueI18n({
locale: \'CN\',
messages: {
\'CN\': require(\'./assets/lang/cn\'),
\'EN\': require(\'./assets/lang/en\')
}
})
- 在assets/lang下新建文件en.js和cn.js
// en.js
export const message = {
hello: \'Hello, World\'
}
// cn.js
export const message = {
hello: \'你好, 世界\'
}
- 在main.js里引入i18n.js
import { i18n } from \'./i18n\'
new Vue({
el: \'#app\',
i18n,
...
})
- 使用
<span>{{ $t("message.hello") }}</span>