先簡單說明一下,這個Demo引入的vue,iview的方式是標籤引入的,沒有用到webpack之類的構建工具... 畢竟公司還在用angularjs+jq. 這也是我第一次寫文章,你們看看思路就好了,要是有大佬指點指點就更好了javascript
咱們再看下極爲簡單的目錄結構css
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: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="stylesheet" />
</head>
<body style="background-color: #f0f3f4;">
<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>
複製代碼
首頁沒什麼說的,都是基本的架子. 這是須要渲染的數據及其說明:vue
{
"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)html5
Vue.prototype.utils = utils
window.$http = axios
複製代碼
editTable.js :java
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的規則編寫渲染的列:android
//...
/** * @name columnsList (瀏覽器 渲染的列) * @author catkin * @see https://www.iviewui.com/components/table * @param * { * titleHtml : 渲染帶有html的表頭 列: '資金<em class="blue" style="color:red">來源</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']
}]
//...
複製代碼
此時頁面應該已經能夠渲染出表格了webpack
//...
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函數ios
//全局添加
//根據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;
}
複製代碼
到這裏完成已經差很少了,補上保存數據與刪除數據的函數git
// 保存數據
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" style="color:red">來源</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: github.com/catkinmu/vu…