JS导出word文件(Html to word)

image

本次使用插件:jquery.wordexport.js;

Dependencies: jQuery and FileSaver.js ;

遇到了需要将页面内容导出为word的需求,本以为是个简单的需求,却折腾了一天,主要难点在css样式和echarts图表的导出,wordexport.js插件的使用非常简单:

<script src="js/jquery-2.1.0.js" type="text/javascript" charset="utf-8"></script>
<script src="js/FileSaver.js"></script>
<script src="js/jquery.wordexport.js"></script>
<div id="content" class="content">
    导出内容
</div>
<script type="text/javascript">
    $("#content").wordExport("文件名");
</script>

这样就可以导出content 里的内容,但是你会发现页面上呈现的样式并没有展示在word里,这里需要改动wordexport.js的部分代码来实现样式表的导入,下面是源码(改动后):

if (typeof jQuery !== "undefined" && typeof saveAs !== "undefined") {
    (function($) {
        $.fn.wordExport = function(fileName,rule) {
            fileName = typeof fileName !== 'undefined' ? fileName : "jQuery-Word-Export";
            var static = {
                mhtml: {
                    top: "Mime-Version: 1.0\nContent-Base: " + location.href + "\nContent-Type: Multipart/related; boundary=\"NEXT.ITEM-BOUNDARY\";type=\"text/html\"\n\n--NEXT.ITEM-BOUNDARY\nContent-Type: text/html; charset=\"utf-8\"\nContent-Location: " + location.href + "\n\n<!DOCTYPE html>\n<html>\n_html_</html>",
                    head: "<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n<style>\n_styles_\n</style>\n</head>\n",
                    body: "<body>_body_</body>"
                }
            };
            var options = {
                maxWidth: 624
            };
            // Clone selected element before manipulating it
            var markup = $(this).clone();

            // Remove hidden elements from the output
            markup.each(function() {
                var self = $(this);
                if (self.is(':hidden'))
                    self.remove();
            });

            // Embed all images using Data URLs
            var images = Array();
            var img = markup.find('img');
            for (var i = 0; i < img.length; i++) {
                // Calculate dimensions of output image
                var w = Math.min(img[i].width, options.maxWidth);
                var h = img[i].height * (w / img[i].width);
                // Create canvas for converting image to data URL
                var canvas = document.createElement("CANVAS");
                canvas.width = w;
                canvas.height = h;
                // Draw image to canvas
                var context = canvas.getContext('2d');
                context.drawImage(img[i], 0, 0, w, h);
                // Get data URL encoding of image
                var uri = canvas.toDataURL("image/png");
                $(img[i]).attr("src", img[i].src);
                img[i].width = w;
                img[i].height = h;
                // Save encoded image to array
                images[i] = {
                    type: uri.substring(uri.indexOf(":") + 1, uri.indexOf(";")),
                    encoding: uri.substring(uri.indexOf(";") + 1, uri.indexOf(",")),
                    location: $(img[i]).attr("src"),
                    data: uri.substring(uri.indexOf(",") + 1)
                };
            }

            // Prepare bottom of mhtml file with image data
            var mhtmlBottom = "\n";
            for (var i = 0; i < images.length; i++) {
                mhtmlBottom += "--NEXT.ITEM-BOUNDARY\n";
                mhtmlBottom += "Content-Location: " + images[i].location + "\n";
                mhtmlBottom += "Content-Type: " + images[i].type + "\n";
                mhtmlBottom += "Content-Transfer-Encoding: " + images[i].encoding + "\n\n";
                mhtmlBottom += images[i].data + "\n\n";
            }
            mhtmlBottom += "--NEXT.ITEM-BOUNDARY--";

            //TODO: load css from included stylesheet
            //源码将styles置为"",这里我们只需改动成传入css,赋值给styles即可;
            var styles = rule || "";
            // Aggregate parts of the file together
            var fileContent = static.mhtml.top.replace("_html_", static.mhtml.head.replace("_styles_", styles) + static.mhtml.body.replace("_body_", markup.html())) + mhtmlBottom;
            // Create a Blob with the file contents
            var blob = new Blob([fileContent], {
                type: "application/msword;charset=utf-8"
            });
            saveAs(blob, fileName + ".doc");
        };
    })(jQuery);
} else {
    if (typeof jQuery === "undefined") {
        console.error("jQuery Word Export: missing dependency (jQuery)");
    }
    if (typeof saveAs === "undefined") {
        console.error("jQuery Word Export: missing dependency (FileSaver.js)");
    }
}

