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/items
type: Object,
required: true
}
},
created() {
const { items, cards, rules } = this.config;
// 绑定表单验证器this
for (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 + :value
return <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-picker
attrs={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-radio
attrs={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() {
// submit
this.$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)
]);
// 实现方式2
return (
<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.js
export const deepClone = (obj)=>{
// 处理环形对象带来的递归栈内存溢出
let cache = new WeakMap(); // 避免强引用
var objType = '[object Object]';
function innerDeepClone(obj) {
// 处理了基本数据类型 undefined null function
if (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" }
]
},
}
)