1.公共表单组件
//commonForm.vue
<script>
import {TEXT,SELECT,PASSWORD,TEXTAREA,RADIO,DATE_PICKER
} from '@/conf/uiTypes'
import { deepClone } from '@/utils'
export default {name: 'GFormCreator',props: {config: { // title/itemstype: Object,required: true}},created() {const { items, cards, rules } = this.config;// 绑定表单验证器thisfor (let key in rules) {rules[key].forEach(r => {// 若该方法是全局方法,第二次bind会失效,因为bind只能绑定一次if (r.validator) {r.validator = r.validator.bind(this)}}) }if (cards) {cards.forEach(card => {this.reactiveFields(card.children);})} else if (items) {this.reactiveFields(items);}},data() {return {ruleForm: {}}},methods: {reactiveFields(items) {console.log(items);if (!items) return;items.forEach((row, rowIndex) => {row.forEach((item, colIndex) => {// this.ruleForm[item.key] = item.value;// Object.defineProperty 对所有key进行响应式,更改后更新// 无法检测到动态添加的key,访问、设置,set/get都无法触发响应式 if (this.ruleForm.hasOwnProperty(item.key)) {// 异常抛出,外部方法没有捕获的画,程序结束throw new Error(`行:${rowIndex + 1}_列${colIndex + 1}` + '已经存在相同的key:' + item.key + ',value:' + item.value)}this.$set(this.ruleForm, item.key, item.value);})});},renderItem(item) {const fd = this.ruleForm;const attrs = item.attrs;switch (item.type) {case TEXT:case PASSWORD:case TEXTAREA:// v-model = @input + :valuereturn <el-input attrs={attrs} v-model={fd[item.key]} type={item.type} ></el-input>case SELECT:return <el-select attrs={attrs} v-model={fd[item.key]}>{item.options.map(opt => {return <el-option value={opt.value} label={opt.label}></el-option>})}</el-select>case DATE_PICKER:return <el-date-pickerattrs={attrs}v-model={fd[item.key]}type="date"placeholder="选择日期"></el-date-picker>case RADIO: // { label:'xxx' radios:[ { attrs:{},label:'xxx' } ] }return item?.radios?.map(radio => {console.log('radio:', fd[item.key])return <el-radioattrs={radio.attrs}v-model={fd[item.key]} label={radio.label}>{radio.title}</el-radio>})default:return <h2>未匹配{item.type}</h2>}},renderColumns(columns) {return columns.map(col => {return <el-col span={col.colspan}><el-form-item label={col.label} prop={col.key}>{this.renderItem(col)}</el-form-item></el-col>})},renderRows(rows) {return rows.map(rowArr => {return <el-row>{this.renderColumns(rowArr)}</el-row>})},getData() {return deepClone(this.ruleForm)},passData() {// submitthis.$emit('submit', deepClone(this.ruleForm));},// 外部验证// 内部验证返回数据doSubmit() {this.$refs.form.validate(valid => {if (valid) {return this.$emit('submit', deepClone(this.ruleForm));} else {console.log('验证失败');return false;}})},valid(callback) {this.$refs.form.validate(valid => {if (valid) {return callback(deepClone(this.ruleForm))} else {callback(false);console.log('验证失败');return false;}});},reset() {this.$refs.form.resetFields();}, renderCards(cards) {let { renderRows } = this;return cards.map(card => {// 渲染name和children(renderRows) return (<el-card class="box-card" header={card.name}>{card.children && renderRows(card.children)}</el-card >)// 实现方式1// let h = this.$createElement;return h('el-card', { class: 'box-card' }, [h('template', { slot: 'header' }, [h('span', card.name)]),card.children && renderRows(card.children)]);// 实现方式2return (<el-card class="box-card"><template slot="header"><span>{card.name}</span></template>{card.children && renderRows(card.children)}</el-card >);})}},render() {const { title, items, rules, cards } = this.config;const { ruleForm, $scopedSlots: { btn } } = this;return (<div class="form-box">{title && <h2>{title}</h2>} <el-form ref="form" attrs={{ model: ruleForm, }} rules={rules} label-width="80px">{cards ? this.renderCards(cards) : this.renderRows(items)}</el-form> <div class="btn-bow">{btn ? btn({ t: '我是scopod' }) : (<div><el-button type="primary" onClick={e => this.doSubmit()}>提交</el-button><el-button onClick={e => this.reset()}>重置</el-button></div>)}</div> </div>)}
}
</script><style scoped>
.el-input,
.el-select,
.form-box .el-date-editor {width: 100%;
}:deep(.el-card__header) {text-align: left;
}.box-card {margin-bottom: 10px;
}
</style>
(1)声明表单类型
// conf/uniTypes.js
export const TEXT = 'text';
export const SELECT = 'select';
export const PASSWORD = 'password';
export const TEXTAREA = 'textarea';
export const RADIO = 'radio';
export const DATE_PICKER= 'datepicker';
(2)封装深拷贝
// utils/index.jsexport const deepClone = (obj)=>{ // 处理环形对象带来的递归栈内存溢出let cache = new WeakMap(); // 避免强引用var objType = '[object Object]';function innerDeepClone(obj) {// 处理了基本数据类型 undefined null functionif (typeof obj !== 'object' || !obj) {return obj;}// obj不是方法的参数, 改变的this,由于不同类型的对象的type不同,// toString从不同this拿到的就不一样// var type = Object.prototype.toString.call(obj);if (cache.has(obj)){return cache.get(obj);}let tmp;// 处理非对象和数组if (obj instanceof Map) {tmp = new Map();cache.set(obj,tmp);obj.forEach((val, key) => {tmp.set(innerDeepClone(key), innerDeepClone(val))})} else if (obj instanceof Set) {tmp = new Set();cache.set(obj,tmp);obj.forEach(val => {tmp.add(innerDeepClone(val))})} else if (obj instanceof RegExp || obj instanceof Date) {tmp = new obj.constructor(obj); cache.set(obj,tmp);} else {tmp = new obj.constructor();cache.set(obj,tmp);for (let key in obj) {tmp[key] = innerDeepClone(obj[key]);}}return tmp;}return innerDeepClone(obj)
}
2.使用公共表单组件
<template><div><GFormCreator :config="conf" @submit="createLoan"/><!-- <hr><GFormCreator ref="f2" :config="conf"><template #btn="{t}"><el-button @click="test1">提交{{ t }}</el-button></template></GFormCreator> --></div>
</template><script>
import conf from './loan-input-page'
import { createLoanApi } from '@/api/loan'
export default {methods: {async createLoan(user){let res = await createLoanApi(user);this.$notify.success('添加成功');},test2(data){console.log('数据:',data)},test1() {console.log(this.$refs.f2.getData())}},data() {return {conf}}
}
</script>
/loan-input-page.js
import {TEXT,SELECT,PASSWORD,TEXTAREA,RADIO,DATE_PICKER
} from '@/conf/uiTypes';//性别
export const sexOptions = [{ value: "man", label: "男" },{ value: "woman", label: "女" }
];
//行业
export const companyOptions = [{ value: "education", label: "教育" },{ value: "finance", label: "金融" }
];
//婚否
export const marriageOptions = [{ value: "married", label: "已婚" },{ value: "unmarried", label: "未婚" }
];
//学历
export const educationOptions = [{ value: "college", label: "大学" },{ value: "highschool", label: "高中" }
];// 优化 => data方法中,默认是会Object.defineProperty => 当触发属性的get/set => 页面的更新export default Object.freeze({cards: [{name: "个人基本信息",children: [[{ label: "姓名", key: "name", type: TEXT },{ label: "出生日期", key: "birthday", type: DATE_PICKER },{label: "性别",key: "sex",type: SELECT,options: sexOptions}],[{ label: "身份证", key: "identity_card", type: TEXT }],[{label: "婚姻状态",key: "marriage",type: "select",options: marriageOptions},{label: "教育程度",key: "education",type: "select",options: educationOptions},{ label: "居住地址", key: "address1", type: TEXT }],[{ label: "户籍地址", key: "address2", type: TEXT },{ label: "居住电话", key: "phone", type: TEXT },{ label: "手机号", key: "mobile_phone", type: TEXT }]].map(row => row.map(item => ({ colspan: 8, ...item })))},{name: "职业信息",children: [[ // element原生属性{ label: "现职公司", key: "company", type: TEXT },{label: "所属行业",attrs: { placeholder: '请选择Green' },key: "trade",type: "select",options: companyOptions},{ label: "职位", key: "position", type: TEXT },{ label: "公司地址", key: "address3", type: TEXT }].map(item => ({ colspan: 6, ...item })),[{ label: "公司类型", key: "company_type", type: TEXT },{ label: "公司邮箱", key: "company_email", type: TEXT },{ label: "公司电话", key: "company_phone", type: TEXT }].map(item => ({ colspan: 8, ...item }))]},{name: "收支情况",children: [[{ label: "收支情况", key: "income", type: TEXT, colspan: 12 }]]},{name: "家庭联系人",children: [[{ label: "关系1", key: "contact", type: TEXT },{ label: "姓名", key: "contact_name", type: TEXT },{ label: "手机", key: "contact_phone", type: TEXT }].map(item => ({ colspan: 12, ...item }))]},{name: "工作证明人",children: [[{ label: "关系2", key: "contact2", colspan: 12, type: TEXT },{ label: "姓名", key: "contact2_name", colspan: 12, type: TEXT },{ label: "手机", key: "contact2_phone", colspan: 12, type: TEXT }],[{ label: "部门", key: "contact2_dep", colspan: 12, type: TEXT },{ label: "职位", key: "contact2_pos", colspan: 12, type: TEXT }],[{ label: "备注", key: "remark", type: "textarea" }]]}],rules: {name: [{ required: true, message: "请输入姓名", trigger: "blur" },{min: 2,max: 5,message: "长度在 2 到 5 个字符",trigger: "blur"}],identity_card: [{ required: true, message: "请输入身份证", trigger: "change" }],birthday: [{type: 'date',required: true,message: "请选择日期",trigger: "change"}],sex: [{ required: true, message: "请选择性别", trigger: "change" }],marriage: [{ required: true, message: "请选择婚姻状态", trigger: "change" }],education: [{ required: true, message: "请选择教育程度", trigger: "change" }],trade: [{ required: true, message: "请选择所属行业", trigger: "change" }],address1: [{ required: true, message: "请输入居住地址", trigger: "blur" }],address2: [{ required: true, message: "请输入户籍地址", trigger: "blur" }],phone: [{ required: true, message: "请输入居住电话", trigger: "blur" }],mobile_phone: [{ required: true, message: "请输入手机号", trigger: "blur" }],company: [{ required: true, message: "请输入现职公司全称", trigger: "blur" }],position: [{ required: true, message: "请输入职位", trigger: "blur" }],address3: [{ required: true, message: "请输入公司地址", trigger: "blur" }],company_type: [{ required: true, message: "请输入公司类型", trigger: "blur" }],company_email: [{ required: true, message: "请输入公司邮箱", trigger: "blur" }],company_phone: [{ required: true, message: "请输入公司电话", trigger: "blur" }],income: [{ required: true, message: "请输入收支情况", trigger: "blur" }],contact: [{ required: true, message: "请输入关系1", trigger: "blur" }],contact_name: [{ required: true, message: "请输入姓名", trigger: "blur" }],contact_phone: [{ required: true, message: "请输入手机", trigger: "blur" }],contact2: [{ required: true, message: "请输入关系2", trigger: "blur" }],contact2_name: [{ required: true, message: "请输入姓名", trigger: "blur" }],contact2_phone: [{ required: true, message: "请输入手机", trigger: "blur" }],contact2_dep: [{ required: true, message: "请输入部门", trigger: "blur" }],contact2_pos: [{ required: true, message: "请输入职位", trigger: "blur" }]},
}
)