首先我们构建表单的 dom 元素,这里我们使用的是 render 函数代替 template,利用 scopedSlots 字段向子组件中传入一个作用域插槽的对象。
<a-table
rowKey={(record, index) => index + record.varietyCode}
pagination={false}
data-source={this.tableData}
columns={columns.call(this, h)}
{...{ scopedSlots: this.scopedSlotsList }}
/>
由于不同的 mode 形式下,可能 column 项是可编辑的,也有可能是不可以编辑,只能查看相关数据的。
const validators = []
scopedSlotsList () {
const obj = {}
Array.prototype.forEach.call(['applyAssistQty'], item => {
obj[item] = (text, record, index) => {
return (['edit', 'add'].includes(this.mode) || (record.waitInfo === true && this.mode === 'tracking'))
? <cell-validate mode="a-input-number"
validateProp={`${item}${index}`}
validators={validators}
rules={[{ required: true, message: '该选项必填' }, {
validator: (rule, value, callback) => {
......
return callback()
},
}]}
v-model={this.tableData[index][item]} />
: text
}
})
}
我们可以看到上述 cell-validate 组件就是我们要实现的表单校验功能组件。
mode: 标签名或组件名
validateProp:校验组件的 prop 属性,由于是循环表单,我们添加了 index 的值
validators:要校验的组件
rules: 校验规则
同时向外还提供了两个方法:validate 和 clearValidate
validate: 用来校验
clearValidate 用来清空校验规则
async validate () {
try {
await Promise.all([...validators.map(vm => vm.validate())])
return true
} catch (err) {
return false
}
}
async clearAllValidate () {
await [...validators.map(vm => vm.clearValidate())]
},
接下来就是如何实现 cell-validate 组件?
我们再来回顾下 async-validator 的基本用法:ant-design-vue 表单验证和 validator 自定义表单验证。
validate 校验表单方法就是采用 async-validator 的基本用法实现的,使用 errorMessage 记录错误信息,清空校验只需将errorMessage 清空即可。
validate () {
// 真正校验的的时候再生成validator函数
if (!this.validateProp || !this.rules) return Promise.resolve(true)
const descriptor = {
[this.validateProp]: this.rules,
}
const validator = new Schema(descriptor)
if (!validator) return Promise.resolve(true)
const model = {
[this.validateProp]: this.$attrs.value,
}
return validator.validate(model, (errors, fields) => {
this.errorMessage = errors ? errors[0].message : ''
})
},
clearValidate () {
this.errorMessage = ''
},
我们可以看到我们使用的 validators 是由验证表单内部创建的。
created () {
this.validators.push(this)
},
beforeDestroy () {
const index = this.validators.findIndex(vm => vm._uid === this._uid)
if (index >= 0) {
this.validators.splice(index, 1)
}
},
由于传入的自己自身有一些 attribute 和 事件,在添加校验规则的同时如何拥有组件自身的属性呢?我们将使用 $attrs 和 $listeners 来获取。详情请查看:在 render 函数中,Vue 实例属性:$attrs、$props、$listeners 和 $scopedSlots的使用。
render (h) {
return <div class={this.errorMessage ? 'has-error' : ''} style="position:relative;">
{
h(this.mode,
{
style: {
width: '100%',
},
props: {
...this.$attrs,
dropdownMatchSelectWidth: false,
},
on: {
...this.$listeners,
select: (param) => this.$emit('select', param),
change: this.handleChange,
},
}
)
}
{ this.errorMessage && <span style="color:red;font-size:12px;line-height:1;">{this.errorMessage}</span> }
</div>
},
进阶版:
如何给选中的 table 行添加校验规则,未选中的行则取消校验规则呢?
由于校验规则是通过组件外部传入的,所以在添加出做校验规则那里只需做判断处理即可。
scopedSlotsList () {
const obj = {}
Array.prototype.forEach.call(['weight'], item => {
obj[item] = (text, record, index) => {
return <cell-validate mode="a-input-number" class="w140"
validateProp={`${item}${index}`}
validators={validators}
rules={this.asyncRules(record, item)}
v-model={this.tableData[index][item]} />
}
})
return obj
},
我们可以上述的校验规则是由 asyncRules 所产生的。这里的 asyncRules 可以使用计算属性也可以使用方法实现。对于勾选的数据则返回校验规则即可,未勾选的返回 null。
asyncRules () {
return function (record, key) {
if (this.selectedRowKeys.includes(record.id) && ['weight'].includes(key)) {
return {
validator: (rule, value, callback) => {
......
return callback()
},
}
}
return
}
},
// 组件 validate 校验方法
validate () {
// 真正校验的的时候再生成validator函数
if (!this.validateProp || !this.rules) return Promise.resolve(true)
......
},