1. 程式人生 > >使用Freemarker生成Word文件並在文件內新增Echarts圖形報表或迴圈新增表格、圖片資料

使用Freemarker生成Word文件並在文件內新增Echarts圖形報表或迴圈新增表格、圖片資料

一、製作.ftl字尾的word模板檔案

1、新建一個word文件模板

        

使用其他文字編輯器編寫表示式,如:Editplus

 

 2、將word文件另存為xml並改名為.ftl字尾的檔案

另存完之後關閉word文件,將demo.xml的字尾修改為.ftl,然後使用文字編輯器開啟demo.ftl檔案

3、修改.ftl檔案並生成最終的模板檔案

① 修改圖片的資料內容使用表示式代替

替換之後如下:

② 在資料表格中新增迴圈標籤

 二、通過模板檔案生成word文件

1、新增pom.xml的依賴

<dependency>
     <groupId>org.freemarker</groupId>
     <artifactId>freemarker</artifactId>
     <version>2.3.23</version>
</dependency>

2、index.jsp中新增一個Echarts圖形報表

    var option = {
            angleAxis: {
                type: 'category',
                data: ['週一', '週二', '週三', '週四', '週五', '週六', '週日'],
                z: 10
            },
            radiusAxis: {
            },
            polar: {
            },
            series: [{
                type: 'bar',
                data: [1, 2, 3, 4, 3, 5, 1],
                coordinateSystem: 'polar',
                name: 'A',
                stack: 'a'
            }, {
                type: 'bar',
                data: [2, 4, 6, 1, 3, 2, 1],
                coordinateSystem: 'polar',
                name: 'B',
                stack: 'a'
            }, {
                type: 'bar',
                data: [1, 2, 3, 4, 1, 2, 5],
                coordinateSystem: 'polar',
                name: 'C',
                stack: 'a'
            }],
            legend: {
                show: true,
                data: ['A', 'B', 'C']
            }
        };

        var myChart = echarts.init(document.getElementById("content"));
        myChart.setOption(option);
        //獲取Echart圖形報表生成的Base64編碼格式的資料
        var imgData = myChart.getConnectedDataURL();
        $.post('/demo/word',{'imgData':imgData},function (data) {
            alert(data);
        },'json');

3、後臺處理請求並設定模板資料

@Controller
@RequestMapping("/demo")
public class DemoController {

    @RequestMapping("/word")
    @ResponseBody
    public String generateWord(String imgData){
        // 傳遞過程中  "+" 變為了 " " ,所以需要替換
        String newImageInfo = imgData.replaceAll(" ", "+");
        // 資料中:data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABI4AAAEsCAYAAAClh/jbAAA ...
        // 在"base64,"之後的才是圖片資訊
        String[] arr = newImageInfo.split("base64,");

        //新增模板資料
        Map<String,Object> dataMap = new HashMap<>();
        dataMap.put("userName","張三");
        dataMap.put("imgData",arr[1]);

        Person person1 = new Person("李四", "男", 36, "18811240001");
        Person person2 = new Person("王五", "女", 22, "18811240002");
        Person person3 = new Person("趙六", "男", 46, "18811240003");
        List<Person> personList = new ArrayList<>();
        personList.add(person1);
        personList.add(person2);
        personList.add(person3);
        dataMap.put("personList",personList);

        //檔案生成路徑
        String wordFilePath = "E:\\ftl";
        //檔案生成名稱(因為是2003版本的xml模板,這裡使用.doc字尾,如果使用.docx字尾生成的檔案有問題)
        String wordFileName = "演示文件.doc";
        //模板路徑
        String templatePath = "E:\\ftl";
        //模板檔名稱
        String templateFileName = "demo.ftl";

        //生成word文件
        Boolean result = WordUtil.writeWordReport(wordFilePath, wordFileName, templatePath, templateFileName, dataMap);
        if(result){
            return "success";
        }else {
            return "error";
        }
    }
}

4、生成word文件的工具類方法

/**
     * 根據freemarker生成word文件並存到指定目錄
     * @param wordFilePath word檔案生成的目錄
     * @param wordFileName word檔名
     * @param templatePath 模板檔案所在的目錄
     * @param templateFileName 模板檔名
     * @param beanParams 生成word檔案所需要的模板資料
     * @return
     */
    public static Boolean writeWordReport(String wordFilePath,String wordFileName,
        String templatePath,String templateFileName, Map<String, Object> beanParams) {
        Configuration config = new Configuration(Configuration.getVersion());
        Writer out = null;
        try {
            config.setDirectoryForTemplateLoading(new File(templatePath));
            Template template = config.getTemplate(templateFileName, "UTF-8");

            //獲取檔案目錄,如果不存在則建立
            String filePath = "";
            int index = wordFilePath.lastIndexOf(File.separator);
            if(index != wordFilePath.length()-1){
                filePath = wordFilePath+ File.separator;
            }else {
                filePath = wordFilePath;
            }
            File file1 = new File(filePath);
            if(!file1.exists()){
                file1.mkdirs();
            }

            //輸出檔案
            File file = new File(filePath+wordFileName);
            FileOutputStream fos = new FileOutputStream(file);
            out = new OutputStreamWriter(fos, "UTF-8");
            template.process(beanParams, out);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }finally{
            try {
                if(out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

5、最終生成的word文件如下:

 

6、補充一:解決傳空值導致程式報空指標異常的問題

        如果生成文件時報如下錯誤:

        Tip: If the failing expression is known to be legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??

        這有可能是你在傳值過程中傳了空值,導致模板引擎無法獲取引數值,這時可以將表示式更改為${person.name?default('')}這種形式,這個表示式的意思是如果為空值就替換成'', 這樣即便不小心傳了空值也不會導致程式出錯。

7、補充二:解決匯出圖片缺失或圖片不完整問題

        實際開發中會遇到有一些圖形報表匯出圖片時不完整,比如:折線圖匯出時只有點卻沒有線條,這是由於折線圖有動畫,而生成的圖片是在動畫還未結束時就生成的。解決方法就是在配置項中關閉動畫效果。

8、補充三 :解決迴圈新增多張圖片重複問題

        按照上面的思路,既然可以迴圈新增資料,那肯定也可以迴圈新增圖片,但是當我們使用迴圈標籤新增圖片時,卻發現生成的多張圖片都是一樣的,這是由於我們沒有修改模板檔案中的圖片標籤的屬性導致的。需要修改的地方有兩個一個是:<w:binData></w:binData>標籤的w:name屬性,一個是:<v:imagedata></v:imagedata>標籤的src屬性。示例如下:

         上面這個屬性的含義指的是當前文件的第幾張圖片,如果我們的圖片集合資料從下標0開始則這裡的字尾要修改成"下標+1"的形式。虛擬碼示例如下:

<#list dataList as obj> <!--迴圈開始,obj_index代表當前迴圈物件的索引,從下標0開始-->
<!--虛擬碼-->
<w:binData w:name="${"wordml://0300000"+ obj_index+1 +".png"}" xml:space="preserve">${obj.imgUrl?default('')}</w:binData>
<v:imagedata src="${"wordml://0300000"+ obj_index+1 +".png"}" o:title=""/>
<#/list> <!--迴圈結束-->

         如果文件前面已經有一張圖片了,然後才開始迴圈圖片呢?那迴圈開始的圖片就是當前文件的第二張圖片,那字尾就要改成“下標+2”的形式,以此類推。示例如下:

參考: