Vue3 + Element Plus 动态表单实现

news2025/5/16 20:46:05

 完整代码

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2377125.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity光照笔记

问题 在做项目中遇到了播放中切换场景后地面阴影是纯黑的问题&#xff0c;不得不研究一下光照。先放出官方文档。 Lighting 窗口 - Unity 手册 播放中切换场景后地面阴影是纯黑 只有投到地面的阴影是纯黑的。且跳转到使用相同Terrain的场景没有问题。 相关文章&#xff1a…

嵌入式学习的第二十天-数据结构-调试+链表的一般操作

一、调试 1.一般调试 2.找段错误 二、链表的一般操作 1.单链表的修改 int ModifyLinkList(LinkList*ll,char*name,DATATYPE*data) {DATATYPE * tmp FindLinkList(ll, name);if(NULL tmp){return 1;}memcpy(tmp,data,sizeof(DATATYPE));return 0; } 2.单链表的销毁 int D…

家具制造行业的现状 质检LIMS如何赋能家具制造企业质检升级

在家具制造行业&#xff0c;从原木切割到成品出厂&#xff0c;质检环节贯穿始终 —— 木材含水率是否达标、板材甲醛释放量是否合规、涂层耐磨性能否通过标准…… 这些看似琐碎的检测项目&#xff0c;实则是企业把控产品品质、规避市场风险的核心关卡。传统人工质检模式在效率、…

idea整合maven环境配置

idea整合maven 提示&#xff1a;帮帮志会陆续更新非常多的IT技术知识&#xff0c;希望分享的内容对您有用。本章分享的是springboot的使用。前后每一小节的内容是存在的有&#xff1a;学习and理解的关联性。【帮帮志系列文章】&#xff1a;每个知识点&#xff0c;都是写出代码…

无偿帮写毕业论文(看不懂的可以私信博主)

以下教程教你如何利用相关网站和AI免费帮你写一个毕业论文。毕竟毕业论文只要过就行&#xff0c;脱产学习这么多年&#xff0c;终于熬出头了&#xff0c;完成毕设后有空就去多看看亲人好友&#xff0c;祝好&#xff01; 一、找一个论文模板 废话不多说&#xff0c;先上干货Ov…

小白成长之路-vim编辑

文章目录 Vim一、命令模式二、插入模式3.a:进入插入模式&#xff0c;在当前光标的后一个字符插入![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/fd293c3832ed49e2974abfbb63eeb5bb.png)4.o: 在当前光标的下一行插入5.i:在当前光标所在字符插入&#xff0c;返回命令模…

【常用算法:排序篇】7.算法魔法与面试秘籍:从趣味排序到实战通关

一、趣味排序算法&#xff1a;突破常规的思维火花 1. 睡眠排序&#xff08;Sleep Sort&#xff09;—— 时间维度的魔法 核心思想&#xff1a;利用多线程休眠时间模拟数值大小&#xff0c;自然输出有序结果。Python示例&#xff1a;import threading import timedef sleep_so…

Android | IOS — Solox性能测试

文章目录 Solox性能测试1. 前置条件2. 软件图片 Solox性能测试 1. 前置条件 安装Python:3.10.0以上版本&#xff1a; Windows&#xff1a;Python官网 安装 SoloX python -m solox2. 软件图片 软件图片 报告分析&#xff1a;

Midjourney 最佳创作思路与实战技巧深度解析【附提示词与学习资料包下载】

引言 在人工智能图像生成领域&#xff0c;Midjourney 凭借其强大的艺术表现力和灵活的创作模式&#xff0c;已成为设计师、艺术家和创意工作者的核心工具。作为 CSDN 博主 “小正太浩二”&#xff0c;我将结合多年实战经验&#xff0c;系统分享 Midjourney 的创作方法论&#x…

工具篇-扣子空间MCP,一键做游戏,一键成曲

一、登陆扣子空间 地址如下&#xff1a; 扣子空间 打开&#xff0c;然后登陆扣子 登陆之后快速开始&#xff1a; 二、生成游戏 小试牛刀&#xff0c;我们让它做一个打地鼠的游戏&#xff1a; 已经开始设计制作&#xff1a; 制作完成&#xff1a; 三、制作音乐 新…

5.6 - 5.9 MySQL

