javascript
复制代码
//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>