Bootstrap

VUE+Element 动态生成表单 and 动态生成校验(全网首发)

需求: 在状态流中,需要一个必填表单,里面的必填字段都是未知的,需要动态生成

俗话说得好,可以cv绝不手敲代码,但是找遍了全网,没有类似的案例。。。
被逼无奈只能手写,但最后效果还是挺满意的,话不多说,上代码!

<!-- 表单必填项控件,当必填项不通过时,会触发表单通知User输入 -->
<template>
  <el-dialog v-loading="loading" title="Require" :visible.sync="dialogFormVisible" append-to-body>
    <el-form ref="form" :model="queryData" :rules.sync="rules">
      <!-- 下拉框 -->
      <el-row>
        <el-col v-for="(item,index) of correctTypeGroup[2]" :key="index" :span="12">
          <el-form-item v-if="refreshForm" :label="item.fieldName" :prop="item.fieldAttributeName" :label-width="formLabelWidth">
            <!-- assignee -->
            <el-select v-if="item.fieldAttributeName === 'assignee' " v-model="queryData[item.fieldAttributeName]" :disabled="item.readOnly === 1" @change="updateForm">
              <el-option
                v-for="assignee in assignees"
                :key="assignee.id"
                :label="assignee.name"
                :value="assignee.id"
              />
            </el-select>
            <!-- mailNotifier  v-else-if="item.fieldAttributeName === 'mailNotifier'"-->
            <el-select v-else-if="item.fieldAttributeName === 'mailNotifier'" v-model="queryData[item.fieldAttributeName]" :disabled="item.readOnly === 1" multiple @change="updateForm">
              <el-option
                v-for="assignee in assignees"
                :key="assignee.id"
                :label="assignee.name"
                :value="assignee.id"
              />
            </el-select>
            <!-- orther -->
            <el-select v-else v-model="queryData[item.fieldAttributeName]" :disabled="item.readOnly === 1" @change="updateForm">
              <el-option
                v-for="assignee in pageEnum[item.fieldId]"
                :key="assignee.id"
                :label="assignee.value"
                :value="assignee.id"
              />
            </el-select>

          </el-form-item>
        </el-col>
      </el-row>

      <!-- boolean -->
      <el-row>
        <el-col v-for="(item,index) of correctTypeGroup[5]" :key="index" :span="6">
          <el-form-item v-if="refreshForm" :label="item.fieldName" :prop="item.fieldAttributeName" :label-width="formLabelWidth">
            <!-- true-label="true" falselabel="false" -->
            <el-checkbox v-model="queryData[item.fieldAttributeName]" :disabled="item.readOnly === 1" checked @change="updateForm">{{ item.fieldName }}</el-checkbox>
          </el-form-item>
        </el-col>
      </el-row>

      <!-- 时间 -->
      <el-row>
        <el-col v-for="(item,index) of correctTypeGroup[3]" :key="index" :span="12">
          <el-form-item v-if="refreshForm" :label="item.fieldName" :prop="item.fieldAttributeName" :label-width="formLabelWidth">
            <el-date-picker
              v-model="queryData[item.fieldAttributeName]"
              :disabled="item.readOnly === 1"
              type="date"
              value-format="yyyy-MM-dd"
              @input="updateForm"
            />
          </el-form-item>
        </el-col>
      </el-row>

      <!-- 文本框 -->
      <el-row>
        <el-col v-for="(item,index) of correctTypeGroup[1]" :key="index" :span="24">
          <el-form-item v-if="refreshForm" :label="item.fieldName" :prop="item.fieldAttributeName" :label-width="formLabelWidth">
            <el-input v-model="queryData[item.fieldAttributeName]" :disabled="item.readOnly === '1'" type="textarea" @input="updateForm" />
          </el-form-item>
        </el-col>
      </el-row>

      <!-- 数字 -->
      <el-row>
        <el-col v-for="(item,index) of correctTypeGroup[4]" :key="index" :span="24">
          <el-form-item v-if="refreshForm" :label="item.fieldName" :prop="item.fieldAttributeName" :label-width="formLabelWidth">
            <el-input v-model="queryData[item.fieldAttributeName]" onkeyup="value=value.replace(/[^\d]/g,'')" @input="updateForm" />
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <div slot="footer" class="dialog-footer">
      <el-button @click="dialogFormVisible = false">Cancel</el-button>
      <el-button type="primary" @click="save">Save</el-button>
    </div>
  </el-dialog>
</template>

<script>

