一、開發背景
2年前做了一個基於element-ui的layout組件發布到npm package上去,廣受大家的歡迎,下載量每周頗升。這個組件的好處在於開發者不用寫太多html代碼和邏輯,只要通過配置json的方式就能馬上生成后台,包括菜單欄和導航面包屑等一鍵生成,還能根據不同的需求做定制化后台界面,就像拼積木一樣,讓用戶只專注於內容核心代碼的開發。現在還沒做大范圍的推廣,如果我覺得它做得足夠好,我一定會放到npm上推薦給各位讀者使用哈。當初為了解答網友的疑問,我還專門建立了該組件的官網。logo看起來有沒有很熟悉?哈哈哈,它其實就是element-ui的logo進行改造的,意義就是告訴使用者,這是element-ui功能的組合加強版。

為了迎合公司的OKR,我提出了對表單表格組件的封裝。為什么我會有這個想法?其實這個組件我很早就想做了,只是以前做的是基於UI層面的,近期我接手了公司的一個項目叫童畫,每天做的事情感覺就是復制粘貼,修改部分不同的功能和字段名稱。組件的意義在於可以在遇到同一類設計場景時,可以復用,從而減少設計的時間和形成產品的統一性。傳統的搜索表單不就是這樣嗎?上面是表單搜索字段,中間是搜索結果的表格,下面是搜索結果的分頁。把表單和表格組合起來的好處在於很多搜索字段都是基於表格組件的某些字段,那我根據search字段進行篩選不就可以了么,很久之前,我們總是在吐槽產品經理總是喜歡截圖現有功能,然后做字段修改,搞成原型扔給前端。如果前端也能像他這樣簡單,那該多好呀,類似這樣的想法油然而生。剛開始我只是為了方便我的工作,沒想到領導卻重視了起來,想把這個組件推廣給公司其他十幾個前端同事使用,於是乎,我便認真開搞了起來。有理論還不行,得有場景實踐,剛好公司的項目童畫有很多場景,我根據它里面的場景,做了很多功能的封裝和兼容。
寫到這里,有人會說了,這不就是CRUD組件嗎?有這想法的話,說明你還太年輕和小看這個組件的功能了。傳統的CRUD組件靈活性不是很高,這個組件的好處是配置即可用,不用考慮其他搜索,翻頁,清空等各種邏輯,讓組件達到高度復用,封裝了場景的插槽類型,但為了防止翻車,我還是預留了變態需求的插槽。字段的使用更多采用elementUI的命名方式,讓使用者減少學習成本。這樣做的好處是什么呢?首先,前端再也不用寫頁面了,其次,對於比較規矩的搜索表單頁面,完全可以通過請求接口的形式交給后端來配置呈現頁面即可,根本沒有前端什么事了,前端的工作可以解放出來做更復雜的功能開發。
二、部分原理
- 搜索字段通過search=true進行篩選;
- 分頁通過請求的total總數進行分頁;
- Vue.prototype.$query接入請求。
三、組件實踐
為了使用方便,我把它做成了組件並放到了公司的私服上,接下來的工作就是寫文檔啦,以下是部分文檔的編寫,因為時間問題,沒來得及好好檢查,各位看官將就看一下就行啦。
3.1 版本升級
- [0.0.0] 項目初始化
- [0.0.1] 文檔編寫
- [0.0.2] 屬性字段的修改
3.2 如何使用
確保安裝好elementUI
npm install element-ui --save
3.3 安裝
- 先切回到公司內部npm源
nrm use xxx - Windows系統安裝
npm install search-table --save - Mac系統安裝
sudo npm install search-table --save
3.4 更新
npm install search-table@latest
3.5 組件列表
search-table 布局組件
3.6 引用
-
main.js
import Vue from "vue" import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' Vue.use(ElementUI) import SearchTable from "search-table" Vue.use(SearchTable) import axios from 'axios' const http = axios.create({ baseURL: '/' }) http.interceptors.request.use(config => { let userInfo = getCookie(xxx) if (userInfo) { userInfo = JSON.parse(decodeURIComponent(userInfo)) config.headers['Access-Token'] = userInfo.accessToken } return config }) Vue.prototype.$query = http -
[page].vue
... <template> <SearchTable :options="options" ref="SearchTable" @on-operation="handleOperation" @on-selection="handleSelect"> </SearchTable> </template> ... import options from './options' export default { ... data () { return { ...options } }, methods: { showDialog ({id}) { this.$confirm('確認已聯系過本用戶嗎?', '提示', { confirmButtonText: '確定', cancelButtonText: '取消', type: 'warning' }).then(async () => { let res = await auditionStatus({id, status: 2}) this.notifyMX(res, '處理成功') this.$refs.SearchTable.getTableData() }).catch(() => {}) }, handleOperation (val, ...arg) { switch (val) { case 'handle': this.showDialog(...arg) break default: break } } } ... }3.7 使用示例 【與[page].vue同級建立options.js文件】
export default { options: { request: { api: '/student/web/student/enroll/list', method: 'GET', paramMap: { index: 'pageIndex', limit: 'pageSize' }, resultMapping: { total: 'total', data: 'pageData' } }, size: '', // medium/mini/small, 默認medium labelWidth: 90, submitBtn: true, // 搜索按鈕,默認true,非必填 submitText: '查詢', // 搜索按鈕的文字,默認查詢,非必填 clearBtn: true, // 清除按鈕,默認true clearSize: 'mini', // medium/mini/small, 默認medium clearText: '清除', // 清除按鈕的文字,默認清除,非必填 column: [ { slotType: 'selection' }, { prop: 'keyword', label: '學員/家長', search: true, hide: true }, { prop: 'studentName', label: '學員' }, { prop: 'telephone', label: '家長手機號', width: 130 }, { prop: 'type', label: '報名類型', width: 90, search: true, type: 'select', slot: true, slotType: 'text', slotArray: [{ label: '新報', value: 1 }, { label: '續報', value: 2 }], dicData: [{ label: '新報', value: 1 }, { label: '續報', value: 2 }] }, { prop: 'courseFee', label: '繳費金額', slot: true, slotType: 'regEx', regEx: '¥{{courseFee/100}}' }, { prop: 'payType', label: '支付方式', search: true, type: 'select', width: 100, dicData: [ { label: '微信', value: '微信' }, { label: '支付寶', value: '支付寶' }, { label: '銀行卡轉賬', value: '銀行卡轉賬' }, { label: '其它', value: '其它' } ] }, { prop: 'courseCount', label: '報名課時' }, { prop: 'followTeacher', label: '跟進人' }, { prop: 'createTime', label: '報名時間', param: 'beginTime,endTime', format: 'yyyy 年 MM 月 dd 日', valueFormat: 'timestamp', search: true, width: 160, type: 'daterange' }, { prop: 'auditor', label: '報名老師', search: true, param: 'teacherId', // 修正請求參數名 type: 'select', dicData: [], dicUrl: '/org/web/org/user/list/teacher', dicMap: { label: 'userName', value: 'id' } }, { prop: 'status', label: '報名狀態', slot: true, slotType: 'tag', width: 110, slotArray: [{ type: 'warning', label: '待審核', value: 1 }, { type: 'danger', label: '審核不通過', value: 3 }, { type: 'success', label: '審核通過', value: 2 }], dicUrl: '' }, { prop: 'operation', label: '操作', width: 80, slot: true, slotType: 'operation', slotArray: [ { label: '去審核', value: 'handle', filter: ({status}) => { return status === 1 } } ] } ] } }
3.8 參數說明
Variables
| 參數 | 說明 | 類型 | 可選值 | 默認值 |
|---|---|---|---|---|
| options | 組件配置屬性,詳情見下面options屬性 | Object | - | - |
Events
| 事件名 | 說明 | 參數 |
|---|---|---|
| on-search | 點擊搜索后觸發該事件,非必填 | - |
| on-page | 切換頁面時觸發該事件,非必填 | - |
| on-selection | 當選擇項發生變化時會觸發該事件,非必填 | - |
| on-select | 當搜索選擇列表發生變化時會觸發該事件,非必填 | - |
| on-operation | 當"操作"(包括'switch-change')發生變化時會觸發該事件 | 屬性slotArray[{value}] |
Methods [this.$refs.SearchTable]
| 方法名 | 說明 | 參數 |
|---|---|---|
| setOptionData | 獲取select選項數據接口后塞入的字段 | {result: res.data,label: 'name',value: 'id',target: 'courseName'} |
| getTableData | 重新請求該頁面事件 | - |
Options Attributes
| 參數 | 說明 | 類型 | 可選值/例子 | 默認值 |
|---|---|---|---|---|
| request.url | 請求地址,必填項 | String | /student/web/audition/list | - |
| request.method | 請求方法,可不填 | String | get/post | get |
| request.paramMap.index | 請求映射index參數 | String | pageIndex | pageIndex |
| request.paramMap.size | 請求映射size參數 | String | pageSize | pageSize |
| request.resultMap.total | 請求映射total參數 | String | total | total |
| request.resultMap.data | 請求映射data參數 | String | pageData | pageData |
| size | 表單組件的大小 | String | medium/mini/small | mini |
| labelWidth | label的寬度 | String | - | 70 |
| submitBtn | 是否顯示搜索按鈕 | Boolean | true/false | true |
| submitText | 搜索文本 | String | - | 查詢 |
| clearBtn | 是否顯示清空按鈕 | Boolean | true/false | true |
| clearText | 清空文本 | String | - | 清除 |
| column | 表格配置屬性,詳情見下面Column屬性 | Array | - | [ ] |
Column Attributes
| 參數 | 說明 | 類型 | 可選值 | 默認值 |
|---|---|---|---|---|
| prop | 字段參數 | String | - | - |
| param | 當請求參數與字段不同時,可用該參數修正,多參用逗號隔開,非必填 | String | 'beginTime,endTime'/'teacherId' | - |
| width | th 寬度,非必填 | String | - | - |
| format | type=daterange時,'yyyy 年 MM 月 dd 日' | String | yyyy 年 MM 月 dd 日 | - |
| valueFormat | type=daterange時,值的顯示格式 | String | timestamp | timestamp |
| label | 字段名稱 | String | - | - |
| hide | 隱藏字段,僅搜索表單顯示,表格不顯示的字段 | Boolean | - | - |
| search | 是否是搜索字段 | Boolean | true/false | false |
| searchType | 搜索表單組件類型, 當search=true時才有效 | String | select/input/daterange | input |
| dicData | select選項的值 | Array | [{label:'',value:''}] | [] |
| dicUrl | select選項的請求地址 | String | - | - |
| dicMap.label | label字段映射 | String | - | - |
| dicMap.value | value字段映射 | String | - | - |
| slot | table字段是否使用插槽 | Boolean | true/false | false |
| slotType | table插槽類型 | String | selection/tag/image/text/button | - |
| slotArray | 表格插槽顯示字段的各種值,對象中,可加filter字段方法過濾條件顯示 | Array[Object] | [{type:'success',label:'文本',value:'',filter:()=>{}}] | - |
| searchRight | 表格頭部(搜索)右側內容插槽顯示字段的各種值 | Array[Object] | [{type:'',label:'',value:'',filter:()=>{}}] | - |
Scoped Slot
默認插槽, 用以處理復雜字段
| 參數 | 說明 |
|---|---|
| 列的名稱 | 列自定義列的內容,參數為{row,label,dic,$index} |
| searchRight | 表格頭部(搜索)右側內容 |
四、最后感謝
最后,感謝同事世丞在字段命名上給了很多建議,同時也感謝領導給了很多刁難的意見,讓這個組件的功能越發強大,也讓開發者使用更方便,達到配置即可使用的地步。當然了,組件還需要更多場景的訓練才能真正實現各種功能的兼容。后期我希望可以放到layout官網上,供大家使用。

