vue3 前端解析帶圖片的excel

孙大猛子發表於2024-10-16

將帶圖片的excel上傳後解析資料,然後將資料放入表格。主要是為了將資料傳給服務端

目前存在的問題

1、沒有判斷合併單元格的情況

2、圖片必須放在一個單元格中,超出則不顯示(也可以自己最佳化程式碼,去掉右下角座標的判斷)

需要安裝 xlsx , jszip

npm install xlsx jszip

<template>
   <div class="container">
      <!-- 長傳元件 -->
      <el-upload action="" :before-upload="beforeUpload" :http-request="() => { }">
         <el-button type="primary">匯入excel</el-button>
      </el-upload>

      <!-- 表格元件 -->
      <el-table :data="tableData" border style="width: auto; margin-top: 10px">
         <el-table-column :prop="item.prop" :label="item.label" align="center" v-for="(item, index) in tableColumnLabel"
            :key="index">
            <template #default="scope" v-if="item.prop == 'images'">
               <img :src="img.path" alt="" style="width: 200px" v-for="img in scope.row.images" />
            </template>
         </el-table-column>
      </el-table>
   </div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import JSZip from "jszip"; // 引入jszip
import type { JSZipObject } from 'jszip';
import * as XLSX from "xlsx"; // 引入xlsx

const tableColumnLabel = ref(); // 獲取表頭內容
const tableData = ref<any[]>([]); // 表格資料
const tableImages = ref<imageList>([]); // 表格圖片

// 上傳excel
const beforeUpload = async (file: any) => {
   // 處理解析圖片
   tableImages.value = await getExcelImage(file);
   // 解析資料
   getExcelData(file);
}

// 解析資料
const getExcelData = (file: Blob) => {
   let fileReader = new FileReader(); // 構建fileReader物件
   fileReader.readAsArrayBuffer(file); // 讀取指定檔案內容
   // 讀取操作完成時
   fileReader.onload = function (e) {
      try {
         let data = e.target?.result; // 取得資料data
         let workbook = XLSX.read(data, { type: "binary" }); // 將data轉換成excel工作表資料
         const worksheet = workbook.Sheets[workbook.SheetNames[0]]; // 獲取第一個工作表
         /*
          * XLSX.utils.sheet_to_json 輸出JSON格式資料
          * 獲取指定工作表中的資料sheetlist[],整個表中的資料存放在一個陣列sheetlist中;
          * sheetlist陣列中的每個元素均為一個陣列rowlist,是每一行的資料;
          * header 如果列太多,需要修改列的長度資料 可以使用預設值 1
          */
         const sheetlist = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
         /**
          * 封裝資料 **********#######*********
          */
         formatDate(sheetlist);
      } catch (e) {
         console.log("檔案型別不正確");
         return;
      }
   };
}

