erp前端项目总结
一、项目目录(vue-cli2)
├── build
├── config
├── dist //打包后生成的静态页面文件
├── src
│ ├── assets //全局静态图片
│ ├── components //所有组件(项目核心部分)
│ │ ├── element //业务代码
│ │ ├── layout //布局代码(导航,头,和主体部分)
│ │ ├── login //登录代码
│ │ ├── public//公用功能组件
│ ├── filters //全局过滤器
│ ├── my-theme //全局样式表
│ ├── router //路由
│ ├── sever
│ │ ├── api.js //全局API
│ │ ├── method // API方法
│ │ ├── https.js // axios封装,路由请求响应拦截等
│ ├── store //状态管理
│ ├── utils //公用方法
│ │ ├── public_function //公用api增删改查审核作废等方法
│ │ ├── fetch.js //签名生成方法
│ │ ├── rules.js //封装验证方法
│ ├── App.vue //父组件
│ ├── main.js //入口文件
└── static //静态资源
项目目录是采用 vue-cli 自动生成的,如需添加依赖,"$ npm install 依赖名" 即可(如果仅用于开发阶段,则使用 $ npm install --save-dev )。
注:-save 和 save-dev 可以省掉你手动修改 package.json 文件的步骤。
npm install module-name -save 自动把模块和版本号添加到 dependencies 部分。
npm install module-name -save-dev 自动把模块和版本号添加到 devdependencies 部分。
现已将 vue-cli2 升级到 vue-cli3 ,大大加快了项目编译及打包速度, 但 src 下的目录结构与业务代码几乎没有变动。
二、开发实践
(一) 权限
- 权限需求定位到每个页面的每个按钮(主要是列表页): 添加、修改、查看、审核、作废、反审核、打印等,如无权限直接屏蔽该按钮。
- 当路由改变时,我们需要动态的监听该页面是否具有权限,可在 router.afterEach 钩子函数中设置( router/index.js )
- 将获取到的权限存放到本地存储里,相应在页面里获取本地存取的权限值然后控制页面上按钮的显示状态。
-
页面使用方法
/** 1. 在 created 或者 mounted 钩子函数初始化时获取 2. 在template模板中给按钮绑定权限 **/ data(){ return { authority: {}, } }, created() { this.authority = JSON.parse(localStorage.getItem("authority")); }
<!-- template --> <el-button icon="el-icon-circle-plus-outline" @click="salesAddF" v-if="authority.b_add == 1">新增</el-button> <el-button icon="el-icon-delete" @click="deleteF" v-if="authority.b_del == 1">删除</el-button>
// router/index.js import Vue from "vue"; import axios from "@/sever/https"; import Router from "vue-router"; Vue.use(Router); const router = new Router({ base: '/tomcat/', routes: [{ path: "/login", name: "登录页面", component: login, }, ...], ] }); router.beforeEach((to, from, next) => { var objArr = str.split("/", 3); if (to.matched.some(r => r.meta.requireAuth)) { if (store.state.system_app.token) { //登录状态当监测到路由变化,则去请求进入页面的权限接口 axios({ method: "get", url: "xxxRuleApiUrl", params: { code: objArr[2] } }).then(res => { if (res.data.code == 200) { if (res.data.data.is_role == 'Y') { //如果该页面设有权限则将权限详情先存于本地 window.localStorage.removeItem("authority") let authority = JSON.stringify(res.data.data.role_list); localStorage.authority = authority; next(); } else { // 未有页面访问权限 next({ path: "/isRole", query: { redirect: to.fullPath } }); } next(); } }); } else { next({ path: "/login", query: { redirect: to.fullPath } }); } } else { next(); } }); export default router;
(二) 各组件间传递数据
- 点击 vuex官网
- 项目中引入了vuex,使组件间传值更加方便,避免使用中间件 bus 等
- 如果是简单的子父传参,建议使用 $parent / $children / $emit / $attrs / $refs.name 来减少代码量,并更易于读写,避免污染全局(灵活运用)
- 项目中大量运用到 vuex 的主要是解决列表页、新增页、修改页、查看页的来回跳转
例,使用vuex存储变量,切换新增修改查看页面
-
页面调用方法
// 使用方法:调用 ACTION 的salesorderIndexACT方法,改变modulename为Index, // 切换组件到首页并更新视图 // salesorderAdd.vue import { mapActions, mapGetters } from "vuex"; methods:{ ...mapActions([ "salesorderAdd", "salesorderIndexACT" ]), goBack() { // 回到模块首页,使用 ACTION salesorderIndexACT this.salesorderIndexACT({ moduleName: "Index" }); } }
-
初始化页面
<!-- template 中默认绑定列表页 --> <template> <div> <component v-bind:is="commont"></component> </div> </template>
-
页面中间件
//组件中引入各模块 <script> import Index from "@/components/element/sales/salesordermanage/salesorder/salesorderIndex"; import salesorderAdd from "@/components/element/sales/salesordermanage/salesorder/salesorderAdd"; import salesorderSee from "@/components/element/sales/salesordermanage/salesorder/salesorderSee"; import { mapGetters } from "vuex"; export default { data() { return { commont: "Index" }; }, components: { Index, //初始化首页 salesorderAdd, //新增页 }, computed: { ...mapGetters([ "salesorderIndexGet", "salesorderAddGet", "salesorderSeeGet" ]) }, watch: { salesorderIndexGet() { this.commont = this.salesorderIndexGet.moduleName; }, salesorderAddGet() { this.commont = this.salesorderAddGet.moduleName; }, salesorderSeeGet() { this.commont = this.salesorderSeeGet.moduleName; } } }; </script>
-
定义状态的方法
//state.js export default { // 销售订单 salesorderIndex: {}, salesorderAdd: {}, salesorderSee: {}, ... }; /** * 最好不要直接使用,state主要用于存取变量,例如: ...mapState({ aa: state => state.system_app.user, }) **/ // ============= 分割线 ============= // //getter.js export default { // 销售订单 salesorderIndexGet(state) { return state.salesorderIndex; }, salesorderAddGet(state) { return state.salesorderAdd; }, salesorderSeeGet(state) { return state.salesorderSee; } }; /** * 用法:写到watch或者computed中,ShowCon类似于data中的一个对象,getShowCon是getters中的一个方法,例如: ...mapGetters({ ShowCon:"getShowCon" }) **/ // ============= 分割线 ============= // //mutations.js export default { // 销售订单 salesorderIndex(state, info) { state.salesorderIndex = info }, salesorderSee(state, info) { state.salesorderSee = info }, salesorderAdd(state, info) { state.salesorderAdd = info } } /** * 获取方法,clickF是本地调用的方法,@click="clickF(...arg)",salesorderIndex是写到mutations里边的方法,例如: ...mapMutations({ clickF:"salesorderIndex" }) **/ // ============= 分割线 ============= // //actions.js export default { salesorderIndexACT(context, data) { context.commit("salesorderIndex", data); }, salesorderSee(context, data) { context.commit("salesorderSee", data); }, salesorderAdd(context, data) { context.commit("salesorderAdd", data); } }; /** * 执行或者多个mutations的方法 **/
(三) 过滤器
- 项目中大量使用到了对数值等的格式化,如处理价格的千分位分隔符过滤器,对价格保留两位小数的过滤器,处理正负数的过滤器等。使用方法:
-
页面上使用
<template slot-scope="scope"> {{scope.row.price | priceFormat}} </template>
-
引入写好的过滤器模块,遍历所有过滤器并挂载到Vue实例上,组件里即可以直接使用
// main.js import filters from '@/filters/index.js' Object.keys(filters).forEach(key => { Vue.filter(key, filters[key]) });
-
自定义过滤器
//filter.js export default { //价格保留两位小数并使用千分位分隔符 priceFormat(s, n) { //可以小于0 if (!s && s !== 0) { return s } var numberS = Number(s) if (numberS) { n = n > 0 && n <= 20 ? n : 2; numberS = parseFloat((s + "").replace(/[^\d\.-]/g, "")).toFixed(n) + ""; var l = numberS.split(".")[0].split("").reverse(), r = numberS.split(".")[1]; var t = ""; for (var i = 0; i < l.length; i++) { t += l[i] + ((i + 1) % 3 == 0 && (i + 1) != l.length ? "," : ""); } if (s < 0 && Math.abs(s) < 1000) { return s } else { return t.split("").reverse().join("") + "." + r; } } else { return s } }, ... };
(四) 路由
-
官网:Vue Router
-
根据接口 system/menu/getLeftMenuList 命名文件
-
模块化路由文件,在 router/index.js 中导入各模块路由的路径
-
避免首次加载页面的时候加载整个模块代码(导致几秒白屏),加入了路由懒加载
//router/index.js { path: "xx", name: "xx", component: resolve => require([ "@/components/element/xx/xx/xx.vue" ], resolve),//路由懒加载 meta: { _meauname: '自定义标题' } }
(五) axios
-
可加入默认自定义参数
// sever/https.js axios.defaults.timeout = 10000; //可等待时长10s axios.defaults.baseURL = "http://60.205.214.105"; //接口baseURL axios.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8";//请求默认头信息
-
可在axios拦截中自定义功能。
-
在请求拦截 axios.interceptors.request.use 中加入 loading 效果、 post 和 get 请求对参数进行格式化。
-
在响应拦截 axios.interceptors.response.use 里如果接口报错,根据状态码给出相应提示语和项目公用方法规范
import axios from "axios"; import { Message, Loading } from "element-ui"; // 请求拦截器 axios.interceptors.request.use( config => { loading = Loading.service({ fullscreen: true, lock: true, text: '正在加载,请稍等……' }); if (config.method == "post") { removePending(config); //在一个ajax发送前执行一下取消操作 config.cancelToken = new cancelToken((c) => { // 这里的ajax标识我是用请求地址&请求方式拼接的字符串,当然你可以选择其他的一些方式 pending.push({ u: config.url + '&' + config.method, f: c }); }); let postData = config.data; config.data.sign = serverMoudle(postData); } else if (config.method == "get") { let getData = config.params; config.params = { sign: serverMoudle(getData), ...config.params }; } if (store.state.system_app.token) { config.headers.Token = `${store.state.system_app.token}`; } // 每次请求添加sign return config; }, err => { if (loading) { loading.close(); } Message({ showClose: true, message: err.msg, type: 'error' }) return Promise.reject(err); } ); // 响应拦截器 axios.interceptors.response.use( response => { if (loading) { loading.close(); } const data = response.data; if (response.config.method == 'post') { removePending(response.config); } if (data.code == 401) { store.dispatch('LOGOUT') router.replace({ path: "/login" }); } else if (data.code == 400) { Message.error(data.msg) } else if (data.code == 503) { Message.error(data.msg) store.dispatch('LOGOUT') router.replace({ path: "/login" }); } return response; }, error => { Message({ message: "网络错误", type: "error", duration: 5 * 1000 }); return Promise.reject(error); } ); export default axios;
(六) 请求列表数据(带分页)
将查询、重置、切换页码、切换列表长度四个功能封装成为一个公用的方法。这四者有着密切的关系,又有各自的不同点(默认:列表30项,显示第一页),它们的传递值分别为:
功能 | params(搜索参数) | page(页码) | per_page(分页长度) |
---|---|---|---|
查询 | 已填搜索项 | 1 | 已选的分页长度 |
重置 | 搜索项置空(根据需求) | 1 | 30 |
切换页码 | 已填搜索项 | 已选的页码 | 已选的分页长度 |
切换分页长度 | 已填搜索项 | 已选的页码 | 已选的分页长度 |
-
使用方法
import { getData } from "@/utils/public_function/commonUtils";//初始化getData方法参数 this.$set(this, "params", { defaultParams: "xx" }); this.$set(this, "searchParams", { params: this.params, //默认参数 TableData: this.TableData, //列表数据 API: "getList", //列表接口名 vm: this //Vue }); getData(this.searchParams); //获取列表 //在实现相应功能时,可以直接使用组合方法改变this.searParams.params的值,即可。 onSearch(){ Object.assign(this.searchParams.params, { per_page: val,//分页长度 page: 1//第一页 }); getData(this.searchParams); //获取列表 }
-
getData 方法
//commonUtils.js export function getData(arg, type) { /* * @Date: 2018-07-06 10:23:10 * 初始化 搜索,重置,切换页码,切换页码长度 都需要调取该方法重新初始化列表 * arg格式: * { * params:{},//请求参数 TableData: this.TableData, //列表数据 API: "getxxx", //接口名 vm: this //传递的this } */ if (arg["params"]) { arg["vm"]["goPage"] = "page" in arg["params"] ? arg["params"]["page"] : 1; //改变当前页 arg["vm"]["pageSize"] = "per_page" in arg["params"] ? arg["params"]["per_page"] : 30; //改变当前页size } arg.vm.loading = true; axios({ method: "get", url: api[arg.API], params: arg.params }).then(res => { if ([200, 304].includes(res.data.code)) { arg.TableData.tableData = res.data.data.data; arg.TableData.total = Number(res.data.data.total); arg.TableData.allList = res.data.data.desc; } arg.vm.loading = false; }); }
(七) 组织部门业务员三级联动
需求:
- 选择上一级需要清除下级绑定的数据和列表
- 页面默认赋值当前登陆者的信息
//使用方法
import {
getCompanyListCommon,
getDepartmentListCommon,
getEmployeeListCommon
} from "@/utils/public_function/commonUtils"; //引入 获取组织、部门、业务员 方法
getCompanyListCommon(this); //即可获取组织 其他同理。其中三者的联动可在各自的回调完成之后再调取下一级数据
(八) 优化性能,手动绑定下拉框数据
-
使用方法
import { bindIdsF } from "@/utils/public_function/commonUtils"; //在详情接口回调中,绑定 this.$axios({ method: "get", url: xxx, params: { order_id: xxx } }).then(({ data }) => { if (data.code == 200) { /** * data.data:下拉绑定的字段可能和详情接口返回的字段不一样,需要在这里用assign做下处理,多数情况下不需要 * assign方法如下: */ Object.assign(data.data, { employee_id: data.data.business_id, employee_name: data.data.business_name }); // 列表下有 options 则传入,没有可不传入 bindIdsF(this, data.data, [ ["customer_id", "customer_name", "customerList", "options"], ["department_id", "department_name", "departmentList", "options"], ["employee_id", "employee_name", "employeeList", "options"] ]); } });
-
绑定下拉框数据方法
// commonUtils.js // 初始化绑定返回的id 下拉 export function bindIdsF(self, oParent, arrList) { if (Array.isArray(arrList)) { for (const item of arrList) { let arr = []; if (item[3]) { arr = [{ [item[0]]: Number(oParent[item[0]]), [item[1]]: oParent[item[1]], }]; self.$set(self[item[2]], item[3], arr) } else { arr = [{ [item[0]]: Number(oParent[item[0]]), [item[1]]: oParent[item[1]], }]; self.$set(self, item[2], arr) } } } }
注意:遇到特殊情况(如选客户等带出其他下拉字段(业务员等)),需在相应接口回调里继续使用 bindIdsF 去初始化下拉数据
(九) 验证
验证模块(utils/rules.js)中定义了组件上所有用到的验证,然后将rules 挂载的Vue原型上即可。
-
使用方法
/** * 组件中直接调用rules下的对应方法即可 * 如果仅加星号,不做限制,只用prop属性即可 **/ <el-form :model="param" ref="paramsRef"> <el-form-item label="库存组织" prop="address" :rules="rules.address"> <el-input v-model="params.address"></el-input> </el-form-item> </el-form>
-
将验证方法挂载到vue实例上
// main.js import rules from './utils/rules' //全局定义表单验证规则 Vue.prototype.rules = rules;
-
rules.js 页面定义方法,常用的有:
// 自定义方法,作为 validator 的属性值 // 第一位不能为0,保留两位小数 const checkNumPot2 = (rule, value, callback) => { const reg = /(^[0-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/; if (!value && value !== 0) { return callback(new Error("请填写数字")); } else if (!reg.test(value)) { return callback(new Error("请填写数字,最多2位小数")); } else { callback(); } }; // 验证身份证 const checkIdNum = (rule, value, callback) => { const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/; if (!reg.test(value)) { return callback(new Error("证件号码不正确")); } else { callback(); } }; // 验证整数 const checkInterNum = (rule, value, callback) => { const reg = /^[0-9]*[1-9][0-9]*$/; if (!value) { return callback(new Error("请填写整数")); } else if (!reg.test(value)) { return callback(new Error("请输入整数")); } else { callback(); } }; //验证座机 const telephone = (rule, value, callback) => { const reg = /^([0-9]{3,4}-)?[0-9]{7,8}$/; if (!reg.test(value) && value != undefined && value != "") { return callback(new Error('请正确输入固定电话,格式为"区号-座机号码"')); } else { callback(); } }; //输入不可为空 const empty = (rule, value, callback) => { if (value == "") { return callback(new Error("输入不能为空")); } else { callback(); } }; //邮箱验证 const emailNew = (rule, value, callback) => { const reg = /^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,5}$/; if (!reg.test(value)) { return callback(new Error('4~18个字符,可使用字母、数字、下划线,需以字母开头@xxx.com/cn')); } else { callback(); } }; /** * requited: 是否可为空 * pattern:自定义正则 * validator:验证方法 * message:验证不通过消息 * trigger:触发方式 * * pattern 和 validator 写一个即可 **/ export default { validatePhone: [{ required: true, pattern:: /^1[0123456789]\d{9}$/, message: "目前只支持中国大陆的手机号码", trigger: "blur" }], validateEmpty: [{ required: true, validator: empty, trigger: "blur" }], //不可输入为空 numPot2: [{ required: true, validator: checkNumPot2, trigger: "blur" }], // 第一位不能为0,保留两位小数 interNum: [{ required: true, validator: checkInterNum, trigger: "blur" }], // 整数 telephone: [{ required: false, validator: telephone, trigger: "blur" }], // 验证座机 emailNew: [{ required: false, validator:emailNew, message: "请输入正确的邮箱格式:xxx@xxx.com/cn", trigger: "blur" }], company_id: [{ required: true, message: "请选择组织", trigger: "change" }], ... };
(十) 打印
自定义打印模板后,以组件的形式引入并调用其打印方法即可。
打印官网
-
使用方法
// template <el-button type="text" @click="printF(scope, scope.row)"> 打印拣货单 </el-button> <!-- 打印组件 --> <div> <printPage ref="printRef"></printPage> </div> <script> import printPage from "@/components/public/pickingList.vue"; //打印组件 // 触发打印组件 methods:{ printF(scope, row) { this.$nextTick(function() { this.$refs.printRef.printPdf(row.outstock_id); }); }, } </script>
-
打印内容页面(自定义的格式)
// 引用打印插件:@/utils/LodopFuncs.js // pickingList.vue <template> <div v-show="false"> <div v-if="params!=null"> <div id="print-header"> <table class="header" cellpadding="0" cellspacing="0" style="width: 100%;"> <tr> <td> 表头 </td> </tr> </table> </div> <div id="print-body"> <table class="body" cellpadding="0" cellspacing="0" style="width: 100%;"> <tr v-for="item in tableData" :key="item.id"> <td>表体</td> </tr> </table> </div> <div id="print-footer"> <table class="footer" cellpadding="0" cellspacing="0" style="width: 100%;"> <tr> <td> 页脚 </td> </tr> </table> </div> <div id="print-pages"> <p style='text-align:center;font-size:9pt;'> 页码: <span tdata='pageNO'>第 ## 页</span> / <span tdata='pageCount'>共 ## 页</span> </p> </div> </div> </div> </template> <script> import { getLodop } from "@/utils/LodopFuncs"; let LODOP = getLodop; export default { data() { return { params: null, tableData: null }; }, methods: { list(val) { let vm = this; this.$axios({ method: "get", url: this.$api.printPick, params: { outstock_id: val } }).then(res => { if (res.data.code == 200) { vm.params = res.data.data; vm.tableData = res.data.data.goods_info; setTimeout(function() { vm.CreatePrintPages(); }, 0); } }); }, printPdf(val) { this.list(val); }, CreatePrintPages() { var LODOP = getLodop(); if (!LODOP) { return false; } var strStyle = LODOP.strStyle; LODOP.PRINT_INIT(""); LODOP.SET_PRINT_PAGESIZE(1, "205mm", "93.3mm", "CreateCustomPage"); // 0 操作者自行决定或打印机缺省设置 1 纵向打印,固定纸张;2 横向打印,固定纸张 LODOP.SET_PREVIEW_WINDOW(1, 0, 0, 1000, 600, ""); // 初始预览窗口大小 LODOP.SET_SHOW_MODE("LANDSCAPE_DEFROTATED", 1); // 横向打印时正向显示 LODOP.SET_SHOW_MODE("HIDE_PAPER_BOARD", 1); // 去除背景滚动线 LODOP.SET_PRINT_MODE("AUTO_CLOSE_PREWINDOW", 1); // 打印后自动关闭预览 LODOP.SET_PRINT_MODE("CUSTOM_TASK_NAME", "拣货单"); // 打印队列中的文档名 // 追加打印头部 LODOP.ADD_PRINT_TABLE( "2mm", "10mm", "185mm", "30mm", strStyle + document.getElementById("print-header").innerHTML ); LODOP.SET_PRINT_STYLEA(0, "ItemType", 1); // 页眉页脚项 // 追加打印主体:分页、循环表格 LODOP.ADD_PRINT_TABLE( "28mm", "10mm", "185mm", "20mm", strStyle + document.getElementById("print-body").innerHTML ); // 追加打印底部 LODOP.ADD_PRINT_TABLE( "63mm", "10mm", "185mm", "30mm", strStyle + document.getElementById("print-footer").innerHTML ); LODOP.SET_PRINT_STYLEA(0, "ItemType", 1); // 页眉页脚项 // 追加页码 LODOP.ADD_PRINT_HTM( "80mm", "10mm", "185mm", "5mm", document.getElementById("print-pages").innerHTML ); LODOP.SET_PRINT_STYLEA(0, "ItemType", 1); LODOP.PREVIEW(); } } }; </script>
(十一) 签名
签名算法前后台要保持一致
-
使用
//签名使用方法 在 axios 请求拦截中,给发送的数据用签名算法格式化 config.data.sign = serverMoudle(config.data) //post请求 config.params.sign = serverMoudle(config.params) //get请求
-
签名算法
import md5 from 'js-md5'; import Qs from "qs"; const unchangeable = 'xxxxxxx'; //根据该字符串加密 import { objKeySort } from '@/utils/public_function/commonUtils.js'; //将接口参数排序并将null或undefined的数据设为空 export function objKeySort(obj) { var newkey = Object.keys(obj).sort(); var newObj = {}; for (var i = 0; i < newkey.length; i++) { //过滤参数中null或者undefined的值,并使之默认为空,否则报错 if ([null, undefined].includes(obj[newkey[i]])) obj[newkey[i]] = ''; newObj[newkey[i]] = obj[newkey[i]]; } return newObj; } export function serverMoudle(params) { let newUrl = '' if (params == undefined) { newUrl = "key=" + unchangeable; return md5(newUrl) } else { if (Qs.stringify(objKeySort(params)) != '') { newUrl = decodeURIComponent(Qs.stringify(objKeySort(params))) + "&key=" + unchangeable; } else { newUrl = "key=" + unchangeable; } return md5(newUrl) } }
三、代码规范
(一) JS规范
1.直接只用对象、数组字面量初始化变量,不用new它的实例(官方推荐,简单明了)
let arr = [] , obj = {};
2.尽量用全等判断是否相等(===);
null === false; //false
3.用变量本身的布尔类型去做判断,必须转布尔值时可以使用双感叹号转布尔类型。
!!null === false //true
4.用其他代码优雅替换if else语句(三目运算符、switch、逻辑判断等)
5.写有意义的注释,了解一些特殊标记,e.g.
TODO: 有功能待实现。
FIXME: 该处代码运行没问题,但可能由于时间赶或者其他原因,需要修正。
HACK: 为修正某些问题而写的不太好或者使用了某些诡异手段的代码。
XXX: 该处存在陷阱。
6.避免代码冗余,能循环则不去单个操作变量
(二) Vue规范
1. 过滤器:
- v-for 要配合key使用,标注唯一性,这将会让 Vue 知道如何使行为更容易预测。
② 避免 v-if 和 v-for 同时用在同一个元素上。
2. 父子组件相互传递数据:
- 在父组件传子组件数据上,prop 起着重要的作用,其中prop的定义应该尽量详细,至少需要指定其类型,同时可指定该数据的默认值(避免控制台报错),注意,在声明 prop 的时候,其命名应该始终使用 camelCase,而在模板和 JSX 中应该始终使用 kebab-case
- 组件间传递数据要灵活使用,如使用vue自带的实例属性( $parent/$children/$emit/$attrs/$refs.name)
- 如果涉及不相关组件间的传值,可使用状态管理 vuex官网 点击
3. 样式:
- 组件里的 <style> 要避免污染全局,请设置属性 scope
4. data
- 每个组件对应的 data ,要用 function 来 return 出去,这样每个 data 的属性就不是挂载到 vue 的原型上,而是有它自己的上下文,当 data 的值是一个对象时,它会在这个组件的所有实例之间共享
5. $set
- 因为 Vue 无法探测普通的新增属性,所以向响应式对象添加属性需用$set赋值
6. JSX语法
如需渲染较为复杂的 DOM ,可使用Jsx语法简化代码 GITHUB 点击
7. 尽量使用Vue自带API
- 使用 vue 自带的 API 替代 js 原生方法 如 $remove Vue API 点击
8. 性能优化:
- 组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法( computed )(如表格中的数据计算或格式化等)
(三) 命名规范
-
小驼峰式命名(格式:myVariale)
-
组件名规范:
新增页面:xxxAdd.vue / 修改页面:xxxModify.vue / 主页面:xxxIndex.vue / 查看页面:xxxSee.vue -
方法名规范:
查询:onSearch / 重置:onReset / 修改某个功能:changeXxx / 新增:addF / 修改:modifyF / 查看:seeF -
变量名规范:
列表数据:xxxList / 表格数据:TableData.tableData / 表头参数:params / 默认值:defaultXxx -
状态管理变量名规范:
states:xxxIndex
mutations:xxxIndex
getters:xxxIndexGet
actions:xxxIndexACT
-
-
良好的命名也能导致维护性更强,可读性更好,同时更大程度避免变量重复导致的bug
(四)文本编辑器规范
-
缩进:两个空格
-
去除eslint语法检测,虽然能使代码更规范,但是及其影响工作效率,不推荐。
-
建议使用编辑器vscode或sublime Text,学会利用编辑器的有用插件。
插件推荐:
① 提高效率:HTML Snippets(html snippets)、Javascript (ES6) Code Snippets(ES6 snippets)、Auto Close Tag(自动补全)、Emmet(css snippets)、Vue2 Snippets
② 功能增强:Color Highlight(识别代码的颜色)、Bracket Pair
Colorizer(识别代码中的括号,标记不同的颜色)③ 设置编辑器自带的自动保存、自动格式化代码功能
④ 等等
- 运用好各编辑器的快捷键,工作效率会事半功倍
- 学会使用控制台调试代码
四、优化内容
该项目还在不断的完善当中,避免不了存在着一些不足,以下是目前规划的开发计划。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2017-10-13 数组的一些方法