Export using EasyExcel template

1. Official method of providing

easyexcel official document fill Excel | Easy Excel

The official demo is filled with local template files and downloaded to the local

    /**
     * 复杂的填充
     *
     * @since 2.1.1
     */
    @Test
    public void complexFill() {
        // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替
        // {} 代表普通变量 {.} 代表是list的变量
        String templateFileName =
            TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "complex.xlsx";

        String fileName = TestFileUtil.getPath() + "complexFill" + System.currentTimeMillis() + ".xlsx";
        // 方案1
        try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet().build();
            // 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。
            // forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用
            // 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存
            // 如果数据量大 list不是最后一行 参照下一个
            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
            excelWriter.fill(data(), fillConfig, writeSheet);
            excelWriter.fill(data(), fillConfig, writeSheet);
            Map<String, Object> map = MapUtils.newHashMap();
            map.put("date", "2019年10月9日13:28:28");
            map.put("total", 1000);
            excelWriter.fill(map, writeSheet);
        }
    }

2. Method according to business scenarios

I am using a web project and changed the output to the output stream OutputStream

	/**
	 * 导出质检任务详情
	 *
	 * @param id 任务主键,用于获取数据
	 */
	@Override
	public void exportTask(Long id, HttpServletResponse response) {
		ExcelWriter excelWriter = null;
		try {
			// outputStream:要导出的文件的输出流
			OutputStream outputStream = response.getOutputStream();
			// 模版文件
			ClassPathResource classPathResource = new ClassPathResource("template/taskTemplate.xlsx");
			// 使用模版文件的两种方式:
			// 	1、文件路径:.withTemplate(templateFileName)
			// 	2、输入流:.withTemplate(inputStream)
			String templateFileName = classPathResource.getFile().getPath();
			InputStream inputStream = classPathResource.getInputStream();

			excelWriter = EasyExcel.write(outputStream).withTemplate(inputStream).excelType(ExcelTypeEnum.XLSX).autoCloseStream(Boolean.FALSE).build();
			WriteSheet writeSheet = EasyExcel.writerSheet().build();

			// 获取数据 dataList columnList formData
			Map<String, Object> mapDetail = qmsQcTaskDetailService.getDataListByTaskId(id);

			// 调用微服务获取字典 determine 用来翻译判定结果
			List<SysDictItem> determineItem = remoteDictService.getDictByType("determine").getData();
			// 调用微服务获取字典 product_unit 用来翻译零件单位
			List<SysDictItem> unitItem = remoteDictService.getDictByType("product_unit").getData();

			/* 1、List以外的数据 */
			QmsQcTask formData = (QmsQcTask) mapDetail.get("formData");
			determineItem.stream().filter(item -> StrUtil.equals(formData.getResult(), item.getItemValue())).findFirst().ifPresent(sysDictItem -> formData.setResult(sysDictItem.getLabel()));
			unitItem.stream().filter(item -> StrUtil.equals(formData.getMaterialUnit(), item.getItemValue())).findFirst().ifPresent(sysDictItem -> formData.setMaterialUnit(sysDictItem.getLabel()));
			excelWriter.fill(formData, writeSheet);
			/* 2、List cols数据 */
			List<Map<String, Object>> cols = new ArrayList<>();
			// 查找抽检数是多少,即有几列
			int num = formData.getSampleNum();
			for (int i = 1; i <= num; i++) {
				Map<String, Object> map = new HashMap<>();
				// 列名
				map.put("label", "" + i);
				cols.add(map);
			}
			// 横向填充
			FillConfig fillConfigCols = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
			excelWriter.fill(new FillWrapper("cols", cols), fillConfigCols, writeSheet);

			/* 3、List details数据 */
			// 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。
			// forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用
			// 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存
			FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
			List<Map<String, Object>> dataList = (List<Map<String, Object>>) mapDetail.get("dataList");
			for (int i = 0; i < dataList.size(); i++) {
				Map<String, Object> data = dataList.get(i);
				// 翻译判定合不合格(因为业务和字典不在一个数据库,所以放在代码里处理)
				String determine = (String) data.get("determine");
				determineItem.stream().filter(item -> StrUtil.equals(determine, item.getItemValue())).findFirst().ifPresent(sysDictItem -> data.put("determine", sysDictItem.getLabel()));
				// 如果没有质检标准,每一个质检值都要转为合不合格
				if (StrUtil.isBlank((String) data.get("inspection_standard"))){
					int samplingNum = Optional.ofNullable((Integer) data.get("sampling_num")).orElse(0);
					for (int j = 1; j <= samplingNum; j++) {
						String val = (String) data.get(""+j);
						int finalJ = j;
						determineItem.stream().filter(item -> StrUtil.equals(val, item.getItemValue())).findFirst().ifPresent(sysDictItem -> data.put(""+ finalJ, sysDictItem.getLabel()));
					}
				}
				// 给数据加上序号
				data.put("index", i + 1);
			}
			excelWriter.fill(new FillWrapper("details", dataList), fillConfig, writeSheet);

			// 设置输出流格式以及文件名:
			response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
			response.setCharacterEncoding("utf-8");
			String fileName = URLEncoder.encode("质检任务", "UTF-8").replaceAll("\\+", "%20");
			response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			// 千万别忘记close关闭流
			if (excelWriter != null) {
				excelWriter.close();
			}
		}

	}

 stencil:

Effect:

 

3. Description

1. Get the output stream

// outputStream:要导出的文件的输出流
OutputStream outputStream = response.getOutputStream();

