写了个动态表单的组件
仅可参考设计模式!!!!!不要瞎复制
这个因为后端不是本人写的,再加上需要对之前的数据格式做兼容,导致某些地方不尽人意,比如单选多选按照@符号拆分,而且只能存选项的文字(多选项明明可以直接存数组)
另外附上后台管理的相关代码
组件代码
<template>
<div class="customForm">
<el-form ref="customForm" :model="form" :rules="rules" :inline="inline" :label-position="labelPosition"
:label-width="labelWidth" :label-suffix="labelSuffix" :hide-required-asterisk="hideRequiredAsterisk"
:show-message="showMessage" :inline-message="inlineMessage" :status-icon="statusIcon"
:validate-on-rule-change="validateOnRuleChange" :size="size" :disabled="disabled">
<template v-for="(item,i) in formData">
<el-form-item :label="item.showName" :prop="item.name">
<!-- 类型 1文本框、、6邮箱输入、7手机号输入、14验证码、15身份证号 -->
<el-input v-if="[1, 6, 7, 14, 15].includes(item.ufType)" v-model="form[item.name]"
:placeholder="rules[item.name][0].message" :maxlength="item.maxInput"></el-input>
<!-- 16数值输入 -->
<el-input-number v-if="item.ufType == 16" v-model.number="form[item.name]"
:placeholder="rules[item.name][0].message" :maxlength="item.maxInput"></el-input-number>
<!-- 17文本域 -->
<el-input v-if="item.ufType == 17" v-model="form[item.name]"
:placeholder="rules[item.name][0].message" :maxlength="item.maxInput" type="textarea"
:autosize="{ minRows: 2, maxRows: 4}">
</el-input>
<!-- 2下拉列表、3单选框、4多选框、5时间框 、10市下拉选、11区下拉选、 -->
<el-select v-if="[2, 3 ,4].includes(item.ufType)" v-model="form[item.name]"
:placeholder="rules[item.name][0].message" :multiple="4 == item.ufType">
<el-option :value="item" :label="item" v-for="(item,i) in options[`${item.name}Opt`]" :key="i">
</el-option>
</el-select>
<el-date-picker v-if="5 == item.ufType"
v-model="form[item.name]"
type="datetime"
:placeholder="rules[item.name][0].message"
align="right"
:format="item.defaultValue"
:value-format="item.defaultValue"
:picker-options="pickerOptions">
</el-date-picker>
<!-- 8国家下拉选 -->
<el-select v-if="8 == item.ufType" v-model="form[item.name]"
:placeholder="rules[item.name][0].message">
<el-option :label="item.nameCn + item.nameEn" :value="item.id"
v-for="(item,i) in options[`${item.name}Opt`]" :key="i">
<span style="float: left">{{ item.nameCn }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.nameEn }}</span>
</el-option>
</el-select>
<!-- 9省下拉选 -->
<el-select v-if="9 == item.ufType" v-model="form[item.name]"
:placeholder="rules[item.name][0].message"
@change="changeProv($event,options[`${item.name}Opt`])">
<el-option :value="item.label" :label="item.label"
v-for="(item,i) in options[`${item.name}Opt`]" :key="i">
</el-option>
</el-select>
<!-- 10市下拉选 -->
<el-select v-if="10 == item.ufType" v-model="form[item.name]"
:placeholder="rules[item.name][0].message"
@change="changeCity($event,options[`${item.name}Opt`])">
<el-option :value="item.label" :label="item.label"
v-for="(item,i) in options[`${item.name}Opt`]" :key="i">
</el-option>
</el-select>
<!-- 11市下拉选 -->
<el-select v-if="11 == item.ufType" v-model="form[item.name]"
:placeholder="rules[item.name][0].message">
<el-option :value="item.label" :label="item.label"
v-for="(item,i) in options[`${item.name}Opt`]" :key="i">
</el-option>
</el-select>
<!-- 12文件上传【图片】 -->
<uploadImage v-if="12 == item.ufType" :limit="1" v-model="form[item.name]" :disabled="disabled">
</uploadImage>
<!-- 13文件上传【文件】 -->
<el-upload v-if="13 == item.ufType" class="upload-demo" ref="upload"
action="/meeting-platform-api/api/appletUser/ali/shardUpdate" :on-preview="handlePreview"
:before-upload="(file)=>beforeUpload(file,item)" :file-list="fileList" :auto-upload="true" drag
:accept="item.defaultValue" multiple name="files">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div slot="tip" class="el-upload__tip"><span
v-if="item.defaultValue">只能上传{{item.defaultValue}}文件,</span>且不超过{{item.maxInput ? item.maxInput:10}}MB
</div>
</el-upload>
</el-form-item>
</template>
</el-form>
</div>
</template>
<script>
//引入省市区
import cityData from '@/utils/cityData';
//引入上传图片组件
import uploadImage from '@/components/uploadImage'
//引入校验规则
import validate from '@/utils/validate';
export default {
components: {
uploadImage
},
props: {
//formdata数据
data: {
type: Object | Array,
default () {
return {};
}
},
//中英文 1:中文 2:英文
lang: {
type: Number | String,
default () {
return 1;
}
},
// 行内表单模式 开启后会自适应表单
inline: {
type: Boolean,
default () {
return false;
}
},
// 表单域标签的位置,如果值为 left 或者 right 时,则需要设置 label-width
labelPosition: {
type: String,
default () {
return 'right';
}
},
// 表单域标签的宽度,例如 '50px'。作为 Form 直接子元素的 form-item 会继承该值。支持 auto。
labelWidth: {
type: String,
default () {
return '100px';
}
},
// 表单域标签的后缀
labelSuffix: String,
// 是否隐藏必填字段的标签旁边的红色星号
hideRequiredAsterisk: {
type: Boolean,
default () {
return false;
}
},
//是否显示校验错误信息
showMessage: {
type: Boolean,
default () {
return true;
}
},
//是否以行内形式展示校验信息
inlineMessage: {
type: Boolean,
default () {
return false;
}
},
statusIcon: {
type: Boolean,
default () {
return false;
}
},
validateOnRuleChange: {
type: Boolean,
default () {
return true;
}
},
//表单组件大小 可选项:medium默认 small mini 用于控制该表单内组件的尺寸
size: {
type: String,
default () {
return 'medium';
}
},
//组件是否禁用
disabled: {
type: Boolean,
default () {
return false;
}
},
},
data: function() {
return {
formData: null,
form: {}, //格式化后的表单
rules: {}, //规则
options: {}, //选项存放 多选单选下拉选
fileList: [], //文件列表
pickerOptions: {//时间快捷选项
shortcuts: [{
text: '今天',
onClick(picker) {
picker.$emit('pick', new Date());
}
}, {
text: '昨天',
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24);
picker.$emit('pick', date);
}
}, {
text: '一周前',
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24 * 7);
picker.$emit('pick', date);
}
}]
},
};
},
watch: {
//监听
data: {
immediate: true,
handler: function() {
this.formData = JSON.parse(JSON.stringify(this.data));
this.generateRuleAndForm();
},
},
// form: {
// immediate: true,
// deep: true,
// handler: function() {
// this.$nextTick(() => {
// this.$refs.customForm.validate();
// })
// },
// }
},
methods: {
// 组件表单提交
submit() {
this.$refs.customForm.validate((valid) => {
console.log(valid);
if (valid) {
// var list = JSON.parse(JSON.stringify(this.formData));
// var form = this.form;
// list.forEach((item, i) => {
// list[i].value = form[item.name];
// })
//这里本来以为是要把整个表单数据(包括配置)传过去,后来沟通只传form
this.$emit('change', this.form);
} else {
return this.$message.error((this.lang == 1 ? '请填写完整表单' : 'Please complete the form'));
}
});
},
//选择省 加载市数据
changeProv(val, list) {
var cityList = this.formData.filter(item => item.ufType == 10);
list.forEach(item => {
if (item.label == val) {
cityList.forEach(city => {
this.form[city.name] = '';
this.options[`${city.name}Opt`] = item.children
})
}
})
var areaList = this.formData.filter(item => item.ufType == 11);
areaList.forEach(area => {
this.form[area.name] = '';
})
},
//选择市 加载区数据
changeCity(val, list) {
var areaList = this.formData.filter(item => item.ufType == 11);
list.forEach(item => {
if (item.label == val) {
areaList.forEach(area => {
this.form[area.name] = '';
this.options[`${area.name}Opt`] = item.children;
})
}
})
},
//第一步生成表单规则及表单
generateRuleAndForm() {
var list = this.formData;
var forms = {};
var rules = {};
var options = {};
list.forEach((item, i) => {
item.ufType = parseInt(item.ufType);
console.log(item.ufType);
var rule = []
//判断是否必填
//追加必填规则 没有提示信息 如果是普通文本显示请填写 如果是选择显示请选择;
if ([2, 3, 4, 5, 8, 9, 10, 11].includes(item.ufType)) {
rule.push({
required: item.openRequired == 1 && item.isCheck == 1 ? true : false,
trigger: 'change',
message: item.tips ?? (this.lang == 1 ? `请选择${item.showName}` :
`Please select ${item.showName}`)
});
}
if ([1, 6, 7, 14, 15, 16, 17].includes(item.ufType)) {
rule.push({
required: item.openRequired == 1 && item.isCheck == 1 ? true : false,
trigger: 'blur',
message: item.tips ?? (this.lang == 1 ? `请输入${item.showName}` :
`Please input ${item.showName}`)
});
}
if ([12, 13].includes(item.ufType)) {
rule.push({
required: item.openRequired == 1 && item.isCheck == 1 ? true : false,
trigger: 'change',
message: item.tips ?? (this.lang == 1 ? `请上传${item.showName}` :
`Please upload ${item.showName}`)
});
}
//追加选项 多选 单选 下拉选
if ([2, 3, 4].includes(item.ufType)) {
options[`${item.name}Opt`] = (item.defaultValue ?? '') != '' ? item.defaultValue.split(
'@') : (this.lang == 1 ? ['未配置选项'] : ['Not configured']);
}
//追加选项 国家下拉选
if (8 == item.ufType) {
options[`${item.name}Opt`] = (item.options ?? '') != '' ? item.options : (this.lang == 1 ?
[{
id: 0,
nameCn: '未配置选项',
shortCode: 'null'
}] : [{
id: 0,
nameCn: 'Not configured',
shortCode: 'null'
}]);
}
//追加选项 省
if (9 == item.ufType) {
options[`${item.name}Opt`] = cityData.cityData;
}
//追加选项 市
if (10 == item.ufType) {
options[`${item.name}Opt`] = [];
}
//追加选项 区
if (11 == item.ufType) {
options[`${item.name}Opt`] = [];
}
//如果有自定义正则
if ((item.pattern ?? '') != '') {
rule.push({
pattern: item.pattern,
message: (this.lang == 1 ? `请确认${item.showName}格式是否正确` :
`Please check ${item.showName}`)
})
} else {
// 1文本框、、6邮箱输入、7手机号输入、14验证码、15身份证号 16数值, 17文本域
var ruleArr = {
rule6: {
pattern: /^([a-zA-Z0-9_.-])+@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/,
trigger: 'change',
message: (this.lang == 1 ? `请确认${item.showName}格式是否正确` :
`Please check ${item.showName}`)
},
rule7: {
pattern: /^1\d{10}$/,
trigger: 'change',
message: (this.lang == 1 ? `请确认${item.showName}格式是否正确` :
`Please check ${item.showName}`)
},
rule15: {
pattern: /(^\d{15}$)|(^\d{17}(x|X|\d)$)/,
trigger: 'change',
message: (this.lang == 1 ? `请确认${item.showName}格式是否正确` :
`Please check ${item.showName}`)
},
rule16: {
type: 'number',
trigger: 'change',
message: (this.lang == 1 ? `请确认${item.showName}格式是否正确` :
`Please check ${item.showName}`)
}
}
if ([6, 7, 15, 16].includes(item.ufType)) {
rule.push(ruleArr[`rule${item.ufType}`])
}
}
console.log(rule);
//动态绑定表单
forms[item.name] = item.ufType == 8 ? (isNaN(parseInt(item.value)) ? '' : parseInt(item
.value)) : item.value;
//动态绑定规则
rules[item.name] = rule;
})
this.$set(this, 'options', options);
this.$set(this, 'form', forms);
this.$set(this, 'rules', rules);
},
//监听文件删除回调
handleRemove() {
},
//监听点击已上传文件
handlePreview(file) {
console.log(file);
if (file.response.code == 200) { //验证真实上传状态
window.open(file.response.data.url)
}
},
//监听文件上传前回调,验证文件
beforeUpload(file, item) {
console.log(file);
console.log(item);
//默认10M
var maxSize = (item.maxInput ?? 10) * 1024 * 1024;
if (file.size > maxSize) {
this.$message.error((this.lang == 1 ? `文件大小超出限制${item.maxInput}MB` :
`File size limit exceeded ${item.maxInput}MB`));
return false;
}
}
},
};
</script>
<style lang="scss" scoped>
.customForm {
padding-right: 10px;
}
</style>
数据格式
[
{
"id": 116,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "姓名",
"name": "name",
"openShow": null,
"openRequired": 1,
"tips": "请输入您的姓名",
"filedType": 1,
"defaultValue": null,
"ufType": "1",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 1,
"pattern": null,
"options": null,
"isCheck": 1
},
{
"id": 171,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "科室",
"name": "keshi",
"openShow": null,
"openRequired": 1,
"tips": "请填写科室",
"filedType": 1,
"defaultValue": "",
"ufType": "1",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 30,
"pattern": "",
"options": null,
"isCheck": 1
},
{
"id": 186,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "下拉选",
"name": "exFiled_186",
"openShow": null,
"openRequired": 1,
"tips": "请选择下拉选",
"filedType": 2,
"defaultValue": "选项一@选项而@选项散",
"ufType": "2",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 20,
"pattern": "",
"options": null,
"isCheck": 1
},
{
"id": 187,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "英雄单选",
"name": "exFiled_187",
"openShow": null,
"openRequired": 1,
"tips": "请选择您的英雄",
"filedType": 2,
"defaultValue": "无极剑圣@厄加特@索拉卡",
"ufType": "3",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 20,
"pattern": "",
"options": null,
"isCheck": 1
},
{
"id": 188,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "技能",
"name": "exFiled_188",
"openShow": null,
"openRequired": 1,
"tips": "请选择您的技能",
"filedType": 2,
"defaultValue": "闪现@治疗@虚弱@疾跑@传送@引燃",
"ufType": "4",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 20,
"pattern": "",
"options": null,
"isCheck": 1
},
{
"id": 184,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "生日",
"name": "birthday",
"openShow": null,
"openRequired": 1,
"tips": "请选择生日",
"filedType": 1,
"defaultValue": "yyyy-MM-dd",
"ufType": "5",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 3,
"pattern": "",
"options": null,
"isCheck": 1
},
{
"id": 118,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "邮箱",
"name": "email",
"openShow": null,
"openRequired": 1,
"tips": "请输入您的邮箱",
"filedType": 1,
"defaultValue": null,
"ufType": "6",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 20,
"pattern": null,
"options": null,
"isCheck": 1
},
{
"id": 117,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "手机号码",
"name": "mobile",
"openShow": null,
"openRequired": 1,
"tips": "请输入您的手机号",
"filedType": 1,
"defaultValue": null,
"ufType": "7",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 11,
"pattern": null,
"options": null,
"isCheck": 1
},
{
"id": 175,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "国家",
"name": "country",
"openShow": null,
"openRequired": 1,
"tips": "请选择国家",
"filedType": 1,
"defaultValue": "",
"ufType": "8",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 50,
"pattern": "",
"options": [
{
"nameCn": "几内亚",
"id": 1,
"nameEn": "Guinea",
"shortCode": "GN"
},
{
"nameCn": "圣文森特和格林纳丁斯",
"id": 2,
"nameEn": "Saint Vincent and the Grenadines",
"shortCode": "VG"
},
{
"nameCn": "中国",
"id": 3,
"nameEn": "China",
"shortCode": "CN"
},
{
"nameCn": "日本",
"id": 4,
"nameEn": "Japan",
"shortCode": "JP"
},
{
"nameCn": "韩国",
"id": 5,
"nameEn": "Republic of Korea",
"shortCode": "KR"
}
],
"isCheck": 1
},
{
"id": 176,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "省",
"name": "province",
"openShow": null,
"openRequired": 1,
"tips": "请选择省",
"filedType": 1,
"defaultValue": "",
"ufType": "9",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 50,
"pattern": "",
"options": null,
"isCheck": 1
},
{
"id": 177,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "市",
"name": "city",
"openShow": null,
"openRequired": 1,
"tips": "请选择市",
"filedType": 1,
"defaultValue": "",
"ufType": "10",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 50,
"pattern": "",
"options": null,
"isCheck": 1
},
{
"id": 178,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "区县",
"name": "district",
"openShow": null,
"openRequired": 1,
"tips": "请选择区县",
"filedType": 1,
"defaultValue": "",
"ufType": "11",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 50,
"pattern": "",
"options": null,
"isCheck": 1
},
{
"id": 121,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "头像",
"name": "head_photo",
"openShow": null,
"openRequired": 1,
"tips": "请上传您的头像",
"filedType": 1,
"defaultValue": "png@jpg",
"ufType": "12",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 200,
"pattern": null,
"options": null,
"isCheck": 1
},
{
"id": 189,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "文件上传",
"name": "exFiled_189",
"openShow": null,
"openRequired": 1,
"tips": "请选择文件",
"filedType": 2,
"defaultValue": ".zip,.png",
"ufType": "13",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 20,
"pattern": "",
"options": null,
"isCheck": 1
},
{
"id": 190,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "验证码",
"name": "exFiled_190",
"openShow": null,
"openRequired": 1,
"tips": "请填写验证码",
"filedType": 2,
"defaultValue": "",
"ufType": "14",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 5,
"pattern": "",
"options": null,
"isCheck": 1
},
{
"id": 185,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "身份证号",
"name": "id_card",
"openShow": null,
"openRequired": 1,
"tips": "请填写正确身份证号",
"filedType": 1,
"defaultValue": "",
"ufType": "15",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 18,
"pattern": "",
"options": null,
"isCheck": 1
},
{
"id": 183,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "年龄",
"name": "ages",
"openShow": null,
"openRequired": 1,
"tips": "请填写年龄",
"filedType": 1,
"defaultValue": "",
"ufType": "16",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 3,
"pattern": "",
"options": null,
"isCheck": 1
},
{
"id": 120,
"mid": 333,
"tmpId": 14,
"lang": 1,
"showName": "性别",
"name": "gender",
"openShow": null,
"openRequired": 1,
"tips": "请选择您的性别",
"filedType": 1,
"defaultValue": "男@女",
"ufType": "3",
"canEdit": 1,
"fieldAddShow": 1,
"sort": null,
"createTime": null,
"updateTime": null,
"delFalg": null,
"value": "",
"maxInput": 1,
"pattern": null,
"options": null,
"isCheck": 1
}
]
使用方法
<customForm v-if="showEdit" ref="custom" :data="formData" @change="changeForm"
:disabled="customDisabled" :lang="editForm.lang" />
import customForm from '@/components/customForm';
components: {
customForm,
},
