若依+FreeMarker实现培养方案教学计划表导出Word文件(超详细)

createh52个月前 (02-01)技术教程13

上篇文章分享了基于Vue3若依开源框架的专业培养方案教学计划表的前端实现,今天这篇文章分享一下如何将前端设计的教学计划表导出为Word文件。

先看看导出word的效果

而在前端,设计教学计划表的界面是这样的:

技术选型

Java实现word文件导出,可以有这些技术方案:Jacob,POI,Java2word,XML+FreeMarker,PageOffice插件,Docx4j,POI-tl等。经过分析,最终选择XML+FreeMarker的技术方案。

FreeMarker模板制作

现在word或wps软件中,根据导出的word文件格式要求设计模板,并设定好占位符,以便后续替换为数据变量。模板设计结果如下图所示。

设计好后将模板文件另存为*.xml格式,用Notepad++编辑器打开该文件,执行菜单【插件-->XML Tools-->Pretty print】,格式化xml文件,便于后续查找和增加freemarker语法。格式化后如下图所示。

将整个xml文件内容用下面的freemarker标签进行包裹,其作用是对最终导出的word文件进行压缩,也就是去掉代码的缩进和换行符,以达到减小文件体积的目的。

Bash
<@compress single_line=true>
......

是用Notepad++软件的查找功能,找到占位符“代码1”所在位置。

向上找到它的父节点,加上如下代码。其中,#list语法是循环列表的作用,相当于for循环;courseList是后端返回的课程列表变量;item是迭代变量名;#if语法是条件判断;item_index是当前的索引,格式是:迭代变量名_index。因为数据行的第一行(也就是第一门课程)的xml结构与其它行不同,所以这里进行了判断。

Bash
<#list courseList as item>
				<#if item_index==0>

接下来,在第一行内,把相应的占位符替换成freemarker变量。具体替换如下:

占位符

替换为

代码1

${item.courseCode}

课程1

<#if item.isCore=='1'>★

<#if item.isZcrh=='1'>*${item.courseNameZh} ${item.courseNameEn}

分1

${item.credits}

总1

${item.classesTotal}

讲1

${item.classesTheory}

实1

${item.classesPractice}(${item.classesExperiment})

11

<#if item.doTerm=='1'>${item.classesWeek}

12

<#if item.doTerm=='2'>${item.classesWeek}

13

<#if item.doTerm=='3'>${item.classesWeek}

14

<#if item.doTerm=='4'>${item.classesWeek}

15

<#if item.doTerm=='5'>${item.classesWeek}

16

<#if item.doTerm=='6'>${item.classesWeek}

17

<#if item.doTerm=='7'>${item.classesWeek}

18

<#if item.doTerm=='8'>${item.classesWeek}

在第一个数据行之后添加如下代码,结束第一行的判断和循环标签,开始第二行循环:

Bash
		

<#list courseList as item>
		<#if (item_index>0)>

第二行的循环体内部,占位符的替换与第一行的替换类似,不再赘述。

在第二个数据行之后添加如下代码,结束判断和循环标签:

Bash
	

第三个数据行是小计行,进行占位符替换如下:

分0

${courseSummary.credits}

总0

${courseSummary.classesTotal}

讲0

${courseSummary.classesTheory}

实0

${courseSummary.classesPractice}(${courseSummary.classesExperiment})

01

${courseSummary.classesWeek1}

02

${courseSummary.classesWeek2}

03

${courseSummary.classesWeek3}

04

${courseSummary.classesWeek4}

05

${courseSummary.classesWeek5}

06

${courseSummary.classesWeek6}

07

${courseSummary.classesWeek7}

08

${courseSummary.classesWeek8}

修改好后,将XML文件copy到项目的resource文件夹下的templates下,并重命名为“plan.ftl”。

?【service实现类】

Bash
package com.ruoyi.web.service.impl;

import com.ruoyi.web.controller.common.CommonController;
import com.ruoyi.web.utils.WordUtils;
import com.ruoyi.web.service.IWordExportService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

