探索 Element Plus 的Form 表单配置化生成

先看代码

这是Element Plus官网上面的一个带验证的表单示例

js 复制代码
<template
  <el-form
    :model="ruleForm"
    :rules="rules"
  >
  
  //输入框
    <el-form-item label="Activity name" prop="name"> 
      <el-input v-model="ruleForm.name" />
    </el-form-item>
    
  // 选择器
    <el-form-item label="Activity zone" prop="region">
      <el-select v-model="ruleForm.region" placeholder="Activity zone">
        <el-option label="Zone one" value="shanghai" />
        <el-option label="Zone two" value="beijing" />
      </el-select>
    </el-form-item>
    
 
    //多选框
     <el-form-item label="Activity type" prop="type">
          <el-checkbox-group v-model="ruleForm.type">
            <el-checkbox label="Online activities" name="type" />
            <el-checkbox label="Promotion activities" name="type" />
            <el-checkbox label="Offline activities" name="type" />
            <el-checkbox label="Simple brand exposure" name="type" />
          </el-checkbox-group>
     </el-form-item>

</template>

可以看到除了最外层的 <el-form>以外,有大量的重复el-form-item以及里面各类的表单组件,既然有重复那能不能通过循环数组,然后根据数组里面的数据动态生成呢? 答案是肯定的。

利用vue提供的内置组件<component>就能够根据运行时的数据动态地选择和渲染不同的组件。通过defineProps接收父组件传递进来的数据,v-bind="$attrs"接收非props数据,就能够实现表单的配置化。

先实现一个最简单的带有验证的输入框跟密码框

首先看最外层的el-form它需要的核心属性就2个model , rulesel-form-item需要的属性就label , prop

js 复制代码
  <template>
    <el-form :model="model" :rules="rules">
      <template v-for="(item, index) in options" :key="index">
        <el-form-item :label="item.label" :prop="item.prop">
          <component
            :is="`el-${item.type}`"
            v-bind="item.attrs"
            v-model="model[item.prop]"
          >
          </component>
        </el-form-item>
      </template>
    </el-form>
  </template>

<script setup>
import { ref} from 'vue'

const props = defineProps({
  options: {
    type: Array,
    required: true
  }
})

const model = ref(null)
const rules = ref(null)
</style>

el-form定义2个响应式数据,el-form-item重复的就靠props接收父组件传进来数据循环生成, <component>负责动态渲染组件, v-bind="item.attrs"接收属性。剩下的就是配置数据了。

父组件里使用封装的组件,根据使用的表单组件,查看需要的属性名一一配置,表单就生成好了。

js 复制代码
<template>
  <div class="form">
    <m-from :options="options" label-width="100px"></m-from>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import mFrom from '@/components/content/mFrom/mFrom.vue'
const options = ref([
  // 输入框
  {
    type: 'input',
    label: '用户名',
    value: '',
    prop: 'username',
    rules: [
      {
        required: true,
        message: '请输入用户名',
        trigger: 'blur'
      },
      {
        min: 3,
        max: 5,
        message: '用户名长度在3到5个字符',
        trigger: 'blur'
      }
    ],
    attrs: {
      type: '',
      clearable: true,
      size: 'large',
      style: {
        width: '30%'
      }
    }
  },
  // 密码框
  {
    type: 'input',
    label: '密码',
    value: '',
    prop: 'password',
    rules: [
      {
        required: true,
        message: '请输入密码',
        trigger: 'blur'
      },
      {
        min: 6,
        max: 10,
        message: '用户名长度在6到10个字符',
        trigger: 'blur'
      }
    ],
    attrs: {
      type: 'password',
      showPassword: true,
      clearable: true,
      size: 'large',
      style: {
        width: '30%'
      }
    }
  }
 ])
</script>

完成这些后会有个问题,我定义的model,rules数据模型是个空里面是没有key的,所以表单中的输入元素的值与 <el-form :model="model" :rules="rules"> 建立不了双向绑定关系。

通过循环可以解决这个问题,因为这是一个响应式对象引用类型所以需要深拷贝,这里用到lodash库中的cloneDeep