2. Obtain the template file

// 模版文件
ClassPathResource classPathResource = new ClassPathResource("template/taskTemplate.xlsx");
// 方式一:路径
String templateFileName = classPathResource.getFile().getPath();
// 方式二:输入流
InputStream inputStream = classPathResource.getInputStream();

3. Create ExcelWriter and WriteSheet

excelWriter = EasyExcel.write(outputStream).withTemplate(inputStream).excelType(ExcelTypeEnum.XLSX).autoCloseStream(Boolean.FALSE).build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();

There are two ways to use template files, use one of them:
    1. File path: .withTemplate(templateFileName)
    2. Input stream: .withTemplate(inputStream)

4. Get data

Map<String, Object> mapDetail = qmsQcTaskDetailService.getDataListByTaskId(id);

I called the method directly here, and the map contains

dataList main list data formData data outside the list

5. Fill data

1. Get dictionary data

// 调用微服务获取字典 determine 用来翻译判定结果
List<SysDictItem> determineItem = remoteDictService.getDictByType("determine").getData();
// 调用微服务获取字典 product_unit 用来翻译零件单位
List<SysDictItem> unitItem = remoteDictService.getDictByType("product_unit").getData();

My business data and dictionary are not in the same database, sql cannot be translated, so it needs to be translated in the code

2. Fill the data outside the list

/* 1、List以外的数据 */
			QmsQcTask formData = (QmsQcTask) mapDetail.get("formData");
			determineItem.stream().filter(item -> StrUtil.equals(formData.getResult(), item.getItemValue())).findFirst().ifPresent(sysDictItem -> formData.setResult(sysDictItem.getLabel()));
			unitItem.stream().filter(item -> StrUtil.equals(formData.getMaterialUnit(), item.getItemValue())).findFirst().ifPresent(sysDictItem -> formData.setMaterialUnit(sysDictItem.getLabel()));
			excelWriter.fill(formData, writeSheet);

As long as the object attribute name and the tag written in the template are known

3. Padding column names with few columns

stencil:

 Effect:

 Because the second half of my column names involve row-to-column conversion, and the number of columns is not fixed, the upper column names need to be filled horizontally. Note that the name of the list "cols" should correspond.

Use .direction(WriteDirectionEnum.HORIZONTAL) to set horizontal padding.

/* 2、List cols数据 */
List<Map<String, Object>> cols = new ArrayList<>();
// 查找抽检数是多少,即有几列
int num = formData.getSampleNum();
for (int i = 1; i <= num; i++) {
	Map<String, Object> map = new HashMap<>();
	// 列名
	map.put("label", "" + i);
	cols.add(map);
}
// 横向填充
FillConfig fillConfigCols = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
excelWriter.fill(new FillWrapper("cols", cols), fillConfigCols, writeSheet);

4. Populate the main list data

/* 3、List details数据 */
// 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。
// forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用
// 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
List<Map<String, Object>> dataList = (List<Map<String, Object>>) mapDetail.get("dataList");

/*以下为个人业务处理*/
for (int i = 0; i < dataList.size(); i++) {
	Map<String, Object> data = dataList.get(i);
	// 翻译判定合不合格(因为业务和字典不在一个数据库,所以放在代码里处理)
	String determine = (String) data.get("determine");
	determineItem.stream().filter(item -> StrUtil.equals(determine, item.getItemValue())).findFirst().ifPresent(sysDictItem -> data.put("determine", sysDictItem.getLabel()));
	// 如果没有质检标准,每一个质检值都要转为合不合格
	if (StrUtil.isBlank((String) data.get("inspection_standard"))){
		int samplingNum = Optional.ofNullable((Integer) data.get("sampling_num")).orElse(0);
		for (int j = 1; j <= samplingNum; j++) {
			String val = (String) data.get(""+j);
			int finalJ = j;
			determineItem.stream().filter(item -> StrUtil.equals(val, item.getItemValue())).findFirst().ifPresent(sysDictItem -> data.put(""+ finalJ, sysDictItem.getLabel()));
		}
	}
	// 给数据加上序号
	data.put("index", i + 1);
}
/*以上为个人业务处理*/

excelWriter.fill(new FillWrapper("details", dataList), fillConfig, writeSheet);

The three most important sentences:


FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
List<Map<String, Object>> dataList = (List<Map<String, Object>>) mapDetail.get("dataList");


excelWriter.fill(new FillWrapper("details", dataList), fillConfig, writeSheet);
Note here that the input parameter uses forceNewRow, which means that when writing to the list, a row will be created regardless of whether there is a blank row below the list, and then the following data will move backward. The default is false, the next line will be used directly, if there is no one, it will be created.
If forceNewRow is set to true, there is a disadvantage that it will put all the data in memory, so use it with caution
Simply put, if your template has a list, and the list is not the last row, and there is still data below to be filled, you must set forceNewRow=true, but this will put all the data in memory and consume a lot of memory

5. Set the output stream format and file name

// 设置输出流格式以及文件名:
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("质检任务", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

4. Another thought

My template uses dynamic columns, not sure how many columns there are, so I wrote more than a hundred.

 

There is another idea:

If the data outside the list is filled first, then the columns of the main list are filled, and then the mark of the main list is written. After these operations are completed, the file is output to another output stream, and then the output stream is converted into an input stream. Then fill the data in the main list and output it to the response.

It's theoretically possible, but I haven't tried it.

Guess you like

Origin blog.csdn.net/weixin_45735511/article/details/127762821