@Service
public class WordExportServiceImpl implements IWordExportService {
    private static final Logger log = LoggerFactory.getLogger(WordExportServiceImpl.class);
    @Override
    public void exportWord(HttpServletRequest request, HttpServletResponse response) {
        //测试列表数据
        List list = new ArrayList<>();

        //声明一个数据Map
        Map map1 = new HashMap();
        map1.put("courseCode","12345678");
        map1.put("courseNameZh","专业导论");
        map1.put("courseNameEn","Professional introduction");
        map1.put("credits","1");
        map1.put("classesTotal","16");
        map1.put("classesTheory","16");
        map1.put("classesExperiment","1");
        map1.put("classesPractice","2");
        map1.put("doTerm","1");
        map1.put("classesWeek","2");
        map1.put("isCore","0");
        map1.put("isZcrh","1");

        list.add(map1);

        Map map2 = new HashMap();
        map2.put("courseCode","22345678");
        map2.put("courseNameZh","高级语言程序设计(C)");
        map2.put("courseNameEn","High Level Language Programming (C)");
        map2.put("credits","4");
        map2.put("classesTotal","64");
        map2.put("classesTheory","44");
        map2.put("classesExperiment","20");
        map2.put("classesPractice","0");
        map2.put("doTerm","2");
        map2.put("classesWeek","3");
        map2.put("isCore","1");
        map2.put("isZcrh","1");
        list.add(map2);

        Map map3 = new HashMap();
        map3.put("courseCode","32345678");
        map3.put("courseNameZh","电路与模拟电子技术");
        map3.put("courseNameEn","Circuit and analog electronic technology");
        map3.put("credits","3.5");
        map3.put("classesTotal","56");
        map3.put("classesTheory","44");
        map3.put("classesExperiment","12");
        map3.put("classesPractice","0");
        map3.put("doTerm","3");
        map3.put("classesWeek","3");
        map3.put("isCore","1");
        map3.put("isZcrh","0");
        list.add(map3);

        Map map_sum = new HashMap();
        map_sum.put("credits", "8.5");
        map_sum.put("classesTotal", "136");
        map_sum.put("classesTheory", "104");
        map_sum.put("classesExperiment", "33");
        map_sum.put("classesPractice", "2");
        map_sum.put("classesWeek1", "2");
        map_sum.put("classesWeek2", "3");
        map_sum.put("classesWeek3", "3");
        map_sum.put("classesWeek4", "0");
        map_sum.put("classesWeek5", "0");
        map_sum.put("classesWeek6", "0");
        map_sum.put("classesWeek7", "0");
        map_sum.put("classesWeek8", "0");

        Map map = new HashMap();
        map.put("courseList",list);
        map.put("courseSummary", map_sum);

        try {
            //将数据导出
            WordUtils.exportMillCertificateWord(request,response,map,"导出word测试","plan.ftl");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

【word工具类】

参考自网友编写的工具类

Bash
package com.ruoyi.web.utils;

import freemarker.template.Configuration;
import freemarker.template.Template;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

/**
 * @ClassName: WordUtil
 * @Description: word导出工具类
 * @author ssqxx
 * @version 1.0
 * @date 2020-11-19
 */
public class WordUtils {
    private static Configuration configuration = null;
    private static final String templateFolder = WordUtils.class.getResource("/templates").getPath();


    static {
        configuration = new Configuration(Configuration.VERSION_2_3_31);
        configuration.setDefaultEncoding("utf-8");
        try {
            configuration.setDirectoryForTemplateLoading(new File(templateFolder));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private WordUtils() {
        throw new AssertionError();
    }

    /**
     * 导出word文件到浏览器
     * @param request
     * @param response
     * @param map 内容
     * @param title 标题
     * @param ftlFile 模板
     * @throws IOException
     */
    public static void exportMillCertificateWord(HttpServletRequest request, HttpServletResponse response, Map map, String title, String ftlFile) throws IOException {
        Template freemarkerTemplate = configuration.getTemplate(ftlFile);
        File file = null;
        InputStream fin = null;
        ServletOutputStream out = null;
        try {
            // 调用工具类的createDoc方法生成Word文档
            file = createDoc(map, freemarkerTemplate);
            fin = new FileInputStream(file);

            response.setCharacterEncoding("utf-8");
            response.setContentType("application/msword");
            //获取指定格式时间
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            // 设置浏览器以下载的方式处理该文件名
            String fileName = title + sdf.format(new Date()) + ".doc";
            //文件名称乱码解决
            String encodedFileName = fileName +";filename*=utf-8''"+ URLEncoder.encode(fileName,"UTF-8");
            response.setHeader("Content-Disposition", "attachment;filename="+encodedFileName+"");
            response.setContentType("application/vnd.ms-excel;charset=UTF-8");
            response.setHeader("Pragma", "no-cache");
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);

            out = response.getOutputStream();
            byte[] buffer = new byte[512];  // 缓冲区
            int bytesToRead = -1;
            // 通过循环将读入的Word文件的内容输出到浏览器中
            while ((bytesToRead = fin.read(buffer)) != -1) {
                out.write(buffer, 0, bytesToRead);
            }
        } finally {
            if (fin != null) fin.close();
            if (out != null) out.close();
            if (file != null) file.delete(); // 删除临时文件
        }
    }

    /**
     * 创建的doc
     * @param dataMap 数据内容
     * @param template 模板
     * @return
     */
    private static File createDoc(Map dataMap, Template template) {
        String name = "ssqxx.doc";
        File f = new File(name);
        Template t = template;
        try {
            // 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开
            Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
            t.process(dataMap, w);
            w.close();
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }
        return f;
    }
}

【service】

Bash
package com.ruoyi.web.service;

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

public interface IWordExportService {
    void exportWord(HttpServletRequest request, HttpServletResponse response);
}

【controller】

Bash
package com.ruoyi.web.controller.common;

import com.ruoyi.web.service.IWordExportService;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
@RequestMapping("exportWord")
@Api(tags = "Word导出控制器")
public class WordController {
    @Autowired
    private IWordExportService wordExportService;

    /**
     * 导出word方法
     * @param request
     * @param response
     */
    @PostMapping("export")
    public void exportWord(HttpServletRequest request, HttpServletResponse response){
        wordExportService.exportWord(request,response);
    }
}

【vue文件】

添加按钮,按钮的click事件处理函数如下。

function of handleExport() {

proxy.download("exportWord/export", {

...queryParams.value,

},`plan_${new Date().getTime()}.docx`);

};

相关文章

你知道Java的对象拷贝方式有哪几种吗?

【死记硬背】总共有四种,分别是直接赋值拷贝、浅拷贝、深拷贝和序列化。直接赋值拷贝:这个实际上复制的是对象的引用地址,如:Person p1 = p2,则p1和p2指向的是同一个对象的地址。因此,p1属...

Java的深拷贝与浅拷贝详解(java的深拷贝与浅拷贝详解图)

前言拷贝,顾名思义,就是复制一个一模一样的东西。那么放到对象上,也就是复制一个一模一样的对象了。Java中的数据类型分为基本数据类型和引用数据类型。对于这两种数据类型,在进行赋值操作、用作方法参数或返...

java对象深拷贝的三种实现方式,可避免循环引用导致的堆栈溢出

在java编程中,难免要对一个对象进行复制,复制分为深拷贝和浅拷贝。浅拷贝只复制对象本身,对于对象引用的其他对象不进行复制。 深拷贝则将对象与引用对象,全部进行拷贝。最简单的深拷贝就是自己new一个对...

Java中的深拷贝和浅拷贝的原理以及区别

深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是在Java编程中与对象复制有关的两个概念,表面上看二者都可以实现对象的复制,但是在复制的方式以及实现效果上却有着很大的区别,下面我们就...

④ JAVA IO—拷贝(java拷贝文件夹到另外一个文件夹)

一、 传统的IO1. 数据由磁盘拷贝到内核空间(DMA),再由内核空间拷贝到用户空间(JVM)2. 用户可能会对拷贝进来的数据进行操作3. 数据从用户空间拷贝到内核空间(JVM),再通过内核空间将数据...

几种实体拷贝方式实战(拷贝如何使用)

背景我们有这样一个场景,有一个StudentDto类,还有一个StudentVo类@Datapublic class StudentDto { private String id; private S...