Vue3 + Element Plus 动态表单实现

完整代码

javascript 复制代码
<template>
  <div class="dynamic-form-container">
    <el-form
      ref="dynamicFormRef"
      :model="formData"
      :rules="formRules"
      label-width="auto"
      label-position="top"
      v-loading="loading"
    >
      <!-- 动态渲染表单字段 -->
      <template v-for="field in formConfig" :key="field.name">
        <!-- 输入框 -->
        <el-form-item
          v-if="field.type === 'input'"
          :label="field.label"
          :prop="field.name"
          :rules="generateFieldRules(field)"
        >
          <el-input
            v-model="formData[field.name]"
            :placeholder="field.placeholder || `请输入${field.label}`"
            :type="field.inputType || 'text'"
            :clearable="field.clearable !== false"
          />
        </el-form-item>

        <!-- 下拉选择 -->
        <el-form-item
          v-else-if="field.type === 'select'"
          :label="field.label"
          :prop="field.name"
          :rules="generateFieldRules(field)"
        >
          <el-select
            v-model="formData[field.name]"
            :placeholder="field.placeholder || `请选择${field.label}`"
            :clearable="field.clearable !== false"
            style="width: 100%"
          >
            <el-option
              v-for="option in field.options"
              :key="option.value"
              :label="option.label"
              :value="option.value"
            />
          </el-select>
        </el-form-item>

        <!-- 单选框 -->
        <el-form-item
          v-else-if="field.type === 'radio'"
          :label="field.label"
          :prop="field.name"
          :rules="generateFieldRules(field)"
        >
          <el-radio-group v-model="formData[field.name]">
            <el-radio
              v-for="option in field.options"
              :key="option.value"
              :label="option.value"
            >
              {{ option.label }}
            </el-radio>
          </el-radio-group>
        </el-form-item>

        <!-- 复选框 -->
        <el-form-item
          v-else-if="field.type === 'checkbox'"
          :label="field.label"
          :prop="field.name"
          :rules="generateFieldRules(field)"
        >
          <el-checkbox-group v-model="formData[field.name]">
            <el-checkbox
              v-for="option in field.options"
              :key="option.value"
              :label="option.value"
            >
              {{ option.label }}
            </el-checkbox>
          </el-checkbox-group>
        </el-form-item>

        <!-- 日期选择器 -->
        <el-form-item
          v-else-if="field.type === 'date'"
          :label="field.label"
          :prop="field.name"
          :rules="generateFieldRules(field)"
        >
          <el-date-picker
            v-model="formData[field.name]"
            :type="field.dateType || 'date'"
            :placeholder="field.placeholder || `请选择${field.label}`"
            style="width: 100%"
          />
        </el-form-item>

        <!-- 开关 -->
        <el-form-item
          v-else-if="field.type === 'switch'"
          :label="field.label"
          :prop="field.name"
        >
          <el-switch v-model="formData[field.name]" />
        </el-form-item>

        <!-- 自定义插槽 -->
        <el-form-item
          v-else-if="field.type === 'slot'"
          :label="field.label"
          :prop="field.name"
          :rules="generateFieldRules(field)"
        >
          <slot :name="field.slotName" :field="field" :model="formData" />
        </el-form-item>
      </template>

      <el-form-item>
        <el-button type="primary" @click="submitForm">提交</el-button>
        <el-button @click="resetForm">重置</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'

// 表单引用
const dynamicFormRef = ref()

// 加载状态
const loading = ref(false)

// 表单数据
const formData = ref({})

// 表单验证规则
const formRules = ref({})

// 表单配置(从后端获取)
const formConfig = ref([
  // 默认配置,实际会被后端数据覆盖
  {
    name: 'username',
    label: '用户名',
    type: 'input',
    required: true,
    placeholder: '请输入用户名'
  }
])

// 模拟从后端获取表单配置
const fetchFormConfig = async () => {
  try {
    loading.value = true
    
    // 这里替换为实际的API调用
    const response = await mockApiGetFormConfig()
    
    formConfig.value = response.data.fields
    
    // 初始化表单数据
    initFormData()
    
    // 生成验证规则
    generateFormRules()
  } catch (error) {
    ElMessage.error('获取表单配置失败: ' + error.message)
  } finally {
    loading.value = false
  }
}

// 初始化表单数据
const initFormData = () => {
  const data = {}
  formConfig.value.forEach(field => {
    // 根据字段类型设置默认值
    switch (field.type) {
      case 'checkbox':
        data[field.name] = field.defaultValue || []
        break
      case 'switch':
        data[field.name] = field.defaultValue || false
        break
      default:
        data[field.name] = field.defaultValue || ''
    }
  })
  formData.value = data
}

// 生成表单验证规则
const generateFormRules = () => {
  const rules = {}
  formConfig.value.forEach(field => {
    if (field.required || field.rules) {
      rules[field.name] = generateFieldRules(field)
    }
  })
  formRules.value = rules
}

