vue3 + luckysheet 實現在線編輯Excel

效果圖奉上:
在這里插入圖片描述
引入的依賴:

 "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" href="https://cdn.jsdelivr.net/npm/luckysheet@2.1.13/dist/plugins/css/pluginsCss.css" /><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/luckysheet@2.1.13/dist/plugins/plugins.css" /><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/luckysheet@2.1.13/dist/css/luckysheet.css" /><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/luckysheet@2.1.13/dist/assets/iconfont/iconfont.css" /><!-- 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 庫 --><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>導入Excel</el-button><el-button type="success" @click="exportExcel"><el-icon><Download /></el-icon>導出Excel</el-button><el-button type="warning" @click="clearData"><el-icon><Delete /></el-icon>清空數據</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'// 響應式數據
const fileInput = ref<HTMLInputElement>()
let luckysheetInstance: any = null
const luckysheetRef = ref<HTMLDivElement>()
// 默認數據
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: '這是一個示例表格',ct: { fa: 'General', t: 'g' },m: '這是一個示例表格',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('開始初始化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('創建Luckysheet實例...')console.log('使用的配置:', options)// 直接調用全局方法window.luckysheet.create(options)// 檢查是否創建成功setTimeout(() => {const sheets = window.luckysheet.getAllSheets()console.log('初始化后獲取到的sheets:', sheets)if (sheets && sheets.length > 0) {console.log('Luckysheet初始化成功')luckysheetInstance = window.luckysheet // 使用全局對象作為實例resolve(true)} else {console.error('Luckysheet初始化失敗,沒有獲取到sheets')resolve(false)}}, 1000)} catch (error) {console.error('創建Luckysheet實例失敗:', error)resolve(false)}})
}// 等待Luckysheet加載
const waitForLuckysheet = (maxAttempts = 10): Promise<boolean> => {return new Promise((resolve) => {let attempts = 0const checkLuckysheet = () => {attempts++console.log(`檢查Luckysheet加載狀態 (${attempts}/${maxAttempts})`)if (window.luckysheet && typeof window.luckysheet.create === 'function') {console.log('Luckysheet已加載完成')resolve(true)} else if (attempts >= maxAttempts) {console.error('Luckysheet加載超時')resolve(false)} else {setTimeout(checkLuckysheet, 500)}}checkLuckysheet()})
}// 導入Excel文件
const importExcel = () => {console.log('importExcel被調用')console.log('window.XLSX:', window.XLSX)// 檢查XLSX庫是否可用if (!window.XLSX || !window.XLSX.utils) {ElMessage.error('XLSX庫未加載,請刷新頁面重試')return}fileInput.value?.click()
}// 處理文件選擇
const handleFileChange = async (event: Event) => {const target = event.target as HTMLInputElementconst file = target.files?.[0]if (!file) returnconsole.log('選擇的文件:', file.name, file.size)try {ElMessage.info('正在解析Excel文件...')const data = await parseExcelFile(file)console.log('解析到的數據:', data)if (data && data.length > 0) {// 直接進行重新初始化,不再檢查可用方法console.log('開始加載數據到Luckysheet...')console.log('解析到的數據:', data)try {// 直接使用重新初始化的方式,避免transToData的錯誤console.log('跳過transToData方法,直接重新初始化...')ElMessage.info('正在加載Excel數據...')const initSuccess = await initLuckysheetWithData(data)if (initSuccess) {// 初始化成功后,檢查導入結果setTimeout(() => {try {const sheets = window.luckysheet.getAllSheets()console.log('導入完成,檢查結果:')console.log('- 工作表數量:', sheets.length)sheets.forEach((sheet: any, index: number) => {console.log(`- 工作表 ${index}: ${sheet.name}`)console.log(`  - 行數: ${sheet.row}`)console.log(`  - 列數: ${sheet.column}`)console.log(`  - 單元格數據: ${sheet.celldata ? sheet.celldata.length : 0} 個`)if (sheet.config && sheet.config.merge) {console.log(`  - 合并單元格: ${sheet.config.merge.length} 個`)}})// 顯示成功消息const totalCells = sheets.reduce((total: number, sheet: any) => {return total + (sheet.celldata ? sheet.celldata.length : 0)}, 0)// 生成詳細的導入報告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('導入報告:', importReport)// 顯示詳細成功消息const sheetDetails = importReport.sheets.map(s =>`${s.name}(${s.cells}個單元格)`).join('、')ElMessage.success(`Excel文件導入成功!共導入 ${importReport.totalSheets} 個工作表,${importReport.totalCells} 個單元格數據`)console.log(`工作表詳情: ${sheetDetails}`)// 顯示合并單元格檢測信息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}個)`).join('、')console.log(`檢測到合并單元格: ${mergeInfo}`)ElMessage.info(`注意:檢測到合并單元格但暫時未應用,以避免顯示錯誤`)}} catch (error) {console.error('檢查導入結果時出錯:', error)ElMessage.success('Excel文件導入成功!')}}, 500)} else {ElMessage.error('表格初始化失敗,請刷新頁面重試')}} catch (loadError) {console.error('加載數據失敗:', loadError)ElMessage.error('加載數據失敗: ' + (loadError instanceof Error ? loadError.message : '未知錯誤'))}} else {ElMessage.warning('Excel文件為空或格式不正確')}} catch (error) {console.error('導入Excel失敗:', error)const errorMessage = error instanceof Error ? error.message : '未知錯誤'ElMessage.error('導入Excel失敗: ' + errorMessage)}target.value = ''
}// 使用新數據初始化Luckysheet
const initLuckysheetWithData = (data: any[]): Promise<boolean> => {return new Promise((resolve) => {console.log('開始使用新數據初始化Luckysheet...')console.log('新數據:', data)// 檢查容器是否存在const container = document.getElementById('luckysheet')console.log('容器元素:', container)if (!container) {console.error('找不到luckysheet容器')resolve(false)return}// 清空容器container.innerHTML = ''// 驗證和清理數據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 || {}}// 暫時禁用合并單元格處理以避免內部錯誤if (cleanSheet.config.merge) {console.log(`工作表 ${index} 移除合并單元格配置以避免內部錯誤`)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('使用新數據創建Luckysheet實例...')console.log('使用的配置:', options)// 直接調用全局方法window.luckysheet.create(options)// 檢查是否創建成功setTimeout(() => {try {const sheets = window.luckysheet.getAllSheets()console.log('重新初始化后獲取到的sheets:', sheets)if (sheets && sheets.length > 0) {console.log('Luckysheet重新初始化成功')luckysheetInstance = window.luckysheet // 使用全局對象作為實例resolve(true)} else {console.error('Luckysheet重新初始化失敗,沒有獲取到sheets')resolve(false)}} catch (error) {console.error('檢查初始化結果時出錯:', error)// 即使有錯誤,如果容器中有內容,也認為初始化成功if (container.children.length > 0) {console.log('容器中有內容,認為初始化成功')luckysheetInstance = window.luckysheetresolve(true)} else {resolve(false)}}}, 1000)} catch (error) {console.error('使用新數據創建Luckysheet實例失敗:', error)resolve(false)}})
}// 解析Excel文件
const parseExcelFile = (file: File): Promise<any[]> => {return new Promise((resolve, reject) => {console.log('開始解析Excel文件...')console.log('文件信息:', {name: file.name,size: file.size,type: file.type,lastModified: file.lastModified})// 檢查XLSX庫是否可用if (!window.XLSX || !window.XLSX.utils) {console.error('XLSX庫未加載')reject(new Error('XLSX庫未加載'))return}// 檢查文件類型const validTypes = ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx'application/vnd.ms-excel', // .xls'application/octet-stream' // 某些系統可能顯示為這個類型]if (!validTypes.includes(file.type) && !file.name.match(/\.(xlsx|xls)$/i)) {console.error('不支持的文件類型:', file.type)reject(new Error('不支持的文件類型,請選擇.xlsx或.xls文件'))return}const reader = new FileReader()reader.onload = (e) => {try {console.log('文件讀取完成,開始解析...')const result = e.target?.resultconsole.log('讀取結果類型:', typeof result)if (!result) {reject(new Error('文件讀取結果為空'))return}const data = new Uint8Array(result as ArrayBuffer)console.log('轉換為Uint8Array,長度:', data.length)// 嘗試不同的解析方式let workbooktry {workbook = window.XLSX.read(data, { type: 'array', cellStyles: true })} catch (readError) {console.error('使用array類型解析失敗,嘗試binary類型:', readError)try {workbook = window.XLSX.read(data, { type: 'binary', cellStyles: true })} catch (binaryError) {console.error('使用binary類型解析也失敗:', binaryError)reject(new Error('無法解析Excel文件,請檢查文件格式'))return}}console.log('工作簿解析成功:', workbook)console.log('工作表名稱:', workbook.SheetNames)if (!workbook.SheetNames || workbook.SheetNames.length === 0) {reject(new Error('Excel文件中沒有找到工作表'))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[] = []// 處理合并單元格 - 暫時禁用以避免mergeCalculation錯誤if (worksheet['!merges']) {console.log(`工作表 ${sheetName} 檢測到合并單元格:`, worksheet['!merges'].length, '個')console.log('注意:合并單元格已識別但暫時禁用以避免內部錯誤')// 暫時注釋掉合并單元格處理,但保留識別信息/*worksheet['!merges'].forEach((merge: any, mergeIndex: number) => {try {// 確保合并單元格數據格式正確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}// 驗證合并單元格數據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(`跳過無效的合并單元格 ${mergeIndex}:`, mergeData)}} catch (mergeError) {console.warn(`處理合并單元格 ${mergeIndex} 時出錯:`, 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),// 只有有背景色時才加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 = 1if (style.font.italic) cellData.v.it = 1if (style.font.size) cellData.v.fs = style.font.sizeif (style.font.color) {const color = style.font.colorif (color.rgb) {cellData.v.fc = '#' + color.rgb.substring(2)}}}// 背景色if (style.fill) {if (style.fill.fgColor) {const bgColor = style.fill.fgColorif (bgColor.rgb) {cellData.v.bg = '#' + bgColor.rgb.substring(2)}}}// 對齊方式if (style.alignment) {const alignment = style.alignmentif (alignment.horizontal) {switch (alignment.horizontal) {case 'left':cellData.v.ff = 0breakcase 'center':cellData.v.ff = 1breakcase 'right':cellData.v.ff = 2break}}if (alignment.vertical) {switch (alignment.vertical) {case 'top':cellData.v.vt = 0breakcase 'middle':cellData.v.vt = 1breakcase 'bottom':cellData.v.vt = 2break}}}// 邊框if (style.border) {const border = style.borderif (border.top || border.bottom || border.left || border.right) {cellData.v.cl = 1}}}// 處理數字格式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} 轉換后的celldata:`, celldata)console.log(`工作表 ${sheetName} 合并單元格:`, merges)// 驗證數據完整性if (celldata.length === 0) {console.warn(`工作表 ${sheetName} 沒有數據,添加默認單元格`)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}})}// 創建工作表配置 - 不處理邊框const sheetConfig: any = {}// 不再賦值borderInfo,保持默認網格線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('所有工作表轉換完成:', sheets)resolve(sheets)} catch (error) {console.error('解析Excel文件時出錯:', error)reject(error)}}reader.onerror = (error) => {console.error('文件讀取失敗:', error)reject(new Error('文件讀取失敗'))}reader.onprogress = (event) => {if (event.lengthComputable) {const progress = (event.loaded / event.total) * 100console.log(`文件讀取進度: ${progress.toFixed(2)}%`)}}console.log('開始讀取文件...')reader.readAsArrayBuffer(file)})
}// 導出Excel
const exportExcel = async () => {console.log('exportExcel被調用')console.log('luckysheetInstance:', luckysheetInstance)console.log('window.luckysheet:', window.luckysheet)console.log('window.XLSX:', window.XLSX)try {// 檢查XLSX庫是否可用if (!window.XLSX || !window.XLSX.utils) {ElMessage.error('XLSX庫未加載,請刷新頁面重試')return}// 檢查是否有可用的Luckysheet實例const availableInstance = luckysheetInstance || window.luckysheetif (!availableInstance || typeof availableInstance.getAllSheets !== 'function') {console.log('Luckysheet實例不可用,嘗試重新初始化...')// 等待Luckysheet加載const isLoaded = await waitForLuckysheet()if (!isLoaded) {ElMessage.error('Luckysheet加載失敗,請刷新頁面重試')return}// 嘗試初始化const initSuccess = await initLuckysheet()if (initSuccess) {// 等待初始化完成setTimeout(() => {exportExcel()}, 1500)} else {ElMessage.error('表格初始化失敗,請刷新頁面重試')}return}ElMessage.info('正在導出Excel文件...')console.log('開始導出,使用實例:', availableInstance)const data = availableInstance.getAllSheets()console.log('獲取到的數據:', data)if (!data || data.length === 0) {ElMessage.warning('沒有數據可導出')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} 為空,創建空工作表`)const worksheet = window.XLSX.utils.aoa_to_sheet([['']])window.XLSX.utils.book_append_sheet(workbook, worksheet, sheet.name)return}// 計算最大行列const maxRow = Math.max(...celldata.map((cell: any) => cell.r)) + 1const maxCol = Math.max(...celldata.map((cell: any) => cell.c)) + 1console.log(`工作表 ${sheet.name} 大小: ${maxRow}行 x ${maxCol}列`)// 初始化二維數組for (let r = 0; r < maxRow; r++) {sheetData[r] = []for (let c = 0; c < maxCol; c++) {sheetData[r][c] = ''}}// 填充數據celldata.forEach((cell: any) => {if (cell.v && cell.v.v !== undefined) {sheetData[cell.r][cell.c] = cell.v.v}})console.log(`工作表 ${sheet.name} 數據:`, 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('導出文件名:', fileName)window.XLSX.writeFile(workbook, fileName)ElMessage.success('Excel文件導出成功!')} catch (error) {console.error('導出Excel失敗:', error)const errorMessage = error instanceof Error ? error.message : '未知錯誤'ElMessage.error('導出Excel失敗: ' + errorMessage)}
}// 清空數據
const clearData = async () => {try {await ElMessageBox.confirm('確定要清空所有數據嗎?此操作不可恢復。', '確認清空', {confirmButtonText: '確定',cancelButtonText: '取消',type: 'warning'})console.log('開始清空數據...')// 重新初始化 Luckysheet 使用默認數據const success = await initLuckysheetWithData(defaultData)if (success) {ElMessage.success('數據已清空')} else {ElMessage.error('清空數據失敗,請刷新頁面重試')}} catch (error) {if (error !== 'cancel') {console.error('清空數據失敗:', error)ElMessage.error('清空數據失敗')}}
}// 添加工作表
const addSheet = async () => {try {console.log('開始添加工作表...')// 獲取當前所有工作表let currentSheets: any[] = []if (window.luckysheet && typeof window.luckysheet.getAllSheets === 'function') {currentSheets = window.luckysheet.getAllSheets() || []}console.log('當前工作表數量:', currentSheets.length)const sheetCount = currentSheets.lengthconst 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)// 合并現有工作表和新工作表const newData = [...currentSheets, newSheet]console.log('合并后的數據:', newData)// 重新初始化 Luckysheet 使用新數據const success = await initLuckysheetWithData(newData)if (success) {ElMessage.success('工作表添加成功')} else {ElMessage.error('添加工作表失敗,請刷新頁面重試')}} catch (error) {console.error('添加工作表失敗:', error)ElMessage.error('添加工作表失敗: ' + (error instanceof Error ? error.message : '未知錯誤'))}
}// 獲取數據
const getData = async () => {console.log("1111111");console.log('getData被調用')console.log('luckysheetInstance:', luckysheetInstance)console.log('window.luckysheet:', window.luckysheet)// 檢查是否有可用的Luckysheet實例const availableInstance = luckysheetInstance || window.luckysheetif (!availableInstance || typeof availableInstance.getAllSheets !== 'function') {console.log('Luckysheet實例不可用,嘗試重新初始化...')// 等待Luckysheet加載const isLoaded = await waitForLuckysheet()if (!isLoaded) {ElMessage.error('Luckysheet加載失敗,請刷新頁面重試')return}// 嘗試初始化const initSuccess = await initLuckysheet()if (initSuccess) {// 等待初始化完成setTimeout(() => {getData()}, 1500)} else {ElMessage.error('表格初始化失敗,請刷新頁面重試')}return}try {// 使用可用的實例const instance = availableInstance// 獲取所有工作表數據const sheets = instance.getAllSheets()console.log('所有工作表數據:', sheets)// 獲取當前工作表數據const currentSheet = instance.getSheetData()console.log('當前工作表數據:', currentSheet)// 獲取選中的單元格數據const selectedRange = instance.getRangeByTxt()console.log('選中的單元格范圍:', selectedRange)ElMessage.success('數據已獲取,請查看控制臺')} catch (error) {console.error('獲取數據失敗:', error)ElMessage.error('獲取數據失敗')}
}// 打印工作表
const printSheet = () => {const container = document.getElementById('luckysheet');if (!container) {window.print();return;}// 獲取內容實際高度和寬度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;// 設置為內容實際尺寸container.style.height = contentHeight + 'px';container.style.width = contentWidth + 'px';// 等待渲染后打印setTimeout(() => {window.print();// 恢復原始尺寸container.style.height = originalHeight;container.style.width = originalWidth;}, 500);
};// 組件掛載時初始化
onMounted(async () => {console.log('組件掛載,開始初始化...')console.log('window.luckysheet:', window.luckysheet)// 等待Luckysheet加載完成const isLoaded = await waitForLuckysheet()if (isLoaded) {console.log('Luckysheet已加載,開始初始化')await initLuckysheet()} else {console.error('Luckysheet加載失敗')ElMessage.error('Luckysheet加載失敗,請刷新頁面重試')}
})// 組件卸載時清理
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;
}/* 響應式設計 */
@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>

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/912753.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/912753.shtml
英文地址,請注明出處:http://en.pswp.cn/news/912753.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux下MinIO分布式安裝部署

文章目錄 一、MinIO簡單說明二、MinIO分布式安裝部署1、關閉SELINUX2、開啟防火墻2.1、關閉firewall&#xff1a;2.2、安裝iptables防火墻 3、安裝MinIO4、添加MinIO集群控制腳本4.1添加啟動腳本4.2添加關閉腳本 5、MinIO控制臺使用 一、MinIO簡單說明 1、MinIO是一個輕量的對…

Codeforces Round 980 (Div. 2)

ABC 略 D 這個過程一定是由1向后跳的過程中穿插有幾次向前一步一步走。直到跳到一個位置后再把前面所有沒有走過的位置倒序走一遍。總分就等于最大位置的前綴和-前面所有起跳位置和。前綴和固定我們只需要求到每個位置的最小起跳和即可。對于這個向后跳和向前走的過程我們可以…

Langchain實現rag功能

RAG&#xff08;檢索增強生成&#xff09;的核心是通過外部知識庫增強大模型回答的準確性和針對性&#xff0c;其工作流程與優化策略如下&#xff1a; 一、RAG 核心流程 ?知識庫構建? ?文檔加載與分割?&#xff1a;將非結構化文檔&#xff08;PDF、Markdown等&#xff09;…

算法筆記上機訓練實戰指南刷題

算法筆記上機訓練實戰指南刷題記錄 文章目錄 算法筆記上機訓練實戰指南刷題記錄模擬B1001 害死人不償命的(3n1)猜想B1011 AB 和 CB1016 部分ABB1026 程序運行時間B1046劃拳B1008數組元素循環右移問題B1012 數字分類B1018 錘子剪刀布A1042 Shuffling Machine 每天兩題&#xff0…

MYSQL基礎內容

一、介紹 1.不用數據庫&#xff1a;使用IO流對數據進行管理 2.使用數據庫&#xff1a;使用SQL語句對開發的數據進行管理&#xff0c;能儲存上億條數據 3.MYSQL&#xff1a; 是流行的關系型數據庫管理系統之一&#xff0c;將數據保存在不同的數據表中&#xff0c;通過表與表之…

音視頻會議服務搭建(設計方案)-01

前言 最近在做音視頻會議系統服務搭建的工作任務&#xff0c;因為內容過多&#xff0c;我會逐篇分享相關的設計方案、開發思路、編程語言、使用的組件集合等等。如果你也有大型音視頻會議系統搭建架構的需求&#xff0c;希望這些可以對你有所幫助。 EchoMeet 音視頻會議系統架構…

刷leetcode hot100/準備機試--圖

圖的基礎知識【這部分建議使用acm模式】 圖論理論基礎 | 代碼隨想錄 存儲&#xff1a; 一般有鄰接表【適合稀疏圖】【數組 鏈表 】和鄰接矩陣【適合稠密圖】存儲方式 注意鄰接表 和 鄰接矩陣的寫法都要掌握&#xff01; 鄰接矩陣 n個節點&#xff0c;申請n*n或者&#xf…

無代碼自動化測試工具介紹

無代碼自動化測試工具允許用戶無需編寫代碼即可創建和運行測試,通過拖拽式界面或錄制回放等可視化界面進行操作。 這些工具利用圖形用戶界面和預定義命令來創建測試,使非編程人員也能進行自動化測試。 無代碼自動化測試工具使團隊能夠: 使用直觀的拖拽界面開發和執行自動化…

python學習打卡day58

DAY 58 經典時序預測模型2 知識點回顧&#xff1a; 時序建模的流程時序任務經典單變量數據集ARIMA&#xff08;p&#xff0c;d&#xff0c;q&#xff09;模型實戰SARIMA摘要圖的理解處理不平穩的2種差分 n階差分---處理趨勢季節性差分---處理季節性 建立一個ARIMA模型&#xf…

分布式鎖的實現方式:使用 Redisson 實現分布式鎖( Spring Boot )

Redisson提供了分布式和可擴展的Java數據結構&#xff0c;包括分布式鎖的實現。 1. 添加依賴 在pom.xml中添加Redisson依賴&#xff1a; <dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId>…

Web基礎關鍵_004_CSS(二)

目 錄 一、樣式 1.行內樣式 2.內部樣式 3.外部樣式 二、選擇器優先級 1.非關系選擇器 2.關系選擇器 三、屬性 四、盒子模型 五、元素 1.塊級元素 2.行內元素 3.行內塊級元素 4.元素類型轉換 六、浮動 七、定位 1.靜態定位 2.相對定位 3.絕對定位 4.固定定位 …

數據使用權與所有權分離:能否誕生“數據租賃”市場

——首席數據官高鵬律師數字經濟團隊創作&#xff0c;AI輔助 數據如礦藏&#xff0c;開采需“契約” 想象一座蘊藏著無盡資源的數字礦山&#xff1a;企業或個人擁有數據的“所有權”&#xff0c;如同手握礦脈的產權&#xff0c;但若無法高效挖掘其價值&#xff0c;礦石終將沉…

【esp32s3】2 - 第一個組件

下面的內容編寫時間跨度有點大&#xff0c;亂了得一團&#xff0c;也沒ai整理。食之無味&#xff0c;棄之可惜。 推薦筆記&#xff1a;ESP32 之 ESP-IDF 教學&#xff08;十八&#xff09;—— 組件配置&#xff08;KConfig&#xff09; 推薦筆記&#xff1a;Kconfig 拓展 樂鑫…

【Java_EE】單例模式、阻塞隊列、線程池、定時器

目錄 單例模式 餓漢模式<代碼> 懶漢模式<代碼> 阻塞隊列 阻塞隊列概念 阻塞隊列解決的問題 阻塞隊列工作原理 阻塞隊列的優/缺點 優點 缺點 模擬實現阻塞隊列<代碼> 線程池 線程池概念 線程池解決的問題 線程池參數 四種拒絕策略 線程池工作…

Redis初識第七期---ZSet的命令和應用場景

ZSet相較于Set來說&#xff0c;它又是有序的&#xff0c;這個有序指的就是我們通常意義上的有序了&#xff0c;ZSet內部中是按照升序來排序的。 排序規則&#xff1a;ZSet相較于Set來說&#xff0c;它內部引入了一個新的屬性&#xff1a;分數&#xff08;Score&#xff09;&am…

Wps開放平臺v5升級v7上傳實體文件踩坑(Java使用restTemplate)

背景&#xff1a; 最近接到一個老項目需求&#xff0c;之前開發的WPS開放平臺文件&#xff08;商密集成&#xff09;預覽功能因為升級需要重新對接api&#xff0c;新的上傳文件接口踩坑特意記錄一下。 這里出問題的是第二步&#xff0c;請求文件上傳信息 踩坑代碼 調用后403 p…

啥時候上RAG?啥時候上微調?丨實戰筆記

哈嘍&#xff0c;大家好&#x1f44f; 我是阿星&#xff01; 現在很多AI科普文章都會提到微調&#xff0c;RAG。 但是沒有實戰的過的同學可能會問&#x1f914;—— 啥時候用RAG&#xff1f;啥時候用微調呢&#xff1f;有啥區別&#xff1f;不都是讓模型增加知識面的嗎&…

RabbitMQ-基礎篇

前言&#xff1a; 今天開始學RabbitMQ,還是跟著黑馬的課程。 今日所學&#xff1a; RabbitMQ介紹RabbitMQ入門Java客戶端中的MQ 1.RabbitMQ介紹 1.1 什么是RabbitMQ RabbitMQ 是一個開源的消息代理軟件&#xff08;消息隊列中間件&#xff09;&#xff0c;實現了高級消息…

docker-compose配置redis哨兵詳細步驟和配置文件

docker-compose配置redis哨兵詳細步驟和配置文件 目錄結構調整 redis-cluster/ ├── config/ │ ├── master.conf # 主節點配置 │ ├── slave1.conf # 從節點1配置 │ ├── slave2.conf # 從節點2配置 │ ├── sentinel1.…

多模態大語言模型arxiv論文略讀(146)

Exploring Response Uncertainty in MLLMs: An Empirical Evaluation under Misleading Scenarios ?? 論文標題&#xff1a;Exploring Response Uncertainty in MLLMs: An Empirical Evaluation under Misleading Scenarios ?? 論文作者&#xff1a;Yunkai Dang, Mengxi G…