模板导出(根据key填值)

这段代码展示了如何使用Java后端导出年度项目预算模板,并根据给定的数据填充信息,包括填报日期、标题、不同年份的预算,以及不同类型项目的详细预算信息。通过遍历项目列表并使用Map进行键值对匹配,实现Excel模板的动态填充,最后导出文件。

这是我的后端代码:

/**
     * 导出年度项目预算
     * @param proProject
     * @param response
     */
    public static void exportYearProBud(ProProject proProject,List<ProProject> regularList,List<ProProject> disposableList,List<ProProject> informatizationList, HttpServletResponse response) {
        //map的key对应Excel单元格里的值
        Map<String, Object> map = new HashMap<String, Object>();
        //获取当前日期并格式化
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
        map.put("${fillDate}", "填报日期:"+sdf.format(date));        //填报日期
        map.put("${title}",proProject.getThisYear()+"~"+(proProject.getAfterYear())+"年市本级财政项目预算(按项目)");//标题
        map.put("${thisYear}",proProject.getThisYear()+"年申报预算");            //选择的年份
        map.put("${LastYear}",(proProject.getThisYear()-1)+"年批复数");        //选择的年份的前年
        map.put("${nextYear}",(proProject.getNextYear())+"年申报预算");            //选择的年份的明年
        map.put("${afterYear}",(proProject.getAfterYear())+"年申报预算");        //选择的年份的后年
        //遍历经常性项目
        Map<String, Object> regularMap = proProjectList(regularList,proProject.getThisYear(),"regular");
        map.putAll(regularMap);
        //遍历一次性项目
        Map<String, Object> disposableMap = proProjectList(disposableList,proProject.getThisYear(),"disposable");
        map.putAll(disposableMap);
        //遍历信息化项目
        Map<String, Object> informatizationMap = proProjectList(informatizationList,proProject.getThisYear(),"informatization");
        map.putAll(informatizationMap);
        int regularSize = regularList.size();
        int disposableSize = disposableList.size();
        int informatizationSize = informatizationList.size();
        //导出Excel文件
        exportYearProBud(proProject,regularSize,disposableSize,informatizationSize,map,response);
    }
    
    /**
     * 遍历项目信息
     * @param list
     * @param flag
     * @return
     */
    public static Map<String, Object> proProjectList(List<ProProject> list, int year, String flag){
        Map<String, Object> map = new HashMap<String, Object>();
        //遍历项目信息
        for(int i=0; i<list.size(); i++){
            map.put("${"+flag+"["+i+"].kind}", list.get(i).getBudSystem().getKind());
            map.put("${"+flag+"["+i+"].paragraph}", list.get(i).getBudSystem().getParagraph());
            map.put("${"+flag+"["+i+"].item}", list.get(i).getBudSystem().getItem());
            map.put("${"+flag+"["+i+"].name}", list.get(i).getName());
            map.put("${"+flag+"["+i+"].budProClass.name}", list.get(i).getBudProClass().getName());
            map.put("${"+flag+"["+i+"].budSystem.name}", list.get(i).getBudSystem().getName());
            map.put("${"+flag+"["+i+"].content}", list.get(i).getContent());
            if(list.get(i).getTotalMoney1()!=null && !"".equals(list.get(i).getTotalMoney1())){
                map.put("${"+flag+"["+i+"].thisYear}", list.get(i).getTotalMoney1());
            }
            if(list.get(i).getTotalMoney2()!=null && !"".equals(list.get(i).getTotalMoney2())){
                map.put("${"+flag+"["+i+"].nextYear}", list.get(i).getTotalMoney2());
            }
            if(list.get(i).getTotalMoney3()!=null && !"".equals(list.get(i).getTotalMoney3())){
                map.put("${"+flag+"["+i+"].afterYear}", list.get(i).getTotalMoney3());
            }
            map.put("${"+flag+"["+i+"].number}", ++i+"");
            --i;
        }
        return map;
    }
    
    /**
     * 根据map导出Excel文件
     * @param proProject
     * @param map
     * @param response
     */
    public static void exportYearProBud(ProProject proProject,int regularSize, int disposableSize, int informatizationSize, 
                                            Map<String, Object> map, HttpServletResponse response){
        //读取的模板文件的位置(在resources下的resources文件夹里面)
        String srcPath = GetResource.class.getClassLoader().getResource("/resources/某某年支出项目预算.xls").getPath();
        String filrName = proProject.getThisYear()+"年项目支出项目预算.xls";
        OutputStream os = null;
        try {
            POIFSFileSystem fs  =new POIFSFileSystem(new FileInputStream(srcPath));
            HSSFWorkbook wb = new HSSFWorkbook(fs);
            HSSFSheet sheet = wb.getSheetAt(0);
            Iterator<Row> rows = sheet.rowIterator();
            int j = 0;
            while(rows.hasNext()){
                HSSFRow row = (HSSFRow) rows.next();
                if(row!=null) {
                    int num = row.getLastCellNum();
                    j++;
                    for(int i=0; i<num; i++) {
                        if(j==4 || j==5 || j==6 || j==28 || j==50){
                            break;
                        }
                        HSSFCell cell =  row.getCell(i);
                        if(cell!=null){
                            String value= cell.toString();
                            if(value==null || "".equals(value)) {
                                continue;
                            }
                            if(!"".equals(value)) {
                                Set<String> keySet = map.keySet();
                                Iterator<String> it = keySet.iterator();
                                while (it.hasNext()) {
                                    String text = it.next();
                                    if(value.equalsIgnoreCase(text)) {
                                        if(i>=9 && j!=3){
                                            BigDecimal totalMoney = new BigDecimal(map.get(text).toString());
                                            cell.setCellValue(totalMoney.doubleValue());
                                        }else{
                                            cell.setCellValue((String)map.get(text));
                                        }
                                        break;
                                    }
                                }
                            } else {
                                cell.setCellValue("");
                            }
                        }
                    }
                }
            }
            //删掉多余的行
            int delRow = 7;    //删除行
            sheet = deleteCell(sheet,sheet.getLastRowNum(),regularSize,delRow);
            int delRow2 = 29-(20-regularSize);     //删除行(初始),要减掉上面删除的行
            sheet = deleteCell(sheet,sheet.getLastRowNum(),disposableSize,delRow2);
            int delRow3 = 51-(40-regularSize-disposableSize);     //删除行(初始),要减掉上面删除的行
            sheet = deleteCell(sheet,sheet.getLastRowNum(),informatizationSize,delRow3);
            HSSFRow row = sheet.getRow(sheet.getLastRowNum());
            sheet.removeRow(row);
            //检索替换
            retrieve(rows,sheet,regularSize,disposableSize,informatizationSize);
            //刷新公式
            sheet.setForceFormulaRecalculation(true);
            response.reset();
            os = response.getOutputStream();   //获取响应的输出流
            response.setContentType("text/html; charset=UTF-8"); //设置编码字符  
            response.setContentType("application/octet-stream"); //设置内容类型为下载类型  
            response.setHeader("Content-disposition", "attachment;filename="+URLEncoder.encode(filrName, "utf-8"));
            wb.write(os);
         } catch (Exception e) {
            e.printStackTrace();
         } finally {
            if(os != null){
                try {
                    os.flush();//释放缓存
                    os.close();//关闭输出流
                } catch (Exception e) {
                    logger.info("关闭输出流失败:"+e.getMessage() );
                    e.printStackTrace();
                }
            }
         }
    }
    
    /**
     * 删除预留多余的行
     * @param sheet
     * @param lastRow
     * @param size
     * @param delRow
     * @return
     */
    public static HSSFSheet deleteCell(HSSFSheet sheet, int lastRow, int size, int delRow){
        int totalDelRow = 20;    //预留的行
        delRow += size;
        totalDelRow -= size;    //要删除的行数量
        for(int i=0;i<totalDelRow;i++){
            sheet.shiftRows(delRow,lastRow-1,-1);
        }
        return sheet;
    }
    
    /**
     * 检索替换
     * @param rows
     * @param sheet
     * @param regularSize
     * @param disposableSize
     */
    public static void retrieve(Iterator<Row> rows, HSSFSheet sheet, int regularSize, int disposableSize, int informatizationSize){
        rows = sheet.rowIterator();
        int j = 0;
        while(rows.hasNext()){
            HSSFRow row1 = (HSSFRow) rows.next();
            j++;
            if(row1!=null) {
                int num = row1.getLastCellNum();
                for(int i=0; i<num; i++) {
                    if(j<=6 || j==(28-20)+regularSize || j==(50-40)+regularSize+disposableSize){
                        if(j==6 || j==(28-20)+regularSize || j==(50-40)+regularSize+disposableSize){
                            row1.setHeight((short) 600);//目的是想把行高设置成600px(30磅)
                        }
                        break;
                    }else if(j>=14+regularSize+disposableSize+informatizationSize){
                        row1.setHeight((short) 400);//目的是想把行高设置成400px
                    }
                    HSSFCell cell =  row1.getCell(i);
                    if(cell!=null){
                        String value= cell.toString();
                        if(value==null || "".equals(value)) {
                            continue;
                        }
                        if(value.contains("${")){//检索Excel表中包含有"${"字符的单元格替换为""
                            cell.setCellValue("");
                        }
                    }
                }
            }
        }
    }

