系列文章目录
本篇文章主要介绍vue导出html页面为word, 在下载的同时把当前文件流通过接口形式传输到文件服务器供其他页面预览。
提示:html导出成word格式较老,不能直接预览,必须下载到本地用本地word/wps重新编辑才可预览。所以研究出方法,用两个文件流,一个是html流直接预览,一个是word流供下载
文章目录
前言
很多BI类项目,需要把HTML流导出成各种格式,但是导出word方法很少,本文章主要解决html导出到word,包含canvas图片。
提示:上传文件平时是不是都用upload组件?本文由直接上传文件方法,不依赖组件
一、怎么导出html为word?
思路:
1.首先判断当前页面有无canvas图形,没有直接下载,有图形需要挨个处理canvas为图片base64流才能在word展示图片及其顺序。
2.获取想要导出的HTML内容,创建一个dom元素,拿到原来父节点 ,拿到子节点的top值, 循环克隆子节点的top值数组,在克隆父节点上添加appendChild索引值的节点,找到索引,拿到top最小值的节点,在新建节点处添加index处的node,删除节点防止元素堆积。
3.剩下步骤看代码。
二、使用步骤
1.引入库
代码如下(示例):
import html2canvass from 'html2canvas'
import axios from 'axios'
import htmlDocx from 'html-docx-js/dist/html-docx'
2.导出方法第一步,判断当前有无canvas图片,没有直接下载,有就处理图片并排序。
代码如下(示例):
exportWord() {
const _this = this
_this.dataLoading = true
_this.showExportBtn = false
let canvasnum = document.getElementsByTagName('canvas').length
if (canvasnum === 0) {
this.exportFile()
} else {
setTimeout(() => {
document.getElementsByTagName('canvas').forEach((item) => {
const img = document.createElement('img')
img.style.position = 'relative'
img.style.width = item.width + 'px'
img.style.height = item.height + 'px'
img.style.zIndex = '100'
img.style.top = item.offsetTop + 'px'
img.style.left = item.offsetLeft + 'px'
// console.log(`getElementsByTagName`, item)
html2canvass(item).then((canvas) => {
canvas.willReadFrequency = true
const url = canvas.toDataURL('image/png', 1)
img.src = url
item.parentNode.appendChild(img)
item.style.display = 'none'
canvasnum--
if (canvasnum === 0) {
setTimeout(() => {
this.downloadWord()
}, 10000)
}
})
})
}, 50)
}
},
3.获取canvas图片,处理成html文件流,构建word文档。
exportFile() {
// 获取内容的HTML结构
const boxHtml = this.$refs['imageWrapper']
// 创建一个临时的DOM容器
const container = document.createElement('div')
const arr = [...boxHtml.querySelectorAll('.component-outer')]
console.log(`arr`, arr)
arr.sort((a, b) => {
return parseInt(a.style.top) - parseInt(b.style.top)
})
console.log(`arr`, arr)
arr.forEach((item) => {
container.appendChild(item)
})
// 获取所有的img元素
const imgElements = container.getElementsByTagName('img')
// 异步处理每个img元素
const promises = Array.from(imgElements).map((img) => {
return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
// 加载图片
const image = new Image()
image.crossOrigin = 'anonymous'
image.onload = () => {
// 设置canvas的尺寸
canvas.width = image.naturalWidth
canvas.height = image.naturalHeight
// 获取图片的宽度和高度
const imgWidth = image.naturalWidth
const imgHeight = image.naturalHeight
// 设置img元素的宽度和高度
img.style.width = imgWidth + 'px'
img.style.height = imgHeight + 'px'
// 在canvas上绘制图片
context.drawImage(image, 0, 0)
// 将canvas转换为Base64图片数据
const base64Data = canvas.toDataURL('image/png')
// 替换img元素的src属性为Base64图片数据
img.src = base64Data
resolve()
}
image.onerror = () => {
reject()
}
image.src = img.src
})
})
// 等待所有图片处理完成后,构建WORD文档
Promise.all(promises).then(() => {
// 生成最终的HTML内容
const articleContent = `<!DOCTYPE html>
<Html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>${container.innerHTML}</body>
</html>`
// 调用importTemplate方法,传入更新后的content
this.importTemplate(articleContent, 1)
})
},
4.文件流没有canvas直接下载方法,此方法可用来直接导出没有图片的html为word,并排序防止位置错乱。
downloadWord() {
// 获取HTML内容
var top = document.getElementById('preview-temp-canvas-main')
// 创建一个dom元素
var newTop = document.createElement('div')
newTop.style = 'position:relative;width:100%;height:100%;'
console.log(newTop, '创建的top节点')
// 原来父节点 拿到子节点的top值
var childrenTopArray = [] // 100,300,800,200,700,400
top.childNodes.forEach(item => {
// console.log(item.style.top)
childrenTopArray.push(Number(item.style.top.slice(0, (item.style.top).length - 2)))
})
// 循环克隆子节点的top值数组,在克隆父节点上添加appendChild索引值的节点
console.log(childrenTopArray, 'childrenTopArray')
function getMinNumber(arr) {
let max = 10000
arr.forEach((i) => {
if (max > i) {
max = i
}
})
return max
}
// var min = 9999
for (var jj = 0; jj < 25; jj++) {
top.childNodes.forEach((item, index) => {
// 找到索引
// var minIndex
// console.log(getMinNumber(childrenTopArray))
// // 拿到top最小值的节点
console.log(top.childNodes)
if (Number(item.style.top.slice(0, (item.style.top).length - 2)) == getMinNumber(childrenTopArray)) {
// 在新建节点处添加index处的node
newTop.appendChild(top.childNodes[index])
// 删除节点
childrenTopArray.splice(index, 1)
}
})
}
// console.log(newTop)
newTop = newTop.innerHTML
var content = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport">
<title>Document</title>
</head>
<body style="width: 100%; height: 100%; position: relative;">
${newTop}
</body>
</html>`
const sss = content.replace(/solid/g, 'none')
const aaa = sss.replace(/class="component" style="/g, 'class="component" style="position:absolute; ')
const getFile = JSON.parse(localStorage.getItem('fileName'))
const docx = htmlDocx.asBlob(aaa) // 将HTML转换为docx格式的二进制数据
const links = document.createElement('a') // 创建一个链接元素
links.href = URL.createObjectURL(docx, { type: "text/plain;charset='utf-8'" }) // 设置链接的URL为生成的文件
links.download = `${getFile.name}` // 设置下载文件的名称
document.body.appendChild(links) // 将链接元素添加到页面中
links.click() // 模拟点击链接进行下载
document.body.removeChild(links) // 下载完成后移除链接元素
this.importTemplate(aaa, 2)
// exportHtmlToWord(aaa, 'word')
},
5.下载当前文档方法
importTemplate(articleContent, status) {
const _this = this
const getFile = JSON.parse(localStorage.getItem('fileName'))
const secondBlob = htmlDocx.asBlob(articleContent)
const convertedBlob = new Blob([articleContent], {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
})
convertedBlob.lastModifiedDate = new Date()
if (status == 1) {
saveAs(secondBlob, `${getFile.name}.docx`)
}
_this.dataLoading = false
const reader1 = new FileReader()
reader1.onload = (event1) => {
const binaryArray1 = new Uint8Array(event1.target.result)
const reader2 = new FileReader()
reader2.onload = (event2) => {
const binaryArray2 = new Uint8Array(event2.target.result)
const fileExtension = '.docx'
const filename = getFile.name + fileExtension
const formData = new FormData()
formData.append(
'file',
new Blob([binaryArray1], {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
}),
filename
)
formData.append(
'file2',
new Blob([binaryArray2], {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
}),
filename
)
formData.append('reportName', getFile.name)
formData.append('templateName', getFile.pid)
formData.append('templateId', getFile.id)
const baseUrl = process.env.VUE_APP_BASE_API
axios
.post(`${baseUrl}reportManagement/importTemplate`, formData, {
headers: { Authorization: getToken() }
})
.then((response) => {
if (response.data) {
location.reload()
}
// 处理服务器返回的响应数据
// console.log(response.data)
})
.catch((error) => {
// 处理错误
console.error('Error importing template:', error)
})
}
reader2.readAsArrayBuffer(secondBlob)
}
reader1.readAsArrayBuffer(convertedBlob)
},
提示:secondBlob是下载的流,convertedBlob是在线预览的流,此处reader2的方法,就是通过axios传formData直接上传文件到文件服务器
代码小拆解:
const reader2 = new FileReader()
reader2.onload = (event2) => {
const binaryArray2 = new Uint8Array(event2.target.result)
const fileExtension = '.docx'
const filename = getFile.name + fileExtension
const formData = new FormData()
formData.append(
'file2',
new Blob([binaryArray2], {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
}),
filename
)
formData.append('reportName', getFile.name)
formData.append('templateName', getFile.pid)
formData.append('templateId', getFile.id)
const baseUrl = process.env.VUE_APP_BASE_API
axios
.post(`${baseUrl}reportManagement/importTemplate`, formData, {
headers: { Authorization: getToken() }
})
.then((response) => {
if (response.data) {
location.reload()
}
// 处理服务器返回的响应数据
// console.log(response.data)
})
.catch((error) => {
// 处理错误
console.error('Error importing template:', error)
})
}
reader2.readAsArrayBuffer(secondBlob)
这就是自己包装一个formData,不使用upload等上传组件直接上传文件流到文件服务器关键代码,有这个需求可直接借鉴这个思路。
总结
仅仅是自己完成需求的一些思路,可以借鉴部分代码
踩过的坑:
1.htmlDocx插件导出的文件流比较老,不修改的话在线预览的插件都不能完成预览。
2.导出html同时需要上传当前流到文件服务器。
3.纯文本html导出成word流排序问题。
解决方案:
1.包装两个文件流,一个下载一个提供预览。
2.自己实现文件上传,使用axios包装formData,拼接token等参数进行调接口上传。
3.自己进行排序,已经导出方法。
2和3的方法有这方面需求的可直接使用代码。