简单但是实用的表单组件封装

226 阅读4分钟

简单但是实用的表单组件封装

提醒:下面代码不展示css部分,仅提供给大家一个思路

实现功能

通过传递配置实现表单开发

设置表单布局,确定输入框类型,以及调整样式

通过插槽

实现标题,按钮等元素的位置存放

使用Element Plus组件库

Form表单的各种属性,自行查看文档
Form 表单 | Element Plus
Layout 布局 | Element Plus

Form组件基本使用+Layout布局

<el-form :model="form" label-width="120px">
	<el-row>
   <el-col :span="6">
    <el-form-item label="Activity name">
      <el-input v-model="form.name" />
    </el-form-item>
   </el-col> 
  </el-row>  
</el-form>

正式开始封装

文件目录:
users.vue是爷爷组件
page-search.vue是爸爸组件
form.vue是子组件
解释:为什么要这样分
users是主页面 —— 负责使用组件,展示页面的
page-search.vue —— 配置本项目可能用到的关于搜索框所有的公共部分,包括插槽的使用
form.vue —— 为确保其他项目也能使用此组件,只能封装其他项目也能用的公共代码
是层层递进的,确保代码更好地复用

第一步:设置接口,继承Form表单的属性值/设置自己需要的属性:

// 设置表单类型
type IFormType = 'input' | 'password' | 'select' | 'datepicker'
// 定义接口,继承FormItem的属性 以下简单写了几个属性,定义
export interface IFormItem {
  // 将此属性值设置为当我们进行搜索功能时,后端需要我们传递搜索数据的属性名
  field?: string
  type: IFormType
  label: any
  rules?: any[]
  placeholder?: any
  options?: any[]
  otherOptions?: any // 设置一些特殊的属性
}

// 定义接口,继承Form的属性,和定义布局属性 
export interface IForm {
  formItems: IFormItem[]
  labelWidth?: string
  itemStyle?: any
  colLayout?: any
}

第二步:编写动态生成表单的配置

配置一

import type { IForm } from '@/base-ui/form/type'
export const formConfig: IForm = {
  formItems: [
    {
      field: 'id',
      type: 'input',
      label: 'id',
      placeholder: '请输入id'
    },
    {
      field: 'name',
      type: 'input',
      label: '用户名',
      placeholder: '请输入用户名'
    },
    {
      field: 'realname',
      type: 'input',
      label: '真实姓名',
      placeholder: '请输入真实姓名'
    },
    {
      field: 'cellphone',
      type: 'input',
      label: '电话号码',
      placeholder: '请输入电话号码'
    },
    {
      field: 'enable',
      type: 'select',
      label: '用户状态',
      options: [
        { title: '启用', value: 1 },
        { title: '禁用', value: 2 }
      ]
    },
    {
      field: 'createAt',
      type: 'datepicker',
      label: '创建时间',
      otherOptions: {
        startPlaceholder: '开始时间',
        endPlaceholder: '结束时间',
        type: 'daterange'
      }
    }
  ],
  labelWidth: '120px',
  itemStyle: { padding: '10px 40px' },
  colLayout: {
    span: 8
  }
}

实现效果:
上方标题和下方按钮可通过后续插槽进行设置
image.png

配置二

import type { IForm } from '@/base-ui/form/type'
export const modalConfig: IForm = {
  formItems: [
    {
      field: 'name',
      type: 'input',
      label: '用户名',
      placeholder: '请输入用户名'
    },
    {
      field: 'realname',
      type: 'input',
      label: '真实姓名',
      placeholder: '请输入真实姓名'
    },
    {
      field: 'cellphone',
      type: 'input',
      label: '电话号码',
      placeholder: '请输入电话号码'
    },
    {
      field: 'password',
      type: 'input',
      label: '用户密码',
      placeholder: '请输入密码',
      isHidden: true
    },
    {
      field: 'departmentId',
      type: 'select',
      label: '选择部门',
      placeholder: '请选择部门',
      options: []
    },
    {
      field: 'roleId',
      type: 'select',
      label: '选择角色',
      placeholder: '请选择角色',
      options: []
    }
  ],
  labelWidth: '120px',
  itemStyle: {},
  colLayout: {
    span: 24
  }
}

实现效果
image.png

第三步:根据配置,动态生成表单Form

思路:采用template标签,进行v-for遍历配置formItems.type确定表单类型,再将相应属性绑定在表单上

// form.vue

