vue+iview 实现可编辑表格的示例代码
程序员文章站
2023-11-20 15:27:10
先简单说明一下,这个demo引入的vue,iview的方式是标签引入的,没有用到webpack之类的构建工具...
毕竟公司还在用angularjs+jq.
这...
先简单说明一下,这个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 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>
首页没什么说的,都是基本的架子. 这是需要渲染的数据及其说明:
{ "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" 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'] }] //...
此时页面应该已经可以渲染出表格了
既然要编辑数据,并且我的需求是整行整行的编辑,而编辑的同时就会同步更新数据,那么问题来了
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" 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: https://github.com/catkinmu/vue.js-practice/tree/master/iviewedittable
参考
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。