记一次在线展示excel时出现合并单元格的问题

  最近项目中遇到一个需要解析Excel并在线展示的功能,excel的内容不是固定的,是需要事先通过专用的工具生成xml模板导入到系统中,然后系统解析上报的excel文件时就可以根据模板来解析内容。

  其中生成的xml模板保存了针对每一个单元格的一些属性,所以excel解析不是难事,关键是页面展示excel内容的时候有个合并单元格的需求,这个着实让我苦恼了几天,上下班的路上都在思考这个问题,后来终于想到一种方案,这里记录一下。

  因为excel内容不是固定的,所以解析与展示数据时,我都是以单元格为单位的,所以这里我的实体类对象是:

public class TemplateItem {

    private Long id;

    /** 列位置 **/
    private String col;

    /** 列合并数 **/
    private String colSpan;

    /** 行位置 **/
    private String row;

    /** 行合并数 **/
    private String rowSpan;

    /** 数据类型(0标签 1指标值 2计算项) **/
    private String contentEnum;

    /** 指标信息 **/
    private String content;
}

  返回给前端展示的时候,要么返回每一个单元格,这样直接前端通过<table></table>标签或者Bootstrap Table插件画出即可;要么只返回有数据的单元格,需要前端计算出单元格具体的展示位置。

  既然是后端开发(虽然前端也是自己写),那我就把计算的逻辑放后端了,而且对比了一下,感觉前端计算好像也不是太方便。然后问题就来了,存在合并单元格的情况时我怎么计算每个单元格的具体位置。首先明确的是,前端直接采用<table></table>标签来实现,虽然两种都实现了,但是Bootstrap Table在合并单元格的时候似乎不是很好用,当初采用只是为了样式美观😓,<table></table>标签则可直接通过<td>rowspan、colspan属性来控制合并的行数和列数。

------------------------------------------------ 重点来了 ------------------------------------------------
  使用<table></table>来画excel的内容,也就是我返回是数据格式得是一个二维数组,因为<table></table>标签是使用<tr>、<td>来画表格的,这样我数组里每一项放那个单元格对象即可。

  在组装二维数组的时候,一开始我总是想着在没有数据的位置拼上空的单元格对象,但是感觉计算好复杂,因为合并多行多列的情况都有,这样计算的时候要考虑很多。后来一想,干脆我反过来做,因为已经知道一共有多少行多少列了,我先按照一张空白excel的样子,先画出每一个单元格(先组装一个空的二维数组),然后根据已有的数据项信息,删除掉被合并的单元格(删除被合并的数组项),这样不就能很容易得出来想要的二维数组了吗。

上代码:

两个实体类TemplateTemplateItem,其中Template存储了这一套模板的属性信息,TemplateItem存储了模板里每一个单元格的属性信息。
Template对象:

package com.example.demo.excel;

import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

/**
 * 模板
 *
 * @author zzs
 * @date 2021/12/7 17:16
 */
@Getter
@Setter
public class Template implements Serializable {

    private Long id;

    /** 模板名称 **/
    private String templateName;

    /** 行数 **/
    private String rowCount;

    /** 列数 **/
    private String colCount;

    /** 表头行数 **/
    private Integer headCount;

    /** 备注 **/
    private String remark;

    private Boolean isDel;

    private Long createBy;

    private Date createTime;

    private Long updateBy;

    private Date updateTime;

    /** 模板项列表 **/
    private List<TemplateItem> items;

    private static final long serialVersionUID = 1L;
}

TemplateItem对象:

package com.example.demo.excel;

import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

/**
 * 模板项
 *
 * @author zzs
 * @date 2021/12/7 17:11
 */
@Getter
@Setter
public class TemplateItem implements Serializable {

    private Long id;

    /** 模板id **/
    private Long templateId;

    /** 列位置 **/
    private String col;

    /** 列合并数 **/
    private String colSpan;

    /** 行位置 **/
    private String row;

    /** 行合并数 **/
    private String rowSpan;

    /** 数据类型(0标签 1指标值 2计算项) **/
    private String contentEnum;

