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

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