<template>
  <div class="tt-form">
    // 头部插槽
    <div class="header"><slot name="header"></slot></div>
    // 中间表格代码
    <el-form :label-width="labelWidth">
      <el-row>
        // 遍历配置formItems数组,拿到各个表单配置对象
        <template v-for="item in formItems" :key="item.label">
          // el-col 确定一个表单占几分(共24份)
          <el-col v-bind="colLayout">
            // lable:标签文本 
            // style:设置表单样式:这里设置了边距
            <el-form-item :label="item.label" :style="itemStyle">
              // 判断表单类型
              <template v-if="item.type === 'input' || item.type === 'password'">
                // 绑定表单属性 设置双向绑定
                <el-input
                  v-bind="item.otherOptions"
                  :placeholder="item.placeholder"
                  :model-value="modelValue[`${item.field}`]"
                  @update:model-value="handleDate($event, item.field)"
                ></el-input>
              </template>
								// 判断表单类型
              <template v-else-if="item.type === 'select'">
                // 绑定表单属性 设置双向绑定
                <el-select
                  style="width: 100%"
                  v-bind="item.otherOptions"
                  :placeholder="item.placeholder"
                  :model-value="modelValue[`${item.field}`]"
                  @update:model-value="handleDate($event, item.field)"
                  ><el-option
                    v-for="option in item.options"
                    :key="option.value"
                    :value="option.title"
                    :model-value="modelValue[`${item.field}`]"
                    @update:model-value="handleDate($event, item.field)"
                  >
                    {{ option.title }}</el-option
                  ></el-select
                ></template
              >
              <template v-else-if="item.type === 'datepicker'">
                <el-date-picker
                  style="width: 100%"
                  v-bind="item.otherOptions"
                  :model-value="modelValue[`${item.field}`]"
                  @update:model-value="handleDate($event, item.field)"
                ></el-date-picker
              ></template>
            </el-form-item>
          </el-col>
        </template>
      </el-row>
    </el-form>
  </div>
  // 下面插槽
  <div class="footer"><slot name="footer"></slot></div>                  
</template>

<script setup lang="ts">
import type { IFormItem } from '../type'
const props = withDefaults(
  defineProps<{
    formItems: IFormItem[]
    labelWidth?: any
    itemStyle?: any
    colLayout?: any
    modelValue: any
  }>(),
  {
    formItems: () => [],
    labelWidth: '200px',
    itemStyle: () => ({ padding: '10px 20px' }),
    colLayout: () => ({ span: 8 }),
    modelValue: () => ({})
  }
)
const emit = defineEmits(['update:modelValue'])
const handleDate = (value: any, field: any) => {
  emit('update:modelValue', { ...props.modelValue, [field]: value })
}
</script>

第四步:传配置

传配置

// users.vue
<template>
  <div class="user">
    <pageSearch
      :searchFormConfig="formConfig"
      @reset-btn-click="getRetsetQuest"
      @query-btn-click="getQueryQuest"
    ></pageSearch>
</template>

<script lang="ts" setup>
import { formConfig } from './config/search-config'
import pageSearch from '@/components/page-search'
const getRetsetQuest = () => {
  // 网络请求代码
}
const getQueryQuest = (queryInfo: any) => {
  // 网络请求代码
}
</script>

接配置

// page-search.vue

<template>
  <div class="page-search">
    <HyForm v-bind="searchFormConfig" v-model="formDate">
      <template #header>
        <h1 class="header">高级检索</h1>
      </template>
      <template v-slot="footer">
        <div class="handle-btns">
          <el-button @click="handleResetClick"
            ><el-icon class="icon"><refresh /></el-icon>重置</el-button
          >
          <el-button type="primary" @click="handlequeryClick"
            ><el-icon class="icon"><Search /></el-icon>搜索</el-button
          >
        </div>
      </template>
    </HyForm>
  </div>
</template>

<script setup lang="ts">
import HyForm from '@/base-ui/form/index'
import { ref } from 'vue'
const props = withDefaults(
  defineProps<{
    searchFormConfig: any
  }>(),
  {}
)
const emit = defineEmits(['resetBtnClick', 'queryBtnClick'])
// 拿到配置的表单数组
const formItems = props.searchFormConfig.formItems ?? []
const formDateOrigin: any = {}
//         
for (const data of formItems) {
  formDateOrigin[data.field] = ''
}
// 收集表单数据
const formDate = ref(formDateOrigin)

// 重置
const handleResetClick = () => {
  formDate.value = formDateOrigin
  emit('resetBtnClick')
}
// 搜索,传递表单数据进行搜索    
const handlequeryClick = () => {
  emit('queryBtnClick', formDate.value)
}

</script>

**写在最后

文中封装的代码中涉及不少细节,包括组件中使用v-model,如何遵守单向数据流原则......

但本篇文章目的目的在于分享封装思路,一些细节就不多加赘述,后面会更新文章细说,小白刚开始写文章,有什么写的不好的,欢迎各位批评指正,一起讨论一起进步!加油**