我现在公司的前端操作规范

3,852 阅读6分钟

Tips:这个规范是公司要求我来制定的,希望看官们可以帮忙多提些意见~

关于业务代码数据规范

本项规范中,单词的命名均为驼峰形式

具有业务功能的按钮

对于具有一定业务功能的按钮,如 添加用户 删除用户 编辑用户 等,我们将用以  handle 开头,业务结尾的方式来命名
以下述情况为例:

按钮业务命名
添加用户handleUserAdd
删除用户handleUserDel
编辑用户handleUserEdit
批量删除用户handleUserDelBatch
导入用户ExcelimportUserExcel
导出用户ExcelexportUserExcel

tips:在这里我们需要注意一种情况,那就是如果我们的方法名称和我们导入的接口名称一样,那么我们应该以改变导入的接口名称为第一操作方式

**

普通的命名方法

有意义的命名即可

搜索条件及表格数据的命名

这将会分为两种情况,第一情况是无重名情况,另一种情况是有重名的情况

无重名的情况

字段意义命名
搜索条件searchApi
表格数据tableData
树的数据treeData

有重名情况

我们可以结合实际业务对每个条件进行命名
下面将会给出示例:

字段意义命名
搜索城市列表条件citySearchApi
城市表格数据cityTableData
城市的树形数据cityTreeData

对于data中的数据规范

分两种情况

数据量不大

按如下方式

		{
      stringVariable: "",
  		nullVariable|numberVariable:null,	
  		arrayVariable:[],
			objectVariable:{},
			arrayObjectVariable:[{},{}]
    }

按具体的业务分组

		{
  		//group1
      stringVariable: "",
  		nullVariable|numberVariable:null,	
  		arrayVariable:[],
			objectVariable:{},
			arrayObjectVariable:[{},{}]
      
      //group2
      stringVariable: "",
  		nullVariable|numberVariable:null,	
  		arrayVariable:[],
			objectVariable:{},
			arrayObjectVariable:[{},{}]
    }

书写方法注释的规范

需要为方法提供它的变量、以及作用的注释

/**
   * @description: 方法作用
   * @param {参数类型} 参数名称	//如果接收参数的话
   * @param {参数类型} 参数名称
   * @return {返回值类型}	//如果有返回值的话
   */

接口api的书写规范


/**
 * @func 接口名称
 * @param  params	//params是放在request header 中的请求参数 ~可选
 * @param  data	// data是放在request body 内的参数 ~可选
 * @description 接口的描述
 */
export const getAuthUserList = (params,data) => {
  return request({
    url: '/auth/role/list',	//接口请求的url
    method: 'get',	// 接口请求方式
    params,	//request header参数
    data	// request boyd参数
  })
}

tips:对于同属于一个功能模块的接口请求,我们应该将它们放在同个js文件内,并封装成如上形式

**

接口api的使用规范

import { getAuthUserList } from '@/api/auth'

//.......省略的部分.....

async getuserList(){

  try{
  	const res = await getAuthUserList()
    //........
  }catch{
  	// do something
  }
}


Vuex的使用规范

对于经常会在页面中调用的state,需要在getter中暴露



代码规范

关于v-for的key

总是不建议直接使用index来作为当前列表的key,应当尽可能地选用其它具有唯一性的值作为key

关于v-if 与 v-show ,需要明确二者之间的区别

v-if 如果为false,则初始不会渲染,如果切换v-if的值,它总是会重新渲染 v-show 不管初始值为何,都会渲染,且切换值仅仅是切换display:none这一css 对于过于频繁切换显示与否,且不涉及到内容视图强制更新的场景,更加推荐使用v-show

关于v-once

对于某些仅需要渲染一次的内容,更加推荐使用v-once

**tips:**如果在v-for中使用了v-once,那么我们必须要为这个元素加上一个key


关于视图强制渲染 

这里总是推荐使用如下两种方式

forceUpdate  


//component.vue
export default {
  methods:{
    componentUpdate(){
      this.$forceUpdate()
    }
  }
}

key 

//component.vue
<template>
  <component-child :key='componentChildKey'></component-child>
</template>
<script>
  export default {
    data(){
      return {
        componentChildKey:1
      }
    },
    methods:{
      upDateComponentChild(){
        this.componentChildKey += 1
      }
    }
  }
