kendoGrid动态列的实现-高级查询结果展示优化过程

简介:

 高级查询功能是针对所有流程及表单数据的查询入口,作为工作流报表的暂时性替代功能,对于领导和相关流程的业务单位来说十分重要。高级查询功能提供了包括了流程名称、发起人、发起人所属部门等的基本条件查询以及可用作查询条件(是否可用作查询条件在列定义中设置,选中某个表单后,可供选择的列自动展示,并且根据列定义中的数据格式提供不同的查询方式,比如模糊匹配、比如日期的区间查询等)的高级条件查询,通过组合查询条件得到想要的查询结果。

  由于高级查询一直存在分布的问题,究其原因主要是前端的展示与后台的分布难以协同,oaGrid或者原生的kendoGrid均无法展现动态列(这个问题网上确实没有例子,有gridView可以实现动态列的范例)。高级查询先后经历无分页(用<table>标签拼接)、伪前端分页等一系列演化。

  高级查询的权限控制等在前面的博文中有介绍,本文主要介绍针对高级查询结果的前后端协同的分页实现。下面介绍一下目前的实现逻辑以及整个优化的历程。该功能的需求最初由我定义,形成需求后下发给工程师R具体实现,关于条件组合、sql拼接我们暂时不介绍,当时作为雏形的结果展示是一种一次查询出所有结果,然后采用<table>标签动态接拼展示动态的查询结果。下面是最初的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
  * 动态创建table
  * */
function  createDyTable(showColumn, columnName, resultList) {
     //组装table表头
     var  showColumns = showColumn;
     var  title =  "<tr>" ;
     var  value =  "" ;
     $( '#dyTable' ).empty();
     if (columnName.length > 0 &&
         showColumn.length > 0) {
         $.each(columnName,  function (index, items) {
             title +=  "<th>"  + items +  "</th>" ;
         });
         title +=  "<th>操作</th>" ;
         title +=  "</tr>" ;
         $( '#dyTable' ).append(title);
         $.each(resultList,  function (idx, key) {
             value =  "<tr>" ;
             $.each(showColumn,  function (index, items) {
                 var  rowValue = key[ ''  "f_"  + items +  '' ];
                 value +=  "<td style='word-break:break-all;width:300px;'>"  + rowValue +  "</td>" ;
             });
             var  id = key[ 'f_id' ];
             var  operation =  "<td style='word-break:break-all;width:200px;'>" ;
             operation +=  "<span class='operate_detail'></span><a onclick=\"querySeniorDetailById(\'"  + id +  "\');return false;\" href=\"javascript:void(0)\">查看详情</a> " ;
             operation +=  "<span class='operate_history'></span><a onclick=\"querySeniorHistoryById(\'"  + id +  "\');return false;\" href=\"javascript:void(0)\">查看历史</a> " ;
             operation +=  "</td>" ;
             value += operation;
             value +=  "</tr>" ;
             $(' #dyTable').append(value);
         });
     }
};

  以上的js比较简单,第一、从高级查询条件中获得表单信息;第二、循环遍历一行一行地写进<table>。

  由于开发这一版本的时候正是OA一期上线的关键时期,R对我说实现分布的功能对于kendoGrid是不可能的,而当时我们所有的表格的展示用的都是基于kendoGrid的oaGrid,熟悉kendoGrid和js的主管Z也说kendoGrid实现动态列的分布是不行的。当时刚刚担任主管的我本着“评价个冰箱,自己一定也得会制冷”、“要求别人做到的,自己先做到”,而当时的我刚从上家公司C/S开发上转过来不久,对BS结构以及js等前端的开发技术并不了解,所以只有暂时接受这种根本没有分页的方案。

  但这个一直是我关注的心结之一,不久我将该任务分配给了高中毕业,但热衷技术、态度端正、经总监推荐、总经理还是副总裁特批进来的L,结果仍然是以grid无法实现动态列且前后端分页而不了了之。

  再接下来,新入职的G,熟悉js但对grid并不了解。我当时按照固有的思路,即实现前后端分页是不行的。在当时的OA的原项目经理、后来转做技术总监的徐总指导下,在这个固有的思路指引下,开发了基于原生kendoGrid的页面的前端分页。该实现并不是真正的分页查询,只是将所有数据查询后,在前端的分页展示。但这个方案以及其部分实现确实为我后来解决oaGrid的分页提供了一种很好的思路。我们看一下这个阶段的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