// 生成单个字段的验证规则
const generateFieldRules = (field) => {
  const rules = []
  
  // 必填规则
  if (field.required) {
    rules.push({
      required: true,
      message: field.message || `${field.label}不能为空`,
      trigger: field.trigger || 'blur'
    })
  }
  
  // 自定义规则
  if (field.rules && Array.isArray(field.rules)) {
    rules.push(...field.rules)
  }
  
  // 类型校验
  if (field.type === 'input' && field.inputType === 'email') {
    rules.push({
      type: 'email',
      message: '请输入正确的邮箱格式',
      trigger: ['blur', 'change']
    })
  }
  
  return rules
}

// 提交表单
const submitForm = async () => {
  try {
    // 表单验证
    await dynamicFormRef.value.validate()
    
    // 这里替换为实际的提交API
    const response = await mockApiSubmitForm(formData.value)
    
    ElMessage.success('提交成功')
    console.log('表单数据:', formData.value)
    console.log('服务器响应:', response)
    
    // 可以在这里处理提交成功后的逻辑
  } catch (error) {
    if (error instanceof Error) {
      ElMessage.error('表单验证失败: ' + error.message)
    }
  }
}

// 重置表单
const resetForm = () => {
  dynamicFormRef.value.resetFields()
}

// 模拟API获取表单配置
const mockApiGetFormConfig = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        data: {
          fields: [
            {
              name: 'username',
              label: '用户名',
              type: 'input',
              required: true,
              placeholder: '请输入用户名',
              maxlength: 20
            },
            {
              name: 'password',
              label: '密码',
              type: 'input',
              inputType: 'password',
              required: true,
              placeholder: '请输入密码',
              rules: [
                { min: 6, max: 18, message: '密码长度在6到18个字符', trigger: 'blur' }
              ]
            },
            {
              name: 'gender',
              label: '性别',
              type: 'select',
              required: true,
              options: [
                { label: '男', value: 'male' },
                { label: '女', value: 'female' },
                { label: '其他', value: 'other' }
              ]
            },
            {
              name: 'hobbies',
              label: '兴趣爱好',
              type: 'checkbox',
              options: [
                { label: '游泳', value: 'swimming' },
                { label: '跑步', value: 'running' },
                { label: '阅读', value: 'reading' }
              ]
            },
            {
              name: 'subscribe',
              label: '订阅通知',
              type: 'switch',
              defaultValue: true
            },
            {
              name: 'birthday',
              label: '出生日期',
              type: 'date',
              dateType: 'date',
              required: true
            }
          ]
        }
      })
    }, 800)
  })
}

// 模拟API提交表单
const mockApiSubmitForm = (data) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ code: 200, message: 'success', data })
    }, 500)
  })
}

// 组件挂载时获取表单配置
onMounted(() => {
  fetchFormConfig()
})
</script>

<style scoped>
.dynamic-form-container {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}
</style>

后端API数据结构建议

后端API返回的表单配置建议采用如下JSON格式:

javascript 复制代码
{
  "code": 200,
  "message": "success",
  "data": {
    "fields": [
      {
        "name": "username",
        "label": "用户名",
        "type": "input",
        "required": true,
        "placeholder": "请输入用户名",
        "inputType": "text",
        "maxlength": 20,
        "rules": [
          {
            "pattern": "^[a-zA-Z0-9_]+$",
            "message": "只能包含字母、数字和下划线"
          }
        ]
      },
      {
        "name": "gender",
        "label": "性别",
        "type": "select",
        "required": true,
        "options": [
          {
            "label": "男",
            "value": "male"
          },
          {
            "label": "女",
            "value": "female"
          }
        ]
      },
      {
        "name": "subscribe",
        "label": "订阅通知",
        "type": "switch",
        "defaultValue": true
      }
    ]
  }
}
相关推荐
揣晓丹20 分钟前
JAVA实战开源项目:健身房管理系统 (Vue+SpringBoot) 附源码
java·vue.js·spring boot·后端·开源
景尘37 分钟前
vue3 全局注册自定义指令,input聚焦失焦展示对应值
vue.js
春天姐姐2 小时前
vue知识点总结 依赖注入 动态组件 异步加载
前端·javascript·vue.js
Pop–3 小时前
Vue3 el-tree:全选时只返回父节点,半选只返回勾选中的节点(省-市区-县-镇-乡-村-街道)
开发语言·javascript·vue.js
阿金要当大魔王~~3 小时前
面试问题(连载。。。。)
前端·javascript·vue.js
yuanyxh3 小时前
commonmark.js 源码阅读(一) - Block Parser
开发语言·前端·javascript
进取星辰4 小时前
22、城堡防御工事——React 19 错误边界与监控
开发语言·前端·javascript
MaCa .BaKa4 小时前
37-智慧医疗服务平台(在线接诊/问诊)
java·vue.js·spring boot·tomcat·vue·maven
ドロロ8065 小时前
element-plus点击重置表单,却没有进行重置操作
javascript·vue.js·elementui