Vue2转Word方法(html-docx-js库)

Vue2转Word方法(html-docx-js库)

前言

本文主要讲解使用Js中Dom实现将Vue中部分代码转换成html。文末有示例完整Vue2源代码.

应用场景:

Vue2文件导出成Word(先转成Html、再转成Word)

步骤一:获取当前内容

bash 复制代码
 const container = this.$el.querySelector('.stats-container').cloneNode(true)

先是用querySelector 截取当前Dom 元素的Class 类来获取内容,再用cloneNode来实现克隆当前元素内容。

步骤二:构建完整Html

获取到的内容就可以作为htmlbody 体。将固定的html 模板、样式、body 体内容写入到变量中。

注:可以使用new Blob([htmlContent,{type:"text/html;charset=utf-8}])实现对转换后的html文件导出。

这两部分的Vue2 源代码代码示例如下(运行代码需要下载fileSaver库,如不想下载可修改成模拟a标签click事件实现导出):

bash 复制代码
<template>
  <div class="vue-html-example">
    <el-card>
      <div slot="header" class="clearfix">
        <span>数据导出示例</span>
        <el-button style="float: right; padding: 3px 0" type="text" @click="handleExportHtml">
          导出HTML
        </el-button>
      </div>

      <div class="export-container">
        <h2 class="doc-title">{{ reportTitle }}</h2>
        <p class="doc-desc">
          本报告生成于 {{ currentDate }},包含最新的项目人员状态及分工详情。
          所有数据均为实时获取,确保信息的准确性与时效性。
        </p>

        <table class="doc-table" border="1">
          <thead>
            <tr>
              <th>姓名</th>
              <th>职位</th>
              <th>状态</th>
              <th>备注</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(item, index) in tableData" :key="index">
              <td>{{ item.name }}</td>
              <td>{{ item.role }}</td>
              <td>
                <span :style="{ color: item.status === '在职' ? 'green' : 'red' }">
                  {{ item.status }}
                </span>
              </td>
              <td>{{ item.remark || '-' }}</td>
            </tr>
          </tbody>
        </table>

        <div class="doc-footer">
          <p>生成日期:{{ currentDate }}</p>
          <p>制表人:系统管理员</p>
        </div>
      </div>
    </el-card>
  </div>
</template>

<script>
import { saveAs } from 'file-saver'

export default {
  name: 'Vue2HtmlExportExample',
  data() {
    return {
      reportTitle: '2026年度人员统计报告',
      currentDate: new Date().toLocaleDateString(),
      tableData: [
        { name: '张三', role: '项目经理', status: '在职', remark: '负责整体进度' },
        { name: '李四', role: '前端开发', status: '在职', remark: '负责页面实现' },
      ]
    }
  },
  methods: {
    handleExportHtml() {
      try {
        const originalElement = this.$el.querySelector('.export-container')
        if (!originalElement) {
          this.$message.warning('未找到导出内容区域')
          return
        }

        const clonedNode = originalElement.cloneNode(true)
        const htmlContent = `
            <!DOCTYPE html>
            <html lang="zh-CN">
            <head>
                <meta charset="UTF-8">
                <title>${this.reportTitle}</title>
                <style>
                    body { font-family: "Microsoft YaHei", sans-serif; margin: 20px; color: #333; }
                    .doc-title { font-size: 24px; font-weight: bold; text-align: center; margin: 20px 0; border-bottom: 2px solid #409eff; padding-bottom: 10px; }
                    .doc-desc { line-height: 1.6; margin-bottom: 20px; text-indent: 2em; }
                    .doc-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
                    .doc-table th { background-color: #f5f7fa; font-weight: bold; }
                    .doc-table th, .doc-table td { border: 1px solid #000; padding: 12px 8px; text-align: center; }
                    .doc-footer { margin-top: 30px; text-align: right; font-size: 14px; color: #666; border-top: 1px dashed #ccc; padding-top: 10px; }
                </style>
            </head>
            <body>
                <div style="max-width: 800px; margin: 0 auto; background: #fff; padding: 20px; box-shadow: 0 0 10px rgba(0,0,0,0.1);">
                    ${clonedNode.innerHTML}
                </div>
            </body>
            </html>
        `

        const blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' })
        saveAs(blob, `${this.reportTitle}.html`)
        this.$message.success('导出 HTML 成功!')
      } catch (error) {
        console.error('导出失败:', error)
        this.$message.error('导出失败,请查看控制台')
      }
    }
  }
}
</script>

<style scoped>
.vue-html-example {
  padding: 20px;
}
.export-container {
  border: 1px dashed #dcdfe6;
  padding: 20px;
  margin-bottom: 20px;
  background: #fff;
}
.doc-title { text-align: center; color: #333; margin: 20px 0; }
.doc-desc { text-indent: 2em; line-height: 1.8; }
.doc-table { width: 100%; border-collapse: collapse; margin-top: 20px; }
.doc-table th, .doc-table td { border: 1px solid #ccc; padding: 10px; text-align: center; }
.doc-table th { background-color: #f5f7fa; }
.doc-footer { margin-top: 30px; text-align: right; }
</style>

步骤三:使用html-docx-js-typescript(简写hdj)库实现html-word转换

cpp 复制代码
import { asBlob } from 'html-docx-js-typescript'
 const blob = await asBlob(htmlContent, { orientation: 'portrait' })

这个步骤比较简单,因为本身hdj库它是根据html+css样式来动态控制word文件内容的,而不需要对样式布局手动的转换。所以只需要导入hdj库并且把做好的html文件放到asBlob方法中即可。那么弊端有很明显,就是它内部有很多问题是不可定制化的,就比方说对css样式做转义并没有做的那么完善。而且更新不及时,不过应用于简单的页面转word对样式没有高保真、精细化处理那还是比较适合的。

如果想看asBlob方法内容可参考这:

https://blog.csdn.net/m0_64720065/article/details/153397010

ps:如果有高要求,最好还是使用docx.js来实现转换导出word。因为docx这个库本身是不可直接将html转换成word的,需要手动定制化将html转换成word可接受的结构。费时但是好用,这里就不赘述了。Vue2-Html-Word导出完整示例代码如下:

cpp 复制代码
<template>
  <div class="vue-html-example">
    <el-card>
      <div slot="header" class="clearfix">
        <span>数据导出示例</span>
        <div style="float: right;">
          <el-button type="text" @click="handleExportHtml">导出HTML</el-button>
          <el-button type="text" @click="handleExportWord">导出Word</el-button>
        </div>
      </div>

      <div class="export-container">
        <h2 class="doc-title">{{ reportTitle }}</h2>
        <p class="doc-desc">
          本报告生成于 {{ currentDate }},包含最新的项目人员状态及分工详情。
          所有数据均为实时获取,确保信息的准确性与时效性。
        </p>

        <table class="doc-table" border="1">
          <thead>
            <tr>
              <th>姓名</th>
              <th>职位</th>
              <th>状态</th>
              <th>备注</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(item, index) in tableData" :key="index">
              <td>{{ item.name }}</td>
              <td>{{ item.role }}</td>
              <td>
                <span :style="{ color: item.status === '在职' ? 'green' : 'red' }">
                  {{ item.status }}
                </span>
              </td>
              <td>{{ item.remark || '-' }}</td>
            </tr>
          </tbody>
        </table>

        <div class="doc-footer">
          <p>生成日期:{{ currentDate }}</p>
          <p>制表人:系统管理员</p>
        </div>
      </div>
    </el-card>
  </div>
</template>

<script>
import { saveAs } from 'file-saver'
import { asBlob } from 'html-docx-js-typescript'

export default {
  name: 'Vue2HtmlExportExample',
  data() {
    return {
      reportTitle: '2026年度人员统计报告',
      currentDate: new Date().toLocaleDateString(),
      tableData: [
        { name: '张三', role: '项目经理', status: '在职', remark: '负责整体进度' },
        { name: '李四', role: '前端开发', status: '在职', remark: '负责页面实现' },
      ]
    }
  },
  methods: {
    getHtmlContent() {
      const originalElement = this.$el.querySelector('.export-container')
      if (!originalElement) {
        this.$message.warning('未找到导出内容区域')
        return null
      }
      const clonedNode = originalElement.cloneNode(true)
      return `
        <!DOCTYPE html>
        <html lang="zh-CN">
        <head>
            <meta charset="UTF-8">
            <title>${this.reportTitle}</title>
            <style>
                body { font-family: "Microsoft YaHei", sans-serif; margin: 20px; color: #333; }
                .doc-title { font-size: 24px; font-weight: bold; text-align: center; margin: 20px 0; border-bottom: 2px solid #409eff; padding-bottom: 10px; }
                .doc-desc { line-height: 1.6; margin-bottom: 20px; text-indent: 2em; }
                .doc-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
                .doc-table th { background-color: #f5f7fa; font-weight: bold; }
                .doc-table th, .doc-table td { border: 1px solid #000; padding: 12px 8px; text-align: center; }
                .doc-footer { margin-top: 30px; text-align: right; font-size: 14px; color: #666; border-top: 1px dashed #ccc; padding-top: 10px; }
            </style>
        </head>
        <body>
            <div style="max-width: 800px; margin: 0 auto; background: #fff; padding: 20px; box-shadow: 0 0 10px rgba(0,0,0,0.1);">
                ${clonedNode.innerHTML}
            </div>
        </body>
        </html>
      `
    },
    handleExportHtml() {
      try {
        const htmlContent = this.getHtmlContent()
        if (!htmlContent) return
        const blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' })
        saveAs(blob, `${this.reportTitle}.html`)
        this.$message.success('导出 HTML 成功!')
      } catch (error) {
        console.error('导出HTML失败:', error)
        this.$message.error('导出HTML失败')
      }
    },
    handleExportWord() {
      try {
        const htmlContent = this.getHtmlContent()
        if (!htmlContent) return
        Promise.resolve(asBlob(htmlContent)).then(blob => {
          saveAs(blob, `${this.reportTitle}.docx`)
          this.$message.success('导出 Word 成功!')
        })
      } catch (error) {
        console.error('导出Word失败:', error)
        this.$message.error('导出Word失败')
      }
    }
  }
}
</script>

<style scoped>
.vue-html-example {
  padding: 20px;
}
.export-container {
  border: 1px dashed #dcdfe6;
  padding: 20px;
  margin-bottom: 20px;
  background: #fff;
}
.doc-title { text-align: center; color: #333; margin: 20px 0; }
.doc-desc { text-indent: 2em; line-height: 1.8; }
.doc-table { width: 100%; border-collapse: collapse; margin-top: 20px; }
.doc-table th, .doc-table td { border: 1px solid #ccc; padding: 10px; text-align: center; }
.doc-table th { background-color: #f5f7fa; }
.doc-footer { margin-top: 30px; text-align: right; }
</style>
相关推荐
冴羽4 小时前
资深前端都在用的 9 个调试偏方
前端·javascript
兆子龙4 小时前
xx.d.ts 文件有什么用,为什么不引入都能生效?
javascript
吴声子夜歌4 小时前
小程序——界面API(一)
java·javascript·小程序
兆子龙4 小时前
万字解析 OpenClaw 源码架构:从入门到精通
前端·javascript
@大迁世界4 小时前
精通 React 面试:从零到中高级
前端·javascript·react.js·面试·前端框架
梁正雄4 小时前
Python前端-2-css基础
前端·python·html
lichenyang4534 小时前
虚拟 DOM、Diff 算法与 Fiber
前端·javascript·面试
angerdream5 小时前
最新版vue3+TypeScript开发入门到实战教程之学会vue3真正的响应式数据
javascript·vue.js
全栈老石6 小时前
再见 Moment.js!浏览器原生支持的新一代时间库 Temporal 来了
前端·javascript