首页>>前端>>Vue->Vue2 Element Schema Form 配置式生成表单的实现

Vue2 Element Schema Form 配置式生成表单的实现

时间:2023-11-30 本站 点击:0

前置知识点

为了实现一个Schema Form配置式表单的生成,需要了解一部分前置知识点。

Component

vue 提供了一个内置组件 Component,用来动态渲染组件,举个例子,本篇文章以Element UI 为例,假设我们全局注册了Element UI的组件,那么下面一段代码

 <Component is="el-input"></Component>

将被渲染为el-input组件。

好了,最重要的实现知识点已经完了,只要了解了这一点,我这个Schema Form的实现思路,你就能完全看懂了,就这么简单。

但是为了我们表单的易用性,我们再了解一点儿其他的。

比如Component组件并非只能接收一个字符串,渲染全局组件,同时可以接收一个Component组件,也就是说,当我们Element UI 组件没有全局注册的时候,我们可以通过import { ElInput } from 'element',传递给Component组件,同样可以完成渲染一个el-input组件的功能。

$attrs

第二个知识点,vue属性透传,假如你有这么一个疑惑 —— 我对el-input进行了二次封装,那el-input接收的props我是否需要在二次封装的组件中进行props的定义,再逐一传递给el-input才能生效。

如果有这样的疑惑那么$attrs可以帮你,对于二次封装的组件,通过定义v-bind="$attrs",传递给父组件的属性即可透传给el-input,于是就有了这么一个疑问,$attrs可以绑定给v-bind,那么一个普通的对象可以吗,答案是可以的。

这是我们 schema form 变得好用的第二个重要的知识点。

$listeners

$attrs 相同,做事件透传的,通过v-on绑定。

以上就是我认为,实现Schema Form配置式生成表单所需要掌握的全部知识及扩展思考,接下来,我们简单完成一个配置式表单.

表单的结构是什么样的

1、我可能希望对表单进行一部分特殊的处理,所以,包一层div总是没错的,当然,这是我的习惯,你们可以自行选择。

2、既然是表单,那一定被el-form包裹。

3、为了配置式布局的需要,我希望引入el-row el-col栅格系统

4、无论如何完善表单,必然不可能满足所有要求,那最简单的方式就是抛出slot

5、有些时候我希望渲染文本呢?有些时候我希望渲染字段的值而不涉及任何组件呢?

6、最后才是渲染对应的组件

那么,一个表单的结构化雏形就完成了。

<div class="schema-form">    <el-form>        <el-row>            <el-col v-for="item in config" :key="item.key">                <el-form-item>                    <slot v-if="item.component === 'slot'" :name="item.slotName"></slot>                    <div v-else-if="item.component === 'innerText'"></div>                    <template v-else>                        渲染对应组件,但需要对某些组件特殊处理                    </template>                </el-form-item>            </el-col>        </el-row>    </el-form></div>

从上面的结构中,我们再来思考,我们的配置config数组的字段结构应该是什么样的。

配置数组数据结构

1、el-form 可能需要一些字段注入,作为最外层的组件,将传入schema form的字段都给它

2、el-form 需要传入一个数据源,这个数据源可以内部定义再传给外部,也可以传入一个对象,利用对象的特性做双向绑定,我选择了后者

3、el-col项可能有时不显示,所以config上扩展一个字段hidden,用于控制该项是否显示。

4、el-form-item的属性透传

5、innerText的样式定义

一些特殊的描述出来了,其余的再赘述,那么,config的数据结构就是这样

