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

最終效果圖
首先,根據(jù)form表單和業(yè)務(wù)功能需求,定義組件輸入的數(shù)據(jù)
- form model 接收的數(shù)據(jù),定義為params
// params的key,既是業(yè)務(wù)字段,也是el-form-item的prop
params: {
name: '',
}
- 定義渲染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 },
]
- 針對特殊場景,可能現(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>
- 校驗規(guī)則,key與prop值保持一致
rules: {
name: [
{ required: true, message: '請?zhí)顚懶彰?, trigger: 'blur' }
],
}
- 預(yù)留兩個操作按鈕,可根據(jù)業(yè)務(wù)擴(kuò)展
// 查詢按鈕
searchBtn: {
isShow: true, // 是否顯示
text: '查詢'
}
// 重置按鈕
resetBtn: {
isShow: true, // 是否顯示
text: '重置'
}
以上兩個操作,最終都會觸發(fā)表單提交,對應(yīng)業(yè)務(wù)父組件中處理params數(shù)據(jù)
- 其它屬性
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>