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>
相关推荐
天真萌泪5 小时前
JS逆向自用
开发语言·javascript·ecmascript
柳杉6 小时前
震惊!字符串还能这么玩!
前端·javascript
仍然.7 小时前
算法题目---模拟
java·javascript·算法
我命由我123458 小时前
React - 类组件 setState 的 2 种写法、LazyLoad、useState
前端·javascript·react.js·html·ecmascript·html5·js
聊聊MES那点事8 小时前
JavaScript图表控件AG Charts使用教程:使用AG Charts React实时更新柱状图
开发语言·javascript·react.js·图表控件
斯班奇的好朋友阿法法9 小时前
离线ollama导入Qwen3.5-9B.Q8_0.gguf模型
开发语言·前端·javascript
莫物10 小时前
vue过滤表格数据导致的索引错乱问题
前端·javascript·vue.js
竹林81810 小时前
从监听失败到实时更新:我在NFT铸造项目中搞定合约事件监听的全过程
前端·javascript
光影少年10 小时前
手写防抖和节流
前端·javascript·前端框架
后藤十八里11 小时前
维普期刊逆向笔记
javascript·笔记·js逆向