一、項目目錄(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(識別代碼中的括號,標記不同的顏色)③ 設置編輯器自帶的自動保存、自動格式化代碼功能
④ 等等
- 運用好各編輯器的快捷鍵,工作效率會事半功倍
- 學會使用控制台調試代碼
四、優化內容
該項目還在不斷的完善當中,避免不了存在着一些不足,以下是目前規划的開發計划。
- [x] 緩存功能
- [ ] 完善項目細節
- [x] 優化性能,減少接口調用次數
- [x] 升級Vue3.0,極大提升編譯與打包速度
