记一次开发过程中,iview遇到的一些坑以及解决办法

时间:2023-03-10 05:26:54
记一次开发过程中,iview遇到的一些坑以及解决办法

写在开头:本次项目采用的是vue2.0+iview3.0,最近公司没啥事,来总结一下开发过程中遇到的问题。

1、Modal关闭问题

需求背景:modal框里面是个form表单,点击确定之后,先验证form表单,验证通过则关闭modal框,验证不成功则提示用户,不关闭。

问题描述:本来刚开始想通过modal框v-model绑定的值(true或false)来进行控制,手动改之后,报错。

解决办法:

官方iview的modal组件的api里面有个loading属性,可通过控制loading的值来进行控制modal的显示。

记一次开发过程中,iview遇到的一些坑以及解决办法

举例说明:

记一次开发过程中,iview遇到的一些坑以及解决办法

记一次开发过程中,iview遇到的一些坑以及解决办法

注意: refuseLoading刚开始一定要设置为true。

记一次开发过程中,iview遇到的一些坑以及解决办法

这样的话就可以解决问题了。

衍生出来的问题:当关闭模态框之后,再次打开时,表单数据没有重置,还是上一次的数据。

解决办法: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最新版本后,问题得以解决。

这也给我以后很好的警示,有时候一些异常情况,可以先看哈官方的更新日志,因为我们刚开始做项目的时候,版本只是当时的最新版,一些问题可能官方后面已经修复了,所以应及时更新版本。

记一次开发过程中,iview遇到的一些坑以及解决办法

4、Table相关问题

       (1)render函数的运用

记一次开发过程中,iview遇到的一些坑以及解决办法

参数解读:

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主题

记一次开发过程中,iview遇到的一些坑以及解决办法

按照官网,一步一步做的,最后却报错:

记一次开发过程中,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%";
}
}
},

页面效果预览:

记一次开发过程中,iview遇到的一些坑以及解决办法

说明:此方法主要还是通过改变样式来实现效果,对于一些特殊的比较复杂的合并行列,可能无法实现。