Vue3項(xiàng)目中通過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)站可能還沒來得及從npm上同步過去,故而使用這個(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字段。
如果不方便訪問 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、在頁面中進(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 //安裝文件保存的庫 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.寫入 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}
// 按開始行,開始列,結(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: '華文隸書',
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
}
在頁面中引入export.js文件
import { exportExcel } from './export'
在頁面中使用下面這句代碼進(jìn)行導(dǎo)出
exportExcel(luckysheet.getAllSheets(), '導(dǎo)出的文件名字')
四、注意事項(xiàng)
在導(dǎo)出文件時(shí),如果需要將導(dǎo)出的數(shù)據(jù)通過接口傳輸給后端,需要進(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});
/**
這里書寫接口上傳代碼
*/
}).catch(err=>{
dialogVisible.value = false
})
LuckySheet現(xiàn)在升級(jí)為Univer Docs,如果需要使用Univer Docs進(jìn)行展示excel,可以參考另一篇文章進(jìn)行了解。
以上就是Vue3項(xiàng)目中通過LuckySheet實(shí)現(xiàn)Excel在線編輯的詳細(xì)內(nèi)容,更多關(guān)于Vue3 LuckySheet在線編輯Excel的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue通過路由實(shí)現(xiàn)頁面間參數(shù)的傳遞
這篇文章主要介紹了Vue通過路由實(shí)現(xiàn)頁面間參數(shù)的傳遞,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04
vue element項(xiàng)目引入icon圖標(biāo)的方法
這篇文章主要介紹了vue element項(xiàng)目引入icon圖標(biāo)的方法,本文圖文并茂給大家介紹的非常詳細(xì),需要的朋友可以參考下2018-06-06
vue長(zhǎng)按事件和點(diǎn)擊事件沖突的解決
這篇文章主要介紹了vue長(zhǎng)按事件和點(diǎn)擊事件沖突的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
關(guān)于Vue.js一些問題和思考學(xué)習(xí)筆記(1)
這篇文章主要為大家分享了關(guān)于Vue.js一些問題和思考的學(xué)習(xí)筆記,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
vue實(shí)現(xiàn)打印指定組件內(nèi)容的示例詳解
這篇文章主要和大家分享一下vue中打印指定組件內(nèi)容,多頁打印自動(dòng)適配紙張大小打印的方案,文中的示例代碼講解詳細(xì),需要的可以參考一下2024-03-03
vue實(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