function  newCreateDyTable(showColumn, columnName, resultList) {
     $( "#dyTable" ).html( "" );
     var  provinces =  ""
     //组装table表头
     var  showColumns = showColumn;
     if (columnName.length > 0 &&
         showColumn.length > 0) {
          var  clumnss = [];
         $.each(columnName,  function (index, items) {
             clumnss.push({
                  field: showColumn[index],
                  title: items,
                  width: 150
                  });
         });
 
         
         clumnss.push({         
            title:  "流程状态" ,
            field: "f_lczt" ,
            width : 150
         });
         
         var  opertion = []
         clumnss.push({         
            title:  "操作" ,
            field: "opertion" ,
            template:  function (id) {
                var  operation =  "" ;
                operation +=  "<span class='operate_detail'></span><a onclick=\"querySeniorDetailById(\'"  + id.opertion +  "\');return false;\" href=\"javascript:void(0)\">查看详情</a> " ;
                  operation +=  "<span class='operate_history'></span><a onclick=\"querySeniorHistoryById(\'"  + id.opertion +  "\');return false;\" href=\"javascript:void(0)\">查看历史</a> " ;
                 return  operation;
             },
            width : 150
         });
         
         $.each(resultList,  function (idx, key) {
             provinces +=  "{ " ;
             $.each(showColumn,  function (index, items) {
                 var  rowValue = key[' ' + "f_" + items + ' '];
                 
                 provinces+="\""+items+"\""+":"+"\""+rowValue+"\",";
             });
             if(""!=provinces&&null!=provinces){
                 var id = key[' f_id '];
                 var f_lczt = key[' f_lczt '];
                 provinces+= "\"f_lczt\""+":"+"\"" +f_lczt + "\",";
                 provinces+= "\"opertion\""+":"+"\"" + id + "\"";
             }
             provinces += " },"
         });
     }
     if(""!=provinces&&null!=provinces){
         provinces = provinces.substring(0, provinces.length-1);
     }
     provinces = "[" + provinces+"]";
     var reg = /(\r\n)/g;
     provinces.replace(reg, " ")
     var dateobj = eval(' ( ' + provinces + ' )');
      $( "#dyTable" ).kendoGrid({
             dataSource: {
                 data: dateobj,             
                 pageSize: 10
             },
             selModel: true ,
             pageable: {
                 input:  true ,
                 numeric:  false ,
                 messages: {
                     display:  "{0} - {1} 共 {2} 条数据" ,
                     empty:  "没有数据" ,
                     page:  "页" ,
                     of:  "/ {0}" ,
                     itemsPerPage:  "条每页" ,
                     first:  "第一页" ,
                     previous:  "前一页" ,
                     next:  "下一页" ,
                     last:  "最后一页" ,
                     refresh:  "刷新"
                 }
             },         
             columns: clumnss
         });
}

  这一版本的相校初始版本一是实现了用户直接感观的分页,二是表格样式比较美观。

  这一版本的设计中实际已经组装出了oaGrid所需的columns,其设计思路剑走偏锋,对columns的组装从查询的高级条件中直接获取,比我之前设想的从后台组装columns更简单、易行且更符合需求(从后台组装实际无法判断哪些字段是用户期望展示的,虽然可以将状态\id等写死在判断代码中)。

  通过第二版本引出了关于前台应用oaGrid分页的思路,前端的代码如下,这一版本相当简单,保持了代码风格的统一,即查询结果使用oaGrid展示及分页:

1
2
3
4
5
6
7
8
9
10
11
12
13
$( '#myToDoTaskGrid' ).oaGrid({
     beforeload:  function (operation) {
         var  processDefineVo = {dataMap:{
             seniorQueryInfo:seniorQueryInfo,
             showColumn:showColumn
         },seniorQueryBaseDto:seniorQueryBaseDto};
         operation.processDefineVo = processDefineVo;
     },
     title: '待办任务查询结果' ,
     selModel: true ,
     url :  '../workflow/querySeniorInfo.action' ,
     columns : clumnss
});

  参考一般模块的grid分页的逻辑,其grid的列是固定的,分页逻辑的采用了com.gaochao.platform.orm.mybatis.PagingIntercept,对所有的query方法进行分页,如传进的参数有start和limit,就将start和limit设置进来,如果没有,则默认使用limit=2147483647,offset=0,为普通查询语句添加分页逻辑。在oaGrid.js中有page\skip用来传递页码和页数,然后进行查询

   经过查看gc.oaTools.js中的oaGird实现,在第L608

