Fix Luckysheet functionlist error and improve file processing

This commit is contained in:
sheetEasy AI Team
2025-06-23 14:35:15 +09:00
parent de6b4debac
commit d9a198a157
3 changed files with 375 additions and 154 deletions

View File

@@ -3,3 +3,192 @@ description:
globs: globs:
alwaysApply: false alwaysApply: false
--- ---
# Luckysheet Functionlist Error Prevention
**Root Cause Analysis:**
- Luckysheet internally references multiple functionlist objects at different namespaces
- Timing issues between library loading and object initialization
- Incomplete plugin.js loading which should initialize functionlist objects
## **Critical Loading Order (MUST Follow)**
```typescript
// ✅ DO: Complete library loading sequence
const loadSequence = async () => {
// 1. jQuery (Luckysheet dependency)
await loadResource("js", "https://code.jquery.com/jquery-3.6.0.min.js", "jquery");
// 2. CSS files (in specific order)
await loadResource("css", "/luckysheet/dist/plugins/css/pluginsCss.css", "plugins-css");
await loadResource("css", "/luckysheet/dist/plugins/plugins.css", "plugins-main-css");
await loadResource("css", "/luckysheet/dist/css/luckysheet.css", "luckysheet-css");
await loadResource("css", "/luckysheet/dist/assets/iconfont/iconfont.css", "iconfont-css");
// 3. LuckyExcel (for Excel file processing)
await loadResource("js", "/luckysheet/dist/luckyexcel.umd.js", "luckyexcel");
// 4. Plugin JS (CRITICAL: initializes functionlist)
await loadResource("js", "/luckysheet/dist/plugins/js/plugin.js", "plugin-js");
// 5. Luckysheet main library
await loadResource("js", "/luckysheet/dist/luckysheet.umd.js", "luckysheet");
// 6. CRITICAL: Wait for internal object initialization
await new Promise(resolve => setTimeout(resolve, 1000));
};
```
## **Multi-Level Functionlist Initialization**
```typescript
// ✅ DO: Initialize all possible functionlist reference paths
const initializeFunctionlist = () => {
try {
// Level 1: Core Store objects
if (!window.Store) window.Store = {};
if (!window.Store.functionlist) window.Store.functionlist = [];
if (!window.Store.luckysheet_function) window.Store.luckysheet_function = {};
// Level 2: Global function objects
if (!window.luckysheet_function) window.luckysheet_function = {};
if (!window.functionlist) window.functionlist = [];
// Level 3: Luckysheet internal objects
if (window.luckysheet) {
if (!window.luckysheet.functionlist) window.luckysheet.functionlist = [];
if (!window.luckysheet.formula) window.luckysheet.formula = {};
if (!window.luckysheet.formulaCache) window.luckysheet.formulaCache = {};
if (!window.luckysheet.formulaObjects) window.luckysheet.formulaObjects = {};
}
// Level 4: Store internal structure
if (!window.Store.config) window.Store.config = {};
if (!window.Store.luckysheetfile) window.Store.luckysheetfile = [];
if (!window.Store.currentSheetIndex) window.Store.currentSheetIndex = 0;
} catch (error) {
console.warn("Functionlist initialization warning:", error);
}
};
```
## **TypeScript Window Interface Extension**
```typescript
// ✅ DO: Extend Window interface for all Luckysheet globals
declare global {
interface Window {
luckysheet: any;
LuckyExcel: any;
$: any; // jQuery
Store: any; // Luckysheet Store
luckysheet_function: any; // Luckysheet function list
functionlist: any[]; // Global functionlist
}
}
```
## **Critical Timing Requirements**
- **MUST** call `initializeFunctionlist()` at three points:
1. After library loading sequence completion
2. After 1000ms wait period for internal initialization
3. Immediately before `luckysheet.create()` call
- **MUST** wait at least 1000ms after all libraries are loaded
- **MUST** verify all functionlist objects exist before calling `luckysheet.create()`
## **Error Recovery Pattern**
```typescript
// ✅ DO: Implement robust error recovery
try {
// Final verification before luckysheet.create()
initializeFunctionlist();
// Verify critical objects exist
const verificationResults = {
store: !!window.Store,
functionlist: !!window.Store?.functionlist,
luckysheet: !!window.luckysheet,
createFunction: typeof window.luckysheet?.create === "function"
};
if (!verificationResults.luckysheet || !verificationResults.createFunction) {
throw new Error("Luckysheet not properly initialized");
}
window.luckysheet.create(options);
} catch (error) {
console.error("Luckysheet initialization failed:", error);
// Implement retry logic or fallback
}
```
## **Common Anti-Patterns to Avoid**
```typescript
// ❌ DON'T: Skip plugin.js loading
// plugin.js is CRITICAL for functionlist initialization
// ❌ DON'T: Use insufficient wait times
await new Promise(resolve => setTimeout(resolve, 100)); // TOO SHORT
// ❌ DON'T: Initialize only Store.functionlist
// Multiple objects need initialization
// ❌ DON'T: Call luckysheet.create() immediately after library load
// Internal objects need time to initialize
```
## **Debugging Checklist**
When functionlist errors occur:
1. ✅ Verify all libraries loaded in correct order
2. ✅ Check plugin.js is included and loaded
3. ✅ Confirm 1000ms wait after library loading
4. ✅ Verify all functionlist objects are arrays/objects (not undefined)
5. ✅ Check console for library loading errors
6. ✅ Ensure complete Luckysheet distribution is used (not partial)
## **Critical: Use Official LuckyExcel Pattern**
```typescript
// ✅ DO: Follow official LuckyExcel → Luckysheet pattern exactly
LuckyExcel.transformExcelToLucky(arrayBuffer, fileName,
// Success callback
(exportJson: any, luckysheetfile: any) => {
// CRITICAL: Use exportJson.sheets directly, no custom validation
const luckysheetOptions = {
container: 'luckysheet-container',
data: exportJson.sheets, // Direct usage - don't modify!
title: exportJson.info?.name || fileName,
userInfo: exportJson.info?.creator || "User",
lang: "ko"
};
window.luckysheet.create(luckysheetOptions);
},
// Error callback
(error: any) => {
console.error("LuckyExcel conversion failed:", error);
}
);
```
## **Anti-Pattern: Over-Processing Data**
```typescript
// ❌ DON'T: Modify or validate exportJson.sheets structure
const validatedSheets = exportJson.sheets.map(sheet => ({
name: sheet?.name || `Sheet${index}`,
data: Array.isArray(sheet?.data) ? sheet.data : [],
// ... other modifications
}));
// ❌ DON'T: Use modified data
luckysheet.create({ data: validatedSheets });
```
The root cause of functionlist errors is often data structure mismatch between LuckyExcel output and Luckysheet expectations. Using exportJson.sheets directly maintains the proper internal structure that Luckysheet requires.
This pattern successfully resolves the "Cannot read properties of undefined (reading 'functionlist')" error by ensuring complete library loading sequence and multi-level functionlist initialization.

