如何一步步基於element-ui封裝查詢組件
功能
接著前一篇文章基於element-ui框架封裝一個更好用的表格組件,我們開始寫查詢組件. 查詢組件的話,需要有什麼呢? 下面我畫瞭一個粗略的原型,基本描述瞭查詢組件需要實現的功能瞭。
基本的查詢功能 [輸入條件,選擇下拉數據點擊查詢]
添加查詢下拉面板 [很多查詢的話,一行放不下,需要給一下更多查詢下拉面板,除掉默認查詢的條件,都放下拉面板去,具體默認查詢條件放幾個,可以做成通過傳入的參數控制]
添加條件展示 [如果有更多查詢面板的話,不打開查詢面板,根本不清楚數據是根據哪些條件查出來的.]
添加功能按鈕區 [一般放置增刪改等批量操作的功能按鈕]
基本的查詢功能
查詢功能的話,需要搭配element-ui的輸入框、下拉框、日期框等組件去實現,而用我們的組件的話,希望用一個數組去描述這個查詢條件,然後組件根據數組去把條件給識別並渲染出來. 首先,我們得先定義一下這個數組到底長什麼樣.
// methods initSearch () { const searchArr = this.searchArr if (searchArr && searchArr.length) { let searchForm = {} searchArr.forEach((val) => { searchForm[val.key] = '' }) this.searchForm = searchForm } } // mounted mounted () { this.initSearch() },
目前最基本的就是這三個字段瞭,字段意義在代碼註釋已經寫明白瞭。 後邊這個對象裡邊可能還會擴展一些其他的字段. 比如下拉數據, 它一般都是通過後臺接口給到前端, 我們可以在這裡添加一個url、param、method來去定義接口的名稱、參數和請求方式. 當然後面實現的時候,我們再說. 那麼我們開始開發吧.
查詢條件初始化
外部我們傳入瞭searchArr數據後,我們得先在mounted的時候,遍歷它,給內容定義的查詢條件searchForm設置響應式數據.
// methods initSearch () { const searchArr = this.searchArr if (searchArr && searchArr.length) { let searchForm = {} searchArr.forEach((val) => { searchForm[val.key] = '' }) this.searchForm = searchForm } } // mounted mounted () { this.initSearch() },
渲染頁面
因為我們設計的查詢頁面是兩部分的,包括展示在外邊的查詢條件和展示在更多的查詢條件. 這樣的話,如果我們在template裡邊把結構定義好的話,會發現,兩遍需要寫重復的代碼。 如果後邊擴展新功能上去的話,又是一個不好受的體力活瞭. 所以我們這邊就在render函數裡邊寫jsx,這樣可以讓結構更靈活,代碼可以更好的復用. 思路說完瞭,那麼開始寫代碼瞭
// props 傳入一個searchArr數組描述查詢條件都有哪一些 /** * 查詢條件描述 */ searchArr: { type: Array, default: () => [] }, // data 定義兩個變量,searchForm:傳遞到後臺的查詢字段,selectOption:下拉數據 searchForm: {}, selectOption: {} // mounted 初始化數據 initSearch () { const searchArr = this.searchArr if (searchArr && searchArr.length) { let searchForm = {} searchArr.forEach((val) => { searchForm[val.__key] = '' // 把用戶定義的字段拿出來,放到searchForm裡邊,再賦值一個空字符串 this.setOption(val) // 如果是下拉數據的話,去把下拉的列表賦值一下放到selectOption裡邊 }) this.searchForm = searchForm } }, // methods 設置下拉數據 async setOption (val) { if (~['select', 'mulSelect'].indexOf(val.__type)) { // 第一種情況如果下拉數據是本地寫死的 if (val.data && Array.isArray(val.data)) { this.$set(this.selectOption, val.__key, val.data) } else if (val.fetchUrl) { // 第二種情況,如果下拉數據是從後臺接口傳遞過來的數據 const result = await request({ url: val.fetchUrl, method: val.method || 'post' }) if (result && result.data && result.data.success) { this.$set(this.selectOption, val.__key, result.data.list || []) } } } },
好的,我們完成初始化工作後,就可以在進行渲染工作瞭,前面我們說到,我們采用render函數裡邊寫jsx會更靈活一點,那麼我們開始寫這部分的工作吧.
// render 函數 <div class="searchTable"> <el-form inline={true} props={{model: this.searchForm}} ref="searchForm" class="searchForm"> { searchArr.map((o) => { return components[o.__type] ? components[o.__type](h, o, this) : '' }) } <el-form-item> <el-button size="small" type="primary" icon="el-icon-search" on-click={ queryTable }>查詢</el-button> <el-form-item> <el-link style="margin-left: 10px;" type="primary" underline={ false } on-click={ () => { this.moreShow = !this.moreShow } }>更多查詢 </el-link> </el-form-item> </el-form-item> <div class="more-search animated" class={ this.moreShow ? 'fadeInDown' : 'fadeOutUp' } v-show={ this.moreShow }> // ...更多查詢 </div> </el-form> </div>
在jsx中操作有個地方值得註意一下,在最新的vue-cli中,是已經支持瞭在jsx中使用v-model等指令,但是element-ui中的el-input使用會報錯,下方使用的v-show指令就可以用的. 但是其實v-model也隻是一個語法糖而已,我們這裡就使用input事件拿到最新的輸入數據,然後再賦值給searchForm. 這段代碼是抽離出來瞭的,因為外部顯示的查詢條件和收縮的查詢條件都得用到它,抽離出來後,就不用寫重復的邏輯瞭。 以下是抽離的components組件
/** * 輸入框 為什麼抽離? 這裡我們隻是做表格查詢需要。 * 其實如果要封裝彈窗框那種表單提交組件。 也可以復用這裡的邏輯 * @param { Function } h vue提供的h函數 * @param { Object } item 用戶寫入的描述對象 * @param { Object } vm vue實例 */ export const input = function (h, item, vm) { const { searchForm } = vm return ( <el-form-item label={item.label}> <el-input size="small" on-input={(v) => { searchForm[item.__key] = v }} props={{ value: searchForm[item.__key], placeholder: "輸入內容信息", ...item }} > </el-input> </el-form-item> ) } /** * * @param { Function } h vue提供的h函數 * @param { Object } item 用戶寫入的描述對象 * @param { Object } vm vue實例 */ export const select = function (h, item, vm) { const { searchForm = {}, selectOption = {}, } = vm /** * 監聽下拉改變事件 * @param { String | Number | Boolean } value 選擇下拉數據的值 * @param { Object } value */ const selectChange = function (value, item) { searchForm[item.__key] = value vm.$emit('on-change', value) } return ( <el-form-item label={item.label}> <el-select props={{ value: searchForm[item.__key], placeholder: "===請選擇===", filterable: true, clearable: true, ...item }} on-change={ (val) => { selectChange(val, item) } } size="small"> { selectOption[item.__key] && selectOption[item.__key].map((o) => { return ( <el-option key={ o.value } label={ o.text } value={ o.value } > </el-option> ) }) } </el-select> </el-form-item> ) } /** * * 多選的下拉框,可以再單選基礎上加上兩個屬性,寫一個這個方法,用戶可以稍微少些幾個單詞. * @param { Function } h vue提供的h函數 * @param { Object } item 用戶寫入的描述對象 * @param { Object } vm vue實例 */ export const mulSelect = function (h, item, vm) { item['multiple'] = true item['collapse-tags'] = true return select(h, item, vm) }
以上其實就是把element的幾個組件抽離成獨立的方法,我們在外邊組件使用的時候,就能直接根據用戶傳遞的type,匹配到對應的組件渲染瞭。 並且後邊要增加新的查詢組件的話,隻要在這裡擴展就行,不用修改其他地方。 更容易維護。 另外我們在組件內部默認一些行為,比如默認下拉可以搜索,默認有清空按鈕等,如果系統不需要的話,也可在searchArr去寫上對應屬性覆蓋.
return components[o.__type] ? components[o.__type](h, o, this) : ''
基本的渲染就寫好瞭, 然後我們在外邊使用組件
searchArr: [ { __type: 'input', label: '姓名', __key: 'name' }, { __type: 'select', label: '性別', __key: 'sex', fetchUrl: '/getSelect', method: 'post' // data: [ // { text: '男', value: '1' }, // { text: '女', value: '0' }, // ] }, ]
寫上對應的mock數據
Mock.mock(/\/getSelect/, 'post', () => { return { status: 200, success: true, message: '獲取成功', list: [ { text: '男人', value: '1' }, { text: '女人', value: '2' } ], total: data.list.length } })
ok, 以上就簡單的把條件通過數組描述的方式渲染出來瞭。 接下來,我們得做更多查詢面板的查詢條件.
更多查詢以及展示優化
外邊展示的查詢條件按用戶給的數量展示多少個,更多查詢也可按照用戶給定的參數展示一行展示多少個
=============== props =============== /** * 外部查詢條件展示多少個 */ frontCount: { type: Number, default: 2 }, /** * 更多查詢條件展示多少個 */ backCount: { type: Number, default: 3 } =============== end props =============== =============== computed =============== // 顯示在頁面上的查詢條件 frontSearchArr: function () { return this.searchArr.slice(0, this.frontCount) }, // 顯示在更多查詢裡邊的條件 backSearchArr: function () { return this.searchArr.slice(this.frontCount, this.searchArr.length) }, // 返回寬度 getSpan: function () { const yu = this.backSearchArr.length % this.backCount // 餘數 const duan = 24 / this.backCount // 每個條件的寬度 if (yu === 0) { return 24 } else { return 24 - duan } } =============== end computed =============== ============== render ============== <div style={`width: ${this.backCount * 265 + 30}px`} class={ this.moreShow ? `${className} fadeInDown` : `${className} fadeOutUp` } v-show={ this.moreShow }> <el-row> { backSearchArr.map((o) => { return ( <el-col span={24 / this.backCount}> { components[o.__type] ? components[o.__type](h, o, this) : '' } </el-col> ) }) } <el-col class="searchBtn" span={ getSpan } > <el-button size="small" type="primary" icon="el-icon-search" on-click={ queryTable }>查詢</el-button> <el-button size="small" type="default" icon="el-icon-upload2" on-click={ () => { this.moreShow = !this.moreShow } }>收縮</el-button> </el-col> </el-row> </div> ============== end render ==============
在計算屬性裡邊設置外部顯示的數據和內部顯示的數據。 把數據切割成兩部分. 搜索組件固定長度265. 然後根據條件計算出來更多查詢面板的寬度,再根據getSpan計算屬性算每行數據給的span寬度是多少. 查詢和收縮按鈕根據查詢條件的多少,去計算它放哪個位置。
經測試,效果基本上符合要求.
下拉組件聯動查詢
比如我們需要用我們的查詢組件寫三級聯動的話,應該怎麼實現呢?
選擇省後,需要根據選擇的省加載對應的市下拉數據
如果刪除省後,需要把對應的省和市的下拉也清空掉
聯動主要就是做好這兩塊功能。 一塊是加載對應數據,一塊清空對應數據. ok, 清楚需要做什麼瞭,我們就開始著手怎麼做吧.
{ __type: 'select', __key: 'province', __fetchUrl: '/getProvince', __method: 'get', __nextKey: 'city', __nextFetch: '/getCity', __nextParam: ['province'], __nextMethod: 'get', __emptyArr: ['city', 'district'], label: '省', labelWidth: '40px', }, { __type: 'select', __key: 'city', __method: 'get', __nextKey: 'district', __nextFetch: '/getDistrict', __nextParam: ['province', 'city'], __nextMethod: 'get', __emptyArr: ['district'], label: '市', labelWidth: '40px', }, { __type: 'select', __key: 'district', label: '區', labelWidth: '40px', }
其中獲取省的數據,其實隻要寫__fetchUrl: ‘/getProvince’和__method: ‘get’定義請求接口就可以拿到下拉數據瞭。 然後選擇省下拉的時候,需要在change事件裡邊去根據選擇的省請求數據. 這時候我們需要在數據裡邊定義一下change的時候,需要做的事情. 下面看一下代碼:
__nextKey: 'city', // 給哪一個下拉數據賦值 __nextFetch: '/getCity', // 賦值的請求接口 __nextParam: ['province'], // 請求參數 __nextMethod: 'get', // 請求方式 __emptyArr: ['city', 'district'] // 當修改瞭下拉數據的時候,需要清空哪些下拉類別的值 /** * 監聽下拉改變事件 * @param { String | Number | Boolean } value 選擇下拉數據的值 * @param { Object } value */ const selectChange = async function (value, item) { searchForm[item.__key] = value // 置空下拉列表和下拉數據 if (item && item.__emptyArr) { for (let i = 0; i < item.__emptyArr.length; i++) { let key = item.__emptyArr[i] if (selectOption[key]) { selectOption[key] = [] } vm.$set(searchForm, key, '') } } if (item && item.__nextFetch && item.__nextParam) { let param = {} for (let j = 0; j < item.__nextParam.length; j++) { let value = searchForm[item.__nextParam[j]] if (value) { param[item.__nextParam[j]] = value } } const res = await request({ url: item.__nextFetch, method: item.__nextMethod || 'post' }, param) if (res) { let { data } = res if (data && data.success) { vm.$set(selectOption, item.__nextKey, data.list) } } } vm.$emit('on-change', value) }
ok, 這樣基本就完成瞭三級聯動的功能瞭。 以下是效果圖
組件擴展
組件擴展就很簡單瞭,如果我們還缺少一些需要的組件,可以直接在components.js裡邊新增一個
/** * 日期選擇器 * @param {*} h * @param {*} item * @param {*} vm */ export const date = function (h, item, vm) { const { searchForm } = vm return ( <el-form-item label={item.label} labelWidth={item.labelWidth}> <el-date-picker on-input={(v) => { searchForm[item.__key] = v }} props={{ type: 'date', size: 'small', value: searchForm[item.__key], placeholder: "選擇日期", ...item }} > </el-date-picker> </el-form-item> ) } // searchArr使用的時候添加 { __type: 'date', __key: 'birthday', label: '生日' }
其他地方都不用修改,就完成瞭一個新查詢條件類型的添加瞭. 以下是效果
搜索條件展示
搜索條件功能比較簡單,就是在用戶點擊搜索後,顯示輸入的條件. 我們把這個分離出來作為一個獨立的小組件
<query-info ref="queryInfo" on-remove={this.removeTag} selectionOption={ this.selectOption }></query-info>
這個組件傳入下拉數組過去,我們在設計的時候,有說過,他還可以點擊叉刪除條件,所以得拋出來一個remove自定義事件,然後刪除掉條件後,我們把這個條件清空,然後調用查詢方法. 查詢的時候,再去把搜索條件傳遞給組件,讓組件顯示出來。 這裡需要註意,我們不能直接把搜索條件通過props傳入到組件裡邊,如果是這樣傳入的話,由於數據是響應式的,在你輸入條件的時候,下方就會顯示出來,這並不是我們想要的效果,我們想要的效果是在點擊查詢按鈕的時候,展示正在查詢的查詢條件. 所以這裡的話,我們就在查詢按鈕方法裡邊,直接去調用它內部的方法把條件傳入過去,然後內部再利用JSON.parse(JSON.stringify(val))的方式,實現一個深拷貝,讓數據和外部數據隔離開.
removeTag (name) { this.searchForm[name] = '' this.queryTable() } // 查詢 queryTable () { this.$refs.queryInfo && this.$refs.queryInfo.queryTable(this.searchForm) this.moreShow = false this.$emit('search', this.searchForm) }
組件的實現如下:
<template> <div class="queryInfo"> <el-tag v-for="tag in tags" :key="tag" closable style="margin-right: 10px;" @close="close(tag)" > {{displayName(tag)}} </el-tag> </div> </template> <script> import { getTitle } from '@/utils/utils' export default { props: { // 下拉數據 selectionOption: { type: Object, default: () => [] } }, data () { return { // 查詢條件 searchForm: {} } }, computed: { /// 計算出輸入的條件的數組 tags: function () { let tags = [] let keys = Object.keys(this.searchForm) keys.forEach((key) => { if (this.searchForm[key]) { tags.push(key) } }) return tags } }, methods: { // 點擊關閉 close (tag) { this.searchForm[tag] = '' this.$emit('remove', tag) }, // 外部調用的方法 queryTable (searchForm) { this.searchForm = JSON.parse(JSON.stringify(searchForm)) }, // 顯示名稱, 如果是下拉數據,則去下拉數據裡邊匹配到名稱顯示出來 displayName(key) { let value = this.searchForm[key] if (this.selectionOption[key]) { return getTitle(value, this.selectionOption[key]) } else { return value } } } } </script> <style scoped> .queryInfo { margin-bottom: 10px; } </style>
實現的效果如下:
添加功能按鈕區
這個目前就在searchTable組件用插槽的方式加入
<div class="right"> {this.$slots.rightButton} </div> // 使用 <search-table :searchArr="searchArr" @search="search"> <template v-slot:rightButton> <el-button size="small" type="success" @click="() => { MessageBox({ message: '開發中', type: 'warning' })}"> 添加 </el-button> </template> </search-table> 復制代碼
效果:
寫在最後
那麼至此,查詢組件想要實現的一些功能都一一實現好瞭. 其實做起來並不復雜,最重要的點不在於做,而在於做之前要想清楚. 後面我們再把動態表頭給做好,那麼大致關於表格這塊的相關需求就完成瞭.
也可以給我們的開源項目點點star: http://github.crmeb.net/u/defu 不勝感激 !
到此這篇關於如何一步步基於element-ui封裝查詢組件的文章就介紹到這瞭,更多相關element-ui封裝查詢組件內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- avue實現自定義搜索欄及清空搜索事件的實踐
- 前端vue2 element ui高效配置化省時又省力
- Vue Element前端應用開發之常規的JS處理函數
- Vue.$set 失效的坑 問題發現及解決方案
- element ui提交表單返回成功後自動清空表單的值的實現代碼