您好,登錄后才能下訂單哦!
先簡單說明一下,這個Demo引入的vue,iview的方式是標簽引入的,沒有用到webpack之類的構建工具...
畢竟公司還在用angularjs+jq.
這也是我第一次寫文章,大家看看思路就行了,要是有大佬指點指點就更好了
話不多說,先來個效果圖
我們再看下極為簡單的目錄結構
IViewEditTable ## vue+iview 實現的可編輯表格 └── index.html ## 首頁 └── js └── editTable.js ## 首頁JS └── ivew ## iview相關 └── vue ├── axios.min.js ## axios (ajax) ├── util.js ## 與業務無關的純工具函數包 └── vue.min.js ## vue (2.x)
首頁html:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>可編輯表格</title> <link href="iview/iview.css" rel="external nofollow" rel="stylesheet" /> </head> <body > <div id="editTableCtrl"> <i-table :loading="loading" border :data="dataList" :columns="columnsList" stripe size="small"></i-table> </div> <script src="vue/axios.min.js"></script> <script src="vue/vue.min.js"></script> <script src="iview/iview.min.js"></script> <script src="vue/util.js"></script> <script src="js/editTable.js"></script> </body> </html>
首頁沒什么說的,都是基本的架子. 這是需要渲染的數據及其說明:
{ "Status": 1, "Total": 233, "Items": [{ "ID": 1, "PID": 3, "PRJCODE": "2018-001", //項目編號 不可編輯 "PRJNAME": "淡化海水配套泵站", //項目名稱 文本輸入框 "PRJTYPE": "基礎設施", //項目類型 下拉選項 "JSUNIT": "投資公司", //建設單位 文本輸入框 "FLOW_TYPE_CODE":"A02", //流程分類 下拉選項,與數據庫以code形式交互 "DATE_START": "2018-12-1", //開工時間 日期選擇 "DATE_END": "2019-12-1", //竣工時間 日期選擇 "CONTENT": "建設淡化海水配套泵站一座,占地面積約8500平方米", //建設內容 多行輸入框 "INVEST_ALL": "1000" //總投資 數字輸入框 }] }
還有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, //表格的數據源 dataList: [], // 列 columnsList: [], // 增加編輯狀態, 保存狀態, 用于操作數據 避免干擾原數據渲染 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的規則編寫渲染的列:
//... /** * @name columnsList (瀏覽器 渲染的列) * @author catkin * @see https://www.iviewui.com/components/table * @param * { * titleHtml : 渲染帶有html的表頭 列: '資金<em class="blue" >來源</em>' * editable : true,可編輯的列 必須有字段 * option : 渲染的下拉框列表,如果需要與數據庫交互的值與顯示的值不同,須使用[{value:'value',label:'label'}]的形式,下面有例子 * date : 渲染成data類型 ,可選參數: * date | daterange: yyyy-MM-dd (默認) * datetime | datetimerange: yyyy-MM-dd HH:mm:ss * year: yyyy * month: yyyy-MM * input : 渲染input類型 ,可選參數為html5所有類型 (額外增加 textarea 屬性), 默認text * handle : 數組類型, 渲染操作方式,目前只支持 'edit', 'delete' * } * @version 0.0.1 */ columnsList: [{ width: 80, type: 'index', title: '序號', align: 'center' }, { align: 'center', title: '項目編號', key: 'PRJCODE' }, { align: 'center', title: '項目名稱', titleHtml: '項目名稱 <i class="ivu-icon ivu-icon-edit"></i>', key: 'PRJNAME', editable: true }, { align: 'center', title: '項目分類', titleHtml: '項目分類 <i class="ivu-icon ivu-icon-edit"></i>', key: 'PRJTYPE', option: ['產業項目', '基礎設施', '民生項目', '住宅項目'], editable: true }, { align: 'center', title: '建設單位', titleHtml: '建設單位 <i class="ivu-icon ivu-icon-edit"></i>', key: 'JSUNIT', editable: true }, { align: 'center', title: '流程分類', titleHtml: '流程分類 <i class="ivu-icon ivu-icon-edit"></i>', key: 'FLOW_TYPE_CODE', option: [{ value: 'A01', label: '建筑-出讓' }, { value: 'A02', label: '建筑-劃撥' }, { value: 'B01', label: '市政-綠化' }, { value: 'B02', label: '市政-管線' }], editable: true }, { align: 'center', title: '開工時間', titleHtml: '開工時間 <i class="ivu-icon ivu-icon-edit"></i>', key: 'DATE_START', //這里在后面處理的時候會分割成['month','yyyy-MM']的數組,分別代表iview的DatePicker組件選擇日期的格式與數據庫傳過來時頁面顯示的格式 date: 'month_yyyy-MM', editable: true }, { align: 'center', title: '竣工時間', titleHtml: '竣工時間 <i class="ivu-icon ivu-icon-edit"></i>', key: 'DATE_END', date: 'month_yyyy-MM', editable: true }, { align: 'center', title: '建設內容', titleHtml: '建設內容 <i class="ivu-icon ivu-icon-edit"></i>', key: 'CONTENT', input: 'textarea', editable: true }, { align: 'center', title: '總投資(萬元)', titleHtml: '總投資<br />(萬元) <i class="ivu-icon ivu-icon-edit"></i>', key: 'INVEST_ALL', input: 'number', editable: true }, { title: '操作', align: 'center', width: 150, key: 'handle', handle: ['edit', 'delete'] }] //...
此時頁面應該已經可以渲染出表格了
既然要編輯數據,并且我的需求是整行整行的編輯,而編輯的同時就會同步更新數據,那么問題來了
vue中, 數據更新,視圖會隨之更新. 想象一下,我在輸入框中屬于一個值,觸發了數據更新,接著又觸發了視圖更新,那么我每次輸入時,這個input都會失焦,毫無用戶體驗. 所以我們把可編輯的動態內容用cloneDataList渲染,靜態顯示的用dataList渲染
//... self.dataList = res.data.Items; // 簡單的深拷貝,雖然map會返回新數組,但是數組元素也是引用類型,不能直接改,所以先深拷貝一份 self.cloneDataList = JSON.parse(JSON.stringify(self.dataList)).map(function(item) { // 給每行添加一個編輯狀態 與 保存狀態, 默認都是false item.editting = false; item.saving = false; return item; }); //...
接下來,我們要根據columnsList做一次循環判斷,根據相應的key寫出不同的render函數
//全局添加 //根據value值找出數組中的對象元素 function findObjectInOption(value) { return function(item) { return item.value === value; } } //動態添加編輯按鈕 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() { // 點擊按鈕時改變當前行的編輯狀態, 當數據被更新時,render函數會再次執行,詳情參考https://cn.vuejs.org/v2/api/#render // handleBackdata是用來刪除當前行的editting屬性與saving屬性 var tempData = vm.handleBackdata(currentRow) if (!currentRow.editting) { currentRow.editting = true; } else { // 這里也是簡單的點擊編輯后的數據與原始數據做對比,一致則不做操作,其實更好的應該遍歷所有屬性并判斷 if (JSON.stringify(tempData) == JSON.stringify(vm.dataList[index])) { console.log('未更改'); return currentRow.editting = false; } vm.saveData(currentRow, index) currentRow.saving = true; } } } }, currentRow.editting ? '保存' : '編輯'); }; //動態添加 刪除 按鈕 var deleteButton = function(vm, h, currentRow, index) { return h('Poptip', { props: { confirm: true, title: currentRow.WRAPDATASTATUS != '刪除' ? '您確定要刪除這條數據嗎?' : '您確定要對條數據撤銷刪除嗎?', 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 可以觸發視圖更新 // 如果含有titleHtml屬性 將其值填入表頭 if (item.titleHtml) { self.$set(item, 'renderHeader', function(h, params) { return h('span', { domProps: { innerHTML: params.column.titleHtml } }); }); } // 如果含有操作屬性 添加相應按鈕 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]; // 非編輯狀態 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 { // 編輯狀態 //如果含有option屬性 if (item.option && self.utils.isArray(item.option)) { return h('Select', { props: { // ***重點***: 這里要寫currentRow[params.column.key],綁定的是cloneDataList里的數據 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) } } }); } } }; } }); }, // 還原數據,用來與原始數據作對比的 handleBackdata: function(object) { var clonedData = JSON.parse(JSON.stringify(object)); delete clonedData.editting; delete clonedData.saving; return clonedData; }
到這里完成已經差不多了,補上保存數據與刪除數據的函數
// 保存數據 saveData: function(currentRow, index) { var self = this; // 修改當前的原始數據, 就不需要再從服務端獲取了 this.$set(this.dataList, index, this.handleBackdata(currentRow)) // 需要保存的數據 // 模擬ajax setTimeout(function() { 充值編輯與保存狀態 currentRow.saving = false; currentRow.editting = false; self.$Message.success('保存完成'); console.log(self.dataList); }, 1000) }, // 刪除數據 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代碼
// 根據數據中下拉的值找到對應的對象 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() { // 點擊按鈕時改變當前行的編輯狀態,當數據被更新時,render函數會再次執行,詳情參考https://cn.vuejs.org/v2/api/#render // handleBackdata是用來刪除當前行的editting屬性與saving屬性 var tempData = vm.handleBackdata(currentRow) if (!currentRow.editting) { currentRow.editting = true; } else { // 這里也是簡單的點擊編輯后的數據與原始數據做對比,一致則不做操作,其實更好的應該遍歷所有屬性并判斷 if (JSON.stringify(tempData) == JSON.stringify(vm.dataList[index])) { console.log('未更改'); return currentRow.editting = false; } vm.saveData(currentRow, index) currentRow.saving = true; } } } }, currentRow.editting ? '保存' : '編輯'); }; //動態添加 刪除 按鈕 var deleteButton = function(vm, h, currentRow, index) { return h('Poptip', { props: { confirm: true, title: currentRow.WRAPDATASTATUS != '刪除' ? '您確定要刪除這條數據嗎?' : '您確定要對條數據撤銷刪除嗎?', 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, //表格的數據源 dataList: [], /** * @name columnsList (瀏覽器 渲染的列) * @author ch * @see https://www.iviewui.com/components/table * @param * { * titleHtml : 渲染帶有html的表頭 列: '資金<em class="blue" >來源</em>' * editable : true,可編輯的列 必須有字段 * option : 渲染的下拉框列表 * date : 渲染成data類型 ,可選參數: * date | daterange: yyyy-MM-dd (默認) * datetime | datetimerange: yyyy-MM-dd HH:mm:ss * year: yyyy * month: yyyy-MM * input : 渲染input類型 ,可選參數為html5所有類型 (額外增加 textarea 屬性), 默認text * handle : 數組類型, 渲染操作方式,目前只支持 'edit', 'delete' * } * @version 0.0.1 */ columnsList: [{ width: 80, type: 'index', title: '序號', align: 'center' }, { align: 'center', title: '項目編號', key: 'PRJCODE' }, { align: 'center', title: '項目名稱', titleHtml: '項目名稱 <i class="ivu-icon ivu-icon-edit"></i>', key: 'PRJNAME', editable: true }, { align: 'center', title: '項目分類', titleHtml: '項目分類 <i class="ivu-icon ivu-icon-edit"></i>', key: 'PRJTYPE', option: ['產業項目', '基礎設施', '民生項目', '住宅項目'], editable: true }, { align: 'center', title: '建設單位', titleHtml: '建設單位 <i class="ivu-icon ivu-icon-edit"></i>', key: 'JSUNIT', editable: true }, { align: 'center', title: '流程分類', titleHtml: '流程分類 <i class="ivu-icon ivu-icon-edit"></i>', key: 'FLOW_TYPE_CODE', option: [{ value: 'A01', label: '建筑-出讓' }, { value: 'A02', label: '建筑-劃撥' }, { value: 'B01', label: '市政-綠化' }, { value: 'B02', label: '市政-管線' }], editable: true }, { align: 'center', title: '開工時間', titleHtml: '開工時間 <i class="ivu-icon ivu-icon-edit"></i>', key: 'DATE_START', //這里在后面處理的時候會分割成['month','yyyy-MM']的數組,分別代表iview的DatePicker組件選擇日期的格式與數據庫傳過來時頁面顯示的格式 date: 'month_yyyy-MM', editable: true }, { align: 'center', title: '竣工時間', titleHtml: '竣工時間 <i class="ivu-icon ivu-icon-edit"></i>', key: 'DATE_END', date: 'month_yyyy-MM', editable: true }, { align: 'center', title: '建設內容', titleHtml: '建設內容 <i class="ivu-icon ivu-icon-edit"></i>', key: 'CONTENT', input: 'textarea', editable: true }, { align: 'center', title: '總投資(萬元)', titleHtml: '總投資<br />(萬元) <i class="ivu-icon ivu-icon-edit"></i>', key: 'INVEST_ALL', input: 'number', editable: true }, { title: '操作', align: 'center', width: 150, key: 'handle', handle: ['edit', 'delete'] }], // 增加編輯狀態, 保存狀態, 用于操作數據 避免干擾原數據渲染 cloneDataList: [] } }, methods: { getData: function() { var self = this; self.loading = true; $http.get('json/editTable.txt').then(function(res) { // 給每行添加一個編輯狀態 與 保存狀態 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; }); }, //初始化數據 //methods中添加 init: function() { console.log('init'); var self = this; self.columnsList.forEach(function(item) { // 使用$set 可以觸發視圖更新 // 如果含有titleHtml屬性 將其值填入表頭 if (item.titleHtml) { self.$set(item, 'renderHeader', function(h, params) { return h('span', { domProps: { innerHTML: params.column.titleHtml } }); }); } // 如果含有操作屬性 添加相應按鈕 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]; // 非編輯狀態 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 { // 編輯狀態 //如果含有option屬性 if (item.option && self.utils.isArray(item.option)) { return h('Select', { props: { // ***重點***: 這里要寫currentRow[params.column.key],綁定的是cloneDataList里的數據 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; // 修改當前的原始數據, 就不需要再從服務端獲取了 this.$set(this.dataList, index, this.handleBackdata(currentRow)) // 需要保存的數據 // 模擬ajax setTimeout(function() { // 重置編輯與保存狀態 currentRow.saving = false; currentRow.editting = false; self.$Message.success('保存完成'); console.log(self.dataList); }, 1000) }, // 刪除數據 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) }, // 還原數據,用來與原始數據作對比的 handleBackdata: function(object) { var clonedData = JSON.parse(JSON.stringify(object)); delete clonedData.editting; delete clonedData.saving; return clonedData; } }, created: function() { this.getData(); this.init(); } });
總結
兩三天的時間搞的這些,剛開始也是各種懵逼.期間也試過用插槽來實現,但還是沒有這個方法來的清晰(隊友都能看懂), 總的來說就是在columnsList自定義一些屬性,后面根據這些屬性,在render函數里return不同的值,思路還是很簡單的.
第一次寫文章,并且我也是vue初學者,寫的湊合看吧 ^_^ ,歡迎留言指正
本demo 連同之前學習vue時寫的demo一起放在的我的github上,歡迎star...
github: https://github.com/catkinmu/vue.js-practice/tree/master/IViewEditTable
參考
vue: render文檔
iview
iview admin1.x 版本
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。