在群里请教了一下这样的页面有没有标准的称谓,有的叫标准的CRUD页面,有的说是表格信息页,我也不知道叫什么,上边是查询下边是列表,索性就叫查询列表页了,借助罗永浩的口头禅:”少废话,先看东西“。 演示地址
高频出现的列表页面
从去年9月份换工作以后,参与了几个项目,有数据BI平台,有可视化平台,有大屏项目,有JIRA的二次开发,种类繁多,技术栈也丰富多彩,学习到很多。
- 基于vue的BI平台用iview搭了一套后台,
- 基于react的熟悉了antDesign\umi\dva\bizcharts
- 基于fis3+yog2的JIRA的二次开发
- 最近后端同事又给过来三个基于vue-element-admin的中台系统。
说了这么多,想表达的是”查询列表页面出现的频率太高了“,不管是BI系统、可视化系统,还是toB的中台系统,就前端岗位而言,不可避免的要开发很多这样的查询列表页面,没有什么技术要求门槛,但就是得做,所谓的”低端重复劳动“。
分页组件的迭代
之前BI的项目里用iview的table和Page组件封装了一个分页组件,只需要传入表头、api地址、查询条件,就完成了,所有分页列表都是用它实现的。
接到重构之前一个中台系统的需求,拆分成三个应用,后端给过来三个基于vue-element-admin的git项目地址,粗略的瞄了一下之前的系统,几乎所有左侧频道点进去都是查询列表页面,直接引入之前的分页组件开发v0.1版本,v1.0需求越来越多,索性抽了两天时间把查询条件也一块封装了吧😃。
五分钟生成查询列表页面
查询条件一般都是key:value
的形式,比如后端给出的文档如下图。
我们把查询条件和列表封装成一个组件,查询条件其实就是一个option配置,,组件必不可少的三个入参,接口地址、查询条件配置、自定义表头,开发过程中往往都是先新建.vue组件、引入查询列表组件、编写入参,然后再引入必要的工具方法,其实也很繁琐。
于是我们自己写了个生成工具,按照文档把入参填写一下,点击导出,vue组件代码直接就复制到剪切板,新建.vue
文件,然后ctrl+v
即可。
日光之下无新事,页面可视化的工具很多,借用之前可视化文章里的观点,大家为什么不用可视化,可视化工具为什么没火起来。
- 学习成本高于开发难度
- 工具简单,不能满足业务需求
这么简单的小东西,能满足业务需求吗?入参类型够全吗?可以加权限判断吗? 怎么自定义表头?
组件功能设计
这个组件也是在实际开发过程中封装出来的,不同的业务场景、开发的体验、测试的反馈才能使这个组件更稳定、更好用,我们也是在一个一个的实际需求版本迭代中才把api给确定下来。
先看看组件做什么,很简单,组件分为两部分,上边是查询条件、下边分页表格。
这个组件就是在点击查询,把查询条件的结果拼成入参传入列表分页,上半部分的未来就是封装更多类型,代码也很简单,后续可以在组件代码里自己增加。
主要还是在下半部分,也可以单独使用列表组件,一起看看列表分页的主要入参吧。url
是接口地址,每个列表的接口地址肯定不一样,灵活配置;searchData
是要查询条件,Columns
是iview的表格组件表头,在巨人的肩膀子真的是有益,具体见iview的表格api文档 可以自定义列的展示;allDate
是一个钩子,可以修改后端返回的结果,比如根据权限判断是否增加操作按钮等。
剩下的就是上一页、下一页、跳转、修改页数的操作了,和后端订好格式就OK了。
列表的数据格式也有一定要求,dataCount
总条数,tableData
表体数据,Columns
表头,pageSize
每页条数,如下
{
"code": 1,
"msg": "",
"data": {
"dataCount": 444,
"tableData": [{
"id": 443,
"order_no": "1016410719705984",
"source": 10,
"order_type": 1,
"order_status": 1,
"car_name": "奥迪 A6 2006款 1.8 手动"
}, {
"id": 443,
"order_no": "1016410719705984",
"source": 10,
"order_type": 1,
"order_status": 1,
"car_name": "奥迪 A6 2006款 1.8 手动"
}],
"Columns": [{
"key": "order_no",
"title": "订单ID"
}, {
"key": "order_type",
"title": "订单类型"
}, {
"key": "source",
"title": "来源"
}, {
"key": "car_name",
"title": "车型"
}, {
"key": "order_status",
"title": "状态"
}],
"pageSize": 10
}
}
复制代码
我们再回顾一下,整个组件的功能。
- 查询条件组件根据文档列成配置文件并展示
- 用户输入后把查询条件封装,给到列表分页组件
- 分页根据自定义表头展示内容,完成机械的上下页、跳转等操作
说了这么多,到底有哪些api
可以讲下吧?
数据格式与api
template
部分
除了组件上的props,需要提一下的就是buttons
这个solt
了,除了页面上的搜索和重置按钮,往往需要增加其他的按钮,最常见的比如新建
。
url
接口地址
字符串类型,每个列表都有自己不同的地址
apiType
全局地址
字符串类型,我们ajax使用的是axios
,如果是微服务的项目会有多个域名配置,把axios
的实例名称填上即可,默认为http,在main.js中给vue原型指向。
// 引入微服务apis
import { http, OderApi, ReportApi} from '@/lib/apis.js'
Vue.prototype.$http = http // 默认地址
Vue.prototype.$OderApi = OderApi // 订单服务
Vue.prototype.$reportApi = ReportApi // 报表服务
复制代码
fromOption
表单配置
数组类型,每一个查询项为一个对象,目前仅支持四种输入项,后续会增加其他类型,本着小步快跑原则,先把文章发出来,省的要夭折在自己的笔记软件中(我太懒)。
select
:下拉项
date-picker
:开始-结束日期
input
:输入框
selectAndInput
:下拉+输入
select
{
label:'沿途城市', // 标签名称
name:['cityId'], // *入参名称 必须唯一
type:'select', // *元素类型 selectAndInput
dataType:'arr', // 数据格式 arr/json
arrKey:{ // arr数组格式下为必填
value:'city_id',
key:'name'
},
value:'', // *默认选中值
options:{ // *element组件属性
data:[ // 列表数据 仅在select类型下可用
{
city_id:'',
name:'全部'
},
{
city_id:1001,
name:'北京'
},
{
city_id:1002,
name:'上海'
}
]
}
},
复制代码
下拉菜单的数据支持数组和对象两种格式,分别为arr
和json
表示,后续可能会优化掉这一入参,直接用类型判断。
dataType
为json
时的数据格式,推荐使用json
,可以减少遍历操作。
options:{ // *element组件属性
data:[ // 列表数据 仅在select类型下可用
{
全部:'',
北京:1001,
上海:1002
}
]
}
复制代码
date-picker
使用的elementUI
的日期组件,options
可以传入elementUI
组件的官方属性,type
目前仅支持daterange
,elementUI 日期date-picker文档。
注:因为目前项目是基于vue-element-admin
,封装时没有考虑,结果上半部分是elementUI
的表单组件,下半部分为iviewUI
的表格组件,后期会统一。
{
label:'操作时间',
name:['start_time','end_time'], // *入参名称2个
type:'date-picker',
value:'',
options:{
'value-format':"yyyy-MM-dd",
type:"daterange"
}
}
复制代码
input
{
label:'车牌号',
name:['car_no'], // *入参名称2个
value:'', // *默认值
type:'input'
}
复制代码
selectAndInput
{
label: "ID",
type: "selectAndInput",
value: "",
defaulType:"id", // 默认选中的下拉项值
name: // 下拉菜单数据
{
运单:'transport_order_id',
主运单:'transport_order_main_id'
},
selectOption:{}, // 下拉框官网api设置
inputOtions:{} // 输入框官网api设置
}
复制代码
以往我们在生成这类查询条件时,会特别检查一下后端的接口。 谨防接口设计为类型字段和id字段,不利于拆分为单个查询条件。
// 错误类型入参实例
{
type:'transport_order_id',
id:'99234i88845'
}
// 正确入参实例
{
transport_order_main_id:'99234i88845'
}
// 或
{
transport_order_id:'99234i88845'
}
复制代码
Columns
自定义表头
数组类型
使用过iview
的表格组件应该就很熟悉了文档。
[
{ key: "order_no", minWidth:200, fixed: 'left'},
{
key: "order_status",
minWidth:90,
render:(h, params) => {
let text = this.matchType('order_status',params)
return h('div',text)
}
},
{
key: "source",
render:(h, params) => {
let text = this.matchTypeArr('source',params)
return h('div',text)
}
},
{title: "操作", key: "action",align:'center', minWidth:200, fixed: 'right',
render:(h, params) => {
let setingBtn = h('Button', {
props: {
type: 'primary',
size: 'small'
},
on: {
click: () => {
this.showSet(params.row)
}
}
}, '发运设置')
let editBtn = h('Button', {
props: {
type: 'primary',
size: 'small'
},
style:{
marginLeft:'10px'
},
on: {
click: () => {
this.$router.push('/constExplorer/editorLine/' + params.row.id)
}
}
}, '班车设置')
return h('div',[setingBtn,editBtn])
}
}
]
复制代码
allDate
修改返回结果
函数类型
其实就是一个钩子函数,在每次翻页、搜索的时候,拿到数据并做一些修改,比如给每列加一个最小宽度,或者根据权限判断是否展示操作按钮。
addWidth(data){
// 列最小宽度
data.data.Columns.forEach(item => {
item.minWidth = 170
})
// 操作按钮权限判断
if(_.get(this.powers, 'oderList')){
data.data.Columns.push({title: "操作", key: "action"})
}
}
复制代码
常用方法
日常的实际开发中,除了api,还有很多我们常用的操作,比如下拉菜单是接口,格式化表格数据等,我们来一起过一下吧,把代码贴一下,提高我们的开发效率。
下拉菜单调接口数据在组件初始化时获取下拉菜单的数据
created(){
this.$http.get('/mock/city.json').then(res => {
// 城市列表 数组格式
let formIndex = _.findIndex(this.form, ['name', ['cityid']]);
this.form[formIndex].options.data = this.form[formIndex].options.data.concat(res.data)
})
// 城市列表 对象格式
let formIndex = _.findIndex(this.form, ['name', ['cityid']]);
this.form[formIndex].options.data = Object.assign(this.form[formIndex].options.data,res.data)
})
},
复制代码
表格列数据格式化 比如这样的场景,查询条件中存储了状态码,我们需要根据查询条件中的数据格式化表格的列数据。
查询条件
列数据中返回的是1
或100
这样的格式,我们封装了两个方法,分别进行格式化,后期会优化为一个方法。
// 表头格式
{
key: "order_type",
render:(h, params) => {
let text = this.matchType('order_type',params)
return h('div',text)
}
},
{
key: "source",
render:(h, params) => {
let text = this.matchTypeArr('source',params)
return h('div',text)
}
},
复制代码
matchType(key,params){
let formIndex = _.findIndex(this.form, ['name', [key]]);
let formType = this.form[formIndex].options.data
let text = _.findKey(formType, item => {
return item == params.row[key]
});
return text
}
matchTypeArr(key,params){
let formIndex = _.findIndex(this.form, ['name', [key]]);
let formType = this.form[formIndex].options.data
let keyName = this.form[formIndex].arrKey.value
let text = _.find(formType, item => {
return item[keyName] == params.row[key]
});
return text.name
}
复制代码
按钮权限
权限管理有一些需要精确到按钮,我们有专门的接口获取权限并存在store里,在allData里进行判断,是否展示操作按钮。
addWidth(data){
// 操作按钮权限判断
if(_.get(this.powers, 'oderList')){
data.data.Columns.push({title: "操作", key: "action"})
}
}
复制代码
未解决问题列表
- 获取筛选项下拉后再请求列表接口
- 下拉菜单数据格式判断
- 列数据格式化方法封装
- 权限多按钮的判断
- 查询条件单日期类型配置项目