代码规范
项目配置
一.基础配置(指定应用上下文,端口号---vue.config.js)
const port = 7070;
module.exports = {
publicPath: '/best-practice', // 部署应用包时的基本 URL
devServer: {
port,
}
};
二.配置webpack: configureWebpack
- 设置一个组件存放路径的别名---vue.config.js
const path=require('path')
module.exports = {
configureWebpack: {
resolve: {
alias: {
comps: path.join(__dirname, 'src/components'),
}
} }
}
- 设置一个webpack配置项用于⻚面title---vue.config.js
const path=require('path')
module.exports = {
configureWebpack: {
resolve: {
alias: {
comps: path.join(__dirname, 'src/components'),
}
}
}
}
// 在宿主⻚面使用lodash插值语法使用它,./public/index.html
// <title><%= webpackConfig.name %></title>
- 基于环境有条件地配置---vue.config.js
// 传递一个函数给configureWebpack
// 可以直接修改,或返回一个用于合并的配置对象
configureWebpack: config => {
config.resolve.alias.comps = path.join(__dirname, 'src/components')
if (process.env.NODE_ENV === 'development') {
config.name = 'vue项目最佳实践' }
else {
config.name = 'Vue Best Practice'
}
}
三.配置webpack: chainWebpack
webpack-chain称为链式操作,可以更细粒度控制webpack内部配置。(示范svgicon引入)
- 下载图标,存入src/icons/svg中
- 安装依赖:svg-sprite-loader
npm i svg-sprite-loader -D
- 修改规则和新增规则---vue.config.js
// resolve定义一个绝对路径获取函数
const path = require('path')
function resolve(dir) {
return path.join(__dirname, dir)
}
chainWebpack(config) {
// 配置svg规则排除icons目录中svg文件处理
// 目标给svg规则增加一个排除选项exclude:['path/to/icon']
config.module.rule("svg")
.exclude.add(resolve("src/icons"))
// 新增icons规则,设置svg-sprite-loader处理icons目录中的svg
config.module.rule('icons')
.test(/\.svg$/)
.include.add(resolve('./src/icons')).end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({symbolId: 'icon-[name]'})
}
- 使用---App.vue
<template>
<svg>
<use xlink:href="#icon-wx" />
</svg>
</template>
<script>
import '@/icons/svg/wx.svg'
</script>
- 自动导入
// 创建 icons/index.js
const req = require.context('./svg', false, /\.svg$/)
req.keys().map(req);
// 创建SvgIcon组件,components/SvgIcon.vue
<template>
<svg :class="svgClass" v-on="$listeners">
<use :xlink:href="iconName"/>
</svg>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
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: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
// 在最外层导入--- main.js
import './icons'
四.环境变量和模式
如果想给多种环境做不同配置,可以利用vue-cli提供的模式。默认 有 development 、 production 、 test 三种模式,对应的,它们的配置文件形式 是 .env.development 。
- 定义一个开发时可用的配置项,创建.env.dev
# 只能用于服务端
name='曾小白'
# 可用于客户端
VUE_APP_DONG=candy-曾小白
- 修改mode选项覆盖模式名称---package.json
// --mode的默认值是development
"serve": "vue-cli-service serve --mode dev"
权限控制
路由分为两种: constantRoutes和asyncRoutes,前者是默认路由可直接访问,后者中定义的路由需要先登录,获取⻆色并过滤后动态加入到Router中。
- 前端根据后端返回的角色信息进行过滤,可以看一下我的实现思路流程图
- 异步获取路由表,当用户登录后向后端请求可访问的路由表,从而动态生成可访问⻚面,将后端返回路由表中组件名称和本地的组件映射
// 前端组件名和组件映射表 const map = {
//xx: require('@/views/xx.vue').default // 同步的方式
xx: () => import('@/views/xx.vue') // 异步的方式
}
// 服务端返回的asyncRoutes
const asyncRoutes = [
{ path: '/xx', component: 'xx',... }
]
// 遍历asyncRoutes,将component替换为map[component]
function mapComponent(asyncRoutes) {
asyncRoutes.forEach(route => {
route.component = map[route.component];
if(route.children) {
route.children.map(child => mapComponent(child))
}
})
}
mapComponent(asyncRoutes)
- 按钮权限,⻚面中某些按钮、链接有时候需要更细粒度权限控制,这时候可以封装一个指令v-permission,自定义指令参考,该指令只能删除挂载指令的元素,对于那些额外生成的和指令无关的元素无能为力--- /src/directives/permission.js
import store from "@/store";
const permission = {
inserted(el, binding) {
// 获取指令的值:按钮要求的角色数组
const { value:pRoles } = binding;
// 获取用户角色
const roles = store.getters && store.getters.roles;
if (pRoles && pRoles instanceof Array && pRoles.length > 0) {
// 判断用户角色中是否有按钮要求的角色
const hasPermission = roles.some(role => {
return pRoles.includes(role);
});
// 如果没有权限则删除当前dom
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el);
}
} else {
throw new Error(`需要指定按钮要求角色数组,如v-permission="['admin','editor']"`);
}
}
};
export default permission;
main.js引入
import vPermission from "./directives/permission";
Vue.directive("permission", vPermission);
App.vue中使用
<button v-permission="['admin', 'editor']">editor button</button>
<button v-permission="['admin']">admin button</button>
- 按钮权限在v-permission不能操作的时候,使用v-if
- 自动生成导航菜单,导航菜单是根据路由信息并结合权限判断而动态生成的。它需要对应路由的多级嵌套,所以要用到递归组件。
封装request
对axios做一次封装,统一处理配置、请求和响应拦截
- 安装
npm i axios -S
- 创建@/utils/request.js
import Axios from "axios";
import { MessageBox, Message } from "element-ui";
import store from "@/store";
// 创建axios实例
const axios = Axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url基础地址,解决不同数据源url变化问题
// withCredentials: true, // 跨域时若要发送cookies需设置该选项
timeout: 5000 // 超时
});
// 请求拦截
axios.interceptors.request.use(
config => {
// do something
const token = localStorage.getItem('token')
if (token) {
// 设置令牌请求头
config.headers["Authorization"] = 'Bearer ' + token;
}
return config;
},
error => {
// 请求错误预处理
//console.log(error) // for debug
return Promise.reject(error);
}
);
// 响应拦截
axios.interceptors.response.use(
// 通过自定义code判定响应状态,也可以通过HTTP状态码判定
response => {
// 仅返回数据部分
const res = response.data;
// code不为1则判定为一个错误
if (res.code !== 1) {
Message({
message: res.message || "Error",
type: "error",
duration: 5 * 1000
});
// 假设:10008-非法令牌; 10012-其他客户端已登录; 10014-令牌过期;
if (res.code === 10008 || res.code === 10012 || res.code === 10014) {
// 重新登录
MessageBox.confirm(
"登录状态异常,请重新登录",
"确认登录信息",
{
confirmButtonText: "重新登录",
cancelButtonText: "取消",
type: "warning"
}
).then(() => {
store.dispatch("user/resetToken").then(() => {
location.reload();
});
});
}
return Promise.reject(new Error(res.message || "Error"));
} else {
return res;
}
},
error => {
//console.log("err" + error); // for debug
Message({
message: error.message,
type: "error",
duration: 5 * 1000
});
return Promise.reject(error);
}
);
export default axios;
使用
import axios from '@/utils/request'
export function login(data) {
return axios.post('/user/login', data)
}
export function getInfo() {
return axios.get('/user/info')
}
-
设置VUE_APP_BASE_API环境变量,创建.env.development文件
-
编写服务接口,创建@/api/user.js
解决跨域
如果请求的接口在另一台服务器上,开发时则需要设置代理避免跨域问题
项目测试
主要介绍一些单元测试写法以及测试报告查看等