freemarker導出復雜Excel


原文: freemarker導出復雜Excel
date: 2017-04-20 12:39:04


[TOC]

序言

用Freemarker做Excel導出確實很容易. 但是導出復雜Excel, 例如多行合并的還是費了一天時間

步驟

首先是pom依賴, 構建工具使用Maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

準備模板

接下來, 制作ftl模板

在Office中編輯一個Excel文件, 這就是最后要生成的模板.

這里寫圖片描述

注意: 另存為xml格式. 不要直接改后綴名

拷貝到項目下, 修改后綴名為.ftl (這里我放到resources/templates目錄下)

用xml方式打開這個.ftl文件. 找到如下代碼

<Table ss:ExpandedColumnCount="4" ss:ExpandedRowCount="3" x:FullColumns="1"
x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="13.5">
    <Row>
        <Cell ss:MergeAcross="3" ss:StyleID="s66"><Data ss:Type="String">人員列表</Data></Cell>
    </Row>
    <Row>
        <Cell ss:StyleID="s67"/>
        <Cell ss:StyleID="s67"><Data ss:Type="String">name</Data></Cell>
        <Cell ss:StyleID="s67"><Data ss:Type="String">age</Data></Cell>
        <Cell ss:StyleID="s67"><Data ss:Type="String">address</Data></Cell>
    </Row>
    <Row>
        <Cell ss:StyleID="s67"><Data ss:Type="Number">1</Data></Cell>
        <Cell ss:StyleID="s67"><Data ss:Type="String">zhangsan</Data></Cell>
        <Cell ss:StyleID="s67"><Data ss:Type="Number">22</Data></Cell>
        <Cell ss:StyleID="s67"><Data ss:Type="String">BeiJing</Data></Cell>
    </Row>
</Table>

主要看上面這段, 對比你的Excel很容易看出: 每個就是一行, 每個是一個單元格

找到要循環(huán)的一行, 添加表達式和標簽

關于Freemarker的語法, 可以參考http://freemarker.foofun.cn/index.html

加完標簽后如下:

<Table ss:ExpandedColumnCount="4" ss:ExpandedRowCount="${userListSize}" x:FullColumns="1"
x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="13.5">
<Row>
    <Cell ss:MergeAcross="3" ss:StyleID="s66"><Data ss:Type="String">人員列表</Data></Cell>
</Row>
<Row>
    <Cell ss:StyleID="s67"/>
    <Cell ss:StyleID="s67"><Data ss:Type="String">姓名</Data></Cell>
    <Cell ss:StyleID="s67"><Data ss:Type="String">年齡</Data></Cell>
    <Cell ss:StyleID="s67"><Data ss:Type="String">地址</Data></Cell>
</Row>

<#assign index = 0 >
<#list userList as u>
<#assign index = index + 1 >
   <Row>
    <Cell ss:StyleID="s67"><Data ss:Type="Number">${index}</Data></Cell>
    <Cell ss:StyleID="s67"><Data ss:Type="String">${u.name}</Data></Cell>
    <Cell ss:StyleID="s67"><Data ss:Type="Number">${u.age}</Data></Cell>
    <Cell ss:StyleID="s67"><Data ss:Type="String">${u.address}</Data></Cell>
   </Row>
</#list>
</Table>

注意ExpandedRowCount的值可以變量傳進來, 如果不好計算可以寫一個很大的值.

自己寫的值有什么影響我沒測試…

準備數據源

對于你要動態(tài)生成的List, 里面可以放Map, 也可以放對象, 都行.

這里我先用Map, 等下用對象

  • 模擬獲取數據
/**
 * 構造user數據List<Map<String, Object>>
 */
private static List<Map<String, Object>> getUserList(){
    List<Map<String, Object>> returnList = new ArrayList<Map<String, Object>>();
    Map<String,Object> map1 = new HashMap<String, Object>();
    Map<String,Object> map2 = new HashMap<String, Object>();
    map1.put("name", "張三");
    map1.put("age", "18");
    map1.put("address", "廣東");
    
    map2.put("name", "王五");
    map2.put("age", "22");
    map2.put("address", "北京");
    
    returnList.add(map1);
    returnList.add(map2);
    return returnList;
}
  • 控制器
@SuppressWarnings("unchecked")
@RequestMapping(value="/exportExcel", method=RequestMethod.GET)
public void exportExcelByFreeMarker(HttpServletRequest request, HttpServletResponse response) {
    try {
        List<Map<String, Object>> userList= this.getUserList();
        
        configuration.setDefaultEncoding("UTF-8");  
        configuration.setTemplateUpdateDelayMilliseconds(0);  
        configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);  
        //獲取模板
        Template template = configuration.getTemplate("userlist.ftl");
        Map<String,Object> root = new HashMap<String,Object>();
        root.put("userList", userList);
        root.put("userListSize", String.valueOf(userList.size()+2));
        
        SimpleDateFormat  sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        Date today = new Date();
        String fileName = "人員列表" + sdf.format(today);//以今天的日期為文件名 
        response.setContentType("application/msexcel;charset=UTF-8");
        response.setHeader("Content-disposition","attachment;filename=\""+new String((fileName+".xls").getBytes("GBK"),"ISO8859-1")+"\"");
        //response字符流轉換成字節(jié)流,template需要字節(jié)流作為輸出
        OutputStream outputStream = response.getOutputStream();
        OutputStreamWriter outputWriter = new OutputStreamWriter(outputStream,"UTF-8");
        Writer writer = new BufferedWriter(outputWriter);
        template.process(root, writer);
        writer.flush();  
        writer.close();  
    } catch (Exception e) {
        e.printStackTrace();
    }
}

