Element-plus封裝搜索組件的實(shí)現(xiàn)
前言
在后臺(tái)管理系統(tǒng)中,經(jīng)常需要在多個(gè)頁面中使用搜索功能。搜索的形式可以包括文本搜索、時(shí)間范圍搜索、選擇框搜索、日期搜索和多級(jí)聯(lián)選搜索等。然而,在每個(gè)頁面中編寫重復(fù)的搜索功能代碼會(huì)導(dǎo)致效率低下且代碼冗余。為了解決這個(gè)問題,我們可以封裝一個(gè)通用的搜索組件,以節(jié)省開發(fā)時(shí)間和減少重復(fù)代碼的量。(僅供參考,歡迎評(píng)論區(qū)jym提出更好的意見)

首先,讓我們分析頁面布局和功能需求:
- 搜索組件本質(zhì)上是一個(gè)表單。
- 表單中包含了文本框、選擇框、級(jí)聯(lián)選擇器和日期選擇器等不同類型的輸入控件。
- 當(dāng)點(diǎn)擊搜索按鈕時(shí),應(yīng)觸發(fā)搜索事件;當(dāng)點(diǎn)擊重置按鈕時(shí),應(yīng)觸發(fā)重置事件。
基于上述分析,我們可以逐步實(shí)現(xiàn)具體的功能。請(qǐng)按照以下步驟進(jìn)行操作:
- 創(chuàng)建一個(gè)新的 Vue 3 項(xiàng)目:npm create vue@latest
- 刪除項(xiàng)目中自動(dòng)生成的文件和內(nèi)容。
- 在項(xiàng)目中安裝 Element Plus:npm install element-plus --save
在components目錄中創(chuàng)建 mySearch 組件,包含了一個(gè)基于 Element Plus 的表單 (el-form)。表單的屬性包括大小 (size)、數(shù)據(jù)模型 (model) 和標(biāo)簽寬度 (label-width),這些屬性通過組件的 props 傳遞進(jìn)來。
在 el-row 元素中,使用了 v-for 指令來遍歷 props.option 數(shù)組,該數(shù)組用于配置不同類型的搜索輸入控件。
搜索按鈕 (el-button) 和重置按鈕 (el-button),它們分別綁定了 search 方法和 reset 方法。這些方法會(huì)在用戶點(diǎn)擊按鈕時(shí)觸發(fā)相應(yīng)的事件。
<template>
<div>
<el-form ref="searchRef" :size="props.size" :model="searchVal" :label-width="props.labelWidth">
<el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="12" :lg="props.span" v-for="(item, index) in props.option" :key="index" class="mb20">
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="props.span">
<el-form-item>
<el-button type="primary" @click="search">搜索</el-button>
<el-button @click="reset(searchRef)">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script lang='ts' setup>
import { onMounted, reactive, ref } from 'vue'
import type { ElForm } from 'element-plus'
import { ElMessage } from 'element-plus'
import { formatDate } from '@/utils/formatTime'
import type { Option } from '@/components/type.d.ts'
type FormInstance = InstanceType<typeof ElForm>
const searchRef = ref<FormInstance>()
// 搜索值
let searchVal: any = reactive({})
// 搜索
const search = () => {
for (let i in searchVal) {
if (searchVal[i] === undefined || searchVal[i] === '') {
delete searchVal[i]
}
}
emit('search', searchVal)
}
// 重置
const reset = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
emit('search', searchVal)
}
interface Props {
labelWidth?: number | string
size?: 'small' | 'default' | 'large'
span?: number
option: Option[]
defaultValue?: any
}
const props = withDefaults(defineProps<Props>(), {
size: 'default',
span: 8,
labelWidth: 100
})
</script>聲明了一個(gè)類型別名 FormInstance,用于表示表單的實(shí)例。通過 ref 函數(shù)創(chuàng)建了一個(gè)響應(yīng)式引用 searchRef,用于引用表單實(shí)例。使用 reactive 函數(shù)創(chuàng)建了一個(gè)響應(yīng)式對(duì)象 searchVal,用于存儲(chǔ)搜索的值。
定義 search 方法,該方法會(huì)在搜索按鈕被點(diǎn)擊時(shí)觸發(fā)。遍歷 searchVal 對(duì)象的屬性,如果屬性值為空或未定義,則從 searchVal 中刪除該屬性。最后,我們通過 emit 方法觸發(fā) search 的自定義事件,并將 searchVal 作為參數(shù)傳遞給父組件。定義了 reset 方法,該方法會(huì)在重置按鈕被點(diǎn)擊時(shí)觸發(fā)。傳入的表單實(shí)例調(diào)用 resetFields 方法,將表單重置為初始狀態(tài)。最后,再次通過 emit 方法觸發(fā)search 的自定義事件,并將 searchVal 作為參數(shù)傳遞給父組件。
接下來分析option中的數(shù)據(jù),新建type.d.ts
//選擇框的option類型
interface SelectOption {
label: string
value: any
}
//選擇框和文本輸入框的option類型
interface InputOptions {
prop: string
option: SelectOption[]
}
export interface Option {
label: string //標(biāo)簽名稱
prop: string //表單項(xiàng)字段名
endProp?: string //日期范圍的結(jié)束日期字段名
placeholder?: string //輸入框占位文本
type?: 'input' | 'select' | 'cascader' | 'date' | 'datetime' | 'daterange' | 'inputSelect' //表單項(xiàng)類型
optionLabel?: string //選擇框的label字段
optionValue?: string //選擇框的value字段
option?: any[] //選擇框option
inputOptions?: InputOptions //選擇框和文本輸入框的option
max?: number //級(jí)聯(lián)選擇器的最大選擇數(shù)量
children?: string //級(jí)聯(lián)選擇器children字段名
multiple?: boolean //級(jí)聯(lián)選擇器是否多選
value?: string //級(jí)聯(lián)選擇器返回值字段名
itemLabel?: string //級(jí)聯(lián)選擇器選項(xiàng)名稱
checkStrictly?: boolean //是否可選擇父節(jié)點(diǎn)和子節(jié)點(diǎn)
emitPath?: boolean // 是否返回由該節(jié)點(diǎn)所在的各級(jí)菜單的值所組成的數(shù)組
show?: boolean //是否顯示選中值的完整路徑
filterable?: boolean //選擇框是否可搜索
}
完善表單,根據(jù)實(shí)際需求,暴露出表單的配置項(xiàng),完整代碼如下
ini 代碼解讀復(fù)制代碼<template>
<div>
<el-form ref="searchRef" :size="props.size" :model="searchVal" :label-width="props.labelWidth">
<el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="12" :lg="props.span" v-for="(item, index) in props.option" :key="index" class="mb20">
<el-form-item v-if="item.type == 'input' || !item.type" @keyup.enter="search" :prop="item.prop" :label="item.label">
<el-input v-model="searchVal[item.prop]" :placeholder="item.placeholder"></el-input>
</el-form-item>
<el-form-item v-if="item.type == 'inputSelect'" label-width="0" :prop="item.prop">
<el-input v-model="searchVal[item.prop]" clearable :placeholder="item.placeholder">
<template #prepend>
<el-select v-model="searchVal[item.inputOptions.prop]" :style="{ width: props.labelWidth + 'px' }">
<el-option v-for="(sItem, sIndex) in item.inputOptions.option" :key="sIndex" :label="sItem.label" :value="sItem.value"></el-option>
</el-select>
</template>
</el-input>
</el-form-item>
<el-form-item v-else-if="item.type == 'select'" :prop="item.prop" :label="item.label">
<el-select v-model="searchVal[item.prop]" :filterable="item.filterable" :fit-input-width="true" style="width: 100%" :placeholder="item.placeholder">
<el-option v-for="(sItem, sIndex) in item.option" :key="sIndex" :label="sItem[item.optionLabel || 'label']" :value="sItem[item.optionValue || 'value']"></el-option>
</el-select>
</el-form-item>
<el-form-item v-else-if="item.type == 'cascader'" :prop="item.prop" :label="item.label">
<el-cascader
v-model="searchVal[item.prop]"
:options="item.option"
:placeholder="item.placeholder"
clearable
:props="{
value: item.value || 'value',
label: item.itemLabel || 'label',
children: item.children || 'children',
multiple: item.multiple || false,
emitPath: item.emitPath || false,
checkStrictly: item.checkStrictly || false
}"
:show-all-levels="item.show || false"
@change="(val: any) => changeType(val, item)"
/>
</el-form-item>
<el-form-item v-else-if="item.type == 'date'" :prop="item.prop" :label="item.label">
<el-date-picker
v-model="searchVal[item.prop]"
type="date"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
:placeholder="item.placeholder"
style="width: 100%"
></el-date-picker>
</el-form-item>
<el-form-item v-else-if="item.type == 'datetime'" :prop="item.prop" :label="item.label">
<el-date-picker
v-model="searchVal[item.prop]"
type="datetime"
value-format="YYYY-MM-DD HH:mm"
format="YYYY-MM-DD HH:mm"
:placeholder="item.placeholder"
style="width: 100%"
></el-date-picker>
</el-form-item>
<el-form-item v-else-if="item.type == 'daterange'" :prop="item.prop" :label="item.label">
<el-date-picker
v-model="searchVal[item.prop + 'Range']"
style="width: 100%"
type="daterange"
range-separator="~"
start-placeholder="開始時(shí)間"
end-placeholder="結(jié)束時(shí)間"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
@change="changeDateRange(item)"
>
</el-date-picker>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="props.span">
<el-form-item>
<el-button type="primary" @click="search">搜索</el-button>
<el-button @click="reset(searchRef)">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue'
import type { ElForm } from 'element-plus'
import { ElMessage } from 'element-plus'
import { formatDate } from '@/utils/formatTime'
import type { Option } from '@/components/type.d.ts'
type FormInstance = InstanceType<typeof ElForm>
const searchRef = ref<FormInstance>()
// 搜索值
let searchVal: any = reactive({})
// 搜索
const search = () => {
for (let i in searchVal) {
if (searchVal[i] === undefined || searchVal[i] === '') {
delete searchVal[i]
}
}
emit('search', searchVal)
}
// 重置
const reset = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
emit('search', searchVal)
}
// 級(jí)聯(lián)選擇
const changeType = (value: any, data: Option) => {
if (!value) value = []
if (value.length > (data.max as number)) {
searchVal[data.prop] = value.slice(0, 4)
ElMessage.error('館校類型數(shù)量限制為4個(gè)')
}
}
// 日期范圍選擇
const changeDateRange = (item: any) => {
let dateArr = searchVal[item.prop + 'Range']
if (dateArr) {
searchVal[item.prop] = formatDate(dateArr[0], 'YYYY-mm-dd')
searchVal[item.endProp] = formatDate(dateArr[1], 'YYYY-mm-dd')
} else {
searchVal[item.prop] = ''
searchVal[item.endProp] = ''
}
}
const emit = defineEmits(['search', 'reset'])
interface Props {
labelWidth?: number | string
size?: 'small' | 'default' | 'large'
span?: number
option: Option[]
defaultValue?: any
}
const props = withDefaults(defineProps<Props>(), {
size: 'default',
span: 8,
labelWidth: 100
})
onMounted(() => {
if (props.defaultValue) {
props.option.forEach((p: any) => {
if (p.type == 'daterange' && props.defaultValue[p.prop]) {
searchVal[p.prop + 'Range'] = []
if (props.defaultValue[p.prop]) {
searchVal[p.prop + 'Range'][0] = props.defaultValue[p.prop]
}
if (props.defaultValue[p.endProp]) {
searchVal[p.prop + 'Range'][1] = props.defaultValue[p.endProp]
}
}
})
searchVal = Object.assign(searchVal, props.defaultValue)
}
})
</script>
<style lang="scss" scoped></style>實(shí)際使用,App.vue中
<template>
<div class="main">
<MySearch :option="searchOptions" :defaultValue="{ daterange: '', endDaterange: '' }" @search="search" />
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import MySearch from './components/mySearch.vue'
import type { Option } from '@/components/type.d.ts'
const cascaderOptions = [
{
value: 'guide',
label: 'Guide',
children: [
{
value: 'disciplines',
label: 'Disciplines',
children: [
{
value: 'consistency',
label: 'Consistency'
},
{
value: 'feedback',
label: 'Feedback'
},
{
value: 'efficiency',
label: 'Efficiency'
},
{
value: 'controllability',
label: 'Controllability'
}
]
},
{
value: 'navigation',
label: 'Navigation',
children: [
{
value: 'side nav',
label: 'Side Navigation'
},
{
value: 'top nav',
label: 'Top Navigation'
}
]
}
]
},
{
value: 'component',
label: 'Component',
children: [
{
value: 'basic',
label: 'Basic',
children: [
{
value: 'layout',
label: 'Layout'
},
{
value: 'color',
label: 'Color'
},
{
value: 'typography',
label: 'Typography'
},
{
value: 'icon',
label: 'Icon'
},
{
value: 'button',
label: 'Button'
}
]
},
{
value: 'form',
label: 'Form',
children: [
{
value: 'radio',
label: 'Radio'
},
{
value: 'checkbox',
label: 'Checkbox'
},
{
value: 'input',
label: 'Input'
},
{
value: 'input-number',
label: 'InputNumber'
},
{
value: 'select',
label: 'Select'
},
{
value: 'cascader',
label: 'Cascader'
},
{
value: 'switch',
label: 'Switch'
},
{
value: 'slider',
label: 'Slider'
},
{
value: 'time-picker',
label: 'TimePicker'
},
{
value: 'date-picker',
label: 'DatePicker'
},
{
value: 'datetime-picker',
label: 'DateTimePicker'
},
{
value: 'upload',
label: 'Upload'
},
{
value: 'rate',
label: 'Rate'
},
{
value: 'form',
label: 'Form'
}
]
},
{
value: 'data',
label: 'Data',
children: [
{
value: 'table',
label: 'Table'
},
{
value: 'tag',
label: 'Tag'
},
{
value: 'progress',
label: 'Progress'
},
{
value: 'tree',
label: 'Tree'
},
{
value: 'pagination',
label: 'Pagination'
},
{
value: 'badge',
label: 'Badge'
}
]
},
{
value: 'notice',
label: 'Notice',
children: [
{
value: 'alert',
label: 'Alert'
},
{
value: 'loading',
label: 'Loading'
},
{
value: 'message',
label: 'Message'
},
{
value: 'message-box',
label: 'MessageBox'
},
{
value: 'notification',
label: 'Notification'
}
]
},
{
value: 'navigation',
label: 'Navigation',
children: [
{
value: 'menu',
label: 'Menu'
},
{
value: 'tabs',
label: 'Tabs'
},
{
value: 'breadcrumb',
label: 'Breadcrumb'
},
{
value: 'dropdown',
label: 'Dropdown'
},
{
value: 'steps',
label: 'Steps'
}
]
},
{
value: 'others',
label: 'Others',
children: [
{
value: 'dialog',
label: 'Dialog'
},
{
value: 'tooltip',
label: 'Tooltip'
},
{
value: 'popover',
label: 'Popover'
},
{
value: 'card',
label: 'Card'
},
{
value: 'carousel',
label: 'Carousel'
},
{
value: 'collapse',
label: 'Collapse'
}
]
}
]
},
{
value: 'resource',
label: 'Resource',
children: [
{
value: 'axure',
label: 'Axure Components'
},
{
value: 'sketch',
label: 'Sketch Templates'
},
{
value: 'docs',
label: 'Design Documentation'
}
]
}
]
const searchOptions: Option[] = reactive([
{
label: '文本類型',
placeholder: '請(qǐng)輸入文本',
type: 'input',
prop: 'input'
},
{
label: '選擇框',
prop: 'select',
placeholder: '請(qǐng)選擇支付方式',
type: 'select',
option: [
{
label: '微信',
value: 0
},
{
label: '支付寶',
value: 1
}
]
},
{
label: '文本選擇框',
prop: 'inputSelect1',
placeholder: '請(qǐng)輸入文本',
type: 'inputSelect',
inputOptions: {
prop: 'inputSelect2',
option: [
{
label: '微信',
value: 0
},
{
label: '支付寶',
value: 1
}
]
}
},
{
label: '級(jí)聯(lián)選擇器',
placeholder: '請(qǐng)選擇級(jí)聯(lián)選擇器',
type: 'cascader',
prop: 'cascader',
option: cascaderOptions
},
{
label: '日期選擇',
type: 'date',
prop: 'date',
placeholder: '請(qǐng)選擇日期'
},
{
label: '日期時(shí)間',
type: 'datetime',
prop: 'datetime',
placeholder: '請(qǐng)選擇日期時(shí)間'
},
{
label: '日期范圍',
type: 'daterange',
prop: 'daterange',
endProp: 'endDaterange'
}
])
// 搜索
const search = (val: any) => {
tableData.searchForm = val
tableData.page.pageIndex = 1
initTableData()
}
//表格數(shù)據(jù)
const tableData: any = reactive({
data: [],
searchForm: {},
page: {
pageIndex: 1,
pageSize: 10,
total: 0
}
})
// 初始化表格數(shù)據(jù)
const initTableData = () => {
console.log(tableData.searchForm)
}
</script>
<style scoped>
.main {
padding: 20px;
}
</style>總結(jié):
實(shí)現(xiàn)了一個(gè)搜索組件,根據(jù) props.option 數(shù)組動(dòng)態(tài)生成不同類型的表單項(xiàng),以便用戶輸入搜索條件。
- 代碼中的 el-col 元素表示一個(gè)列布局,用于展示生成的表單項(xiàng)。
- 根據(jù) props.option 數(shù)組的每一項(xiàng) item,根據(jù) item.type 的不同選擇不同的表單項(xiàng)類型進(jìn)行渲染。
- 支持的表單項(xiàng)類型包括輸入框、帶下拉選擇框的輸入框、選擇框、級(jí)聯(lián)選擇器、日期選擇器和日期范圍選擇器。
- 每個(gè)表單項(xiàng)都綁定了相應(yīng)的數(shù)據(jù)模型,通過 v-model 實(shí)現(xiàn)了數(shù)據(jù)的雙向綁定。
- 表單項(xiàng)的標(biāo)簽、屬性和占位符等信息都是根據(jù) item 對(duì)象中的屬性動(dòng)態(tài)設(shè)置的。
這個(gè)搜索組件提供了一種靈活的方式來生成不同類型的表單項(xiàng),以便用戶輸入搜索條件。通過配置 props.option 數(shù)組,可以定制化生成所需的表單項(xiàng),并且支持不同的表單驗(yàn)證和交互方式。如果需要進(jìn)一步定制和擴(kuò)展,可以根據(jù)具體的需求修改和補(bǔ)充代碼。
到此這篇關(guān)于Element-plus封裝搜索組件的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Element-plus 搜索組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 基于Vue3和Element Plus實(shí)現(xiàn)可擴(kuò)展的表格組件
- Vue3+Element Plus實(shí)現(xiàn)自定義彈窗組件的全屏功能
- ElementPlus組件與圖標(biāo)按需自動(dòng)引入的實(shí)現(xiàn)方法
- vue3中ts語法使用element plus分頁組件警告錯(cuò)誤問題
- Vue3使用element-plus組件不顯示問題
- Vue3導(dǎo)入Elementplus時(shí)組件無法加載的情況及解決
- vue3使用Element-plus的el-pagination分頁組件時(shí)無法顯示中文
- vue3+elementplus基于el-table-v2封裝公用table組件詳細(xì)代碼
相關(guān)文章
vue中this.$http.post()跨域和請(qǐng)求參數(shù)丟失的解決
這篇文章主要介紹了vue中this.$http.post()跨域和請(qǐng)求參數(shù)丟失的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04
Vue3 實(shí)現(xiàn)雙盒子定位Overlay的示例
這篇文章主要介紹了Vue3 實(shí)現(xiàn)雙盒子定位Overlay的示例,幫助大家更好的理解和使用vue,感興趣的朋友可以了解下2020-12-12
詳解element-ui中el-select的默認(rèn)選擇項(xiàng)問題
這篇文章主要介紹了詳解element-ui中el-select的默認(rèn)選擇項(xiàng)問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
vue實(shí)現(xiàn)簡單loading進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)簡單loading進(jìn)度條,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06
vue實(shí)現(xiàn)圖片滾動(dòng)的示例代碼(類似走馬燈效果)
下面小編就為大家分享一篇vue實(shí)現(xiàn)圖片滾動(dòng)的示例代碼(類似走馬燈效果),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-03-03
登錄頁面的實(shí)現(xiàn)及跳轉(zhuǎn)代碼實(shí)例(vue-router)
在Vue.js中可以使用vue-router來實(shí)現(xiàn)前端路由,通過路由來跳轉(zhuǎn)頁面,這篇文章主要給大家介紹了關(guān)于登錄頁面的實(shí)現(xiàn)及跳轉(zhuǎn)(vue-router)的相關(guān)資料,需要的朋友可以參考下2023-12-12

