Vue2二次封装el-form通过配置项生成表单

在实现表单功能的时候,我们经常会选择使用el-form,但是如果表单项比较多,使用和修改起来都会比较繁琐,那可不可以通过配置项来生成表单呢,说干就干。通过二次封装el-form实现下面的需求。

一、封装布局组件
1、封装grid网格布局组件

先不要急着去实现,思考观察一下,整个页面就是一个表单,它大致分为两列,那么我们首先要考虑的是如何布局,这里我使用的是grid布局,先封装一个网格布局盒子。盒子的样式通过计算属性绑定,计算属性整合默认样式和通过配置项传入的样式。下面就实现了一个两列的网格布局。

Grid.js

<template>
  <div :style="style">
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: 'Grid',
  props: {
    // 传递配置详情可以参考网格布局属性
    gridProps: {
      type: Object,
      default: () => {}
    }
  },
  computed: {
    style () {
      return {
        display: 'grid',
        gridGap: '10px',
        gridTemplateColumns: `repeat(2, minmax(300px, 1fr))`,
        gridAutoRows: '50px',
        gridTemplateRows: '50px',
        alignItems: 'center',
        ...this.gridProps
      }
    }
  }
}
</script>
<style scoped>
</style>
2、封装网格布局的单元格组件

整体的组件封装完毕后,对网格的单元格也进行一次封装,逻辑大致和上面一样,样式也是可以通过配置项传入。

GridItem.js

<template>
  <div :style="gridItemProps">
    <slot></slot>
  </div>
</template>
<script>
export default{
  name: 'GridItem',
  props: {
    // 传递配置详情可以参考网格布局单元格属性
    gridItemProps: {
      type: Object,
      default: () => {}
    }
  }
}
</script>

<style scoped>

</style>

布局到这儿就搞定了,步入正题开始写表单组件,行数有点多,请耐心观看。

二、封装表单组件

为什么最外层需要包裹一个el-form,大家应该都知道,第一是绑定表单数据,第二是绑定表单的校验规则等。当然,这里都是使用时传入的。引入上面封装好的布局组件,每个单元格组件中放入一个表单项,表单项通过遍历传入的配置项渲染。在这里除了表单项的渲染外还加入了标题,标题占独占一行,默认就是两列。表单项则通过component组件进行动态渲染,考虑到自己会定义一些组件以及el组件中有些需要选项的组件,这里把他单独拿出去封装,然后引入存放在对象里,动态渲染时先判断该对象里是否有相应的组件,没有就使用el的组件。

这里有一个重要的点,就是el组件的样式,它默认的宽度不是100%,所以我此处样式穿透,修改了它的宽度,使用时有组件宽度没有占满,可以在最下面我注释的地方设置其宽度

ProForm.js

<template>
<div>
  <el-form v-bind="formProps" :model="formData">
    <Grid :gridProps="gridProps">
      <grid-item v-for="item in formColumns" :key="item.prop" :gridItemProps="item.gridItemProps" v-if="!item.isHide">
        <!--标题-->
        <div v-if="item.component==='title'" class="box-title"><span />{{item.label}}</div>
        <!--表单项-->
        <el-form-item v-else :label="item.label" :prop="item.prop" :formItemProps="item.formItemProps">
          <component
            :is='getComponent(item.component)'
            v-model="formData[item.prop]"
            :enums="item.enums"
            v-bind="item.componentProps"
            @formChange="formChange(item.prop, $event)"
          />
        </el-form-item>
      </grid-item>
    </Grid>
  </el-form>
</div>
</template>

<script>
import Grid from './Grid.vue'
import GridItem from './GridItem.vue'
// 该部分为自己封装的一些需要传递选项的el组件和自定义组件,设置静态的对象存储它们
import selectWidget from './components/select-widget.vue'
import cascaderWidget from './components/cascader-widget.vue'
import checkboxWidget from './components/checkbox-widget.vue'
import radioWidget from './components/radio-widget.vue'
import buttonWidget from './components/button-widget.vue'
const widgets = {selectWidget, cascaderWidget, checkboxWidget, radioWidget, buttonWidget}
export default {
  name: 'ProForm',
  components: {
    Grid,
    GridItem
  },
  props: {
    // 网格布局配置
    gridProps: {
      type: Object,
      default: () => {}
    },
    // 表单配置
    formProps: {
      type: Object,
      default: () => {
        return {
          labelWidth: '100px', // 表单项标签宽度
          rules: {} // 表单校验规则
        }
      }
    },
    // 表单项配置
    formColumns: {
      type: Array,
      default: () => {
        return {
          prop: null, // 字段名
          label: null, // 标签
          component: null, // 组件
          enums: null, // 枚举类型
          isHide: false, // 是否隐藏
          gridItemProps: null, // 网格单元格的配置
          formItemProps: null, // 单一表单项的配置
          componentProps: null // 组件的配置
        }
      }
    },
    // 表单数据
    formData: {
      type: Object,
      default: () => {}
    }
  },
  methods: {
    // 获取对应组件
    getComponent (name) {
      return widgets[`${name}Widget`] || `el-${name}`
    },
    /**
     * 向上抛表单项数据变化事件
     * @param prop 字段名
     * @param value 值
     */
    formChange (prop, value) {
      this.$emit('formChange', prop, value)
    }
  }
}
</script>