import { getEnumsByProjectIdAndEnumsIds } from '@/api/enum'
import { copyProperties } from '@/utils/bean-util'
import { searchAction } from '@/api/quality/action'
import { searchField } from '@/api/quality/page'
import { IsEmpty } from '@/utils/validate'
import { formatDate } from '@/utils/date'
export default {
  name: 'CorrectForm',
  props: {
    assignees: {
      type: Array,
      default() {
        return []
      }
    }
  },
  data() {
    return {
      dialogFormVisible: false,
      historyData: {},
      loading: false,
      refreshForm: true,
      queryData: {
        id: '',
        projectId: '',
        actionId: ''
      },
      // 页面枚举容器
      pageEnum: {

      },
      // 输入框类型分组容器
      correctTypeGroup: {

      },
      formLabelWidth: '180px',
      customRules: {},
      rules: {}
    }
  },
  methods: {
    /**
     * 提交表单的方法,此方法会校验表单,校验通过后
     * 会回调父组件提供的save方法
     * params: action 点击的action对象
     * params: queryData 用户当前页的表单数据
     */
    async submit(action, queryData, history) {
      this.queryData = {
        id: '',
        projectId: '',
        actionId: '',
        nativeId: ''
      }
      // 获取actionList
      const actionConfigList = await this.getActionConfigList(action)
      // 校验参数
      const result = this.validate(actionConfigList, queryData)
      // 设置扭转状态的actionId
      queryData.actionId = action.actionId
      queryData.nativeId = action.nativeId
      // 校验不通过
      if (!result.success) {
        // 打开dialog
        this.dialogFormVisible = true
        this.historyData = queryData

        // 清空历史数据
        // 1.文本 2.下拉框 3.时间 4.数字 5.boolean
        this.correctTypeGroup = {
          1: [],
          2: [],
          3: [],
          4: [],
          5: []
        }
        this.customRules = {}

        // 输入框分组
        // 获取页面所有的配置字段,因为字段type存在页面配置中
        const pageFields = await this.getPageField(action)
        const loadFields = []
        actionConfigList.forEach(actionField => {
          const pageField = pageFields.filter(pageField => pageField.fieldId === actionField.fieldId)[0]
          if (pageField === undefined) {
            this.$message({
              message: 'get Page Config Field ERROR, FIELD NOT FIND, Place To Page Add ' + actionField.fieldId,
              type: 'warning'
            })
          }
          // 保存已加载的field,用户加载页面枚举资源
          loadFields.push(pageField)
          // 加入输入分组中, correctTypeGroup的数据会被循环渲染在页面上
          this.correctTypeGroup[pageField.type].push(actionField)
          // console.log(actionField)
          if (actionField.require === 1) {
            // 添加验证规则
            this.customRules[actionField.fieldAttributeName] = JSON.parse(pageField.rule)

            // 添加数据绑定对象
            this.queryData[actionField.fieldAttributeName] = ''
          }
        })

        // 加载页面的所有枚举值
        this.loadPageEnums(loadFields)

        // 拷贝属性
        copyProperties(queryData, this.queryData)
        copyProperties(history, this.queryData)

        // 处理时间格式
        this.correctTypeGroup[3].forEach(actionField => {
          const timeStramp = this.queryData[actionField.fieldAttributeName]
          if (!IsEmpty(timeStramp)) {
            this.queryData[actionField.fieldAttributeName] = formatDate(new Date(timeStramp), 'yyyy-MM-dd')
          }
        })

        // 校验参数
        return 'fail'
      }
      // 提交表单
      this.$emit('update', queryData, action.nativeId, null, true)
    },
    // 加载页面所有的枚举值
    loadPageEnums(pageFields) {
      // 找出枚举字段
      this.pageEnum = {}
      const enumFields = pageFields.filter(element => element.type === 2)
      if (enumFields === undefined || enumFields.length <= 0) {
        // 页面没有枚举值
        return
      }

      const fieldEnumIds = enumFields.map(field => field.fieldId)
      const queryData = {
        projectId: this.historyData.projectId,
        enumIds: fieldEnumIds
      }
      // 获取页面的枚举值,并绑定成{fieldAlias: []}数据格式
      this.loading = false
      getEnumsByProjectIdAndEnumsIds(queryData).then(response => {
        const pageEnums = response.data
        enumFields.forEach(field => {
          let options = []
          // 寻找当前字段的枚举值
          const fieldEnums = Object.keys(pageEnums).filter(enumId => field.fieldId === enumId)
          if (fieldEnums != null && fieldEnums.length > 0) {
            options = pageEnums[fieldEnums[0]]
          }
          // 需要调用vue的set方法,刷新视图,否则视图不会刷新
          this.$set(this.pageEnum, field.fieldId, options)
        })

        this.loading = false
      }).catch(() => {
        this.$message({
          message: 'getEnumsByProjectIdAndEnumsIds is failed.',
          type: 'warning'
        })
        this.loading = false
      })
    },
    // 获取所有页面的字段
    async getPageField() {
      try {
        const res = await searchField({})
        return res.data
      } catch (err) {
        this.$message({
          message: 'getActionConfigList is failed.',
          type: 'warning'
        })
      }
    },
    // 获取action的配置list
    async getActionConfigList(action) {
      try {
        const queryData = {
          actionId: action.nativeId
        }
        const res = await searchAction(queryData)
        return res.data
      } catch (err) {
        this.$message({
          message: 'getActionConfigList is failed.',
          type: 'warning'
        })
      }
    },
    // 验证表单的内容是否有填
    validate(actionConfigList, queryData) {
      return { success: actionConfigList.length < 1 }
    },
    // updateForm
    updateForm() {
      this.$forceUpdate()
    },
    // 提交
    save() {
      // this.$emit('update', this.queryData, this.queryData.nativeId, null, true)
      // 切换校验规则
      this.loading = true
      // 跳转路由, 去新增BUG的页面
      // this.$refs.form.validate(valid => {
      //   this.loading = false
      //   if (valid) {
      //     // 跳转路由, 去新增BUG的页面
      //     this.$emit('update', this.queryData, null, true)
      //   }
      // })
      // 刷新form表单,不知道为什么,表单校验内容一直加载不到
      this.refreshForm = false
      // 重新加载所有表单项
      setTimeout(() => {
        this.refreshForm = true
        this.rules = this.customRules
        // 数据要载入
        setTimeout(() => {
          this.$refs.form.validate(valid => {
            this.loading = false
            if (valid) {
              // 跳转路由, 去新增BUG的页面
              this.dialogFormVisible = false
              this.$emit('update', this.queryData, null, null, true)
            }
          })
        }, 2000)
      }, 500)
    }
  }
}

</script>
<style scoped>
</style>

效果预览
在这里插入图片描述

;