基于vue3/Element-plus form 对搜索二次封装

针对大部分后台系统,搜索功能被大量使用。表单功能被频繁重复使用,不利于后期维护和做统一样式调整。可以将搜索功能二次封装,避免这些问题。

效果图如下
最终效果图
首先,根据form表单和业务功能需求,定义组件输入的数据
  1. form model 接收的数据,定义为params
// params的key,既是业务字段,也是el-form-item的prop
params: {
  name: '', 
}
  1. 定义渲染el-form-item的数据格式

我们希望通过循环el-form-item,渲染出有序的表单筛选组件,那就需要一个包含筛选组件选项的数组

// label和prop是el-form-item上的属性,label可选,prop必填且与params中的key对应
[
  { label: '组件1', prop: 'name' },
  { label: '组件2', prop: 'age' },
]

同时根据业务需要,我们用两个这样的数组代表基础和更多筛选选项

// 基础选项
baseAttribute: []
// 更多选项
moreAttribute: []

根据筛选组件的特性,我们定义组件的参数
下面以输入框组件为例

    <el-input
      v-if="item.type === 'input'"
      v-model.trim="attrs.params[item.prop]"
      :maxlength="item.maxlength"
      clearable
      :placeholder="item.placeholder"
    ></el-input>

根据el-input的属性,我们定义了如下的参数。type代表当前的组件类型,这样输入数据和html模版就能一一对应

{ type: 'input', label: '输入框', prop: 'name', placeholder: '请填写名称', maxlength: 10 }

目前已定义的组件如下,根据业务可自主扩展:

[
    { type: 'input', label: '输入框', prop: 'name', placeholder: '请填写名称', maxlength: 10 },
    // multiple 是否为多选
    { type: 'select', multiple: false, filterable: false, label: '下拉选择框', prop: 'region', placeholder: '清选择活动区域', options: [{ label: '区域一', value: 1 }, { label: '区域二', value: 2 }] },
    { type: 'date-picker', label: '日期选择', prop: 'date1', placeholder: '请选择活动时间' },
    { type: 'time-picker', label: '选择时间', prop: 'date2', placeholder: '清选择时间' },
    { type: 'daterange', label: '选择时间', prop: 'date3', startPlaceholder: "开始日期", endPlaceholder: "结束日期" },
    { type: 'switch', label: '开关', prop: 'delivery', placeholder: '' },
    { type: 'radio-group', label: '单选框', prop: 'type', placeholder: '', options: [{ label: '线上品牌商赞助', value: 1 }, { label: '线下场地免费', value: 2 }] },
    { type: 'checkbox-group', label: '多选框', prop: 'activeName', placeholder: '', options: [{ label: '美食/餐厅线上活动', value: 1 }, { label: '地推活动', value: 2 }] },
    { type: 'autocomplete', label: '远程搜索输入框', prop: 'name', placeholder: '请输入进行搜索', querySearchAsync: Function, handleSelect: Function },
  ]
  1. 针对特殊场景,可能现有element基础组件不能满足业务需求。需要开发自己去封装实现一些组件,但这些组件又不是通用的,可以通过具名插槽完成
    在组件中我们预留了一个特殊类型组件
<slot v-if="item.type === 'slot'" :name="item.slotName"></slot>

对应数据

{ type: 'slot', slotName: 'input1', prop: 'name1', label: '特殊名词' }

template中这些写

  <Search v-bind="formData" @submitForm="submitForm">
      <el-input v-slot:input1 v-model="form.params.name1" clearable placeholder="请输入特殊名词"></el-input>
  </Search>
  1. 校验规则,key与prop值保持一致
  rules: {
    name: [
      { required: true, message: '请填写姓名', trigger: 'blur' }
    ],
  }
  1. 预留两个操作按钮,可根据业务扩展
// 查询按钮
searchBtn: {
    isShow: true, // 是否显示
    text: '查询'
}
// 重置按钮
 resetBtn: {
    isShow: true, // 是否显示
    text: '重置'
 } 

以上两个操作,最终都会触发表单提交,对应业务父组件中处理params数据

  1. 其它属性
labelWidth: 'auto', // 表单域标签的宽度,格式'100px'或auto,默认auto
labelPosition: 'left', // 表单域标签的位置 right/left/top,默认left
loading: false, // 查询按钮loading
具体封装如下

search.vue el-form 模块,定义主体样式

<template>
  <el-form
    class="search"
    :model="attrs.params"
    :rules="attrs.rules"
    ref="ruleForm"
    :label-width="attrs.labelWidth || 'auto'"
    :label-position="attrs.labelPosition || 'left'"
  >
    <div class="base">
      <FormItem v-bind="$attrs" attribute="baseAttribute"></FormItem>
    </div>
    <div class="more" v-if="isShow">
      <FormItem v-bind="$attrs" attribute="moreAttribute"></FormItem>
    </div>
    <div class="btnRight">
      <el-button
        v-if="attrs.searchBtn && attrs.searchBtn.isShow"
        :loading="attrs.loading"
        type="primary"
        :icon="Search"
        @click="submitForm(ruleForm)"
        >{{ attrs.searchBtn.text }}</el-button
      >
      <el-button
        v-if="attrs.resetBtn && attrs.resetBtn.isShow"
        :loading="attrs.loading"
        :icon="Refresh"
        @click="resetForm(ruleForm)"
        >{{ attrs.resetBtn.text }}</el-button
      >
      <el-button
        v-if="attrs.moreAttribute && attrs.moreAttribute.length != 0"
        type="text"
        @click="isShow = !isShow"
        >{{ isShow ? "收起" : "展开" }}</el-button
      >
    </div>
  </el-form>