js 复制代码
import cloneDeep from 'lodash/cloneDeep'
const model = ref(null)
const rules = ref(null)
onMounted(() => {
  if (props.options && props.options.length) {
    let m = {}
    let r = {}
    props.options.forEach((item) => {
      m[item.prop] = item.value
      r[item.prop] = item.rules
    })
    model.value = cloneDeep(m)
    rules.value = cloneDeep(r)
  }
})

通过循环就可将数组里prop字段的值添加到对象里面了。给在最外层 <el-form>添加 v-if="model"只有当model有值的时候才渲染整个组件,这样就完成了一个带有输入框跟密码框表单。


但是又会出现一个问题,这是一个选择器组件的示例,能看到里面除了<el-form-item><el-select>还有一个子组件,我并没有对里面这个子组件做处理,所以是渲染不出来的。下面对它进行特殊处理。

js 复制代码
<el-form-item label="Activity zone">
   <el-select v-model="form.region" placeholder="please select your zone">
    <!-- 子组件 -->
      <el-option label="Zone one" value="shanghai" />
      <el-option label="Zone two" value="beijing" />
   </el-select>
 </el-form-item>

首先数据增加children字段对子组件进行配置

js 复制代码
//下拉列表
  {
    type: 'select',
    label: '部门',
    value: '',
    prop: 'salary',
    rules: [
      {
        required: true,
        message: '--请选择部门--',
        trigger: 'blur'
      }
    ],
    attrs: {
      size: 'large',
      style: {
        width: '30%'
      }
    },
    children: [  // 子组件的配置
      {
        type: 'option',
        label: '开发部',
        value: '1'
      },
      {
        type: 'option',
        label: '人事部',
        value: '2'
      },
      {
        type: 'option',
        label: '运营部',
        value: '3'
      }
    ]
  },

然后添加判断,有children字段的就渲染,没有的就不渲染。

js 复制代码
<template>
  <el-form
    v-if="model"
    v-bind="$attrs"
    :model="model"
    :rules="rules"
    :validate-on-rule-change="false"
  >
    <template v-for="(item, index) in options" :key="index">
      <!-- 没有children才显示的 -->
      <el-form-item
        v-if="!item.children || !item.children.length"
        :label="item.label"
        :prop="item.prop"
      >
        <component
          :is="`el-${item.type}`"
          v-bind="item.attrs"
          v-model="model[item.prop]"
        >
        </component>
      </el-form-item>

      <!-- 有children才显示的 -->
      <el-form-item
        v-if="item.children && item.children.length"
        :label="item.label"
        :prop="item.prop"
      >
        <component
          :is="`el-${item.type}`"
          v-bind="item.attrs"
          v-model="model[item.prop]"
        >
          <component
            v-for="(child, i) in item.children"
            :key="i"
            :label="child.label"
            :value="child.value"
            :is="`el-${child.type}`"
          >
          </component>
        </component>
      </el-form-item>
    </template>
  </el-form>
</template>

最终效果

相关推荐
鬼谷中妖7 分钟前
JavaScript 循环与对象:深入理解 for、for...in、for...of、不可枚举属性与可迭代对象
前端
大厂码农老A12 分钟前
你打的日志,正在拖垮你的系统:从P4小白到P7专家都是怎么打日志的?
java·前端·后端
im_AMBER14 分钟前
CSS 01【基础语法学习】
前端·css·笔记·学习
DokiDoki之父17 分钟前
前端速通—CSS篇
前端·css
pixle020 分钟前
Web大屏适配终极方案:vw/vh + flex + clamp() 完美组合
前端·大屏适配·vw/vh·clamp·终极方案·web大屏
ssf198726 分钟前
前后端分离项目前端页面开发远程调试代理解决跨域问题方法
前端
@PHARAOH27 分钟前
WHAT - 前端性能指标(加载性能指标)
前端
尘世中一位迷途小书童32 分钟前
🎨 SCSS 高级用法完全指南:从入门到精通
前端·css·开源
非凡ghost37 分钟前
火狐浏览器(Firefox)tete009 Firefox 多语便携版
前端·firefox
文心快码BaiduComate37 分钟前
文心快码Comate3.5S更新,用多智能体协同做个健康管理应用
前端·人工智能·后端