探索 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>

最终效果

相关推荐
Martin -Tang9 分钟前
vite和webpack的区别
前端·webpack·node.js·vite
迷途小码农零零发10 分钟前
解锁微前端的优秀库
前端
王解1 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
我不当帕鲁谁当帕鲁1 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂1 小时前
工程化实战内功修炼测试题
前端·javascript
放逐者-保持本心,方可放逐2 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
毋若成4 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css
红中马喽4 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
Black蜡笔小新5 小时前
网页直播/点播播放器EasyPlayer.js播放器OffscreenCanvas这个特性是否需要特殊的环境和硬件支持
前端·javascript·html
秦jh_6 小时前
【Linux】多线程(概念,控制)
linux·运维·前端