摘要:先簡單說明一下這個引入的的方式是標簽引入的沒有用到之類的構(gòu)建工具畢竟公司還在用這也是我第一次寫文章大家看看思路就行了要是有大佬指點指點就更好了話不多說先來個效果圖我們再看下極為簡單的目錄結(jié)構(gòu)實現(xiàn)的可編輯表格首頁首頁相關(guān)與業(yè)務(wù)無關(guān)的純工具函數(shù)
先簡單說明一下,這個Demo引入的vue,iview的方式是標簽引入的,沒有用到webpack之類的構(gòu)建工具...
畢竟公司還在用angularjs+jq.
這也是我第一次寫文章,大家看看思路就行了,要是有大佬指點指點就更好了
我們再看下極為簡單的目錄結(jié)構(gòu)
IViewEditTable ## vue+iview 實現(xiàn)的可編輯表格 └── index.html ## 首頁 └── js └── editTable.js ## 首頁JS └── ivew ## iview相關(guān) └── vue ├── axios.min.js ## axios (ajax) ├── util.js ## 與業(yè)務(wù)無關(guān)的純工具函數(shù)包 └── vue.min.js ## vue (2.x)
首頁html:
可編輯表格
首頁沒什么說的,都是基本的架子. 這是需要渲染的數(shù)據(jù)及其說明:
{ "Status": 1, "Total": 233, "Items": [{ "ID": 1, "PID": 3, "PRJCODE": "2018-001", //項目編號 不可編輯 "PRJNAME": "淡化海水配套泵站", //項目名稱 文本輸入框 "PRJTYPE": "基礎(chǔ)設(shè)施", //項目類型 下拉選項 "JSUNIT": "投資公司", //建設(shè)單位 文本輸入框 "FLOW_TYPE_CODE":"A02", //流程分類 下拉選項,與數(shù)據(jù)庫以code形式交互 "DATE_START": "2018-12-1", //開工時間 日期選擇 "DATE_END": "2019-12-1", //竣工時間 日期選擇 "CONTENT": "建設(shè)淡化海水配套泵站一座,占地面積約8500平方米", //建設(shè)內(nèi)容 多行輸入框 "INVEST_ALL": "1000" //總投資 數(shù)字輸入框 }] }
還有editTable.js的基本架子,$http是我為了方便在utils最后一行加入的 (angularjs用多了,習慣用$http)
Vue.prototype.utils = utils window.$http = axios
editTable.js :
var vm = new Vue({ el: "#editTableCtrl", data: function() { return { loading: true, //表格的數(shù)據(jù)源 dataList: [], // 列 columnsList: [], // 增加編輯狀態(tài), 保存狀態(tài), 用于操作數(shù)據(jù) 避免干擾原數(shù)據(jù)渲染 cloneDataList: [] } }, methods: { getData: function() { var self = this; self.loading = true; $http.get("json/editTable.txt").then(function(res) { self.dataList = res.data.Items; self.loading = false; }); }, }, created: function() { this.getData(); } });
我們再來按照iview的規(guī)則編寫渲染的列:
//... /** * @name columnsList (瀏覽器 渲染的列) * @author catkin * @see https://www.iviewui.com/components/table * @param * { * titleHtml : 渲染帶有html的表頭 列: "資金來源" * editable : true,可編輯的列 必須有字段 * option : 渲染的下拉框列表,如果需要與數(shù)據(jù)庫交互的值與顯示的值不同,須使用[{value:"value",label:"label"}]的形式,下面有例子 * date : 渲染成data類型 ,可選參數(shù): * date | daterange: yyyy-MM-dd (默認) * datetime | datetimerange: yyyy-MM-dd HH:mm:ss * year: yyyy * month: yyyy-MM * input : 渲染input類型 ,可選參數(shù)為html5所有類型 (額外增加 textarea 屬性), 默認text * handle : 數(shù)組類型, 渲染操作方式,目前只支持 "edit", "delete" * } * @version 0.0.1 */ columnsList: [{ width: 80, type: "index", title: "序號", align: "center" }, { align: "center", title: "項目編號", key: "PRJCODE" }, { align: "center", title: "項目名稱", titleHtml: "項目名稱 ", key: "PRJNAME", editable: true }, { align: "center", title: "項目分類", titleHtml: "項目分類 ", key: "PRJTYPE", option: ["產(chǎn)業(yè)項目", "基礎(chǔ)設(shè)施", "民生項目", "住宅項目"], editable: true }, { align: "center", title: "建設(shè)單位", titleHtml: "建設(shè)單位 ", key: "JSUNIT", editable: true }, { align: "center", title: "流程分類", titleHtml: "流程分類 ", key: "FLOW_TYPE_CODE", option: [{ value: "A01", label: "建筑-出讓" }, { value: "A02", label: "建筑-劃撥" }, { value: "B01", label: "市政-綠化" }, { value: "B02", label: "市政-管線" }], editable: true }, { align: "center", title: "開工時間", titleHtml: "開工時間 ", key: "DATE_START", //這里在后面處理的時候會分割成["month","yyyy-MM"]的數(shù)組,分別代表iview的DatePicker組件選擇日期的格式與數(shù)據(jù)庫傳過來時頁面顯示的格式 date: "month_yyyy-MM", editable: true }, { align: "center", title: "竣工時間", titleHtml: "竣工時間 ", key: "DATE_END", date: "month_yyyy-MM", editable: true }, { align: "center", title: "建設(shè)內(nèi)容", titleHtml: "建設(shè)內(nèi)容 ", key: "CONTENT", input: "textarea", editable: true }, { align: "center", title: "總投資(萬元)", titleHtml: "總投資
(萬元) ", key: "INVEST_ALL", input: "number", editable: true }, { title: "操作", align: "center", width: 150, key: "handle", handle: ["edit", "delete"] }] //...
此時頁面應(yīng)該已經(jīng)可以渲染出表格了
既然要編輯數(shù)據(jù),并且我的需求是整行整行的編輯,而編輯的同時就會同步更新數(shù)據(jù),那么問題來了
vue中, 數(shù)據(jù)更新,視圖會隨之更新. 想象一下,我在輸入框中屬于一個值,觸發(fā)了數(shù)據(jù)更新,接著又觸發(fā)了視圖更新,那么我每次輸入時,這個input都會失焦,毫無用戶體驗. 所以我們把可編輯的動態(tài)內(nèi)容用cloneDataList渲染,靜態(tài)顯示的用dataList渲染
//... self.dataList = res.data.Items; // 簡單的深拷貝,雖然map會返回新數(shù)組,但是數(shù)組元素也是引用類型,不能直接改,所以先深拷貝一份 self.cloneDataList = JSON.parse(JSON.stringify(self.dataList)).map(function(item) { // 給每行添加一個編輯狀態(tài) 與 保存狀態(tài), 默認都是false item.editting = false; item.saving = false; return item; }); //...
接下來,我們要根據(jù)columnsList做一次循環(huán)判斷,根據(jù)相應(yīng)的key寫出不同的render函數(shù)
//全局添加 //根據(jù)value值找出數(shù)組中的對象元素 function findObjectInOption(value) { return function(item) { return item.value === value; } } //動態(tài)添加編輯按鈕 var editButton = function(vm, h, currentRow, index) { return h("Button", { props: { size: "small", type: currentRow.editting ? "success" : "primary", loading: currentRow.saving }, style: { margin: "0 5px" }, on: { click: function() { // 點擊按鈕時改變當前行的編輯狀態(tài), 當數(shù)據(jù)被更新時,render函數(shù)會再次執(zhí)行,詳情參考https://cn.vuejs.org/v2/api/#render // handleBackdata是用來刪除當前行的editting屬性與saving屬性 var tempData = vm.handleBackdata(currentRow) if (!currentRow.editting) { currentRow.editting = true; } else { // 這里也是簡單的點擊編輯后的數(shù)據(jù)與原始數(shù)據(jù)做對比,一致則不做操作,其實更好的應(yīng)該遍歷所有屬性并判斷 if (JSON.stringify(tempData) == JSON.stringify(vm.dataList[index])) { console.log("未更改"); return currentRow.editting = false; } vm.saveData(currentRow, index) currentRow.saving = true; } } } }, currentRow.editting ? "保存" : "編輯"); }; //動態(tài)添加 刪除 按鈕 var deleteButton = function(vm, h, currentRow, index) { return h("Poptip", { props: { confirm: true, title: currentRow.WRAPDATASTATUS != "刪除" ? "您確定要刪除這條數(shù)據(jù)嗎?" : "您確定要對條數(shù)據(jù)撤銷刪除嗎?", transfer: true, placement: "left" }, on: { "on-ok": function() { vm.deleteData(currentRow, index) } } }, [ h("Button", { style: { color: "#ed3f14", fontSize: "18px", padding: "2px 7px 0", border: "none", outline: "none", focus: { "-webkit-box-shadow": "none", "box-shadow": "none" } }, domProps: { title: "刪除" }, props: { size: "small", type: "ghost", icon: "android-delete", placement: "left" } }) ]); }; //methods中添加 init: function() { console.log("init"); var self = this; self.columnsList.forEach(function(item) { // 使用$set 可以觸發(fā)視圖更新 // 如果含有titleHtml屬性 將其值填入表頭 if (item.titleHtml) { self.$set(item, "renderHeader", function(h, params) { return h("span", { domProps: { innerHTML: params.column.titleHtml } }); }); } // 如果含有操作屬性 添加相應(yīng)按鈕 if (item.handle) { item.render = function(h, param) { var currentRow = self.cloneDataList[param.index]; var children = []; item.handle.forEach(function(item) { if (item === "edit") { children.push(editButton(self, h, currentRow, param.index)); } else if (item === "delete") { children.push(deleteButton(self, h, currentRow, param.index)); } }); return h("div", children); }; } //如果含有editable屬性并且為true if (item.editable) { item.render = function(h, params) { var currentRow = self.cloneDataList[params.index]; // 非編輯狀態(tài) if (!currentRow.editting) { // 日期類型多帶帶 渲染(利用工具暴力的formatDate格式化日期) if (item.date) { return h("span", self.utils.formatDate(currentRow[item.key], item.date.split("_")[1])) } // 下拉類型中value與label不一致時多帶帶渲染 if (item.option && self.utils.isArray(item.option)) { // 我這里為了簡單的判斷了第一個元素為object的情況,其實最好用every來判斷所有元素 if (typeof item.option[0] === "object") { return h("span", item.option.find(findObjectInOption(currentRow[item.key])).label); } } return h("span", currentRow[item.key]); } else { // 編輯狀態(tài) //如果含有option屬性 if (item.option && self.utils.isArray(item.option)) { return h("Select", { props: { // ***重點***: 這里要寫currentRow[params.column.key],綁定的是cloneDataList里的數(shù)據(jù) value: currentRow[params.column.key] }, on: { "on-change": function(value) { self.$set(currentRow, params.column.key, value) } } }, item.option.map(function(item) { return h("Option", { props: { value: item.value || item, label: item.label || item } }, item.label || item); })); } else if (item.date) { //如果含有date屬性 return h("DatePicker", { props: { type: item.date.split("_")[0] || "date", clearable: false, value: currentRow[params.column.key] }, on: { "on-change": function(value) { self.$set(currentRow, params.column.key, value) } } }); } else { // 默認input return h("Input", { props: { // type類型也是自定的屬性 type: item.input || "text", // rows只有在input 為textarea時才會起作用 rows: 3, value: currentRow[params.column.key] }, on: { "on-change"(event) { self.$set(currentRow, params.column.key, event.target.value) } } }); } } }; } }); }, // 還原數(shù)據(jù),用來與原始數(shù)據(jù)作對比的 handleBackdata: function(object) { var clonedData = JSON.parse(JSON.stringify(object)); delete clonedData.editting; delete clonedData.saving; return clonedData; }
到這里完成已經(jīng)差不多了,補上保存數(shù)據(jù)與刪除數(shù)據(jù)的函數(shù)
// 保存數(shù)據(jù) saveData: function(currentRow, index) { var self = this; // 修改當前的原始數(shù)據(jù), 就不需要再從服務(wù)端獲取了 this.$set(this.dataList, index, this.handleBackdata(currentRow)) // 需要保存的數(shù)據(jù) // 模擬ajax setTimeout(function() { 充值編輯與保存狀態(tài) currentRow.saving = false; currentRow.editting = false; self.$Message.success("保存完成"); console.log(self.dataList); }, 1000) }, // 刪除數(shù)據(jù) deleteData: function(currentRow, index) { var self = this; console.log(currentRow.ID); setTimeout(function() { self.$delete(self.dataList, index) self.$delete(self.cloneDataList, index) vm.$Message.success("刪除成功"); }, 1000) },
完整的editTable.js代碼
// 根據(jù)數(shù)據(jù)中下拉的值找到對應(yīng)的對象 function findObjectInOption(name) { return function(item) { return item.value === name; } } var editButton = function(vm, h, currentRow, index) { return h("Button", { props: { size: "small", type: currentRow.editting ? "success" : "primary", loading: currentRow.saving }, style: { margin: "0 5px" }, on: { click: function() { // 點擊按鈕時改變當前行的編輯狀態(tài),當數(shù)據(jù)被更新時,render函數(shù)會再次執(zhí)行,詳情參考https://cn.vuejs.org/v2/api/#render // handleBackdata是用來刪除當前行的editting屬性與saving屬性 var tempData = vm.handleBackdata(currentRow) if (!currentRow.editting) { currentRow.editting = true; } else { // 這里也是簡單的點擊編輯后的數(shù)據(jù)與原始數(shù)據(jù)做對比,一致則不做操作,其實更好的應(yīng)該遍歷所有屬性并判斷 if (JSON.stringify(tempData) == JSON.stringify(vm.dataList[index])) { console.log("未更改"); return currentRow.editting = false; } vm.saveData(currentRow, index) currentRow.saving = true; } } } }, currentRow.editting ? "保存" : "編輯"); }; //動態(tài)添加 刪除 按鈕 var deleteButton = function(vm, h, currentRow, index) { return h("Poptip", { props: { confirm: true, title: currentRow.WRAPDATASTATUS != "刪除" ? "您確定要刪除這條數(shù)據(jù)嗎?" : "您確定要對條數(shù)據(jù)撤銷刪除嗎?", transfer: true, placement: "left" }, on: { "on-ok": function() { vm.deleteData(currentRow, index) } } }, [ h("Button", { style: { color: "#ed3f14", fontSize: "18px", padding: "2px 7px 0", border: "none", outline: "none", focus: { "-webkit-box-shadow": "none", "box-shadow": "none" } }, domProps: { title: "刪除" }, props: { size: "small", type: "ghost", icon: "android-delete", placement: "left" } }) ]); }; var vm = new Vue({ el: "#editTableCtrl", data: function() { return { loading: true, //表格的數(shù)據(jù)源 dataList: [], /** * @name columnsList (瀏覽器 渲染的列) * @author ch * @see https://www.iviewui.com/components/table * @param * { * titleHtml : 渲染帶有html的表頭 列: "資金來源" * editable : true,可編輯的列 必須有字段 * option : 渲染的下拉框列表 * date : 渲染成data類型 ,可選參數(shù): * date | daterange: yyyy-MM-dd (默認) * datetime | datetimerange: yyyy-MM-dd HH:mm:ss * year: yyyy * month: yyyy-MM * input : 渲染input類型 ,可選參數(shù)為html5所有類型 (額外增加 textarea 屬性), 默認text * handle : 數(shù)組類型, 渲染操作方式,目前只支持 "edit", "delete" * } * @version 0.0.1 */ columnsList: [{ width: 80, type: "index", title: "序號", align: "center" }, { align: "center", title: "項目編號", key: "PRJCODE" }, { align: "center", title: "項目名稱", titleHtml: "項目名稱 ", key: "PRJNAME", editable: true }, { align: "center", title: "項目分類", titleHtml: "項目分類 ", key: "PRJTYPE", option: ["產(chǎn)業(yè)項目", "基礎(chǔ)設(shè)施", "民生項目", "住宅項目"], editable: true }, { align: "center", title: "建設(shè)單位", titleHtml: "建設(shè)單位 ", key: "JSUNIT", editable: true }, { align: "center", title: "流程分類", titleHtml: "流程分類 ", key: "FLOW_TYPE_CODE", option: [{ value: "A01", label: "建筑-出讓" }, { value: "A02", label: "建筑-劃撥" }, { value: "B01", label: "市政-綠化" }, { value: "B02", label: "市政-管線" }], editable: true }, { align: "center", title: "開工時間", titleHtml: "開工時間 ", key: "DATE_START", //這里在后面處理的時候會分割成["month","yyyy-MM"]的數(shù)組,分別代表iview的DatePicker組件選擇日期的格式與數(shù)據(jù)庫傳過來時頁面顯示的格式 date: "month_yyyy-MM", editable: true }, { align: "center", title: "竣工時間", titleHtml: "竣工時間 ", key: "DATE_END", date: "month_yyyy-MM", editable: true }, { align: "center", title: "建設(shè)內(nèi)容", titleHtml: "建設(shè)內(nèi)容 ", key: "CONTENT", input: "textarea", editable: true }, { align: "center", title: "總投資(萬元)", titleHtml: "總投資總結(jié)
(萬元) ", key: "INVEST_ALL", input: "number", editable: true }, { title: "操作", align: "center", width: 150, key: "handle", handle: ["edit", "delete"] }], // 增加編輯狀態(tài), 保存狀態(tài), 用于操作數(shù)據(jù) 避免干擾原數(shù)據(jù)渲染 cloneDataList: [] } }, methods: { getData: function() { var self = this; self.loading = true; $http.get("json/editTable.txt").then(function(res) { // 給每行添加一個編輯狀態(tài) 與 保存狀態(tài) self.dataList = res.data.Items; self.cloneDataList = JSON.parse(JSON.stringify(self.dataList)).map(function(item) { item.editting = false; item.saving = false; return item; }); self.loading = false; }); }, //初始化數(shù)據(jù) //methods中添加 init: function() { console.log("init"); var self = this; self.columnsList.forEach(function(item) { // 使用$set 可以觸發(fā)視圖更新 // 如果含有titleHtml屬性 將其值填入表頭 if (item.titleHtml) { self.$set(item, "renderHeader", function(h, params) { return h("span", { domProps: { innerHTML: params.column.titleHtml } }); }); } // 如果含有操作屬性 添加相應(yīng)按鈕 if (item.handle) { item.render = function(h, param) { var currentRow = self.cloneDataList[param.index]; var children = []; item.handle.forEach(function(item) { if (item === "edit") { children.push(editButton(self, h, currentRow, param.index)); } else if (item === "delete") { children.push(deleteButton(self, h, currentRow, param.index)); } }); return h("div", children); }; } //如果含有editable屬性并且為true if (item.editable) { item.render = function(h, params) { var currentRow = self.cloneDataList[params.index]; // 非編輯狀態(tài) if (!currentRow.editting) { // 日期類型多帶帶 渲染(利用工具暴力的formatDate格式化日期) if (item.date) { return h("span", self.utils.formatDate(currentRow[item.key], item.date.split("_")[1])) } // 下拉類型中value與label不一致時多帶帶渲染 if (item.option && self.utils.isArray(item.option)) { // 我這里為了簡單的判斷了第一個元素為object的情況,其實最好用every來判斷所有元素 if (typeof item.option[0] === "object") { return h("span", item.option.find(findObjectInOption(currentRow[item.key])).label); } } return h("span", currentRow[item.key]); } else { // 編輯狀態(tài) //如果含有option屬性 if (item.option && self.utils.isArray(item.option)) { return h("Select", { props: { // ***重點***: 這里要寫currentRow[params.column.key],綁定的是cloneDataList里的數(shù)據(jù) value: currentRow[params.column.key] }, on: { "on-change": function(value) { self.$set(currentRow, params.column.key, value) } } }, item.option.map(function(item) { return h("Option", { props: { value: item.value || item, label: item.label || item } }, item.label || item); })); } else if (item.date) { //如果含有date屬性 return h("DatePicker", { props: { type: item.date.split("_")[0] || "date", clearable: false, value: currentRow[params.column.key] }, on: { "on-change": function(value) { self.$set(currentRow, params.column.key, value) } } }); } else { // 默認input return h("Input", { props: { // type類型也是自定的屬性 type: item.input || "text", // rows只有在input 為textarea時才會起作用 rows: 3, value: currentRow[params.column.key] }, on: { "on-change"(event) { self.$set(currentRow, params.column.key, event.target.value) } } }); } } }; } }); }, saveData: function(currentRow, index) { var self = this; // 修改當前的原始數(shù)據(jù), 就不需要再從服務(wù)端獲取了 this.$set(this.dataList, index, this.handleBackdata(currentRow)) // 需要保存的數(shù)據(jù) // 模擬ajax setTimeout(function() { // 重置編輯與保存狀態(tài) currentRow.saving = false; currentRow.editting = false; self.$Message.success("保存完成"); console.log(self.dataList); }, 1000) }, // 刪除數(shù)據(jù) deleteData: function(currentRow, index) { var self = this; console.log(currentRow.ID); setTimeout(function() { self.$delete(self.dataList, index) self.$delete(self.cloneDataList, index) vm.$Message.success("刪除成功"); }, 1000) }, // 還原數(shù)據(jù),用來與原始數(shù)據(jù)作對比的 handleBackdata: function(object) { var clonedData = JSON.parse(JSON.stringify(object)); delete clonedData.editting; delete clonedData.saving; return clonedData; } }, created: function() { this.getData(); this.init(); } });
兩三天的時間搞的這些,剛開始也是各種懵逼.期間也試過用插槽來實現(xiàn),但還是沒有這個方法來的清晰(隊友都能看懂), 總的來說就是在columnsList自定義一些屬性,后面根據(jù)這些屬性,在render函數(shù)里return不同的值,思路還是很簡單的.
第一次寫文章,并且我也是vue初學者,寫的湊合看吧 ^_^ ,歡迎留言指正
本demo 連同之前學習vue時寫的demo一起放在的我的github上,歡迎star...
github: https://github.com/catkinmu/v...
參考vue: render文檔
iview
iview admin1.x 版本
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/98843.html
摘要:簡介是基于,搭配使用組件庫形成的一套后臺集成解決方案,由前端可視化團隊部分成員開發(fā)維護。遵守設(shè)計和開發(fā)約定,風格統(tǒng)一,設(shè)計考究,并且更多功能在不停開發(fā)中。 showImg(https://segmentfault.com/img/remote/1460000011603206); 簡介 iView Admin 是基于 Vue.js,搭配使用 iView UI 組件庫形成的一套后臺集成解...
摘要:因為我們項目中首要的是單元格拆分的,因此以拆分為例。使用函數(shù)對表格組件的表格列配置數(shù)據(jù)進行動態(tài)改造,普通單元格渲染標簽呈現(xiàn)數(shù)據(jù),要拆分的單元格渲染原生標簽最后隱藏嵌套表格的邊框及調(diào)整相關(guān)原生表格樣式。 最近在開發(fā)的Vue項目中,使用了iview第三方UI庫;對于表格組件的需求是最多的,但是在一些特定場景下,發(fā)現(xiàn)iview的表格組件沒有單元格合并與拆分的API,搜了一下發(fā)現(xiàn)很多同學提問關(guān)...
摘要:而則是用到到指令結(jié)合的方式去生成,批量生成元素。表格操作列自定義渲染的時,使用的是的函數(shù),直接在中插入對應(yīng)模板表格分頁都需要引入分頁組件配合使用兩者總體比較,要比簡潔許多。 element VS iview(最近項目UI框架在選型 ,做了個分析, 不帶有任何利益相關(guān))主要從以下幾個方面來做對比使用率(npm 平均下載頻率,組件數(shù)量,star, issue…)API風格打包優(yōu)化與設(shè)計師友...
摘要:相對時間組件錨點組件面板分割組件分割線組件單元格組件相對時間組件用于表示幾分鐘前幾小時前等相對于此時此刻的時間描述。單元格組件在手機上比較常見,在上則常用于固定的側(cè)邊菜單項。開發(fā)者社區(qū)這是發(fā)布會最勁爆的一款產(chǎn)品了。 showImg(https://segmentfault.com/img/bVbeuj6?w=2864&h=1458); 7 月 28 日,我們成功地召開了 iView 3...
閱讀 1148·2021-11-25 09:43
閱讀 2966·2019-08-30 15:54
閱讀 3349·2019-08-30 15:54
閱讀 2991·2019-08-30 15:44
閱讀 1624·2019-08-26 12:18
閱讀 2256·2019-08-26 11:42
閱讀 875·2019-08-26 11:35
閱讀 3295·2019-08-23 18:22