一、前言
1、問(wèn)題描述
有時(shí)候難免會(huì)遇到解析excel的情況,現(xiàn)在前端的很多插件都可以實(shí)現(xiàn)excel文件中文本內(nèi)容的解析;但是很多時(shí)候excel文件中是帶有圖片文件的,這個(gè)圖片文件的提取著實(shí)是讓人有點(diǎn)頭疼的;
本人查閱了很多資料,試了很多方法,結(jié)果都是以失敗告終!
現(xiàn)決定使用一個(gè)迂回戰(zhàn)術(shù),完成一次曲線救國(guó),哈哈哈,方法可能不太好,但勉強(qiáng)能夠使用,如果有哪位大佬看見(jiàn),還望指點(diǎn)迷津,跪謝~
2、excel文件
3、實(shí)現(xiàn)效果
二、實(shí)現(xiàn)思路
- 第一步,使用XLSX插件,解析excel中的文本內(nèi)容;
- 第二步,使用JSZip插件,解析excel中的圖片內(nèi)容;
- 第三步,將圖片數(shù)據(jù)和文本數(shù)據(jù)進(jìn)行整理,封裝成我們最終需要的數(shù)據(jù)格式;
三、完整代碼
<template>
<div class="container">
<!-- 長(zhǎng)傳組件 -->
<el-upload action="" :before-upload="beforeUpload" :http-request="() => {}">
<el-button type="primary">導(dǎo)入excel</el-button>
</el-upload>
<!-- 表格組件 -->
<el-table :data="tableData" border style="width: auto; margin-top: 10px">
<el-table-column
:prop="item"
:label="item"
align="center"
v-for="(item, index) in tableColumnLabel"
:key="index"
>
<template #default="scope" v-if="item == 'avatar'">
<img :src="scope.row.avatar" alt="" style="width: 200px" />
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup>
import { ref } from "vue";
import JSZip from "jszip"; // 引入jszip
import * as XLSX from "xlsx"; // 引入xlsx
const tableColumnLabel = ref([]); // 獲取表頭內(nèi)容
const tableData = ref([]); // 表格數(shù)據(jù)
const imageList = ref([]); // 表格圖片
// 加載按鈕的回調(diào)
async function beforeUpload(file) {
// 解析圖片
imageList.value = await getExcelImage(file);
// 解析數(shù)據(jù)
getExcelData(file);
}
// 解析數(shù)據(jù)
function getExcelData(file) {
let fileReader = new FileReader(); // 構(gòu)建fileReader對(duì)象
fileReader.readAsArrayBuffer(file); // 讀取指定文件內(nèi)容
// 讀取操作完成時(shí)
fileReader.onload = function (e) {
try {
let data = e.target.result; // 取得數(shù)據(jù)data
// console.log(data);
let workbook = XLSX.read(data, { type: "binary" }); // 將data轉(zhuǎn)換成excel工作表數(shù)據(jù)
// console.log("Excel工作簿", workbook);
const worksheet = workbook.Sheets[workbook.SheetNames[0]]; // 獲取第一個(gè)工作表
// console.log("第一張工作表", worksheet);
/*
* XLSX.utils.sheet_to_json 輸出JSON格式數(shù)據(jù)
* 獲取指定工作表中的數(shù)據(jù)sheetlist[],整個(gè)表中的數(shù)據(jù)存放在一個(gè)數(shù)組sheetlist中;
* sheetlist數(shù)組中的每個(gè)元素均為一個(gè)數(shù)組rowlist,是每一行的數(shù)據(jù);
*/
const sheetlist = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
//console.log('sheetlist', sheetlist);
// 封裝數(shù)據(jù)
formatDate(sheetlist);
} catch (e) {
console.log("文件類型不正確");
return;
}
};
}
// 封裝數(shù)據(jù)
function formatDate(sheetlist) {
try {
if (sheetlist.length < 1) return;
tableColumnLabel.value = sheetlist[0]; // 獲取表格列名
for (let i = 0; i < sheetlist.length - 1; i++) {
// 這里length-1是因?yàn)槲覀兊膕heetlist[0]為列名,實(shí)際的數(shù)據(jù)要少 1;
let obj = {};
for (let j = 0; j < sheetlist[0].length; j++) {
// 頭像列,則取解析到的imageList中的相應(yīng)值;
// 這里要求我們知道excel中存放圖片的列名,否則會(huì)導(dǎo)致讀取失??;
if (sheetlist[0][j] == "avatar") {
obj[sheetlist[0][j]] = `data:image/png;base64,${imageList.value[i]}`; // 注意base64編碼的處理
} else {
// 非頭像列,直接取值;
obj[sheetlist[0][j]] = sheetlist[i + 1][j] ?? "";
}
}
// console.log(obj);
tableData.value.push(obj); // 添加到el-table綁定的數(shù)據(jù)源中
}
console.log("tableData.value", tableData.value);
} catch (error) {
console.log(error);
return;
}
}
// 獲取圖片
async function getExcelImage(file) {
// console.log(file);
let imageList = []; // 用來(lái)存放圖片
const zip = new JSZip(); // 創(chuàng)建jszip實(shí)例
try {
let zipLoadResult = await zip.loadAsync(file); // 將xlsx文件轉(zhuǎn)zip文件
// console.log("zipLoadResult", zipLoadResult);
for (const key in zipLoadResult["files"]) {
// 遍歷結(jié)果中的files對(duì)象
if (key.indexOf("media/image") != -1 && !key.dir) {
await zip
.file(zipLoadResult["files"][key].name)
.async("base64")
.then((res) => {
imageList.push(res); // 將解析出的圖片的base64編碼值 先存入imageList數(shù)組中;
});
}
}
} catch (error) {
console.log(error);
}
// console.log('imageList', imageList);
return imageList;
}
</script>
<style lang="scss" scoped>
</style>
四、準(zhǔn)備工作
1、excel文件
先準(zhǔn)備一個(gè)test.xlsx文件,內(nèi)容如下圖所示,主要帶上圖片就行;
2、上傳組件
添加一個(gè)上傳組件,用來(lái)上傳excel文件;這里采用的是element-plus中的el-upload組件;
<el-upload action="" :before-upload="beforeUpload" :http-request="() => {}">
<el-button type="primary">導(dǎo)入excel</el-button>
</el-upload>
這個(gè)上傳組件并不是用來(lái)做上傳,只是利用它來(lái)讀取我們的excel文件;
后續(xù)我們將在beforeUpload()方法中處理導(dǎo)入的excel文件;
3、展示組件
添加一個(gè)數(shù)據(jù)展示組件,用來(lái)展示我們解析好的數(shù)據(jù);這里采用的是el-table組件;
注意這里的列是動(dòng)態(tài)生成的,如果是圖片的列,則添加img標(biāo)簽顯示圖片;?
<el-table :data="tableData" border style="width: auto; margin-top: 10px">
<el-table-column :prop="item" :label="item" align="center" v-for="(item, index) in tableColumnLabel" :key="index">
<template #default="scope" v-if="item == 'avatar'">
<img :src="scope.row.avatar" alt="" style="width: 200px" />
</template>
</el-table-column>
</el-table>
表格需要用到的數(shù)據(jù);?
import { ref } from "vue";
const tableColumnLabel = ref([]); // 獲取表頭內(nèi)容
const tableData = ref([]); // 表格數(shù)據(jù)
const imageList = ref([]); // 表格圖片
4、安裝依賴?
安裝jszip依賴;
npm install jszip
或
yarn add jszip
安裝xlsx依賴;
npm install xlsx
或
yarn xlsx
5、引入插件
在頁(yè)面中引入需要用的jszip和xlsx兩個(gè)依賴,可以使用其中的一些方法解析excel文件;
import JSZip from "jszip";
import * as XLSX from "xlsx";
6、現(xiàn)有效果?
到此,準(zhǔn)備工作完成,現(xiàn)有代碼與頁(yè)面效果如下所示:
<template>
<div class="container">
<!-- 長(zhǎng)傳組件 -->
<el-upload action="" :before-upload="beforeUpload" :http-request="() => {}">
<el-button type="primary">導(dǎo)入excel</el-button>
</el-upload>
<!-- 表格組件 -->
<el-table :data="tableData" border style="width: auto; margin-top: 10px">
<el-table-column :prop="item" :label="item" align="center" v-for="(item, index) in tableColumnLabel" :key="index">
<template #default="scope" v-if="item == 'avatar'">
<img :src="scope.row.avatar" alt="" style="width: 200px" />
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup>
import { ref } from "vue";
import JSZip from "jszip"; // 引入jszip
import * as XLSX from "xlsx"; // 引入xlsx
const tableColumnLabel = ref([]); // 獲取表頭內(nèi)容
const tableData = ref([]); // 表格數(shù)據(jù)
const imageList = ref([]); // 表格圖片
// 加載按鈕的回調(diào)
function beforeUpload(file){
console.log(file);
}
</script>
<style lang="scss" scoped>
</style>
?可以在頁(yè)面看到一個(gè)導(dǎo)入按鈕和一個(gè)沒(méi)有任何數(shù)據(jù)的表格;
五、實(shí)現(xiàn)過(guò)程
1、加載excel
點(diǎn)擊導(dǎo)入excel按鈕,選擇我們之前準(zhǔn)備好的測(cè)試excel【test.xlsx】文件,
導(dǎo)入按鈕的回調(diào):
可以在控制臺(tái)查看打印輸出的結(jié)果:
到這里呢,表示我們的excel已經(jīng)加載成功了,接下來(lái)繼續(xù)解析讀取到的內(nèi)容即可;
2、解析圖片文件
(1)解析思路
- 使用jszip將xlsx文件轉(zhuǎn)成zip文件;
- 獲取zip文件中的圖片內(nèi)容;
- 將圖片信息轉(zhuǎn)base64編碼存儲(chǔ);
解析圖片文件我們需要用到j(luò)szip這個(gè)插件,至于為什么呢?因?yàn)槲易约涸嚵似渌姆椒ǘ紱](méi)有成功;
首先,我們先將【test.xlsx】轉(zhuǎn)成【test.zip】文件;這里復(fù)制一份文件后 直接修改文件擴(kuò)展名;壓縮是看不到效果的;
點(diǎn)開(kāi)這個(gè)【test.zip】文件,可以看到以下內(nèi)容,
打開(kāi)這個(gè)【xl】文件夾,在【media】下存放的是圖片文件,worksheets下存放的是文本文件;
所以,我們想要獲取excel中的圖片文件,只需訪問(wèn)這個(gè)zip文件,然后獲取其中【media】文件夾中的內(nèi)容即可;
這個(gè)zip文件只是簡(jiǎn)單查看,有助于我們理解的,并沒(méi)有解析的實(shí)際作用,后續(xù)刪除即可;
(2)解析過(guò)程
? 在beforeUpload()回調(diào)函數(shù)中,編寫(xiě)解析代碼;
// 加載按鈕的回調(diào)
async function beforeUpload(file){
// console.log(file);
let imageList = []; // 用來(lái)存放圖片
const zip = new JSZip(); // 創(chuàng)建jszip實(shí)例
try {
let zipLoadResult = await zip.loadAsync(file); // 將xlsx文件轉(zhuǎn)zip文件
console.log("zipLoadResult", zipLoadResult);
// ......
} catch (error) {
console.log(error);
}
return imageList;
}
在打印輸出的結(jié)果中,我們可以看到跟之前自行手動(dòng)轉(zhuǎn)zip后的結(jié)果是一致的;圖片文件在【xl/media】 路徑文件夾中;
繼續(xù)解析獲取到的files對(duì)象,
// 加載按鈕的回調(diào)
async function beforeUpload(file){
// console.log(file);
let imageList = []; // 用來(lái)存放圖片
const zip = new JSZip(); // 創(chuàng)建jszip實(shí)例
try {
let zipLoadResult = await zip.loadAsync(file); // 將xlsx文件轉(zhuǎn)zip文件
// console.log("zipLoadResult", zipLoadResult);
for (const key in zipLoadResult["files"]) { // 遍歷結(jié)果中的files對(duì)象
if (key.indexOf("media/image") != -1 && !key.dir) {
await zip
.file(zipLoadResult["files"][key].name)
.async("base64")
.then((res) => {
imageList.push(res); // 將解析出的圖片的base64編碼值 先存入imageList數(shù)組中;
});
}
}
} catch (error) {
console.log(error);
}
console.log('imageList', imageList);
return imageList;
}
可以看到已經(jīng)成功獲取到了兩個(gè)圖片的base64編碼值;
在下方鏈接中,可以檢測(cè)取到的bae64是否正確:
在線Base64轉(zhuǎn)圖片
到此,我們已經(jīng)取到了excel文件中的圖片信息imageList??!
最后,我們將這段解析excel中圖片的代碼進(jìn)行封裝,后續(xù)在其他地方也可以使用;
// 加載按鈕的回調(diào)
async function beforeUpload(file){
// 解析圖片
imageList.value = await getExcelImage(file);
}
// 獲取圖片
async function getExcelImage(file){
// console.log(file);
let imageList = []; // 用來(lái)存放圖片
const zip = new JSZip(); // 創(chuàng)建jszip實(shí)例
try {
let zipLoadResult = await zip.loadAsync(file); // 將xlsx文件轉(zhuǎn)zip文件
// console.log("zipLoadResult", zipLoadResult);
for (const key in zipLoadResult["files"]) { // 遍歷結(jié)果中的files對(duì)象
if (key.indexOf("media/image") != -1 && !key.dir) {
await zip
.file(zipLoadResult["files"][key].name)
.async("base64")
.then((res) => {
imageList.push(res); // 將解析出的圖片的base64編碼值 先存入imageList數(shù)組中;
});
}
}
} catch (error) {
console.log(error);
}
// console.log('imageList', imageList);
return imageList;
}
3、解析文本內(nèi)容
(1)解析思路
- 使用FileReader對(duì)象來(lái)讀取excel文件數(shù)據(jù)data;
- 使用xlsx獲取excel表格數(shù)據(jù);
- 封裝sheetlist數(shù)據(jù)為el-table綁定的數(shù)據(jù)格式;
(2)解析過(guò)程
首先,查看FileReader的讀取結(jié)果;
async function beforeUpload(file){
// 解析圖片
imageList.value = await getExcelImage(file);
// ==================================================================================== //
// 解析數(shù)據(jù)
let fileReader = new FileReader(); // 構(gòu)建fileReader對(duì)象
fileReader.readAsArrayBuffer(file); // 讀取指定文件內(nèi)容
// 讀取操作完成時(shí)
fileReader.onload = function (e) {
try {
console.log('data', e);
// ......
} catch (e) {
console.log("文件類型不正確");
return;
}
};
}
其次,使用XLSX.utils.sheet_to_json(worksheet, { header: 1 })方法,處理FileReader讀取到的數(shù)據(jù)data;
該方法的第一個(gè)參數(shù)woksheet是必須要傳的,值為工作表數(shù)據(jù);
第二個(gè)參數(shù)可選,用于指定輸出excel的格式;
// 加載按鈕的回調(diào)
async function beforeUpload(file){
// 解析圖片
imageList.value = await getExcelImage(file);
// ==================================================================================== //
// 解析數(shù)據(jù)
let fileReader = new FileReader(); // 構(gòu)建fileReader對(duì)象
fileReader.readAsArrayBuffer(file); // 讀取指定文件內(nèi)容
// 讀取操作完成時(shí)
fileReader.onload = function (e) {
try {
let data = e.target.result; // 取得數(shù)據(jù)data
// console.log(data);
let workbook = XLSX.read(data, { type: "binary" }); // 將data轉(zhuǎn)換成excel工作表數(shù)據(jù)
// console.log("Excel工作簿", workbook);
const worksheet = workbook.Sheets[workbook.SheetNames[0]]; // 獲取第一個(gè)工作表
// console.log("第一張工作表", worksheet);
/*
* XLSX.utils.sheet_to_json 輸出JSON格式數(shù)據(jù)
* 獲取指定工作表中的數(shù)據(jù)sheetlist[],整個(gè)表中的數(shù)據(jù)存放在一個(gè)數(shù)組sheetlist中;
* sheetlist數(shù)組中的每個(gè)元素均為一個(gè)數(shù)組rowlist,是每一行的數(shù)據(jù);
*/
const sheetlist = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
console.log(sheetlist,'sheetlist');
} catch (e) {
console.log("文件類型不正確");
return;
}
};
}
到此為止,我們已經(jīng)獲取到了excel中的文本數(shù)據(jù)sheetlist,可以在控制臺(tái)查看如下所示結(jié)果;
外層是一個(gè)數(shù)組[ ],存放整個(gè)excel中的文本數(shù)據(jù);
其中的每個(gè)元素均為一個(gè)數(shù)組[ ],存放的是每一行的數(shù)據(jù);
?最后,我們將這段解析excel中文本的代碼進(jìn)行抽離;
// 加載按鈕的回調(diào)
async function beforeUpload(file){
// 解析圖片
imageList.value = await getExcelImage(file);
// 解析數(shù)據(jù)
getExcelData(file);
}
// 解析數(shù)據(jù)
function getExcelData(file) {
let fileReader = new FileReader(); // 構(gòu)建fileReader對(duì)象
fileReader.readAsArrayBuffer(file); // 讀取指定文件內(nèi)容
// 讀取操作完成時(shí)
fileReader.onload = function (e) {
try {
let data = e.target.result; // 取得數(shù)據(jù)data
// console.log(data);
let workbook = XLSX.read(data, { type: "binary" }); // 將data轉(zhuǎn)換成excel工作表數(shù)據(jù)
// console.log("Excel工作簿", workbook);
const worksheet = workbook.Sheets[workbook.SheetNames[0]]; // 獲取第一個(gè)工作表
// console.log("第一張工作表", worksheet);
/*
* XLSX.utils.sheet_to_json 輸出JSON格式數(shù)據(jù)
* 獲取指定工作表中的數(shù)據(jù)sheetlist[],整個(gè)表中的數(shù)據(jù)存放在一個(gè)數(shù)組sheetlist中;
* sheetlist數(shù)組中的每個(gè)元素均為一個(gè)數(shù)組rowlist,是每一行的數(shù)據(jù);
*/
const sheetlist = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
// console.log('sheetlist', sheetlist);
} catch (e) {
console.log("文件類型不正確");
return;
}
};
}
4、封裝數(shù)據(jù)?
根據(jù)上述內(nèi)容,excel文件中的圖片和文本內(nèi)容均已取得,如何封裝稱自己想要的數(shù)據(jù)格式,可以自行解決了;這里給出我自己在el-table中需要的數(shù)據(jù)格式封裝方法;
(1)封裝思路
獲取到的sheetlist的數(shù)據(jù)格式如下:
sheetlist = [
["name", "age", "avatar"],
["張三", 18],
["李四", 20],
]
這并不是我們想要的,期望的格式應(yīng)該是如下所示:
最外層是一個(gè)數(shù)組[ ],里面是每個(gè)數(shù)據(jù)對(duì)象itemObj;
[
{
name:'張三',
age:18,
avatar:"imageUrl"
},
{
name:'李四',
age:20,
avatar:"imageUrl"
},
]
- 首先,我們可以看出sheetlist[0],就是我們想要的el-table的列名;
- 其次,使用雙重循環(huán)拆解重組數(shù)據(jù);
????????創(chuàng)建一個(gè)對(duì)象obj,在我們?nèi)〉絪heetlist[0][0],也就是“name”的時(shí)候,讓obj[“name”] = “張三”,obj[“age”] = “18”,obj[“avatar”] = “”,也就是obj[sheetlist[0][0]] =?sheetlist[1][0]、obj[sheetlist[0][1]] =?sheetlist[1][1],obj[sheetlist[0][2]] =?sheetlist[1][2],以此類推...;
????????這里,剛剛好在給obj[“avatar”] = “”賦值的時(shí)候我們可以賦值成imagelist中的值,也就是obj[sheetlist[0][2]] =?imagelist[i];注意,imagelist中存儲(chǔ)的是base64編碼值,別忘記賦值之前進(jìn)行編碼轉(zhuǎn)換,obj[sheetlist[0][2]]? = `data:image/png;base64,${imageList.value[i]};
- 最后,將新建的obj對(duì)象push進(jìn)el-table綁定的數(shù)據(jù)源中即可看到結(jié)果了;
(2)實(shí)現(xiàn)過(guò)程
我們編寫(xiě)一個(gè)封裝數(shù)據(jù)的方法,并在解析好excel的文本數(shù)據(jù)后調(diào)用它;
獲取el-table的列名;
// 封裝數(shù)據(jù)
function formatDate(sheetlist) {
tableColumnLabel.value = sheetlist[0]; // 獲取表格列名
}
此時(shí),頁(yè)面上已經(jīng)有了顯示效果;
使用雙重循環(huán)拆解組合數(shù)據(jù),這里不做過(guò)多解釋,大家可以自己寫(xiě)個(gè)方法,能夠讓我參考參考,感覺(jué)自己寫(xiě)的也不怎么好;
// 封裝數(shù)據(jù)
function formatDate(sheetlist) {
tableColumnLabel.value = sheetlist[0]; // 獲取表格列名
for (let i = 0; i < sheetlist.length - 1; i++) {
let obj = {};
for (let j = 0; j < sheetlist[0].length; j++) {
// 頭像列,則取解析到的imageList中的相應(yīng)值;
// 這里要求我們知道excel中存放圖片的列名,否則會(huì)導(dǎo)致讀取失??;
if (sheetlist[0][j] == "avatar") {
obj[sheetlist[0][j]] = `data:image/png;base64,${imageList.value[i]}`; // 注意base64編碼的處理
} else { // 非頭像列,直接取值;
obj[sheetlist[0][j]] = sheetlist[i + 1][j] ?? "";
}
}
console.log(obj);
}
}
?可以在控制臺(tái)查看每個(gè)obj對(duì)象的輸出結(jié)果;
?最后,將這個(gè)obj對(duì)象push進(jìn)el-table綁定的數(shù)據(jù)源中;
// 封裝數(shù)據(jù)
function formatDate(sheetlist) {
tableColumnLabel.value = sheetlist[0]; // 獲取表格列名
for (let i = 0; i < sheetlist.length - 1; i++) { // 這里length-1是因?yàn)槲覀兊膕heetlist[0]為列名,實(shí)際的數(shù)據(jù)要少 1;
let obj = {};
for (let j = 0; j < sheetlist[0].length; j++) {
// 頭像列,則取解析到的imageList中的相應(yīng)值;
// 這里要求我們知道excel中存放圖片的列名,否則會(huì)導(dǎo)致讀取失敗;
if (sheetlist[0][j] == "avatar") {
obj[sheetlist[0][j]] = `data:image/png;base64,${imageList.value[i]}`; // 注意base64編碼的處理
} else { // 非頭像列,直接取值;
obj[sheetlist[0][j]] = sheetlist[i + 1][j] ?? "";
}
}
// console.log(obj);
tableData.value.push(obj); // 添加到el-table綁定的數(shù)據(jù)源中
}
console.log("tableData.value", tableData.value);
}
?控制臺(tái)查看打印輸出結(jié)果;
頁(yè)面最終顯示效果;
5、現(xiàn)有問(wèn)題
第一,采取這個(gè)方法的前提是得明確知道excel中存放圖片的列名;
第二,圖片是按照在excel中的順序加載的,存在較多未知的隱患;
第三,謹(jǐn)慎使用!謹(jǐn)慎使用!謹(jǐn)慎使用!
======================================================================================================================
每天進(jìn)步一點(diǎn)點(diǎn),本文就做一個(gè)簡(jiǎn)單記錄吧~
暫時(shí)也沒(méi)有更好的方法去讀取excel,先這樣完成就行;文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-836897.html
謝謝大家,還望哪位大佬指點(diǎn)指點(diǎn)!!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-836897.html
到了這里,關(guān)于前端解析包含圖片的excel文件的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!