</script>

关于数据的处理

过滤掉我们不需要的字段

对于某些业务场景中,如果后端返回的数据有一些是我们用不到的,那么我们应该在接收到数据的时候使用map映射来对这个数据进行简单的处理

//..... 
try{
	const res = await getDates()
  this.tableData = res.map(el => {
  	let {a,b,c} = el
    return{a,b,c}
  }}
}catrh{return}

//.....

处理不需要响应式变动的数据

对于这种数据,我们应该使用**Object.freeze()**将这个数据进行冻结,因为vue对于configurable属性为false的数据是不会注入依赖的
亦或者我们也可以使用其它方法,类似Object.preventExtensions的这种方法

数组或者对象中判断是否存在某一条件的某一值

对于数组

直接的找值,我们可以使用Array.includes 、Array.indexOf等
对于条件值,需要我们进行循环的,我们可以使用Array.some

对于对象

如果我们有同上面类似的需求,查找是否存在符合某一条件的字段~
那么我们可以先使用Object.entries来行到其自身的可以枚举属性,拉下来就是数组的活了~

关于Promise

对于异步的请求,我总是推荐将其放在try catch中进行书写,在上面已经有一段示例中用到了这种写法

对于复杂的if条件判断

如果一个if()中的判断条件达到了2条或者以上,亦或者这个判断条件会在这个代码块中多次用到,我们应当使用一个命名合理的变量将这个判断结果保存

关于 watch computed methods

首先,我们先明确它们的使用场景:

  1. watch

监听某一个数据,它会影响到多个数据或者当它变化时,我们需要进行大量的其它操作

  1. computed

它是依赖于其它数据进行变化的,亦或者此项数据的计算有着极大地性能消耗

  1. methods

它是实时更新的

业务场景:同时监听多个数据

<script>
export default(){
  computed:{
    handleChange(){
      const {fileName,FolderName} = this.$props
    }
  },
    watch:{
      handleChange:{
        handler:function(val,oldval){
          //do somting
        },
        immediate:true
      }
    }
}
</script>

Css书写规范

命名语义化

不要使用表象和晦涩难懂的名称,以下是错误的示例

<div class='red'></div>

Tips:如果不是必要情况,请不使用id

顺序问题

我们书写css的属性时,应该尽可能地遵循以下顺序

1.位置属性(position, display, float, z-index等) 2.大小(width, height, padding, margin) 3.文字相关 4.背景相关(background, border等) 5.其他(animation, transition等)

回流与重绘问题

如果某一块的css可能会经常改变,那么我们需要考虑其回流与重绘造成的性能代价,我们使用transition、transform等来替代
关于transiton和transform,可以参见下文:

transform
描述
none定义不进行转换。
matrix(n,n,n,n,n,n)定义 2D 转换,使用六个值的矩阵。
matrix3d(n,n,n,n,n,n,n,n,n,n,n,n,n,n,n,n)定义 3D 转换,使用 16 个值的 4x4 矩阵。
translate(x,y)定义 2D 转换。
translate3d(x,y,z)定义 3D 转换。
translateX(x)定义转换,只是用 X 轴的值。
translateY(y)定义转换,只是用 Y 轴的值。
translateZ(z)定义 3D 转换,只是用 Z 轴的值。
scale(x,y)定义 2D 缩放转换。
scale3d(x,y,z)定义 3D 缩放转换。
scaleX(x)通过设置 X 轴的值来定义缩放转换。
scaleY(y)通过设置 Y 轴的值来定义缩放转换。
scaleZ(z)通过设置 Z 轴的值来定义 3D 缩放转换。
rotate(angle)定义 2D 旋转,在参数中规定角度。
rotate3d(x,y,z,angle)定义 3D 旋转。
rotateX(angle)定义沿着 X 轴的 3D 旋转。
rotateY(angle)定义沿着 Y 轴的 3D 旋转。
rotateZ(angle)定义沿着 Z 轴的 3D 旋转。
skew(x-angle,y-angle)定义沿着 X 和 Y 轴的 2D 倾斜转换。
skewX(angle)定义沿着 X 轴的 2D 倾斜转换。
skewY(angle)定义沿着 Y 轴的 2D 倾斜转换。
perspective(n)为 3D 转换元素定义透视视图。
默认值:none
继承性:no
版本:CSS3
JavaScript 语法:object.style.transform="rotate(7deg)"

