具体问题具体分析,关于DatePicker的结论请直达结论
问题描述
场景
根据业务需求,存在这样一个表单,初始从接口中获取数据,通过Object.assign为字段赋值,编辑表单可以提交;在页面中存在删除子项的操作,该操作会重新调用接口进行赋值。
问题
正常提交时,表单正常校验,进入validate的回调函数;但执行删除操作后,无法进入validate回调函数,但可以发现表单触发了校验。
问题排查
业务场景本身并不复杂,因此将排查重心放在校验相关代码,最开始以为是自定义校验方法的问题(自定义校验必须要有callback返回),检查后发现和此无关。
之后考虑可能是表单某个字段的问题,在逐个试验后,最后确定是一个日期字段 basj (备案时间)导致的,此处使用了DatePicker组件,通过v-modal双向绑定进行赋值。
检查后发现,初始化数据后提交,该字段为日期格式;而在删除操作后,重新赋值再进行提交,该字段则变成了字符串。由此联想到刚开始时,在提交表单时,bssj校验不通过,总是提示请选择时间,后来增加了type:"date"才得以解决。
basj: [
{ required: true,type:"date", message: "请选择备案时间", trigger: "change" },
],
这么做的原因就是DatePicker组件使用v-modal后,得到的是时间格式,String类型自然无法校验;那么问题又来了,为什么删除操作之后,日期就变成了字符串类型呢。
分析
对于表单而言,删除操作和直接提交唯一的区别在于重新赋值,如果没有对bssj进行重新赋值,则不存在该问题;而在接口的返回中,bssj是字符串格式,即'2021-05-01 00:00:00';初始化时,变为了日期格式,因此可以确定,DatePicker会自动将字符串格式转化为日期格式;而重新赋值后,变成了字符串,此时可以大胆的猜测,重新赋值时,DatePicker不会再进行转换。根据猜想进行如下实验,先赋初始值,再重新赋值,观察初始值和新值的变化
// this.formItem.basj = null // 赋值后:日期格式
// this.formItem.basj = new Date() // 赋值后:日期格式
// this.formItem.basj = new Date( '2021-05-01 00:00:00') // 赋值后:字符串
// this.formItem.basj = '2021-05-01 00:00:00' // 赋值后:字符串
this.formItem.basj = '2021-05-02 00:00:00' // 赋值后:日期格式
// 获取格式化后的时间
setTimeout(() => {
console.log("start------" + this.formItem.basj)
}, 100);
// 重新赋值
setTimeout(() => {
this.formItem.basj = '2021-05-01 00:00:00'
}, 200);
// 获取赋值后的时间
setTimeout(() => {
console.log("end------" + this.formItem.basj)
}, 300);
结论
对于DatePicker组件
- 通过v-modal赋值字符串时,会自动转换为时间类型
- 当新值为字符串时,且新值与原值为同一时间,则保留字符串
其它
- 对于该问题的解决方案,在赋值时,手动将字符串转化为时间类型即可。
- 对于validate,设置了type:"date",但对于String类型,既没有提示,也没有报错,而是一直loading的问题,可能是async-validate的Bug,如果有清楚的同学可以解答一二。
- 以上结论是实验推论而得,这里附上iview中DatePicker,格式化日期的源码,大家可以自行研究
parseDate(val) {
const isRange = this.type.includes('range');
const type = this.type;
const parser = (
TYPE_VALUE_RESOLVER_MAP[type] ||
TYPE_VALUE_RESOLVER_MAP['default']
).parser;
const format = this.format || DEFAULT_FORMATS[type];
const multipleParser = TYPE_VALUE_RESOLVER_MAP['multiple'].parser;
if (val && type === 'time' && !(val instanceof Date)) {
val = parser(val, format, this.separator);
} else if (this.multiple && val) {
val = multipleParser(val, format, this.separator);
} else if (isRange) {
if (!val){
val = [null, null];
} else {
if (typeof val === 'string') {
val = parser(val, format, this.separator);
} else if (type === 'timerange') {
val = parser(val, format, this.separator).map(v => v || '');
} else {
const [start, end] = val;
if (start instanceof Date && end instanceof Date){
val = val.map(date => new Date(date));
} else if (typeof start === 'string' && typeof end === 'string'){
val = parser(val.join(this.separator), format, this.separator);
} else if (!start || !end){
val = [null, null];
}
}
}
} else if (typeof val === 'string' && type.indexOf('time') !== 0){
val = parser(val, format) || null;
}
return (isRange || this.multiple) ? (val || []) : [val];
},