问题形容
集体愚见编写代码其实就是:
- 学习规定(看官网文档)
- 应用规定(在应用的过程中进一步了解官网文档)
- 最终基于原有底层官网文档规定再自定义新规定(封装新的规定,便于复用)
所以本文讲述一下基于原有的el-form的规定,进行二次封装自定义新的规定的思路,以及附上能间接用的代码。咱们先看一下效果图:
效果图
思路剖析
最终成果是配置化“写代码”,就像echarts一样,写不同的配置,呈现不同的成果,天然是配置,所以就要提前思考好有哪些须要配置。当然也要思考数据的回显。
- 配置表单项类型(组件中要加上校验规定)
- 配置表单项的名字
- 配置表单项的字段
- 配置表单项是否必填
- 配置输入框的单位(如果有的话)
- 配置placeholder的文本提醒
- 配置下拉框选项数据数据(如果是固定的下拉框能够传过来)
- 如果是枚举值类型的下拉框就须要发申请获取下拉框选项数组数据
等...
这里要多提一下表单项类型
配置表单项~输入框的类型
首先咱们要分明form表单项的类型,这里为了便于了解,只举例三种大类型,当然大类型中也蕴含小类型,同时也要做校验。至于别的类型,大家了解了这几个类型当前,就能够本人写了。
输入框类型
- 文本输入框类型(校验得填写,不能为空)
- 数字输入框类型(校验输出的数字类型,比方须要正整数、须要保留两位小数等)
下拉框类型
- 固定选项的下拉框类型(这里间接写死,传过来即可,比方性别下拉框,只有男女两种类型选项)
- 枚举多个选项的单选下拉框类型(须要提前发申请获取数据,或者visible-change事件发申请获取)
- 枚举多个选项的单选多选下拉框类型(同上)
- 工夫选择器范畴类型
留神绑定的后果值是数组即可
最初不要忘了回显逻辑哦
el-form表头数据举例
子组件表单数据依据依据父组件传递过去的formHeader动静渲染。即v-for中搭配v-if去出现,先简略看一下formHeader数据结构,具体在后边代码中都有的
// 表头数组数据 formHeader: [ { itemType: "text", // 输入框类型 labelName: "姓名", // 输入框名字 propName: "name", // 输入框字段名 isRequired: true, // 是否必填 placeholder: "请填写名字", // 输入框placeholder提醒语加上,可用于告知用户规定 }, { itemType: "number", labelName: "年龄", propName: "age", isRequired: true, unit: "year", // 数字类型的要有单位 placeholder: "请输出年龄(大于0的正整数)", }, { itemType: "selectOne", // 下拉框类型一,固定的选项能够写死在配置里,比方性别只有男女 labelName: "性别", propName: "gender", isRequired: true, placeholder: "请抉择性别", optionsArr: [ { label: "男", value: 1, }, { label: "女", value: 2, }, ], }, ],
残缺代码
倡议复制粘贴,运行跑起来,这样成果更加显著,更便于了解。
毕竟:no words,show codes
父组件传递配置数据
<template> <div class="myWrap"> <h2>填写表单</h2> <br /> <my-form ref="myForm" :formHeader="formHeader" @submitForm="submitForm" @resetForm="resetForm" ></my-form> <h2>表单数据回显</h2> <el-button size="small" type="primary" @click="showData" >点击按钮回显数据</el-button > </div></template><script>import myForm from "./myForm.vue";export default { components: { myForm, }, data() { return { // 表头数组数据 formHeader: [ /** * 输入框类型3种 * 1. 一般文本输入框 text * 2. 数字类型输入框 number * 3. 文本域输入框 textarea * * 下拉框select类型2中 * 1. 固定配置的el-option selectOne * 2. 枚举值的el-option单选 selectTwo * 2. 枚举值的el-option多选 selectThree * * 工夫选择器类型1种 * 1. 两个工夫选择器、选取一个范畴 * * 等等,还有其余类型,这里举三种类型,别的类型仿照着即可写进去 * 组件封装适可而止。如果是比较复杂(奇葩)的须要联动的表单,倡议一个个写 * 毕竟适度的封装,会导致代码不好保护(集体愚见) * * */ { itemType: "text", // 输入框类型 labelName: "姓名", // 输入框名字 propName: "name", // 输入框字段名 isRequired: true, // 是否必填 placeholder: "请填写名字", // 输入框placeholder提醒语加上,可用于告知用户规定 }, { itemType: "number", labelName: "年龄", propName: "age", isRequired: true, unit: "year", // 数字类型的要有单位 placeholder: "请输出年龄(大于0的正整数)", }, { itemType: "number", labelName: "工资", propName: "salary", isRequired: true, unit: "元/月", // 数字类型的要有单位 placeholder: "请输出每月工资金额(大于0且保留两位小数)", }, { itemType: "textarea", labelName: "备注", propName: "remark", isRequired: true, placeholder: "请填写备注", }, { itemType: "selectOne", // 下拉框类型一,固定的选项能够写死在配置里,比方性别只有男女 labelName: "性别", propName: "gender", isRequired: true, placeholder: "请抉择性别", optionsArr: [ { label: "男", value: 1, }, { label: "女", value: 2, }, ], }, { itemType: "selectTwo", // 下拉框类型二,枚举值单选,在点击下拉选项时依据枚举id发申请,获取枚举值 labelName: "可选职业", propName: "job", isRequired: true, placeholder: "请抉择职业", enumerationId: "123123123", }, { itemType: "selectTwo", // 下拉框类型二,枚举值单选,在点击下拉选项时依据枚举id发申请,获取枚举值 labelName: "欲望", propName: "wish", isRequired: true, placeholder: "请抉择欲望", enumerationId: "456456456", }, { itemType: "selectThree", // 下拉框类型三,枚举值多选,在点击下拉选项时依据枚举id发申请,获取枚举值 labelName: "喜好", propName: "hobby", isRequired: true, placeholder: "请抉择喜好", enumerationId: "789789789", }, { itemType: "selectThree", // 下拉框类型三,枚举值多选,在点击下拉选项时依据枚举id发申请,获取枚举值 labelName: "想买手机", propName: "wantPhone", isRequired: true, placeholder: "请抉择手机", enumerationId: "147258369", }, { itemType: "dateRange", // 日期范畴类型 labelName: "日期", propName: "date", isRequired: true, }, ], }; }, mounted() { // 数据回显的时候,要先发申请获取枚举值下拉框的值才可能正确的回显,所以 // 就提前发申请获取对应下拉框的值了,这里要留神!留神!留神! this.formHeader.forEach((item) => { if ((item.itemType == "selectTwo") | (item.itemType == "selectThree")) { this.$refs.myForm.getOptionsArrData(item); } }); }, methods: { showData() { let apiData = { name: "孙悟空", age: 500, salary: 6666.66, remark: "齐天大圣是也", gender: 1, // 1代表男 job: 1, // 1医生 2老师 3公务员 wish: 3, // 1成为百万富翁 2长生不老 3家人衰弱幸福安全 hobby: [1, 2, 3], // 1乒乓球 2羽毛球 3篮球 wantPhone: [1, 2, 4], // 1华为 2小米 3苹果 4三星 date: ["2018-06-06", "2022-05-05"], }; setTimeout(() => { this.$refs.myForm.form = apiData; }, 300); }, submitForm(form) { console.log("表单提交喽", form); }, resetForm() { console.log("表单重置喽"); }, },};</script><style lang='less' scoped>.myWrap { width: 100%; height: 100%; box-sizing: border-box; padding: 25px; overflow-y: auto;}</style>
封装的子组件依据传递的配置数据动静渲染
<template> <div class="formWrap"> <el-form ref="form" label-position="top" :model="form" label-width="80px"> <template v-for="(item, index) in formHeader"> <!-- 当类型为一般文本输入框时 --> <el-form-item v-if="item.itemType == 'text'" :key="index" :label="item.labelName" :prop="item.propName" :rules=" item.isRequired ? [ { required: true, // 是否必填 是 trigger: 'blur', // 触发形式,失去焦点 itemType: 'text', // 以后类型,文字输入框 labelName: item.labelName, // 以后输入框的名字 value: form[item.propName], // 输入框输出的绑定的值 validator: validateEveryData, // 校验规定函数 }, ] : [] " > <el-input :placeholder="item.placeholder" v-model.trim="form[item.propName]" clearable size="small" ></el-input> </el-form-item> <!-- 当类型为数字类型输入框时 --> <el-form-item v-if="item.itemType == 'number'" :key="index" :label="item.labelName" :prop="item.propName" :rules=" item.isRequired ? [ { required: true, // 是否必填 是 trigger: 'blur', // 触发形式,失去焦点 itemType: 'number', // 以后类型,文字输入框 labelName: item.labelName, // 以后输入框的名字 value: form[item.propName], // 输入框输出的绑定的值 validator: validateEveryData, // 校验规定函数 }, ] : [] " > <el-input :placeholder="item.placeholder" v-model.trim="form[item.propName]" @change="checkInput(item)" clearable size="small" > <span slot="suffix">{{ item.unit }}</span> </el-input> </el-form-item> <!-- 当类型为文本域输入框时 --> <el-form-item v-if="item.itemType == 'textarea'" :key="index" :label="item.labelName" :prop="item.propName" :rules=" item.isRequired ? [ { required: true, // 是否必填 是 trigger: 'blur', // 触发形式,失去焦点 itemType: 'textarea', // 以后类型,文本域输入框 labelName: item.labelName, // 以后输入框的名字 value: form[item.propName], // 输入框输出的绑定的值 validator: validateEveryData, // 校验规定函数 }, ] : [] " > <el-input type="textarea" :placeholder="item.placeholder" v-model.trim="form[item.propName]" clearable size="small" ></el-input> </el-form-item> <!-- 当类型为下拉框一时,固定下拉选项 --> <el-form-item v-if="item.itemType == 'selectOne'" :key="index" :label="item.labelName" :prop="item.propName" :rules=" item.isRequired ? [ { required: true, // 是否必填 是 trigger: '', // blur 或 change 这里就不指定触发形式了,保留提交时再校验 itemType: 'selectOne', // 以后类型,固定下拉框类型 labelName: item.labelName, // 以后输入框的名字 value: form[item.propName], // 输入框输出的绑定的值 validator: validateEveryData, // 校验规定函数 }, ] : [] " > <el-select v-model="form[item.propName]" :placeholder="item.placeholder" clearable size="small" > <el-option v-for="(ite, ind) in item.optionsArr" :key="ind" :label="ite.label" :value="ite.value" ></el-option> </el-select> </el-form-item> <!-- 当类型为下拉框二时,属于枚举值(单选)下拉框,须要依据枚举id发申请获取枚举值 --> <el-form-item v-if="item.itemType == 'selectTwo'" :key="index" :label="item.labelName" :prop="item.propName" :rules=" item.isRequired ? [ { required: true, // 是否必填 是 trigger: '', // blur 或 change 这里就不指定触发形式了,保留提交时再校验 itemType: 'selectTwo', // 以后类型,枚举值单选 labelName: item.labelName, // 以后输入框的名字 value: form[item.propName], // 输入框输出的绑定的值 validator: validateEveryData, // 校验规定函数 }, ] : [] " > <el-select v-model="form[item.propName]" :placeholder="item.placeholder" clearable @visible-change=" (flag) => { getOptionsArr(flag, item); } " :loading="loadingSelect" size="small" > <el-option v-for="(ite, ind) in selectTwoOptionsObj[item.propName]" :key="ind" :label="ite.label" :value="ite.value" ></el-option> </el-select> </el-form-item> <!-- 当类型为下拉框三时,属于枚举值(多选)下拉框,须要依据枚举id发申请获取枚举值 --> <el-form-item v-if="item.itemType == 'selectThree'" :key="index" :label="item.labelName" :prop="item.propName" :rules=" item.isRequired ? [ { required: true, // 是否必填 是 trigger: 'blur', // 这里用blur,避免首次默认校验触发 itemType: 'selectThree', // 以后类型,枚举值多选 labelName: item.labelName, // 以后输入框的名字 value: form[item.propName], // 输入框输出的绑定的值 validator: validateEveryData, // 校验规定函数 type: 'number', }, ] : [] " > <el-select v-model="form[item.propName]" :placeholder="item.placeholder" clearable @visible-change=" (flag) => { getOptionsArr(flag, item); } " :loading="loadingSelect" multiple collapse-tags size="small" > <el-option v-for="(ite, ind) in selectTwoOptionsObj[item.propName]" :key="ind" :label="ite.label" :value="ite.value" ></el-option> </el-select> </el-form-item> <!-- 当类型为日期范畴 --> <el-form-item v-if="item.itemType == 'dateRange'" :key="index" :label="item.labelName" :prop="item.propName" :rules=" item.isRequired ? [ { required: true, // 是否必填 是 trigger: '', itemType: 'dateRange', // 以后类型,枚举值多选 labelName: item.labelName, // 以后输入框的名字 value: form[item.propName], // 输入框输出的绑定的值 validator: validateEveryData, // 校验规定函数 }, ] : [] " > <el-date-picker v-model="form[item.propName]" format="yyyy-MM-dd" value-format="yyyy-MM-dd" clearable type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="完结日期" size="small" > </el-date-picker> </el-form-item> </template> </el-form> <!-- 提交表单和重置表单局部 --> <div class="btns"> <el-button type="primary" @click="submitForm" size="small" >保留</el-button > <el-button @click="resetForm" size="small">重置</el-button> </div> </div></template><script>export default { props: { // 父组件传递过去的表头的数据 formHeader: { type: Array, default: () => { return []; }, }, }, data() { var validateEveryData = (rule, value, callback) => { // console.log("callback", callback); // console.log("校验某一项的规定对象", rule); // console.log("用户输出的值", value); // 对输入框类型的校验 if (value) { if ((value + "").length > 0) { // 用于回显时候的校验,因为输出的时候是字符串类型的数字,然而回显的时候可能就是数字 callback(); // cb函数告知校验后果,必须要加 return; } } // 对下拉框类型的校验 if ( (rule.itemType == "selectOne") | (rule.itemType == "selectTwo") | (rule.itemType == "selectThree") ) { if (value) { if ((value + "").length > 0) { // 留神枚举值是数字类型的,所以这里要转换成为字符串类型的 callback(); return; } } } // 依据不同的类型给予不同的校验提醒 switch (rule.itemType) { case "text": callback(new Error(rule.labelName + "不能为空")); // 文本类型的规定简略,就是得填写 break; case "number": callback(new Error(rule.labelName + "请按规定填写")); // 数字类型的规定比拟繁多 break; case "textarea": callback(new Error(rule.labelName + "不能为空")); // 文本域类型的规定也简略,就是得填写 break; case "selectOne": callback(new Error("请抉择" + rule.labelName)); // 下拉框类型一 得填写 break; case "selectTwo": callback(new Error("请抉择" + rule.labelName)); // 下拉框类型二 得填写 break; case "selectThree": callback(new Error("请抉择" + rule.labelName)); // 下拉框类型三 多选数组得填写 break; case "dateRange": callback(new Error("请抉择" + rule.labelName + "范畴")); // 下拉框类型三 多选数组得填写 break; default: break; } }; return { // 此对象用于存储各个下拉框的数组数据值,其实也能够挂在vue的原型上,不过集体认为写在data中好些 selectTwoOptionsObj: {}, // 用于下拉框加载时的成果 loadingSelect: false, // 绑定的数据 form: {}, // 校验规定 validateEveryData: validateEveryData, }; }, methods: { // 获取下拉框数据 async getOptionsArr(flag, item) { // console.log(flag, item); // 为true时示意开展,这里模仿依据枚举值id发申请,获取下拉框的值的 if (flag) { this.loadingSelect = true; // 应用了加载中成果,最好加上一个try catch捕捉异样 // let result = await this.$api.getEnumList({id:item.enumerationId}) this.getOptionsArrData(item); } else { // 解决多选下拉框失去焦点校验规定依然存在问题 if (item.itemType == "selectThree") { // console.log("敞开时校验多选值", this.form[item.propName]); if (this.form[item.propName].length > 0) { // 如果至多抉择一个了,阐明符合要求,就再校验一次,这样校验规定就去掉了 this.$refs.form.validateField(item.propName); } } } }, getOptionsArrData(item) { setTimeout(() => { this.loadingSelect = false; if (item.enumerationId == "123123123") { this.selectTwoOptionsObj[item.propName] = [ { label: "医生", value: 1, }, { label: "老师", value: 2, }, { label: "公务员", value: 3, }, ]; } if (item.enumerationId == "456456456") { this.selectTwoOptionsObj[item.propName] = [ { label: "成为百万富翁", value: 1, }, { label: "长生不老", value: 2, }, { label: "家人衰弱幸福安全", value: 3, }, ]; } if (item.enumerationId == "789789789") { this.selectTwoOptionsObj[item.propName] = [ { label: "乒乓球", value: 1, }, { label: "羽毛球", value: 2, }, { label: "篮球", value: 3, }, ]; } if (item.enumerationId == "147258369") { this.selectTwoOptionsObj[item.propName] = [ { label: "华为", value: 1, }, { label: "小米", value: 2, }, { label: "苹果", value: 3, }, { label: "三星", value: 4, }, ]; } this.$forceUpdate(); // 这里须要强制更新一下,否则渲染不进去下拉框选项 }, 300); }, // 数字类型加校验规定 checkInput(item) { console.log("数字类型的再细分规定,能够依据item.labelName再写判断", item); if (item.labelName == "年龄") { let reg = /^[1-9]\d*$/; if (reg.test(this.form[item.propName] * 1)) { // console.log("符合要求,年龄大于0的正整数"); } else { this.form[item.propName] = null; } } if (item.labelName == "工资") { let reg = /^((0{1}\.\d{1,2})|([1-9]\d*\.{1}\d{1,2})|([1-9]+\d*))$/; if (reg.test(this.form[item.propName] * 1)) { // console.log("符合要求,工资保留两位小数"); this.form[item.propName] = (this.form[item.propName] * 1).toFixed(2); } else { this.form[item.propName] = null; } } if ("某个数字类型字段值") { // 加对应规定 } }, // 保留提交表单 async submitForm() { this.$refs.form.validate((valid) => { if (valid) { this.$emit("submitForm", this.form); } else { console.log("error submit!!"); return false; } }); }, // 重置表单 resetForm() { this.$refs.form.resetFields(); this.form = {}; // 这里重置完了当前,要从新初始化数据,否则会呈现输出不下来的问题 this.$emit("resetForm"); }, },};</script><style lang='less' scoped>.formWrap { width: 100%; /deep/ .el-form { display: flex; flex-wrap: wrap; justify-content: space-between; .el-form-item { width: 47%; margin-bottom: 12px !important; .el-form-item__label { padding: 0 !important; line-height: 24px !important; } .el-form-item__content { // 给文本域类型定高度 .el-textarea { textarea { height: 75px !important; } } // 给下拉框指定宽度百分比 .el-select { width: 100% !important; } // 工夫选择器指定宽度百分比 .el-date-editor { width: 100% !important; .el-range-separator { width: 10% !important; } } .el-form-item__error { padding-top: 1px !important; } } } } .btns { width: 100%; text-align: center; margin-top: 12px; }}</style>
好忘性不如烂笔头,记录一下吧。欢送批评指正 ^_^