From 01b417ff2c3cadacf1c2fbfceaf55ea354936bb0 Mon Sep 17 00:00:00 2001 From: linfso Date: Tue, 23 Jul 2024 05:58:08 +0800 Subject: [PATCH] md --- .idea/libraries/Maven__cglib_cglib_3_1.xml | 13 + .../Maven__com_alibaba_easyexcel_2_2_7.xml | 13 + ...en__com_github_virtuald_curvesapi_1_04.xml | 13 + ...pache_commons_commons_collections4_4_1.xml | 13 + ...g_apache_commons_commons_compress_1_21.xml | 13 + .../Maven__org_apache_poi_poi_3_17.xml | 13 + .../Maven__org_apache_poi_poi_ooxml_3_17.xml | 13 + ..._org_apache_poi_poi_ooxml_schemas_3_17.xml | 13 + ...en__org_apache_xmlbeans_xmlbeans_2_6_0.xml | 13 + .../Maven__org_ehcache_ehcache_3_9_9.xml | 13 + .../libraries/Maven__org_ow2_asm_asm_4_2.xml | 13 + emis-biz-web/emis-biz-web.iml | 22 +- .../web/emis/EmisSettleBillController.java | 24 ++ emis-biz/emis-biz.iml | 24 +- emis-biz/pom.xml | 20 ++ .../erp/emis/service/EmisBaseService.java | 5 + .../billexport/DynamicDataMapping.java | 50 ++++ .../ExcelFillCellMergePrevColUtils.java | 70 +++++ .../ExcelFillCellMergeStrategyUtils.java | 110 ++++++++ .../service/billexport/ExcelTemplateProc.java | 251 ++++++++++++++++++ .../emis/service/billexport/ExcelUtils.java | 157 +++++++++++ .../ExportExcelByTemplateUtils.java | 65 +++++ .../service/billexport/MapObjectUtil.java | 97 +++++++ .../main/resources/tpl/settle_bill_tpl.xls | Bin 0 -> 21504 bytes 24 files changed, 1017 insertions(+), 21 deletions(-) create mode 100644 .idea/libraries/Maven__cglib_cglib_3_1.xml create mode 100644 .idea/libraries/Maven__com_alibaba_easyexcel_2_2_7.xml create mode 100644 .idea/libraries/Maven__com_github_virtuald_curvesapi_1_04.xml create mode 100644 .idea/libraries/Maven__org_apache_commons_commons_collections4_4_1.xml create mode 100644 .idea/libraries/Maven__org_apache_commons_commons_compress_1_21.xml create mode 100644 .idea/libraries/Maven__org_apache_poi_poi_3_17.xml create mode 100644 .idea/libraries/Maven__org_apache_poi_poi_ooxml_3_17.xml create mode 100644 .idea/libraries/Maven__org_apache_poi_poi_ooxml_schemas_3_17.xml create mode 100644 .idea/libraries/Maven__org_apache_xmlbeans_xmlbeans_2_6_0.xml create mode 100644 .idea/libraries/Maven__org_ehcache_ehcache_3_9_9.xml create mode 100644 .idea/libraries/Maven__org_ow2_asm_asm_4_2.xml create mode 100644 emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/DynamicDataMapping.java create mode 100644 emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExcelFillCellMergePrevColUtils.java create mode 100644 emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExcelFillCellMergeStrategyUtils.java create mode 100644 emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExcelTemplateProc.java create mode 100644 emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExcelUtils.java create mode 100644 emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExportExcelByTemplateUtils.java create mode 100644 emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/MapObjectUtil.java create mode 100644 emis-biz/src/main/resources/tpl/settle_bill_tpl.xls diff --git a/.idea/libraries/Maven__cglib_cglib_3_1.xml b/.idea/libraries/Maven__cglib_cglib_3_1.xml new file mode 100644 index 000000000..bd3cddd6b --- /dev/null +++ b/.idea/libraries/Maven__cglib_cglib_3_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__com_alibaba_easyexcel_2_2_7.xml b/.idea/libraries/Maven__com_alibaba_easyexcel_2_2_7.xml new file mode 100644 index 000000000..a74963b4c --- /dev/null +++ b/.idea/libraries/Maven__com_alibaba_easyexcel_2_2_7.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__com_github_virtuald_curvesapi_1_04.xml b/.idea/libraries/Maven__com_github_virtuald_curvesapi_1_04.xml new file mode 100644 index 000000000..2db743118 --- /dev/null +++ b/.idea/libraries/Maven__com_github_virtuald_curvesapi_1_04.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_apache_commons_commons_collections4_4_1.xml b/.idea/libraries/Maven__org_apache_commons_commons_collections4_4_1.xml new file mode 100644 index 000000000..1e0b53d1f --- /dev/null +++ b/.idea/libraries/Maven__org_apache_commons_commons_collections4_4_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_apache_commons_commons_compress_1_21.xml b/.idea/libraries/Maven__org_apache_commons_commons_compress_1_21.xml new file mode 100644 index 000000000..49cd123ce --- /dev/null +++ b/.idea/libraries/Maven__org_apache_commons_commons_compress_1_21.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_apache_poi_poi_3_17.xml b/.idea/libraries/Maven__org_apache_poi_poi_3_17.xml new file mode 100644 index 000000000..1a446fbf1 --- /dev/null +++ b/.idea/libraries/Maven__org_apache_poi_poi_3_17.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_apache_poi_poi_ooxml_3_17.xml b/.idea/libraries/Maven__org_apache_poi_poi_ooxml_3_17.xml new file mode 100644 index 000000000..d50f385ea --- /dev/null +++ b/.idea/libraries/Maven__org_apache_poi_poi_ooxml_3_17.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_apache_poi_poi_ooxml_schemas_3_17.xml b/.idea/libraries/Maven__org_apache_poi_poi_ooxml_schemas_3_17.xml new file mode 100644 index 000000000..d214d8b81 --- /dev/null +++ b/.idea/libraries/Maven__org_apache_poi_poi_ooxml_schemas_3_17.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_apache_xmlbeans_xmlbeans_2_6_0.xml b/.idea/libraries/Maven__org_apache_xmlbeans_xmlbeans_2_6_0.xml new file mode 100644 index 000000000..cb0f76d6c --- /dev/null +++ b/.idea/libraries/Maven__org_apache_xmlbeans_xmlbeans_2_6_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_ehcache_ehcache_3_9_9.xml b/.idea/libraries/Maven__org_ehcache_ehcache_3_9_9.xml new file mode 100644 index 000000000..9ccdf70d9 --- /dev/null +++ b/.idea/libraries/Maven__org_ehcache_ehcache_3_9_9.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_ow2_asm_asm_4_2.xml b/.idea/libraries/Maven__org_ow2_asm_asm_4_2.xml new file mode 100644 index 000000000..b5e374897 --- /dev/null +++ b/.idea/libraries/Maven__org_ow2_asm_asm_4_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/emis-biz-web/emis-biz-web.iml b/emis-biz-web/emis-biz-web.iml index c5cd89ef1..5b3cf1c18 100644 --- a/emis-biz-web/emis-biz-web.iml +++ b/emis-biz-web/emis-biz-web.iml @@ -112,15 +112,6 @@ - - - - - - - - - @@ -160,7 +151,6 @@ - @@ -266,5 +256,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/emis-biz-web/src/main/java/com/xdadan/erp/web/emis/EmisSettleBillController.java b/emis-biz-web/src/main/java/com/xdadan/erp/web/emis/EmisSettleBillController.java index 73c8c0ba6..45a593497 100644 --- a/emis-biz-web/src/main/java/com/xdadan/erp/web/emis/EmisSettleBillController.java +++ b/emis-biz-web/src/main/java/com/xdadan/erp/web/emis/EmisSettleBillController.java @@ -11,13 +11,16 @@ package com.xdadan.erp.web.emis; +import java.io.IOException; import java.util.List; import javax.servlet.http.HttpServletResponse; import com.xdadan.erp.emis.domain.EmisSettleSubBill; +import com.xdadan.erp.emis.domain.EmisSettleSubBillFeeitem; import com.xdadan.erp.emis.domain.exception.EmisBizError; import com.xdadan.erp.emis.domain.vo.scan.ScanResult; import com.xdadan.erp.emis.service.EmisBaseService; +import com.xdadan.erp.emis.service.billexport.ExcelUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.util.CollectionUtils; @@ -189,6 +192,27 @@ public class EmisSettleBillController extends BaseController } + @Log(title = "导出账单", businessType = BusinessType.EXPORT) + @GetMapping(value = "/exportBill") + public void exportBill(HttpServletResponse response,String settleBillNo) + { +// return AjaxResult.success(); + + EmisSettleBill bill=emisBaseService.getSettleBillBySettleBillNo(settleBillNo); + List list = emisBaseService.selectSubBillListBySettleBillNo(settleBillNo); + + ExcelUtil util = new ExcelUtil(EmisSettleSubBill.class); +// util.exportExcel(response, list, "账单明细数据"); + try { + ExcelUtils.exportToTemplate(response, "tpl/settle_bill_tpl.xls", bill.getSettleBillName()+".xls", null, + null, null, + null); + } catch (IOException e) { + e.printStackTrace(); + } + + } + /** * 修改结算总账单 diff --git a/emis-biz/emis-biz.iml b/emis-biz/emis-biz.iml index a33e6214c..162fa7f70 100644 --- a/emis-biz/emis-biz.iml +++ b/emis-biz/emis-biz.iml @@ -65,15 +65,6 @@ - - - - - - - - - @@ -85,7 +76,6 @@ - @@ -115,7 +105,6 @@ - @@ -137,6 +126,7 @@ + @@ -225,5 +215,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/emis-biz/pom.xml b/emis-biz/pom.xml index 66d62d4ce..739d48f56 100644 --- a/emis-biz/pom.xml +++ b/emis-biz/pom.xml @@ -105,6 +105,26 @@ 1.1.14 + + + + com.alibaba + easyexcel + 2.2.7 + + + + + org.apache.poi + poi + 3.17 + + + org.apache.poi + poi-ooxml + 3.17 + + diff --git a/emis-biz/src/main/java/com/xdadan/erp/emis/service/EmisBaseService.java b/emis-biz/src/main/java/com/xdadan/erp/emis/service/EmisBaseService.java index 3e82011f7..81b295332 100644 --- a/emis-biz/src/main/java/com/xdadan/erp/emis/service/EmisBaseService.java +++ b/emis-biz/src/main/java/com/xdadan/erp/emis/service/EmisBaseService.java @@ -1305,6 +1305,11 @@ public class EmisBaseService { return emisSettleSubBillMapper.setBillErrorBySite(emisSettleSubBill); } + + public List selectSubBillListBySettleBillNo(String settleBillNo){ + return emisSettleSubBillMapper.selectSubBillListBySettleBillNo(settleBillNo); + } + /** * 根据账单号获取总账单 * @param settleBillNo diff --git a/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/DynamicDataMapping.java b/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/DynamicDataMapping.java new file mode 100644 index 000000000..71c5cea11 --- /dev/null +++ b/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/DynamicDataMapping.java @@ -0,0 +1,50 @@ +package com.xdadan.erp.emis.service.billexport; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Data +public class DynamicDataMapping { + + private String dataId; + private List> dataList; + + + /** + * @description: 组装只有一个list类型的动态数据 + * @param dataId + * @param dataList + */ + public static List createOneDataList(String dataId, List> dataList) { + if (dataList == null) + return null; + return Collections.singletonList(getDynamicDataMapping(dataId,dataList)); + } + + /** + * @description: 组装只有多个list类型的动态数据 + * @param transMap + */ + public static List createMorDataList(Map>> transMap) { + if (transMap == null) + return null; + List list = new ArrayList<>(); + transMap.forEach((dataId,dataList)->{ + list.add(getDynamicDataMapping(dataId,dataList)); + }); + return list; + } + + public static DynamicDataMapping getDynamicDataMapping(String dataId, List> dataList){ + DynamicDataMapping dynamicData = new DynamicDataMapping(); + dynamicData.dataId = dataId; + dynamicData.dataList = dataList; + return dynamicData; + } + +} + diff --git a/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExcelFillCellMergePrevColUtils.java b/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExcelFillCellMergePrevColUtils.java new file mode 100644 index 000000000..ec86291eb --- /dev/null +++ b/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExcelFillCellMergePrevColUtils.java @@ -0,0 +1,70 @@ +package com.xdadan.erp.emis.service.billexport; + +import com.alibaba.excel.metadata.CellData; +import com.alibaba.excel.metadata.Head; +import com.alibaba.excel.write.handler.CellWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteTableHolder; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.util.CellRangeAddress; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 列合并工具类 + * + * @author DaiHaijiao + */ +public class ExcelFillCellMergePrevColUtils implements CellWriteHandler { + private static final String KEY = "%s-%s"; + //所有的合并信息都存在了这个map里面 + Map mergeInfo = new HashMap<>(); + + public ExcelFillCellMergePrevColUtils() { + } + + @Override + public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer integer, Integer integer1, Boolean aBoolean) { + + } + + @Override + public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer integer, Boolean aBoolean) { + + } + + @Override + public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) { + + } + + @Override + public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List list, Cell cell, Head head, Integer integer, Boolean aBoolean) { + //当前行 + int curRowIndex = cell.getRowIndex(); + //当前列 + int curColIndex = cell.getColumnIndex(); + + Integer num = mergeInfo.get(String.format(KEY, curRowIndex, curColIndex)); + if (null != num) { + // 合并最后一行 ,列 + this.mergeWithPrevCol(writeSheetHolder, cell, curRowIndex, curColIndex, num); + } + } + + public void mergeWithPrevCol(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex, int num) { + Sheet sheet = writeSheetHolder.getSheet(); + CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex, curRowIndex, curColIndex, curColIndex + num); + sheet.addMergedRegion(cellRangeAddress); + } + + //num从第几列开始增加多少列,(6,2,7)代表的意思就是第6行的第2列至第2+7也就是9列开始合并 + public void add(int curRowIndex, int curColIndex, int num) { + mergeInfo.put(String.format(KEY, curRowIndex, curColIndex), num); + } + +} \ No newline at end of file diff --git a/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExcelFillCellMergeStrategyUtils.java b/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExcelFillCellMergeStrategyUtils.java new file mode 100644 index 000000000..7480ed0b6 --- /dev/null +++ b/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExcelFillCellMergeStrategyUtils.java @@ -0,0 +1,110 @@ +package com.xdadan.erp.emis.service.billexport; + + +import com.alibaba.excel.metadata.CellData; +import com.alibaba.excel.metadata.Head; +import com.alibaba.excel.write.handler.CellWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteTableHolder; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.util.CellRangeAddress; + +import java.util.List; + +/** + * 行合并工具类 + * + * @author DaiHaijiao + */ +public class ExcelFillCellMergeStrategyUtils implements CellWriteHandler { + + /** + * 合并字段的下标 + */ + private int[] mergeColumnIndex; + /** + * 合并几行 + */ + private int mergeRowIndex; + + public ExcelFillCellMergeStrategyUtils(int mergeRowIndex, int[] mergeColumnIndex) { + this.mergeRowIndex = mergeRowIndex; + this.mergeColumnIndex = mergeColumnIndex; + } + + @Override + public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, + Head head, Integer integer, Integer integer1, Boolean aBoolean) { + + } + + @Override + public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, + Head head, Integer integer, Boolean aBoolean) { + + + } + + + @Override + public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, + CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) { + + } + + @Override + public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, + List list, Cell cell, Head head, Integer integer, Boolean aBoolean) { + //当前行 + int curRowIndex = cell.getRowIndex(); + //当前列 + int curColIndex = cell.getColumnIndex(); + + if (curRowIndex > mergeRowIndex) { + for (int i = 0; i < mergeColumnIndex.length; i++) { + if (curColIndex == mergeColumnIndex[i]) { + this.mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex); + break; + } + } + } + } + + private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) { + //获取当前行的当前列的数据和上一行的当前列列数据,通过上一行数据是否相同进行合并 + //获取当前行的第一列 + Cell firstNowCell = cell.getSheet().getRow(curRowIndex).getCell(curColIndex); + Object curData = firstNowCell.getCellTypeEnum() == CellType.STRING ? firstNowCell.getStringCellValue() : firstNowCell.getNumericCellValue(); + Row preRow = cell.getSheet().getRow(curRowIndex - 1); + if (preRow == null) { + // 当获取不到上一行数据时,使用缓存sheet中数据 + preRow = writeSheetHolder.getCachedSheet().getRow(curRowIndex - 1); + } + Cell preCell = preRow.getCell(curColIndex); + Object preData = preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() : preCell.getNumericCellValue(); + // 比较当前行的第一列的单元格与上一行是否相同,相同合并当前单元格与上一行 + if (curData.equals(preData)) { + Sheet sheet = writeSheetHolder.getSheet(); + List mergeRegions = sheet.getMergedRegions(); + boolean isMerged = false; + for (int i = 0; i < mergeRegions.size() && !isMerged; i++) { + CellRangeAddress cellRangeAddr = mergeRegions.get(i); + // 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元 + if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) { + sheet.removeMergedRegion(i); + cellRangeAddr.setLastRow(curRowIndex); + sheet.addMergedRegion(cellRangeAddr); + isMerged = true; + } + } + // 若上一个单元格未被合并,则新增合并单元 + if (!isMerged) { + CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex); + sheet.addMergedRegion(cellRangeAddress); + } + } + } +} \ No newline at end of file diff --git a/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExcelTemplateProc.java b/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExcelTemplateProc.java new file mode 100644 index 000000000..f6f8afad3 --- /dev/null +++ b/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExcelTemplateProc.java @@ -0,0 +1,251 @@ +package com.xdadan.erp.emis.service.billexport; + +import com.xdadan.erp.emis.service.billexport.DynamicDataMapping; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.ss.usermodel.CellCopyPolicy; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFCell; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.util.ResourceUtils; + + +import java.io.*; +import java.util.List; +import java.util.Map; + +/** + * @Description 根据模版导出Excel程序 + */ +public class ExcelTemplateProc { + + /** + * @param templateFileName + * @param exportFilePathAndName + * @param staticDataMap + * @param dynamicDataMappingList + * @return void + * @description: 根据模版导出Excel入口 + * @author susu + * @date 2024/2/20 + */ + public static void doExportExcelByTemplateProc(String templateFileName, String exportFilePathAndName, + Map staticDataMap, + List dynamicDataMappingList) throws IOException { + /** + * 1. 从resources下加载模板并替换 + * 使用 ResourceUtils 加载文件 + */ + File file = ResourceUtils.getFile("classpath:"+templateFileName); + InputStream inputStream = new FileInputStream(file); + + Workbook workbook = dealFirstSheetByTemplate(inputStream, staticDataMap, dynamicDataMappingList); + // 2. 保存到本地 + saveExportFile(workbook, exportFilePathAndName); + } + + /** + * @param workbook + * @param excelFilePath + * @return void + * @description: 保存导出的Excel文件到服务器 + * @author susu + * @date 2024/2/20 + */ + public static void saveExportFile(Workbook workbook, String excelFilePath) throws IOException { + FileOutputStream outputStream = new FileOutputStream(excelFilePath); + executeWorkBookWrite(workbook, outputStream); + } + + /** + * @param workbook + * @param outputStream + * @return void + * @description: 数据输出 + * @author susu + * @date 2024/2/20 + */ + public static void executeWorkBookWrite(Workbook workbook, OutputStream outputStream) throws IOException { + workbook.write(outputStream); + outputStream.flush(); + outputStream.close(); + workbook.close(); + } + + /** + * @param inputStream + * @param staticDataMap + * @param dynamicDataMappingList + * @return org.apache.poi.ss.usermodel.Workbook + * @description: 处理只有一个sheet页的模版 + * @author susu + * @date 2024/2/20 + */ + public static Workbook dealFirstSheetByTemplate(InputStream inputStream, + Map staticDataMap, + List dynamicDataMappingList) throws IOException { + XSSFWorkbook workbook = new XSSFWorkbook(inputStream); + XSSFSheet sheet = workbook.getSheetAt(0); + // 按模板处理sheet页 + dealSheetDataByTemplate(sheet, staticDataMap, dynamicDataMappingList); + return workbook; + } + + /** + * @param sheet + * @param staticDataMap + * @param dynamicDataMappingList + * @return void + * @description: 按模板处理sheet页里的数据 + * @author susu + * @date 2024/2/19 + */ + private static void dealSheetDataByTemplate(XSSFSheet sheet, Map staticDataMap, List dynamicDataMappingList) { + // 循环sheet里每一行 + for (int i = sheet.getFirstRowNum(); i <= sheet.getLastRowNum(); i++) { + XSSFRow row = sheet.getRow(i); + DynamicDataMapping dynamicDataMapping = getDynamicRowDataByMatch(row, dynamicDataMappingList); + if (dynamicDataMapping != null) { + i = getTemplateLastRowIndexAfterDealTemplate(sheet, i, dynamicDataMapping); + } else { + dealTemplateDataRow(row, null, staticDataMap); + } + } + } + + /** + * @param row + * @param dataMap + * @param dataPrefix + * @return void + * @description: 循环处理模版中每行的数据 + * @author susu + * @date 2024/2/20 + */ + private static void dealTemplateDataRow(XSSFRow row, String dataPrefix, Map dataMap) { + if (dataMap == null) { + return; + } + for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) { + XSSFCell cell = row.getCell(i); + fillInTemplateCellDataValue(cell, dataPrefix, dataMap); + } + } + + /** + * @param cell + * @param dataPrefix + * @param dataMap + * @return void + * @description: 填充模版里单元格的值 + * @author susu + * @date 2024/2/20 + */ + private static void fillInTemplateCellDataValue(XSSFCell cell, String dataPrefix, Map dataMap) { + if (cell == null) { + return; + } + String cellValue = cell.getStringCellValue();//获取模版里设置的数据 + if (StringUtils.isEmpty(cellValue)) { + return; + } + boolean flag = false; + dataPrefix = StringUtils.isEmpty(dataPrefix) ? "" : (dataPrefix + "."); + for (Map.Entry entry : dataMap.entrySet()) { + // 循环所有,因为可能一行有多个占位符 + String cellTemplateStr = "{{" + dataPrefix + entry.getKey() + "}}"; + if (cellValue.contains(cellTemplateStr)) { + // 替换模版中单元格的数据 + cellValue = cellValue.replace(cellTemplateStr, entry.getValue() == null ? "" : entry.getValue().toString()); + flag = true; + } + } + if (flag) { + cell.setCellValue(cellValue); + } + } + + /** + * @param row + * @param dynamicDataMappingList + * @return com.liu.susu.excel.template.poi.common.DynamicDataMapping + * @description: 通过模版sheet中的行数据 与 动态数据匹配,获取此行需要填充的动态数据 + * @author susu + * @date 2024/2/21 + */ + private static DynamicDataMapping getDynamicRowDataByMatch(XSSFRow row, List dynamicDataMappingList) { + if (dynamicDataMappingList == null || dynamicDataMappingList.size() < 1) { + return null; + } + for (int j = row.getFirstCellNum(); j < row.getLastCellNum(); j++) { + XSSFCell cell = row.getCell(j); + String value = cell.getStringCellValue(); + if (value != null) { + for (DynamicDataMapping dynamicData : dynamicDataMappingList) { + if (value.startsWith("{{" + dynamicData.getDataId() + ".")) { + return dynamicData; + } + } + } + } + return null; + } + + /** + * @param sheet + * @param rowIndex + * @param dynamicDataMapping + * @return int + * @description: 根据动态数据的条数动态复制模版行,每处理一个类型的list返回最后的行数,进而处理下一个类型的list + * @author susu + * @date 2024/2/20 + */ + private static int getTemplateLastRowIndexAfterDealTemplate(XSSFSheet sheet, int rowIndex, DynamicDataMapping dynamicDataMapping) { + if (dynamicDataMapping == null) { + return rowIndex; + } + int dataRows = dynamicDataMapping.getDataList().size(); + // 需要拷贝的行数(因为模板行本身占1行,所以-1) + int copyRows = dataRows - 1; + if (copyRows > 0) { + /** + * shiftRows: 从动态数据模版行(rowIndex)到最后一行,这些全部行都向下移copyRows行 + * 相当于模版行上面插入n行空行(n=copyRows) + */ + sheet.shiftRows(rowIndex, sheet.getLastRowNum(), copyRows, true, false); + // 拷贝策略 + CellCopyPolicy cellCopyPolicy = makeCellCopyPolicy(); + // 因为从模版行开始向下平移了copyRows行,所以这里 模板行=rowIndex + copyRows, + int templateDataRow = rowIndex + copyRows; + // 因为模版行上新增了空行,所以要把模板所在行的模版 拷贝到上面新增的空行 + for (int i = 0; i < copyRows; i++) { + //templateDataRow-模版行数据 rowIndex + i循环的当前空行 + sheet.copyRows(templateDataRow, templateDataRow, rowIndex + i, cellCopyPolicy); + } + } + // 循环模版行:动态替换模版行(将模版行里的模版替换成动态数据) + for (int j = rowIndex; j < rowIndex + dataRows; j++) { + Map dataMap = dynamicDataMapping.getDataList().get(j - rowIndex); + dealTemplateDataRow(sheet.getRow(j), dynamicDataMapping.getDataId(), dataMap); + } + return rowIndex + copyRows; + } + + /** + * @param + * @return org.apache.poi.ss.usermodel.CellCopyPolicy + * @description: 拷贝策略 + * @author susu + * @date 2024/2/20 + */ + public static CellCopyPolicy makeCellCopyPolicy() { + CellCopyPolicy cellCopyPolicy = new CellCopyPolicy(); + cellCopyPolicy.setCopyCellValue(true); + cellCopyPolicy.setCopyCellStyle(true); + return cellCopyPolicy; + } + + +} + diff --git a/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExcelUtils.java b/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExcelUtils.java new file mode 100644 index 000000000..bba13454b --- /dev/null +++ b/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExcelUtils.java @@ -0,0 +1,157 @@ +package com.xdadan.erp.emis.service.billexport; + +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.alibaba.excel.write.metadata.fill.FillConfig; +import com.alibaba.excel.write.metadata.fill.FillWrapper; +import org.apache.commons.lang3.StringUtils; +import org.springframework.core.io.ClassPathResource; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.net.URLEncoder; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.*; + +/** + * excel导出工具类 + * + */ +public class ExcelUtils { + + /** + * 导出数据到指定Excel + * + * @param response HttpServletResponse对象 + * @param excelPath Excel模板地址 + * @param excelFileName 文件名称 + * @param outerMap 头部内容map + * @param innerMapList 表格内容list + * @param excelFillCellMergePrevColUtils 列合并参数 + * @param excelFillCellMergeStrategyUtils 行合并参数 + * @throws IOException 异常错误 + */ + public static void exportToTemplate(HttpServletResponse response, String excelPath, String excelFileName, Map outerMap, List> innerMapList, + ExcelFillCellMergePrevColUtils excelFillCellMergePrevColUtils, ExcelFillCellMergeStrategyUtils excelFillCellMergeStrategyUtils) throws IOException { + InputStream inputStream = new ClassPathResource(excelPath).getInputStream(); + response.setContentType("application/vnd.ms-excel"); + response.setCharacterEncoding("utf-8"); + String fileName = URLEncoder.encode(excelFileName, "UTF-8"); + response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); + ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()) + .withTemplate(inputStream) + .registerWriteHandler(excelFillCellMergePrevColUtils) + .registerWriteHandler(excelFillCellMergeStrategyUtils) + .build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); + if (null != innerMapList && innerMapList.size() > 0) { + FillWrapper listWrapper = new FillWrapper("list", innerMapList); + excelWriter.fill(listWrapper, fillConfig, writeSheet); + } + if (null != outerMap && outerMap.size() > 0) { + excelWriter.fill(outerMap, writeSheet); + } + excelWriter.finish(); + } + + /** + * 验证并获取excel单元格内的值 + * + * @param columnData 列值,object类型 + * @param rowIndex 行号 + * @param columnIndex 列号 + * @param fieldName 字段名称 + * @param lengthLimit 限制长度数(null时不做判断) + * @param ifJudgeEmpty 是否需要判空(默认是) + * @return 字符串格式值 + * @throws Exception 逻辑异常 + */ + public static String checkValue(Object columnData, int rowIndex, int columnIndex, String fieldName, Integer lengthLimit, + Boolean ifJudgeEmpty) throws Exception { + String value = getStringValue(columnData); + ifJudgeEmpty = null == ifJudgeEmpty ? true : ifJudgeEmpty; + if (ifJudgeEmpty) { + //需要判空 + if (StringUtils.isEmpty(value)) { + throw new Exception("第" + (rowIndex + 1) + "行,第" + (columnIndex + 1) + "列," + fieldName + "不能为空"); + } + } + if (null != lengthLimit && lengthLimit > 0) { + //需要判断字符长度 + if (StringUtils.isNotEmpty(value)) { + if (value.length() > lengthLimit) { + throw new Exception("第" + (rowIndex + 1) + "行,第" + (columnIndex + 1) + "列," + fieldName + "不能超过" + lengthLimit + "个字符"); + } + } + } + return value; + } + + /** + * String => LocalDate + * 入参str和pattern格式需要对应 + * + * @param str + * @return LocalDate + */ + public static LocalDate str2LocalDate(String str) { + if (StringUtils.isEmpty(str)) { + return null; + } + if (str.indexOf("-") != -1 || str.indexOf("/") != -1) { + String pattern = str.indexOf("/") != -1 ? "yyyy/MM/dd" : "yyyy-MM-dd"; + try { + //测试日期字符串是否符合日期 + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern); + return LocalDate.parse(str, dateTimeFormatter); + } catch (Exception e) { + pattern = str.indexOf("/") != -1 ? "yyyy/M/d" : "yyyy-M-d"; + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern); + return LocalDate.parse(str, dateTimeFormatter); + } + } else { + Calendar calendar = new GregorianCalendar(1900, 0, -1); + Date date = calendar.getTime(); + int amount = Integer.parseInt(str); + if (amount > 0) { + Calendar calendar1 = Calendar.getInstance(); + calendar1.setTime(date); + calendar1.add(Calendar.DAY_OF_YEAR, amount); + date = calendar.getTime(); + } + return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + } + } + + /** + * 获取String类型的值 + * + * @param columnData 列值,object类型 + * @return 字符串格式值 + */ + public static String getStringValue(Object columnData) { + if (columnData == null) { + return null; + } else { + String res = columnData.toString().replace("[\\t\\n\\r]", "").trim(); + return res; +// //判断是否是科学计数法 true是科学计数法,false不是科学计数法 +// boolean isMache=SCIENTIFIC_COUNTING_METHOD_PATTERN.matcher(res).matches(); +// if(isMache){ +// BigDecimal resDecimal = new BigDecimal(res); +// return resDecimal.toPlainString(); +// }else { +// return res; +// } + } + } + + public static void main(String[] args) { +// ExcelUtils.exportToTemplate(response, "excel/某某管理导入模板.xlsx", "某某管理导入模板", null, null, null, null); + } +} \ No newline at end of file diff --git a/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExportExcelByTemplateUtils.java b/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExportExcelByTemplateUtils.java new file mode 100644 index 000000000..21d9a52b8 --- /dev/null +++ b/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/ExportExcelByTemplateUtils.java @@ -0,0 +1,65 @@ +package com.xdadan.erp.emis.service.billexport; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @Description 根据模版导出Excel工具类 + * @Author susu + * @Date 2024/2/19 + */ +public class ExportExcelByTemplateUtils { + + /** + * @description: 根据模版导出Excel入口(单个list数据) + * @param templateFileName + * @param exportFilePathAndName + * @param staticDataMap + * @param dataId + * @param originDataList + * @return void + * @author susu + * @date 2024/2/21 + */ + public static void doExportExcelOneListByTemplate(String templateFileName, String exportFilePathAndName, + Map staticDataMap, + String dataId, + List originDataList) throws Exception{ + + List> exportDataList = MapObjectUtil.objListToMapList(originDataList); + // 只有一个list数据 + List dynamicDataMappingList = DynamicDataMapping.createOneDataList(dataId, exportDataList); + // 导出 + ExcelTemplateProc.doExportExcelByTemplateProc(templateFileName,exportFilePathAndName,staticDataMap,dynamicDataMappingList); + + } + + /** + * @description: 根据模版导出Excel入口(多个list数据) + * @param templateFileName + * @param exportFilePathAndName + * @param staticSource + * @param originDataMapList + * @return void + * @author susu + * @date 2024/2/20 + */ + public static void doExportExcelMoreListByTemplate(String templateFileName, + String exportFilePathAndName, + Map staticSource, + Map> originDataMapList) throws Exception{ + + Map>> transMap = new HashMap<>(); + originDataMapList.forEach((dataId,originDataList)->{ + List> transDataList = MapObjectUtil.objListToMapList(originDataList); + transMap.put(dataId,transDataList); + }); + // 多个list类型数据 + List dynamicDataMappingList = DynamicDataMapping.createMorDataList(transMap); + // 导出 + ExcelTemplateProc.doExportExcelByTemplateProc(templateFileName,exportFilePathAndName,staticSource,dynamicDataMappingList); + + } + +} diff --git a/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/MapObjectUtil.java b/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/MapObjectUtil.java new file mode 100644 index 000000000..6652fb5eb --- /dev/null +++ b/emis-biz/src/main/java/com/xdadan/erp/emis/service/billexport/MapObjectUtil.java @@ -0,0 +1,97 @@ +package com.xdadan.erp.emis.service.billexport; + +import com.alibaba.fastjson.JSONObject; +import org.apache.commons.beanutils.BeanMap; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @Description + * @Author susu + * @Date 2024/2/19 + */ +public class MapObjectUtil { + + /** + * @description: 将object的list数据 转换成 map的list(如:List>) + * @param objDataList + * @return java.util.List> + * @author susu + */ + public static List> objListToMapList(List objDataList){ + List> dataList = new ArrayList<>(); + if (objDataList==null || objDataList.size()<1){ + return null; + } + objDataList.forEach(obj->{ + try { + Map map = MapObjectUtil.objectToMap(obj); + dataList.add(map); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + return dataList; + } + + /** + * @description: 将object数据转换成map数据 + * @param obj + * @return java.util.Map + * @author susu + */ + public static Map objectToMap(Object obj) throws IllegalAccessException { + Map map = new HashMap(); + Class cla = obj.getClass(); + Field[] fields = cla.getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + String keyName = field.getName(); + Object value = field.get(obj); + if (value == null) + value = ""; + map.put(keyName, value); + } + return map; + } + + /** + * @description: 使用 JSONObject 将object转换成map + * @param obj + * @return java.util.Map + * @author susu + */ + public static Map objectToMap2(Object obj) { + if (obj == null) + return null; + return JSONObject.parseObject(JSONObject.toJSONString(obj),Map.class); + } + + /** + * @description: 使用BeanMap将object转换成map + * @param obj + * @return java.util.Map + * @author susu + */ + public static Map objectToMap3(Object obj) { + if (obj == null) + return null; + return new BeanMap(obj); + } + + + public static void main(String[] args) { +// DogEntity2 dog = new DogEntity2(); +// dog.setDogId("A-1001"); +// dog.setDogAge(3); +// dog.setDogName("aaa"); +//// Map map = JSONObject.parseObject(JSONObject.toJSONString(dog),Map.class); +// Map map = objectToMap2(dog); +// System.out.println(map); + } + +} diff --git a/emis-biz/src/main/resources/tpl/settle_bill_tpl.xls b/emis-biz/src/main/resources/tpl/settle_bill_tpl.xls new file mode 100644 index 0000000000000000000000000000000000000000..19e1da38f35ede244cd317f03cf5f3358abf39db GIT binary patch literal 21504 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