vue2+ant-design-vue a-form-model组件二次封装(form表单组件)

一、效果图

二、参数配置

1、代码示例

html 复制代码
<t-antd-form
  :ref-obj.sync="formOpts.ref"
  :formOpts="formOpts"
  :widthSize="1"
  :labelCol="{ span:2}"
  :wrapperCol="{ span:22}"
  @handleEvent="handleEvent"
/>

2. 配置参数继承FormModel的所有属性

参数 说明 类型 默认值
refObj form 表单校验规则方法 (可以参考 antd FormModel 表单方法中的 validate) obj -
className 自定义类名 String -
layout 改变表单项 label 与输入框的布局方式(默认:horizontal) /vertical String 'horizontal'
widthSize 每行显示几个输入项(默认两项) 最大值 4 Number 2
isTrim 全局是否开启清除前后空格(comp 为 a-input 且 type 不等于'password') Boolean true
formOpts 表单配置项 Object {}
---listTypeInfo 下拉选择数据源(type:'select'有效) Object {}
---fieldList form 表单每项 list Array \[\]
------isHideItem 某一项不显示 Boolean false
------slotName 自定义表单某一项输入框 slot -
------childSlotName 自定义表单某一下拉选择项子组件插槽(a-select-option) slot -
------comp form 表单每一项组件是输入框还是下拉选择等(可使用第三方 UI 如 a-select/a-input 也可以使用自定义组件) String -
------formItemBind 表单每一项属性(继承FormModelItem的 Attributes) Object {}
------bind 表单每一项属性(继承第三方 UI 的 Attributes,如 a-input 中的 allowClear 清空功能)默认清空及下拉过滤 Object {}
------isTrim 是否不清除前后空格(comp 为 a-input 且 type 不等于'password') Boolean false
------type form 表单每一项类型 String -
------widthSize form 表单某一项所占比例(如果占一整行则设置 1) Number 2
------width form 表单某一项所占实际宽度 String 100%
------arrLabel type=select-arr 时,每个下拉显示的中文 String 'label'
------arrKey type=select-arr 时,每个下拉显示的中文传后台的数字 String 'value'
------label form 表单每一项 title String -
------labelRender 自定义某一项 title function -
------value form 表单每一项传给后台的参数 String -
------rules 每一项输入框的表单校验规则 Object/Array -
------list 下拉选择数据源(仅仅对 type:'select'有效) String -
------event 表单每一项事件标志(handleEvent 事件) String -
------eventHandle 继承 comp 组件的事件(返回两个参数,第一个自己自带,第二个 formOpts) Object -
------isSelfCom 是否使用自己封装的组件(TAntdSelect等---含有下拉框) Boolean false
---formData 表单提交数据(对应 fieldList 每一项的 value 值) Object -
---labelCol label 宽度({ span:2}) Object {span:2}
---wrapperCol 输入框 宽度 Object {span:22}
---rules 规则(可依据 AntdUI FormModel 配置------------对应 formData 的值) Object/Array -
---operatorList 操作按钮 list Array -

3. events继承FormModel的所有事件

事件名 说明 返回值
handleEvent 单个查询条件触发事件 fieldList 中 type/查询条件输入的值/fieldList 中 event 值

4. Methods

事件名 说明 参数
resetFields 重置表单 -
clearValidate 清空校验 -

5. 关于 Ant-Design-Vue FormModel/FormModelItem 提供的一些属性可直接使用,无需其他配置

三、源码