    /** 指标信息 **/
    private String content;

    /** 公式 **/
    private String expstr;

    /** 是否特指单位 **/
    private String isUnitConver;

    /** 特指单位 **/
    private String eleUnit;

    /** 自定义详细样式 **/
    private String cssClass;

    /** 自定义格式化 **/
    private String formatString;

    /** 是否只读 **/
    private String isReadonly;

    private static final long serialVersionUID = 1L;
}

业务方法:
其中二维数组我是用guavaTable来代替,这个使用起来比较方便。

package com.example.demo.excel;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import org.springframework.stereotype.Service;

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author zzs
 * @date 2021/12/7 17:12
 */
@Service
public class TemplateService {

    /**
     * 入口
     */
    public List<List<TemplateItem>> getFormData() {
        // 这里就不展示数据的获取了
        Template template = new Template();
        List<TemplateItem> items = template.getItems();

        HashBasedTable<Integer, Integer, TemplateItem> dataTable = HashBasedTable.create();
        items.stream().forEach(item -> {
            Integer row = Integer.valueOf(item.getRow());
            Integer col = Integer.valueOf(item.getCol());
            dataTable.put(row, col, item);
        });
        Integer rowCount = Integer.valueOf(template.getRowCount());
        Integer colCount = Integer.valueOf(template.getColCount());
        List<List<TemplateItem>> dataList = fillTableCell(rowCount, colCount, dataTable);

        return dataList;
    }

    /** 填充表格 **/
    private List<List<TemplateItem>> fillTableCell(int rowCount, int colCount,
                                                   Table<Integer, Integer, ? extends TemplateItem> dataTable) {
        // 初始化一个空的table
        HashBasedTable<Integer, Integer, TemplateItem> fullTable = HashBasedTable.create();
        for (int i = 1; i <= rowCount; i++) {
            for (int j = 1; j <= colCount; j++) {
                fullTable.put(i, j, new TemplateItem());
            }
        }

        // 排序
        List<List<? extends TemplateItem>> dataList = dataTable.rowMap().entrySet().stream()
                .sorted(Comparator.comparing(Map.Entry::getKey))
                .map(entry -> entry.getValue().entrySet().stream()
                        .sorted(Comparator.comparing(Map.Entry::getKey))
                        .map(Map.Entry::getValue)
                        .collect(Collectors.toList()))
                .collect(Collectors.toList());
        // 将实际的值存储到table中的cell
        for (List<? extends TemplateItem> rowData : dataList) {
            for (TemplateItem templateItem : rowData) {
                Integer row = Integer.valueOf(templateItem.getRow());
                Integer col = Integer.valueOf(templateItem.getCol());
                Integer rowSpan = Integer.valueOf(templateItem.getRowSpan());
                Integer colSpan = Integer.valueOf(templateItem.getColSpan());
                fullTable.put(row, col, templateItem);

                // 判断要合并的cell
                if (rowSpan == 1 && colSpan == 1) {
                    continue;
                }
                if (rowSpan > 1 && colSpan == 1) {
                    for (int i = 1; i < rowSpan; i++) {
                        fullTable.remove(row + i, col);
                    }
                    continue;
                }
                if (rowSpan == 1 && colSpan > 1) {
                    for (int i = 1; i < colSpan; i++) {
                        fullTable.remove(row, col + i);
                    }
                    continue;
                }
                if (rowSpan > 1 && colSpan > 1) {
                    for (int i = 0; i < rowSpan; i++) {
                        for (int j = 0; j < colSpan; j++) {
                            if (i == 0 && j == 0) {
                                continue;
                            }
                            fullTable.remove(row + i, col + j);
                        }
                    }
                    continue;
                }
            }
        }

        // 转换为输出的格式
        return fullTable.rowMap().entrySet().stream()
                .map(rowEntry -> rowEntry.getValue().entrySet().stream()
                        .map(colEntry -> colEntry.getValue())
                        .collect(Collectors.toList()))
                .collect(Collectors.toList());
    }
}

这样返回的集合在前端直接遍历画出每一行<tr>每一个<td>即可。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值