Vue에서 HTML을 Word 문서로 내보내기

개요

Vue 프로젝트에서 HTML 내용을 Word 문서로 내보내는 방법에 대해 설명합니다. 이 방법은 템플릿이 필요 없으며, echarts 차트도 포함하여 내보낼 수 있습니다. html-docx-jsfile-saver 패키지를 사용하여 구현합니다.

구현 방식은 다음과 같습니다:

  1. HTML 요소 가져오기
  2. 페이지의 Canvas 요소를 이미지로 변환
  3. 변환된 이미지를 Word 파일에 포함하여 내보내기

설치

npm install html-docx-js --save
npm install file-saver --save

내보내기 모듈 구현

exportWord.js 파일을 생성하여 Word 내보내기 기능을 구현합니다.

// exportWord.js

import htmlDocx from 'html-docx-js/dist/html-docx';
import saveAs from 'file-saver';

/**
 * HTML을 Word 문서로 내보내기
 * @param fileName 내보낼 파일 이름
 * @param elementSelector 선택자 문자열
 */
export function exportWord(fileName, elementSelector) {
  const targetElement = document.querySelector(elementSelector);
  const clonedElement = targetElement.cloneNode(true);
  const originalCanvases = targetElement.getElementsByTagName('canvas');
  const clonedCanvases = clonedElement.getElementsByTagName('canvas');

  const conversionPromises = Array.from(originalCanvases).map((canvas, index) => {
    return new Promise((resolve) => {
      const dataUrl = canvas.toDataURL('image/png', 1.0);
      const imageElement = new Image();
      
      imageElement.onload = () => {
        URL.revokeObjectURL(dataUrl);
        resolve();
      };
      
      imageElement.src = dataUrl;
      // 클론된 DOM의 canvasの前に画像を挿入
      clonedCanvases[index].parentNode.insertBefore(
        imageElement,
        clonedCanvases[index]
      );
    });
  });

  // 元のcanvasを削除
  const clonedCanvasElements = clonedElement.getElementsByTagName('canvas');
  Array.from(clonedCanvasElements).forEach((canvas) => {
    canvas.parentNode.removeChild(canvas);
  });

  Promise.all(conversionPromises).then(() => {
    convertImagesToBase64(clonedElement);
    
    const wordBlob = htmlDocx.asBlob(`
      <html xmlns:o='urn:schemas-microsoft-com:office:office' 
             xmlns:w='urn:schemas-microsoft-com:office:word' 
             xmlns='http://www.w3.org/TR/REC-html40'>
        <head>
          <style>
            ${document.head.outerHTML}
          </style>
        </head>
        <body>
          ${clonedElement.outerHTML}
        </body>
      </html>
    `);
    
    saveAs(wordBlob, fileName);
  });
}

/**
 * 画像データをBase64形式に変換
 * @param clonedElement クローンされた要素
 */
function convertImagesToBase64(clonedElement) {
  const imageElements = clonedElement.getElementsByTagName('img');
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');

  for (let i = 0; i < imageElements.length; i++) {
    const img = imageElements[i];
    canvas.width = img.width;
    canvas.height = img.height;
    
    ctx.drawImage(
      img,
      0,
      0,
      img.width * 0.8,
      img.height * 0.8
    );
    
    const extension = img.src
      .substring(img.src.lastIndexOf('.') + 1)
      .toLowerCase();
    const base64Data = canvas.toDataURL('image/' + extension);
    img.setAttribute('src', base64Data);
  }
  
  canvas.remove();
}

Vue 컴포넌트에서 사용

내보내기 기능을 사용할 Vue 컴포넌트의 예제입니다.

<template>
  <div>
    <div>
      <el-row>
        <button type="button" @click="handleExportWord">Word 내보내기</button>
      </el-row>

      <div id="contentDom" style="text-align: center" class="export-area">
        <div>내용 영역</div>
      </div>
    </div>
  </div>
</template>

<script>
import { exportWord } from '@/utils/feature/exportWord';

export default {
  name: "WordExport",
  data() {
    return {
      // 데이터 속성
    }
  },
  methods: {
    // HTML을 Word로 변환
    handleExportWord() {
      exportWord('report.docx', '.export-area');
    }
  }
}
</script>

<style scoped>
</style>

작동 원리

이 구현의 핵심적인 작동 방식은 다음과 같습니다:

  1. DOM 복제: 선택한 HTML 요소를 복제합니다.
  2. Canvas 변환: echarts 같은 차트 라이브러리는 Canvas를 사용하므로, Canvas 요소를 Base64 형식의 이미지로 변환합니다.
  3. 이미지 변환: 기존의 이미지들도 Base64로 변환하여 Word 문서에 포함시킵니다.
  4. Word 생성: HTML_DOCX 라이브러리를 사용하여 변환된 HTML을 Word Blob으로 변환하고 file-saver로 다운로드합니다.

참고 사항

  • CSS 스타일이 Word 문서에 그대로 반영됩니다.
  • 복잡한 차트나 그래프가 포함된 경우에도 Canvas를 이미지로 변환하여 깔끔하게 내보낼 수 있습니다.
  • Word 문서의 크기나 레이아웃은 CSS로 조정할 수 있습니다.

태그: vue word-export html-docx-js file-saver JavaScript

5월 24일 13:50에 게시됨