数据库&#xff1a;存储和管理数据的仓库DB。 数据库管理系统&#xff1a;操纵和管理数据库的大型软件DBMS。 关系型数据库 一个数据库内可以创建多张表&#xff0c;在一个表内能存放多个数据。 SQL语句&#xff1a; DDL&#xff1a; 存储字符串用varchar。&#xff08;类似于…

C# WinForm 如何高效地将大量数据从 CSV 文件导入 DataGridView

如果你有非常多的csv文件&#xff0c;每个文件包含N多行与M多列&#xff0c;如&#xff1a;18000 行和 27 列。现在&#xff0c;想制作一个 Windows 窗体应用程序&#xff0c;导入它们并在 datagridview 中显示&#xff0c;然后进行一些数学运算。可是&#xff0c;发现数据导入…

SQLPub:一个提供AI助手的免费MySQL数据库服务

给大家介绍一个免费的 MySQL 在线数据库环境&#xff1a;SQLPub。它提供了最新版本的 MySQL 服务器测试服务&#xff0c;可以方便开发者和测试人员验证数据库功能&#xff0c;也可以用于学习 MySQL。 免费申请 在浏览器中输入以下网址&#xff1a; https://sqlpub.com/ SQLP…

URP相机如何将场景渲染定帧模糊绘制

1&#xff09;URP相机如何将场景渲染定帧模糊绘制 2&#xff09;为什么Virtual Machine会随着游戏时间变大 3&#xff09;出海项目&#xff0c;打包时需要勾选ARMv7吗 4&#xff09;Unity是手动还是自动调用GC.Collect 这是第431篇UWA技术知识分享的推送&#xff0c;精选了UWA社…

WeakAuras Lua Script ICC (BarneyICC)

WeakAuras Lua Script ICC &#xff08;BarneyICC&#xff09; https://wago.io/BarneyICC/69 全量英文字符串&#xff1a; !WA:2!S33c4TXX5bQv0kobjnnMowYw2YAnDKmPnjnb4ljzl7sqcscl(YaG6HvCbxaSG7AcU76Dxis6uLlHNBIAtBtRCVM00Rnj8Y1M426ZH9XDxstsRDR)UMVCTt0DTzVhTjNASIDAU…

FramePack - 开源 AI 视频生成工具

&#x1f3ac; 项目简介 由开发者 lllyasviel 创建的一个轻量级动画帧处理工具库&#xff0c;专门用于游戏开发、动画制作和视频处理中的帧序列打包与管理。该项目采用高效的算法实现&#xff0c;能够显著提升动画资源的处理效率。 此 AI 视频生成项目&#xff0c;旨在通过低显…

Vuetify框架使用(一)之v-snackbar 组件封装及全局使用

说明&#xff1a;v-snackbar 组件适用于统一管理消息提示框(操作反馈的提示) 看效果&#xff1a; 1、在状态管理中创建文件&#xff0c;统一管理 // stores/snackbar.js /*** 统一管理消息提示框(操作反馈的提示)*/import { defineStore } from pinia; // 消息类型 export co…

FPGA: UltraScale+ bitslip实现(方案+代码)

收获 一晃五年~ 五年前那个夏夜&#xff0c;我对着泛蓝的屏幕敲下《给十年后的自己》&#xff0c;在2020年的疫情迷雾中编织着对未来的想象。此刻回望&#xff0c;第四届集创赛的参赛编号仍清晰如昨&#xff0c;而那个在家熬夜焊电路板的"不眠者"&#xff0c;现在…

【SpeechLMs】语音大型语言模型综述《A Survey on Speech Large Language Models》

摘要 大型语言模型 (LLM) 表现出强大的上下文理解能力和显著的多任务性能。 因此&#xff0c;研究人员一直在寻求将 LLM 整合到更广泛的语音语言理解 (SLU) 领域。 与传统方法不同&#xff0c;传统方法是将 LLM 级联以处理自动语音识别 (ASR) 生成的文本&#xff0c;而新方法则…

吴恩达机器学习笔记:特征与多项式回归

1.特征和多项式回归 如房价预测问题&#xff0c; ℎθ (x) θ0 θ1 frontage θ2 deptℎ x1 frontage&#xff08;临街宽度&#xff09;&#xff0c;x2 deptℎ&#xff08;纵向深度&#xff09;&#xff0c;x frontage ∗ deptℎ area &#xff08;面积&#xff09;…