1.框架周边生态使用
- 框架限于vue2.x
- 目前公司研发中台,交付中台均属于中大型项目,因此vuex 和 vue-router必须使用
- UI基于elementui ,使用体验设计部开发的md-ui 后续开发剔除掉elementui
- 工具库使用lodash,包括青鸟组件库的 md-hooks的使用
2.脚手架
- 目前项目框架搭建由体验设计部提供通用框架,部门工具需求进行一定的自定义修改
- 部门可以搭建 npm 私服,结合通用框架使用Node环境开发CLI工具,提供一套部门的通用框架
- 可根据需求完善开发的工具(如:快速创建组件,快速创建页面,过滤器,指令,工具函数等)
3.开发规范
- 代码风格,命名规范,目录结构统一
- git提交记录,多人协作开发规范
- 静态资源使用的规范
- 页面结构
- 根据业务需要划分,按照路由层级在 views 中创建相对应的页面组件,以文件夹的形式创建,并在文件夹内创建 index.vue 文件作为页面的入口文件。
- 页面内的组件:在页面文件夹下创建 components 文件夹,在其内部对应创建相应的组件文件,如果是复杂组件,应以文件夹的形式创建组件。
- 工具模块:能够高度抽象的工具模块,应创建在 @/lib 内创建 js 文件。
4.样式
-
目前按照项目框架依赖,部门项目都使用 Sass/Scss 预处理器
-
局部样式使用scoped 方案
-
全局样式定义variable.scss文件书写,多项目公共样式使用common.scss,布局样式使用 layout.scss
-
可以不通过 import 的方式在项目中任何位置使用这些变量和公共样式。
-
// vue.config.js module.exports = { css: { loaderOptions: { sass: { prependData: ` @import '@/styles/variable.scss'; @import '@/styles/mixins.scss'; `, }, }, }, }
-
-
使用nprogress 对路由跳转做伪进度条,解决网络不好情况下用户知道页面已经在加载中了
-
import NProgress from 'nprogress'; router.beforeEach(() => { NProgress.start(); }); router.afterEach(() => { NProgress.done(); });
-
-
首次加载页面时,会产生大量的白屏时间,按照目前交付,研发,需求中心等项目, 直接在 public/index.html 里写一些静态的样式即可。
-
关于100vh的使用,目前项目大量使用100vh,移动端100vh会出现视口高度与想象不一致的情况
-
100vh !== 视口高度 100vh === 视口高度 + 浏览器工具栏(地址栏等等)的高度
-
安装 vh-check
npm install vh-check --save
解决 -
import vhCheck from 'vh-check'; vhCheck('browser-address-bar'); // 定义css mixin @mixin vh($height: 100vh) { height: $height; height: calc(#{$height} - var(--browser-address-bar, 0px)); }
-
5.组件库
- 使用 md-ui 进行开发,不要多UI框架一起使用,不要引入element-ui
- 对md-ui 按需引入,避免打包后css 体积过大
- 设计师设计图区别于md-ui的样式定义单独的样式文件进行覆盖,局部更改则在组件中v-deep穿透修改
- 一些体积较大的组件不要全局引用,尽量局部引用,减缓首屏加载压力
- 第三方工具进行二次封装,不论是之后由于各种原因更换工具,还是使用上都会更加灵活
6.异步请求,封装Axios
根据之前项目结合其他开源项目,在 @/libs/http.js
路径下对 Axios 进行封装,封装了请求参数,请求头,以及错误提示信息、 request 拦截器、response 拦截器、统一的错误处理、baseURL 设置等。
import axios from 'axios';
import get from 'lodash/get';
import storage from 'store';
// 创建 axios 实例
const request = axios.create({
// API 请求的默认前缀
baseURL: '/',
timeout: 50000, // 请求超时时间
});
// 异常拦截处理器
const errorHandler = (error) => {
const status = get(error, 'response.status');
switch (status) {
/* eslint-disable no-param-reassign */
case 400: error.message = '请求错误'; break;
case 401: error.message = '未授权,请登录'; break;
case 403: error.message = '拒绝访问'; break;
case 404: error.message = `请求地址出错: ${error.response.config.url}`; break;
case 408: error.message = '请求超时'; break;
case 500: error.message = '服务器内部错误'; break;
case 501: error.message = '服务未实现'; break;
case 502: error.message = '网关错误'; break;
case 503: error.message = '服务不可用'; break;
case 504: error.message = '网关超时'; break;
case 505: error.message = 'HTTP版本不受支持'; break;
default: break;
/* eslint-disabled */
}
return Promise.reject(error);
};
// request interceptor
request.interceptors.request.use((config) => {
// 如果 token 存在
// 让每个请求携带自定义 token 请根据实际情况自行修改
// eslint-disable-next-line no-param-reassign
config.headers.Authorization = `bearer ${storage.get('ACCESS_TOKEN')}`;
return config;
}, errorHandler);
// response interceptor
request.interceptors.response.use((response) => {
const dataAxios = response.data;
// 这个状态码是和后端约定的
const { code } = dataAxios;
// 根据 code 进行判断
if (code === undefined) {
// 如果没有 code 代表这不是项目后端开发的接口
return dataAxios;
// eslint-disable-next-line no-else-return
} else {
// 有 code 代表这是一个后端接口 可以进行进一步的判断
switch (code) {
case 200:
// [ 示例 ] code === 200 代表没有错误
return dataAxios.data;
case 'xxx':
// [ 示例 ] 其它和后台约定的 code
return 'xxx';
default:
// 不是正确的 code
return '不是正确的code';
}
}
}, errorHandler);
export default request;
- 我们项目是通过cookie 存储通证,此处使用的是sotre 的包作为本地储存的工具用来存储 token
- 跨域问题的话使用proxy解决,根据之前项目,都是在config文件夹定义proxy.js来做开发时跨域处理
7.mock数据
Mock 数据功能是基于 mock.js开发,通过webpack配置,自动加载mock配置文件
-
所有的 mock 配置文件均应放置在
@/mock/services
路径内 -
在
@/mock/services
内部可以建立业务相关的文件夹分类存放配置文件。 -
所有的配置文件应按照
***.mock.js
的命名规范创建。 -
入口
-
import Mock from 'mockjs'; Mock.setup({ timeout: '500-800', }); const context = require.context('./services', true, /\.mock.js$/); context.keys().forEach((key) => { Object.keys(context(key)).forEach((paramKey) => { Mock.mock(...context(key)[paramKey]); }); });
-
8.路由
-
Layout:
-
目前体验设计部提供的Layout组件有三个,分别为 Basic ,User,Blank。 分别为有侧边栏,无侧边栏,空白三种
-
结合一些开源项目,可以对于登录或权限认证的路由进行划分,拆出FrameIn,FrameOut两种,一个是需要权限验证的页面,一个是不需要权限验证的页面,如登录页
-
在路由中,集成了权限验证的功能,需要为页面增加权限时,在 meta 下添加相应的 key:
-
auth
- 类型:Boolean , 当 auth 为 true 时,此页面需要进行登陆权限验证,只针对 frameIn 路由有效。
-
permissions
- 类型:Object , permissions 每一个 key 对应权限功能的验证,当 key 的值为 true 时,代表具有权限,若 key 为 false,配合
v-permission
指令,可以隐藏相应的 DOM - https://blog.csdn.net/weixin_45646663/article/details/112277206 【网上关于permissions做权限的博文】
- 类型:Object , permissions 每一个 key 对应权限功能的验证,当 key 的值为 true 时,代表具有权限,若 key 为 false,配合
-
import router from '@/router';
import store from '@/store';
import storage from 'store';
import util from '@/libs/utils';
// 进度条
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
const loginRoutePath = '/user/login';
const defaultRoutePath = '/home';
/**
* 路由拦截
* 权限验证
*/
router.beforeEach(async (to, from, next) => {
// 进度条
NProgress.start();
// 验证当前路由所有的匹配中是否需要有登录验证的
if (to.matched.some((r) => r.meta.auth)) {
// 是否存有token作为验证是否登录的条件
const token = storage.get('ACCESS_TOKEN');
if (token && token !== 'undefined') {
// 是否处于登录页面
if (to.path === loginRoutePath) {
next({ path: defaultRoutePath });
// 查询是否储存用户信息
} else if (Object.keys(store.state.system.user.info).length === 0) {
store.dispatch('system/user/getInfo').then(() => {
next();
});
} else {
next();
}
} else {
// 没有登录的时候跳转到登录界面
// 携带上登陆成功之后需要跳转的页面完整路径
next({
name: 'Login',
query: {
redirect: to.fullPath,
},
});
NProgress.done();
}
} else {
// 不需要身份校验 直接通过
next();
}
});
router.afterEach((to) => {
// 进度条
NProgress.done();
util.title(to.meta.title);
});
9.构建优化
-
使用 vue 的图形化界面可以查看界面资源的一些具体情况
-
开启 Gzip
-
chainWebpack: (config) => { config .plugin('CompressionPlugin') .use(CompressionPlugin, []); },
-
-
路由懒加载
-
{ path: 'home', name: 'Home', component: () => import( /* webpackChunkName: "home" */ '@/views/home/index.vue' ), }, // webpackChunkName 这条注释必须加上,方便打包后知道那个页面过于冗余。
-
-
preload & prefetch 优化页面打开速度
10.单元测试
- 根据具体项目进行推进,暂时项目均未做单元测试
11.Vuex
- 用户信息管理(储存信息、对 token 进行操作等)
- 登陆(调接口)
- 菜单管理(储存路由信息,生成菜单,模糊查询等功能)
- 全屏操作
- Loading
12.过滤器
常用过滤器全局注册
- 日期时间
- 文件大小
13.指令
常用指令全局注册
- 组件权限验证
- 焦点
- 图片懒加载
- 滚动至指定位置
14.代码基础规范
-
降低css选择器复杂性,增强性能【浏览器读取选择器,遵循的原则是从选择器的右边到左边读取。】
-
避免使用通配符选择器*
-
不考虑IE 678 情况下尽量使用 flexbox布局,性能更好
-
transforms 和 opacity 这两个属性更改不会触发重排与重绘,做动画尽量使用他们
-
当数值为 0 - 1 之间的小数时,建议省略整数部分的 0。
-
当长度为 0 时建议省略单位
-
不使用命名色值。
-
当元素需要撑起高度以包含内部的浮动元素时,通过对伪类设置 clear 或触发 BFC 的方式进行 clearfix。尽量不使用增加空标签的方式。
-
除公共样式之外,在业务代码中尽量不能使用 !important。
-
将 z-index 进行分层,对文档流外绝对定位元素的视觉层级关系进行管理。
-
字号应不小于 12px(PC端)。
-
font-weight 属性使用数值方式描述。
15.vue规范
- 当在组件中使用 data 属性的时候 (除了 new Vue 外的任何地方),它的值必须是返回一个对象的函数
data() { return {...} }
。 - prop 的定义应该尽量详细,至少需要指定其类型。
- 布尔类型的 attribute, 为 true 时直接写属性值。
- 不要在computed中对vue变量进行操作。
- 应该优先通过 prop 和事件进行父子组件之间的通信,而不是 this.$parent 或改变 prop。
- 在组件上总是必须用 key 配合 v-for,以便维护内部组件及其子树的状态。
- v-if 和 v-for 不能同时使用
- 公共方法尽量不要挂到原型上, 可以写在 utils 文件,也可以使用 mixin 文件。不要将业务公共组件注册到全局。
- 不要将任何第三方插件挂载到 vue 原型上。
- 具有高度通用性的方法,要封装到 libs、全局组件或指令集里。
- 为组件样式设置作用域。
- 尽量使用指令缩写。
16.vuex规范
State为单一状态树,在 state 中需要定义我们所需要管理的数组、对象、字符串等等,只有在这里定义了,在 vue 的组件中才能获取你定义的这个对象的状态
- 修改
state
中数据必须通过mutations
。 - 每一个可能发生改变的
state
必须同步创建一条或多条用来改变它的mutations
。 - 服务端获取的数据存放在
state
中,作为原始数据保留,不可变动。
Getters 类似 vue.js 的计算属性,当我们需要从 store 的 state 中派生出一些状态,那么我们就需要使用 getters,getters 会接收 state 作为第一个参数,而且 getters 的返回值会根据它的依赖被缓存起来,只有 getters 中的依赖值(state 中的某个需要派生状态的值)发生改变的时候才会被重新计算。
- 通过
getters
处理你需要得到的数据格式,而不是通过修改state
原始数据。 - 组件内不强制使用
mapGetters
,因为你可能需要使用getter
和setter
。 - 改变
state
的唯一方法就是提交mutations
。 - 组件内使用
mapMutations
辅助函数将组件中的methods
映射为store.commit
调用。 - 命名采用
大写字母
+下划线
的规则。
Actions 处理复杂数据处理以及异步操作时使用
- 页面重的数据接口尽量在
actions
中调用。 - 服务端返回的数据尽量不作处理,保留原始数据。
- 获取到的数据必须通过调用
mutations
改变state
。
Modules 按照页面情况划分modules, 之前项目都有做这一块
- 通常情况下按照页面划分
modules
- 默认内置了
system
保证了脚手架的基础功能。 - 每个页面模块或页面的子模块添加属性
namespaced: true
。