通用组件封装——表单组件


背景

项目中涉及非常多的查询表单、提交的表单,引用的组件库是 Element,使用 el-form 等组件进行二次封装成一个通用的定制表单组件,只需传入配置项即可使用。

一、考虑的点

  1. 表单项配置传入一个数组,使用 v-for 遍历数组添加表单项
  2. 页面宽度变化时可能会影响表单项的布局,引入 element 的 Layout 布局,保证每个表单项所占的宽度按比例,这样宽度变化时布局不受影响
  3. 表单项可能是输入框、选择器、时间选择器等多种控件,这些控件都选择 element 组件,使用 component 动态组件实现渲染不同的组件
  4. 表单项可能是只读的文本,并且支持传入格式化的函数
  5. 表单还需要保留原有的 label 插槽和默认的表单项插槽
  6. 表单的 label 可能会出现过长的情况,要求使用 … 省略显示,鼠标悬浮显示 el-tooltip 提示框显示完整的 label
  7. 表单项的校验规则支持在表单项配置中传入
  8. 自定义的通用表单组件的 props 除表单数据对象、表单项配置数组外,尽可能少,其他的 form 需要接收的属性和事件,可通过 $attrs、$listeners 进行传递,form-item 组件中的属性和事件则通过表单项配置中的 item.attrs 和 item.listeners 传递
  9. 表单项数组中传入的项可能只在某些情况下显示,所以对表单项增加 v-if = “!item.hidden” 进行条件渲染

二、组件实现

CustomForm.vue

<template>
  <el-form
    class="custom-form"
    ref="customForm"
    :model="formModel"
    :label-width="labelWidth"
    :label-position="labelPosition"
    label-suffix=":"
    v-bind="$attrs"
    v-on="$listeners"
    @keyup.enter.native="handleEnter"
    @submit.native.prevent
  >
    <el-row :gutter="gutter">
      <template v-for="item in formItems">
        <el-col
          v-if="!item.hidden"
          :key="item.prop"
          :span="item.span"
          :offset="item.offset"
        >
          <el-form-item
            :label="item.label || ''"
            :label-width="item.labelWidth"
            :prop="item.prop"
            :rules="genRules(item)"
            :ref="item.prop"
          >
            <!-- label 插槽 -->
            <template #label>
              <slot :name="item.labelSlot" v-if="item.labelSlot" :item="item" />
              <el-tooltip
                v-else
                effect="light"
                :content="item.label || ''"
                placement="bottom"
                :disabled="tooltipDisabled"
              >
                <span
                  class="ellipsis"
                  @mouseenter="spanMouseenter($event)">
                  {{ item.label || '' }}
                </span>
              </el-tooltip>
            </template>
            <!-- form-item 插槽 -->
            <slot v-if="item.formItemSlot" :name="item.formItemSlot" />
            <!-- 只读文本 -->
            <span v-else-if="this.onlyRead || item.onlyRead">
              {{ item.formatter ? item.formatter(formModel[item.prop]) : formModel[item.prop] }}
            </span>
            <!-- 表单元素组件 -->
            <component
              v-else
              class="form-item-component"
              :is="item.type"
              v-model="formModel[item.prop]"
              v-bind="item.attrs"
              v-on="item.listeners"
            >
              <template v-if="item.type === 'el-select'">
                <el-option
                  v-for="opt in getOptions(item.options)"
                  :key="opt.value"
                  :label="opt.label"
                  :value="opt.value"
                  :disabled="opt.disabled"
                ></el-option>
              </template>
            </component>
          </el-form-item>
        </el-col>
      </template>
    </el-row>
  </el-form>
</template>

<script>
import { Form, FormItem, Row, Col, Option } from 'element-ui'

export default {
  name: 'CustomForm',
  props: {
    gutter: {
      type: Number,
      default: 16
    },
    labelWidth: {
      type: String,
      default: '120px'
    },
    labelPosition: {
      type: String,
      default: 'left'
    },
    onlyRead: Boolean, // 只读
    formItems: Array, // 表单配置
    formModel: Object, // 表单数据
  },
  components: {
    [Form.name]: Form,
    [FormItem.name]: FormItem,
    [Row.name]: Row,
    [Col.name]: Col,
    [Option.name]: Option,
  },
  data() {
    return {
      tooltipDisabled: false
    }
  },
  computed: {},
  methods: {
    // 回车回调方法
    handleEnter() {
      this.$emit('enter')
    },
    // 获取当前表单项校验规则
    genRules(item) {
      const { rules, label, required = false } = item
      return rules || [{ required, message: `${label}不能为空`, trigger: item.trigger || 'blur' }]
    },
    // 获取枚举
    getOptions(option) {
      return typeof option === 'function' ? option() : option
    },
    spanMouseenter(ev) {
      if (ev.target.clientWidth < ev.target.scrollWidth) {
        this.tooltipDisabled = false
      } else {
        this.tooltipDisabled = true
      }
    },
  },
}
</script>

<style lang="scss" scoped>
.ellipsis {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
</style>

三、使用示例

template

<CustomForm
  ref="form"
  label-position="top"
  :formModel="formModel"
  :formItems="formItems"
  @enter="handleQuery"
>
  <template #amountSlot="{ item }">
     {{ toThausands(formModel[item.prop]) }}
  </template>
</CustomForm>

js - 配置项仅供参考

formModel: {
  taxRate: '',
  amount: '',
  billStatus: ''
},
formItems: [
  {
    label: '推送状态',
    prop: 'pushStatus',
    type: 'el-select',
    options: () => this.pushStatusOptions,
    attrs: {
      placeholder: '请选择...',
      clearable: true
    },
    span: 6
  },
  {
    label: '税率',
    prop: 'taxRate',
    type: 'text',
    onlyRead: true,
    formatter: val => {
      if (val === 0) return 0
      return val ? parseInt(val) + '%' : ''
    },
    span: 6
  },
  {
    label: '金额(元)',
    prop: 'amount',
    formItemSlot: 'amountSlot',
    span: 6
   },
   {
     label: '隐藏',
     prop: 'hide',
     hidden: true
     span: 6
   },
   // ...
]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值