动态表单生成

我现在在做一项前端技改,是根据特定的数据结构,利用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>
相关推荐
ZXT5 分钟前
promise & async await总结
前端
Jerry说前后端5 分钟前
RecyclerView 性能优化:从原理到实践的深度优化方案
android·前端·性能优化
画个太阳作晴天13 分钟前
A12预装app
linux·服务器·前端
77238939 分钟前
解决 Microsoft Edge 显示“由你的组织管理”问题
前端·microsoft·edge
烛阴1 小时前
前端必会:如何创建一个可随时取消的定时器
前端·javascript·typescript
JarvanMo1 小时前
Swift 应用在安卓系统上会怎么样?
前端
LinXunFeng2 小时前
Flutter - 详情页 TabBar 与模块联动?秒了!
前端·flutter·开源
萌萌哒草头将军2 小时前
Oxc 最新 Transformer Alpha 功能速览! 🚀🚀🚀
前端·javascript·vue.js
Justinc.2 小时前
HTML5新增属性
前端·html·html5
1024小神3 小时前
nextjs项目build导出静态文件
前端·javascript