1
2
3
4
5
schema : {
     model : dataModel,
     data :  'pageResult.result'
     ,
     total :  'pageResult.count'

}并结合网上的关于dendoGrid的范例model的格式即为我们使用一般模块使用oaGrid时前端传入的columns数组,再反观data和total即是我们com.gaochao.platform.entity.PageResult中的两个属性,由此我们得到一种方案,即columns不由前端传入而改为重写PageResult加入dataModel,并在后端实现dataModel的拼接(拼接会查询数据库,可能影响效率)。

  第二种方案:很多关于mybatis的分页方案是采用了所谓的Interceptor,对某些数据库操作进行拦截然后处理,我们的数据范围就是使用的com.gaochao.oa.module.com.dependence.server.service.impl.DataruleHandler,这是一种职责链的设计模式。网上提供的插件,也就是interceptor方案我们的系统里已有,即是对所有一般使用oaGrid的后端统一的分页处理:com.gaochao.platform.orm.mybatis.PagingIntercept,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
@Intercepts ({ @Signature (type = Executor. class , method =  "query" , args = { 
     MappedStatement. class , Object. class , RowBounds. class , ResultHandler. class  }) })
public  class  PagingIntercept  implements  Interceptor {
 
     private  PagingDialect pagingDialect;
     
     public  Object intercept(Invocation invocation)  throws  Throwable {
         //query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
         Object[] args = invocation.getArgs();
         MappedStatement ms = (MappedStatement) args[ 0 ];
         Object parameter = args[ 1 ];
         RowBounds rowBounds = (RowBounds) args[ 2 ];
         int  offset = rowBounds.getOffset();
         int  limit = rowBounds.getLimit();
         if  (pagingDialect.supportsPaging() && (offset != RowBounds.NO_ROW_OFFSET || limit != RowBounds.NO_ROW_LIMIT)) {
             BoundSql boundSql = ms.getBoundSql(parameter);
             String sql = boundSql.getSql().trim();
             sql = pagingDialect.getPagingSql(sql, offset, limit);
             offset = RowBounds.NO_ROW_OFFSET;
             limit = RowBounds.NO_ROW_LIMIT;
             args[ 2 ] =  new  RowBounds(offset, limit);
             BoundSql newBoundSql =  new  BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject());
             copyMetaParameters(boundSql, newBoundSql);
             MappedStatement newMs = copyFromMappedStatement(ms,  new  BoundSqlSqlSource(newBoundSql));
             args[ 0 ] = newMs;
        
         return  invocation.proceed();
     }
     
     public  Object plugin(Object target) {
         return  Plugin.wrap(target,  this );
     }
 
     public  void  setProperties(Properties properties) {
         String className = (String) properties.get( "dialect" );
         if (StringUtils.isBlank(className)) {
             throw  new  MyBatisException( "分页方言 不能为空." );
         }
         Class<?> dialectClass =  null ;
         try  {
             dialectClass = ClassUtils.forName(className);
         catch  (Exception e) {
             throw  new  MyBatisException(e);
         }
         pagingDialect = (PagingDialect) BeanUtils.instantiate(dialectClass);
     }
 
      /**
      * 复制BoundSql的MetaParameter
      * <br>------------------------------<br> 
      * @param lhs
      * @param rhs
      */
     private  void  copyMetaParameters(BoundSql lhs, BoundSql rhs) {
         for  (ParameterMapping map : lhs.getParameterMappings()) {
             String key = map.getProperty();
             Object value = lhs.getAdditionalParameter(key);
             if  ( null  != value) {
                 rhs.setAdditionalParameter(key, value);
             }
         }
     }
 
     // see: MapperBuilderAssistant
     private  MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
         Builder builder =
             new  MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
 
         builder.resource(ms.getResource());
         builder.fetchSize(ms.getFetchSize());
         builder.statementType(ms.getStatementType());
         builder.keyGenerator(ms.getKeyGenerator());
         builder.keyProperty(ms.getKeyProperty());
         // setStatementTimeout()
         builder.timeout(ms.getTimeout());
         // setStatementResultMap()
         builder.parameterMap(ms.getParameterMap());
         // setStatementResultMap()
         builder.resultMaps(ms.getResultMaps());
         builder.resultSetType(ms.getResultSetType());
         // setStatementCache()
         builder.cache(ms.getCache());
         builder.flushCacheRequired(ms.isFlushCacheRequired());
         builder.useCache(ms.isUseCache());
         return  builder.build();
     }
     