html 复制代码
<template>
  <FormModel
    ref="form"
    class="t_antd_form"
    :class="className"
    :model="formOpts.formData"
    :rules="formOpts.rules"
    :layout="formOpts.layout||'horizontal'"
    v-bind="formAttr"
    v-on="$listeners"
  >
    <template v-for="(item, index) in fieldList">
      <FormModelItem
        v-if="!item.isHideItem"
        :key="index"
        :prop="item.value"
        :label="item.label"
        :class="[item.className]"
        :rules="item.rules"
        :style="getChildWidth(item)"
        v-bind="{...item.formItemBind}"
      >
        <!-- 自定义label -->
        <template #label v-if="item.labelRender">
          <render-comp :createElementFunc="item.labelRender" />
        </template>
        <!-- 自定义输入框插槽 -->
        <template v-if="item.slotName">
          <slot :name="item.slotName"></slot>
        </template>
        <!-- 文本展示值 -->
        <template v-if="item.textShow">
          <span>{{item.textValue||formOpts.formData[item.value]}}</span>
        </template>
        <template v-if="item.isSelfCom">
          <component
            :is="item.comp"
            v-model="formOpts.formData[item.value]"
            :placeholder="item.placeholder||getPlaceholder(item)"
            v-bind="{allowClear:true,showSearch:true,...item.bind}"
            :style="{width: item.width||'100%'}"
            v-on="cEvent(item)"
          />
        </template>
        <component
          v-if="!item.slotName&&!item.textShow&&!item.isSelfCom"
          :is="item.comp"
          v-model="formOpts.formData[item.value]"
          :type="item.type||item.bind.type"
          :mode="item.comp.includes('picker')?(item.type||item.bind.type):''"
          :placeholder="item.placeholder||getPlaceholder(item)"
          @change="handleEvent(item.event, formOpts.formData[item.value],item)"
          v-bind="{allowClear:true,showSearch:true,...item.bind}"
          :style="{width: item.width||'100%'}"
          v-on="cEvent(item)"
        >
          <template #addonBefore v-if="item.addonBefore">{{ item.addonBefore }}</template>
          <template #addonAfter v-if="item.addonAfter">{{ item.addonAfter }}</template>
          <template v-if="item.childSlotName">
            <slot :name="item.childSlotName"></slot>
          </template>
          <component
            v-else
            :is="compChildName(item)"
            v-for="(value, key, index) in selectListType(item)"
            :key="index"
            :disabled="value.disabled"
            :label="compChildLabel(item,value)"
            :value="compChildValue(item,value,key)"
          >{{compChildShowLabel(item,value)}}</component>
        </component>
      </FormModelItem>
    </template>
    <!-- 按钮 -->
    <div class="footer_btn">
      <template v-if="formOpts.btnSlotName">
        <slot :name="formOpts.btnSlotName"></slot>
      </template>
      <template v-if="!formOpts.btnSlotName&&formOpts.operatorList&&formOpts.operatorList.length>0">
        <Button
          v-for="(val,index) in formOpts.operatorList"
          :key="index"
          @click="val.fun(val)"
          :type="val.type||'primary'"
          :icon="val.icon"
          :size="val.size || 'default'"
          :disabled="val.disabled"
        >{{ val.label }}</Button>
      </template>
    </div>
  </FormModel>
