前端实现 html 下载(保存)为 word 格式的文件

1 篇文章 0 订阅
1 篇文章 0 订阅

前端实现 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)");
	}
}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值