transtion
描述
transtion-property规定设置过渡效果的 CSS 属性的名称。
transtion-duration规定完成过渡效果需要多少秒或毫秒。
transtion-timing-function规定速度效果的速度曲线。
transtion-delay定义过渡效果何时开始。
默认值:all 0 ease 0
继承性:no
版本:CSS3
JavaScript 语法:object.style.transition="width 2s"

尽量避免使用标签名

尽量避免以下写法:

.app-header span{
	
}

推荐以下写法

.app-header>.name-title{

}

Css注释规范

/* 
 * 块状注释
 */

/* 单行注释文字 */

关于常规框架使用

路由

懒加载

首先,对于路由,我们要使用懒加载的方式

{
   path: '/home',
   name: 'home',
   component: import(/* webpackChunkName: '你要设置的chunk的name' */ '../page/home')
}

权限

我们的系统内部的路由有两个部分,一部分是静态路由,一部分是动态路由
对于引入权限之后,需要接入动态路由的情况下,可以打开 src/store/modules/permission.js 拉到下面的 generateRoutes 这个action,切换注释区域

组件

单独的页面

我们的最外层我已经设置了一个class,如果不是需要,请不要单独再设置一个class来控制,具体的在APP.vue中~

懒加载

强调的是,对于象Modal的这种的组件,我们总是应该使用懒加载来加载它~
这里的 /* webpackChunkName: "[request]" */ 中的 request 表示实际解析的文件名。

const PageFooter = () => import(/* webpackChunkName: "[request]" */'_c/PageFooter')

export default {
    components: {
        'page-footer':pageFooter
    }
}

权限

我们组件内部的一些按钮权限会放在meta中

只需要控制单个按钮的情况:
<template>
	<div>
    <el-Button v-permission:delete="roles">按钮</el-Button>
	</div>
</template>

<script>
computed: {
    hasSelected() {
      return this.delUserList.length > 0
    },
    roles() {
      return this.$route.meta.roles ? this.$route.meta.roles : []
    }
  },
</script>

控制条件渲染的情况
<template>
	<div>
    <div v-if='hasRole'>条件渲染区域</div>
	</div>
</template>

<script>
computed: {
    hasRole() {
      const arr = ['add', 'edit', 'delete']
      const hasRole = arr.some(el => {
        return this.roles.some(e => e === el)
      })
      return hasRole
    }
    roles() {
      return this.$route.meta.roles ? this.$route.meta.roles : []
    }
  },
</script>

SearchForm

这是一个表单搜索组件,目前只提供一些简单的功能,建议可以使用的 type 目前只有 : el-input el-select el-date-picker
需要注意的是,戴止我写完这偏文档为止,时间选择器在同一SearchForm中,有且仅能有一个~
使用方法我将在下面做出一个demo示例:

<template>
  <div class="card-block">
    <search-form :list="formList" :search-width="200" @change="initChange" />
  </div>
</template>

<script>
const SearchForm = () => import('_c/SearchForm')
export default {
  components: {
    'search-form': SearchForm
  },
  data() {
    return {
      formList: [
        {
          name: 'name',
          type: 'el-input',
          value: '',
          placeholder: '请输入姓名'
        },
        {
          name: 'age',
          type: 'el-select',
          value: '',
          placeholder: '请输入岁数',
          children: {
            type: 'el-option',
            list: [{
              value: '选项1',
              label: '黄金糕',
              title: '黄金糕'
            },
            {
              value: '选项2',
              label: '黄金糕2',
              title: '黄金糕2'
            }]
          }
        },
        {
          name: 'time',
          type: 'el-date-picker',
          //指定单个表单的宽度,如果不写,则会取上面的search-width,如果search-width也没有给,则是会取默认宽度180
          width: 450	
        }
      ],
      pageApi: {
        Index: 1,
        PageSize: 10
      }
    }
  },
  methods: {
    initChange(date) {
      // 全并查询条件
      this.pageApi = Object.assign(this.pageApi, date)
      console.log(this.pageApi)
    }
  }
}
</script>