<style scoped>
.box-title {
  display: flex;
  width: 100%;
  margin-bottom: 10px;
  font-size: 16px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.box-title span{
  display: inline-block;
  width: 4px;
  height: 18px;
  margin-right: 5px;
  margin-bottom: 3px;
  vertical-align: middle;
  background: #0f64a9;
}
/deep/ .el-form-item{
  display: flex;
  width: 100%;
}
/* 使表单项内容垂直居中*/
/deep/ .el-form-item__content{
  display: flex;
  flex: 1;
  margin-left: 0 !important;
  align-items: center;
}
/* 在这里修改element各个组件的宽度*/
/deep/ .el-select,
.el-date-editor{
  width: 100%;
}
</style>
三、二次封装特殊的el组件

下面为el中需要选项的组件,把它拎出来二次封装,因为绑定的变量是表单组件传递过来的,所以在该组件中不能直接v-model绑定,需要代理处理一下。

<template>
  <el-select v-bind="$attrs" v-model="vModelValue">
    <el-option v-for="option in enums" :key="option.value" v-bind="option" />
  </el-select>
</template>
<script>
import {vModelMixin} from '../../mixins'
export default {
  name: 'SelectWidget',
  mixins: [vModelMixin],
  props: {
    enums: {
      type: Array,
      default: () => []
    }
  }
}
</script>
<style></style>
四、编写代理对象的混合
export const vModelMixin = {
  inheritAttrs: false,
  // 更改v-model的默认属性名和事件名
  model: {
    prop: 'modelValue',
    event: 'modelChange'
  },
  props: {
    modelValue: {
      default: ''
    },
    prop: {
      type: String,
      default: ''
    }
  },
  computed: {
    vModelValue: {
      get () {
        return this.modelValue
      },
      set (val) {
        this.modelChange(val)
      }
    }
  },
  methods: {
    // 值变化时触发该事件,同时向上抛出表单数据变化formChange事件,方便外面监听数据的变化
    modelChange (val) {
      this.$emit('modelChange', val)
      this.$emit('formChange', val)
    }
  }
}
五、使用示例

到这里一个简单的自定义表单就写完了,如何使用呢,可以参考下面的示例

<template>
  <div>
     <pro-form :form-props="formProps"
     :form-columns="formColumns"
     :form-data="formData"></pro-form>
    </div>
</template>

<script>
import ProForm from './ProForm.vue'
export default {
  name: 'HelloWorld',
  components: {ProForm},
  data () {
    return {
      formColumns: [
        {prop: 'title1', label: '基本信息', component: 'title', gridItemProps: {gridColumnStart: 'span 2'}},
        {prop: 'name', label: '姓名', component: 'input'},
        {prop: 'sex',
          label: '性别',
          component: 'select',
          // isHide: true,
          enums: [
            {label: '男', value: '0'},
            {label: '女', value: '1'}
          ]
        },
        {prop: 'button', label: '附件', component: 'button'},
        {
          prop: 'date',
          label: '出生日期',
          component: 'date-picker'
        }
      ], // 表单项配置
      formProps: {
        labelWidth: '100px',
        rules: {
          name: [
            { required: true, message: '请输入姓名', trigger: 'blur' }
          ]
        }
      }, // 表单配置
      formData: {
        name: '帅哥',
        sex: '1'
      } // 表单绑定数据
    }
  }
}
</script>

<style scoped>
</style>
总结

最后感谢大家的观看,有不对的地方还请及时指正。本人前端菜鸟,上述内容都是在工作中所学,分享给大家,便于相互成长,相互进步。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值