JFinal使用技巧-Enjoy导出XLS

看见群里在说Enjoy除了能渲染页面还能干什么用?

看见“@毛斯特洛夫斯基”说:


你用的到模版的时候,就能体会到enjoy的优点
用不到的时候,不要手里有锤子就看谁都像是钉子

笑坏我了哈哈~ 嗯,我来分享一个小工具吧, 我自己这边一直在用的偏方。。。
上 石马 吧~

FileTemplateRender.java

package com.momathink.common.plugin.xls;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import com.jfinal.kit.PathKit;
import com.jfinal.kit.StrKit;
import com.jfinal.render.FileRender;
import com.jfinal.render.TemplateRender;
import com.jfinal.template.Engine;

/***
 * 文件模版 渲染后 输出 , 应用场景: 拼接XLS文件 , 在线代码生成下载 , 等...
 *
 * @author 杜福忠 2018-12-14 10:53:12 改进
 */
public class FileTemplateRender extends TemplateRender {

    protected static Engine MyEngine = null;

    /*
     * 自定义 Engine
     */
    public static void setEngine(Engine jfEngine) {
        if (jfEngine == null) {
            throw new IllegalArgumentException("engine can not be null");
        }
        MyEngine = jfEngine;
    }

    protected static String downloadPath = getDownloadPath();

    /*
     * 临时文件地址
     */
    protected static String getDownloadPath() {
        String path = PathKit.getWebRootPath() + "/download/FileTemplateRender";
        new File(path).mkdirs();
        return path;
    }

    protected String   downloadFileName = null;
    protected Callback callback         = null;

    /**
     * 渲染文件
     *
     * @param view
     * @param downloadFileName
     */
    public FileTemplateRender(String view, String downloadFileName) {
        super(view);
        this.downloadFileName = downloadFileName;
    }

    /**
     * 文件渲染下载后会回调 run(File file)方法
     */
    public static abstract class Callback {

        public abstract void run(File file);
    }

    /**
     * 渲染后下载完回调文件处理
     *
     * <pre>
     * new FileTemplateRender(view, downloadFileName,
     *         new FileTemplateRender.Callback() {
     *
     *             public void run(File file) {
     *                 // 处理 file
     *             }
     *         });
     * </pre>
     *
     * @param view
     * @param downloadFileName
     * @param callback
     *            回调函数
     */
    public FileTemplateRender(String view, String downloadFileName,
            Callback callback) {
        this(view, downloadFileName);
        this.callback = callback;
    }

    @Override
    public void render() {
        File file = renderFile();
        try {
            // 转FileRender进行下载文件
            FileRender fr = new FileRender(file, downloadFileName);
            fr.setContext(request, response);
            fr.render();
        } finally {
            // 下载完是否回调, 不回调就删除掉临时文件
            if (null == callback) {
                file.delete();
            } else {
                callback.run(file);
            }
        }
    }

    /**
     * 渲染模版文件
     *
     * @return 渲染后的文件
     */
    protected File renderFile() {
        Map<Object, Object> data = new HashMap<>();
        for (Enumeration<String> attrs = request.getAttributeNames(); attrs
                .hasMoreElements();) {
            String attrName = attrs.nextElement();
            data.put(attrName, request.getAttribute(attrName));
        }

        StringBuilder pathname = new StringBuilder(downloadPath).append("/")
                .append(StrKit.getRandomUUID()).append(downloadFileName);
        File file = new File(pathname.toString());

        getEngine().getTemplate(view).render(data, file);
        return file;
    }

    /**
     * 优先使用自定义Engine没有设置的时候使用全局的
     *
     * @return
     */
    protected Engine getEngine() {
        return null == MyEngine ? engine : MyEngine;
    }

    /***
     * 文件名称 自动加上时间戳
     */
    public FileTemplateRender setDate() {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm_");
        String dateStr = dateFormat.format(new Date());
        downloadFileName = dateStr.concat(downloadFileName);
        return this;
    }

}


控制器使用姿势:(貌似这个轮子用的较多的地方就是导出XLS文件了, 而且可以和其他的函数使用同一个方法,这个G点自己体会吧)

    /**
     * 导出
     * /weisite/wxcourse/order/toExcel
     */
    public void toExcel() {
        // 可设置默认值query
        setAttr( "recordList", srv.paginate(getLoginAccount(), getQueryParamExport()).getList());
        //注意文件后缀名 .xls 或者叫 .csv
        render(new FileTemplateRender("wxCouseOrderExcelList.html", "约课列表.xls").setDate());
        //调试的时候可以开启这个 注掉上面的,就可以实现预览了
        //render("wxCouseOrderExcelList.html");
    }
    
    /**
     * 单页应用列表数据
     * /weisite/wxcourse/order/oneListJson
     */
    public void oneListJson() {
        renderJsonPage(srv.paginate(getLoginAccount(), getQueryParam()));
    }

模版文件:   #(i18n.get('xxx')) 是我自己造的国际化数据库存储方式轮子这里就不说明了,只是表示这里是可以国际化的,和普通渲染是一样的。如:

.xls 这种适合复杂表头各种跨行跨列都可以, 使用在线表格HTML生成工具合成复杂的表头等信息,再把代码复制过来当模板: 
https://www.tablesgenerator.com/html_tables

