前端实现 html 下载(保存)为 word 格式的文件
需求:不依靠后端的文件链接,直接将页面中的
DOM
元素转码为相应的word
文档,并保持页面风格大致不变。
前端一般的下载文件的方式
前端下载文件大致有三种方式,网上都有相应的答案,但是总的来说是通过 a
标签的her直接访问或者 a
标签的H5的download方法来实现,此处我不多赘述,详情请参考网上的答案
我这里要说的是,如何将一个页面上的某个 DOM
元素直接转换成 Word
文件; 并且保存到本地,注意这里没有下载,只是获取DOM
然后转存为Word
文件,说白了其实就是一种格式的转换。
如何转换呢?总的来讲,我们要借助JQuery来实现,具体来说就是jquery.wordexport.js,所以首先要在页面中引入JQ,JQ有许多的版本,网上也有许多的CDN链接,我这里使用的是百度的JQ CDN,当然也可以自己 npm i
JQ,然后实现全局的引用。
首先你需要引入三个文件
<!-- 引入下载功能的jq依赖 -->
<script src="http://apps.bdimg.com/libs/jquery/1.9.1/jquery.min.js"></script>
<!-- 引入文件保存功能的js-->
<script type="text/javascript" src="js/FileSaver.js"></script>
<!-- 引入导出文件的JQ方法-->
<script type="text/javascript" src="js/jquery.wordexport.js"></script>
下边两个文件需要自行下载到本地,然后使用script
标签进行相应的引入
文件源码附在后边
然后的操作就是
<script type="text/javascript">
// 获取某个元素,添加点击事件,获取目标元素,通过wordExport方法实现导入
$(".export_button").click(function(event) {
$("#pagecontent").wordExport();
});
</script>
当然这是最基本的操作,如果你要在Vue中使用应该怎么办呢?
看这里!!!
首先你需要在 根元素 app.html
中引入相应的JQ 依赖,或者直接npm i
安装相应的JQ 环境
然后将 FileSaver.js
以及 jquery.wordexport.js
文件下载到本地进行引用,具体的代码如下
<!-- app.template.htmll 文件中-->
<script src="http://apps.bdimg.com/libs/jquery/1.9.1/jquery.min.js"></script>
// 相应的vue组件中
import "~/components/contract/lib/FileSaver.js";
import "~/components/contract/lib/jquery.wordexport.js";
然后Vue的页面中有这样一个标签
<div class="law-essay-export-contents">
<h2 class="law-essay-title" style="text-align:center">{{essayData.title||''}}</h2>
<div class="law-essay-contents" v-html="essayData.content"></div>
</div>
<div class="download-button" @click="exportDocs(essayData.title||'',essayData.id)">立即下载</div>
export default {
methods:{
exportDocs(title){
try{
$(".law-essay-export-contents").wordExport(title);
}catch{
console.error('下载失败,请稍后重试!');
}
},
}
}
这样你就能够实现相应的页面下载和保存了
- 当然如果你也想像我一样使用了后端传过来的富文本,并且使用
v-html
注入到页面的话,这里还可以再次优化,我们可以在 node 层对富文本字符串进行解码,然后返回页面 - 之后你就能够直接操作富文本,而不用直接获取元素,因为直接操作元素,往其中追加样式或者内容的话,会改变页面本身的样式,这样不太好,所以这里我补充下我优化之后的方法,直接动态创建dom,返回自定义样式的word文件
exportDocs(id){
let oBox = document.createElement('div');
let h2 = document.createElement('h2');
h2.setAttribute('style','text-align:center');
this.$axios.post('/cms/getContentDetail',{ id })
.then(({data})=>{
let {title , content} = data;
oBox.innerHTML=content
h2.innerHTML = title;
$(oBox).prepend(h2).wordExport(title);
}).then(()=>{
this.$message({
message: '下载成功',
type: 'success',
});
}).then(async ()=>{
await this.updateDownloadNumber(id)
}).then(()=>{
window.location.reload()
})
.catch((err)=>{
this.$message.error('下载失败,请稍后重试!');
console.error(err)
})
},
async updateDownloadNumber(id){
await this.$axios.post('/cms/saveDownload', {id})
},
虽然堆的东西有点多,但是耐心看完还是会有很大收获的,这是我工作中遇到的问题,如有不成熟的地方,请大家多多包涵,也欢迎大家多多指点!
附录
【源码1】FileSaver
/*
* FileSaver.js
* A saveAs() FileSaver implementation.
*
* By Eli Grey, http://eligrey.com
*
* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)
* source : http://purl.eligrey.com/github/FileSaver.js
*/
// The one and only way of getting global scope in all environments
// https://stackoverflow.com/q/3277182/1008999
var _global = typeof window === 'object' && window.window === window
? window : typeof self === 'object' && self.self === self
? self : typeof global === 'object' && global.global === global
? global
: this
function bom (blob, opts) {
if (typeof opts === 'undefined') opts = { autoBom: false }
else if (typeof opts !== 'object') {
console.warn('Deprecated: Expected third argument to be a object')
opts = { autoBom: !opts }
}
// prepend BOM for UTF-8 XML and text/* types (including HTML)
// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
return new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type })
}
return blob
}
function download (url, name, opts) {
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'blob'
xhr.onload = function () {
saveAs(xhr.response, name, opts)
}
xhr.onerror = function () {
console.error('could not download file')
}
xhr.send()
}
function corsEnabled (url) {
var xhr = new XMLHttpRequest()
// use sync to avoid popup blocker
xhr.open('HEAD', url, false)
try {
xhr.send()
} catch (e) {}
return xhr.status >= 200 && xhr.status <= 299
}
// `a.click()` doesn't work for all browsers (#465)
function click (node) {
try {
node.dispatchEvent(new MouseEvent('click'))
} catch (e) {
var evt = document.createEvent('MouseEvents')
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
20, false, false, false, false, 0, null)
node.dispatchEvent(evt)
}
}
// Detect WebView inside a native macOS app by ruling out all browsers
// We just need to check for 'Safari' because all other browsers (besides Firefox) include that too
// https://www.whatismybrowser.com/guides/the-latest-user-agent/macos
var isMacOSWebView = _global.navigator && /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent)
var saveAs = _global.saveAs || (
// probably in some web worker
(typeof window !== 'object' || window !== _global)
? function saveAs () { /* noop */ }
// Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView
: ('download' in HTMLAnchorElement.prototype && !isMacOSWebView)
? function saveAs (blob, name, opts) {
var URL = _global.URL || _global.webkitURL
var a = document.createElement('a')
name = name || blob.name || 'download'
a.download = name
a.rel = 'noopener' // tabnabbing
// TODO: detect chrome extensions & packaged apps
// a.target = '_blank'
if (typeof blob === 'string') {
// Support regular links
a.href = blob
if (a.origin !== location.origin) {
corsEnabled(a.href)
? download(blob, name, opts)
: click(a, a.target = '_blank')
} else {
click(a)
}
} else {
// Support blobs
a.href = URL.createObjectURL(blob)
setTimeout(function () { URL.revokeObjectURL(a.href) }, 4E4) // 40s
setTimeout(function () { click(a) }, 0)
}
}
// Use msSaveOrOpenBlob as a second approach
: 'msSaveOrOpenBlob' in navigator
? function saveAs (blob, name, opts) {
name = name || blob.name || 'download'
if (typeof blob === 'string') {
if (corsEnabled(blob)) {
download(blob, name, opts)
} else {
var a = document.createElement('a')
a.href = blob
a.target = '_blank'
setTimeout(function () { click(a) })
}
} else {
navigator.msSaveOrOpenBlob(bom(blob, opts), name)
}
}
// Fallback to using FileReader and a popup
: function saveAs (blob, name, opts, popup) {
// Open a popup immediately do go around popup blocker
// Mostly only available on user interaction and the fileReader is async so...
popup = popup || open('', '_blank')
if (popup) {
popup.document.title =
popup.document.body.innerText = 'downloading...'
}
if (typeof blob === 'string') return download(blob, name, opts)
var force = blob.type === 'application/octet-stream'
var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari
var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent)
if ((isChromeIOS || (force && isSafari) || isMacOSWebView) && typeof FileReader !== 'undefined') {
// Safari doesn't allow downloading of blob URLs
var reader = new FileReader()
reader.onloadend = function () {
var url = reader.result
url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;')
if (popup) popup.location.href = url
else location = url
popup = null // reverse-tabnabbing #460
}
reader.readAsDataURL(blob)
} else {
var URL = _global.URL || _global.webkitURL
var url = URL.createObjectURL(blob)
if (popup) popup.location = url
else location.href = url
popup = null // reverse-tabnabbing #460
setTimeout(function () { URL.revokeObjectURL(url) }, 4E4) // 40s
}
}
)
_global.saveAs = saveAs.saveAs = saveAs
if (typeof module !== 'undefined') {
module.exports = saveAs;
}
【源码2】
FileSaver </jquery.wordexport.js>
if (typeof jQuery !== "undefined" && typeof saveAs !== "undefined") {
(function($) {
$.fn.wordExport = function(fileName) {
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
var styles = "";
// 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)");
}
}