如文中注释部分所言:源码将styles置为"",这里我们只需改动成传入css,赋值给styles即可;在调用的时候先将html中使用的css拼接成一个字符串,再传入该插件中:

var rules = "",
    ss = document.styleSheets;
for (var i = 0; i < ss.length; ++i) {
    for (var x = 0; x < ss[i].cssRules.length; ++x) {
        rules += ss[i].cssRules[x].cssText;
    }
}
$("#content").wordExport(name, rules);

如此一来,你将发现:一部分css样式生效了,比如文字的宽高和颜色等,然而还有很多样式没有生效:如大部分css3选择器~;这里是因为word上支持的样式有限导致的;

这就非常难受了,最开始我的页面上的表格使用dhtmlx构建,导出后不仅样式完全没有,而且发现dhtmlxGrid的表头和表体是两个table,这样在word里非常不好调整,于是我更换了dataTables插件,问题依然是样式没有生效;

于是我想到一个办法:既然是因为css3选择器不支持导致的,那将dom的样式取出来,写入html标签的style里是否可以解析:

var node = $("#container")[0];
var treeWalker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT, null, false);
if(node && node.nodeType === 1) {
    var rule = getComputedStyle(node);
    $(node).attr("style", rule.cssText)
}
var node = treeWalker.nextNode();
while(node != null) {
    var rule = getComputedStyle(node);
    $(node).attr("style", rule.cssText)
    node = treeWalker.nextNode();
}

测试以后果然可以,但是若是页面结构复杂,这种方法会很耗时,而且因为rule.cssText 得到的样式字符串很长,生成的word体积就很庞大,

解决思路:

  • 可以只取出部分需要的样式信息,先列出页面所需的样式名数组,然后通过rule.样式名 的方式获取,这样可以去掉rule.cssText 中绝大部分的无用样式;
  • 可以只对需要的部分结构进行以上操作,比如我的页面中只有表格样式加载不出,那我只需对所有表格追加style 即可;

因为项目时间紧迫,我没有实践以上思路,而是直接在wordexport.js中写入了word可以解析的table样式:

var rules = "table{border-collapse:collapse;margin:0 auto;text-align:center;width: 100%;}table td,table th{border:1px solid #cad9ea;color:#666;height:30px}table thead th{background-color:#F1F1F1;min-width:400px}table tr{background:#fff}",
        ss = document.styleSheets;
for (var i = 0; i < ss.length; ++i) {
    for (var x = 0; x < ss[i].cssRules.length; ++x) {
            rules += ss[i].cssRules[x].cssText;
    }
}

关于echarts图表的导出:word的导出会忽略很多dom结构,echarts的canvas正是其中之一,于是在这里我们需要先使用curEchart.getConnectedDataURL()将图表转为base64的编码的图片,然后指定img标签的src为获取到的base64字符串,这样就可以顺利导出图片啦~,下面是我最终的代码:

var rules = "table{border-collapse:collapse;margin:0 auto;text-align:center;width: 100%;}table td,table th{border:1px solid #cad9ea;color:#666;height:30px}table thead th{background-color:#F1F1F1;min-width:400px}table tr{background:#fff}",
    ss = document.styleSheets;
for(var i = 0; i < ss.length; ++i) {
    for(var x = 0; x < ss[i].cssRules.length; ++x) {
        rules += ss[i].cssRules[x].cssText;
    }
}

//先clone来避免影响页面显示
var clone = $("#container").clone();
//找到所有echarts图表容器
var charts = clone.find(".chart");
//隐藏无需导出的dom
clone.find(".input_div").hide();
//简单控制流程
var flag = charts.length;
for(var i = 0; i < charts.length; i++) {
    //获取echarts对象
    var curEchart = echarts.getInstanceByDom(charts[i]);
    if(curEchart) {
        //将图表替换为图片
        var base = curEchart.getConnectedDataURL();
        var img = $('<img style="" src="' + base + '"/>');
        $(charts[i]).html(img);
        flag--;
    } else {
        flag--;
    }
}
var interval = setInterval(function() {
    if(!flag) {
        clearInterval(interval);
        //导出word
        clone.wordExport(name, rules);
    }
}, 200)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容