<html>
<head>
<meta http-equiv=Content-Type content="text/html; charset=UTF-8">
<meta name=ProgId content=Excel.Sheet>
<meta name=Generator content="Microsoft Excel 15">
</head>
<body topmargin=0 leftmargin=0 >
<table border='1' cellspacing="0" cellpadding="0" >
    <thead>
        <tr>
            <th>序号</th>
            <th>学生</th>
            <th>班级</th>
            <th>#(i18n.get('课程'))</th>
            <th>#(i18n.get('教师'))</th>
            <th>#(i18n.get('教室'))</th>
            <th>#(i18n.get('预约时间'))</th>
            <th>#(i18n.get('状态'))</th>
            <th>#(i18n.get('请假备注'))</th>
            <th>#(i18n.get('签到情况'))</th>
            <th>#(i18n.get('签到时间'))</th>
        </tr>
    <thead>
    <tbody>
    #for(record : recordList)
        <tr>
            <td>#(for.count)</td>
            <td>#(record.userName)</td>
            <td>#(record.className)</td>
            <td>#(record.courseName)</td>
            <td>#(record.teacherName)</td>
            <td>#(record.classRoomName)</td>
            <td>#date(record.createTime, "yyyy-MM-dd HH:mm")</td>
            <td>#(record.type)</td>            
            <td>#(record.remark)</td>
            <td>#(record.sign)</td>
            <td>#date(record.signTime")</td>
        </tr>
    #end
</table>
</body>
</html>

这个不好的地方是第一次会提示 文件不安全, 选择是就可以了, 其他模版则不会

微信截图_20181214142404.png

.csv 这种模版比较简单了 (由@三圣乡吴彦祖@简单代码 反馈)
    序号,学生,班级,课程,教师,教室,预约时间,状态,请假备注,签到情况,签到时间
#for(record : recordList)
    #(for.count),#(record.userName),#(record.className),#(record.courseName),#(record.teacherName),#(record.classRoomName),#date(record.createTime, "yyyy-MM-dd HH:mm"),#(record.type),#(record.remark),#(record.sign),#date(record.signTime")
#end
.xxx 其他类型的。。。@社友们有其他想法的模版,可在下方评论处贴上 <pre class="brush:java;toolbar:false">你的代码</pre>

(可选项)
JFinalConfig 中使用同一个Engine

 @Override
    public void configEngine(Engine me) {
        //自定义 模版插件: 如XLS文件导出
        FileTemplateRender.setEngine(me);
        。。。
    }

很多时候只是想导出数据,而不是非得弄个真的XLS文件啊,
所以这个可比POI导出XLS爽多了~  
当然导出SQL 啊什么的,算了自己想吧~ 手里有锤子就好哈哈~
  点个赞呗~

评论区

JFinal

2018-01-17 21:22

又见新玩法, 看来手里有新锤子的时候,是得想想原来的东西可不可以变成钉子

lyh061619

2018-01-17 21:40

哈哈!不错哦。

macaque

2018-01-18 09:53

@杜福忠 有点没看明白, 这个是下载了个后缀名为xsl的 html文件?

杜福忠

2018-01-18 11:53

@macaque 是的, 只是下了个html后缀为 .xls 的文件,
这个html的格式是从Excel中导出来,套用的, 所以兼容性,还可以,都能打开。
因为很多时候只是想导出数据,而不是非得弄个真的XLS文件,所以这个比POI导出XLS方便些

macaque

2018-01-18 13:40

@杜福忠 好办法!这个好像重新保存一下 就会成为真的xls文件了

杜福忠

2018-01-18 13:55

@macaque 是的, 只要下载下来的时候用Excel打开(会有安全提示非常的不爽),再按保存的时候会提示这个文件是只读文件的,需要另存为,这个时候Excel就保存为真的xls文件了

三圣乡吴彦祖

2018-01-22 17:39

导出xls,用这种文本方式的话,为什么不直接输出csv格式呢?

简单代码

2018-01-23 09:33

这个方法输出的字符没有问题,但数字等其他格式就不好用了,还是导出CSV导入为xls比较好。

杜福忠

2018-01-23 09:47

@三圣乡吴彦祖 可以的,文件后缀名叫xx.csv就可以了, 这个分享只是做一个示例,可以导出任何以模版渲染的文本。

就xls与csv相比,做复杂的表头,是一件很麻烦的事情,而且Office是可以把表格文件另存为“.html”的, 这个时候,直接使用这个html当作模版文件,进行数据动态化就可以了。

当然实现这个功能的软件有很多,只是觉得这个学习成本低比较易用。

就我自己使用来说,在数据准备好的情况下,使用这个做导出不要10分钟就完事了,而POI怎么搞也得几个点了,而且做调试预览,这个模版本身就是html, 所以render("xxx.html");浏览器直接请求就能立马看见结果了

杜福忠

2018-01-23 16:16

.txt 类型的

#for(record : recordList)
----------#(for.count)START----------
学生=#(record.userName)
班级=#(record.className)
课程=#(record.courseName)
教师=#(record.teacherName)
教室=#(record.classRoomName)
预约时间=#date(record.createTime, "yyyy-MM-dd HH:mm")
状态=#(record.type)
请假备注=#(record.remark)
签到情况=#(record.sign)
签到时间=#date(record.signTime")
----------#(for.count)END----------
#end

Irin.Chan

2018-01-24 12:38

我现在代码生成,到处excel,word都是用enjoy .

杜福忠

2018-01-24 13:40

@Irin.Chan enjoy 谁用谁enjoy

凉凉凉凉凉

2018-04-16 16:33

师兄,导入导出工具磨得是真的亮(磨刀不误砍柴工),分分钟砍倒一片大森林呀。

2020-06-01 11:15

@Irin.Chan word的导出能分享下吗?我导出word一直有问题。

杜福忠

2020-06-01 13:53

@谢 你试试xdoc 小木老师推荐的 http://www.xdocin.com/xdoc.html

himans

2020-06-05 16:11

牛叉!