在瀏覽器中輸入請求地址就可以導出了.

復雜Excel

生成Excel也有很多復雜的情況, 比如帶圖片, 跨行合并等.

這里例多行合并的, 例如:


這里寫圖片描述

生成的xml:

<Table ss:ExpandedColumnCount="5" ss:ExpandedRowCount="99999" x:FullColumns="1"
 x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="13.5">
 <Row>
  <Cell ss:MergeAcross="3" ss:StyleID="s66"><Data ss:Type="String">人員列表</Data></Cell>
  <Cell ss:StyleID="s62"/>
 </Row>
 <Row>
  <Cell ss:StyleID="s67"/>
  <Cell ss:StyleID="s67"><Data ss:Type="String">姓名</Data></Cell>
  <Cell ss:StyleID="s67"><Data ss:Type="String">年齡</Data></Cell>
  <Cell ss:StyleID="s67"><Data ss:Type="String">孩子</Data></Cell>
  <Cell ss:StyleID="s69"><Data ss:Type="String">地址</Data></Cell>
 </Row>
 <Row>
  <Cell ss:MergeDown="1" ss:StyleID="m87387632"><Data ss:Type="Number">1</Data></Cell>
  <Cell ss:MergeDown="1" ss:StyleID="m87387612"><Data ss:Type="String">張三</Data></Cell>
  <Cell ss:MergeDown="1" ss:StyleID="m87387592"><Data ss:Type="Number">22</Data></Cell>
  <Cell ss:StyleID="s67"><Data ss:Type="String">小張</Data></Cell>
  <Cell ss:MergeDown="1" ss:StyleID="m87387572"><Data ss:Type="String">北京</Data></Cell>
 </Row>
 <Row>
  <Cell ss:Index="4" ss:StyleID="s67"><Data ss:Type="String">二張</Data></Cell>
 </Row>
</Table>

仔細觀察這段代碼, 跟上面的不帶合并的xml進行對比:

  • 每個帶合并的Cell都帶有一個屬性MergeDown, 它的值為 (合并格數-1)
  • 合并的格數是通過一個對象的循環(huán)來得到, 比如這里的”孩子”字段
  • 擴展的Row會有一個Index屬性, 它的值就是字段的列數, 這里是4

分析好之后開始加標簽:

  <Table ss:ExpandedColumnCount="5" ss:ExpandedRowCount="4" x:FullColumns="1"
   x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="13.5">
   <Row>
    <Cell ss:MergeAcross="3" ss:StyleID="s66"><Data ss:Type="String">人員列表</Data></Cell>
    <Cell ss:StyleID="s62"/>
   </Row>
   <Row>
    <Cell ss:StyleID="s67"/>
    <Cell ss:StyleID="s67"><Data ss:Type="String">姓名</Data></Cell>
    <Cell ss:StyleID="s67"><Data ss:Type="String">年齡</Data></Cell>
    <Cell ss:StyleID="s67"><Data ss:Type="String">孩子</Data></Cell>
    <Cell ss:StyleID="s69"><Data ss:Type="String">地址</Data></Cell>
   </Row>
   
<#assign index = 0 >
<#list userBoList as u>
<#assign index = index + 1 >
<#assign num = u.children?size-1>
<#if num lt 0>
<#assign num = 0>
</#if>
   <Row>
    <Cell ss:MergeDown="${num}" ss:StyleID="m87387632"><Data ss:Type="Number">${index}</Data></Cell>
    <Cell ss:MergeDown="${num}" ss:StyleID="m87387612"><Data ss:Type="String">${u.name}</Data></Cell>
    <Cell ss:MergeDown="${num}" ss:StyleID="m87387592"><Data ss:Type="Number">${u.age}</Data></Cell>
    <!-- 在children的List中取出第一個 -->
    <#list u.children as firstChildren>
      <#if firstChildren_index == 0>
        <Cell ss:StyleID="s67"><Data ss:Type="String">${firstChildren.name}</Data></Cell>
      </#if>
    </#list>
    <Cell ss:MergeDown="${num}" ss:StyleID="m87387572"><Data ss:Type="String">${u.address}</Data></Cell>
   </Row>
   
  <!-- 從第一個之后開始取 -->
  <#list u.children as c>
    <#if (u.children?size > 1 && c_index > 0 )>  
     <Row>
      <Cell ss:Index="4" ss:StyleID="s67"><Data ss:Type="String">${c.name}</Data></Cell>
     </Row>
    </#if>
  </#list>
  
</#list>

  </Table>

注意:
MergeDown這個屬性中判斷了是否小于0, 親測在office中<0會導致Excel報錯, WPS不會, 最好還是判斷一下
ExpandedRowCount可以給個很大的值, 最好還是通過程序來傳大小

在這個Excel中需要兩個List:
User: List<UserBo>
Children: List<ChildrenBo>
User -> Children : 1->N
也可以是Map: List<Map<String, Object>>
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容