1. 程式人生 > >element UI表單驗證:一個複雜迴圈表單渲染後資料修改後的部分表單項驗證

element UI表單驗證:一個複雜迴圈表單渲染後資料修改後的部分表單項驗證

 一、前言

普通的表單驗證參考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>