继导出pdf之后,需求又来了个导出word的需求(你开心就好)
于是在无数次尝试后,总结用到的两个word的方案
word导出比较坑的点是图片的导出,一般的库进行导出必须要把图片转成base64才能导出,所以如果dom元素中存在图片,要进行图片转base64的处理,而涉及到图片转base64(甚至是svg转base64),还有必须提到一点就是blob的存储操作,当然图方便你可以装一个flieSaver就不用自己写原生blob保存了
图片转base64
//获取div下的img标签转base64
const url = 'https://img.haomeiwen.com/i14626058/37ab230cd97ccf87.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp'
const imgSection = document.querySelector('#test-img')
// 通过构造函数来创建的 img 实例,在赋予 src 值后就会立刻下载图片,相比 createElement() 创建 <img> 省去了 append(),也就避免了文档冗余和污染
let Img = new Image();
Img.setAttribute('crossOrigin', 'Anonymous') // 配置图片跨越
Img.src = url;
let dataURL = '';
Img.onload = () => {
let canvas = document.createElement('canvas'); // 创建canvas元素
let width = Img.width; // 确保canvas的尺寸和图片一样
let height = Img.height;
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(Img, 0, 0, width, height); // 将图片绘制到canvas中
dataURL = canvas.toDataURL('image/jpeg'); // 转换图片为dataURL
console.log(dataURL)
imgSection.children[0].setAttribute('src',dataURL)
};
html2canvas或者domtoimage
通过html2canvas.js将dom元素"截图",在函数参数的canvas调用canvas.toDataURL方法,可以直接转base64,然后再配合word导出
// import html2canvas from 'html2canvas'
import domtoimage from 'dom-to-image'
// 将dom元素下的img的src转为base64
const convertImagesToBase64 = (contentDocument, imgWidth, imgHeight) => {
if (typeof contentDocument === 'string') {
contentDocument = document.querySelector(contentDocument)
}
var regularImages = contentDocument.querySelectorAll('img')
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d');
[].forEach.call(regularImages, function(imgElement) {
imgElement.width = imgWidth || 100
imgElement.height = imgHeight || 140
// preparing canvas for drawing
ctx.clearRect(0, 0, canvas.width, canvas.height)
const img = new Image() // 创建新的图片对象
img.src = imgElement.src
img.setAttribute('crossOrigin', 'Anonymous') // 配置图片跨越
img.onload = function() { // 图片加载完,再draw 和 toDataURL
ctx.drawImage(img, 0, 0, imgElement.width, imgElement.height)
const base64 = canvas.toDataURL('image/png')
imgElement.setAttribute('src', base64)
}
})
canvas.remove()
}
// 使用domtoimage将html转为base64的图片
const convertHtmlToBase64 = (contentDocument, callBack) => {
if (typeof contentDocument === 'string') {
contentDocument = document.querySelector(contentDocument)
}
domtoimage.toPng(contentDocument).then(function(dataUrl) {
callBack(dataUrl)
})
}
// 使用事例
convertHtmlToBase64('#example', function(imageBase64) {
const img = new Image()
img.src = imageBase64
//dom.appendChild(img)
})
export {
convertImagesToBase64,
convertHtmlToBase64
}
一行琉璃
1.html-docx-js
这是一个叫html-docx-js的库,能够将指定dom元素(最好是完整的html,包括 DOCTYPE,html 和 body 标记)进行word导出,注意需要把图片转成base64,而且dom的样式必须写在style里面,导出时才能检测到。
库地址:https://github.com/evidenceprime/html-docx-js,用于参考html-docx-js的页面调整参数
//封装了一个exportDocx方法
// 注意:file-saver 依赖 Blob 对象
import { saveAs } from 'file-saver'
// htmlDocx 的作用就是将html字符串转成Blob对象
import htmlDocx from 'html-docx-js/dist/html-docx'
// 测试函数
export const exportDocxTest = (domString, fileName) => {
var converted = htmlDocx.asBlob(domString)
// var blob = new Blob(['Hello, world!'], { type: 'text/plain;charset=utf-8' })
// saveAs(blob, 'test.docx')
saveAs(converted, fileName + '.docx')
}
/**
* 1.导出局部的html页面
* @param {} dom 局部的html页面
* @param {*} fileName 导出文件的名称
* @param {*} title
*/
export const exportDocx = (dom, fileName, config, { title = document.title, width } = {}) => {
if (!dom) return
config = config || {}
let copyDom = document.createElement('span')
// const styleDom = document.querySelectorAll('style, link, meta')
const titleDom = document.createElement('title')
titleDom.innerText = title
copyDom.appendChild(titleDom)
// Array.from(styleDom).forEach(item => {
// copyDom.appendChild(item.cloneNode(true))
// })
const cloneDom = dom.cloneNode(true)
if (width) {
const domTables = cloneDom.getElementsByTagName('table')
if (domTables.length) {
for (const table of domTables) {
table.style.width = width + 'px'
}
}
}
copyDom.appendChild(cloneDom)
const htmlTemp = copyDom.innerHTML
copyDom = null
// console.log('htmlTemp=', htmlTemp)
const iframeDom = document.createElement('iframe')
const attrObj = {
height: 0,
width: 0,
border: 0,
wmode: 'Opaque'
}
const styleObj = {
position: 'absolute',
top: '-999px',
left: '-999px'
}
Object.entries(attrObj).forEach(([key, value]) => {
iframeDom.setAttribute(key, value)
})
Object.entries(styleObj).forEach(([key, value]) => {
iframeDom.style[key] = value
})
document.body.insertBefore(iframeDom, document.body.children[0])
const iframeWin = iframeDom.contentWindow // 1.获取iframe中的window
const iframeDocs = iframeWin.document // 2.获取iframe中的document
iframeDocs.write(`<!doctype html>`)
iframeDocs.write(htmlTemp)
const htmlDoc = `
<!DOCTYPE html>
<html lang="en">
${iframeDocs.documentElement.innerHTML}
</html>
`
var converted = htmlDocx.asBlob(htmlDoc, config)
saveAs(converted, fileName + '.docx')
document.body.removeChild(iframeDom)
}
// 使用事例
exportDocx(document.querySelector('#excep-detail-p-basic-info'),
'服刑人员个人资料-' + getDateTimeStr(),
{
// orientation: 'landscape', margins: { top: 150 }
margins: { top: 150 }
}
)
一行琉璃
2.jquery.wordexport
Jquery,永远滴神。如果你考虑兼容的问题(不单止浏览器的兼容,还涉及到word版本的兼容),那首选当然是jquery.wordexport.js,同样样式需要style写入,而且dom需要转成jquery对象,使用的时候$("#example").wordExport()方法就行
结构
jquerys.js(JQ引入)
vue项目可以配置webpack的plugin全局引入jq,也可以单独定义jquerys.js文件
// 两种方法选一种
// 1.新建js文件
import $ from "jquery";
window.$ = $;
window.jQuery = $;
export default $;
// 2.webpack全局引入
const isProduction = process.env.NODE_ENV === 'production'
module.exports = {
...
configureWebpack: (config) => {
if (isProduction) {
// 1.排除哪些库不需要打包 import Vue from 'vue'
// 用cdn方式引入
config.externals = {
// 'vue': 'Vue', // key 是 require 的包名,value 是全局的变量
// 'vuex': 'Vuex',
'jQuery': 'jQuery'
}
// 2.减少 vendor-jsxxxx.js的大小
config.optimization = {
splitChunks: {
cacheGroups: {
commons: {
name: 'commons',
chunks: 'initial',
minChunks: 2
},
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
minSize: 540000,
maxSize: 840000
// priority: 4
}
}
}
}
// 开发环境
} else {
// 1.直接引入第三方法的框架
config.externals = {
'jQuery': 'jQuery'
}
}
}
}
jquery.wordexport.js
import { saveAs } from "file-saver";
if (typeof jQuery !== "undefined" && typeof saveAs !== "undefined") {
(function ($) {
$.fn.wordExport = function (fileName) {
fileName = typeof fileName !== 'undefined' ? fileName : "jQuery-Word-Export";
var statics = {
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 xmlns:v=\'urn:schemas-microsoft-com:vml\' xmlns:o=\'urn:schemas-microsoft-com:office:office\' xmlns:w=\'urn:schemas-microsoft-com:office:word\' xmlns:m=\'http://schemas.microsoft.com/office/2004/12/omml\' xmlns=\'http://www.w3.org/TR/REC-html40\'>\n<html xmlns:v=\'urn:schemas-microsoft-com:vml\' xmlns:o=\'urn:schemas-microsoft-com:office:office\' xmlns:w=\'urn:schemas-microsoft-com:office:word\' xmlns:m=\'http://schemas.microsoft.com/office/2004/12/omml\' xmlns=\'http://www.w3.org/TR/REC-html40\'>\n_html_</html>",
head: "<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n<style>\n_styles_\n</style>\n<!--[if gte mso 9]><xml><w:WordDocument><w:View>Print</w:View><w:TrackMoves>false</w:TrackMoves><w:TrackFormatting/><w:ValidateAgainstSchemas/><w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid><w:IgnoreMixedContent>false</w:IgnoreMixedContent><w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText><w:DoNotPromoteQF/><w:LidThemeOther>EN-US</w:LidThemeOther><w:LidThemeAsian>ZH-CN</w:LidThemeAsian><w:LidThemeComplexScript>X-NONE</w:LidThemeComplexScript><w:Compatibility><w:BreakWrappedTables/><w:SnapToGridInCell/><w:WrapTextWithPunct/><w:UseAsianBreakRules/><w:DontGrowAutofit/><w:SplitPgBreakAndParaMark/><w:DontVertAlignCellWithSp/><w:DontBreakConstrainedForcedTables/><w:DontVertAlignInTxbx/><w:Word11KerningPairs/><w:CachedColBalance/><w:UseFELayout/></w:Compatibility><w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel><m:mathPr><m:mathFont m:val=\'Cambria Math\'/><m:brkBin m:val=\'before\'/><m:brkBinSub m:val=\'--\'/><m:smallFrac m:val=\'off\'/><m:dispDef/><m:lMargin m:val=\'0\'/> <m:rMargin m:val=\'0\'/><m:defJc m:val=\'centerGroup\'/><m:wrapIndent m:val=\'1440\'/><m:intLim m:val=\'subSup\'/><m:naryLim m:val=\'undOvr\'/></m:mathPr></w:WordDocument></xml><![endif]-->\n<style>.ql-align-center{text-align:center;}</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/jpg");
$(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
//var styles=' /* Font Definitions */@font-face{font-family:宋体;panose-1:2 1 6 0 3 1 1 1 1 1;mso-font-alt:SimSun;mso-font-charset:134;mso-generic-font-family:auto;mso-font-pitch:variable;mso-font-signature:3 680460288 22 0 262145 0;} @font-face{font-family:"Cambria Math";panose-1:2 4 5 3 5 4 6 3 2 4;mso-font-charset:1;mso-generic-font-family:roman;mso-font-format:other;mso-font-pitch:variable;mso-font-signature:0 0 0 0 0 0;} @font-face{font-family:"\@宋体";panose-1:2 1 6 0 3 1 1 1 1 1;mso-font-charset:134;mso-generic-font-family:auto;mso-font-pitch:variable;mso-font-signature:3 680460288 22 0 262145 0;}/* Style Definitions */p.MsoNormal, li.MsoNormal, div.MsoNormal{mso-style-unhide:no;mso-style-qformat:yes;mso-style-parent:"";margin:0cm;margin-bottom:.0001pt;mso-pagination:widow-orphan;font-size:14.0pt;font-family:宋体;mso-bidi-font-family:宋体;}p.MsoHeader, li.MsoHeader, div.MsoHeader{mso-style-noshow:yes;mso-style-priority:99;mso-style-link:"页眉 Char";margin:0cm;margin-bottom:.0001pt;text-align:center;mso-pagination:widow-orphan;layout-grid-mode:char;font-size:9.0pt;font-family:宋体;mso-bidi-font-family:宋体;}p.MsoFooter, li.MsoFooter, div.MsoFooter{mso-style-noshow:yes;mso-style-priority:99;mso-style-link:"页脚 Char";margin:0cm;margin-bottom:.0001pt;mso-pagination:widow-orphan;layout-grid-mode:char;font-size:9.0pt;font-family:宋体;mso-bidi-font-family:宋体;}p.MsoAcetate, li.MsoAcetate, div.MsoAcetate{mso-style-noshow:yes;mso-style-priority:99;mso-style-link:"批注框文本 Char";margin:0cm;margin-bottom:.0001pt;mso-pagination:widow-orphan;font-size:9.0pt;font-family:宋体;mso-bidi-font-family:宋体;}span.Char{mso-style-name:"页眉 Char";mso-style-noshow:yes;mso-style-priority:99;mso-style-unhide:no;mso-style-locked:yes;mso-style-link:页眉;font-family:宋体;mso-ascii-font-family:宋体;mso-fareast-font-family:宋体;mso-hansi-font-family:宋体;}span.Char0{mso-style-name:"页脚 Char";mso-style-noshow:yes;mso-style-priority:99;mso-style-unhide:no;mso-style-locked:yes;mso-style-link:页脚;font-family:宋体;mso-ascii-font-family:宋体;mso-fareast-font-family:宋体;mso-hansi-font-family:宋体;}span.Char1{mso-style-name:"批注框文本 Char";mso-style-noshow:yes;mso-style-priority:99;mso-style-unhide:no;mso-style-locked:yes;mso-style-link:批注框文本;font-family:宋体;mso-ascii-font-family:宋体;mso-fareast-font-family:宋体;mso-hansi-font-family:宋体;}p.msochpdefault, li.msochpdefault, div.msochpdefault{mso-style-name:msochpdefault;mso-style-unhide:no;mso-margin-top-alt:auto;margin-right:0cm;mso-margin-bottom-alt:auto;margin-left:0cm;mso-pagination:widow-orphan;font-size:10.0pt;font-family:宋体;mso-bidi-font-family:宋体;}span.msonormal0{mso-style-name:msonormal;mso-style-unhide:no;}.MsoChpDefault{mso-style-type:export-only;mso-default-props:yes;font-size:10.0pt;mso-ansi-font-size:10.0pt;mso-bidi-font-size:10.0pt;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman";mso-font-kerning:0pt;}/* Page Definitions */ @page WordSection1{size:595.3pt 841.9pt;margin:72.0pt 90.0pt 72.0pt 90.0pt;mso-header-margin:42.55pt;mso-footer-margin:49.6pt;mso-paper-source:0;}div.WordSection1{page:WordSection1;}';
var styles = "";
// Aggregate parts of the file together
var fileContent = statics.mhtml.top.replace("_html_", statics.mhtml.head.replace("_styles_", styles) + statics.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)");
}
}
组件调用
<template>
<div
class="htmlToPdf"
id="export_word"
style="width:1000px;margin:0 auto;line-height: 25px;"
>
<div style="color: #333; position: relative;padding: 30px;">
<div style="height: 1320px; padding-top: 100px;text-align: center;">
<h2 style="line-height: 50px;">
// 标题
<span>{{ wordTitle }}</span>
</h2>
</div>
<div style="margin-top:40px" v-html="editContent"></div>
</div>
</div>
</template>
<script>
import $ from "./js/jquerys"
// import $ from 'jQuery'
import "./js/jquery.wordexport"
export default {
props: {
editContent: {
type: String,
default: () => {
return ""
},
},
wordTitle: {
type: String,
default: () => {
return ""
},
}
},
methods: {
exportWord() {
// 等待dom渲染完成再导出
this.$nextTick(() => {
$("#export_word").wordExport(this.wordTitle);
}, 500);
}
}
}
</script>
网友评论