Vue3 + Element Plus 动态表单实现

发布于:2025-05-10 ⋅ 阅读:(16) ⋅ 点赞:(0)

 完整代码

<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格式:

{
  "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
      }
    ]
  }
}


网站公告

今日签到

点亮在社区的每一天
去签到