Vue3 Element Schema Form 配置式生成表单的实现 (woodenF原作者)

843 阅读1分钟

代码基于(woodenf)作者进行vue3实现

Component

<template>
  <div class="SchemaForm">
    <el-form
      ref="SchemaForm"
      v-bind="$attrs"
      :model="source"
      :class="className"
      :rules="rules"
      label-width="auto"
    >
      <el-row>
        <el-col
          v-for="item in config"
          :key="item.key"
          :span="item.hidden ? 0 : item.span || 15"
        >
          <el-form-item
            v-if="!item.hidden"
            :label="item.label"
            :prop="item.key"
            :labelWidth="item.labelWidth || 'auto'"
            v-bind="item.itemProps"
          >
            <!-- <slot v-if="item.component === 'slot'" :name="item.slotName"></slot> -->
            <div
              v-if="item.component === '-'"
              :class="item.innerClass"
              :style="item.style"
            >
              {{ item.innerText || source[item.key] }}
            </div>
            <template v-else>
              <div class="nx-flex-aling-center">
                <component
                  :is="item.component"
                  //这一段报错Unexpected mutation of "source" prop
                  //楼主是直接关闭了eslint检测的
                  //也可以通过设置一个变量保存父组件props传过来的值,或者通过计算属性返回
                  v-model="source[item.key]"
                  v-bind="item.props"
                >
                  <template v-if="item.component === 'el-select'">
                    <el-option
                      v-for="(option, optionIndex) in item.data"
                      :key="optionIndex"
                      :label="option[item.option[0]]"
                      :value="option[item.option[1]]"
                    ></el-option>
                  </template>

                  <template v-if="item.component === 'el-radio-group'">
                    <el-radio
                      v-for="(option, optionIndex) in item.data"
                      :key="optionIndex"
                      :label="optionIndex.toString()"
                      >{{ option }}</el-radio
                    >
                  </template>
                </component>
                <!-- <div v-if="item.after" class="nx-form_after">
                  {{ item.after }}
                </div> -->
              </div>
              <!-- <div
                v-if="item.tips"
                class="nx-form_tips"
                v-html="item.tips"
              ></div> -->
            </template>
          </el-form-item>
        </el-col>
      </el-row>
      <slot name="submit"></slot>
    </el-form>
  </div>
</template>

<script>
import { ref, onBeforeUnmount } from 'vue'
import { ElInput, ElSelect, ElRadioGroup } from 'element-plus'
import bus from '@/libs/but'

export default {
  props: {
    config: {
      type: Array,
      default: () => []
    },

    source: {
      type: Object,
      default: () => ({})
    },
    className: {
      type: String,
      default: ''
    },
    rules: {
      type: Object,
      default: () => ({})
    }
  },
  components: {
    ElInput, ElSelect, ElRadioGroup
  },
  setup () {

    const SchemaForm = ref(null)
    function resetFields () {
      SchemaForm.value.resetFields();
    }
    // 清除表单
    const clearValidate = () => {
      SchemaForm.value.clearValidate();
    }
    //    启用监听
    bus.on('resetFields', resetFields);

    // 在组件卸载之前移除监听
    onBeforeUnmount(() => {
      bus.off('resetFields', resetFields);
    })
    async function validate () {
      const valid = await SchemaForm.value.validate();
      return valid;
    }
    return {
      resetFields,
      clearValidate,
      validate,
      SchemaForm


    }
  }
}
</script>

父组件

这里代码比较多就不直接粘贴上来了

<template>
       <schema-form
          ref="addForm"
          :source="form"
          :config="searchConfig"
          :className="'addDialog'"
          :rules="rules"
        >
          <template #submit>
            <div class="save">
              <el-button
                type="primary"
                @click="submitForm"
                :loading="btn_lodding"
                >保存</el-button
              >
            </div>
          </template>
        </schema-form>
        </template>
        <script setup>
            const searchConfig = computed(() => {
  return [
    {
      key: 'username',
      component: 'el-input',
      value: form.username,
      span: 20,
      label: '用户名称:',
      props: {
        placeholder: '请输入用户名称',
      }
    },
    {
      key: 'phone',
      component: 'el-input',
      span: 20,
      label: '手机号码:',
      props: {
        placeholder: '请输入手机号码',
      }
    },
    {
      key: 'password',
      component: 'el-input',
      span: 20,
      label: '输入密码:',
      props: {
        placeholder: '请输入密码',
        'show-password': true
      }
    },
    {
      key: 'password_s',
      component: 'el-input',
      span: 20,
      label: '确认密码:',
      props: {
        placeholder: '请确认密码',
        'show-password': true
      }
    },

    {
      key: 'roleNames',
      component: 'el-select',
      span: 20,
      label: '所属角色:',
      data: form.select.name,
      option: ['name', 'name'],

      props: {}
    },

    {
      key: 'state',
      component: 'el-radio-group',
      span: 20,
      label: '启用状态:',
      data: ['启用', '停用'],
      props: {}
    },
    {
      hidden: false,
      span: 20,

    }

  ]
})
        </script>

新增了一个rulse属性,这个是我自己封装的表单验证,不过个人感觉较为冗余,后续会继续优化

我在main.js直接使用app.config.globalProperties.$rules=rulesFn进行全局复用

