最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue3實現(xiàn)前端生成Word并下載的全過程

 更新時間:2026年01月05日 08:40:50   作者:外啫啫  
本文介紹了前端實現(xiàn)Word文件導(dǎo)出功能的技術(shù)方案,通過使用docxtemplater、JSZip等庫,支持單個文件導(dǎo)出和批量壓縮包下載功,同時總結(jié)了常見問題及解決方法,需要的朋友可以參考下

1. 前情

在后臺管理平臺開發(fā)過程中,有這樣一個需求:從后端獲取到數(shù)據(jù)后,前端生成指定模板內(nèi)容的word文件并下載到本地。當(dāng)導(dǎo)出的文件過多時,為了減少響應(yīng)時間,也加上了進(jìn)行壓縮下載部分。使用過程中可根據(jù)實際需求作出調(diào)整。

2. 準(zhǔn)備

依賴

npm install docxtemplater
 
npm install jszip
 
npm install pizzip
 
npm install file-saver
 
npm install jszip-utils
 
npm install angular-expressions

模板文件

需要按導(dǎo)出樣式準(zhǔn)備一個模板文件,放到 public 目錄下。模板中占位符包含以下部分:

  • 單一變量:{變量名}。直接顯示改變量的值。
  • 遍歷:{#變量名}內(nèi)容{/變量名}。會對該數(shù)據(jù)進(jìn)行遍歷展示
  • 顯示/隱藏:{#變量名}內(nèi)容{/變量名}。其中為true的時候顯示,false則不顯示。
  • 圖片:{%變量名}。變量值為base64格式。
  • if-else:{#變量名}A{/變量名}{^變量名}B{/變量名}。其中值為true的時候顯示“A”,為false顯示“B”;
  • 復(fù)選框:{#變量名}選中{/變量名}{^變量名}非選中{/變量名} 以上部分是我在實際需求中使用到的,其他使用可參考:docxtemplater.com/docs/
let obj={
    name:'張三',
    age:12,
    hobby:['basketball','swimming'],
    url:'xxxxxxxxxxxxxx.png',
    isPic:false
}

這里定一個名為 obj 的對象,接下來按照下面的模板生成:

3. 實現(xiàn)

docFiles.js

import { imageHandle } from '../common/image'
import { saveAs } from 'file-saver';
import JsZip from 'jszip'
import PizZip from "pizzip";
import docxtemplater from "docxtemplater";
import JSZipUtils from "jszip-utils";
import expressions from "angular-expressions";
 
let promises: any[] = [];

下載單個文件

 
/**導(dǎo)出單個word文件
 * @param {object} opts 配置項
 */
export const exportDocx = (opts) => {
  let {
    //模板文件路徑(必填)
    tempDocxpath,
    //模板文件數(shù)據(jù)(必填)
    data,
    //導(dǎo)出文件名
    fileName,
    //是否包含圖片
    imageable,
    //導(dǎo)出成功后的回調(diào)
    onSuccess,
    //導(dǎo)出失敗后的回調(diào)
    onError,
  } = Object.assign({
    imageable: false,
    fileName:'新建文件1',
  }, opts)
 
  const promise = new Promise((resolver, reject) => {
    JSZipUtils.getBinaryContent(tempDocxpath, (error, content) => {
      if (error) {
        throw error;
      }
      expressions.filters.size = function (input, width, height) {
        return {
          data: input,
          size: [width, height],
        };
      }
 
      //創(chuàng)建一個PiZip示例,內(nèi)容為模板的內(nèi)容
      const zip = new PizZip(content);
 
      //創(chuàng)建并加載docxtemplater實例對象
      let doc = new docxtemplater();
 
      if (imageable) {
        let opts = imageHandle()
        doc.attachModule(new ImageModule(opts));
      }
 
      doc.loadZip(zip);
      doc.setData(data);
 
      try {
        //用模板變量的值替換所有模板變量
        doc.render();
      } catch (error) {
        const e = {
          "message": error.message,
          "name": error.name,
          "stack": error.stack,
          "properties": error.properties
        };
        console.log({ error: e });
        throw error;
      }
 
      //生成一個代表docxtemplater對象的zip文件(在內(nèi)存中表示)
      const out = doc.getZip().generate({
        type: "blob",
        mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
      });
      resolver(out);
    })
  })
 
  Promise.all([promise]).then((out) => {
    if(onSuccess) onSuccess()
    saveAs(out, fileName);
  }).catch(() => {
    if(onError) onError()
  })
}

壓縮包形式批量下載

/**多級目錄的形式導(dǎo)出文件
 * @param {object} opts 配置項
 */
export const exportFile_MultiLevelDirectory = (opts) => {
  let {
    //模板文件路徑
    tempDocxpath,
    //模板文件數(shù)據(jù)
    data,
    //一級目錄名
    zipName,
    //下級目錄對應(yīng)數(shù)據(jù)路徑
    path,
    //下級目錄名稱類型
    subFolderNameType,
    //文件名稱類型
    fileNameType,
    //是否包含圖片
    imageable,
    //導(dǎo)出成功后的回調(diào)
    onSuccess,
    //導(dǎo)出失敗后的回調(diào)
    onError,
  } = Object.assign({
    imageable: false,
    zipName:'新建文件1'
  }, opts)
 
  const zips = new JsZip();
  //創(chuàng)建一級目錄的壓縮包
  const folder = zips.folder(zipName);
 
  let creatOpts = {
    tempDocxpath,
    data,
    path,
    fileNameType,
    subFolderNameType,
    imageable,
    folder,
    zips,
  }
  create_Directory(creatOpts)
  Promise.all(promises).then(() => {
    zips.generateAsync({ type: "blob" }).then(content => {
      //生成二進(jìn)制流
      if(onSuccess) onSuccess()
      saveAs(content, zipName);
    }).catch(() => {
      if(onError) onError()
    })
  })
}
 
/**創(chuàng)建下級目錄
 * @param {object} opts 配置項
 */
function create_Directory(opts) {
  let { 
     tempDocxpath,
     data,
     path,
     folder,
     subFolderNameType,
     fileNameType,
     imageable,
     zips
  } = opts
 
  //遍歷數(shù)據(jù),依次形成下級目錄/文件
  data.forEach((item, index) => {
    if (item[path] || item[path]?.length) {
     item[path].forEach((item2, index2) => {
        //創(chuàng)建下級目錄
        let subFolderName = item2.folderName
        let subFolder = folder.folder(subFolderName);
 
        let creatOpts = {
          tempDocxpath,
          data: item[path],
          fileNameType,
          subFolderNameType,
          imageable,
          zips,
          folder: subFolder
        }
        create_Directory(creatOpts)
      })
    }else {
      const fileName = item.fileName
      const promise = new Promise((resolver, reject) => {
        JSZipUtils.getBinaryContent(tempDocxpath, (error, content) => {
          if (error) {
            throw error;
          }
          expressions.filters.size = function (input, width, height) {
            return {
              data: input,
              size: [width, height],
            };
          };
          const zip = new PizZip(content);
          let doc = new docxtemplater();
          if (imageable) {
            let opts = imageHandle()
            doc.attachModule(new ImageModule(opts));
          }
          doc.loadZip(zip);
 
          doc.setData(item);
          try {
            doc.render();
          } catch (error) {
            const e = {
              "message": error.message,
              "name": error.name,
              "stack": error.stack,
              "properties": error.properties
            };
            console.log({ error: e });
            throw error;
          }
          const out = doc.getZip().generate({
            type: "blob",
            mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
          });
          folder.file(fileName, out, { binary: true });
          resolver('success');
        });
      })
      promises.push(promise);
    }
  }
}

注意:導(dǎo)出多個word文件時需注意文檔命名問題,避免因名稱重復(fù)產(chǎn)生的文件覆蓋問題。

image.js

/**
 * 圖片處理選項配置生成函數(shù)。
 * @returns {Object} 返回一個包含圖片處理選項的對象。
 */
export const imageHandle = () => {
  // 圖片處理
  let opts = {}
 
  opts = {
    //圖像是否居中
    centered: false
  };
  opts.getImage = (chartId) => {
    // 將base64的數(shù)據(jù)轉(zhuǎn)為ArrayBuffer
    return base64DataURLToArrayBuffer(chartId);
  }
  opts.getSize = function (img, tagValue, tagName) {
    //自定義指定圖像大小
    return [500, 400];
  }
  return opts
}
 
/**
 * 將base64格式的數(shù)據(jù)轉(zhuǎn)為ArrayBuffer
 * @param {Object} dataURL base64格式的數(shù)據(jù)
 */
export function base64DataURLToArrayBuffer(dataURL) {
  const base64Regex = /^data:image\/(png|jpg|jpeg|svg|svg\+xml);base64,/;
  if (!base64Regex.test(dataURL)) {
    return false;
  }
  const stringBase64 = dataURL.replace(base64Regex, "");
  let binaryString;
  if (typeof window !== "undefined") {
    binaryString = window.atob(stringBase64);
  } else {
    binaryString = new Buffer(stringBase64, "base64").toString("binary");
  }
  const len = binaryString.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    const ascii = binaryString.charCodeAt(i);
    bytes[i] = ascii;
  }
  return bytes.buffer;
}

應(yīng)用頁面文件

注釋部分為批量導(dǎo)出

<template>
    <div>
        <n-button type="primary" @click="downWord">導(dǎo)出word</el-button>
    </div>
</template>
<script setup>
    let obj={
        name:'張三',
        images:[]
    }
    
    /*let objList=[
        {
            folderName:'濟(jì)南市',
            children:[
                {
                    fileName:'張村申請合同',
                    name:'張村',
                    count:72
                },
                {
                    fileName:'大王村申請合同',
                    name:'大王村',
                    count:56
                },
            ]
        },
        {
            folderName:'德州市',
            children:[
                {
                    fileName:'高家村申請合同',
                    name:'高家村',
                    count:10
                },
            ]
        },
        {
            fileName:'居戶里村申請合同',
            name:'居戶里村',
            count:74
        },
    ]*/
 
    function downWord(){
        let opts={
            tempDocxpath:'/moBan.docx',
            data:obj,
            fileName:'申請表'
        }
        exportDocx(opts)
        /*let opts={
            tempDocxpath:'/moBan.docx',
            data:objList,
            zipName:'合同申請'
        }*/
        //exportFile_MultiLevelDirectory(opts)
    }
</script>

4. 問題

(1)End of data reached (data length = 0, asked index = 4). Corrupted zip ?

原因:模板文件為空文件;

排查:檢查模板文件引入是否正確

(2)導(dǎo)出的文件中圖片顯示undefined

原因:圖片路徑轉(zhuǎn)換成base64后的格式不對

(3)文件數(shù)量不對

排查:是不是文件名重復(fù)覆蓋導(dǎo)致數(shù)量不對

以上就是Vue3實現(xiàn)前端生成Word并下載的全過程的詳細(xì)內(nèi)容,更多關(guān)于Vue3前端生成Word并下載的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論

马边| 乐都县| 休宁县| 旺苍县| 洱源县| 陵水| 英吉沙县| 鸡东县| 双江| 黔南| 宜城市| 三江| 孝感市| 吉隆县| 广元市| 中卫市| 黑水县| 万全县| 辉县市| 临沧市| 科技| 金坛市| 延津县| 广丰县| 会东县| 庆阳市| 环江| 郁南县| 桂林市| 无锡市| 东辽县| 南华县| 广德县| 华阴市| 永福县| 浏阳市| 克东县| 澎湖县| 巫溪县| 轮台县| 祁阳县|