一、前言
普通的表单验证参考element UI官方文档即可找到解决方案并顺利进行代码实现,官方也给出了几个示例,是很好的参考。不过,对于复杂的表单验证,官方文档并没有给出过多示例或者说明。文章中的实例就是在实际项目中遇到的一个复杂表单的验证问题。
文章中前端代码基于Vue.js框架,element UI开发。表单的初始数据来自API返回的数据。返回的数据结构如下:
{
Id:'',
Des:'',
MedicalPayDetail:[ // 渲染到表单中的数据
{
EKey:'',
ListMedicalExadmin:
[
{
ProjectName:'',
PayMoney:'', // 需要验证的项
ReducePayRate:'', // 需要验证的项
……
}
]
},
{
EKey:'',
ListMedicalExadmin:
[
{
ProjectName:'',
PayMoney:'', // 需要验证的项
ReducePayRate:'', // 需要验证的项
……
}
]
},
……
]
}
其中,API返回的数据放到了source对象中。element UI中的表单组件要进行验证无论什么情况下都需要对表单绑定model,那么如何对表单绑定model是第一个难点。如果使用 :model="source.MedicalPayDetail" 方式进行绑定,那么在控制台可以看到框架报错了。报错信息的意思是:期望对象,得到的是数组。所以按照以往的直接绑定的方式不行了,换一种绑定方式来解决绑定对象报错问题,Vue中还有另外一种绑定model的方法——:model="{MedicalPayDetail:source.MedicalPayDetail}"。利用这种绑定方式解决了“期望对象,得到的是数组”的错误。
当对表单部分表单项进行验证时可以在表单项中指定验证规则而无需再表单上指定即在FormItem上指定而不是在el-form上。代码如下:
<template>
<div class="medical-main">
<div class="bh-title-yl">
医疗审核表
</div>
<el-form :disabled="disabled" :model="{MedicalPayDetail:source.MedicalPayDetail}" ref="ruleForm">
<div v-for="(item,index) in source.MedicalPayDetail" :key="index">
<simple-box v-if="item.EKey!=3" :title="item.EKey==1?'门诊费':'住院费'" class="medical-item">
<medical-step-first :disabled="disabled" slot="body" :title="item.EKey==1?'门诊费合计':'住院费合计'" :Cost.sync="item.Cost" :EKey="item.EKey" :totalTitle="item.EKey==1?'门诊扣减合计:':'住院扣减合计:'" :intpatientCount="intpatientCount" :itemIndex="index" :items="item.ListMedicalExadmin"></medical-step-first>
</simple-box>
</div>
<Row class="medical-bottom-btn">
<i-col span="24">
<i-button :disabled="disabled" @click="createImg('ruleForm')" class="medical-bottom-btn-item" type="primary">保存并更新费用项目</i-button>
</i-col>
</Row>
</el-form>
</div>
</template>
<script>
import Enumerable from 'linq'
import { GetCompensateMedicalExamine, AddMedicalPayDetail } from '@/apis/medicalAudit'
import { Base64FileUpload } from '@/apis/upload.js'
import medicalStepFirst from './components/medicalStepFirst'
import medcalStepSecond from './components/medcalStepSecond'
import simpleBox from '../../SimpleCase/Components/form/simpleBox'
import importModelVue from '../../../components/Common/importModel'
import html2canvas from 'html2canvas'
import { Canvas2Image } from '../../../Units/cancas2image'
import { mapGetters } from 'vuex'
let vm
export default {
data: function () {
vm = this
return {
source: {
},
followId: 0,
ruleForm: {
MedicalPayDetail: []
}
}
},
created: function () {
this.followId = this.$route.query.followId
GetCompensateMedicalExamine({ followId: this.followId }).then(param => {
this.source = param
this.source.MedicalPayDetail = Enumerable.from(this.source.MedicalPayDetail).orderBy('a=>a.EKey').toArray()
this.ruleForm.MedicalPayDetail = this.source.MedicalPayDetail
})
},
components: {
'medical-step-first': medicalStepFirst,
'medcal-step-second': medcalStepSecond,
'simple-box': simpleBox,
'import-model-vue': importModelVue
},
computed: {
...mapGetters(['get_UserId']),
disabled: () => {
if ((vm.source.FollowStatus < 1 || vm.source.FollowStatus === 3) && vm.permissionsVerif('MedicalExamineEidt') > -1 && vm.source.CurrentCheckId === vm.get_UserId) {
return false
} else {
return true
}
}
},
watch: {
},
methods: {
totalMoney: function (item) {
if (item.EKey !== 3) {
return Enumerable.from(item.ListMedicalExadmin).sum(function (a) {
return parseFloat(a.PayMoney)
})
} else {
return Enumerable.from(item.ListMedicalExadmin).sum(function (a) {
return parseFloat(a.ReducePayMoney)
})
}
},
createImg: function (formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
let vm = this
let shareContent = this.$el // 需要截图的包裹的(原生的)DOM 对象
let width = shareContent.offsetWidth // 获取dom 宽度
let height = shareContent.offsetHeight // 获取dom 高度
let canvas = document.createElement('canvas') // 创建一个canvas节点
var scale = 1 // 定义任意放大倍数 支持小数
canvas.width = width * scale // 定义canvas 宽度 * 缩放
canvas.height = height * scale // 定义canvas高度 *缩放
canvas.getContext('2d').scale(scale, scale) // 获取context,设置scale
let opts = {
scale: scale, // 添加的scale 参数
canvas: canvas, // 自定义 canvas
// logging: true, //日志开关,便于查看html2canvas的内部执行流程
width: width, // dom 原始宽度
height: height,
useCORS: true // 【重要】开启跨域配置
}
html2canvas(shareContent, opts).then(function (canvas) {
let context = canvas.getContext('2d')
// 【重要】关闭抗锯齿
context.mozImageSmoothingEnabled = false
context.msImageSmoothingEnabled = false
context.imageSmoothingEnabled = false
// 【重要】默认转化的格式为png,也可设置为其他格式
let img = Canvas2Image.convertToJPEG(canvas, canvas.width, canvas.height)
Base64FileUpload({
Base64String: img.src,
Suffix: '.jpeg',
ReportId: vm.followId
}).then((respone) => {
let url = respone.Value
vm.saveData(url)
})
})
} else {
return false
}
})
},
saveData: function (url) {
var that = this
this.source.ImgUrl = url
this.source.UserId = this.get_UserId
AddMedicalPayDetail(that.source).then(param => {
if (!window.opener.mvm.$refs.payDetial) {
that.$Message.success('保存医疗审核表成功')
return false
}
window.opener.mvm.$refs.payDetial.addMedical({
ApprovedMoney: 0,
Days: 0,
IsMedicalExamination: true,
PayItemMoney: Number(vm.NeedPayCount),
PayItemName: '医疗费',
PayItemNameCode: '1',
PayItemStandard: Number(vm.NeedPayCount),
ReduceMoney: Number(vm.NeedReduceCount),
Remark: ''
})
that.$Message.success('保存医疗审核表成功')
})
}
}
</script>
以下代码是组件medicalStepFirst 的代码。需要进行验证的表单项需要指定prop属性,而复杂表单中prop变得也比较复杂,medicalStepFirst组件中的表单项prop为::prop="'MedicalPayDetail['+itemIndex+'].ListMedicalExadmin['+index+'].PayMoney'"。注意:prop的值为string,因此双引号中又包一层单引号,因为表单项是根据初始数据进行初始化并且可以增加或减少表单项,因此表单项在数组中的索引是不固定的,这个时候需要用到v-for指令中声明的index来进行动态的拼出来prop。
关键点:prop的值需要与数据中的该项的路径一致,否则编译后运行时会报错。另外,进行验证的方法是可以卸载methods中的,官方文档中这类方法是卸载data中以const来声明的,感觉不妥。
<template>
<div>
<Row class="step-first-box">
<!-- row 1 -->
<i-col span="4" class="step-first-item step-first-blod step-first-tit">
核减项目
</i-col>
<i-col span="6" class="step-first-item step-first-blod step-first-tit">
金额(元)
</i-col>
<i-col span="6" class="step-first-item step-first-blod step-first-tit">
自费原因
</i-col>
<i-col span="3" class="step-first-item step-first-blod step-first-tit">
自费比例(%)
</i-col>
<i-col span="3" class="step-first-item step-first-blod step-first-tit">
自费金额(元)
</i-col>
<i-col span="2" class="step-first-item step-first-blod step-first-tit">
操作
</i-col>
<!-- row 2 -->
<div v-for="(item,index) in ListMedicalExadmin" :key="index">
<i-col span="4" class="step-first-item step-first-rowledge">
<el-form-item>
<el-input :disabled="disabled" v-model="item.ProjectName" placeholder="" style="width:90%; height:100%;"></el-input>
</el-form-item>
</i-col>
<i-col span="6" class="step-first-item step-first-rowledge">
<el-form-item :prop="'MedicalPayDetail['+itemIndex+'].ListMedicalExadmin['+index+'].PayMoney'" :rules="[{ validator: checkPayMoney, trigger: 'blur' }]">
<el-input :disabled="disabled" v-model="item.PayMoney" placeholder="" style="width:90%; height:100%;">
<span slot="append" style="width:100%; height:100%;">元</span>
</el-input>
</el-form-item>
</i-col>
<i-col span="6" class="step-first-item step-first-rowledge">
<el-form-item>
<el-input :disabled="disabled" v-model="item.ReduceReason" placeholder="" style="width:90%; height:100%;">
</el-input>
</el-form-item>
</i-col>
<i-col span="3" class="step-first-item step-first-rowledge">
<el-form-item :prop="'MedicalPayDetail['+itemIndex+'].ListMedicalExadmin['+index+'].ReducePayRate'" :rules="[{ validator: checkPayRate, trigger: 'blur' }]">
<el-input :disabled="disabled" v-model="item.ReducePayRate" placeholder="" style="width:90%; height:100%;">
<span slot="append" style="width:100%; height:100%;">%</span>
</el-input>
</el-form-item>
</i-col>
<i-col span="3" class="step-first-item step-first-rowledge">
<!-- <i-input :value="item.ReducePayRate*item.PayMoney/100" placeholder="" style="width:100%; height:100%;"></i-input> -->
{{isNaN(item.ReducePayRate*item.PayMoney/100)?0:(item.ReducePayRate*item.PayMoney/100)}}
</i-col>
<i-col span="2" class="step-first-item step-first-rowledge">
<a :disabled="disabled" @click="removeItem(index)">删除</a>
</i-col>
</div>
<!-- row 3 btn-->
<i-col v-if="!disabled" span="24" class="step-first-item step-first-blod">
<Row>
<i-col>
<i-button @click="pushNewItem" type="dashed" long style="background-color:#f7f7f7;">+添加核减项目</i-button>
</i-col>
</Row>
</i-col>
<!-- row 4 -->
<i-col span="16" class="step-first-item step-first-rowledge">
<Row>
<i-col span="6" class="step-first-blod">
{{title}}
</i-col>
<i-col style="padding-left:10px" span="8">
<el-form-item>
<el-input v-model="selCost" placeholder="请输入" style="width:100%; height:100%;">
<span slot="append" style="width:100%; height:100%;">元</span>
</el-input>
</el-form-item>
</i-col>
</Row>
</i-col>
<i-col span="8" class="step-first-item step-first-blod step-first-rowledge">
<i-col span="7">
{{totalTitle}}
</i-col>
<i-col span="17">
<font>{{intpatientCount}}</font>
</i-col>
</i-col>
</Row>
</div>
</template>
<script>
import Enumerable from 'linq'
export default {
props: {
title: {
type: String,
default: '缺省'
},
items: {
type: Array
},
EKey: {
type: Number,
default: 1
},
Cost: 0,
totalTitle: {
type: String,
default: '门诊扣减合计'
},
disabled: {
type: Boolean
},
itemIndex: {
type: Number,
default: 0
}
},
data: function () {
return {
source: {
},
followId: 0,
ListMedicalExadmin: this.items,
selCost: this.Cost
}
},
computed: {
intpatientCount: function () {
return Enumerable.from(this.ListMedicalExadmin).sum(function (a) {
let result = parseFloat(a.ReducePayRate) * parseFloat(a.PayMoney) / 100
if (isNaN(result)) {
return 0
}
return result
})
}
},
/**
* 取消自动计算
*/
watch: {
selCost: function (val) {
this.$emit('update:Cost', val)
}
},
methods: {
pushNewItem: function () {
this.ListMedicalExadmin.push({
'Id': 0,
'ProjectName': '',
'PayMoney': '0',
'ReduceReason': '',
'ReducePayRate': 0,
'ReducePayMoney': 0,
'ExaminationType': 0,
'LossId': 0,
'CreateTime': '',
'UpdateTime': '',
'IsDelete': 0,
'LimiteMoney': 0,
'FollowId': this.followId
})
},
removeItem: function (index) {
let _items = this.ListMedicalExadmin
_items.splice(index, 1)
},
checkPayMoney: (rule, value, callback) => {
if (parseInt(value) >= 0) {
callback()
} else {
callback(new Error('金额必须大于等于0'))
}
},
checkPayRate: (rule, value, callback) => {
if (parseInt(value) >= 0 && parseInt(value) <= 100) {
callback()
} else {
callback(new Error('必须在0-100之间'))
}
}
}
}
</script>