隐藏
换装

Vue / 前端 · 2022年7月21日 0

动态表单

写了个动态表单的组件

仅可参考设计模式!!!!!不要瞎复制

这个因为后端不是本人写的,再加上需要对之前的数据格式做兼容,导致某些地方不尽人意,比如单选多选按照@符号拆分,而且只能存选项的文字(多选项明明可以直接存数组)

另外附上后台管理的相关代码

组件代码

<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,
},