     public  static  class  BoundSqlSqlSource  implements  SqlSource {
        
         private  BoundSql boundSql;
 
         public  BoundSqlSqlSource(BoundSql boundSql) {
             this .boundSql = boundSql;
         }
 
         public  BoundSql getBoundSql(Object parameterObject) {
             return  boundSql;
         }
     }
}


  如前所述,当我们有start、limit时,该方法使用start\limit进行分页,如果没有传入,相当于不分页;该拦截针对的所有query方法

1
2
@Intercepts ({ @Signature (type = Executor. class , method =  "query" , args = { 
     MappedStatement. class , Object. class , RowBounds. class , ResultHandler. class  }) })

  

        我们的高级查询也是已有拦截的,只是因为我们没有传入start\limit,默认不分页;另外G在做前端伪分页时采用了拼接columns的方法将columns传入,但在接下来的处理是查询所有,然后在前端使用原生的kendogrid进行类似table的动态拼接即newCreateDyTable方法。这种方法是将结果一次查出,分页展示。结合PagingIntercept和G的columns拼接方法,我们只需要简单修改高级查询的查询逻辑,传入start、limit等分页元素,前端完全复用一般模块的gird。这种分页是后端的分页,且不用增加太多的代码。可以肯定,在同样的sql和数据量的情况下,这种分页要比不分页或前端分页给用户的体验更佳,等待时间更短。



     本文转自 gaochaojs 51CTO博客,原文链接:http://blog.51cto.com/jncumter/1624376 ,如需转载请自行联系原作者


相关文章
|
7月前
|
PHP 数据库
fastadmin框架如何查询数据表指定时间段内的数据
fastadmin框架如何查询数据表指定时间段内的数据
170 0
|
SQL 前端开发 JavaScript
eggjs 怎么实现获取账单列表接口并且实现列表数据分页查询功能?
eggjs 怎么实现获取账单列表接口并且实现列表数据分页查询功能?
155 0
eggjs 怎么实现获取账单列表接口并且实现列表数据分页查询功能?
|
1月前
|
缓存 关系型数据库 MySQL
MySQL查询优化:提速查询效率的13大秘籍(合理使用索引合并、优化配置参数、使用分区优化性能、避免不必要的排序和group by操作)(下)
MySQL查询优化:提速查询效率的13大秘籍(合理使用索引合并、优化配置参数、使用分区优化性能、避免不必要的排序和group by操作)(下)
|
1月前
|
SQL 前端开发
基于jeecgboot复杂sql查询的列表自定义列实现
基于jeecgboot复杂sql查询的列表自定义列实现
19 0
|
1月前
|
关系型数据库 MySQL 数据库
十七、MySQL查询优化
十七、MySQL查询优化
31 0
|
8月前
|
存储 关系型数据库 MySQL
“视图优化、索引策略和数据导入导出技巧“
“视图优化、索引策略和数据导入导出技巧“
28 0
|
11月前
|
存储 自然语言处理 NoSQL
【Java项目】1000w数据量的表如何做到快速的关键字检索?
【Java项目】1000w数据量的表如何做到快速的关键字检索?
83 0
|
12月前
|
JavaScript 前端开发 API
【项目数据优化三】长列表数据优化
【项目数据优化三】长列表数据优化
101 0
【SQL开发实战技巧】系列(二十五):数仓报表场景☞结果集中的重复数据只显示一次以及计算部门薪资差异高效的写法以及如何对数据进行快速分组
本篇文章讲解的主要内容是:***如何使用lag函数让结果集重复数据只显示一次、用行转列pivot写法优化部门之间计算工资差异类似需求、如何通过ceil函数对已有数据进行分组打印、放假安排团队分组值班,如何通过ntile()over(order by )快速进行人员分组***
【SQL开发实战技巧】系列(二十五):数仓报表场景☞结果集中的重复数据只显示一次以及计算部门薪资差异高效的写法以及如何对数据进行快速分组
|
SQL 关系型数据库 MySQL
DQL (数据查询语言)之基础查询之列控制标题 | 学习笔记
快速学习 DQL (数据查询语言)之基础查询之列控制标题
115 0
DQL (数据查询语言)之基础查询之列控制标题 | 学习笔记