一、规范目的:
为提高团队协作效率,便于后台人员添加功能及前端后期优化维护,输出高质量的文档,特制订此文档。本规范文档一经确认,前端开发组人员必须按本文档规范进行前台页面开发。本文档如有不对或者不合适的地方请及时提出,经讨论决定后方可更改。使开发流程更加规范化。
二、基本准则:
符合 vue 标准,结构表现行为分离,兼容性优良。页面性能方面, 代码要求简洁明了有序,尽可能的减小服务器负载,保证最快的解析速度。
三、规范:
本规范主要包括以下部分 :
- vue 框架构建及开发规范
- 打包优化
1、vue 框架构建及开发规范
1.1、vue 项目框架搭建
1.1.1、脚手架构建
// 1.下载项目脚手架(默认安装node开发环境)
git clone nuxt-iview-template.git // PC模版
git clone /nuxt-vant-template.git // H5模版
// 2.安装项目依赖 // 2.1.安装依赖包:
npm install
// 2.2.本地开发:
npm run dev
// 2.3.打包静态html文件:
npm run dist
1.1.2、项目目录结构
├── app.html // 模版文件
├── assets // 静态资源,会被webpack打包
├── backpack.config.js // 打包配置,一般不会动
├── build // nuxt打包后自动生成
├── components // 封装的公共业务组件
├── config // 公共配置文件
│ ├── axios.js //axios请求库配置文件
│ └── index.js
├── layouts // nuxt布局文件,全局
│ ├── default.vue
│ ├── error.vue // 错误处理
│ └── nav.vue
├── lib //公共类
│ └── utils.js
├── middleware // nuxt中间件
│ └── auth.js
├── my-theme // 主题文件
│ └── index.less
├── nuxt.config.js // nuxt主配置文件,修改代理等配置在此,具体配置参考nuxt.js官网
├── pages // 业务页面
├── plugins // 插件
│ ├── apis.js // 全局请求接口封装
│ ├── http.js // 请求库封装this.$http.xxx
│ ├── qs.js // 请求参数处理this.$qs.xxx
│ ├── quill.js // 富文本
│ ├── ui.js // UI库
│ └── utils.js // 工具类this.$Utils.xxx
├── server
│ ├── api
│ ├── apis.js // 后端请求接口,调用this.$APIS.xxx
│ └── index.js
├── static // 静态资源,不会被打包
│ ├── css
│ │ ├── them.css // 主题文件
│ ├── fonts
│ ├── img
│ └── js
└── store // nuxt已经封装vuex状态管理
├── index.js
└── sale.js
1.1.3、技术栈
vue、nuxt(基于vue框架)、axios(http请求库)、querystring
1.1.4、UI 框架选择
- PC 端:推荐使用 iView
- 移动端:推荐使用 vant
1.1.5、css 预处理器
推荐使用 less,scss , 可在 common.css 设置全局样式,如
- 常用样式设置原子类名
- 主题颜色, UI 设计规范等样式
- 全局组件公共样式
1.1.6、移动端适配
- 以蓝湖 750px 设计稿。
- 使用 rem 适配,代码书写 px,用 postcss-px2rem 将 px 转化为 rem。
// nuxt.config.js
postcss: [
require('autoprefixer')({
browsers: ['last 3 versions']
}),
require('postcss-px2rem')({
remUnit: 50
})
],
// rem.js
/* eslint-disable */
!function () {
var a = "@charset \"utf-8\";html{color:#000;background:#fff;overflow-y:scroll;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}html *{outline:0;-webkit-text-size-adjust:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}html,body{font-family:sans-serif}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td,hr,button,article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{margin:0;padding:0}input,select,textarea{font-size:100%}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}abbr,acronym{border:0;font-variant:normal}del{text-decoration:line-through}address,caption,cite,code,dfn,em,th,var{font-style:normal;font-weight:500}ol,ul{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:500}q:before,q:after{content:''}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}a:hover{text-decoration:underline}ins,a{text-decoration:none}",
b = document.createElement("style");
if (document.getElementsByTagName("head")[0].appendChild(b), b.styleSheet) b.styleSheet.disabled || (b.styleSheet.cssText = a); else try {
b.innerHTML = a
} catch (c) {
b.innerText = a
}
}();
!function (a, b) {
function c() {
var b = f.getBoundingClientRect().width;
b / i > 540 && (b = 540 * i);
var c = b / 7.5;
f.style.fontSize = c + "px", k.rem = a.rem = c
}
var d, e = a.document, f = e.documentElement, g = e.querySelector('meta[name="viewport"]'),
h = e.querySelector('meta[name="flexible"]'), i = 0, j = 0, k = b.flexible || (b.flexible = {});
if (g) {
console.warn("将根据已有的meta标签来设置缩放比例");
var l = g.getAttribute("content").match(/initial\-scale=([\d\.]+)/);
l && (j = parseFloat(l[1]), i = parseInt(1 / j))
} else if (h) {
var m = h.getAttribute("content");
if (m) {
var n = m.match(/initial\-dpr=([\d\.]+)/), o = m.match(/maximum\-dpr=([\d\.]+)/);
n && (i = parseFloat(n[1]), j = parseFloat((1 / i).toFixed(2))), o && (i = parseFloat(o[1]), j = parseFloat((1 / i).toFixed(2)))
}
}
if (!i && !j) {
var p = (a.navigator.appVersion.match(/android/gi), a.navigator.appVersion.match(/iphone/gi)),
q = a.devicePixelRatio;
i = p ? q >= 3 && (!i || i >= 3) ? 3 : q >= 2 && (!i || i >= 2) ? 2 : 1 : 1, j = 1 / i
}
if (f.setAttribute("data-dpr", i), !g)if (g = e.createElement("meta"), g.setAttribute("name", "viewport"), g.setAttribute("content", "initial-scale=" + j + ", maximum-scale=" + j + ", minimum-scale=" + j + ", user-scalable=no"), f.firstElementChild) f.firstElementChild.appendChild(g); else {
var r = e.createElement("div");
r.appendChild(g), e.write(r.innerHTML)
}
a.addEventListener("resize", function () {
clearTimeout(d), d = setTimeout(c, 300)
}, !1), a.addEventListener("pageshow", function (a) {
a.persisted && (clearTimeout(d), d = setTimeout(c, 300))
}, !1), "complete" === e.readyState ? e.body.style.fontSize = 12 * i + "px" : e.addEventListener("DOMContentLoaded", function () {
e.body.style.fontSize = 12 * i + "px"
}, !1), c(), k.dpr = a.dpr = i, k.refreshRem = c, k.rem2px = function (a) {
var b = parseFloat(a) * this.rem;
return "string" == typeof a && a.match(/rem$/) && (b += "px"), b
}, k.px2rem = function (a) {
var b = parseFloat(a) / this.rem;
return "string" == typeof a && a.match(/px$/) && (b += "rem"), b
}
}(window, window.lib || (window.lib = {}));
1.1.7、字体图标
推荐使用 iconfont
1.2、路由
Nuxt.js 依据 pages 目录结构自动生成 vue-router 模块的路由配置。 具体参考 Nuxt.js 官方文档。
1.3、http 数据请求
推荐使用 axios 作为 http 请求库
1.3.1、设置全局请求拦截和响应封装
// config/http.js
import axios from 'axios'
import Vue from 'vue'
import querystring from "querystring";
if (process.server) {
axios.defaults.baseURL = `http://${process.env.HOST || 'localhost'}:${process.env.PORT || 3000}/api`
}
axios.defaults.headers['Content-Type'] = 'application/json'
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
axios.defaults.withCredentials = true
axios.defaults.timeout = 100000
// 请求之前
axios.interceptors.request.use(req => {
// console.log('request: ', JSON.stringify(req))
return req
})
// 登陆失效跳转登录页面(没有实现)
axios.interceptors.response.use(
response => {
if (response.status === 200) {
var status = response.data.status
return Promise.reject(response.data)
} else {
return Promise.reject(response)
}
},
error => {
if (error.response.status) {
switch (error.response.status) {
case 401:
//
break
}
}
return Promise.reject(error)
}
)
export default axios
1.3.2、把 axios 方法挂载到 vue 实例上
// plugins/http.js
import Vue from 'vue'
import axios from '../config/http'
Vue.prototype.$http = axios //注册为vue内部方法 使用时this.$http.get/post
1.4、工具类
1.4.1、工具方法
具体查看代码lib/utils.js
1.4.2、将工具类挂载到 Vue 实例上
// plugins/utils.js
import * as Utils from '../lib/utils'
import Vue from 'vue'
Vue.prototype.$Utils = Utils; //注册为vue内部方法 使用时this.$Utils
1.5、接口地址
1.5.1、全局配置
// server/apis.js
const PREFIX = ""
const OSS = ""
const ERP = ""
const APIS = {
FILE_BASE_URL: '',//文件默认url
}
export default APIS;
1.5.2、挂载到 vue 实例
// plugins/apis.js
import Vue from 'vue';
import apis from '../server/apis';
Vue.prototype.$APIS = apis; //注册为vue内部方法 使用时this.$APIS
1.6、命名规范
——让团队当中其他人看你的代码能一目了然
- 1.文件夹和文件命名以业务或者模块名字为主,驼峰式命名。
- 2.组件命名遵循以下原则,使用短横线(car-lib)进行组件声明,使用短横线分隔命名(`)进行使用。
- 3.当项目中需要自定义比较多的基础组件的时候,比如一些 button,input,icon,建议以一个统一的前缀如 Erp 开头,这样做的目的是为了方便查找。
- 4.method 方法命名使用驼峰式,动词+名词,如 getData, submitForm
- 5.变量命遵循语义化原则,使用驼峰式。
1.7、命名规范
1.7.1、 vue 风格推荐
- Props 定义应该尽量详细。
// bad
props: ['status']
// good
props: {
status: String
}
// better
props: {
status: {
type: String,
required: true,
validator: function (value) {
return ['syncing','synced','version-conflict','error'].indexOf(value) !== -1
}
}
}
- 使用 v-for 必须加上 key 值
<!-- bad -->
<ul>
<li v-for="todo in todos"></li>
</ul>
<!-- good -->
<ul>
<li v-for="todo in todos" :key="todo.id"></li>
</ul>
- 组件的 data 必须是一个函数
// bad
Vue.component('some-comp', {
data: {
foo: 'bar'
}
})
// good
Vue.component('some-comp', {
data: function() {
return {
foo: 'bar'
}
}
})
- 组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。
// bad
- 指令缩写
<!-- bad -->
<input v-bind:value="newTodoText" :placeholder="newTodoInstructions" v-on:input="onInput" />
<!-- good -->
<input :value="newTodoText" :placeholder="newTodoInstructions" @input="onInput" />
1.7.2 、关于组件内样式
- 为组件样式设置作用域
/* bad */
<style>
.btn-close {
background-color: red;
}
</style>
/* good */
<style scoped>
.button-close {
background-color: red;
}
</style>
- 若要改变第三方组件库的样式,需要加上顶级作用域。
/* bad */
.ivu-input {
width: 254px !important;
}
/* good */
.customerForm .ivu-input {
width: 254px !important;
}
/* .customerForm为当前组件的顶级dom */
1.7.3、关于组件结构
- 组件结构遵循从上往下 template,script,style 的结构。
<template>
<div></div>
</template>
<script>
export default {}
</script>
<style lang="scss" scoped></style>
script 部分各方法成员遵循以下顺序放置。
- name
- components
- props
- data
- computed
- watch
- created
- mounted
- update
- methods
1.7.4、关于注释规范
以下情况需要加注释,以方便代码维护和他人理解
- 公共组件使用说明
- 各组件中重要函数或者类说明
- 复杂的业务逻辑处理说明
- 多重 if 判断语句
1.7.5、其他规范
- 建议不再使用双引号,静态字符串使用单引号,动态字符串使用反引号衔接。
// bad
const foo = 'jack'
const bar = foo + ',前端工程师'
// good
const foo = 'jack'
const bar = `${foo},前端工程师`
- 使用数组展开操作符 ... 复制数组。
// bad
const len = items.length
const itemsCopy = []
let i
for (i = 0; i < len; i += 1) {
itemsCopy[i] = items[i]
}
// good
const itemsCopy = [...items]
- 使用数组对象解构赋值
const arr = [1, 2, 3, 4]
// bad
const first = arr[0]
const second = arr[1]
// good
const [first, second] = arr
2、打包优化
为什么要优化打包?
- 项目越做越大,依赖包越来越多,打包文件太大
- 单页面应用首页白屏时间长,用户体验差
我们的目的
- 减小打包后的文件大小
- 首页按需引入文件
- 优化 webpack 打包时间
下面是提供的思路:
2.1 、升级 nuxt
npm install nuxt@latest
npm run dev
2.2、升级配置
// 升级配置 package.json
"dependencies": {
"ali-oss": "^6.0.1",
"axios": "^0.16.2",
"cross-env": "^5.0.1",
"echarts": "^4.0.4",
"express": "^4.15.3",
"iview": "^3.4.0",
"js-cookie": "^2.2.0",
"less": "^2.7.2",
"less-loader": "^4.0.5",
"lodash-core": "^0.8.1",
"moment": "^2.22.1",
"nuxt": "^2.3.4",
"postcss": "^7.0.6",
"querystring": "^0.2.0",
"source-map-support": "^0.5.9",
"v-charts": "^1.16.8",
"vue-style-loader": "^4.1.2"
},
"devDependencies": {
"@nuxtjs/axios": "^5.3.6",
"@nuxtjs/proxy": "^1.3.1",
"autoprefixer-loader": "^3.2.0",
"babel-eslint": "^10.0.1",
"babel-polyfill": "^6.26.0",
"backpack-core": "^0.8.3",
"css-loader": "^1.0.1",
"eslint": "^5.9.0",
"eslint-config-standard": "^12.0.0",
"eslint-loader": "^2.1.1",
"eslint-plugin-html": "^5.0.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-node": "^8.0.0",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-standard": "^4.0.0"
}
2.3、升级后可能碰到的问题及修复
- 升级后每次修复都会弹 Are you interested in participation?
//nuxt.config.js https://blog.csdn.net/qq_39473019/article/details/107424181
telemetry: false,
- 运行后有部分地方使用 loadash,发现会报错,解决方案,在 plugins 文件夹下创建 lodash.js,并在 nuxt.config.js 里配置
// https://blog.csdn.net/dexing07/article/details/78907639
// plugins/lodash.js
let _ = require('lodash')
if(process.browser){
window._ = _
}
// nuxt.config.js
plugins: [
{src: '~plugins/lodash', ssr: false},
],
2.4、打包优化方向
2.4.1、修改 nuxt 配置文件
- 修改nuxt.config.js配置analyze: true,运行 npm run generate 分析包的大小
- 处理 vendor.app.js 文件,修改 nuxt.config.js 配置文件
// nuxt.config.js
optimization:{
splitChunks:{
minSize:10000,
maxSize:250000
}
},
2.4.2、UI 框架优化
- vant 按需加载,修改 plugins/ui.js,删除全局引用 vant,改为按需引用(H5 端)
- 修改 vant 引用 css 的方式(H5 端)
2.4.3、其他优化方向
CDN、服务器开启 Gzip