</template>

<script setup>
import FormItem from './FormItem.vue';
import {
  Search,
  Refresh
} from '@element-plus/icons-vue'
import { ref, useAttrs } from 'vue'

const attrs = useAttrs()

const emit = defineEmits(['submitForm'])

const isShow = ref(true)

const ruleForm = ref(null)

function submitForm(formEL) {
  formEL.validate((valid) => {
    if (valid) {
      emit('submitForm')
    } else {
      return false
    }
  });
}
function resetForm(formEL) {
  formEL.resetFields()
  submitForm(formEL)
}
</script>

<style scoped>
.search {
  width: 100%;
  overflow-x: auto;
}
.base {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
}
.btnRight {
  float: right;
}
.more {
  display: flex;
  flex-wrap: wrap;
}
</style>

FormItem.vue 处理el-form-item下的组件

<template>
  <el-form-item
    v-for="(item, index) in attrs[attrs.attribute]"
    :key="index"
    :label="item.label"
    :class="[item.label === '' ? 'special' : '']"
    :prop="item.prop"
  >
    <el-input
      v-if="item.type === 'input'"
      v-model.trim="attrs.params[item.prop]"
      :maxlength="item.maxlength"
      clearable
      :placeholder="item.placeholder"
    ></el-input>

    <el-select
      v-if="item.type === 'select'"
      v-model="attrs.params[item.prop]"
      :filterable="item.filterable"
      :multiple="item.multiple"
      :placeholder="item.placeholder"
      clearable
    >
      <el-option
        v-for="(option, index) in item.options"
        :key="index"
        :label="option.label"
        :value="option.value"
      ></el-option>
    </el-select>

    <el-date-picker
      v-if="item.type === 'date-picker'"
      type="date"
      :placeholder="item.placeholder"
      v-model="attrs.params[item.prop]"
      clearable
    ></el-date-picker>

    <el-time-picker
      v-if="item.type === 'time-picker'"
      :placeholder="item.placeholder"
      v-model="attrs.params[item.prop]"
      clearable
    ></el-time-picker>

    <el-switch
      v-if="item.type === 'switch'"
      v-model="attrs.params[item.prop]"
      :placeholder="item.placeholder"
    ></el-switch>

    <el-checkbox-group
      v-if="item.type === 'checkbox-group'"
      v-model="attrs.params[item.prop]"
      :placeholder="item.placeholder"
    >
      <el-checkbox
        v-for="(option, index) in item.options"
        :key="index"
        :label="option.value"
        >{{ option.label }}</el-checkbox
      >
    </el-checkbox-group>

    <el-radio-group
      v-if="item.type === 'radio-group'"
      v-model="attrs.params[item.prop]"
    >
      <el-radio
        v-for="(option, index) in item.options"
        :key="index"
        :label="option.value"
        >{{ option.label }}</el-radio
      >
    </el-radio-group>

    <el-date-picker
      v-if="item.type === 'daterange'"
      style="width: 250px"
      v-model="attrs.params[item.prop]"
      type="daterange"
      format="YYYY-MM-DD"
      value-format="YYYY-MM-DD"
      range-separator="至"
      :start-placeholder="item.startPlaceholder"
      :end-placeholder="item.endPlaceholder"
      clearable
    >
    </el-date-picker>

    <el-autocomplete
      v-if="item.type === 'autocomplete'"
      v-model="attrs.params[item.prop]"
      :fetch-suggestions="item.querySearchAsync"
      :placeholder="item.placeholder"
      @select="item.handleSelect"
      clearable
    ></el-autocomplete>

    <slot v-if="item.type === 'slot'" :name="item.slotName"></slot>
  </el-form-item>
</template>

<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>

<style scoped>
.el-form-item {
  margin-right: 10px;
}
.el-form-item:last-child {
  margin-right: 0;
}
.el-form-item__label-wrap {
  margin-left: 0 !important;
}
.special .el-form-item__label-wrap {
  margin-left: 0 !important;
}
.special .el-form-item__content {
  margin-left: 0 !important;
}
</style>

应用

可以注册为全局组件

import Search from '@/components/search.vue'
const app = createApp(App)
app.component('Search', Search)

业务组件

<template>
  <Search v-if="formData.baseAttribute.length != 0" v-bind="formData" @submitForm="submitForm"></Search>
</template>

<script setup lang="ts">
import { form } from "./form"
import { ref, reactive, onMounted } from 'vue'

interface FormData {
    params: Object,
    baseAttribute: Array<Object>,
    moreAttribute: Array<Object>,
    searchBtn: Object,
    resetBtn: Object,
    loading: Boolean
  }

  const formData = ref<FormData>({
    params: {},
    baseAttribute: [],
    moreAttribute: [],
    searchBtn: {},
    resetBtn: {},
    loading: false
  })

const orgOptions = ref([
    { label: '组织1', value: 'org1' },
    { label: '组织2', value: 'org2' },
    { label: '组织3', value: 'org3' },
    { label: '组织4', value: 'org4' },
  ])
onMounted(() => {
    formData.value = form({ _data: { orgOptions }, _methods: { querySearchAsync, handleSelect }})
  })

// 表单提交
function submitForm() {
}
</script>
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容