{    key: '', // 当前字段的 key 值,同时会传到 el-form-item 的prop,不传数据验证和重置会失效    label: '', // 当前 el-form-item 的label    hidden: '', // 当前表单项是否展示    labelWidth: '', // el-form-item 的label宽度    component: '', // 支持 slot、innerText、Component,渲染成什么    slotName: '', // compoment 为 slot 时,该值为对应slot的名字供外部使用    innerClass: '', // component 为 innerText 时,给文本的样式,通常为全局样式    innerStyle: '', // 同上    innerText: '', // component 为 innerText 时,优先显示的文本,否则会显示当前的字段值    itemProps: '', // 注入到 el-form-item 的属性    props: '', // 当 component 为渲染组件时,注入到渲染组件当中的属性    listeners: '', // 当 component 为渲染组件时,注入到渲染组件当中的事件}

当把这些实现之后,其余需要扩展的功能可以自行实现,我这里也只是在业务中的扩展,并非完善的。

于是,我们的表单组件就变成了这样

<template>  <div class="nx-form">    <el-form      ref="form"      v-bind="$attrs"      :model="source"      class="nx-form"    >      <el-row :gutter="20">        <el-col          v-for="item in config"          :key="item.key"          :span="item.hidden ? 0 : item.span || 8"        >          <el-form-item            v-if="!item.hidden"            :label="item.label"            :prop="item.key"            :label-width="item.labelWidth || labelWidth || '120px'"            v-bind="item.itemProps"          >            <slot v-if="item.component === 'slot'" :name="item.slotName"></slot>            <div              v-else-if="item.component === 'innerText'"              :class="item.innerClass"              :style="item.style"            >              {{ item.innerText || source[item.key] }}            </div>            <template v-else>              <div class="nx-flex-align-center">                <component                  :is="item.component"                  v-model="source[item.key]"                  style="width: 100%;flex: 1;"                  v-bind="item.props"                  v-on="item.listeners"                >                  <template v-if="item.component === 'el-radio-group'">                    <el-radio                      v-for="(radio, radioIndex) in item.data"                      :key="radioIndex"                      style="margin-top: 10px;"                      :label="radio.value"                      :disabled="radio.disabled"                    >                      {{ radio.label }}                    </el-radio>                  </template>                  <template v-if="item.component === 'el-checkbox-group'">                    <el-checkbox                      v-for="(checkbox, checkboxIndex) in item.data"                      :key="checkboxIndex"                      :label="checkbox.value"                    >                      {{ checkbox.label }}                    </el-checkbox>                  </template>                  <template v-if="item.component === 'el-select'">                    <el-option                      v-for="(option, optionIndex) in item.data"                      :key="optionIndex"                      :label="option.label"                      :value="option.value"                    ></el-option>                  </template>                </component>                <div                  v-if="item.after"                  class="nx-form__after"                >                  {{ item.after }}                </div>              </div>              <div                v-if="item.tips"                class="nx-form__tips"                v-html="item.tips"              ></div>            </template>          </el-form-item>          <!-- <div            v-if="item.tips"            :style="{ marginLeft: item.labelWidth || '120px' }"            class="nx-form__tips"            v-html="item.tips"          ></div> -->        </el-col>      </el-row>    </el-form>  </div></template>export default {  name: 'NxForm',  components: {},  props: {    config: {      type: Array,      default: () => []    },    source: {      type: Object,      default: () => ({})    }  },  methods: {    resetFields() {      this.$refs.form.resetFields();    },    clearValidate() {      this.$refs.form.clearValidate();    },    async validate() {      const valid = await this.$refs.form.validate();      return valid;    }  }};

可以看到,我扩展了一个data字段,用来作为el-select el-checkbox-group el-radio-group的数据源,这些特殊的组件单独列出就行了,基本上也之后有这么几个。

methods里也只是为了方便添加了一些常用的form方法,基本不做逻辑处理。

现在看一下,这样的配置可以生成怎样的表单

注意:我这里定义了一部分全局样式,比如在schema form下的组件,全部占满整格,使其比较美观。

生成这样一个常用的筛选项表单

<template>  <div>    <nx-form      ref="searchForm"      :source="searchForm"      :config="searchConfig"    >      <div slot="search">        <el-button type="primary" @click="$refs.nxTable.search(1)">查询</el-button>        <el-button @click="reset()">重置</el-button>      </div>    </nx-form>  </div></template><script>export default {  data() {     searchForm: {        keyWord: '',         newsType: '',         newsStatus: '',         publishTime: [],        createTime: []     },  },  computed: {      searchConfig() {      return [        {          key: 'keyWord',          component: 'el-input',          span: 8,          label: '关键字',          props: {            placeholder: '标题/创建人',            clearable: true          }        },        {          key: 'newsType',          component: 'el-select',          span: 8,          label: '类型:',          props: {            placeholder: '请选择',            clearable: true          },          data: this.newsTypes        },        {          key: 'newsStatus',          component: 'el-select',          span: 8,          label: '状态:',          props: {            placeholder: '请选择',            clearable: true          },          data: statusTypes        },        {          key: 'publishTime',          component: 'el-date-picker',          label: '发布时间:',          span: 8,          props: {            clearable: true,            valueFormat: 'timestamp',            type: 'datetimerange',            defaultTime: ['00:00:00', '23:59:59'],            rangeSeparator: '-',            startPlaceholder: '请选择开始时间',            endPlaceholder: '请选择结束时间'          }        },        {          key: 'createTime',          component: 'el-date-picker',          label: '创建时间:',          span: 8,          props: {            clearable: true,            valueFormat: 'timestamp',            type: 'datetimerange',            defaultTime: ['00:00:00', '23:59:59'],            rangeSeparator: '-',            startPlaceholder: '请选择开始时间',            endPlaceholder: '请选择结束时间'          }        },        {          component: 'slot',          slotName: 'search',          span: 8,          labelWidth: '0'        }      ];    }  }}</script>

其余的找了一些,也没啥特别的,就不贴了,简单来说,这个百来行的组件,应用在多个大型项目当中,易用性,扩展性,没有出现什么问题,配合我们自定义的其他table组件、dialog组件,十分钟就可以实现一个完整的B端增删改查。

值得一提的是,为了表单的易用性,我们基本上所有的自定义组件都支持了使用v-model做数据绑定,达到简单易用的效果。

结语

代码是找了一个很久的项目写的一个思路,之后的项目中都有改进优化,大致只是分享一个思路,如果有什么想法的,欢迎指正。

原文:https://juejin.cn/post/7099644339449659429


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