<think> 我们正在讨论EasyExcel根据模板导出的方法和实现。根据引用内容,EasyExcel是一个强大的Java Excel处理工具,特别适合导入导出操作。根据模板导出数据通常涉及以下步骤: 1. 准备Excel模板文件,在需要充数据的位置使用占位符(例如:${name}) 2. 编写Java代码,使用EasyExcel模板充功能将数据写入模板 具体步骤: 1. 导入依赖(如引用[1][4]所示): ```xml <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.2.6</version> <!-- 注意:版本可能更新,请根据实际情况选择 --> </dependency> ``` 2. 创建Excel模板(如引用[4]所示): 在模板文件中,使用占位符标识需要充的位置。例如: - 单元格A1:${title} 表示此处将充标题 - 在需要循环表的地方,使用循环标记。例如: A3: ${employeeList[0].name} 表示第一行员工姓名(但这样写只能充一个,不推荐) 正确的循环写法是使用EasyExcel的循环指令,例如: A3: ${employeeList.name} 并且将这一行作为循环行(注意:需要设置循环区域) 但是,根据EasyExcel的文档,我们通常需要指定一个区域来循环,这可以通过在模板中设置一个特殊的标记来实现。例如: 在模板中,我们定义: 第一行:标题行(合并单元格等) 第二行:表头行(姓名、年龄等) 第三行:开始循环的行,在第三行的每个单元格使用占位符,如: A3: ${employee.name} B3: ${employee.age} 然后,我们告诉EasyExcel从第三行开始向下循环充(在代码中指定)。 然而,实际上EasyExcel提供了更简单的方式:在模板中,我们只需要在需要循环的行上标记为循环行,然后使用占位符即可。具体做法是: 在第三行(循环行)的每个单元格使用占位符,然后在代码中指定这个区域(第三行)需要循环。 但是,根据EasyExcel的API,我们可以使用`FillConfig`来设置循环,并且在模板中不需要特别标记循环行(除了占位符)。不过,如果有多表头,我们通常会在模板中预留一行作为数据行,然后通过代码告诉EasyExcel这一行需要循环。 3. 编写Java代码: 步骤: a. 准备数据:通常是一个Map,包含模板中的各个变量(单个对象)和表(循环数据) b. 设置输出流(例如文件输出流) c. 使用`EasyExcel.write(outputStream).withTemplate(templatePath).sheet().doFill(dataMap)` 具体示例代码(参考引用[2][3]): ```java // 模板路径 String templateFileName = "template.xlsx"; // 导出的文件名 String outputFileName = "export_result.xlsx"; // 准备数据 Map<String, Object> dataMap = new HashMap<>(); // 充单个属性 dataMap.put("title", "员工信息表"); dataMap.put("date", new Date()); // 准备表数据 List<Employee> employeeList = ...; // 从数据库获取 // 使用EasyExcel导出 try (OutputStream outputStream = new FileOutputStream(outputFileName)) { // 创建ExcelWriter ExcelWriter excelWriter = EasyExcel.write(outputStream) .withTemplate(templateFileName) .build(); // 获取第一个sheet WriteSheet writeSheet = EasyExcel.writerSheet().build(); // 充单个数据 excelWriter.fill(dataMap, writeSheet); // 表数据(循环) // 注意:这里我们假设模板中有一个占位符${employeeList},但实际上我们充的是employeeList这个表,并且需要指定循环的行 // 但是,在模板中,我们的循环行已经写好了占位符,比如第三行:A3:${employee.name}, B3:${employee.age} // 那么,我们这样充: FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build(); // 注意:这里使用fill(employeeList, fillConfig, writeSheet)表,并自动向下扩展行 excelWriter.fill(new FillWrapper("employeeList", employeeList), fillConfig, writeSheet); excelWriter.finish(); } ``` 注意:上面的`FillWrapper`是用于包装表数据,让EasyExcel知道我们要充的是一个表,并且占位符的前缀是"employeeList"(即模板中的占位符是${employeeList.xxx},但我们在,使用了包装器后,只需要写${xxx},但是实际代码中我们包装的指定了前缀,所以模板中的占位符应该是${employeeList.xxx}。不过,在模板中我们通常写${employeeList.name},所以这里我们使用`FillWrapper`将前缀设置为"employeeList",然后模板中的占位符就是${name}(在循环行中)?这里需要澄清。 实际上,根据EasyExcel的文档,我们有两种方式: 方式1:在模板中,循环行使用${employeeList.name},那么直接使用一个Map,其中有一个key为employeeList,value为List<Employee>。然后使用fill方法充这个Map即可(不需要FillWrapper)。但是这样,我们需要指定循环行(通过模板中占位符的位置,EasyExcel会自动识别循环行并扩展)。 方式2:使用FillWrapper,这样在模板中我们可以用不同的方式写占位符。 但是,根据引用[2]中的示例,他们使用了FillWrapper,并且模板中的占位符是${.name}(即前面有个点)。为了避免混淆,我们参考官方示例。 官方文档示例(https://www.yuque.com/easyexcel/doc/fill): 模板:在循环行中,使用${.name}, ${.age}等。 代码: FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build(); excelWriter.fill(new FillWrapper("data", list), fillConfig, writeSheet); 这里,模板中的占位符是${.name},而FillWrapper的前缀是"data",那么实际占位符会变成${data.name}?不对,官方示例中,使用FillWrapper后,在循环行中直接用${.name},表示当前对象的name属性。 所以,我们更常用的方式可能是: 模板中: 第一行:${title} 第三行(循环行):${name} ${age} ... (注意:没有前缀) 代码: // 充单个数据 Map<String, Object> map = new HashMap<>(); map.put("title", "标题"); excelWriter.fill(map, writeSheet); // 表 FillConfig fillConfig = FillConfig.builder().build(); excelWriter.fill(list, fillConfig, writeSheet); // 这里list是List<Employee>,且Employee有name和age属性 这样,循环行中的${name}就会用list中的每个对象的name属性充。 但是,如果我们同有多个表要充,就需要使用FillWrapper来指定不同的区域。例如,有两个表:list1和list2,那么我们就需要分别充,并指定不同的开始行。 4. 复杂模板(多个表): 如果模板中有多个表,我们需要分别指定每个表的充区域。这,我们可以通过指定不同的行来循环。但是,在同一个sheet中,我们可能需要多次充,每次充一个表,并且指定表的起始行(即模板中该循环行所在的行)。 例如: 在模板中: 第1行:${title} 第3行:表头1,表头2(用于表1) 第4行:${list1.name} ${list1.age} (这是表1的循环行) 第10行:表头3,表头4(用于表2) 第11行:${list2.address} ${list2.phone} (表2的循环行) 代码: // 充单个数据 Map<String, Object> map = new HashMap<>(); map.put("title", "双表"); excelWriter.fill(map, writeSheet); // 充第一个表 FillConfig fillConfig1 = FillConfig.builder().build(); // 指定从第4行开始充(注意:行号从0开始,所以是3) // 但是,EasyExcel并没有直接提供设置起始行的方法,而是通过模板中占位符的位置来确定的。所以,我们只需要在模板中把占位符放在第4行,那么充第一个,就会从第4行开始循环。 // 但是,我们如何指定充哪个占位符呢?因为我们的占位符是${list1.name},那么我们需要将list1的数据充到这些位置。 // 因此,我们需要使用一个包装器,并指定变量名为"list1" excelWriter.fill(new FillWrapper("list1", list1Data), fillConfig1, writeSheet); // 充第二个excelWriter.fill(new FillWrapper("list2", list2Data), fillConfig1, writeSheet); 这样,EasyExcel会自动在模板中找到${list1.name}和${list1.age}的位置,然后充第一个表,然后找到${list2.address}和${list2.phone}的位置充第二个表。 5. 注意事项: - 模板文件需要放在项目中,或者通过绝对路径引用。 - 导出,EasyExcel根据模板的样式生成新的Excel文件,并充数据。 - 循环,会自动扩展行(向下或向右,由FillConfig的direction控制,默认是向下)。 6. 更复杂的示例(合并单元格): 在模板中,我们也可以设置合并单元格,然后在充数据,EasyExcel会自动处理合并。但是,如果循环充导致行数变化,合并区域也会自动扩展吗?根据EasyExcel的文档,它支持在循环自动调整合并区域。例如,如果我们在模板中合并了第4行的A和B,然后充了5条数据,那么合并区域会扩展到5行。 7. 官方文档建议: 由于EasyExcel的版本更新,具体用法请参考官方文档:https://www.yuque.com/easyexcel/doc/fill 综上所述,我们给出一个简单的完整示例: 模板文件(template.xlsx): A1: ${title} A2: 姓名 B2: 年龄 A3: ${name} (注意:这里没有前缀,因为我们直接使用对象属性) B3: ${age} Java代码: ```java public class Employee { private String name; private Integer age; // getter/setter } public void export() { String templateFileName = "path/to/template.xlsx"; String outputFileName = "path/to/output.xlsx"; Map<String, Object> dataMap = new HashMap<>(); dataMap.put("title", "员工表"); List<Employee> list = new ArrayList<>(); list.add(new Employee("张三", 25)); list.add(new Employee("李四", 30)); try (OutputStream out = new FileOutputStream(outputFileName)) { ExcelWriter writer = EasyExcel.write(out).withTemplate(templateFileName).build(); WriteSheet sheet = EasyExcel.writerSheet().build(); // 充单个数据 writer.fill(dataMap, sheet); // 表(注意:这里没有使用FillWrapper,因为我们的模板中占位符没有前缀,所以直接使用list) // 但是,直接充list的话,模板中的占位符必须是没有前缀的,即${name}而不是${employee.name} // 如果模板中占位符有前缀,则需要使用FillWrapper writer.fill(list, sheet); writer.finish(); } } ``` 如果模板中的占位符有前缀,比如A3: ${employee.name},那么我们需要这样表: ```java // 使用FillWrapper,指定前缀为"employee" writer.fill(new FillWrapper("employee", list), sheet); ``` 注意:FillWrapper的作用就是给每个属性加一个前缀,这样在模板中,占位符就要写成${employee.name},而FillWrapper会将前缀加上,然后匹配。 因此,根据模板中占位符的写法,我们决定是否使用FillWrapper。 最后,生成相关问题。 </think> ### EasyExcel 根据模板导出数据实现指南 #### 一、核心实现步骤 1. **准备Excel模板** ```excel <!-- 模板示例:template.xlsx --> | A1: ${title} | B1: ${exportDate} | |--------------|-------------------| | A2: 姓名 | B2: 年龄 | | A3: ${name} | B3: ${age} | <!-- 循环行 --> ``` 2. **添加Maven依赖** ```xml <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.3.2</version> <!-- 推荐最新版 --> </dependency> ``` 3. **Java实现代码** ```java public class TemplateExportDemo { // 数据实体类 @Data public static class Employee { private String name; private Integer age; } public static void export() throws IOException { // 1. 准备数据 Map<String, Object> dataMap = new HashMap<>(); dataMap.put("title", "员工信息表"); dataMap.put("exportDate", new Date()); List<Employee> employeeList = Arrays.asList( new Employee("张三", 25), new Employee("李四", 30) ); // 2. 获取模板路径 String template = "template.xlsx"; // 3. 执行导出 String output = "员工数据导出.xlsx"; try (OutputStream out = new FileOutputStream(output)) { ExcelWriter writer = EasyExcel.write(out) .withTemplate(template) .build(); WriteSheet sheet = EasyExcel.writerSheet().build(); // 充单数据 writer.fill(dataMap, sheet); // 表数据(自动循环扩展行) writer.fill(new FillWrapper("data", employeeList), null, sheet); writer.finish(); } } } ``` #### 二、关键技术要点 1. **模板语法** - 单充:`${变量名}` - 充:在循环行使用`${data.属性名}`(`data`为FillWrapper指定的名称)[^2][^4] - 自动扩展:检测到表数据会自动向下复制模板行 2. **复杂结构处理** ```java // 多充 FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build(); writer.fill(new FillWrapper("list1", list1Data), fillConfig, sheet); writer.fill(new FillWrapper("list2", list2Data), fillConfig, sheet); // 嵌套对象充 dataMap.put("dept", department); // 模板中使用 ${dept.name} ``` 3. **样式保留机制** - 保留模板中所有样式(字体、颜色、边框) - 自动扩展合并单元格区域[^1] - 循环行样式自动应用到新增行 #### 三、最佳实践建议 1. **模板设计规范** - 使用单独Sheet作为模板 - 预留足够的空行供数据扩展 - 复杂表头使用合并单元格 2. **性能优化** ```java // 大批量数据分片写入 for (int i = 0; i < total; i += batchSize) { List<User> batchData = data.subList(i, Math.min(i + batchSize, total)); writer.fill(new FillWrapper("data", batchData), sheet); } ``` 3. **特殊场景处理** - 动态:使用`@ExcelProperty`注解动态匹配 - 公式计算:模板中直接写Excel公式(如`=SUM(B3:B10)`) - 图片导出:使用`ImageData`类嵌入图片 #### 四、完整工具类示例 ```java public class ExcelExportUtil { public static void exportByTemplate(String templatePath, String outputPath, Map<String, Object> singleData, List<?> listData) { try (OutputStream out = new FileOutputStream(outputPath)) { ExcelWriter writer = EasyExcel.write(out) .withTemplate(templatePath) .build(); WriteSheet sheet = EasyExcel.writerSheet().build(); // 充单数据 if (singleData != null) { writer.fill(singleData, sheet); } // 表数据 if (listData != null && !listData.isEmpty()) { writer.fill(new FillWrapper("data", listData), sheet); } writer.finish(); } catch (IOException e) { throw new RuntimeException("导出失败", e); } } } ``` 调用方式: ```java Map<String, Object> data = new HashMap<>(); data.put("reportName", "季度销售报告"); List<SalesData> salesList = salesService.getData(); ExcelExportUtil.exportByTemplate( "templates/sales_template.xlsx", "output/sales_report.xlsx", data, salesList ); ``` > **📌 注意事项** > 1. 模板文件需放在资源目录`resources/templates/`下 > 2. 模板行需包含`${data.xxx}`占位符 > 3. 大数据量导出需增加JVM参数:`-Xms512m -Xmx1024m`[^3]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值