let rules = reactive({
  username: [
    {
      required: true,
      validator: proxy.$rules.FormValidate.Form(form, '请输入用户名').userVerify,
      trigger: 'blur',
    },
  ],
  phone: [
    {
      required: true,
      validator: proxy.$rules.FormValidate.Form().phoneVerify,
      trigger: 'blur',
    },
  ],
  password: [
    {
      required: true,
      validator: proxy.$rules.FormValidate.Form().passwordVerify,
      trigger: 'blur',
    },
  ],
  password_s: [
    {
      required: true,
      validator: proxy.$rules.FormValidate.Form(form).password_sVerify,
      trigger: 'blur',
    },
  ],
  roleNames: [
    {
      required: true,
      validator: proxy.$rules.FormValidate.Form(form, '请选择角色').userVerify,
      trigger: 'blur',
    },
  ]
})

rulesFn.js


 const FormValidate = (function () {
    function FormValidate () { }
    FormValidate.Form = function (form,text) {
        return {
            // 表单验证 不能为空
            userVerify (rule, value, callback) {
                if (!value) {
                    return callback(new Error(text))
                }
                return callback()
            },
            // 表单验证下拉框布尔值
            nameVerify(rule,value,callback){
                if (value===null) {

                    return callback(new Error(text))
                }
                return callback()
            },
            // 表单验证 电话
            phoneVerify (rule, value, callback) {
                if (!value) {
                    return callback(new Error('请输入手机号'))
                } else if (!/^[1][3,4,5,6,7,8,9][0-9]{9}$/.test(value)) {
                    return callback(new Error('手机号格式错误'))
                }
                return callback()
            },
            // 表单验证 密码
            passwordVerify (rule, value, callback) {
                if (!value) {
                    return callback(new Error('请输入密码'))
                } else if (!/^\w{6,24}$/.test(value)) {
                    return callback(new Error('密码是6-24个字符'))
                }
                return callback()
            },
            // 确认密码
            password_sVerify (rule, value, callback) {
                if (!value) {
                    return callback(new Error('请输入密码'))
                } else if (value != form.password) {
                    return callback(new Error('两次密码不一致'))
                }
                return callback()
            },
            // 表单验证
                // 地址验证
             urlVerify (rule, value, callback){
                if (!value) {
                return callback(new Error('不能为空'))
                } else if (!/^[A-Za-z]+$/.test(value)) {
                return callback(new Error('只能输入英文字符'))
                }
                return callback()
            },
            // 商品模块验证
            // 表单验证 账号
            userChineVerify (rule, value, callback) {
                if (!value) {
                    return callback(new Error('不能为空'))
                }else  if (!/^[\u4e00-\u9fa5]{2,8}$/.test(value)) {
                    return callback(new Error('请输入28位中文字符'))
                }
                callback()
            },
            // 表单验证 价格
            priceVerify (rule, value, callback) {
                if (!value) {
                    return callback(new Error('请输入价格'))
                } else if (!/^[0-9]*$/.test(value)) {
                    return callback(new Error('请输入数字'))
                }
                callback()
            },
             // 表单验证 价格
             priceVerifys (rule, value, callback) {
                if (!value) {
                    return callback(new Error('请输入价格'))
                } else if (!/^(([1-9]{1}\d*)|(0{1}))(\.\d{1,2})?$/.test(value)) {
                    return callback(new Error('请输入数字,只能2位小数'))
                }
                callback()
            },
             // 表单验证 合同金额
             contractPriceVerify (rule, value, callback) {
                if (!value) {
                    return callback(new Error('请输入价格'))
                } else if (!/^(([1-9]{1}\d*)|(0{1}))(\.\d{1,6})?$/.test(value)) {
                    return callback(new Error('请输入数字,合同金额只能保留6位小数'))
                }
                callback()
            },
             // 表单验证 价格
             amountVerify (rule, value, callback) {
                if (!value) {
                    return callback(new Error('请输入数量'))
                } else if (!/^[1-9]{1}\d*$/.test(value)) {
                    return callback(new Error('请输入整数'))
                }
                callback()
            },
            // 主图验证
            imgUrlVerify (rule, value, callback) {
                if (Boolean(form.value[text])===false) {
                    return callback(new Error('请选择图片'))
                }
                callback()
            },
            // 详情图
            imgDetailerify (rule, value, callback) {
                if (form.value[text].length===0) {
                    return callback(new Error('请选择图片'))
                }
                callback()
            },
            // 官方的联系电话
            phoneOffVerify (rule, value, callback) {
                if (form.value.official===false) {
                    if (!value) {
                        return callback(new Error('请输入手机号'))
                    } else if (!/^[1][3,4,5,6,7,8,9][0-9]{9}$/.test(value)) {
                        return callback(new Error('手机号格式错误'))
                    }
                    callback()
                }
            },
            // 企业档案图片
            detailImgVerify(rule, value, callback) {
                if (!form.value[text]) {
                    return callback(new Error('图片上传不能为空'))
                    } 
                    callback()
                
            },
            // 上传图片
            addImgVerify(rule, value, callback) {

                if (!form[text]) {
                    return callback(new Error('图片上传不能为空'))
                    } 
                    callback()
            },

            imgVerify (rule, value, callback) {
                if (form[text].length===0) {
                    return callback(new Error('请选择图片'))
                }
                callback()
            },
            // 企业电话验证
            detailPhoneVerify(rule,value,callback){
                if(!/^((\d{11})|^((\d{7,8})|(\d{4}|\d{3})-(\d{7,8})|(\d{4}|\d{3})-(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1})|(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1}))$)$/.test(value)){
                    return callback(new Error('手机号格式错误'))
                }
                callback()
            }
        }
    }

    return FormValidate
}())

exports.FormValidate = FormValidate