</template>
<script>
import RenderComp from './render-comp.vue'
import { FormModel, Button } from 'ant-design-vue'
export default {
  name: 'TAntdForm',
  components: {
    RenderComp,
    FormModel,
    FormModelItem: FormModel.Item,
    Button
  },
  props: {
    /** 表单配置项说明
     * formData object 表单提交数据
     * rules object 验证规则
     * fieldList Array 表单渲染数据
     * operatorList Array 操作按钮list
     * listTypeInfo object 下拉选项数据
     */
    formOpts: {
      type: Object,
      default: () => ({})
    },
    // 自定义类名
    className: {
      type: String
    },
    // 一行显示几个输入项;最大值4
    widthSize: {
      type: Number,
      default: 2,
      validator: (value) => {
        return value <= 4
      }
    },
    // 全局是否开启清除前后空格
    isTrim: {
      type: Boolean,
      default: true
    },
    // ref
    refObj: {
      type: Object
    }
  },
  data() {
    return {
      colSize: this.widthSize,
      fieldList: this.formOpts.fieldList
    }
  },
  computed: {
    formAttr() {
      let attr = {}
      this.formOpts.layout === 'vertical'
        ? attr = {
          ...this.$attrs
        } : attr = {
          labelCol: { span: 2 },
          wrapperCol: { span: 22 },
          ...this.$attrs
        }
      return attr
    },
    cEvent() {
      return ({ eventHandle }, type) => {
        let event = { ...eventHandle }
        let changeEvent = {}
        Object.keys(event).forEach(v => {
          changeEvent[v] = (e, ids) => {
            if (type === 't-antd-select-table') {
              event[v] && event[v](e, ids, arguments)
            } else {
              if ((typeof e === 'number' && e === 0) || e) {
                event[v] && event[v](e, this.formOpts, arguments)
              } else {
                event[v] && event[v](this.formOpts, arguments)
              }
            }
          }
        })
        return { ...changeEvent }
      }
    },
    selectListType() {
      return ({ list }) => {
        if (this.formOpts.listTypeInfo) {
          return this.formOpts.listTypeInfo[list]
        } else {
          return []
        }
      }
    },
    // 子组件名称
    compChildName() {
      return ({ type }) => {
        switch (type) {
          case 'checkbox':
            return 'a-checkbox'
          case 'radio':
            return 'a-radio'
          case 'select-arr':
          case 'select-obj':
            return 'a-select-option'
        }
      }
    },
    // 子子组件label
    compChildLabel() {
      return ({ type, arrLabel }, value) => {
        switch (type) {
          case 'radio':
          case 'checkbox':
            return value.value
          case 'select-arr':
            return value[arrLabel || 'label']
          case 'select-obj':
            return value
        }
      }
    },
    // 子子组件value
    compChildValue() {
      return ({ type, arrKey }, value, key) => {
        switch (type) {
          case 'radio':
          case 'checkbox':
            return value.value
          case 'select-arr':
            return value[arrKey || 'value']
          case 'select-obj':
            return key
        }
      }
    },
    // 子子组件文字展示
    compChildShowLabel() {
      return ({ type, arrLabel }, value) => {
        switch (type) {
          case 'radio':
          case 'checkbox':
            return value.label
          case 'select-arr':
            return value[arrLabel || 'label']
          case 'select-obj':
            return value
        }
      }
    }
  },
  watch: {
    'formOpts.formData': {
      handler(val) {
        // 将form实例返回到父级
        this.$emit('update:refObj', this.$refs.form)
      },
      deep: true // 深度监听
    },
    widthSize(val) {
      if (val > 4) {
        this.$message.warning('widthSize值不能大于4!')
        this.colSize = 4
      } else {
        this.colSize = val
      }
    }
  },
  mounted() {
    // 将form实例返回到父级
    this.$emit('update:refObj', this.$refs.form)
  },

  methods: {
    // label与输入框的布局方式
    getChildWidth(item) {
      if (this.formOpts.layout === 'vertical') {
        return `flex: 0 1 calc((${100 / (item.widthSize || this.colSize)}% - 10px));margin-right:10px;`
      } else {
        return `flex: 0 1 ${100 / (item.widthSize || this.colSize)}%;`
      }
    },
    // 得到placeholder的显示
    getPlaceholder(row) {
      let placeholder
      if (typeof row.comp === 'string' && row.comp) {
        if (row.comp.includes('input')) {
          placeholder = row.label ? `请输入${row.label}` : `请输入`
        } else if (row.comp.includes('select') || row.comp.includes('cascader')) {
          placeholder = row.label ? `请选择${row.label}` : `请选择`
        } else if (!row.comp.includes('t-antd-date-picker')) {
          placeholder = row.label
        }
      } else {
        placeholder = row.label
      }
      return placeholder
    },
    // 绑定的相关事件
    handleEvent(type, val, item) {
      // console.log('组件', type, val, item)
      // 去除前后空格
      if (this.isTrim && !item.isTrim && item.comp.includes('input') && item.type !== 'password' && item.type !== 'inputNumber') {
        this.formOpts.formData[item.value] = this.formOpts.formData[item.value].trim()
      }
      this.$emit('handleEvent', type, val)
    },
    validate() {
      // selfValidate() {
      return new Promise((resolve, reject) => {
        this.$refs.form.validate(valid => {
          if (valid) {
            resolve({
              valid,
              formData: this.formOpts.formData
            })
          } else {
            // eslint-disable-next-line prefer-promise-reject-errors
            reject({
              valid,
              formData: null
            })
          }
        })
      })
    },
    // 重置表单
    resetFields() {
      return this.$refs.form.resetFields()
    },
    // 清空校验
    clearValidate() {
      return this.$refs.form.clearValidate()
    }
  }
}
</script>
<style lang="scss">
.t_antd_form {
  display: flex;
  flex-wrap: wrap;
  .ant-select,
  .ant-calendar-picker {
    width: 100%;
  }
  .footer_btn {
    display: flex;
    align-items: center;
    justify-content: center;
    margin-top: 5px;
    width: 100%;
    .ant-btn {
      margin-left: 10px;
    }
    .ant-btn:first-child {
      margin-left: 0;
    }
  }
}
</style>

四、组件地址

gitHub组件地址

gitee码云组件地址

五、相关文章

基于ElementUi再次封装基础组件文档


基于ant-design-vue再次封装基础组件文档


vue3+ts基于Element-plus再次封装基础组件文档

相关推荐
Momo__1 小时前
VueUse createReusableTemplate —— 单文件组件内的模板复用神器
前端·vue.js
程序员小富1 小时前
我开源了一个开发者专属的智能 JSON 工具,得到了媳妇高度认可
前端·vue.js·后端
小小小小宇1 小时前
程序员如何给 LLM 装工具以及看懂推理过程
前端
写代码的皮筏艇1 小时前
React中的forwardRef
前端·react.js·面试
槑有老呆1 小时前
花三个月工资请了个 AI 程序员,结果它连青岛啤酒股价都查不了
前端
风骏时光牛马1 小时前
Verilog开发常见问题汇总解析
前端
子兮曰1 小时前
AI Coding Method Map:一张图看懂 AI 编程的完整链路
前端·人工智能·后端
weedsfly1 小时前
语法糖褪去之后——Babel 转译产物中的 JavaScript 本貌
前端·javascript
JustHappy1 小时前
「软件设计思想杂谈🤔」“切图仔”也能懂编译原理?框架源码也许没那么难。聊聊 Vue 的编译(上)
前端·javascript·vue.js