我是作後端的,看到前端代碼組織很難受。javascript
項目業務一多很難維護。更別談其餘項目複用。固然前端開發人員的水平良莠不齊。另外不少項目都有後臺管理相似的功能css
功能差很少天然想到複用和封裝。html
就算是用了vue,react,dva什麼的,感受該亂仍是亂,這些框架什麼的,最多隻是解決代碼級別組件的組織歸類,談到根據業務模塊來分,該亂的仍是亂。前端
公司項目比較low,通常都是後臺管理系統,有不少的table,上面有查詢條件,每一個表都是增刪改查各類單記錄操做。因此類似的功能不少。
最快速的方案是寫完一個模塊,複製粘貼修改修改,就成了一個新的模塊。這樣作最大的弊端是就後期維護成本很是高,基本很難管理,要統一改個東西就各個模塊的代碼都處理一遍。而後項目人員一多編碼風格都沒法管理,後面就無zf狀態。
雖然有時候會把一些公用的對像或方法放在公用的js文件中,但這遠遠不夠,還有有大量重複的代碼。
$extends只能解決對象或者方法級別的東西,碰到須要幾個步驟的東西就嗝屁了。vue
複用
和個性化
的問題,不僅僅是使用公用屬性 或 $extends,而是使用對象繼承與重寫,理論上能夠重寫父類全部的方法。以表格爲例。注意代碼只是截取一些關鍵部分的代碼,只是爲了說明邏輯和細節,有些可能運行不通。java
define(['class-util', 'usage', 'vue', 'ELEMENT'], function (util, usage, Vue, ELEMENT) { Vue.use(ELEMENT); function BaseTable(bean) { var self = this; //功能對象 this.inited = false; this.defers = {}; //子類常量,通常由子類覆蓋 this.elm = '#t_datatable';//表格的選擇器 this.$elm = null;//表格的jq對象,通常須要加載頁面後再賦值 this.$table = null;//表格的jq對象,通常須要加載頁面後再賦值 this.url = {//相關的操做的接口 search: '',//分頁查詢 delete: '', }; this.form;//提交的數據 //操做不一樣的數據記錄,每次都會變的屬性放在opt裏。 this.opt = { //類變量 $table: null, $dialog: null, $form: null, vm: null,//主要的vue對象 //bean變量 id: null, bean: null, action: null//操做:add,modify,detail }; //屬性 this.page = { detail: null, edit: null }; this.js = { edit: null, detail: null, upload: 'base-upload', audit: 'base-audit', collect: 'base-collect', downloads: 'base-downloads', }; //相關操做顯示的窗口標題 this.titles = { 'delete': '您肯定刪除該項記錄嗎?', 'report': '您肯定上報該項記錄嗎?', 'detail': '詳細信息', 'modify': '修改', 'add': '新增', 'audit': '審覈', }; //提示信息 this.msg = { report: '上報成功!', delete: '刪除成功!', }; //表格操做列按鈕的代碼,由於經常使用就放在基類裏,個性化的狀況子類能夠覆蓋 this.btn = { search: '#b_search', add: '#b_add', collect: '#b_collect', detail: function (id) { return '<a data-id="' + id + '" data-action="detail" class="blue action" title="查看" href="#">\<i class="icon-zoom-in bigger-130" data-row="" data-path="" data-index=""></i>\</a>'; }, delete: function (id) { return '<a data-id="' + id + '" data-action="delete" class="red action" title="刪除" href="#">\<i class="icon-trash bigger-130" data-row="" data-path="" data-index=""></i>\</a>'; }, modify: function (id) { return '<a data-id="' + id + '" data-action="modify" class="green action" title="修改" href="#">\<i class="icon-pencil bigger-130" data-row="" data-path="" data-index=""></i>\</a>'; }, }; //Bootstrap Table(或者其餘)的統一配置,個性化狀況子類覆蓋須要修改的屬性便可 this.tableOpt = { toolbarAlign: 'false', searchAlign: 'right', buttonsAlign: 'right', sidePagination: 'server',//指定服務器端分頁 url: this.baseUrl + this.url.search, method: 'POST', contentType: "application/x-www-form-urlencoded; charset=UTF-8", pagination: true,//是否分頁 pageNumber: 1, //初始化加載第一頁,默認第一頁 pageSize: 10,//單頁記錄數 // pageList:[5,10,20,30],//分頁步進值 queryParams: function (params) { var params2 = self.queryParams.call(self, params); var defParam = { 'access_token': $.cookie('token'), 'limit': params.limit, // 每頁要顯示的數據條數 'start': params.offset, // 每頁顯示數據的開始行號 columns: params.sort, isDesc: params.order === 'desc' ? true : false }; return $.extend(defParam, params2); }, responseHandler: function (res) { var respData = { total: 0, rows: [] }; if (res && res.content) { var content = res.content; var total = content.recordsTotal; var rows = content.data; if (total && $.isNumeric(total)) { respData.total = total; } if (rows && $.isArray(rows)) { respData.rows = rows; } } return respData; }, clickToSelect: true,//是否啓用點擊選中行 striped: true, //是否顯示行間隔色 sortable: true, sortOrder: 'desc', columns: [{title: '序號', field: 'p_id'},],//columns通常都是子類覆蓋的 }; //彙總表默認選項 }; /**********************原型方法*************************/ //原型方法主要有幾個 //1. init、 //2. 渲染操做欄,以下拉選擇框、日曆選擇框,用vue等就更不用說了、 //3. 渲染操做欄按鈕,綁定事件什麼的 //4. 初始化表格 //初始化,主要的執行入口,傳入參數的入口 BaseTable.prototype.init = function (elm, bean, option) { this.$elm = $(elm || this.elm); if (bean) { this.bean = bean; } if (option) { this.opt = option; } this.defers.initToolbar = this.initToolbar(); this.defers.initToolbar = this.initToolbarBtn(); this.defers.initToolbar = this.initTable(this.$elm, this.tableOpt); this.$table = this.initTable(this.$elm, this.tableOpt); }; /** * 初始化操做欄 * 通常子類覆蓋 */ BaseTable.prototype.initToolbar = function () { }; /** * 初始化操做欄按鈕 * 子類基本不用重寫 */ BaseTable.prototype.initToolbarBtn = function () { var self = this; $.when(this.defers).done(function () { //按鈕 if (self.btn.search) $(self.btn.search).on("click", null, self, self.search); if (self.btn.add) $(self.btn.add).on("click", null, self, self.edit); if (self.btn.template) $(self.btn.template).on("click", null, self, self.downloadTemplate); if (self.btn.upload) $(self.btn.upload).on("click", null, self, self.upload); if (self.btn.collect) $(self.btn.collect).on("click", null, self, self.collect); if (self.btn.downloads) $(self.btn.downloads).on("click", null, self, self.downloads); }); }; /** * 初始化表格, * 子類基本不用重寫 */ BaseTable.prototype.initTable = function (elm, tableOpt) { var self = this; var $elm = $(elm); //初始化datatable tableOpt.$el = $elm;//這樣就能夠在bootstrap.table實例中訪問當前的jq table 對象,方便調用bootstrap.table的方法 var $myTable = $elm.bootstrapTable(tableOpt); //綁定操做列按鈕事件 $elm.off('click').on('click', 'tbody .action', $myTable, function (e) { var $btn = $(this); var action = $btn.data('action'); var id = $btn.data('id'); var bean = $elm.bootstrapTable('getRowByUniqueId', id); var actionFun = self.actions[action] || self.confirmAction; if ($.isFunction(actionFun)) { var defer = actionFun(e, id, bean, self, action); //操做完成後執行 if (self.callbacks) { var actionCallback = self.callbacks[action] if (actionCallback) { $.when(defer).done(function (data) { actionCallback.apply(self, arguments); }) } } } }); return $myTable; }; //其餘只個重要方法 /** * 查詢條件方法 * 通常子類覆蓋 * @param d:默認datatable參數 * @returns {*}:提交的data集合 */ BaseTable.prototype.queryParams = function (params) { return {}; }; /** * 刪除操做,實現省略,通常都同樣,子類不用重寫,只要定義好 url.delete給他調就好 */ BaseTable.prototype.delete = function (e, id, bean, self) { }; /** * 顯示詳情操做 */ BaseTable.prototype.showDetail = function (e, id, bean, self, action) { var title = self.titles[action]; var dialogDefer = usage.tableDialog(title, self.page.detail, action); $.when(dialogDefer).then(function (dialog) { var modulePath = self.js[action]; require([modulePath], function (Detail) { self.Detail = Detail; self.detail = new self.Detail(bean, dialog, self);//新建實例 self.detail.init(); }) }); }; /** * 新增/修改操做(設置form爲disable後顯示詳情) */ BaseTable.prototype.edit = function (e, id, bean, self, action) { if (!self) { self = e.data; } action = action || 'add'; var modulePath = self.js.edit; require([modulePath], function (Edit) { self.Edit = Edit; self.edit = util.getInstance(Edit.context, Edit) self.edit.init({ $table: self, action: action, bean: bean, id: id, }); self.edit.run(); }); }; return BaseTable; });
define(['base-table', 'class-util', 'usage', 'vue', 'ELEMENT', 'component'], function (BaseTable, util, usage, Vue, ELEMENT) { Vue.config.devtools = true; Vue.use(ELEMENT); var _context = {}; function UserTable(bean) { BaseTable.call(this, bean);//繼承父類屬性 var self = this; this.elm = '#table';//override this.url.search = 'api/user/search'; this.url.delete = 'api/user/delete'; this.js.edit = 'assets/module/user/user-edit'; this.tableOpt.url = this.url.search; this.tableOpt.sortName = 'id', this.tableOpt.sortOrder = 'desc'; this.tableOpt.columns = [ {title: '序號', field: 'id'}, {title: '名稱', field: 'username'}, {title: '所屬單位', field: 'depart_name'} {title: '角色', field: 'roleNames',}, { title: '狀態', field: 'user_enable', formatter: function (value, row, index) { return userStatusMap[value]; } }, {title: '備註', field: 'user_remark',}, { title: '操做', field: this.idField, formatter: function () { return self.renderActionBtn.apply(self, arguments); } }, ]; }; util.beget2(UserTable, BaseTable);//繼承父類方法 //原型方法 UserTable.prototype.initToolbar = function () { if (!this.opt.vm) { var vm = this.opt.vm = new Vue({ el: '#toolbar', data: {}, mounted: function () { this.defers.toolbar.resolve(this); }, }); } return this.defers.toolbar.promise(); }; UserTable.prototype.queryParams = function (params) { //中間的內容本身處理 return params; }; return UserTable; });
其中class-util兩個方法react
define([], function () { /** * 生孩子函數 beget:龍beget龍,鳳beget鳳。 * 用於繼承中剝離原型中的父類屬性 * @param obj * @returns {F} */ function beget(obj) { var F = function () { }; F.prototype = obj; return new F(); } function beget2(Sub, Sup) { var F = function () { }; F.prototype = Sup.prototype; var proto = new F(); proto.constructor = Sub;//繼承代碼 for (var key in Sub.prototype) {//若是在子類聲明瞭prototype方法以後才調用此繼承方法,複製子類方法以覆蓋父類方法 proto[key] = Sub.prototype[key]; } Sub.prototype = proto;//繼承代碼 return Sub; } /** * 獲取單例對象 * @param context 存放對象的上下文,用於檢測是否已實例化,返回已實例化對象;存放其餘對象如模態框的jq對象 * @param clazz 須要new的類 * @returns {*} */ function getInstance(context, clazz) { if (!context.inst) { context.inst = new clazz(); //context.inst.setDialog(context.$dialog); } return context.inst; }; return { beget: beget, beget2: beget2, getInstance: getInstance, }; });