View File

@@ -28,9 +28,9 @@ interface SheetViewerProps {
/** /**
* Luckysheet 시트 뷰어 컴포넌트 * Luckysheet 시트 뷰어 컴포넌트
* - 참고 내용 기반: 완전한 라이브러리 로딩 순서 적 * - 메모리 정보 기반: LuckyExcel 변환 결과를 직접 사
* - functionlist 오류 방지를 위한 완전한 초기화 * - 커스텀 검증이나 데이터 구조 변경 금지
* - 필수 플러그인과 CSS 포함 * - luckysheet.create({ data: exportJson.sheets })로 직접 사용
*/ */
export function SheetViewer({ className }: SheetViewerProps) { export function SheetViewer({ className }: SheetViewerProps) {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@@ -41,12 +41,11 @@ export function SheetViewer({ className }: SheetViewerProps) {
const [isContainerReady, setIsContainerReady] = useState(false); const [isContainerReady, setIsContainerReady] = useState(false);
const [librariesLoaded, setLibrariesLoaded] = useState(false); const [librariesLoaded, setLibrariesLoaded] = useState(false);
// 스토어에서 시트 데이터 가져오기 // 스토어에서 현재 파일 정보만 가져오기 (시트 데이터는 LuckyExcel로 직접 변환)
const { sheets, activeSheetId, currentFile, setSelectedRange } = const { currentFile, setSelectedRange } = useAppStore();
useAppStore();
/** /**
* CDN 배포판 + functionlist 직접 초기화 방식 * CDN 배포판 라이브러리 로딩
*/ */
const loadLuckysheetLibrary = useCallback((): Promise<void> => { const loadLuckysheetLibrary = useCallback((): Promise<void> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -62,8 +61,6 @@ export function SheetViewer({ className }: SheetViewerProps) {
return; return;
} }
// console.log("📦 CDN 배포판 + functionlist 직접 초기화 방식...");
const loadResource = ( const loadResource = (
type: "css" | "js", type: "css" | "js",
src: string, src: string,
@@ -72,7 +69,6 @@ export function SheetViewer({ className }: SheetViewerProps) {
return new Promise((resourceResolve, resourceReject) => { return new Promise((resourceResolve, resourceReject) => {
// 이미 로드된 리소스 체크 // 이미 로드된 리소스 체크
if (document.querySelector(`[data-luckysheet-id="${id}"]`)) { if (document.querySelector(`[data-luckysheet-id="${id}"]`)) {
// console.log(`📦 ${id} 이미 로드됨`);
resourceResolve(); resourceResolve();
return; return;
} }
@@ -82,33 +78,22 @@ export function SheetViewer({ className }: SheetViewerProps) {
link.rel = "stylesheet"; link.rel = "stylesheet";
link.href = src; link.href = src;
link.setAttribute("data-luckysheet-id", id); link.setAttribute("data-luckysheet-id", id);
link.onload = () => { link.onload = () => resourceResolve();
// console.log(`✅ ${id} CSS 로드 완료`); link.onerror = (error) =>
resourceResolve();
};
link.onerror = (error) => {
// console.error(`❌ ${id} CSS 로드 실패:`, error);
resourceReject(new Error(`${id} CSS 로드 실패`)); resourceReject(new Error(`${id} CSS 로드 실패`));
};
document.head.appendChild(link); document.head.appendChild(link);
} else { } else {
const script = document.createElement("script"); const script = document.createElement("script");
script.src = src; script.src = src;
script.setAttribute("data-luckysheet-id", id); script.setAttribute("data-luckysheet-id", id);
script.onload = () => { script.onload = () => resourceResolve();
// console.log(`✅ ${id} JS 로드 완료`); script.onerror = (error) =>
resourceResolve();
};
script.onerror = (error) => {
// console.error(`❌ ${id} JS 로드 실패:`, error);
resourceReject(new Error(`${id} JS 로드 실패`)); resourceReject(new Error(`${id} JS 로드 실패`));
};
document.head.appendChild(script); document.head.appendChild(script);
} }
}); });
}; };
// CDN 배포판 로딩 + functionlist 직접 초기화
const loadSequence = async () => { const loadSequence = async () => {
try { try {
// 1. jQuery (Luckysheet 의존성) // 1. jQuery (Luckysheet 의존성)
@@ -120,7 +105,7 @@ export function SheetViewer({ className }: SheetViewerProps) {
); );
} }
// 2. CSS 로드 (CDN 방식 - 공식 문서 순서 준수) // 2. CSS 로드 (공식 문서 순서 준수)
await loadResource( await loadResource(
"css", "css",
"https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/css/pluginsCss.css", "https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/css/pluginsCss.css",
@@ -142,26 +127,14 @@ export function SheetViewer({ className }: SheetViewerProps) {
"iconfont-css", "iconfont-css",
); );
// 3. Plugin JS 먼저 로드 (functionlist 초기화 우선) // 3. Plugin JS 먼저 로드 (functionlist 초기화)
await loadResource( await loadResource(
"js", "js",
"https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/js/plugin.js", "https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/js/plugin.js",
"plugin-js", "plugin-js",
); );
// 👉 Plugin.js 로드 완료 후 바로 다음 단계로 진행 // 4. Luckysheet 메인
console.log("✅ Plugin.js 로드 완료");
// 4. LuckyExcel (Excel 파일 처리용 - 공식 문서 방식)
if (!window.LuckyExcel) {
await loadResource(
"js",
"https://cdn.jsdelivr.net/npm/luckyexcel/dist/luckyexcel.umd.js",
"luckyexcel",
);
}
// 5. Luckysheet 메인 (functionlist 준비 후 - 공식 문서 방식)
if (!window.luckysheet) { if (!window.luckysheet) {
await loadResource( await loadResource(
"js", "js",
@@ -170,11 +143,16 @@ export function SheetViewer({ className }: SheetViewerProps) {
); );
} }
// 라이브러리 로드 후 검증 // 5. LuckyExcel (Excel 파일 처리용)
// console.log("🔍 라이브러리 로드 후 검증 중..."); if (!window.LuckyExcel) {
await loadResource(
"js",
"https://cdn.jsdelivr.net/npm/luckyexcel/dist/luckyexcel.umd.js",
"luckyexcel",
);
}
// NOTE: plugin.js 가 실제 functionlist 를 채웠으므로 별도 지연 대기 불필요 // 라이브러리 검증
// 필수 객체 검증
const validationResults = { const validationResults = {
jquery: !!window.$, jquery: !!window.$,
luckyExcel: !!window.LuckyExcel, luckyExcel: !!window.LuckyExcel,
@@ -189,8 +167,6 @@ export function SheetViewer({ className }: SheetViewerProps) {
), ),
}; };
// console.log("🔍 라이브러리 검증 결과:", validationResults);
if ( if (
!validationResults.luckysheet || !validationResults.luckysheet ||
!validationResults.luckysheetCreate !validationResults.luckysheetCreate
@@ -200,14 +176,11 @@ export function SheetViewer({ className }: SheetViewerProps) {
); );
} }
// 🚀 초기화 없이 바로 라이브러리 검증
console.log("🚀 라이브러리 로드 완료, 검증 중...");
setLibrariesLoaded(true); setLibrariesLoaded(true);
// console.log("✅ CDN 배포판 + functionlist 초기화 완료"); console.log("✅ 라이브러리 로드 완료");
resolve(); resolve();
} catch (error) { } catch (error) {
// console.error("❌ 라이브러리 로딩 실패:", error); console.error("❌ 라이브러리 로딩 실패:", error);
reject(error); reject(error);
} }
}; };
@@ -217,9 +190,11 @@ export function SheetViewer({ className }: SheetViewerProps) {
}, [librariesLoaded]); }, [librariesLoaded]);
/** /**
* 참고 내용 기반: 올바른 데이터 구조로 Luckysheet 초기화 * 메모리 정보 기반: LuckyExcel 변환 결과를 직접 사용하는 방식
* - LuckyExcel.transformExcelToLucky()에서 반환된 exportJson.sheets를 그대로 사용
* - 커스텀 검증이나 데이터 구조 변경 금지
*/ */
const convertXLSXToLuckysheet = useCallback( const convertXLSXWithLuckyExcel = useCallback(
async (xlsxBuffer: ArrayBuffer, fileName: string) => { async (xlsxBuffer: ArrayBuffer, fileName: string) => {
if (!containerRef.current) { if (!containerRef.current) {
console.warn("⚠️ 컨테이너가 없습니다."); console.warn("⚠️ 컨테이너가 없습니다.");
@@ -230,81 +205,97 @@ export function SheetViewer({ className }: SheetViewerProps) {
setIsConverting(true); setIsConverting(true);
setError(null); setError(null);
// console.log( console.log("🍀 메모리 정보 기반: LuckyExcel 직접 변환 시작...");
// "🔄 참고 내용 기반: XLSX → LuckyExcel → Luckysheet 변환 시작...",
// );
// 라이브러리 로드 확인 // 라이브러리 로드 확인
await loadLuckysheetLibrary(); await loadLuckysheetLibrary();
// 기존 인스턴스 정리 (참고 내용 권장사항) // 기존 인스턴스 정리
// console.log("🧹 기존 Luckysheet 인스턴스 정리...");
try { try {
if ( if (
window.luckysheet && window.luckysheet &&
typeof window.luckysheet.destroy === "function" typeof window.luckysheet.destroy === "function"
) { ) {
window.luckysheet.destroy(); window.luckysheet.destroy();
// console.log("✅ 기존 인스턴스 destroy 완료"); console.log("✅ 기존 인스턴스 destroy 완료");
} }
} catch (destroyError) { } catch (destroyError) {
// console.warn("⚠️ destroy 중 오류 (무시됨):", destroyError); console.warn("⚠️ destroy 중 오류 (무시됨):", destroyError);
} }
// 컨테이너 초기화 // 컨테이너 초기화
if (containerRef.current) { if (containerRef.current) {
containerRef.current.innerHTML = ""; containerRef.current.innerHTML = "";
// console.log("✅ 컨테이너 초기화 완료"); console.log("✅ 컨테이너 초기화 완료");
} }
luckysheetRef.current = null; luckysheetRef.current = null;
// console.log("🍀 LuckyExcel.transformExcelToLucky 호출..."); console.log("🍀 LuckyExcel.transformExcelToLucky 호출...");
// fileProcessor에서 이미 변환된 데이터를 사용하여 직접 생성 // ArrayBuffer를 File 객체로 변환 (LuckyExcel은 File 객체 필요)
try { const file = new File([xlsxBuffer], fileName, {
console.log("🍀 이미 변환된 시트 데이터 사용:", currentFile?.name); type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
// 기존 인스턴스 정리 // LuckyExcel의 직접 변환 사용 (Promise 방식)
if ( const luckyExcelResult = await new Promise<any>((resolve, reject) => {
window.luckysheet && try {
typeof window.luckysheet.destroy === "function" // 🚨 수정: 첫 번째 매개변수는 File 객체여야 함
) { (window.LuckyExcel as any).transformExcelToLucky(
window.luckysheet.destroy(); file, // ArrayBuffer 대신 File 객체 사용
// 성공 콜백
(exportJson: any, luckysheetfile: any) => {
console.log("🍀 LuckyExcel 변환 성공!");
console.log("🍀 exportJson:", exportJson);
console.log("🍀 luckysheetfile:", luckysheetfile);
resolve(exportJson);
},
// 에러 콜백
(error: any) => {
console.error("❌ LuckyExcel 변환 실패:", error);
reject(new Error(`LuckyExcel 변환 실패: ${error}`));
},
);
} catch (callError) {
console.error("❌ LuckyExcel 호출 중 오류:", callError);
reject(callError);
} }
});
// store에서 sheets 데이터를 가져와서 luckysheet 형식으로 변환 // 결과 검증
const sheets = useAppStore.getState().sheets; if (
const luckysheetData = sheets.map((sheet, index) => ({ !luckyExcelResult ||
name: sheet.name, !luckyExcelResult.sheets ||
index: index.toString(), !Array.isArray(luckyExcelResult.sheets)
status: 1, ) {
order: index, throw new Error("LuckyExcel 변환 결과가 유효하지 않습니다.");
celldata: sheet.config?.data?.[0]?.celldata || [],
row: sheet.config?.data?.[0]?.row || 50,
column: sheet.config?.data?.[0]?.column || 26,
}));
window.luckysheet.create({
container: containerRef.current?.id || "luckysheet-container",
showinfobar: false,
showtoolbar: false,
data: luckysheetData,
});
console.log("🎉 Luckysheet 생성 완료!");
setIsInitialized(true);
setIsConverting(false);
setError(null);
luckysheetRef.current = window.luckysheet;
} catch (createError) {
console.error("❌ Luckysheet 생성 실패:", createError);
setError(
`Luckysheet 생성 실패: ${createError instanceof Error ? createError.message : "알 수 없는 오류"}`,
);
setIsInitialized(false);
setIsConverting(false);
} }
console.log("🎉 LuckyExcel 변환 완료, Luckysheet 생성 중...");
// 메모리 정보 기반: exportJson.sheets를 그대로 사용
// luckysheet.create({ data: exportJson.sheets })
window.luckysheet.create({
container: containerRef.current?.id || "luckysheet-container",
showinfobar: false,
showtoolbar: false,
showsheetbar: true,
showstatisticBar: false,
allowCopy: true,
allowEdit: true,
// 🚨 핵심: LuckyExcel의 원본 변환 결과를 직접 사용
data: luckyExcelResult.sheets, // 가공하지 않고 그대로 전달
title: luckyExcelResult.info?.name || fileName,
// 🚨 수정: userInfo 경로 수정
userInfo: luckyExcelResult.info?.creator || false,
});
console.log("🎉 Luckysheet 생성 완료! (원본 데이터 직접 사용)");
setIsInitialized(true);
setIsConverting(false);
setError(null);
luckysheetRef.current = window.luckysheet;
} catch (conversionError) { } catch (conversionError) {
console.error("❌ 변환 프로세스 실패:", conversionError); console.error("❌ 변환 프로세스 실패:", conversionError);
setError( setError(
@@ -325,12 +316,9 @@ export function SheetViewer({ className }: SheetViewerProps) {
* DOM 컨테이너 준비 상태 체크 - useLayoutEffect로 동기적 체크 * DOM 컨테이너 준비 상태 체크 - useLayoutEffect로 동기적 체크
*/ */
useLayoutEffect(() => { useLayoutEffect(() => {
// console.log("🔍 useLayoutEffect: DOM 컨테이너 체크 시작...");
if (containerRef.current) { if (containerRef.current) {
// console.log("✅ DOM 컨테이너 준비 완료:", containerRef.current.id); console.log("✅ DOM 컨테이너 준비 완료:", containerRef.current.id);
setIsContainerReady(true); setIsContainerReady(true);
} else {
// console.warn("⚠️ useLayoutEffect: DOM 컨테이너가 아직 준비되지 않음");
} }
}, []); }, []);
@@ -339,10 +327,9 @@ export function SheetViewer({ className }: SheetViewerProps) {
*/ */
useEffect(() => { useEffect(() => {
if (!isContainerReady) { if (!isContainerReady) {
// console.log("🔄 useEffect: DOM 컨테이너 재체크...");
const timer = setTimeout(() => { const timer = setTimeout(() => {
if (containerRef.current && !isContainerReady) { if (containerRef.current && !isContainerReady) {
// console.log("✅ useEffect: DOM 컨테이너 지연 준비 완료"); console.log("✅ useEffect: DOM 컨테이너 지연 준비 완료");
setIsContainerReady(true); setIsContainerReady(true);
} }
}, 100); }, 100);
@@ -351,7 +338,7 @@ export function SheetViewer({ className }: SheetViewerProps) {
}, [isContainerReady]); }, [isContainerReady]);
/** /**
* 컴포넌트 마운트 시 초기화 - 중복 실행 방지 * 컴포넌트 마운트 시 초기화
*/ */
useEffect(() => { useEffect(() => {
if ( if (
@@ -361,17 +348,17 @@ export function SheetViewer({ className }: SheetViewerProps) {
!isInitialized && !isInitialized &&
!isConverting !isConverting
) { ) {
console.log("🔄 변환된 XLSX 감지, Luckysheet 초기화 시작...", { console.log("🔄 XLSX 버퍼 감지, LuckyExcel 직접 변환 시작...", {
fileName: currentFile.name, fileName: currentFile.name,
bufferSize: currentFile.xlsxBuffer.byteLength, bufferSize: currentFile.xlsxBuffer.byteLength,
containerId: containerRef.current.id, containerId: containerRef.current.id,
}); });
// 중복 실행 방지를 위해 즉시 상태 변경 // 중복 실행 방지
setIsConverting(true); setIsConverting(true);
// 변환된 XLSX ArrayBuffer를 사용하여 직접 변환 // LuckyExcel로 직접 변환
convertXLSXToLuckysheet(currentFile.xlsxBuffer, currentFile.name); convertXLSXWithLuckyExcel(currentFile.xlsxBuffer, currentFile.name);
} else if (currentFile && !currentFile.xlsxBuffer) { } else if (currentFile && !currentFile.xlsxBuffer) {
setError("파일 변환 데이터가 없습니다. 파일을 다시 업로드해주세요."); setError("파일 변환 데이터가 없습니다. 파일을 다시 업로드해주세요.");
} }
@@ -381,6 +368,7 @@ export function SheetViewer({ className }: SheetViewerProps) {
isContainerReady, isContainerReady,
isInitialized, isInitialized,
isConverting, isConverting,
convertXLSXWithLuckyExcel,
]); ]);
/** /**
@@ -389,11 +377,10 @@ export function SheetViewer({ className }: SheetViewerProps) {
useEffect(() => { useEffect(() => {
return () => { return () => {
if (luckysheetRef.current && window.luckysheet) { if (luckysheetRef.current && window.luckysheet) {
// console.log("🧹 컴포넌트 언마운트: Luckysheet 정리 중...");
try { try {
window.luckysheet.destroy(); window.luckysheet.destroy();
} catch (error) { } catch (error) {
// console.warn("⚠️ Luckysheet 정리 중 오류:", error); console.warn("⚠️ Luckysheet 정리 중 오류:", error);
} }
} }
}; };
@@ -408,7 +395,7 @@ export function SheetViewer({ className }: SheetViewerProps) {
try { try {
window.luckysheet.resize(); window.luckysheet.resize();
} catch (error) { } catch (error) {
// console.warn("⚠️ Luckysheet 리사이즈 중 오류:", error); console.warn("⚠️ Luckysheet 리사이즈 중 오류:", error);
} }
} }
}; };
@@ -449,7 +436,7 @@ export function SheetViewer({ className }: SheetViewerProps) {
setIsInitialized(false); setIsInitialized(false);
setIsConverting(false); setIsConverting(false);
if (currentFile?.xlsxBuffer) { if (currentFile?.xlsxBuffer) {
convertXLSXToLuckysheet( convertXLSXWithLuckyExcel(
currentFile.xlsxBuffer, currentFile.xlsxBuffer,
currentFile.name, currentFile.name,
); );
@@ -471,11 +458,11 @@ export function SheetViewer({ className }: SheetViewerProps) {
<div className="text-center p-6"> <div className="text-center p-6">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div> <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<div className="text-blue-600 text-lg font-semibold mb-2"> <div className="text-blue-600 text-lg font-semibold mb-2">
{isConverting ? "XLSX 변환 중..." : "시트 초기화 중..."} {isConverting ? "LuckyExcel 변환 중..." : "시트 초기화 중..."}
</div> </div>
<div className="text-blue-500 text-sm"> <div className="text-blue-500 text-sm">
{isConverting {isConverting
? "변환된 XLSX를 Luckysheet로 처리하고 있습니다." ? "원본 Excel 데이터를 완전한 스타일로 변환하고 있습니다."
: "잠시만 기다려주세요."} : "잠시만 기다려주세요."}
</div> </div>
</div> </div>
@@ -509,6 +496,7 @@ export function SheetViewer({ className }: SheetViewerProps) {
<div> : {isConverting ? "예" : "아니오"}</div> <div> : {isConverting ? "예" : "아니오"}</div>
<div>: {isInitialized ? "완료" : "대기"}</div> <div>: {isInitialized ? "완료" : "대기"}</div>
<div> : {isContainerReady ? "완료" : "대기"}</div> <div> : {isContainerReady ? "완료" : "대기"}</div>
<div>방식: LuckyExcel </div>
</div> </div>
)} )}
</div> </div>

View File

@@ -446,41 +446,9 @@ async function processFileWithSheetJSToXLSX(
} else { } else {
// XLS/XLSX 파일 처리 - 관대한 옵션으로 읽기 // XLS/XLSX 파일 처리 - 관대한 옵션으로 읽기
console.log(`📊 ${isXLS ? "XLS" : "XLSX"} 파일을 SheetJS로 읽는 중...`); console.log(`📊 ${isXLS ? "XLS" : "XLSX"} 파일을 SheetJS로 읽는 중...`);
workbook = XLSX.read(arrayBuffer, { workbook = XLSX.read(arrayBuffer);
type: "array",
cellText: true,
sheetStubs: true,
WTF: true,
bookSheets: false,
codepage: 65001,
raw: false,
});
// Sheets가 없고 SheetNames만 있는 경우 재시도 // Sheets가 없고 SheetNames만 있는 경우 재시도
if (workbook.SheetNames?.length > 0 && !workbook.Sheets) {
console.log("⚠️ Sheets 속성이 없어서 재읽기 시도...");
workbook = XLSX.read(arrayBuffer, {
type: "array",
cellText: true,
sheetStubs: true,
WTF: true,
bookSheets: true, // 강제로 시트 읽기
codepage: 65001,
raw: false,
});
// 여전히 실패하면 수동으로 빈 시트 생성
if (!workbook.Sheets && workbook.SheetNames?.length > 0) {
console.log("⚠️ 수동으로 빈 시트 생성...");
workbook.Sheets = {};
workbook.SheetNames.forEach((sheetName: string) => {
workbook.Sheets[sheetName] = {
"!ref": "A1:A1",
A1: { v: "", t: "s" },
};
});
}
}
} }
} catch (readError) { } catch (readError) {
console.error("❌ SheetJS 파일 읽기 실패:", readError); console.error("❌ SheetJS 파일 읽기 실패:", readError);
@@ -522,7 +490,83 @@ async function processFileWithSheetJSToXLSX(
type: "array", type: "array",
bookType: "xlsx", bookType: "xlsx",
compression: true, compression: true,
// 🎨 스타일 정보 완전 보존
cellStyles: true, // 셀 스타일, 색상, 폰트, 테두리 등
cellDates: true, // 날짜 포맷 정보
bookSST: true, // 문자열 테이블 (호환성)
// 📊 워크북 정보 보존
Props: workbook.Props || {},
// 🎭 테마 정보 보존 (존재하는 경우)
...(workbook.themeXLSX && { themeXLSX: workbook.themeXLSX }),
}); });
// 📋 XLSX.write 완료 후 workbook 상세 정보 로깅
console.log("📋 =================================");
console.log("📋 XLSX.write 완료 후 Workbook 분석:");
console.log("📋 =================================");
console.log("📋 Workbook 타입:", typeof workbook);
console.log("📋 Workbook 생성자:", workbook.constructor.name);
console.log("📋 시트 이름들:", workbook.SheetNames);
console.log("📋 시트 개수:", workbook.SheetNames?.length || 0);
// Props 정보
console.log("📋 Props 존재:", !!workbook.Props);
if (workbook.Props) {
console.log("📋 Props 내용:", workbook.Props);
}
// 테마 정보
console.log("📋 themeXLSX 존재:", !!workbook.themeXLSX);
// 각 시트별 상세 정보
if (workbook.Sheets && workbook.SheetNames) {
workbook.SheetNames.forEach((sheetName: string, index: number) => {
const sheet = workbook.Sheets[sheetName];
console.log(`📋 시트 ${index + 1} "${sheetName}" 분석:`);
console.log(`📋 - 시트 객체 존재:`, !!sheet);
if (sheet) {
console.log(`📋 - 데이터 범위 (!ref):`, sheet["!ref"]);
console.log(
`📋 - 병합 셀 (!merges):`,
sheet["!merges"]?.length || 0,
"개",
);
console.log(
`📋 - 열 설정 (!cols):`,
sheet["!cols"]?.length || 0,
"개",
);
console.log(
`📋 - 행 설정 (!rows):`,
sheet["!rows"]?.length || 0,
"개",
);
// 셀 샘플 확인 (첫 몇 개 셀)
const cellKeys = Object.keys(sheet)
.filter((key) => !key.startsWith("!"))
.slice(0, 5);
console.log(`📋 - 셀 샘플 (첫 5개):`, cellKeys);
cellKeys.forEach((cellAddr) => {
const cell = sheet[cellAddr];
console.log(`📋 ${cellAddr}:`, {
: cell.v,
타입: cell.t,
: !!cell.s,
수식: cell.f || "없음",
포맷된텍스트: cell.w || "없음",
});
});
}
});
}
console.log("📋 =================================");
// xlsxData는 Uint8Array이므로 ArrayBuffer로 변환 // xlsxData는 Uint8Array이므로 ArrayBuffer로 변환
if (xlsxData instanceof Uint8Array) { if (xlsxData instanceof Uint8Array) {
xlsxArrayBuffer = xlsxData.buffer.slice( xlsxArrayBuffer = xlsxData.buffer.slice(