基于vue3/Element-plus form 對搜索二次封裝

針對大部分后臺系統(tǒng),搜索功能被大量使用。表單功能被頻繁重復(fù)使用,不利于后期維護(hù)和做統(tǒng)一樣式調(diào)整??梢詫⑺阉鞴δ芏畏庋b,避免這些問題。

效果圖如下
最終效果圖
首先,根據(jù)form表單和業(yè)務(wù)功能需求,定義組件輸入的數(shù)據(jù)
  1. form model 接收的數(shù)據(jù),定義為params
// params的key,既是業(yè)務(wù)字段,也是el-form-item的prop
params: {
  name: '', 
}
  1. 定義渲染el-form-item的數(shù)據(jù)格式

我們希望通過循環(huán)el-form-item,渲染出有序的表單篩選組件,那就需要一個包含篩選組件選項的數(shù)組

// label和prop是el-form-item上的屬性,label可選,prop必填且與params中的key對應(yīng)
[
  { label: '組件1', prop: 'name' },
  { label: '組件2', prop: 'age' },
]

同時根據(jù)業(yè)務(wù)需要,我們用兩個這樣的數(shù)組代表基礎(chǔ)和更多篩選選項

// 基礎(chǔ)選項
baseAttribute: []
// 更多選項
moreAttribute: []

根據(jù)篩選組件的特性,我們定義組件的參數(shù)
下面以輸入框組件為例

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

根據(jù)el-input的屬性,我們定義了如下的參數(shù)。type代表當(dāng)前的組件類型,這樣輸入數(shù)據(jù)和html模版就能一一對應(yīng)

{ type: 'input', label: '輸入框', prop: 'name', placeholder: '請?zhí)顚懨Q', maxlength: 10 }

目前已定義的組件如下,根據(jù)業(yè)務(wù)可自主擴(kuò)展:

[
    { type: 'input', label: '輸入框', prop: 'name', placeholder: '請?zhí)顚懨Q', maxlength: 10 },
    // multiple 是否為多選
    { type: 'select', multiple: false, filterable: false, label: '下拉選擇框', prop: 'region', placeholder: '清選擇活動區(qū)域', options: [{ label: '區(qū)域一', value: 1 }, { label: '區(qū)域二', value: 2 }] },
    { type: 'date-picker', label: '日期選擇', prop: 'date1', placeholder: '請選擇活動時間' },
    { type: 'time-picker', label: '選擇時間', prop: 'date2', placeholder: '清選擇時間' },
    { type: 'daterange', label: '選擇時間', prop: 'date3', startPlaceholder: "開始日期", endPlaceholder: "結(jié)束日期" },
    { type: 'switch', label: '開關(guān)', prop: 'delivery', placeholder: '' },
    { type: 'radio-group', label: '單選框', prop: 'type', placeholder: '', options: [{ label: '線上品牌商贊助', value: 1 }, { label: '線下場地免費(fèi)', value: 2 }] },
    { type: 'checkbox-group', label: '多選框', prop: 'activeName', placeholder: '', options: [{ label: '美食/餐廳線上活動', value: 1 }, { label: '地推活動', value: 2 }] },
    { type: 'autocomplete', label: '遠(yuǎn)程搜索輸入框', prop: 'name', placeholder: '請輸入進(jìn)行搜索', querySearchAsync: Function, handleSelect: Function },
  ]
  1. 針對特殊場景,可能現(xiàn)有element基礎(chǔ)組件不能滿足業(yè)務(wù)需求。需要開發(fā)自己去封裝實(shí)現(xiàn)一些組件,但這些組件又不是通用的,可以通過具名插槽完成
    在組件中我們預(yù)留了一個特殊類型組件
<slot v-if="item.type === 'slot'" :name="item.slotName"></slot>

對應(yīng)數(shù)據(jù)

{ 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. 校驗規(guī)則,key與prop值保持一致
  rules: {
    name: [
      { required: true, message: '請?zhí)顚懶彰?, trigger: 'blur' }
    ],
  }
  1. 預(yù)留兩個操作按鈕,可根據(jù)業(yè)務(wù)擴(kuò)展
// 查詢按鈕
searchBtn: {
    isShow: true, // 是否顯示
    text: '查詢'
}
// 重置按鈕
 resetBtn: {
    isShow: true, // 是否顯示
    text: '重置'
 } 

以上兩個操作,最終都會觸發(fā)表單提交,對應(yīng)業(yè)務(wù)父組件中處理params數(shù)據(jù)

  1. 其它屬性
labelWidth: 'auto', // 表單域標(biāo)簽的寬度,格式'100px'或auto,默認(rèn)auto
labelPosition: 'left', // 表單域標(biāo)簽的位置 right/left/top,默認(rèn)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>

應(yīng)用

可以注冊為全局組件

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

業(yè)務(wù)組件

<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>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容