// 封裝資料
function formatDate(sheetlist: string | any[]) {
   const keys = ['images', 'title', 'value', 'type', 'images2'];
   // 處理資料
   const setEmptyList = (ul: string | any[]) => {
      let obj = {} as { [x: string]: any };
      for (let i = 0; i < ul.length; i++) {
         const item = ul[i];
         obj[keys[i]] = item === undefined ? null : item
      }
      return obj
   }
   // 處理表頭
   const setTableColumn = (list: any[]) => {
      return list.map((item: any, index: number) => {
         return {
            label: item,
            prop: keys[index]
         }
      })
   }

   try {
      if (sheetlist.length < 1) return;
      tableColumnLabel.value = setTableColumn(sheetlist[0]); // 使用第一行作為獲取表頭
      for (let i = 1; i < sheetlist.length; i++) {
         // 從第一行開始,因為第0行是表頭

         console.log(sheetlist[i], 'item');
         for (let j = 0; j < sheetlist[i].length; j++) {
            // 這裡的 i 就是行  j 就是列
            // 計算圖片是否在這個單元格中 (這裡只判斷了一張,未判斷多張的情況,如果需要多張使用filter來計算)
            let filterList = tableImages.value.filter(item => {
               const { form, to } = item as { form: { x: string, y: string }, to: { x: string, y: string } };
               const formX = Number(form.x);
               const formY = Number(form.y);
               const toX = Number(to.x);
               const toY = Number(to.y);
               // i行 , j列
               return formX >= j && formY >= i && toX <= j && toY <= i;
            });

            if (filterList.length > 0) {
               const imageItem = filterList.map(item => {
                  return item
               })
               sheetlist[i][j] = imageItem;
            }
         }
         let itemObj = setEmptyList(sheetlist[i]);
         tableData.value.push(itemObj); // 新增到el-table繫結的資料來源中
         console.log(tableData);
      }
      console.log("tableData.value", tableData.value);
   } catch (error) {
      console.log(error);
      return;
   }
}
type imageList = {
   id: string,
   target: string,
   path: string,
   form?: {
      x: string,
      y: string,
   },
   to?: {
      x: string,
      y: string
   }
}[]
// 解析圖片列表
const analysisImageList = async (zipLoadResultFiles: { [x: string]: JSZip.JSZipObject }) => {
   const imageIdKey = 'xl/drawings/_rels/drawing1.xml.rels';//圖片存放id檔案路徑
   const fileContent = await zipLoadResultFiles[imageIdKey].async('string');
   const parser = new DOMParser();
   const xmldom = parser.parseFromString(fileContent, "text/xml");
   const list = xmldom.getElementsByTagName("Relationship");
   let results: imageList = [];
   for (var i = 0; i < list.length; i++) {
      const item = list[i];
      results.push({ id: list[i].getAttribute('Id') || '', target: item.getAttribute('Target') || '', path: '' })
   }
   const PromiseList = results.map(item => {
      return analysisImageBase64(zipLoadResultFiles, item.target)
   })
   await Promise.all(PromiseList).then(res => {
      res.forEach((item, index) => {
         results[index].path = item;
      })
   })
   return results
}

// 將圖片解析為base
const analysisImageBase64 = async (zipLoadResultFiles: { [x: string]: JSZipObject }, keys: string) => {
   const imageKey = keys.replace('..', 'xl');
   const fileContent = await zipLoadResultFiles[imageKey].async('base64');
   const url = `data:image/png;base64,${fileContent}`;
   return url;
}

// 解析圖示座標
const analysisImageLocation = async (zipLoadResultFiles: { [x: string]: JSZip.JSZipObject }, imageList: imageList) => {
   const imageLocationKey = 'xl/drawings/drawing1.xml';//圖片座標檔案路徑

   const fileContent = await zipLoadResultFiles[imageLocationKey].async('string');
   let parser = new DOMParser();
   let xmldom = parser.parseFromString(fileContent, "text/xml");
   // col單元格
   let colList = xmldom.getElementsByTagName("xdr:col");
   // row單元格
   let rowList = xmldom.getElementsByTagName("xdr:row");
   // 圖片
   let blip = xmldom.getElementsByTagName("a:blip");
   let locationList = [] as { form: { x: string, y: string }, to: { x: string, y: string }, id: string, path: string }[];
   for (var i = 0; i < blip.length; i++) {
      const formX = colList[i * 2].textContent || '';
      const toX = colList[i * 2 + 1].textContent || '';
      const formY = rowList[i * 2].textContent || '';
      const toY = rowList[i * 2 + 1].textContent || '';
      const id = blip[i].getAttribute('r:embed');
      const path = imageList.filter(i => i.id == id)[0].path
      locationList.push({
         form: {
            x: formX, y: formY
         },
         to: {
            x: toX, y: toY
         },
         id: blip[i].getAttribute('r:embed') || '',
         path
      });
   }
   return locationList
}
// 獲取圖片
async function getExcelImage(file: any) {
   let result: any[] = []; // 用來存放圖片
   const zip = new JSZip(); // 建立jszip例項
   try {
      let zipLoadResult = await zip.loadAsync(file); // 將xlsx檔案轉zip檔案
      const zipLoadResultFiles = zipLoadResult["files"];
      const imageList = await analysisImageList(zipLoadResultFiles);
      result = await analysisImageLocation(zipLoadResultFiles, imageList);
   } catch (error) {
      console.log(error);
   }
   return result;
}
</script>
<style lang="scss" scoped></style>

原理就是透過jszip將excel解壓,解壓後的檔案目錄為


透過檢視裡面檔案可以找到記錄圖片座標,記錄圖片的xml,還有圖片的路徑

使用DOMparser解析出xml檔案,獲取需要的資料,在計算出圖片所在的位置即可

相關文章