此文章记录解决Vue2+Elementui中Form组件嵌套Table结构校验问题,主要是分享一下发现问题到解决问题的思路。
问题来源
由于上文「Formily IE兼容性」的原因,我需要使用Vue2+Elementui来实现一个比较复杂的表单。在处理到某个部分的时候,遇到一个需求:
页面中的出现的表单控件都需要填写,Table「动态增加行」中的控件支持「远程搜索」,全部控件支持「校验」。
其中Table中控件支持校验的需求就是此次文章的核心。
解决过程
以下模拟数据的键值用a~z字母命名进行演示,未使用项目中的真实字段。
- 尝试一:
我初次看这个结构的时候,首先想到的是上面两个表单控件为一部分,下面的动态Table为另一部分,即上下结构。如下代码:
html
<template>
<div>
<el-form
:model="outpatientMesForm"
ref="outpatientMesForm"
:inline="true"
:rules="rules"
>
...
</el-form>
<el-table :data="tableData" style="width: 100%">
...
</el-table>
</div>
</template>
- 问题:Table部分不好做「控件校验」
- 思考:Table放在外面,整体校验要考虑两部分,就不能使用Form的校验方法,应该让From来校验Table里面的控件,就不用再去另外校验Table中的控件了。
- 尝试二:
为了解决校验的问题,我把Table放到了Form中,并且加上了字段校验;布局也没有被影响,还是正常的。
html
<template>
<div>
<el-form
:model="outpatientMesForm"
ref="outpatientMesForm"
:inline="true"
:rules="rules"
>
...
<el-form-item prop="b" label="出生日期:">
<el-date-picker
v-model="outpatientMesForm.b"
type="date"
value-format="yyyy-MM-dd"
:picker-options="pickerOptions"
placeholder="选择日期"
>
</el-date-picker>
</el-form-item>
...
<el-table :data="outpatientMesForm.tableData" style="width: 100%">
<el-table-column label="病种名称">
<template slot-scope="scope">
<el-form-item prop="c">
<el-autocomplete
class="input-width-200"
v-model="scope.row.c"
placeholder="请输入"
:fetch-suggestions="getData"
@select="
e => {
scope.row.c = e;
}
"
>
<template slot-scope="{ item }">
<div class="name">{{ item }}</div>
</template>
</el-autocomplete>
</el-form-item>
</template>
</el-table-column>
...
</el-table>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
outpatientMesForm: {
a: "",
b: "",
tableData: [
{
c: "",
d: "",
e: "",
f: ""
},
{
c: "",
d: "",
e: "",
f: ""
}
]
},
rules: {
a: [{ required: true, message: "请选择诊断科别", trigger: "change" }],
b: [
{
required: true,
message: "请选择出生日期",
trigger: "change"
}
],
c: [{ required: true, message: "请选择病种名称", trigger: "change" }],
d: [{ required: true, message: "请选择病种代码", trigger: "change" }],
e: [{ required: true, message: "请选择手术及操作名称", trigger: "change" }],
f: [{ required: true, message: "请选择手术及操作代码", trigger: "change" }],
},
...
};
},
...
};
</script>
- 问题:Table中控件的值未填时,校验能够提示。填写了值之后,校验没有消失。
- 思考:目前这个校验没有起到作用,上面代码中与校验相关的
prop
和rules
属性的写法在没有嵌套结构的From
中能正常的工作。与这两个属性相关的是el-form-item
组件,它决定了校验的规则和现实效果,所以我把问题聚焦到了这个组件中。
- 尝试三
我首先看了官方文档的相关部分:
文档中的校验部分没有详细的解释,也就得不到嵌套结构中如何处理的方式了。所以我就转而去研究el-form-item
源码部分,应该这里面会有详细的用法。
这里我挂了一个线上的el-form-item
完整代码,就不全部展示了。
- 问题:在研究代码之前,还有一个问题需要解决,那就是:研究什么?在这样的一段具有校验功能的代码中,哪一个是关键?
html
<el-form-item label="诊断科别:" prop="a">
<el-select
v-model="outpatientMesForm.a"
placeholder="请输入"
filterable
clearable
collapse-tags
>
<el-option
v-for="item in formOptions.aOptions"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
- 思考:校验的核心肯定是
el-form-item
组件的prop属性,这个属性的值来匹配el-form
的rules
属性,从而达成校验的目的。
所以我去研究了源码中和prop
属性相关的部分,发现有两个地方比较关键:
js
computed: {
//...
// 取得prop对应的值
fieldValue() {
const model = this.form.model;
if (!model || !this.prop) { return; }
let path = this.prop;
if (path.indexOf(':') !== -1) {
path = path.replace(/:/, '.');
}
return getPropByPath(model, path, true).v;
},
//...
},
methods: {
//...
// 校验方法
validate(trigger, callback = noop) {
this.validateDisabled = false;
const rules = this.getFilteredRule(trigger);
if ((!rules || rules.length === 0) && this.required === undefined) {
callback();
return true;
}
this.validateState = 'validating';
const descriptor = {};
if (rules && rules.length > 0) {
rules.forEach(rule => {
delete rule.trigger;
});
}
descriptor[this.prop] = rules;
const validator = new AsyncValidator(descriptor);
const model = {};
model[this.prop] = this.fieldValue;
validator.validate(model, { firstFields: true }, (errors, invalidFields) => {
this.validateState = !errors ? 'success' : 'error';
this.validateMessage = errors ? errors[0].message : '';
callback(this.validateMessage, invalidFields);
this.elForm && this.elForm.$emit('validate', this.prop, !errors, this.validateMessage || null);
});
},
}
总体读下来的意思是prop
属性可以是一个路径,需要根据这个值来进行取值,取到el-form-item
对应的值然后和对应的规则进行判断。
- 尝试四
我把Table里面的prop改成了这样:prop="
tableData[{scope.index}].c"
- 问题:校验不显示
- 思考:
prop
和rules
是对应关系,我这里改了prop,应该是能取到嵌套的值的,有没有可能是没有匹配到对应的rule
呢?所以下一步尝试从rules
解决。
- 尝试五
由于prop
改成了:prop="
tableData[{scope.index}].c"
,所以我想把rules改成
js
rules: {
...
`tableData[${scope.$index}].c`: ...
}
但是scope在外面取不到,所以这种方式不行。就在我一筹莫展的时候,我又去看el-form-item
组件源码,想尝试在里面找到问题的解决办法,发现组件的props
中有一个rules
属性。
js
props: {
...
rules: [Object, Array],
...
},
我就准备尝试一下不用取el-form
中的rules,给每一个el-form-item
单独设置rules
- 尝试六
将Table中表单控件的prop
和rules
改为以下的写法:
html
<el-table-column label="病种名称">
<template slot-scope="scope">
<!-- ** 关键点在prop和rules的写法 ** -->
<el-form-item
:prop="`tableData[${scope.$index}].c`"
:rules="[
{ required: true, message: '请输入活动名称', trigger: 'change' }
]"
>
<el-autocomplete
class="input-width-200"
v-model="scope.row.c"
placeholder="请输入"
:fetch-suggestions="getData"
@select="
e => {
scope.row.c = e;
}
"
>
<template slot-scope="{ item }">
<div class="name">{{ item }}</div>
</template>
</el-autocomplete>
</el-form-item>
</template>
</el-table-column>
改为这种方式之后,解决了这个问题,也就说明我的思路是对的。以下是暗色系中的效果。
完整代码
提示:代码中暂未添加Table的动态增加行功能,如有需要可自行处理
html
<template>
<div>
<el-form
:model="outpatientMesForm"
ref="outpatientMesForm"
:inline="true"
:rules="rules"
>
<el-form-item label="诊断科别:" prop="a">
<el-select
v-model="outpatientMesForm.a"
placeholder="请输入"
filterable
clearable
collapse-tags
>
<el-option
v-for="item in formOptions.aOptions"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item prop="b" label="出生日期:">
<el-date-picker
v-model="outpatientMesForm.b"
type="date"
value-format="yyyy-MM-dd"
:picker-options="pickerOptions"
placeholder="选择日期"
>
</el-date-picker>
</el-form-item>
<el-table :data="outpatientMesForm.tableData" style="width: 100%">
<el-table-column label="病种名称">
<template slot-scope="scope">
<!-- ** 关键点在prop和rules的写法上 ** -->
<el-form-item
:prop="`tableData[${scope.$index}].c`"
:rules="[
{ required: true, message: '请输入活动名称', trigger: 'change' }
]"
>
<el-autocomplete
class="input-width-200"
v-model="scope.row.c"
placeholder="请输入"
:fetch-suggestions="getData"
@select="
e => {
scope.row.c = e;
}
"
>
<template slot-scope="{ item }">
<div class="name">{{ item }}</div>
</template>
</el-autocomplete>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="病种代码">
<template slot-scope="scope">
<el-form-item
:prop="`tableData[${scope.$index}].d`"
:rules="[
{ required: true, message: '请选择病种代码', trigger: 'change' }
]"
>
<el-autocomplete
class="input-width-200"
v-model="scope.row.d"
placeholder="请输入"
:fetch-suggestions="getData"
@select="
e => {
scope.row.d = e;
}
"
>
<template slot-scope="{ item }">
<div class="name">{{ item }}</div>
</template>
</el-autocomplete>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="手术及操作名称">
<template slot-scope="scope">
<el-form-item
:prop="`tableData[${scope.$index}].e`"
:rules="[
{
required: true,
message: '请选择手术及操作名称',
trigger: 'change'
}
]"
>
<el-autocomplete
class="input-width-200"
v-model="scope.row.e"
placeholder="请输入"
:fetch-suggestions="getData"
@select="
e => {
scope.row.e = e;
}
"
>
<template slot-scope="{ item }">
<div class="name">{{ item }}</div>
</template>
</el-autocomplete>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="手术及操作代码">
<template slot-scope="scope">
<el-form-item
:prop="`tableData[${scope.$index}].f`"
:rules="[
{
required: true,
message: '请选择手术及操作代码',
trigger: 'change'
}
]"
>
<el-autocomplete
class="input-width-200"
v-model="scope.row.f"
placeholder="请输入"
:fetch-suggestions="getData"
@select="
e => {
scope.row.f = e;
}
"
>
<template slot-scope="{ item }">
<div class="name">{{ item }}</div>
</template>
</el-autocomplete>
</el-form-item>
</template>
</el-table-column>
</el-table>
</el-form>
<el-button @click="submitForm" style="margin-top: 15px;">点击校验</el-button>
</div>
</template>
<script>
export default {
data() {
return {
outpatientMesForm: {
a: "",
b: "",
tableData: [
{
c: "",
d: "",
e: "",
f: ""
},
{
c: "",
d: "",
e: "",
f: ""
},
]
},
formOptions: {
aOptions: [{ label: "11", value: 11 }]
},
pickerOptions: {
disabledDate(time) {
return time.getTime() > Date.now();
}
},
rules: {
a: [{ required: true, message: "请选择诊断科别", trigger: "change" }],
b: [
{
required: true,
message: "请选择出生日期",
trigger: "change"
}
]
}
};
},
methods: {
submitForm() {
this.$refs.outpatientMesForm.validate(valid => {
if (valid) {
alert("submit!");
} else {
console.log("error submit!!");
return false;
}
});
},
async getData(str, cb) {
if (!str) {
cb && cb([]);
return;
}
const p = new Promise((res, rej) => {
res(["1", "2", "3", "4", "5", "6"]);
});
const data = await p;
cb && cb(data);
}
}
};
</script>
总结
以上就是我在解决Form中Table校验的思考路径,经历了问题的出现,到逐步地探索、分析、尝试,最后成功的过程。
之所以想分享出来,是因为,咱们在处理需求的过程中,总会遇到网上搜不出答案 的情况,这种时候咱们需要耐心 一点根据已有的条件一点点找下一个突破口,过程中保持思考 ,大多数问题都能解决。别忘了事后总结和记录 ,对于养成解决问题的习惯很有帮助。
以上就是全部内容,下一篇文章再见