freemarker 導出word(表格,多列表,多圖片)(原創)


一、jar包支持

1、freemarker

freemarker-2.3.28.jar

2、poi

 

poi-3.9.jar
poi-examples-3.9.jar
poi-excelant-3.9.jar
poi-ooxml-3.9.jar
poi-ooxml-schemas-3.9.jar
poi-scratchpad-3.9.jar

ps:如果項目里poi版本為poi-4.1.0及以上,建議使用poi-tl-1.5.0

  附帶地址教程http://deepoove.com/poi-tl/

二、大概步驟

1、創建word—調整樣式—調整XML代碼—修改擴展名ftl,就可以使用了

我用的office版本是  office專業增強版 2016,不同版本可能有所不同。

三、創建office2007的重要步驟

wps沒試過,不清楚。就是討厭wps,沒有原因。

1、表格取值

1、先用office2007新建一個word文檔(一定要!!!),office是向下兼容的,2007及以下版本,無法打開freemarker導出后的2007以上版本的(意思是2007無法打開高版本)

2、新建好07.docx之后,用高版本或者當前版本打開。

  設定你需要的模板,先把內容都填充了,先調整好樣式,在刪掉內容后,寫好參數 ,后台是以map key的形式往模板里插入的${xxx},盡量不要有個空格。

  需要插入圖片的地方插入兩張...最好是不同的圖片,可以看出來區別(方便修改代碼,因為要看懂word的XML語言,也后面多個圖片循環),列表同樣。見下圖

 

3、表格完成之后,另保存為.xml文件,(是另存為,不是直接修改擴展名),選擇word 2003 XML !!!。

 

 

 

4、然后在修改為.ftl后綴名,就可以用Notpad++打開看代碼了,如果遇到  ${xxx}  這幾個占位符不在一起的情況,都把他們調整放在一起,轉xml的時候格式造成的

譬如這種:直接修改,紅圈的樣式可以直接刪掉,不影響。再看看其他的,有很多占位符有問題。

 

2、圖片循環

1、找到有一大串這種字符的地方(Base64字符串),word圖片轉換過的。直接刪掉就可以,我們是要后台傳值過來的。

2、然后修改這段代碼,循環圖片,自己縷縷,就能看出來,跟jsp列表循環差不多

如果用的2016版本,就需要循環三個地方。有指向性的問題,七標題里面會提到

 

方便使用,這里粘貼這部分代碼。

 

 <!-- 代表一行幾列的意思,由圖片寬度決定 -->
                                <w:pict>
                                    <v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f">
                                        <v:stroke joinstyle="miter"/>
                                        <v:formulas>
                                            <v:f eqn="if lineDrawn pixelLineWidth 0"/>
                                            <v:f eqn="sum @0 1 0"/>
                                            <v:f eqn="sum 0 0 @1"/>
                                            <v:f eqn="prod @2 1 2"/>
                                            <v:f eqn="prod @3 21600 pixelWidth"/>
                                            <v:f eqn="prod @3 21600 pixelHeight"/>
                                            <v:f eqn="sum @0 0 1"/>
                                            <v:f eqn="prod @6 1 2"/>
                                            <v:f eqn="prod @7 21600 pixelWidth"/>
                                            <v:f eqn="sum @8 21600 0"/>
                                            <v:f eqn="prod @7 21600 pixelHeight"/>
                                            <v:f eqn="sum @10 21600 0"/>
                                        </v:formulas>
                                        <v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"/>
                                        <o:lock v:ext="edit" aspectratio="t"/>
                                    </v:shapetype>
                                    <!-- office2007 圖片循環-->
                                    <#list image as item >
                                        <w:binData w:name="wordml://${item.image_name}" xml:space="preserve">${item.image_base64}</w:binData>
                                    <v:shape id="_x0000_i1027" type="#_x0000_t75" style="width:255pt;height:255pt">
                                        <v:imagedata src="wordml://${item.image_name}" o:title="photo"/>
                                        <o:lock v:ext="edit" aspectratio="f"/>
                                    </v:shape>
                                    </#list>

                                </w:pict>

 

 

