公司产品迭代,要重新设计统计这一块,分配到我这来做,现在基本已经完成,遂把其中的一部分,统计表的生成的方式拿出来做为总结,由于是一部分,这里我就剔除了业务方面的代码,只描述生成的过程。
1 package com.sun.stat.basic; 2 3 import java.io.Serializable; 4 import java.util.ArrayList; 5 import java.util.List; 6 7 /** 8 * 表头单元格对象 9 * 10 */ 11 public class HeadCell implements Serializable, Cloneable { 12 13 private static final long serialVersionUID = -1756261752878954234L; 14 15 private String id; 16 private String name; 17 private HeadCell parent;// 父单元格对象 18 private List<HeadCell> children = new ArrayList<HeadCell>();// 子单元格集合 19 private Integer deep = 0;// 层深度 20 21 public HeadCell(String id, String name, HeadCell parent, Integer deep) { 22 super(); 23 this.id = id; 24 this.name = name; 25 this.parent = parent; 26 this.deep = deep; 27 } 28 29 public HeadCell getParent() { 30 return parent; 31 } 32 33 public void setParent(HeadCell parent) { 34 this.parent = parent; 35 } 36 37 public List<HeadCell> getChildren() { 38 return children; 39 } 40 41 public void setChildren(List<HeadCell> children) { 42 this.children = children; 43 } 44 45 public String getId() { 46 return id; 47 } 48 49 public void setId(String id) { 50 this.id = id; 51 } 52 53 public Integer getDeep() { 54 return deep; 55 } 56 57 public void setDeep(Integer deep) { 58 this.deep = deep; 59 } 60 61 /* 62 * 克隆headCell实体 63 * 64 * @see java.lang.Object#clone() 65 */ 66 @Override 67 public HeadCell clone() { 68 HeadCell o = null; 69 try { 70 o = (HeadCell) super.clone(); 71 } catch (CloneNotSupportedException e) { 72 e.printStackTrace(); 73 } 74 return o; 75 } 76 77 public String getName() { 78 return name; 79 } 80 81 public void setName(String name) { 82 this.name = name; 83 } 84 85 /** 86 * 如果选择了多个统计项单元格对象的最底层的子对象的个数 87 * 88 * @param countItem 统计项的个数 89 * @return 90 */ 91 public int getColNum(int countItem) { 92 int rowNum = children.size(); 93 for (HeadCell hc : children) { 94 if (hc.getColNum(countItem) > 0) { 95 rowNum += hc.getColNum(countItem) - 1; 96 } 97 } 98 return rowNum == 0 ? countItem : rowNum; 99 } 100 101 /** 102 * 取得单元格对象的最底层的子对象的个数 103 * 104 * @return 深度 105 */ 106 public int getRowNum() { 107 int rowNum = children.size(); 108 for (HeadCell hc : children) { 109 if (hc.getRowNum() > 0) { 110 rowNum += hc.getRowNum() - 1; 111 } 112 } 113 return rowNum; 114 } 115 }
在实际的产品应用中,报表这一块一直是一个产品的亮点,通常一个复杂的报表涉及到了字段的统计,数据范围的统计,多区域的统计,这就需要一条极为复杂的sql了
,这是最考验开发人员严谨性的时候,一个不小心大概就会“弄丢”了某些数据,当然我们这里就不讨论数据的生成了(我所用到的数据是通过实体间的关联关系,“字典”等生产的,说简单点就是我的sql是hibernate给写的,当然了,这是有局限性的,所以我们把sql报表单独抽离出来,但是最终的实现都是殊途同归)。
上面的这个对象就是我们定义的单元格对象,一个统计表涉及到了横向表头、纵向表头,每个表头都可以有自己的子查询(子集),这就构建了一套拥有主子关系的对象,通过这些对象进一步生成统计表!
1 package com.sun.stat.process; 2 3 import java.util.ArrayList; 4 import java.util.Collections; 5 import java.util.List; 6 import java.util.Map; 7 import java.util.Map.Entry; 8 import java.util.UUID; 9 10 import org.junit.Test; 11 12 import com.sun.stat.basic.HeadCell; 13 import com.sun.stat.component.Th; 14 import com.sun.stat.utils.ListUtils; 15 16 /** 17 * 通过HeadCell对象集合构建一个复杂的table 18 */ 19 public class ProductTable { 20 List<HeadCell> rowHeadCells = new ArrayList<HeadCell>(); 21 List<HeadCell> colHeadCells = new ArrayList<HeadCell>(); 22 23 /** 24 * 准备数据,构造一系列的headCell对象 25 */ 26 public void prepare() { 27 // 新建一个横向表头 28 HeadCell r1 = new HeadCell(UUID.randomUUID().toString(), "r1", null, 0); 29 HeadCell r2 = new HeadCell(UUID.randomUUID().toString(), "r2", null, 0); 30 HeadCell r3 = new HeadCell(UUID.randomUUID().toString(), "r3", null, 0); 31 HeadCell r4 = new HeadCell(UUID.randomUUID().toString(), "r4", null, 0); 32 HeadCell r5 = new HeadCell(UUID.randomUUID().toString(), "r5", null, 0); 33 HeadCell r1_1 = new HeadCell(UUID.randomUUID().toString(), "r1_1", r1, 1); 34 HeadCell r1_2 = new HeadCell(UUID.randomUUID().toString(), "r1_2", r1, 1); 35 HeadCell r2_1 = new HeadCell(UUID.randomUUID().toString(), "r2_1", r2, 1); 36 HeadCell r2_2 = new HeadCell(UUID.randomUUID().toString(), "r2_2", r2, 1); 37 HeadCell r3_1 = new HeadCell(UUID.randomUUID().toString(), "r3_1", r3, 1); 38 HeadCell r3_2 = new HeadCell(UUID.randomUUID().toString(), "r3_2", r3, 1); 39 HeadCell r4_1 = new HeadCell(UUID.randomUUID().toString(), "r4_1", r4, 1); 40 HeadCell r4_2 = new HeadCell(UUID.randomUUID().toString(), "r4_2", r4, 1); 41 HeadCell r5_1 = new HeadCell(UUID.randomUUID().toString(), "r5_1", r5, 1); 42 HeadCell r5_2 = new HeadCell(UUID.randomUUID().toString(), "r5_2", r5, 1); 43 r1.getChildren().add(r1_1); 44 r1.getChildren().add(r1_2); 45 r2.getChildren().add(r2_1); 46 r2.getChildren().add(r2_2); 47 r3.getChildren().add(r3_1); 48 r3.getChildren().add(r3_2); 49 r4.getChildren().add(r4_1); 50 r4.getChildren().add(r4_2); 51 r5.getChildren().add(r5_1); 52 r5.getChildren().add(r5_2); 53 54 rowHeadCells.add(r1); 55 rowHeadCells.add(r2); 56 rowHeadCells.add(r3); 57 rowHeadCells.add(r4); 58 rowHeadCells.add(r5); 59 60 // 新建一个纵向表头 61 HeadCell c1 = new HeadCell(UUID.randomUUID().toString(), "c1", null, 0); 62 HeadCell c2 = new HeadCell(UUID.randomUUID().toString(), "c2", null, 0); 63 HeadCell c3 = new HeadCell(UUID.randomUUID().toString(), "c3", null, 0); 64 HeadCell c4 = new HeadCell(UUID.randomUUID().toString(), "c4", null, 0); 65 HeadCell c5 = new HeadCell(UUID.randomUUID().toString(), "c5", null, 0); 66 HeadCell c1_1 = new HeadCell(UUID.randomUUID().toString(), "c1_1", c1, 1); 67 HeadCell c1_2 = new HeadCell(UUID.randomUUID().toString(), "c1_2", c1, 1); 68 HeadCell c2_1 = new HeadCell(UUID.randomUUID().toString(), "c2_1", c2, 1); 69 HeadCell c2_2 = new HeadCell(UUID.randomUUID().toString(), "c2_2", c2, 1); 70 HeadCell c3_1 = new HeadCell(UUID.randomUUID().toString(), "c3_1", c3, 1); 71 HeadCell c3_2 = new HeadCell(UUID.randomUUID().toString(), "c3_2", c3, 1); 72 HeadCell c4_1 = new HeadCell(UUID.randomUUID().toString(), "c4_1", c4, 1); 73 HeadCell c4_2 = new HeadCell(UUID.randomUUID().toString(), "c4_2", c4, 1); 74 HeadCell c5_1 = new HeadCell(UUID.randomUUID().toString(), "c5_1", c5, 1); 75 HeadCell c5_2 = new HeadCell(UUID.randomUUID().toString(), "c5_2", c5, 1); 76 c1.getChildren().add(c1_1); 77 c1.getChildren().add(c1_2); 78 c2.getChildren().add(c2_1); 79 c2.getChildren().add(c2_2); 80 c3.getChildren().add(c3_1); 81 c3.getChildren().add(c3_2); 82 c4.getChildren().add(c4_1); 83 c4.getChildren().add(c4_2); 84 c5.getChildren().add(c5_1); 85 c5.getChildren().add(c5_2); 86 87 colHeadCells.add(c1); 88 colHeadCells.add(c2); 89 colHeadCells.add(c3); 90 colHeadCells.add(c4); 91 colHeadCells.add(c5); 92 } 93 94 /** 95 * 生产一个table 96 */ 97 @Test 98 public void getTable() { 99 prepare();// 准备数据 100 StringBuilder statHtml = new StringBuilder(); 101 // 创建thead标题行 102 statHtml.append("<table border = '1' id='expTable' name='statTable'>"); 103 statHtml.append("<thead>"); 104 List<Th> ths = new ArrayList<Th>(); 105 // 这里获得的是纵向表头的最大深度,这是因为我们在生成表格的时候 106 // 1.如果是二维表,那table的表头并不是我们的单元格对象,我们可以通过这个集合生成表头的rowspan 107 // 2.我们获得每个th的rowspan,从级的方面考虑,除了最低一级的th,上层的th他的rowspan只能是1, 108 // 那么最后一级是怎么判断的呢,我们是用最后一级的深度(deep),拿最大的一级减去他的深度加1不就是他所占的rowspan吗 109 int maxDeep = getMaxDeep(colHeadCells); 110 getColThs(colHeadCells, ths, maxDeep); 111 // 这个ths是用来做什么的呢 112 // 在这里我们为th集合分了组,他的行号(rowNum)就是我们的tr,一行可以生产一个tr 113 statHtml.append(productColTh(ths, rowHeadCells, colHeadCells)); 114 statHtml.append("</thead>"); 115 statHtml.append("<tbody>"); 116 String row = buildContext(rowHeadCells, colHeadCells); 117 row = row.replace("</th><tr>", "</th>"); 118 statHtml.append(row); 119 statHtml.append("</tbody>"); 120 statHtml.append("</table>"); 121 System.out.println(statHtml.toString()); 122 } 123 124 /** 125 * 这里我们就要生产tbody的部分,先说思路 126 * 1.纵向也是有表头的,纵向表头也是有主子关系,但是他和横向表头不同,一个对象的一层叶子节点是在一个tr中,这生成tr就显得容易很多 127 * 2.整个生成过程和纵向表头是相反的,但是生成方式不同 128 * 129 * @param rowHCs 130 * @param colHCs 131 * @return 132 */ 133 public String buildContext(List<HeadCell> rowHCs, List<HeadCell> colHCs) { 134 List<HeadCell> colHeadCells = getColTh(colHCs); 135 StringBuffer sb = new StringBuffer(); 136 int maxDeep = getMaxDeep(rowHCs); 137 for (HeadCell childCell : rowHCs) { 138 sb.append(bulidCol(childCell, colHeadCells, maxDeep)); 139 sb.append("</tr>"); 140 } 141 return sb.toString(); 142 } 143 144 /** 145 * 生成横向表头 146 * @param headCell 147 * @param colHCs 148 * @param maxDeep 149 * @return 150 */ 151 public String bulidCol(HeadCell headCell, List<HeadCell> colHCs, int maxDeep) { 152 StringBuffer sb = new StringBuffer(); 153 sb.append("<tr>"); 154 if (headCell.getChildren() != null && headCell.getChildren().size() > 0) { 155 Integer rowNum = headCell.getRowNum() == 0 ? 1 : headCell.getRowNum(); 156 sb.append("<th id='" + headCell.getId() + "' rowspan='" + rowNum + "' deep='" + (headCell.getDeep() + 1) + "'>" + headCell.getName() + "</th>"); 157 for (HeadCell childCell : headCell.getChildren()) { 158 sb.append(bulidCol(childCell, colHCs, maxDeep)); 159 } 160 } else { 161 int rowNum = headCell.getRowNum() == 0 ? 1 : headCell.getRowNum(); 162 int colNum = maxDeep - headCell.getDeep() + 1; 163 int deep = headCell.getDeep() + 1; 164 sb.append("<th name='boottom' id='" + headCell.getId() + "' rowspan='" + rowNum + "' colspan='" + colNum + "' deep='" + deep + "'>" + headCell.getName() + "</th>"); 165 for (HeadCell colCell : colHCs) { 166 sb.append("<td>0.0</td>"); 167 } 168 } 169 return sb.toString(); 170 } 171 172 /** 173 * 获得最底层的headCell的集合 174 * @param colHCs 175 * @return 176 */ 177 public List<HeadCell> getColTh(List<HeadCell> colHCs) { 178 List<HeadCell> bottomRowHead = new ArrayList<HeadCell>(); 179 for (HeadCell rowCell : colHCs) { 180 bottomRowHead.addAll(builds(rowCell)); 181 } 182 return bottomRowHead; 183 } 184 185 public static List<HeadCell> builds(HeadCell headCell) { 186 List<HeadCell> bottom = new ArrayList<HeadCell>(); 187 if (headCell.getChildren() != null && headCell.getChildren().size() > 0) { 188 for (HeadCell cheadCell : headCell.getChildren()) { 189 bottom.addAll(builds(cheadCell)); 190 } 191 } else { 192 bottom.add(headCell); 193 } 194 return bottom; 195 } 196 197 public String productColTh(List<Th> ths, List<HeadCell> rowHCs, List<HeadCell> colHCs) { 198 StringBuffer sb = new StringBuffer(); 199 boolean headtitle = true; 200 int rowspan = getMaxDeep(colHCs) + 1; 201 int colspan = getMaxDeep(rowHCs) + 1; 202 try { 203 Map<Integer, List<Th>> maps = ListUtils.groupByProperty(ths, "rowNum");// 按照对象中某个字段分组 204 for (Entry<Integer, List<Th>> entry : maps.entrySet()) { 205 sb.append("<tr>"); 206 if (headtitle) { 207 sb.append("<th rowspan='" + rowspan + "' colspan='" + colspan + "'></th>"); 208 headtitle = false; 209 } 210 for (Th th : entry.getValue()) {// 生产纵向表头 211 sb.append(th.getThStr()); 212 } 213 sb.append("</tr>"); 214 } 215 } catch (Exception e) { 216 217 } 218 return sb.toString(); 219 } 220 221 public void getColThs(List<HeadCell> colHCs, List<Th> ths, int maxDeep) { 222 for (HeadCell hc : colHCs) { 223 if (hc.getChildren() != null && hc.getChildren().size() > 0) { 224 Th th = new Th(); 225 th.setRowNum(hc.getDeep()); 226 th.setThId(hc.getId()); 227 th.setColspan(hc.getRowNum()); 228 th.setRowspan(1); 229 if (hc.getParent() != null) { 230 th.setpId(hc.getParent().getId()); 231 } 232 th.setValue(hc.getName()); 233 ths.add(th); 234 getColThs(hc.getChildren(), ths, maxDeep); 235 } else { 236 Th th = new Th(); 237 th.setRowNum(hc.getDeep()); 238 th.setThId(hc.getId()); 239 th.setColspan(hc.getRowNum()); 240 th.setRowspan(maxDeep - hc.getDeep() + 1); 241 if (hc.getParent() != null) { 242 th.setpId(hc.getParent().getId()); 243 } 244 th.setValue(hc.getName()); 245 ths.add(th); 246 } 247 } 248 } 249 250 /** 251 * 求表格对象集合的最大深度 252 * 253 * @param headCells 254 * 单元格集合对象 255 * @return 最大深度 256 */ 257 public static Integer getMaxDeep(List<HeadCell> headCells) { 258 if (headCells != null && headCells.size() > 0) { 259 List<Integer> deep = new ArrayList<Integer>(); 260 for (HeadCell headCell : headCells) { 261 deep.add(maxDeep(headCell)); 262 } 263 return Collections.max(deep); 264 } else { 265 return 1; 266 } 267 } 268 269 private static int maxDeep(HeadCell headCell) { 270 int i = 0; 271 if (headCell.getChildren() != null && headCell.getChildren().size() > 0) { 272 for (HeadCell childCell : headCell.getChildren()) { 273 int j = maxDeep(childCell); 274 i = j > i ? j : i; 275 } 276 } else { 277 i = headCell.getDeep(); 278 } 279 return i; 280 } 281 }
这是生成纵向表头的类
1 package com.sun.stat.component; 2 3 import org.apache.commons.lang3.StringUtils; 4 5 /** 6 * 用于生成横向表头的html 7 * 这里这样设计的原因是我们的表格是按照横向生成也就是一个tr一个tr的生成,然而 8 * 横向表头只有上下级有主子关系,所以我这里用rowNum来区分它们,最终达到我生成tr的效果 9 */ 10 public class Th { 11 private int colspan = 1;// 合并列 12 private int rowspan = 1;// 合并行 13 private String thStr;// html代码 14 private String value;// th标签文本值 15 private int rowNum;// 行号 16 private String thId;//这里的id和pid主要用于一个js的功能,即当一行或者一列的td全为空时隐藏掉当前行 17 private String pId; 18 19 public int getRowNum() { 20 return rowNum; 21 } 22 23 public void setRowNum(int rowNum) { 24 this.rowNum = rowNum; 25 } 26 27 public int getColspan() { 28 return colspan; 29 } 30 31 public void setColspan(int colspan) { 32 this.colspan = colspan; 33 } 34 35 public int getRowspan() { 36 return rowspan; 37 } 38 39 public void setRowspan(int rowspan) { 40 this.rowspan = rowspan; 41 } 42 43 public String getThStr() { 44 StringBuilder thHtml = new StringBuilder(); 45 if (colspan == 0) { 46 colspan = 1; 47 } 48 if (rowspan == 0) { 49 rowspan = 1; 50 } 51 if (StringUtils.isBlank(pId)) { 52 pId = ""; 53 } 54 thHtml.append("<th id='").append(thId).append("'").append(" pId='").append(pId).append("' deep='").append(rowNum + 1).append("' colspan='").append(colspan).append("' rowspan='").append(rowspan); 55 thHtml.append("'>").append(value).append("</th>"); 56 return thHtml.toString(); 57 } 58 59 public void setThStr(String thStr) { 60 this.thStr = thStr; 61 } 62 63 public String getValue() { 64 return value; 65 } 66 67 public void setValue(String value) { 68 this.value = value; 69 } 70 71 public String getThId() { 72 return thId; 73 } 74 75 public void setThId(String thId) { 76 this.thId = thId; 77 } 78 79 public String getpId() { 80 return pId; 81 } 82 83 public void setpId(String pId) { 84 this.pId = pId; 85 } 86 }
生成的html代码就是我们需要的统计表table了,我们的表格生成也就完成了。
当然这里涉及了合计,多项拆分,合并拆分的问题,这些都可以通过业务逻辑判断,并不影响表格的生成.
这里涉及了我博客中的一个工具类方法,还有我为什么要生成id和pid呢,也是我后面需要的一个功能,即隐藏0值的功能,可以在我的博客中找到,这里不再赘述。