Vue3項(xiàng)目中通過(guò)LuckySheet實(shí)現(xiàn)Excel在線編輯功能
一、場(chǎng)景
在實(shí)現(xiàn)Excel文件導(dǎo)入時(shí),領(lǐng)導(dǎo)要求實(shí)現(xiàn)在前端導(dǎo)入文件后,不調(diào)用后端的接口,而是直接顯示excel文件的內(nèi)容,等待用戶修改完以后,再調(diào)用后端接口進(jìn)行文件的提交。在這種應(yīng)用場(chǎng)景下,使用了LuckySheet進(jìn)行改功能的實(shí)現(xiàn)。
二、Vue3中的基本使用
1、引入luckySheet
引入方式有兩種,分別是CDN引入與本地引入,下面都會(huì)做介紹:
(1)CDN引入
在你的index.html中復(fù)制下面這段代碼,實(shí)現(xiàn)CDN引入。
<link rel='stylesheet' /> <link rel='stylesheet' /> <link rel='stylesheet' /> <link rel='stylesheet' /> <script src="https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/js/plugin.js"></script> <script src="https://cdn.jsdelivr.net/npm/luckysheet/dist/luckysheet.umd.js"></script>
注意,https://cdn.jsdelivr.net/npm/luckysheet/dist/luckysheet.umd.js這個(gè)路徑意思是會(huì)拉取到最新的luckysheet代碼,但是如果Luckysheet剛剛發(fā)布,jsdelivr網(wǎng)站可能還沒(méi)來(lái)得及從npm上同步過(guò)去,故而使用這個(gè)路徑還是會(huì)拉到上一個(gè)版本,我們推薦您直接指定最新版本。
想要指定Luckysheet版本,請(qǐng)?jiān)谒械腃DN依賴文件后面加上版本號(hào),如:https://cdn.jsdelivr.net/npm/luckysheet@2.1.12/dist/luckysheet.umd.js。
如何知道最新版本是哪一版?查看最新 release記錄 (opens new window)或者 package.json (opens new window)的
version
字段。
如果不方便訪問(wèn) jsdelivr.net,還可以采用本地方式引入。
(2)本地引入
本地引入是將CDN引入的文件都放在本地文件中
第一步:克隆或者下載下面的代碼
git clone https://github.com/dream-num/Luckysheet.git
第二步:安裝依賴
npm install npm install gulp -g
第三步:運(yùn)行查看
npm run dev
運(yùn)行效果
第四步:打包
npm run build
第五步:本地引入
打包執(zhí)行成功后,在文件夾目錄下會(huì)出現(xiàn)dis文件夾,如下圖所示:
把dist文件夾中的代碼全部復(fù)制粘貼到你項(xiàng)目的public文件夾中,index.html文件除外。
在你項(xiàng)目的index.html文件中引入如下代碼,如果你復(fù)制的位置是其他地方,需要用絕對(duì)路徑引入這些文件。
<link rel='stylesheet' href='./public/plugins/css/pluginsCss.css' /> <link rel='stylesheet' href='./public/plugins/plugins.css' /> <link rel='stylesheet' href='./public/css/luckysheet.css' /> <link rel='stylesheet' href='./public/assets/iconfont/iconfont.css' /> <script src="./public/plugins/js/plugin.js"></script> <script src="./public/luckysheet.umd.js"></script>
2、在頁(yè)面中進(jìn)行展示
(1)指定一個(gè)容器
<div id="luckysheet" class="luckysheet-wrap"></div> <style scoped> .luckysheet-wrap { margin: 0px; padding: 0px; position: absolute; width: 100%; height: 100%; left: 0px; top: 0px; } </style>
(2)創(chuàng)建一個(gè)表格
onMounted(() => { //如果這里luckysheet.create報(bào)錯(cuò) //請(qǐng)使用 window.luckysheet.create luckysheet.create({ container: 'luckysheet'//這里需要和容器的id名稱一致 }) })
三、Excel文件的導(dǎo)入與導(dǎo)出
1、導(dǎo)入Excel文件
安裝luckyexcel
npm install luckyexcel --save
準(zhǔn)備一個(gè)導(dǎo)入按鈕,也可以使用elementUI的導(dǎo)入組件
這邊準(zhǔn)備了一個(gè)導(dǎo)入按鈕
<input id="uploadBtn" type="file" @change="loadExcel" />
const loadExcel = (evt) => { const files = evt.target.files if (files == null || files.length == 0) { alert('請(qǐng)上傳文件') return } let name = files[0].name let suffixArr = name.split('.'), suffix = suffixArr[suffixArr.length - 1] if (suffix != 'xlsx') { alert('只能導(dǎo)入xlsx文件格式的Excel') return } LuckyExcel.transformExcelToLucky(files[0], function (exportJson, luckysheetfile) { if (exportJson.sheets == null || exportJson.sheets.length == 0) { alert('導(dǎo)入失敗!') return } window.luckysheet.destroy() window.luckysheet.create({ container: 'luckysheet', //容器的Id showinfobar: false, data: exportJson.sheets, title: exportJson.info.name, userInfo: exportJson.info.name.creator }) }) }
2、導(dǎo)出Excel
導(dǎo)出需要用到excejs和file-saver
//安裝exceljs npm i exceljs --save //安裝文件保存的庫(kù) npm i file-saver --save
這里附上LuckySheet官網(wǎng)提供的導(dǎo)出函數(shù),直接在項(xiàng)目中新建export.js使用
// import { createCellPos } from './translateNumToLetter' import Excel from 'exceljs' import FileSaver from 'file-saver' const exportExcel = function(luckysheet, value) { // 參數(shù)為luckysheet.getluckysheetfile()獲取的對(duì)象 // 1.創(chuàng)建工作簿,可以為工作簿添加屬性 const workbook = new Excel.Workbook() // 2.創(chuàng)建表格,第二個(gè)參數(shù)可以配置創(chuàng)建什么樣的工作表 if (Object.prototype.toString.call(luckysheet) === '[object Object]') { luckysheet = [luckysheet] } luckysheet.forEach(function(table) { if (table.data.length === 0) return true // ws.getCell('B2').fill = fills. const worksheet = workbook.addWorksheet(table.name) const merge = (table.config && table.config.merge) || {} const borderInfo = (table.config && table.config.borderInfo) || {} // 3.設(shè)置單元格合并,設(shè)置單元格邊框,設(shè)置單元格樣式,設(shè)置值 setStyleAndValue(table.data, worksheet) setMerge(merge, worksheet) setBorder(borderInfo, worksheet) return true }) // return // 4.寫(xiě)入 buffer const buffer = workbook.xlsx.writeBuffer().then(data => { // console.log('data', data) const blob = new Blob([data], { type: 'application/vnd.ms-excel;charset=utf-8' }) console.log("導(dǎo)出成功!") FileSaver.saveAs(blob, `${value}.xlsx`) }) return buffer } var setMerge = function(luckyMerge = {}, worksheet) { const mergearr = Object.values(luckyMerge) mergearr.forEach(function(elem) { // elem格式:{r: 0, c: 0, rs: 1, cs: 2} // 按開(kāi)始行,開(kāi)始列,結(jié)束行,結(jié)束列合并(相當(dāng)于 K10:M12) worksheet.mergeCells( elem.r + 1, elem.c + 1, elem.r + elem.rs, elem.c + elem.cs ) }) } var setBorder = function(luckyBorderInfo, worksheet) { if (!Array.isArray(luckyBorderInfo)) return // console.log('luckyBorderInfo', luckyBorderInfo) luckyBorderInfo.forEach(function(elem) { // 現(xiàn)在只兼容到borderType 為range的情況 // console.log('ele', elem) if (elem.rangeType === 'range') { let border = borderConvert(elem.borderType, elem.style, elem.color) let rang = elem.range[0] // console.log('range', rang) let row = rang.row let column = rang.column for (let i = row[0] + 1; i < row[1] + 2; i++) { for (let y = column[0] + 1; y < column[1] + 2; y++) { worksheet.getCell(i, y).border = border } } } if (elem.rangeType === 'cell') { // col_index: 2 // row_index: 1 // b: { // color: '#d0d4e3' // style: 1 // } const { col_index, row_index } = elem.value const borderData = Object.assign({}, elem.value) delete borderData.col_index delete borderData.row_index let border = addborderToCell(borderData, row_index, col_index) // console.log('bordre', border, borderData) worksheet.getCell(row_index + 1, col_index + 1).border = border } // console.log(rang.column_focus + 1, rang.row_focus + 1) // worksheet.getCell(rang.row_focus + 1, rang.column_focus + 1).border = border }) } var setStyleAndValue = function(cellArr, worksheet) { if (!Array.isArray(cellArr)) return cellArr.forEach(function(row, rowid) { row.every(function(cell, columnid) { if (!cell) return true let fill = fillConvert(cell.bg) let font = fontConvert( cell.ff, cell.fc, cell.bl, cell.it, cell.fs, cell.cl, cell.ul ) let alignment = alignmentConvert(cell.vt, cell.ht, cell.tb, cell.tr) let value = '' if (cell.f) { value = { formula: cell.f, result: cell.v } } else if (!cell.v && cell.ct && cell.ct.s) { // xls轉(zhuǎn)為xlsx之后,內(nèi)部存在不同的格式,都會(huì)進(jìn)到富文本里,即值不存在與cell.v,而是存在于cell.ct.s之后 // value = cell.ct.s[0].v cell.ct.s.forEach(arr => { value += arr.v }) } else { value = cell.v } // style 填入到_value中可以實(shí)現(xiàn)填充色 let letter = createCellPos(columnid) let target = worksheet.getCell(letter + (rowid + 1)) // console.log('1233', letter + (rowid + 1)) for (const key in fill) { target.fill = fill break } target.font = font target.alignment = alignment target.value = value return true }) }) } var fillConvert = function(bg) { if (!bg) { return {} } // const bgc = bg.replace('#', '') let fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: bg.replace('#', '') } } return fill } var fontConvert = function( ff = 0, fc = '#000000', bl = 0, it = 0, fs = 10, cl = 0, ul = 0 ) { // luckysheet:ff(樣式), fc(顏色), bl(粗體), it(斜體), fs(大小), cl(刪除線), ul(下劃線) const luckyToExcel = { 0: '微軟雅黑', 1: '宋體(Song)', 2: '黑體(ST Heiti)', 3: '楷體(ST Kaiti)', 4: '仿宋(ST FangSong)', 5: '新宋體(ST Song)', 6: '華文新魏', 7: '華文行楷', 8: '華文隸書(shū)', 9: 'Arial', 10: 'Times New Roman ', 11: 'Tahoma ', 12: 'Verdana', num2bl: function(num) { return num === 0 ? false : true } } // 出現(xiàn)Bug,導(dǎo)入的時(shí)候ff為luckyToExcel的val let font = { name: typeof ff === 'number' ? luckyToExcel[ff] : ff, family: 1, size: fs, color: { argb: fc.replace('#', '') }, bold: luckyToExcel.num2bl(bl), italic: luckyToExcel.num2bl(it), underline: luckyToExcel.num2bl(ul), strike: luckyToExcel.num2bl(cl) } return font } var alignmentConvert = function( vt = 'default', ht = 'default', tb = 'default', tr = 'default' ) { // luckysheet:vt(垂直), ht(水平), tb(換行), tr(旋轉(zhuǎn)) const luckyToExcel = { vertical: { 0: 'middle', 1: 'top', 2: 'bottom', default: 'top' }, horizontal: { 0: 'center', 1: 'left', 2: 'right', default: 'left' }, wrapText: { 0: false, 1: false, 2: true, default: false }, textRotation: { 0: 0, 1: 45, 2: -45, 3: 'vertical', 4: 90, 5: -90, default: 0 } } let alignment = { vertical: luckyToExcel.vertical[vt], horizontal: luckyToExcel.horizontal[ht], wrapText: luckyToExcel.wrapText[tb], textRotation: luckyToExcel.textRotation[tr] } return alignment } var borderConvert = function(borderType, style = 1, color = '#000') { // 對(duì)應(yīng)luckysheet的config中borderinfo的的參數(shù) if (!borderType) { return {} } const luckyToExcel = { type: { 'border-all': 'all', 'border-top': 'top', 'border-right': 'right', 'border-bottom': 'bottom', 'border-left': 'left' }, style: { 0: 'none', 1: 'thin', 2: 'hair', 3: 'dotted', 4: 'dashDot', // 'Dashed', 5: 'dashDot', 6: 'dashDotDot', 7: 'double', 8: 'medium', 9: 'mediumDashed', 10: 'mediumDashDot', 11: 'mediumDashDotDot', 12: 'slantDashDot', 13: 'thick' } } let template = { style: luckyToExcel.style[style], color: { argb: color.replace('#', '') } } let border = {} if (luckyToExcel.type[borderType] === 'all') { border['top'] = template border['right'] = template border['bottom'] = template border['left'] = template } else { border[luckyToExcel.type[borderType]] = template } // console.log('border', border) return border } function addborderToCell(borders, row_index, col_index) { let border = {} const luckyExcel = { type: { l: 'left', r: 'right', b: 'bottom', t: 'top' }, style: { 0: 'none', 1: 'thin', 2: 'hair', 3: 'dotted', 4: 'dashDot', // 'Dashed', 5: 'dashDot', 6: 'dashDotDot', 7: 'double', 8: 'medium', 9: 'mediumDashed', 10: 'mediumDashDot', 11: 'mediumDashDotDot', 12: 'slantDashDot', 13: 'thick' } } // console.log('borders', borders) for (const bor in borders) { // console.log(bor) if (borders[bor].color.indexOf('rgb') === -1) { border[luckyExcel.type[bor]] = { style: luckyExcel.style[borders[bor].style], color: { argb: borders[bor].color.replace('#', '') } } } else { border[luckyExcel.type[bor]] = { style: luckyExcel.style[borders[bor].style], color: { argb: borders[bor].color } } } } return border } function createCellPos(n) { let ordA = 'A'.charCodeAt(0) let ordZ = 'Z'.charCodeAt(0) let len = ordZ - ordA + 1 let s = '' while (n >= 0) { s = String.fromCharCode((n % len) + ordA) + s n = Math.floor(n / len) - 1 } return s } export { exportExcel }
在頁(yè)面中引入export.js文件
import { exportExcel } from './export'
在頁(yè)面中使用下面這句代碼進(jìn)行導(dǎo)出
exportExcel(luckysheet.getAllSheets(), '導(dǎo)出的文件名字')
四、注意事項(xiàng)
在導(dǎo)出文件時(shí),如果需要將導(dǎo)出的數(shù)據(jù)通過(guò)接口傳輸給后端,需要進(jìn)行文件格式的轉(zhuǎn)換,以file為例。
//獲取buffer的 workbook.xlsx.writeBuffer() .then((arrayBuffer) => { const blob = new Blob(arrayBuffer, { //導(dǎo)出為xlsx文件格式 type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); //轉(zhuǎn)為file格式 let file = new File([blob], '導(dǎo)出.xlsx', {type: blob.type}); /** 這里書(shū)寫(xiě)接口上傳代碼 */ }).catch(err=>{ dialogVisible.value = false })
LuckySheet現(xiàn)在升級(jí)為Univer Docs,如果需要使用Univer Docs進(jìn)行展示excel,可以參考另一篇文章進(jìn)行了解。
以上就是Vue3項(xiàng)目中通過(guò)LuckySheet實(shí)現(xiàn)Excel在線編輯的詳細(xì)內(nèi)容,更多關(guān)于Vue3 LuckySheet在線編輯Excel的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue通過(guò)路由實(shí)現(xiàn)頁(yè)面間參數(shù)的傳遞
這篇文章主要介紹了Vue通過(guò)路由實(shí)現(xiàn)頁(yè)面間參數(shù)的傳遞,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04vue element項(xiàng)目引入icon圖標(biāo)的方法
這篇文章主要介紹了vue element項(xiàng)目引入icon圖標(biāo)的方法,本文圖文并茂給大家介紹的非常詳細(xì),需要的朋友可以參考下2018-06-06vue長(zhǎng)按事件和點(diǎn)擊事件沖突的解決
這篇文章主要介紹了vue長(zhǎng)按事件和點(diǎn)擊事件沖突的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10關(guān)于Vue.js一些問(wèn)題和思考學(xué)習(xí)筆記(1)
這篇文章主要為大家分享了關(guān)于Vue.js一些問(wèn)題和思考的學(xué)習(xí)筆記,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12vue實(shí)現(xiàn)打印指定組件內(nèi)容的示例詳解
這篇文章主要和大家分享一下vue中打印指定組件內(nèi)容,多頁(yè)打印自動(dòng)適配紙張大小打印的方案,文中的示例代碼講解詳細(xì),需要的可以參考一下2024-03-03基于vue實(shí)現(xiàn)分頁(yè)組件的示例代碼
分頁(yè)組件是一種用戶界面元素,用于在長(zhǎng)列表或數(shù)據(jù)集中分割內(nèi)容,分頁(yè)組件是每個(gè)開(kāi)發(fā)人員必須掌握的一個(gè)組件,廣泛應(yīng)用在各個(gè)場(chǎng)所,用以用戶方便閱讀等,本文就給大家介紹一下如何基于vue寫(xiě)出的分頁(yè)組件,需要的朋友可以參考下2023-08-08vue實(shí)現(xiàn)下拉框的多選功能(附后端處理參數(shù))
本文介紹了如何使用Vue實(shí)現(xiàn)下拉框的多選功能,實(shí)現(xiàn)了在選擇框中選擇多個(gè)選項(xiàng)的功能,文章詳細(xì)介紹了實(shí)現(xiàn)步驟和示例代碼,對(duì)于想要了解如何使用Vue實(shí)現(xiàn)下拉框多選功能的讀者具有一定的參考價(jià)值2023-08-08