3、列表循環

 1、列表循環同理,找到大致的列表,寫個list循環就可以,這里只截圖我的結果了

好了,這部分完事了

 

 

四、jsp、js代碼

  

<a class="layui-btn" onclick="exportWord(); return false;">導出word文檔</a>

function exportWord() {
    var $form=$('<form action="'+ sys_ctx +'/EventWord/default.do">'+'<input type="hidden" name="method" value="exportWord"/></form>');
    $form.append('<input type="hidden" name="guid" value="' + guid+ '" />');
    $form.append('<input type="hidden" name="source_type_id" value="' + processInstanceId+ '" />');
    $form.appendTo($("body")).submit().remove();
}

五、java代碼

1、EventWordController

 

package sdcncsi.ict.customized.event.EventWord;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import sdcncsi.ict.flow.WorkFlowService;
import sdcncsi.ict.util.RequestUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Transactional
@Controller
@RequestMapping("/EventWord/default.do")
public class EventWordController {
    @Autowired
    private WorkFlowService workFlowService;


    // 導出word
    @RequestMapping(params = "method=exportWord")
    public void exportWord(HttpServletRequest request, HttpServletResponse response) {
        try {
            EventWord eventWord = new EventWord(RequestUtil.getMap(request));
            eventWord.exportWord(request, response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

 

2、EventWord

package sdcncsi.ict.customized.event.EventWord;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import sdcncsi.ict.customized.event.common.EventFlow;
import sdcncsi.ict.util.Base64Util;
import sdcncsi.ict.util.StringUtil;
import sdcncsi.ict.util.ZhsqBaseDao;
import sdcncsi.ict.util.cache.CacheUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class EventWord extends ZhsqBaseDao {

    public EventWord(Map mapIn) {
        super(mapIn);
    }



    private static String _PATH="/customized/event/EventDoc/2007template/";//模板路徑
    private static String _PATH_NAME="event.ftl";//模板路徑名稱
    private static String _NAME="事件詳細信息表.doc";//導出后的文件名稱


    public void exportWord(HttpServletRequest request, HttpServletResponse response) throws IOException, TemplateException {

//        // 修改導出模板路徑
        String exportTemplatePath = request.getSession().getServletContext().getRealPath(_PATH);
//
//        FileUtil fileUtil = new FileUtil();
//        // 上傳路徑
//        String uploadTempPath = CacheUtil.getParamValue("uploadTempPath");
//        // 插入數據后的文件路徑
//        String dataFilePath = _TYPE + File.separator + DateUtil.getCurrentTime("yyyyMMdd") + File.separator;
//        fileUtil.Createdir(uploadTempPath + dataFilePath);
//        // 把模板拷貝到臨時目錄
//        fileUtil.copyFile("222.ftl", exportTemplatePath + File.separator, uploadTempPath + dataFilePath, _PATH_NAME);

//        String WordPath = uploadTempPath + dataFilePath + File.separator + downloadName;
//
        //第一步:創建一個Configuration對象,直接new一個對象。構造方法的參數就是freemarker對於的版本號。
        Configuration configuration = new Configuration();
        // 第二步:設置模板文件所在的路徑。
        configuration.setDirectoryForTemplateLoading( new File(exportTemplatePath));
        //第三步:設置模板文件使用的字符集。一般就是utf-8.
        configuration.setDefaultEncoding("utf-8");
        // 第四步:加載一個模板,創建一個模板對象。
        Template template = configuration.getTemplate(_PATH_NAME);
        // 第五步:創建一個模板使用的數據集,可以是pojo也可以是map。一般是Map。
        Map dataModel = new HashMap();


        // 第六步 定義向數據集中添加數據

        //--------------從這里開始取所需要的數據 start
        EventFlow eventflow = new EventFlow(map);
        Map EventMap =eventflow.getEventDetail();//這是整個返回值
        eventflow.getAttachmentByEventGuid();//這是圖片信息
        Map<String ,Object> detailMap = (Map) EventMap.get("detail");//從返回值里面取出詳情數據
        List <Map<String ,Object>> photoList = (List<Map<String, Object>>) EventMap.get("fjurl");//圖片信息是多個,所以用list
        map.put("processInstanceId",map.get("source_type_id"));
        List<Map<String, Object>> historyTasks = (List<Map<String, Object>>) eventflow.getHistoricTask().get("historyTasks");



        //開始循環取出多個圖片 start
        List<Map<String, Object>> imageList=new ArrayList();
        String ftpIP = CacheUtil.getParamValue("ftpaddress");//緩存里取出ftp地址
        String port = CacheUtil.getParamValue("ftpserverport");//緩存里取出端口

        //下面有解釋
        int j = 0;
        //需要循環word中的  Relationships標簽,Id=  自己定義從rId10開始
        String relationship = "rId";
        int relationship_id = 9;//
        for (int i = 0; i <photoList.size() ; i++) {
            j++;
            relationship_id++;
            Map<String, Object> _map=new HashMap();
            Map map1 = photoList.get(i);
            String imgUrl = "http://"+ftpIP+":"+port+StringUtil.getStringValue(map1.get("remotepath"));
            //獲取圖片類型
            String type = StringUtil.getStringValue(map1.get("type"));
            //截取圖片類型 jpeg
            type = type.substring(type.indexOf("/")+1);
            //獲取圖片名稱,不加擴展名 word里面是image1 image2  ...依次遞歸
            String filename = "image"+j;
            String imageBase64 = Base64Util.ImageUrlBase64(imgUrl);

            _map.put("image_name",filename+"."+type);
            _map.put("image_base64",imageBase64);
            _map.put("image_type",StringUtil.getStringValue(map1.get("type")));
            _map.put("relationship_id",relationship+relationship_id);
            imageList.add(_map);
        }
        //開始循環取出多個圖片 end


        //開始循環流程 start
        List<Map<String, Object>> list=new ArrayList();
        for (int i = 0; i < historyTasks.size(); i++) {
            Map<String, Object> _map=new HashMap();
            Map map1 = historyTasks.get(i);
            String cur_organizationname  = StringUtil.getStringValue(map1.get("cur_organizationname"));
            String cur_opername  =StringUtil.getStringValue(map1.get("cur_opername"));
            if(StringUtil.isNull(cur_organizationname)){
                cur_organizationname  = "網格員";
            }
            if(StringUtil.isNull(cur_opername)){
                cur_opername  = cur_organizationname;
            }
            //階段
            String jied = "由【"+cur_organizationname+":"+cur_opername+"】"+StringUtil.getStringValue(map1.get("dictname"));
                    if(!"".equals(StringUtil.getStringValue(map1.get("to_organizationname")))){
                        jied+= "給【"+StringUtil.getStringValue(map1.get("to_organizationname"))+"】";
                    }

            //意見
            String yj = StringUtil.getStringValue(map1.get("content"));
            //操作時間
            String date = StringUtil.getStringValue(map1.get("createtime"));

            _map.put("jd",jied);
            _map.put("yj",yj);
            _map.put("date",date);

            list.add(_map);

        }
        //結束循環流程  end

        //--------------從這里開始取所需要的數據 end



        //開始放入數據  map就可以

        /**
         *  例如: .ftl里面取的方式
         * <#list image as item >
         *      <v:shape id="_x0000_i1025" type="#_x0000_t75" style="width:250pt;height:250pt">
         *      <v:imagedata r:id="${item.relationship_id}" o:title="photo"/>
         *      </v:shape>
         * </#list>
         *
         *
         */
        dataModel.put("guid",detailMap.get("guid"));//事件編號
        dataModel.put("source_type",detailMap.get("source_type"));//問題來源
        dataModel.put("createdate",detailMap.get("createdate"));//發現時間
        dataModel.put("createopername",detailMap.get("createopername"));//上報人
        dataModel.put("phonenumber",detailMap.get("phonenumber"));//聯系電話
        dataModel.put("type_1",detailMap.get("type_1"));//事件類型
        dataModel.put("address",detailMap.get("address"));//坐標
        dataModel.put("position",detailMap.get("position"));//發生地址
        dataModel.put("description",detailMap.get("description"));//案件描述
        dataModel.put("orgname",detailMap.get("orgname"));//所屬網格
        dataModel.put("historyTasks",list);//流程
        dataModel.put("image",imageList);//多個圖片


        // 設置下載文檔名稱
        String fileName = _NAME;
        fileName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
        response.setHeader("Content-Disposition", "attachment;filename="+ fileName);
        Writer out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(),"utf-8"));

        // 第七步:調用模板對象的process方法輸出文件。
        template.process(dataModel, out);
        // 第八步:關閉流。
        out.close();

    }

}

3、Base64Util

package sdcncsi.ict.util;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.imageio.stream.FileImageInputStream;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

/**
 * @Description 圖片字符串轉換
 * @Author wangxa
 * @Date 2019-08-20 10:16
 */
public class Base64Util{
    /**
     * 字符串轉圖片
     * @param base64Str
     * @return
     */
    public static byte[] decode(String base64Str){
        byte[] b = null;
        BASE64Decoder decoder = new BASE64Decoder();
        try {
            b = decoder.decodeBuffer(replaceEnter(base64Str));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return b;
    }

    /**
     * 圖片轉字符串
     * @param image
     * @return
     */
    public static String encode(byte[] image){
        BASE64Encoder decoder = new BASE64Encoder();
        return replaceEnter(decoder.encode(image));
    }

    public static String encode(String uri){
        BASE64Encoder encoder = new BASE64Encoder();
        return replaceEnter(encoder.encode(uri.getBytes()));
    }

    /**
     *
     * @path    圖片路徑
     * @return
     */

    public static byte[] imageTobyte(String path){
        byte[] data = null;
        FileImageInputStream input = null;
        try {
            input = new FileImageInputStream(new File(path));
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            byte[] buf = new byte[1024];
            int numBytesRead = 0;
            while((numBytesRead = input.read(buf)) != -1){
                output.write(buf, 0, numBytesRead);
            }
            data = output.toByteArray();
            output.close();
            input.close();

        } catch (Exception e) {
            e.printStackTrace();
        }

        return data;
    }



    public static String replaceEnter(String str){
        String reg ="[\n-\r]";
        Pattern p = Pattern.compile(reg);
        Matcher m = p.matcher(str);
        return m.replaceAll("");
    }

    /**
     * 遠程讀取image轉換為Base64字符串
     * @param imgUrl =圖片地址全路徑
     * @return
     */
    public static String ImageUrlBase64(String imgUrl) {
        URL url = null;
        InputStream is = null;
        ByteArrayOutputStream outStream = null;
        HttpURLConnection httpUrl = null;
        try{
            url = new URL(imgUrl);
            httpUrl = (HttpURLConnection) url.openConnection();
            httpUrl.connect();
            httpUrl.getInputStream();
            is = httpUrl.getInputStream();

            outStream = new ByteArrayOutputStream();
            //創建一個Buffer字符串
            byte[] buffer = new byte[1024];
            //每次讀取的字符串長度,如果為-1,代表全部讀取完畢
            int len = 0;
            //使用一個輸入流從buffer里把數據讀取出來
            while( (len=is.read(buffer)) != -1 ){
                //用輸出流往buffer里寫入數據,中間參數代表從哪個位置開始讀,len代表讀取的長度
                outStream.write(buffer, 0, len);
            }
            // 對字節數組Base64編碼
            return new BASE64Encoder().encode(outStream.toByteArray());
        }catch (Exception e) {
            e.printStackTrace();
        }
        finally{
            if(is != null)
            {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(outStream != null)
            {
                try {
                    outStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(httpUrl != null)
            {
                httpUrl.disconnect();
            }
        }
        return imgUrl;
    }

    /**
     * @Description: 獲取圖片對應的base64碼(以文件的形式)
     * @Author: wangxa
     * @throws IOException
     * @Date: 18:25 2019/8/19
     */
    public static String getImageBase64String(File imgFile) throws IOException {
        InputStream inputStream = new FileInputStream(imgFile);
        byte[] data = new byte[inputStream.available()];
        int totalNumberBytes = inputStream.read(data);
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(data);
    }


}

好了,這就是所有的步驟了。希望需要的人少走彎路。

 

 

六、效果圖展示

 

 

七、附帶office2016的XML

(記錄自己走過的坑o(╥﹏╥)o)  需要修改三個地方!!!圖片才能展現,也就是為什么要用07版本的office創建了。

<!-- 大概是圖片一行兩列的意思 起 -->
                                <w:p w:rsidR="00914388" w:rsidRPr="00786262" w:rsidRDefault="00D04BAC">
                                    <w:pPr>
                                        <w:rPr>
                                            <w:rFonts w:ascii="仿宋" w:eastAsia="仿宋" w:hAnsi="仿宋"/>
                                            <w:sz w:val="28"/>
                                            <w:szCs w:val="28"/>
                                        </w:rPr>
                                    </w:pPr>
                                    <w:r>
                                        <w:rPr>
                                            <w:rFonts w:ascii="仿宋" w:eastAsia="仿宋" w:hAnsi="仿宋"/>
                                            <w:sz w:val="28"/>
                                            <w:szCs w:val="28"/>
                                        </w:rPr>
                                        <w:pict>
                                            <v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f">
                                                <v:stroke joinstyle="miter"/>
                                                <v:formulas>
                                                    <v:f eqn="if lineDrawn pixelLineWidth 0"/>
                                                    <v:f eqn="sum @0 1 0"/>
                                                    <v:f eqn="sum 0 0 @1"/>
                                                    <v:f eqn="prod @2 1 2"/>
                                                    <v:f eqn="prod @3 21600 pixelWidth"/>
                                                    <v:f eqn="prod @3 21600 pixelHeight"/>
                                                    <v:f eqn="sum @0 0 1"/>
                                                    <v:f eqn="prod @6 1 2"/>
                                                    <v:f eqn="prod @7 21600 pixelWidth"/>
                                                    <v:f eqn="sum @8 21600 0"/>
                                                    <v:f eqn="prod @7 21600 pixelHeight"/>
                                                    <v:f eqn="sum @10 21600 0"/>
                                                </v:formulas>
                                                <v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"/>
                                                <o:lock v:ext="edit" aspectratio="t"/>
                                            </v:shapetype>
                                            <!-- 循環這個圖片映射所在位置(自己理解的) <v:shape id="_x0000_i1025"  這個的id好像不用管。 -->
                                            <#list image as item >
                                                <v:shape id="_x0000_i1025" type="#_x0000_t75" style="width:250pt;height:250pt">
                                                    <v:imagedata r:id="${item.relationship_id}" o:title="photo"/>
                                                </v:shape>
                                            </#list>

                                        </w:pict>
                                    </w:r>
                                </w:p>
                                <!-- 大概是圖片一行兩列的意思 止 -->
                            </w:tc>

---------------------------------------------------------


    <pkg:part pkg:name="/word/_rels/document.xml.rels" pkg:contentType="application/vnd.openxmlformats-package.relationships+xml" pkg:padding="256">
        <pkg:xmlData>
            <Relationships
                    xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
                <Relationship Id="rId8" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>
                <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings" Target="webSettings.xml"/>
                <Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/>
                <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/>
                <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
                <Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes" Target="endnotes.xml"/>
                <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes" Target="footnotes.xml"/>

                <!-- 要循環這個,每張圖片一個Id, 根據上面的Id,我們定義從 rId10開始作為循環圖片-->
                <#list image as item>
                    <Relationship Id="${item.relationship_id}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/${item.image_name}"/>
                </#list>

            </Relationships>
        </pkg:xmlData>
    </pkg:part>

------------------------------------------------------------------------------------------------


    <!-- 圖片開始 -->
    <#list image as item >
        <pkg:part pkg:name="/word/media/${item.image_name}" pkg:contentType="${item.image_type}" pkg:compression="store">
            <pkg:binaryData>${item.image_base64}</pkg:binaryData>
        </pkg:part>

    </#list>


    <!-- 圖片結束 -->

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM