一、开发背景
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官网上,供大家使用。