我现在在做一项前端技改,是根据特定的数据结构,利用vue2+element-ui来生成动态表单的一项功能。
我提供如下data的数据结构。代码中有对应的说明注释。其中 fields 为数组结构,其中的元素描述的是表单中的每一项元素。fieldId为提交的元素字段,fieldName为属性中文名,fieldType是element-ui对应的组件类型,fieldValueType是格式类型,required为是否必填,其他的字段对应可映射为对应表单元素的属性。
想要通过提供的data数据,来生成对应的动态表单,请给出实现代码。
js
export const formConfig = {
pageId: 'user_profile',
fields: [
{
fieldId: 'username',
fieldName: '用户名',
fieldType: 'input',
fieldValueType: 'string',
required: true,
maxLength: 20,
placeholder: '请输入用户名'
},
{
fieldId: 'userage',
fieldName: '用户年龄段',
fieldType: 'select',
fieldValueType: 'string',
required: true,
dropdownOptions: [
{ value: 18, label: '小于18' },
{ value: 30, label: '18-30' },
{ value: 60, label: '30-60' },
{ value: 100, label: '大于60' }
]
},
{
fieldId: 'userType',
fieldName: '用户类型',
fieldType: 'select',
fieldValueType: 'string',
required: true,
condition: {
trigRule: 'userage=18', // 单条件示例
displayType: 'hide' // 当用户年龄为18时隐藏此字段
},
dropdownOptions: [
{ value: 'normal', label: '普通用户' },
{ value: 'vip', label: 'VIP用户' }
],
otherOptions: {
'userage=30': [
{ value: 'normal', label: '普通用户22' },
{ value: 'vip', label: 'VIP用户22' }
]
}
},
{
fieldId: 'vipLevel',
fieldName: 'VIP等级',
fieldType: 'select',
fieldValueType: 'string',
required: false,
dropdownOptions: [
{ value: 1, label: 'vip1' },
{ value: 2, label: 'vip2' },
{ value: 3, label: 'vip3' },
{ value: 4, label: 'vip4' }
],
condition: {
// 多条件示例:当用户类型为VIP且年龄在30-60之间时禁用
trigRule: 'userType=vip&userage=60',
displayType: 'disable'
}
},
{
fieldId: 'validDateTime',
fieldName: '有效日期时间',
fieldType: 'dateTime',
fieldValueType: 'string',
required: true,
maxCount: 5
}
]
};
vue
<template>
<div class="form-container">
<el-form ref="dynamicForm" :model="formData" label-width="120px" label-position="top" class="dynamic-form">
<!-- 遍历所有字段配置 -->
<div v-for="field in config.fields" :key="field.fieldId">
<!-- 普通字段 -->
<div v-if="!field.maxCount" class="form-card" v-show="shouldShowField(field)">
<div class="form-title">
<i :class="getFieldIcon(field.fieldType)"></i>
{{ field.fieldName }}
<span v-if="field.required" style="color: #F56C6C; margin-left: 5px">*</span>
</div>
<!-- 输入框 -->
<el-form-item v-if="field.fieldType === 'input' || field.fieldType === 'textarea'" :prop="field.fieldId"
:rules="getRules(field)">
<el-input v-if="field.fieldType === 'input'" v-model="formData[field.fieldId]"
:placeholder="field.placeholder || '请输入'" :maxlength="field.maxLength"
:disabled="isFieldDisabled(field)"></el-input>
<el-input v-else-if="field.fieldType === 'textarea'" type="textarea" v-model="formData[field.fieldId]"
:placeholder="field.placeholder || '请输入'" :maxlength="field.maxLength" :rows="4"
:disabled="isFieldDisabled(field)"></el-input>
</el-form-item>
<!-- 下拉选择 -->
<el-form-item v-else-if="field.fieldType === 'select'" :prop="field.fieldId" :rules="getRules(field)">
<el-select v-model="formData[field.fieldId]" :placeholder="field.placeholder || '请选择'" style="width: 100%"
:disabled="isFieldDisabled(field)">
<el-option v-for="option in getDropdownOptions(field)" :key="option.value" :label="option.label"
:value="option.value"></el-option>
</el-select>
</el-form-item>
<!-- 日期时间选择器 -->
<el-form-item v-else-if="field.fieldType === 'dateTime'" :prop="field.fieldId" :rules="getRules(field)">
<el-date-picker v-model="formData[field.fieldId]" type="datetime" placeholder="选择日期时间" style="width: 100%"
:disabled="isFieldDisabled(field)"></el-date-picker>
</el-form-item>
</div>
<!-- 可动态增减的字段组 -->
<div v-else class="dynamic-group">
<div class="group-header">
<div class="group-title">
<i class="el-icon-tickets"></i>
{{ field.fieldName }} ({{ formData[field.fieldId].length }}/{{ field.maxCount }})
</div>
<div class="group-actions">
<el-tooltip content="添加一项" placement="top">
<el-button type="success" icon="el-icon-plus" circle size="mini" @click="addFieldItem(field)"
:disabled="formData[field.fieldId].length >= field.maxCount"></el-button>
</el-tooltip>
</div>
</div>
<div v-for="(item, index) in formData[field.fieldId]" :key="index">
<div class="form-card">
<div class="form-title">
<i :class="getFieldIcon(field.fieldType)"></i>
{{ field.fieldName }} {{ index + 1 }}
<span v-if="field.required" style="color: #F56C6C; margin-left: 5px">*</span>
</div>
<el-form-item :prop="`${field.fieldId}.${index}`" :rules="getRules(field)">
<!-- 日期时间选择器 -->
<el-date-picker v-if="field.fieldType === 'dateTime'" v-model="formData[field.fieldId][index]"
type="datetime" placeholder="选择日期时间" style="width: 100%"></el-date-picker>
<!-- 其他类型字段可以在这里扩展 -->
</el-form-item>
<div style="text-align: right; margin-top: -10px">
<el-button type="danger" icon="el-icon-delete" size="mini" @click="removeFieldItem(field, index)"
:disabled="formData[field.fieldId].length <= 1">删除</el-button>
</div>
</div>
</div>
</div>
</div>
</el-form>
<div class="form-actions">
<el-button type="primary" @click="submitForm" size="medium">提交表单</el-button>
<el-button @click="resetForm" size="medium">重置表单</el-button>
<el-button @click="showFormData = !showFormData" size="medium">
{{ showFormData ? '隐藏数据' : '查看数据' }}
</el-button>
</div>
<div v-if="showFormData" class="json-view">
<div class="json-view-header">
<span>表单数据 (JSON格式)</span>
<el-button type="text" @click="copyJson" style="color: #67c23a;">
<i class="el-icon-document-copy"></i> 复制JSON
</el-button>
</div>
<pre>{{ formatJson(formData) }}</pre>
</div>
</div>
</template>
<script>
import { formConfig } from './demo2'
export default {
name: 'HelloWorld',
props: {
msg: String
},
data() {
// 初始化表单数据
const formData = {};
formConfig.fields.forEach(field => {
if (field.maxCount) {
// 可动态增减的字段组初始化为数组
formData[field.fieldId] = [null];
} else {
// 普通字段初始化为null
formData[field.fieldId] = null;
}
});
return {
config: formConfig,
formData: formData,
showFormData: false
};
},
methods: {
// 获取字段图标
getFieldIcon(fieldType) {
const icons = {
input: 'el-icon-edit',
textarea: 'el-icon-document',
select: 'el-icon-s-operation',
dateTime: 'el-icon-date',
radio: 'el-icon-circle-check',
checkbox: 'el-icon-check',
file: 'el-icon-paperclip',
url: 'el-icon-link'
};
return icons[fieldType] || 'el-icon-question';
},
// 获取验证规则
getRules(field) {
const rules = [];
if (field.required) {
rules.push({
required: true,
message: `请输入${field.fieldName}`,
trigger: 'blur'
});
}
return rules;
},
// 检查条件是否满足(支持多个条件)
checkCondition(trigRule) {
if (!trigRule) return false;
// 分割多个条件
const conditions = trigRule.split('&');
// 检查每个条件是否满足
return conditions.every(condition => {
const [fieldId, expectedValue] = condition.split('=');
const actualValue = this.formData[fieldId];
// 处理数字类型的比较
if (typeof actualValue === 'number' && !isNaN(expectedValue)) {
return actualValue === Number(expectedValue);
}
// 默认使用宽松比较
return actualValue == expectedValue;
});
},
// 判断字段是否应该显示
shouldShowField(field) {
if (!field.condition) return true;
const conditionMet = this.checkCondition(field.condition.trigRule);
// 如果条件满足且显示类型是隐藏,则不显示
if (field.condition.displayType === 'hide') {
return !conditionMet;
}
// 其他情况都显示
return true;
},
// 判断字段是否被禁用
isFieldDisabled(field) {
if (!field.condition) return false;
const conditionMet = this.checkCondition(field.condition.trigRule);
// 如果条件满足且显示类型是禁用,则禁用字段
if (field.condition.displayType === 'disable') {
return conditionMet;
}
return false;
},
// 获取下拉选项
getDropdownOptions(field) {
if (!field.otherOptions) return field.dropdownOptions;
// 检查是否有满足条件的otherOptions
for (const condition in field.otherOptions) {
if (this.checkCondition(condition)) {
return field.otherOptions[condition];
}
}
return field.dropdownOptions;
},
// 添加字段项
addFieldItem(field) {
if (this.formData[field.fieldId].length < field.maxCount) {
this.formData[field.fieldId].push(null);
}
},
// 删除字段项
removeFieldItem(field, index) {
if (this.formData[field.fieldId].length > 1) {
this.formData[field.fieldId].splice(index, 1);
}
},
// 提交表单
submitForm() {
this.$refs.dynamicForm.validate(valid => {
if (valid) {
this.$message.success('表单验证通过!');
this.showFormData = true;
console.log('-------->', this.formData)
} else {
this.$message.error('请填写完整表单信息');
return false;
}
});
},
// 重置表单
resetForm() {
this.$refs.dynamicForm.resetFields();
this.showFormData = false;
// 重置动态字段组
this.config.fields.forEach(field => {
if (field.maxCount) {
this.formData[field.fieldId] = [null];
}
});
},
// 格式化JSON显示
formatJson(obj) {
return JSON.stringify(obj, null, 2);
},
// 复制JSON
copyJson() {
const textarea = document.createElement('textarea');
textarea.value = JSON.stringify(this.formData, null, 2);
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
this.$message.success('JSON已复制到剪贴板');
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>