引言
不会写代码的业务方不是好研发!报表作为一种供业务人员随时随地掌握业务情况必不可少的工具,在开发阶段往往存在种类繁杂、前后端开发繁琐等问题,开发一张报表可能需要几个工作日。如何提高报表的制作效率,降低开发成本是值得探讨的问题。本文提出了一种基于配置的通用化动态报表平台,该平台可以大大简化前后端的报表开发成本,同时提供丰富的组件生成不同类型和功能的报表,即使是没有开发经验的业务人员经过简单的培训也可以快速上手,制作一个你想要的漂亮的报表。
1、原理概述
本着约定大于配置的原则,参考MarkDown的设计理念,结合一处编写到处使用的思想,我们设计出基于配置的动态报表平台。首先用户在权限系统进行权限认证后,将前端配置好的特殊sql语句和各种查询组件传递到后端进行保存。之后用户点击相应的报表后,前端会解析之前配置的组件信息并动态生成(文本框,下拉选择框,日期选择框等),当在控件中输入对应的查询条件后,后台会根据报表编码读取之前的配置项,并对相关语法进行解析和对应字段映射,通过动态执行sql语句向前端展示对应的报表信息。
2、使用方式
2.1 配置动态报表
若想配置并使用自定义的动态报表,需要进行以下四步操作:
a)编写出自定义报表所需要的SQL语句
b)为SQL中的部分字段设置别名——例如: out_order_code as '出库单号'
c)将where条件改造为动态报表的特定语法
d)对动态报表的元数据进行扩展配置(元数据指的是前端的查询组件)
2.1.1 编写出自定义报表所需要的SQL语句
根据自身需求进行编写,这里给出一个示例:
select
sku_id,
created_time,
entry_order_type
from t1 where
t1.entry_order_type = ?
and t1.sku_id = ?
and created_time >= ?and created_time <= ?
2.1.2 为SQL中的部分字段设置别名
为之前编写的SQL的查询字段设置自定义的别名,这些别名将会成为报表页面的表头,如下图:
select
sku_id as 'skuId',
created_time as '创建时间',
entry_order_type as '单据类型'
from t1 where
t1.entry_order_type = ?
and t1.sku_id = ?
and created_time >= ?and created_time <= ?
2.1.3 将where条件改造为动态报表的特定语法
若要实现entry_order_type,sku_id,created_time的取值是通过前端组件动态生成,并按照顺序展示:
我们可以对上述的sql语句进行如下改造:
select
sku_id as 'skuId',
created_time as '创建时间',
entry_order_type as '单据类型'
from t1 where
1=1
<@if selectSingle_1??> and t1.entry_order_type = ${comp.selectSingle_1_单据类型}</@if>
<@if inputText_2??> and t1.sku_id = ${comp.inputText_2_skuId}</@if>
<@if dateRange_3??> and t1.created_time >= ${comp.dateRange_3_创建时间} and t1.created_time <= ${comp.dateRange_3_创建时间} </@if>
以上语句的含义是:如果selectSingle_1参数不为空,则entry_order_type条件=selectSingle_1传递的参数值。这里的<@if XXX??>是一种固定的写法,用来表示若XXX条件成立,则使用该语句。
comp.selectSingle_1是一种特殊的查询条件组件的语法,通用写法如下:
comp.组件名_序号[_别名]
comp.提供组件提示功能,可以快捷召唤各种已定义好的常用组件,如下图
selectSingle_1以_进行分隔,表示需要在前端显示的组件名_显示顺序。${}用来从前端组件取值。
目前动态报表平台拥有的组件如下:
-
- 单选框
- 固定含义下拉选择框
- 自定义值下拉选择框
- 仓库下拉选择框
- 日期范围选择框
- 持续更新中…
2.1.4 对动态报表的元数据进行扩展配置
如果你想对动态报表做更多的扩展,那么欢迎你看看我们的扩展内容。元数据扩展,是对动态报表的补充,对约定之外各种组件的额外配置。具体配置内容如下。
{
"selectors": {
"组件名": {
"mandatory": false, // 是否必填,默认false
"label": "选项", // 查询组件展示名
"defaultValue": "", // 默认值
"displayAll": true, // 是否全选,默认true
"rangeLimit": 30, // 时间默认区间,默认1天
"defaultRange": 10, // 默认展示时间区间
"options": [{ // options配置下拉选默认值,是一种手动设置下拉框枚举值的方法
"label": "全部",
"value": ""
},
{
"label": "初始状态",
"value": "0"
},
{
"label": "部分分配",
"value": "10"
}
],
"apiOptions": { // apiOptions 配置下拉选取值接口,是一种通过接口调用获取下拉框的枚举值的方法
"url": "/XX/XX",
"method": "get",
"params": {
"enumName": "WmsOutBoundTypeEnum"
},
"label": "typeDesc",// 下拉选kv转换,默认typeDesc
"value": "typeCode" // 下拉选kv转换,默认typeCode
}
}
}
}
2.2 DEMO示例
2.2.1 SQL语句
select
sku_id as 'skuId',
created_time as '创建时间',
entry_order_type as '单据类型'
from t1 where
1=1
<@if selectSingle_1??> and t1.entry_order_type = ${comp.selectSingle_1_单据类型}</@if>
<@if inputText_2??> and t1.sku_id = ${comp.inputText_2_skuId}</@if>
<@if dateRange_3??> and t1.created_time >= ${comp.dateRange_3_创建时间} and t1.created_time <= ${comp.dateRange_3_创建时间} </@if>
2.2.2 元数据配置
{
"selectors": {
"selectSingle_2": {
"apiOptions": {
"url": "/XX/XX",
"method": "get",
"params": {
"enumName": "WmsOutBoundTypeEnum"
}
}
}
}
}
2.2.3 页面展示效果
3、高级特性
3.1 扩展动态报表远程获取数据模式
目前的动态报表依赖SQL,但是有些报表过于复杂,需要通过程序处理才能获得更好的效果。因此设计了一种
远程获取数据模式提供支持。即在“报表开发”中配置查询条件,配置远程API来获取报表所需数据,后端只需要做简单的兼容适配即可。
3.1.1 SQL模块配置
由于报表内容无需通过编写sql语句动态获取,而是通过调用api获取,因此SQL模块只要简单的定义前端需显示的组件即可,例如:
${comp.inputText_1_任务ID}
${comp.inputText_2_SKUID}
${comp.inputText_3_失败原因}
${comp.selectSingleReceivedType_4_收货状态}
${comp.selectMultiWarehouseCode_5_仓库}
3.1.2 元数据模块配置
此处需要额外定义exportApiOptions模块来调用api获取报表数据,传递给后端的入参是由前端将SQL模块拼接成json格式生成。
{
"selectors": {},
"datasource": {
"remote": true,
"apiOptions": {
"url": "http://ip:端口号/XX",
"method": "post"
},
"exportApiOptions": {
"url": "http://ip:端口号/XX",
"type": "wms" //附加定义:用来导出对应业务渠道的报表
}
}
}
3.1.3 后端兼容方式
约定:
-
- 使用POST请求
- 特定的入参
- 特定的出参
// 入参DEMO
@Data
public class DynamicRequest {
private Integer pageNum;
private Integer pageSize;
// {"inputText_1":"Nike SB", "inputText_2":"9527"}
// Map可以替换为合适的Pojo对象
private Map params;
}
// 出参demo
@Data
public class DynamicPagingObject<T> extends PagingObject<T> {
// [{"prop":"skuName", "label":"商品名称"}, {"prop":"color", "label":"颜色"}]
private List<Map> tableTitle;
}
3.2 报表延伸钻取功能
当需要从一个报表的某个字段值关联查找到另一个报表内容,可以在元数据中添加如下设置。
3.2.1 元数据定义
"linkedColumns": [
{
"type": "newWindow", //关联报表打开形式(newWindow表示在新窗口中打开)
"columnName": "交接单号", //关联映射的字段名称
"dataCode": "XXX",//关联报表编号
"params": [
{
"label": "inputText_1", //关联报表的组件名
"index": "交接单号" //关联报表的字段索引
}
]
}
]
3.2.2 效果演示
点击交接单号的某一项后,会将交接单号传到另一个新的报表页面,点击查询后即可查到新报表的数据:
4、总结
动态报表目前已经解决了供应链平台90%以上的报表开发需求,同时已有20张左右的动态报表投入到线上使用,大大减少了前端和后端的开发工作量。原本制作一张报表需要前后端联合开发2-3天的时间,而采用动态报表只需要一个人花费几个小时甚至更短时间就可以实现。除此之外,通过向部分仓库数据分析人员讲解和推广动态报表的使用方法,非研发技术人员也投入到动态报表的开发行列。未来的动态报表还有一些需要改进的地方,例如错误sql执行报错的前端日志提示,大报表的多线程查询优化。
文丨小威弟&蓝蓝的天空