Vue純前端實(shí)現(xiàn)導(dǎo)出excel中的圖片與文件超鏈接
最近公司的需求,要求Excel導(dǎo)出的時(shí)候?qū)?shù)據(jù)里的圖片、文件等都帶出來(lái)。
一頓溝通后,后端不做,那就只好我這個(gè)前端小白來(lái)了。上網(wǎng)搜索了好久,有很多大佬分享了怎么導(dǎo)出圖片,但沒(méi)有將圖片按單元格級(jí)批量導(dǎo)出的例子,只好研究了一整天時(shí)間,實(shí)現(xiàn)效果如下:
這是vue展示的樣式:
這是excel導(dǎo)出的樣式:
下面直接上代碼: 具體需要注意的部分在下面說(shuō)
首先npm 安裝依賴: exceljs
數(shù)據(jù):(主要是展示el table ,順便也可以作為excel 導(dǎo)出的列模版)
data() { return { tableProp: [ { prop: 'creator', label: '提出人', namei18n: 'tichucreator', width: 100, align: 'center' }, { label: '提出時(shí)間', prop: 'creationTime', namei18n: 'creationTime', hidden: true }, { prop: 'projectCode', label: '項(xiàng)目編碼', namei18n: 'projectCode', width: 150, align: 'center' }, { prop: 'projectName', label: '項(xiàng)目名稱', namei18n: 'projectName', width: 240, align: 'center' }, { prop: 'stationCode', label: '工位', namei18n: 'stationCode', width: 150, align: 'center' }, { label: '負(fù)責(zé)人', prop: 'handler', namei18n: 'handler', hidden: true }, { prop: 'questionType', label: '問(wèn)題類型', namei18n: 'questionType', width: 150, align: 'center' }, { prop: 'importance', label: '重要程度', namei18n: 'importance', width: 100, align: 'center', options: [] }, { prop: 'stage', label: '階段', namei18n: 'stage', width: 150, align: 'center', options: [] }, { label: '開(kāi)始處理時(shí)間', prop: 'startDate', namei18n: 'startDate1', hidden: true }, { label: '實(shí)際完成時(shí)間', prop: 'lastModificationTime', namei18n: 'lastModificationTime', hidden: true }, { prop: 'questionDescribe', label: '問(wèn)題描述', namei18n: 'questionDescribe', width: 200, align: 'center' }, { prop: 'uploadPic', label: '圖片描述', namei18n: 'uploadPic', type: 'uploadPic', width: 330, align: 'center' }, { prop: 'uploadFile', label: '附件描述', namei18n: 'uploadFile', type: 'uploadFile', width: 300, align: 'center' }, { prop: 'questionAdvise', label: '問(wèn)題解決建議', namei18n: 'questionAdvise', width: 200, align: 'center' }, { label: '補(bǔ)充說(shuō)明', prop: 'bRemarks', namei18n: 'bRemarks', hidden: true }, { label: '解決方案', prop: 'solution', namei18n: 'solution', hidden: true }, { prop: 'expectedDate', label: '需求完成時(shí)間', namei18n: 'expectedDate', width: 160, align: 'center' }, { prop: 'isOverdue', label: '是否逾期', namei18n: 'isOverdue', width: 100, align: 'center', options: [ { value: true, label: '是' }, { value: false, label: '否' } ] // formatter: (val) => { // if (val) { // return '是'; // } else { // return '否'; // } // } }, ], },
方法:
methods: { async exportExcel1(exportDataList) { //exportDataList 就是后端返回的數(shù)據(jù) exportDataList = this.tableDataFormat(JSON.parse(JSON.stringify(exportDataList))); // 定義表頭 const columns = []; // this.tableProp 在上面有粘出來(lái),主要是表格的表頭配置,el-table就是用這個(gè)循環(huán)出來(lái)的 // 解釋一下這里的操作,excel并不支持同一單元格導(dǎo)出多個(gè)數(shù)據(jù)(也可能是我太菜) // 我們的項(xiàng)目是最多3個(gè)圖 3個(gè)文件,后端返回的是3個(gè)文件路徑的字符串在一個(gè)字段里 this.tableProp.forEach((item) => { if (item.label != '圖片描述' && item.label != '附件描述') { columns.push({ header: item.label, key: item.prop, width: item.width ? item.width / 10 : 14 }); } // 所以將 圖片 和 文件變?yōu)?3項(xiàng),也就是在excel里占 3個(gè) 單元格 if (item.label === '圖片描述') { columns.push({ header: '', key: 'uploadPic1', width: 14 }); columns.push({ header: '', key: 'uploadPic2', width: 14 }); columns.push({ header: '', key: 'uploadPic3', width: 14 }); } if (item.label === '附件描述') { columns.push({ header: '', key: 'uploadFile1', width: 14 }); columns.push({ header: '', key: 'uploadFile2', width: 14 }); columns.push({ header: '', key: 'uploadFile3', width: 14 }); } }); const Exceljs = require('exceljs'); // 創(chuàng)建工作簿 const workbook = new Exceljs.Workbook(); // 創(chuàng)建工作表 const workSheet = workbook.addWorksheet('sheet1'); // 工作表添加表頭 workSheet.columns = columns; // imgFieldList:圖片字段名 const imgFieldList = ['uploadPic1', 'uploadPic2', 'uploadPic3']; // fileFieldList:文件字段名 const fileFieldList = ['uploadFile1', 'uploadFile2', 'uploadFile3']; // 往工作表插入數(shù)據(jù) (插入圖片后,鏈接也會(huì)被顯示,所以把圖片的數(shù)據(jù)干掉) workSheet.addRows( exportDataList.map((item) => { return { ...item, uploadPic1: '', uploadPic2: '', uploadPic3: '' // uploadFile1: '', // uploadFile2: '', // uploadFile3: '' }; }) ); // 往Excel插入圖片 for (let ri = 0; ri < exportDataList.length; ri++) { // 獲取遍歷的當(dāng)前行 const row = exportDataList[ri]; // 過(guò)濾出當(dāng)前行圖片字段有值的 const currentRowImgFieldList = imgFieldList.filter((e) => row[e]); // 遍歷圖片字段 for (let ai = 0; ai < currentRowImgFieldList.length; ai++) { const imgField = currentRowImgFieldList[ai]; // 圖片字段值,一個(gè)完整的url const url = row[imgField]; // 根據(jù)url把圖片轉(zhuǎn)換成base64編碼,這里加了await, 方法名前面必須得加async,把這個(gè)imageToBase64方法變成同步方法 const base64 = await this.imageToBase64(url); // 把base64編碼的圖片插入excel工作簿里面 const imageId = workbook.addImage({ base64: base64, extension: 'png' }); // 當(dāng)前工作表(當(dāng)前excel頁(yè))加入圖片,tl.col:excel第幾列,tl.row:excel第幾行,ext里面表示圖片寬高 // 這里需要細(xì)細(xì)調(diào)一下,加入單元格后是緊挨著單元格頂部,要做一些偏移 workSheet.addImage(imageId, { tl: { col: columns.length + ai - 11.8, row: ri + 1.2 }, // br: { col: columns.length + ai - 11.1, row: ri + 2 } ext: { width: 104, height: 73 } }); } } for (let ri = 0; ri < exportDataList.length; ri++) { // 設(shè)置除了標(biāo)題之外,內(nèi)容的行高,避免圖片太高,這里太小,導(dǎo)致顯示很亂 workSheet.getRow(ri + 2).height = 60; } // 設(shè)置標(biāo)題列的高度 workSheet.getRow(1).height = 20; // 設(shè)置整個(gè)工作表的樣式 const font = { name: '微軟雅黑', size: 10 // 字體大小 }; const font1 = { name: '宋體', size: 10, // 字體大小 bold: true, color: { argb: '00000002' } }; const border = { top: { style: 'thin' }, left: { style: 'thin' }, bottom: { style: 'thin' }, right: { style: 'thin' } }; const alignment = { horizontal: 'center', vertical: 'middle' }; const headerFillColor = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'F0F0F0F0' } // 淺灰色背景,您可以自定義顏色 }; for (let rowIndex = 1; rowIndex <= workSheet.actualRowCount; rowIndex++) { for (let colIndex = 1; colIndex <= workSheet.columnCount; colIndex++) { const cell = workSheet.getCell(rowIndex, colIndex); cell.font = font; cell.border = border; cell.alignment = alignment; // 如果是附件 (這里主要是文件的邏輯) if ((colIndex === 16 || colIndex === 17 || colIndex === 18) && rowIndex !== 1) { cell.font = { underline: true, color: { argb: 'FF1890FF' } }; if (cell.value) { cell.value = { text: cell.value.split('/')[cell.value.split('/').length - 1], hyperlink: window.location.origin + cell.value, tooltip: '點(diǎn)擊跳轉(zhuǎn)' }; } } // 如果是第一行,設(shè)置背景顏色 if (rowIndex === 1) { cell.fill = headerFillColor; cell.font = font1; } } // 合并圖片單元格 workSheet.mergeCells('M' + rowIndex + ':O' + rowIndex); } // 合并文件單元格 workSheet.mergeCells('P1:R1'); // 添加一些數(shù)據(jù)到合并后的單元格 workSheet.getCell('M1').value = '圖片描述'; // 添加一些數(shù)據(jù)到合并后的單元格 workSheet.getCell('P1').value = '附件描述'; // 工作簿寫入excel workbook.xlsx.writeBuffer().then((buffer) => { // // 轉(zhuǎn)換成Blob格式 // const blob = new Blob([buffer], { type: 'application/octet-stream' }); // // 導(dǎo)出excel,這里獲得了blob,有很多種導(dǎo)出方法,可以用FileSaver.js(百度一下就有了),我這里就簡(jiǎn)單點(diǎn)了,用HTML的A標(biāo)簽導(dǎo)出 // // 獲取下載鏈接 // const url = URL.createObjectURL(blob); // // 動(dòng)態(tài)創(chuàng)建a標(biāo)簽 // let downloadLink = document.createElement('a'); // downloadLink.style.display = 'none'; // 隱藏這個(gè)a標(biāo)簽 // document.body.appendChild(downloadLink); // 添加到DOM中 // // 設(shè)置下載鏈接和文件名 // downloadLink.href = url; // downloadLink.download = '問(wèn)題匯總導(dǎo)出.xlsx'; // 自定義下載時(shí)的文件名 // // 觸發(fā)點(diǎn)擊事件來(lái)開(kāi)始下載 // downloadLink.click(); // // 下載后移除這個(gè)a標(biāo)簽 // document.body.removeChild(downloadLink); // 使用saveAs下載 不會(huì)報(bào)出文件不安全 saveAs(new Blob([buffer], { type: 'application/octet-stream' }), "問(wèn)題匯總導(dǎo)出.xlsx") }); this.loading.close(); }, imageToBase64(url, width, height) { return new Promise((resolve, reject) => { const image = new Image(); image.src = url; image.crossOrigin = '*'; image.onload = () => { const canvas = document.createElement('canvas'); canvas.width = width || image.width; canvas.height = height || image.height; const ctx = canvas.getContext('2d'); ctx.drawImage(image, 0, 0, width || image.width, height || image.height); const base64 = canvas.toDataURL('image/png'); resolve(base64); }; }); }, tableDataFormat(exportlist) { exportlist.forEach((item) => { // 這里是測(cè)試數(shù)據(jù),分割是;,要是用這個(gè)測(cè)試的話要改一下下面的分割符 和 文件后綴 // item.annexUrl = 'https://img2.baidu.com/it/u=101236606,1785081092&fm=253&fmt=auto&app=138&f=JPEG?w=727&h=500;https://img1.baidu.com/it/u=2607921640,3248579257&fm=253&fmt=auto&app=138&f=JPEG?w=629&h=500;https://img1.baidu.com/it/u=2976068608,3323494935&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500' // 這里是后端返回id,處理成字符串,大家未必用得上 this.tableProp.forEach((val) => { if (val.options && val.options.length > 0) { val.options.forEach((item1) => { if (item1.value == item[val.prop]) { item[val.prop] = item1.label; } }); } }); item.uploadPic = []; item.uploadPic1 = ''; item.uploadPic2 = ''; item.uploadPic3 = ''; item.uploadFile = []; item.uploadFile1 = ''; item.uploadFile2 = ''; item.uploadFile3 = ''; if (item.annexUrl) { let fileArr = item.annexUrl.split(','); let filetype = ['.doc', '.docx', '.pdf', '.vsdx', '.ppt', '.pptx', '.xlsx', '.xls', '.xmind', '.zip', '.rar', '.7z']; // 移除圖片類型以避免與圖片類型的判斷重疊,并統(tǒng)一轉(zhuǎn)換為小寫 const exclusiveFiletype = filetype.map((type) => type.toLowerCase()).filter((type) => !['.png', '.jpg', '.jpeg', '.gif'].map((ext) => ext.toLowerCase()).includes(type)); fileArr.forEach((fileUrl) => { // 將文件URL轉(zhuǎn)換為小寫,以便進(jìn)行不區(qū)分大小寫的比較 const lowerCaseFileUrl = fileUrl.toLowerCase(); // 先檢查是否為圖片類型 ['.png', '.jpg', '.jpeg', '.gif'].forEach((type) => { const lowerCaseType = type.toLowerCase(); if (lowerCaseFileUrl.endsWith(lowerCaseType)) { item.uploadPic.push(fileUrl); } }); // 然后檢查是否為其他文件類型 exclusiveFiletype.forEach((type) => { if (lowerCaseFileUrl.endsWith(type)) { item.uploadFile.push(fileUrl); } }); }); if (item.uploadPic.length > 0) { item.uploadPic1 = item.uploadPic[0]; item.uploadPic2 = item.uploadPic[1] || ''; item.uploadPic3 = item.uploadPic[2] || ''; } if (item.uploadFile.length > 0) { item.uploadFile1 = item.uploadFile[0]; item.uploadFile2 = item.uploadFile[1] || ''; item.uploadFile3 = item.uploadFile[2] || ''; } } }); return exportlist; }, }
因?yàn)?excel 插件都是按單元格級(jí)別的,圖片、超鏈接等如果想導(dǎo)出多個(gè),就要像我上面那樣一系列復(fù)雜操作,如果一行只有一個(gè)的話,會(huì)簡(jiǎn)單很多!代碼肯定是可以運(yùn)行的,復(fù)制即可使用,就是要調(diào)整一下字段,對(duì)應(yīng)上你自己項(xiàng)目的真實(shí)返回?cái)?shù)據(jù)
到此這篇關(guān)于Vue純前端實(shí)現(xiàn)導(dǎo)出excel中的圖片與文件超鏈接的文章就介紹到這了,更多相關(guān)Vue導(dǎo)出excel內(nèi)容內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
頁(yè)面內(nèi)錨點(diǎn)定位及跳轉(zhuǎn)方法總結(jié)(推薦)
這篇文章主要介紹了頁(yè)面內(nèi)錨點(diǎn)定位及跳轉(zhuǎn)方法總結(jié),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04vue2.x element-ui實(shí)現(xiàn)pc端購(gòu)物車頁(yè)面demo
這篇文章主要為大家介紹了vue2.x element-ui實(shí)現(xiàn)pc端購(gòu)物車頁(yè)面demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06vue實(shí)現(xiàn)多個(gè)元素或多個(gè)組件之間動(dòng)畫效果
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)多個(gè)元素或多個(gè)組件之間動(dòng)畫效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-09-09vue3中事件總線mitt代碼實(shí)例(第三方庫(kù)mitt)
這篇文章主要給大家介紹了關(guān)于vue3中事件總線mitt(第三方庫(kù)mitt)的相關(guān)資料,Mitt是一個(gè)在Vue.js應(yīng)用程序中使用的小型事件總線庫(kù),該庫(kù)允許組件進(jìn)行通信,而不必過(guò)度依賴父級(jí)或子級(jí)組件之間的props,需要的朋友可以參考下2023-09-09Vue+springboot批量刪除功能實(shí)現(xiàn)代碼
這篇文章主要介紹了Vue+springboot批量刪除功能,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-05-05Vue項(xiàng)目中實(shí)現(xiàn)帶參跳轉(zhuǎn)功能
最近做了一個(gè)手機(jī)端系統(tǒng),其中遇到了父頁(yè)面需要攜帶參數(shù)跳轉(zhuǎn)至子頁(yè)面的問(wèn)題,現(xiàn)已解決,下面分享一下實(shí)現(xiàn)過(guò)程,感興趣的朋友一起看看吧2021-04-04