记一次开发过程中,iview遇到的一些坑以及解决办法
写在开头:本次项目采用的是vue2.0+iview3.0,最近公司没啥事,来总结一下开发过程中遇到的问题。
1、Modal关闭问题
需求背景:modal框里面是个form表单,点击确定之后,先验证form表单,验证通过则关闭modal框,验证不成功则提示用户,不关闭。
问题描述:本来刚开始想通过modal框v-model绑定的值(true或false)来进行控制,手动改之后,报错。
解决办法:
官方iview的modal组件的api里面有个loading属性,可通过控制loading的值来进行控制modal的显示。
举例说明:
注意: refuseLoading刚开始一定要设置为true。
这样的话就可以解决问题了。
衍生出来的问题:当关闭模态框之后,再次打开时,表单数据没有重置,还是上一次的数据。
解决办法:this.$refs[name].resetFields(); 重置表单数据,在关闭模态框的时候调用这个方法可解决。
2、同时验证多个表单问题
需求背景:一个页面有多个表单,提交的时候需要验证多个表单,都验证成功才能进行下一步操作
解决办法:用一个数组来存放每个表单的验证结果(true验证通过,false验证不通过),最后循环这个数组如果值都为true,说明验证通过。
举例说明:页面有3个表单,需要同时验证,主要代码如下:
<template> <div class="hello"> <Form ref="formValidate1" :model="formValidate1" :rules="ruleValidate"> <FormItem label="Name" prop="name"> <Input v-model="formValidate1.name" placeholder="Enter your name"></Input> </FormItem> </Form> <Form ref="formValidate2" :model="formValidate2" :rules="ruleValidate"> <FormItem label="Name" prop="name"> <Input v-model="formValidate2.name" placeholder="Enter your name"></Input> </FormItem> </Form> <Form ref="formValidate3" :model="formValidate3" :rules="ruleValidate"> <FormItem label="Name" prop="name"> <Input v-model="formValidate3.name" placeholder="Enter your name"></Input> </FormItem> </Form> <Button @click="submit">提交</Button> </div> </template> <script> export default { name: 'HelloWorld', data() { return { formValidate1: { name: '' }, formValidate2: { name: '' }, formValidate3: { name: '' }, ruleValidate: { name: [ { required: true, message: 'The name cannot be empty', trigger: 'blur' } ] }, arr: [] } }, methods: { check(name){ // 验证表单是否通过 this.$refs[name].validate((valid) => { if(valid) { this.arr.push(true); // arr 这个数组是用来存放单个表单的验证状态 } else { this.arr.push(false); } }) }, submit(){ // 提交 this.arr = []; // 重置数组 // 同时验证多个表单 this.check('formValidate1'); this.check('formValidate2'); this.check('formValidate3'); var flag = null; for(var i = 0; i < this.arr.length; i++) { if(this.arr[i]) { // 单个表单验证通过,继续验证下个表单 flag = true; continue; } else { // 单个表单验证不通过,结束 flag = false; break; } } if(flag){ // 验证表单成功 alert("验证成功"); }else{ alert("验证失败") } } } } </script> <style scoped></style>
3、Select 内的 Option 动态改变时,有时选中值未更新的问题
需求背景:Select的下拉数组是由后台返回的,选中的值也是后台返回的。正确赋值之后,select选中的值未更新。
解决办法:刚开始一直在不停的调试,有时候可能正确显示,有时候又不行。这个随机事件真的。。。。最后查阅官方文档,好吧,这是官方的坑,更新到iview最新版本后,问题得以解决。
这也给我以后很好的警示,有时候一些异常情况,可以先看哈官方的更新日志,因为我们刚开始做项目的时候,版本只是当时的最新版,一些问题可能官方后面已经修复了,所以应及时更新版本。
4、Table相关问题
(1)render函数的运用
参数解读:
h: vue Render函数的别名(全名 createElement)即 Render函数
params: table 该行内容的对象
props:设置创建的标签对象的属性
style:设置创建的标签对象的样式
on:为创建的标签绑定事件
scopedSlots:显示作用域插槽
a、Switch 开关
{ title: "可控开关", key: "isOpen", align: "center", width: 100, render:(h, params) => { return h('i-switch', { props: { value: params.row.isOpen ? params.row.isOpen : false, // 指定当前是否选中 Boolean类型 (isOpen后端返回字段,根据自己接口返回数据,自行修改) }, scopedSlots:{ open: () => h('span', 'on'), // 自定义显示打开时的内容 close: () => h('span', 'off') // 自定义显示关闭时的内容 }, on: { /* * 触发事件是on-change * 参数value是回调值 Boolean类型 */ 'on-change': (value) => { this.data[params.index].isOpen = value; // 赋值 data:表格数据 } } }) } }
b、Button按钮
{ title: '操作', key: 'action', width: 150, align: 'center', render: (h, params) => { // 按钮操作 return h('div', [ h('Button', { props: { type: 'primary', size: 'small' }, style: { // 自定义样式 marginRight: '5px' }, on: { // 自定义事件 click: () => { this.show(params.index) // params.index是拿到table的行序列,可以取到对应的表格值 } } }, '查看'), h('Button', { props: { type: 'error', size: 'small' }, on: { click: () => { this.remove(params.index) } } }, '删除') ]); } }
c、Input 输入框
{ title: "input输入框", key: "inputText", align: "center", render:(h, params) => { return h('Input', { props: { value: params.row.inputText ? params.row.inputText : '', size: 'small' }, on: { 'on-blur': (event) => { // 输入框失去焦点时触发 this.data[params.index].inputText = event.target.value; // 赋值 data:表格数据 } } }); } }
d、Select 下拉框
{ title: 'select下拉框', key: 'selectText', align: 'center', render: (h, params) => { return h('Select',{ props:{ value: params.row.selectText ? params.row.selectText : '', size: 'small' }, on: { 'on-change':(value) => { // 下拉框选定的值 this.data[params.index].selectText = value; } } }, /** * this.selectAction 下拉框Option数组 * selectAction:[ { value: '01', name:'select_1' }, { value: '02', name:'select_2' } ] */ this.selectAction.map((item) =>{ // 下拉选项 return h('Option', { props: { value: item.value, label: item.name } }) }) ) } }
e、Rate评分
{ title: "评分", key: "rate", align: "center", render:(h, params) => { return h('Rate', { props: { value: Number(params.row.rate), // 当前 star 数 Number类型 'allow-half': true, // 可以选中半星 disabled: false // 是否只读 }, on: { 'on-change': (value) => { // 评分改变时触发 this.data[params.index].rate = value; // 赋值 data:表格数据 } } }) } }
f、Img图片
{ title: "头像", key: "avatar", align: "center", width: 100, render:(h, params) => { return h('Avatar', { // 也可用原生img标签代替 style: { width: '30px', height: '30px', 'border-radius': '50%' }, attrs: { src: 'https://i.loli.net/2017/08/21/599a521472424.jpg' } }) } }
g、DatePicker时间选择器
{ title: "时间选择器", key: "date", align: "center", render:(h, params) => { return h('DatePicker', { props: { value: params.row.date, size: 'small', type: 'datetime' }, on: { 'on-change': (value) => { // 输入框失去焦点时触发 this.data[params.index].date = value; // 赋值 data:表格数据 } } }); } }
h、对数据进行处理
比如,后端返回时间是时间戳格式,展示给用户看的肯定不能是时间戳,这时候就需要我们对数据进行处理
{ title: "申请年份", align: "center", key: "applyDate", render: (h, params) => { return h('span', { }, new Date(params.row.applyDate).getFullYear()) // 对后端返回的时间戳进行处理,返回页面需要展示的格式 } }
差不多就总结了这几个,写多了就发现,是一样的模板,直接套到render函数里面就是了。想要更多的学习render函数相关的,可以自己前往官网学习。
完整代码:
<template> <div class="hello"> <Table border :columns="columns" :data="data"></Table> </div> </template> <script> export default { name: 'HelloWorld', data() { return { columns: [ // 表格列的配置描述 { title: "头像", key: "avatar", align: "center", width: 100, render:(h, params) => { return h('Avatar', { // 也可用原生img标签代替 style: { width: '30px', height: '30px', 'border-radius': '50%' }, attrs: { src: 'https://i.loli.net/2017/08/21/599a521472424.jpg' } }) } }, { title: "时间选择器", key: "date", align: "center", render:(h, params) => { return h('DatePicker', { props: { value: params.row.date, size: 'small', type: 'datetime' }, on: { 'on-change': (value) => { // 输入框失去焦点时触发 this.data[params.index].date = value; // 赋值 data:表格数据 } } }); } }, { title: "input输入框", key: "inputText", align: "center", render:(h, params) => { return h('Input', { props: { value: params.row.inputText ? params.row.inputText : '', size: 'small' }, on: { 'on-blur': (event) => { // 输入框失去焦点时触发 this.data[params.index].inputText = event.target.value; // 赋值 data:表格数据 } } }); } }, { title: 'select下拉框', key: 'selectText', align: 'center', render: (h, params) => { return h('Select',{ props:{ value: params.row.selectText ? params.row.selectText : '', size: 'small' }, on: { 'on-change':(value) => { // 下拉框选定的值 this.data[params.index].selectText = value; } } }, /** * this.selectAction 下拉框Option数组 * selectAction:[ { value: '01', name:'select_1' }, { value: '02', name:'select_2' } ] */ this.selectAction.map((item) =>{ // 下拉选项 return h('Option', { props: { value: item.value, label: item.name } }) }) ) } }, { title: "申请年份", align: "center", key: "applyDate", render: (h, params) => { return h('span', { }, new Date(params.row.applyDate).getFullYear()) // 对后端返回的时间戳进行处理,返回页面需要展示的格式 } }, { title: "可控开关", key: "isOpen", align: "center", width: 100, render:(h, params) => { return h('i-switch', { props: { value: params.row.isOpen ? params.row.isOpen : false, // 指定当前是否选中 Boolean类型 (isOpen后端返回字段,根据自己接口返回数据,自行修改) }, scopedSlots:{ open: () => h('span', 'on'), // 自定义显示打开时的内容 close: () => h('span', 'off') // 自定义显示关闭时的内容 }, on: { /* * 触发事件是on-change * 参数value是回调值 Boolean类型 */ 'on-change': (value) => { this.data[params.index].isOpen = value; // 赋值 data:表格数据 } } }) } }, { title: "评分", key: "rate", align: "center", render:(h, params) => { return h('Rate', { props: { value: Number(params.row.rate), // 当前 star 数 Number类型 'allow-half': true, // 可以选中半星 disabled: false // 是否只读 }, on: { 'on-change': (value) => { // 评分改变时触发 this.data[params.index].rate = value; // 赋值 data:表格数据 } } }) } }, { title: '操作', key: 'action', width: 150, align: 'center', render: (h, params) => { // 按钮操作 return h('div', [ h('Button', { props: { type: 'primary', size: 'small' }, style: { // 自定义样式 marginRight: '5px' }, on: { // 自定义事件 click: () => { this.show(params.index) // params.index是拿到table的行序列,可以取到对应的表格值 } } }, '查看'), h('Button', { props: { type: 'error', size: 'small' }, on: { click: () => { this.remove(params.index) } } }, '删除') ]); } } ], data: [ // 表格数据 { inputText: '18', isOpen: false, selectText : '02', rate: 4, date: '2019-02-03 00:08:45', applyDate: 1551835636920 }, { inputText: '', isOpen: true, selectText : '01', rate: 1.5, date: '', applyDate: 1506124800000 } ], selectAction:[ { value: '01', name:'select_1' }, { value: '02', name:'select_2' } ] } }, methods: { show (index) { // 查看 this.$Modal.info({ title: '查看', content: '查看详情' }) }, remove (index) { // 删除 this.data.splice(index, 1); } } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .hello{ width: 90%; margin: 0 auto; padding: 20px 50px 0; } </style>
(2)selection的多选来做单选:
通过给 columns
数据设置一项,指定 type: 'selection'
,即可自动开启多选功能,但是有些产品觉得iview的单选效果不好,非要用selection的来做单选。以下是解决方案:
{ title: '选择', align:'center', key: 'checkBox', width: 80, render:(h,params)=>{ return h('div',[ h('Checkbox',{ props:{ value: params.row.checkBox }, on:{ 'on-change':(e)=>{ this.data.forEach((item)=>{ // 先取消所有对象的勾选,checkBox设置为false this.$set(item, 'checkBox', false); }); this.data[params.index].checkBox = e; // 再将勾选的对象的checkBox设置为true } } }) ]) } }
(3)结合Page分页组件一起使用
一般来讲,在表格数据比较多的情况下,会对表格进行分页展示
<Table border :columns="columns" :data="data"></Table> <Page style="float: right;margin-top:20px" :total="page.total" :current="page.current" :page-size="page.size" @on-change="changePage" @on-page-size-change="changePageSize" show-total show-elevator show-sizer />
分页一般有2种:前端分页、后端分页。前端分页就是前端一次性拿到所有数据,再对拿到的数据进行分页展示。后端分页就是前端一次只拿一页的数据展示,分页的时候再次请求后端。
mounted(){ this.changeTableData(); }, methods: { changePage(current){ // 页码改变的回调,返回改变后的页码 this.page.current = current; this.changeTableData(); }, changePageSize(size){ // 切换每页条数时的回调,返回切换后的每页条数 this.page.current = 1; this.page.size = size; this.changeTableData(); }, changeTableData(){ /** * page定义 * page: { total: 0, 总数 current: 1, // 当前页码 size: 10 // 每页个数 } * * * data: 表格展示数据 * tableData: 后端返回的所有数据 * */ // 前端分页 let _start, _end; let page = this.page; _start = (page.current - 1) * page.size; _end = page.current * page.size; this.data = this.tableData.slice(_start, _end); page.total = this.tableData.length; // 后端分页 // ajax请求后端接口,重新获取数据 } }
使用了type=index,分页之后,索引还是从1开始,如何实现累加呢?
使用render函数可解决:
{ title: '序号', align:'center', type: 'index2', width: 80, render: (h, params) => { return h('span', params.index + (this.page.current - 1) * this.page.size + 1); } }
在翻页之后,如何记住checkbox多选的选中和未选中状态?
用一个数组checkData来装选中的数据,在每次表格数据改变时,通过比对数据来进行回填选中状态。
<Table border :columns="columns" :data="data" @on-select="changeSelect" @on-select-cancel="changeSelectCancel" @on-select-all="changeSelectAll" @on-select-all-cancel="changeSelectAllCancel"></Table>
methods: { changeSelect(data, val){ // 选中某个数据 /** * checkData 多选框选中的数组 * id 每条数据的唯一标识 */ for(let i in this.checkData){ if(val.id === this.checkData[i].id){ this.checkData.splice(i, 1); break; } } this.checkData.push(val); }, changeSelectCancel(data, val){ // 取消某个数据 for(let i in this.checkData){ if(val.id === this.checkData[i].id){ this.checkData.splice(i, 1); break; } } }, changeSelectAll(data){ // 多选选中 let arr = []; for(let i in data){ let flag = true; for(let y in this.checkData){ if(data[i].id === this.checkData[y].id){ // 已有数据 flag = false; break; }else{ continue; } } if(flag){ // 添加新数据 arr.push(data[i]); } } this.checkData = this.checkData.concat(arr); }, changeSelectAllCancel(data){ // 多选取消 for(let i in this.data){ // 取消当前页的内容 for(let y in this.checkData){ if(this.data[i].id === this.checkData[y].id){ this.checkData.splice(y, 1); break; } } } }, }, watch:{ 'data':{ handler:function(newValue, oldValue){ this.data = newValue; for(let i in this.data){ let flag = false; for(let y in this.checkData){ if(this.data[i].id === this.checkData[y].id){ flag = true; break; }else{ continue; } } if(flag){ // 回填选中状态 this.data[i]._checked = true; }else{ this.data[i]._checked = false; } } }, deep:true } }
5、定制iview主题
按照官网,一步一步做的,最后却报错:
那是因为less的版本过高,重新卸载,安装3.0以下的版本(比如2.7.3版本),即可解决问题。
六、合并表格单元行
官网已经提供了表头分组的api。可是我们想合并单元行列怎么办呢,虽然官网没提供api,但是好在我们可以用render函数自定义渲染行列。
1、合并行
{ title: '合并行', align:'center', key: 'content', render: (h, params) => { let a = ['数学','语文'];
return this.renderData(h, a); } } // 下面为渲染方法 renderData(h, a){ let b = []; a.map((val, index) => { b.push(h("p", { style: { height: "40px", lineHeight: "40px", "width": "100%", "display": "block", "padding": "0 10px", borderBottom: a.length !== index + 1 ? "1px solid #e8eaec" : "none" }}, val) ) }) return b; }
2、合并列
{ title: '姓名,昵称', align:'center', key: 'name', renderHeader: (h, params) => { // 自定义表头 let a = ['姓名', '昵称']; return this.renderColumnData(h, a); }, render: (h, params) => { let a = ['张三', '李四']; if(params.row.same){ // 名字姓名相同 a = ['王二麻子']; } return this.renderColumnData(h, a); } }, // 下面为渲染的方法 renderColumnData(h, a){ // 渲染合并列 let b = []; let percent = a.length === 0 ? 100 : (Math.floor(100 / a.length * 100) / 100); a.map((val, index) => { b.push(h("p", { class: a.length !== index + 1 ? "has-border" : "", style: { width: `${percent}%`, padding:'4px 8px', 'display': 'flex', 'align-items': 'center', 'justify-content': 'center' }}, val) ) }) let header = h('div',{ class: 'custom-header' }, b); return header; }
还需添加css样式
.ivu-table-cell{ padding: 0 !important; width: 100%; } .ivu-table-cell .has-border{ position: relative; } .ivu-table-cell .has-border::after{ content: ''; position: absolute; width: 1px; background-color: #e8eaec; top: 0; bottom: 0; right: 0; } .custom-header{ width: 100%; height: 100%; display: flex; }
需要注意的是 iview 里面的 ivu-table-cell class样式 没添加height:100%。但是由于自定义列的时候,右边框的高度要100%,所以只能根据子元素手动改父节点 ivu-table-cell,添加样式。
在初始化,和改变表格数据的时候都需执行一次
changeCustomHeight(){ let customArr = document.getElementsByClassName('custom-header'); //custom-header render函数里面自定义的class for(let i in customArr){ if(customArr[i].parentNode){ customArr[i].parentNode.style.height = "100%"; } } },
页面效果预览:
说明:此方法主要还是通过改变样式来实现效果,对于一些特殊的比较复杂的合并行列,可能无法实现。