vue3+luckysheet實(shí)現(xiàn)在線編輯Excel的項(xiàng)目實(shí)踐
效果圖奉上:

引入的依賴:
"dependencies": {
"@types/jquery": "^3.5.32",
"@types/xlsx": "^0.0.36",
"jquery": "^3.7.1",
"xlsx": "^0.18.5",
}在index.html中引入:
<!-- Luckysheet CSS -->
<link rel="stylesheet" rel="external nofollow" />
<link rel="stylesheet" rel="external nofollow" />
<link rel="stylesheet" rel="external nofollow" />
<link rel="stylesheet" rel="external nofollow" />
<!-- jQuery 和 Luckysheet 的JS CDN -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<!-- jQuery mousewheel 插件 -->
<script src="https://cdn.jsdelivr.net/npm/jquery-mousewheel@3.1.13/jquery.mousewheel.min.js"></script>
<!-- XLSX 庫(kù) -->
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/js/plugin.js"></script>
<script src="https://cdn.jsdelivr.net/npm/luckysheet@2.1.13/dist/luckysheet.umd.js"></script>完整代碼塊
<template>
<div class="luckysheet-container">
<!-- 工具欄 -->
<div class="toolbar">
<el-button type="primary" @click="importExcel">
<el-icon>
<Upload />
</el-icon>
導(dǎo)入Excel
</el-button>
<el-button type="success" @click="exportExcel">
<el-icon>
<Download />
</el-icon>
導(dǎo)出Excel
</el-button>
<el-button type="warning" @click="clearData">
<el-icon>
<Delete />
</el-icon>
清空數(shù)據(jù)
</el-button>
<el-button type="info" @click="addSheet">
<el-icon>
<Plus />
</el-icon>
添加工作表
</el-button>
<el-button type="success" @click="getData">
<el-icon>
<Document />
</el-icon>
保存
</el-button>
<!-- <el-button type="info" @click="printSheet">
<el-icon>
<Document />
</el-icon>
打印
</el-button> -->
</div>
<!-- 隱藏的文件輸入框 -->
<input ref="fileInput" type="file" accept=".xlsx,.xls" style="display: none" @change="handleFileChange" />
<!-- Luckysheet容器 -->
<div id="luckysheet" class="luckysheet-wrapper" ref="luckysheetRef"></div>
</div>
</template>
<script setup lang="ts">
declare global {
interface Window {
luckysheet: any;
XLSX: any;
$: any;
jQuery: any;
}
}
import { ref, onMounted, onUnmounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Upload, Download, Delete, Plus, Document } from '@element-plus/icons-vue'
// 響應(yīng)式數(shù)據(jù)
const fileInput = ref<HTMLInputElement>()
let luckysheetInstance: any = null
const luckysheetRef = ref<HTMLDivElement>()
// 默認(rèn)數(shù)據(jù)
const defaultData = [
{
name: 'Sheet1',
color: '',
index: 0,
status: 1,
order: 0,
hide: 0,
row: 36,
column: 18,
defaultRowHeight: 19,
defaultColWidth: 73,
celldata: [
{
r: 0,
c: 0,
v: {
v: '歡迎使用Luckysheet在線編輯器',
ct: { fa: 'General', t: 'g' },
m: '歡迎使用Luckysheet在線編輯器',
bg: '#f4f5f8',
bl: 1,
it: 0,
ff: 0,
fs: 14,
fc: '#000000',
cl: 0,
un: 0,
vt: 0
}
},
{
r: 1,
c: 0,
v: {
v: '這是一個(gè)示例表格',
ct: { fa: 'General', t: 'g' },
m: '這是一個(gè)示例表格',
bg: '#ffffff',
bl: 0,
it: 0,
ff: 0,
fs: 12,
fc: '#000000',
cl: 0,
un: 0,
vt: 0
}
}
] as any[],
config: {},
scrollLeft: 0,
scrollTop: 0,
luckysheet_select_save: [],
calcChain: [],
isPivotTable: false,
pivotTable: {},
filter_select: {},
filter: null,
luckysheet_alternateformat_save: [],
luckysheet_alternateformat_save_modelCustom: [],
luckysheet_conditionformat_save: {},
frozen: {},
chart: [],
zoomRatio: 1,
image: [],
showGridLines: 1,
dataVerification: {}
}
]
// 初始化Luckysheet
const initLuckysheet = (): Promise<boolean> => {
return new Promise((resolve) => {
console.log('開(kāi)始初始化Luckysheet...')
console.log('window.luckysheet:', window.luckysheet)
// 檢查容器是否存在
const container = document.getElementById('luckysheet')
console.log('容器元素:', container)
if (!container) {
console.error('找不到luckysheet容器')
resolve(false)
return
}
// 清空容器
container.innerHTML = ''
const options = {
container: 'luckysheet',
title: '在線Excel編輯器',
lang: 'zh',
data: defaultData,
showinfobar: true,
showsheetbar: true,
showstatisticBar: true,
enableAddRow: true,
enableAddCol: true,
userInfo: false,
myFolderUrl: '',
showtoolbar: true,
showtoolbarConfig: {
// 隱藏Luckysheet自帶打印按鈕
print: false
},
hook: {
cellEditBefore: (r: number, c: number, value: any) => {
console.log('編輯前:', r, c, value)
return value
},
cellEditAfter: (r: number, c: number, oldValue: any, newValue: any) => {
console.log('編輯后:', r, c, oldValue, newValue)
},
cellUpdated: (r: number, c: number, oldValue: any, newValue: any) => {
console.log('單元格更新:', r, c, oldValue, newValue)
}
}
}
try {
console.log('創(chuàng)建Luckysheet實(shí)例...')
console.log('使用的配置:', options)
// 直接調(diào)用全局方法
window.luckysheet.create(options)
// 檢查是否創(chuàng)建成功
setTimeout(() => {
const sheets = window.luckysheet.getAllSheets()
console.log('初始化后獲取到的sheets:', sheets)
if (sheets && sheets.length > 0) {
console.log('Luckysheet初始化成功')
luckysheetInstance = window.luckysheet // 使用全局對(duì)象作為實(shí)例
resolve(true)
} else {
console.error('Luckysheet初始化失敗,沒(méi)有獲取到sheets')
resolve(false)
}
}, 1000)
} catch (error) {
console.error('創(chuàng)建Luckysheet實(shí)例失敗:', error)
resolve(false)
}
})
}
// 等待Luckysheet加載
const waitForLuckysheet = (maxAttempts = 10): Promise<boolean> => {
return new Promise((resolve) => {
let attempts = 0
const checkLuckysheet = () => {
attempts++
console.log(`檢查L(zhǎng)uckysheet加載狀態(tài) (${attempts}/${maxAttempts})`)
if (window.luckysheet && typeof window.luckysheet.create === 'function') {
console.log('Luckysheet已加載完成')
resolve(true)
} else if (attempts >= maxAttempts) {
console.error('Luckysheet加載超時(shí)')
resolve(false)
} else {
setTimeout(checkLuckysheet, 500)
}
}
checkLuckysheet()
})
}
// 導(dǎo)入Excel文件
const importExcel = () => {
console.log('importExcel被調(diào)用')
console.log('window.XLSX:', window.XLSX)
// 檢查XLSX庫(kù)是否可用
if (!window.XLSX || !window.XLSX.utils) {
ElMessage.error('XLSX庫(kù)未加載,請(qǐng)刷新頁(yè)面重試')
return
}
fileInput.value?.click()
}
// 處理文件選擇
const handleFileChange = async (event: Event) => {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
if (!file) return
console.log('選擇的文件:', file.name, file.size)
try {
ElMessage.info('正在解析Excel文件...')
const data = await parseExcelFile(file)
console.log('解析到的數(shù)據(jù):', data)
if (data && data.length > 0) {
// 直接進(jìn)行重新初始化,不再檢查可用方法
console.log('開(kāi)始加載數(shù)據(jù)到Luckysheet...')
console.log('解析到的數(shù)據(jù):', data)
try {
// 直接使用重新初始化的方式,避免transToData的錯(cuò)誤
console.log('跳過(guò)transToData方法,直接重新初始化...')
ElMessage.info('正在加載Excel數(shù)據(jù)...')
const initSuccess = await initLuckysheetWithData(data)
if (initSuccess) {
// 初始化成功后,檢查導(dǎo)入結(jié)果
setTimeout(() => {
try {
const sheets = window.luckysheet.getAllSheets()
console.log('導(dǎo)入完成,檢查結(jié)果:')
console.log('- 工作表數(shù)量:', sheets.length)
sheets.forEach((sheet: any, index: number) => {
console.log(`- 工作表 ${index}: ${sheet.name}`)
console.log(` - 行數(shù): ${sheet.row}`)
console.log(` - 列數(shù): ${sheet.column}`)
console.log(` - 單元格數(shù)據(jù): ${sheet.celldata ? sheet.celldata.length : 0} 個(gè)`)
if (sheet.config && sheet.config.merge) {
console.log(` - 合并單元格: ${sheet.config.merge.length} 個(gè)`)
}
})
// 顯示成功消息
const totalCells = sheets.reduce((total: number, sheet: any) => {
return total + (sheet.celldata ? sheet.celldata.length : 0)
}, 0)
// 生成詳細(xì)的導(dǎo)入報(bào)告
const importReport = {
totalSheets: sheets.length,
totalCells,
sheets: sheets.map((sheet: any) => ({
name: sheet.name,
cells: sheet.celldata ? sheet.celldata.length : 0,
rows: sheet.row,
columns: sheet.column
}))
}
console.log('導(dǎo)入報(bào)告:', importReport)
// 顯示詳細(xì)成功消息
const sheetDetails = importReport.sheets.map(s =>
`${s.name}(${s.cells}個(gè)單元格)`
).join('、')
ElMessage.success(`Excel文件導(dǎo)入成功!共導(dǎo)入 ${importReport.totalSheets} 個(gè)工作表,${importReport.totalCells} 個(gè)單元格數(shù)據(jù)`)
console.log(`工作表詳情: ${sheetDetails}`)
// 顯示合并單元格檢測(cè)信息
if (data.some((sheet: any) => sheet.config && sheet.config.merge && sheet.config.merge.length > 0)) {
const mergeInfo = data
.filter((sheet: any) => sheet.config && sheet.config.merge && sheet.config.merge.length > 0)
.map((sheet: any) => `${sheet.name}(${sheet.config.merge.length}個(gè))`)
.join('、')
console.log(`檢測(cè)到合并單元格: ${mergeInfo}`)
ElMessage.info(`注意:檢測(cè)到合并單元格但暫時(shí)未應(yīng)用,以避免顯示錯(cuò)誤`)
}
} catch (error) {
console.error('檢查導(dǎo)入結(jié)果時(shí)出錯(cuò):', error)
ElMessage.success('Excel文件導(dǎo)入成功!')
}
}, 500)
} else {
ElMessage.error('表格初始化失敗,請(qǐng)刷新頁(yè)面重試')
}
} catch (loadError) {
console.error('加載數(shù)據(jù)失敗:', loadError)
ElMessage.error('加載數(shù)據(jù)失敗: ' + (loadError instanceof Error ? loadError.message : '未知錯(cuò)誤'))
}
} else {
ElMessage.warning('Excel文件為空或格式不正確')
}
} catch (error) {
console.error('導(dǎo)入Excel失敗:', error)
const errorMessage = error instanceof Error ? error.message : '未知錯(cuò)誤'
ElMessage.error('導(dǎo)入Excel失敗: ' + errorMessage)
}
target.value = ''
}
// 使用新數(shù)據(jù)初始化Luckysheet
const initLuckysheetWithData = (data: any[]): Promise<boolean> => {
return new Promise((resolve) => {
console.log('開(kāi)始使用新數(shù)據(jù)初始化Luckysheet...')
console.log('新數(shù)據(jù):', data)
// 檢查容器是否存在
const container = document.getElementById('luckysheet')
console.log('容器元素:', container)
if (!container) {
console.error('找不到luckysheet容器')
resolve(false)
return
}
// 清空容器
container.innerHTML = ''
// 驗(yàn)證和清理數(shù)據(jù)
const cleanData = data.map((sheet: any, index: number) => {
console.log(`清理工作表 ${index}:`, sheet.name)
// 確保必要的字段存在
const cleanSheet = {
name: sheet.name || `Sheet${index + 1}`,
color: sheet.color || '',
index: sheet.index || index,
status: sheet.status || 1,
order: sheet.order || index,
hide: sheet.hide || 0,
row: Math.max(sheet.row || 36, 36),
column: Math.max(sheet.column || 18, 18),
defaultRowHeight: sheet.defaultRowHeight || 19,
defaultColWidth: sheet.defaultColWidth || 73,
celldata: Array.isArray(sheet.celldata) ? sheet.celldata : [],
config: sheet.config || {},
scrollLeft: sheet.scrollLeft || 0,
scrollTop: sheet.scrollTop || 0,
luckysheet_select_save: sheet.luckysheet_select_save || [],
calcChain: sheet.calcChain || [],
isPivotTable: sheet.isPivotTable || false,
pivotTable: sheet.pivotTable || {},
filter_select: sheet.filter_select || {},
filter: sheet.filter || null,
luckysheet_alternateformat_save: sheet.luckysheet_alternateformat_save || [],
luckysheet_alternateformat_save_modelCustom: sheet.luckysheet_alternateformat_save_modelCustom || [],
luckysheet_conditionformat_save: sheet.luckysheet_conditionformat_save || {},
frozen: sheet.frozen || {},
chart: sheet.chart || [],
zoomRatio: sheet.zoomRatio || 1,
image: sheet.image || [],
showGridLines: sheet.showGridLines || 1,
dataVerification: sheet.dataVerification || {}
}
// 暫時(shí)禁用合并單元格處理以避免內(nèi)部錯(cuò)誤
if (cleanSheet.config.merge) {
console.log(`工作表 ${index} 移除合并單元格配置以避免內(nèi)部錯(cuò)誤`)
delete cleanSheet.config.merge
}
console.log(`清理后的工作表 ${index}:`, cleanSheet)
return cleanSheet
})
const options = {
container: 'luckysheet',
title: '在線Excel編輯器',
lang: 'zh',
data: cleanData,
showinfobar: true,
showsheetbar: true,
showstatisticBar: true,
enableAddRow: true,
enableAddCol: true,
userInfo: false,
myFolderUrl: '',
showtoolbar: true,
showtoolbarConfig: {
// 隱藏Luckysheet自帶打印按鈕
print: false
},
hook: {
cellEditBefore: (r: number, c: number, value: any) => {
console.log('編輯前:', r, c, value)
return value
},
cellEditAfter: (r: number, c: number, oldValue: any, newValue: any) => {
console.log('編輯后:', r, c, oldValue, newValue)
},
cellUpdated: (r: number, c: number, oldValue: any, newValue: any) => {
console.log('單元格更新:', r, c, oldValue, newValue)
}
}
}
try {
console.log('使用新數(shù)據(jù)創(chuàng)建Luckysheet實(shí)例...')
console.log('使用的配置:', options)
// 直接調(diào)用全局方法
window.luckysheet.create(options)
// 檢查是否創(chuàng)建成功
setTimeout(() => {
try {
const sheets = window.luckysheet.getAllSheets()
console.log('重新初始化后獲取到的sheets:', sheets)
if (sheets && sheets.length > 0) {
console.log('Luckysheet重新初始化成功')
luckysheetInstance = window.luckysheet // 使用全局對(duì)象作為實(shí)例
resolve(true)
} else {
console.error('Luckysheet重新初始化失敗,沒(méi)有獲取到sheets')
resolve(false)
}
} catch (error) {
console.error('檢查初始化結(jié)果時(shí)出錯(cuò):', error)
// 即使有錯(cuò)誤,如果容器中有內(nèi)容,也認(rèn)為初始化成功
if (container.children.length > 0) {
console.log('容器中有內(nèi)容,認(rèn)為初始化成功')
luckysheetInstance = window.luckysheet
resolve(true)
} else {
resolve(false)
}
}
}, 1000)
} catch (error) {
console.error('使用新數(shù)據(jù)創(chuàng)建Luckysheet實(shí)例失敗:', error)
resolve(false)
}
})
}
// 解析Excel文件
const parseExcelFile = (file: File): Promise<any[]> => {
return new Promise((resolve, reject) => {
console.log('開(kāi)始解析Excel文件...')
console.log('文件信息:', {
name: file.name,
size: file.size,
type: file.type,
lastModified: file.lastModified
})
// 檢查XLSX庫(kù)是否可用
if (!window.XLSX || !window.XLSX.utils) {
console.error('XLSX庫(kù)未加載')
reject(new Error('XLSX庫(kù)未加載'))
return
}
// 檢查文件類(lèi)型
const validTypes = [
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
'application/vnd.ms-excel', // .xls
'application/octet-stream' // 某些系統(tǒng)可能顯示為這個(gè)類(lèi)型
]
if (!validTypes.includes(file.type) && !file.name.match(/\.(xlsx|xls)$/i)) {
console.error('不支持的文件類(lèi)型:', file.type)
reject(new Error('不支持的文件類(lèi)型,請(qǐng)選擇.xlsx或.xls文件'))
return
}
const reader = new FileReader()
reader.onload = (e) => {
try {
console.log('文件讀取完成,開(kāi)始解析...')
const result = e.target?.result
console.log('讀取結(jié)果類(lèi)型:', typeof result)
if (!result) {
reject(new Error('文件讀取結(jié)果為空'))
return
}
const data = new Uint8Array(result as ArrayBuffer)
console.log('轉(zhuǎn)換為Uint8Array,長(zhǎng)度:', data.length)
// 嘗試不同的解析方式
let workbook
try {
workbook = window.XLSX.read(data, { type: 'array', cellStyles: true })
} catch (readError) {
console.error('使用array類(lèi)型解析失敗,嘗試binary類(lèi)型:', readError)
try {
workbook = window.XLSX.read(data, { type: 'binary', cellStyles: true })
} catch (binaryError) {
console.error('使用binary類(lèi)型解析也失敗:', binaryError)
reject(new Error('無(wú)法解析Excel文件,請(qǐng)檢查文件格式'))
return
}
}
console.log('工作簿解析成功:', workbook)
console.log('工作表名稱(chēng):', workbook.SheetNames)
if (!workbook.SheetNames || workbook.SheetNames.length === 0) {
reject(new Error('Excel文件中沒(méi)有找到工作表'))
return
}
const sheets = workbook.SheetNames.map((sheetName: string, index: number) => {
console.log(`處理工作表: ${sheetName}`)
const worksheet = workbook.Sheets[sheetName]
if (!worksheet) {
console.warn(`工作表 ${sheetName} 為空`)
return {
name: sheetName,
color: '',
index,
status: 1,
order: index,
hide: 0,
row: 36,
column: 18,
defaultRowHeight: 19,
defaultColWidth: 73,
celldata: [],
config: {},
scrollLeft: 0,
scrollTop: 0,
luckysheet_select_save: [],
calcChain: [],
isPivotTable: false,
pivotTable: {},
filter_select: {},
filter: null,
luckysheet_alternateformat_save: [],
luckysheet_alternateformat_save_modelCustom: [],
luckysheet_conditionformat_save: {},
frozen: {},
chart: [],
zoomRatio: 1,
image: [],
showGridLines: 1,
dataVerification: {}
}
}
// 獲取單元格范圍
const range = window.XLSX.utils.decode_range(worksheet['!ref'] || 'A1')
console.log(`工作表 ${sheetName} 范圍:`, range)
const celldata: any[] = []
const merges: any[] = []
// 處理合并單元格 - 暫時(shí)禁用以避免mergeCalculation錯(cuò)誤
if (worksheet['!merges']) {
console.log(`工作表 ${sheetName} 檢測(cè)到合并單元格:`, worksheet['!merges'].length, '個(gè)')
console.log('注意:合并單元格已識(shí)別但暫時(shí)禁用以避免內(nèi)部錯(cuò)誤')
// 暫時(shí)注釋掉合并單元格處理,但保留識(shí)別信息
/*
worksheet['!merges'].forEach((merge: any, mergeIndex: number) => {
try {
// 確保合并單元格數(shù)據(jù)格式正確
const mergeData = {
r: merge.s.r,
c: merge.s.c,
rs: merge.e.r - merge.s.r + 1,
cs: merge.e.c - merge.s.c + 1
}
// 驗(yàn)證合并單元格數(shù)據(jù)
if (mergeData.r >= 0 && mergeData.c >= 0 &&
mergeData.rs > 0 && mergeData.cs > 0 &&
mergeData.r + mergeData.rs <= range.e.r + 1 &&
mergeData.c + mergeData.cs <= range.e.c + 1) {
merges.push(mergeData)
console.log(`合并單元格 ${mergeIndex}:`, mergeData)
} else {
console.warn(`跳過(guò)無(wú)效的合并單元格 ${mergeIndex}:`, mergeData)
}
} catch (mergeError) {
console.warn(`處理合并單元格 ${mergeIndex} 時(shí)出錯(cuò):`, mergeError)
}
})
*/
}
// 遍歷所有單元格
for (let r = range.s.r; r <= range.e.r; r++) {
for (let c = range.s.c; c <= range.e.c; c++) {
const cellAddress = window.XLSX.utils.encode_cell({ r, c })
const cell = worksheet[cellAddress]
if (cell) {
const cellData: any = {
r,
c,
v: {
v: cell.v,
ct: { fa: 'General', t: 'g' },
m: String(cell.v),
// 只有有背景色時(shí)才加bg字段
...(cell.s && cell.s.fill && cell.s.fill.fgColor && cell.s.fill.fgColor.rgb
? { bg: '#' + cell.s.fill.fgColor.rgb.substring(2) }
: {}),
bl: 0,
it: 0,
ff: 0,
fs: 10,
fc: '#000000',
cl: 0,
un: 0,
vt: 0
}
}
// 處理單元格格式
if (cell.s) {
const style = cell.s
// 字體格式
if (style.font) {
if (style.font.bold) cellData.v.bl = 1
if (style.font.italic) cellData.v.it = 1
if (style.font.size) cellData.v.fs = style.font.size
if (style.font.color) {
const color = style.font.color
if (color.rgb) {
cellData.v.fc = '#' + color.rgb.substring(2)
}
}
}
// 背景色
if (style.fill) {
if (style.fill.fgColor) {
const bgColor = style.fill.fgColor
if (bgColor.rgb) {
cellData.v.bg = '#' + bgColor.rgb.substring(2)
}
}
}
// 對(duì)齊方式
if (style.alignment) {
const alignment = style.alignment
if (alignment.horizontal) {
switch (alignment.horizontal) {
case 'left':
cellData.v.ff = 0
break
case 'center':
cellData.v.ff = 1
break
case 'right':
cellData.v.ff = 2
break
}
}
if (alignment.vertical) {
switch (alignment.vertical) {
case 'top':
cellData.v.vt = 0
break
case 'middle':
cellData.v.vt = 1
break
case 'bottom':
cellData.v.vt = 2
break
}
}
}
// 邊框
if (style.border) {
const border = style.border
if (border.top || border.bottom || border.left || border.right) {
cellData.v.cl = 1
}
}
}
// 處理數(shù)字格式
if (cell.t === 'n' && cell.z) {
cellData.v.ct = { fa: cell.z, t: 'n' }
} else if (cell.t === 'd') {
cellData.v.ct = { fa: 'yyyy-mm-dd', t: 'd' }
} else if (cell.t === 'b') {
cellData.v.ct = { fa: 'General', t: 'b' }
}
celldata.push(cellData)
}
}
}
console.log(`工作表 ${sheetName} 轉(zhuǎn)換后的celldata:`, celldata)
console.log(`工作表 ${sheetName} 合并單元格:`, merges)
// 驗(yàn)證數(shù)據(jù)完整性
if (celldata.length === 0) {
console.warn(`工作表 ${sheetName} 沒(méi)有數(shù)據(jù),添加默認(rèn)單元格`)
celldata.push({
r: 0,
c: 0,
v: {
v: '',
ct: { fa: 'General', t: 'g' },
m: '',
bg: '#ffffff',
bl: 0,
it: 0,
ff: 0,
fs: 10,
fc: '#000000',
cl: 0,
un: 0,
vt: 0
}
})
}
// 創(chuàng)建工作表配置 - 不處理邊框
const sheetConfig: any = {}
// 不再賦值borderInfo,保持默認(rèn)網(wǎng)格線
return {
name: sheetName,
color: '',
index,
status: 1,
order: index,
hide: 0,
row: Math.max(range.e.r + 1, 36),
column: Math.max(range.e.c + 1, 18),
defaultRowHeight: 19,
defaultColWidth: 73,
celldata,
config: sheetConfig,
scrollLeft: 0,
scrollTop: 0,
luckysheet_select_save: [],
calcChain: [],
isPivotTable: false,
pivotTable: {},
filter_select: {},
filter: null,
luckysheet_alternateformat_save: [],
luckysheet_alternateformat_save_modelCustom: [],
luckysheet_conditionformat_save: {},
frozen: {},
chart: [],
zoomRatio: 1,
image: [],
showGridLines: 1,
dataVerification: {}
}
})
console.log('所有工作表轉(zhuǎn)換完成:', sheets)
resolve(sheets)
} catch (error) {
console.error('解析Excel文件時(shí)出錯(cuò):', error)
reject(error)
}
}
reader.onerror = (error) => {
console.error('文件讀取失敗:', error)
reject(new Error('文件讀取失敗'))
}
reader.onprogress = (event) => {
if (event.lengthComputable) {
const progress = (event.loaded / event.total) * 100
console.log(`文件讀取進(jìn)度: ${progress.toFixed(2)}%`)
}
}
console.log('開(kāi)始讀取文件...')
reader.readAsArrayBuffer(file)
})
}
// 導(dǎo)出Excel
const exportExcel = async () => {
console.log('exportExcel被調(diào)用')
console.log('luckysheetInstance:', luckysheetInstance)
console.log('window.luckysheet:', window.luckysheet)
console.log('window.XLSX:', window.XLSX)
try {
// 檢查XLSX庫(kù)是否可用
if (!window.XLSX || !window.XLSX.utils) {
ElMessage.error('XLSX庫(kù)未加載,請(qǐng)刷新頁(yè)面重試')
return
}
// 檢查是否有可用的Luckysheet實(shí)例
const availableInstance = luckysheetInstance || window.luckysheet
if (!availableInstance || typeof availableInstance.getAllSheets !== 'function') {
console.log('Luckysheet實(shí)例不可用,嘗試重新初始化...')
// 等待Luckysheet加載
const isLoaded = await waitForLuckysheet()
if (!isLoaded) {
ElMessage.error('Luckysheet加載失敗,請(qǐng)刷新頁(yè)面重試')
return
}
// 嘗試初始化
const initSuccess = await initLuckysheet()
if (initSuccess) {
// 等待初始化完成
setTimeout(() => {
exportExcel()
}, 1500)
} else {
ElMessage.error('表格初始化失敗,請(qǐng)刷新頁(yè)面重試')
}
return
}
ElMessage.info('正在導(dǎo)出Excel文件...')
console.log('開(kāi)始導(dǎo)出,使用實(shí)例:', availableInstance)
const data = availableInstance.getAllSheets()
console.log('獲取到的數(shù)據(jù):', data)
if (!data || data.length === 0) {
ElMessage.warning('沒(méi)有數(shù)據(jù)可導(dǎo)出')
return
}
const workbook = window.XLSX.utils.book_new()
data.forEach((sheet: any, index: number) => {
console.log(`處理工作表 ${index}:`, sheet.name)
const sheetData: any[][] = []
const celldata = Array.isArray(sheet.celldata) ? sheet.celldata : []
if (celldata.length === 0) {
console.log(`工作表 ${sheet.name} 為空,創(chuàng)建空工作表`)
const worksheet = window.XLSX.utils.aoa_to_sheet([['']])
window.XLSX.utils.book_append_sheet(workbook, worksheet, sheet.name)
return
}
// 計(jì)算最大行列
const maxRow = Math.max(...celldata.map((cell: any) => cell.r)) + 1
const maxCol = Math.max(...celldata.map((cell: any) => cell.c)) + 1
console.log(`工作表 ${sheet.name} 大小: ${maxRow}行 x ${maxCol}列`)
// 初始化二維數(shù)組
for (let r = 0; r < maxRow; r++) {
sheetData[r] = []
for (let c = 0; c < maxCol; c++) {
sheetData[r][c] = ''
}
}
// 填充數(shù)據(jù)
celldata.forEach((cell: any) => {
if (cell.v && cell.v.v !== undefined) {
sheetData[cell.r][cell.c] = cell.v.v
}
})
console.log(`工作表 ${sheet.name} 數(shù)據(jù):`, sheetData)
const worksheet = window.XLSX.utils.aoa_to_sheet(sheetData)
window.XLSX.utils.book_append_sheet(workbook, worksheet, sheet.name)
})
const fileName = `luckysheet_export_${new Date().getTime()}.xlsx`
console.log('導(dǎo)出文件名:', fileName)
window.XLSX.writeFile(workbook, fileName)
ElMessage.success('Excel文件導(dǎo)出成功!')
} catch (error) {
console.error('導(dǎo)出Excel失敗:', error)
const errorMessage = error instanceof Error ? error.message : '未知錯(cuò)誤'
ElMessage.error('導(dǎo)出Excel失敗: ' + errorMessage)
}
}
// 清空數(shù)據(jù)
const clearData = async () => {
try {
await ElMessageBox.confirm('確定要清空所有數(shù)據(jù)嗎?此操作不可恢復(fù)。', '確認(rèn)清空', {
confirmButtonText: '確定',
cancelButtonText: '取消',
type: 'warning'
})
console.log('開(kāi)始清空數(shù)據(jù)...')
// 重新初始化 Luckysheet 使用默認(rèn)數(shù)據(jù)
const success = await initLuckysheetWithData(defaultData)
if (success) {
ElMessage.success('數(shù)據(jù)已清空')
} else {
ElMessage.error('清空數(shù)據(jù)失敗,請(qǐng)刷新頁(yè)面重試')
}
} catch (error) {
if (error !== 'cancel') {
console.error('清空數(shù)據(jù)失敗:', error)
ElMessage.error('清空數(shù)據(jù)失敗')
}
}
}
// 添加工作表
const addSheet = async () => {
try {
console.log('開(kāi)始添加工作表...')
// 獲取當(dāng)前所有工作表
let currentSheets: any[] = []
if (window.luckysheet && typeof window.luckysheet.getAllSheets === 'function') {
currentSheets = window.luckysheet.getAllSheets() || []
}
console.log('當(dāng)前工作表數(shù)量:', currentSheets.length)
const sheetCount = currentSheets.length
const newSheet = {
name: `Sheet${sheetCount + 1}`,
color: '',
index: sheetCount,
status: 1,
order: sheetCount,
hide: 0,
row: 36,
column: 18,
defaultRowHeight: 19,
defaultColWidth: 73,
celldata: [] as any[],
config: {},
scrollLeft: 0,
scrollTop: 0,
luckysheet_select_save: [],
calcChain: [],
isPivotTable: false,
pivotTable: {},
filter_select: {},
filter: null,
luckysheet_alternateformat_save: [],
luckysheet_alternateformat_save_modelCustom: [],
luckysheet_conditionformat_save: {},
frozen: {},
chart: [],
zoomRatio: 1,
image: [],
showGridLines: 1,
dataVerification: {}
}
console.log('新工作表配置:', newSheet)
// 合并現(xiàn)有工作表和新工作表
const newData = [...currentSheets, newSheet]
console.log('合并后的數(shù)據(jù):', newData)
// 重新初始化 Luckysheet 使用新數(shù)據(jù)
const success = await initLuckysheetWithData(newData)
if (success) {
ElMessage.success('工作表添加成功')
} else {
ElMessage.error('添加工作表失敗,請(qǐng)刷新頁(yè)面重試')
}
} catch (error) {
console.error('添加工作表失敗:', error)
ElMessage.error('添加工作表失敗: ' + (error instanceof Error ? error.message : '未知錯(cuò)誤'))
}
}
// 獲取數(shù)據(jù)
const getData = async () => {
console.log("1111111");
console.log('getData被調(diào)用')
console.log('luckysheetInstance:', luckysheetInstance)
console.log('window.luckysheet:', window.luckysheet)
// 檢查是否有可用的Luckysheet實(shí)例
const availableInstance = luckysheetInstance || window.luckysheet
if (!availableInstance || typeof availableInstance.getAllSheets !== 'function') {
console.log('Luckysheet實(shí)例不可用,嘗試重新初始化...')
// 等待Luckysheet加載
const isLoaded = await waitForLuckysheet()
if (!isLoaded) {
ElMessage.error('Luckysheet加載失敗,請(qǐng)刷新頁(yè)面重試')
return
}
// 嘗試初始化
const initSuccess = await initLuckysheet()
if (initSuccess) {
// 等待初始化完成
setTimeout(() => {
getData()
}, 1500)
} else {
ElMessage.error('表格初始化失敗,請(qǐng)刷新頁(yè)面重試')
}
return
}
try {
// 使用可用的實(shí)例
const instance = availableInstance
// 獲取所有工作表數(shù)據(jù)
const sheets = instance.getAllSheets()
console.log('所有工作表數(shù)據(jù):', sheets)
// 獲取當(dāng)前工作表數(shù)據(jù)
const currentSheet = instance.getSheetData()
console.log('當(dāng)前工作表數(shù)據(jù):', currentSheet)
// 獲取選中的單元格數(shù)據(jù)
const selectedRange = instance.getRangeByTxt()
console.log('選中的單元格范圍:', selectedRange)
ElMessage.success('數(shù)據(jù)已獲取,請(qǐng)查看控制臺(tái)')
} catch (error) {
console.error('獲取數(shù)據(jù)失敗:', error)
ElMessage.error('獲取數(shù)據(jù)失敗')
}
}
// 打印工作表
const printSheet = () => {
const container = document.getElementById('luckysheet');
if (!container) {
window.print();
return;
}
// 獲取內(nèi)容實(shí)際高度和寬度
const grid = container.querySelector('.luckysheet-grid-container');
const contentHeight = grid ? grid.scrollHeight : container.scrollHeight;
const contentWidth = grid ? grid.scrollWidth : container.scrollWidth;
// 記錄原始尺寸
const originalHeight = container.style.height;
const originalWidth = container.style.width;
// 設(shè)置為內(nèi)容實(shí)際尺寸
container.style.height = contentHeight + 'px';
container.style.width = contentWidth + 'px';
// 等待渲染后打印
setTimeout(() => {
window.print();
// 恢復(fù)原始尺寸
container.style.height = originalHeight;
container.style.width = originalWidth;
}, 500);
};
// 組件掛載時(shí)初始化
onMounted(async () => {
console.log('組件掛載,開(kāi)始初始化...')
console.log('window.luckysheet:', window.luckysheet)
// 等待Luckysheet加載完成
const isLoaded = await waitForLuckysheet()
if (isLoaded) {
console.log('Luckysheet已加載,開(kāi)始初始化')
await initLuckysheet()
} else {
console.error('Luckysheet加載失敗')
ElMessage.error('Luckysheet加載失敗,請(qǐng)刷新頁(yè)面重試')
}
})
// 組件卸載時(shí)清理
onUnmounted(() => {
if (luckysheetInstance) {
luckysheetInstance.destroy()
}
})
</script>
<style scoped lang="scss">
.luckysheet-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.toolbar {
padding: 16px;
background: #f5f5f5;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: flex-end;
gap: 12px;
flex-wrap: wrap;
}
.luckysheet-wrapper {
flex: 1;
min-height: 600px;
position: relative;
}
/* 確保Luckysheet容器有足夠的高度 */
#luckysheet {
width: 100%;
height: 100%;
min-height: 600px;
}
/* 響應(yīng)式設(shè)計(jì) */
@media (max-width: 768px) {
.toolbar {
padding: 12px;
gap: 8px;
}
.toolbar .el-button {
padding: 8px 12px;
font-size: 12px;
}
}
</style>
<style>
.luckysheet_info_detail div.luckysheet_info_detail_back{
display: none !important;
}
.luckysheet_info_detail_update,.luckysheet_info_detail_save{
display: none !important;
}
#luckysheet .luckysheet-share-logo,
.luckysheet .luckysheet-share-logo,
.luckysheet-share-logo{
background-image:url('@/assets/imgs/logo.png') !important;
background-size: contain !important;
background-repeat: no-repeat !important;
background-position: center !important;
width: 120px !important;
height: 40px !important;
}
@media print {
body * {
visibility: hidden;
}
#luckysheet,
#luckysheet * {
visibility: visible;
}
#luckysheet {
position: static !important;
left: 0 !important;
top: 0 !important;
width: 100% !important;
min-height: 100vh !important;
height: auto !important;
background: #fff !important;
overflow: visible !important;
box-sizing: border-box !important;
padding: 0 !important;
margin: 0 !important;
page-break-inside: avoid !important;
}
.toolbar {
display: none !important;
}
}
</style>
到此這篇關(guān)于vue3+luckysheet實(shí)現(xiàn)在線編輯Excel的項(xiàng)目實(shí)踐的文章就介紹到這了,更多相關(guān)vue3 luckysheet 在線編輯Excel內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解vue2.0組件通信各種情況總結(jié)與實(shí)例分析
本篇文章主要介紹了詳解vue2.0組件通信各種情況總結(jié)與實(shí)例分析,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-03-03
Vue.js 單頁(yè)面多路由區(qū)域操作的實(shí)例詳解
這篇文章主要介紹了 Vue.js 單頁(yè)面多路由區(qū)域操作的實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-07-07
Vue3.0?axios跨域請(qǐng)求代理服務(wù)器配置方式
這篇文章主要介紹了Vue3.0?axios跨域請(qǐng)求代理服務(wù)器配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04
el-form-item?prop屬性動(dòng)態(tài)綁定不生效問(wèn)題及解決
這篇文章主要介紹了el-form-item?prop屬性動(dòng)態(tài)綁定不生效問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
vue實(shí)現(xiàn)點(diǎn)擊按鈕“查看詳情”彈窗展示詳情列表操作
這篇文章主要介紹了vue實(shí)現(xiàn)點(diǎn)擊按鈕“查看詳情”彈窗展示詳情列表操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
Vue模板語(yǔ)法中數(shù)據(jù)綁定的實(shí)例代碼
這篇文章主要介紹了Vue模板語(yǔ)法中數(shù)據(jù)綁定的實(shí)例代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2019-05-05
Vue.js刷新當(dāng)前頁(yè)面的常見(jiàn)方法
這篇文章主要介紹了Vue.js刷新當(dāng)前頁(yè)面的常見(jiàn)方法,文中通過(guò)代碼示例講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-12-12

