From 8f8744ba4b4d4ad04d439a44d7662eef92a3d5f2 Mon Sep 17 00:00:00 2001 From: ByungCheol Date: Wed, 3 Jun 2026 18:29:00 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20=EC=9B=B9?= =?UTF-8?q?=20=EA=B0=80=EA=B3=84=EB=B6=80=20=EB=82=B4=EC=97=AD=20=EC=A7=84?= =?UTF-8?q?=EC=9E=85=20=EB=B6=88=EA=B0=80(=EC=B2=AD=ED=81=AC=20=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20=EC=8B=A4=ED=8C=A8)=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - @capacitor/camera 정적 import 제거 → 네이티브에서만 동적 import (웹은 카메라를 호출하지도 않는데 정적 import가 AccountView 청크 평가를 깨뜨려 모바일 웹에서 진입 시 청크 로드 실패 → onError 새로고침 → 로그인 루프 발생) - router.onError 경로별 1회 새로고침으로 제한(무한 루프 방지) + 성공 시 플래그 정리 Co-Authored-By: Claude Opus 4.8 --- src/router/index.js | 18 ++++++++++++------ src/views/account/AccountView.vue | 12 +++++++----- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/router/index.js b/src/router/index.js index d0ac091..61cf1b1 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -108,15 +108,21 @@ router.beforeEach((to, from) => { }) // 새 배포로 청크 해시가 바뀐 뒤, 캐시된 옛 index.html 이 삭제된 옛 청크를 부르면 -// 동적 import 가 실패한다. 이 경우 목적지로 전체 새로고침해 최신 index.html 을 받게 한다. -let reloadingForChunk = false +// 동적 import 가 실패한다. 이 경우 목적지로 1회 전체 새로고침해 최신 index.html 을 받게 한다. +// (경로별 1회만 — 새로고침 후에도 실패하면 무한 새로고침/로그인 루프를 막기 위해 중단) router.onError((err, to) => { const msg = (err && err.message) || '' const chunkError = /dynamically imported module|Importing a module script failed|Failed to fetch dynamically imported module|ChunkLoadError|error loading dynamically imported module/i.test(msg) - if (chunkError && !reloadingForChunk) { - reloadingForChunk = true - window.location.assign(to?.fullPath || window.location.pathname) - } + if (!chunkError) return + const path = to?.fullPath || window.location.pathname + const key = 'chunk-reload:' + path + if (sessionStorage.getItem(key)) return // 이미 한 번 새로고침했는데 또 실패 → 루프 방지 + sessionStorage.setItem(key, '1') + window.location.assign(path) +}) +// 정상 진입 시 해당 경로의 새로고침 플래그 정리 (다음 배포 때 다시 동작하도록) +router.afterEach((to) => { + sessionStorage.removeItem('chunk-reload:' + to.fullPath) }) export default router diff --git a/src/views/account/AccountView.vue b/src/views/account/AccountView.vue index 81ba482..d2ae2a6 100644 --- a/src/views/account/AccountView.vue +++ b/src/views/account/AccountView.vue @@ -4,7 +4,7 @@ import { accountApi } from '@/api/accountApi' import IconBtn from '@/components/ui/IconBtn.vue' import { imageToBlob, parseReceiptText } from '@/utils/receiptOcr' import { Capacitor } from '@capacitor/core' -import { Camera, CameraResultType, CameraSource } from '@capacitor/camera' +// @capacitor/camera 는 네이티브에서만 동적 로드 (웹은 file input 사용 — 정적 import 시 모바일 웹 청크 로드 실패 유발) const now = new Date() const year = ref(now.getFullYear()) @@ -70,7 +70,7 @@ function pickReceipt() { async function chooseCamera() { receiptPickerOpen.value = false if (Capacitor.isNativePlatform()) { - await captureFrom(CameraSource.Camera) + await captureFrom('CAMERA') } else { webCapture.value = true await nextTick() @@ -80,17 +80,19 @@ async function chooseCamera() { async function chooseGallery() { receiptPickerOpen.value = false if (Capacitor.isNativePlatform()) { - await captureFrom(CameraSource.Photos) + await captureFrom('PHOTOS') } else { webCapture.value = false await nextTick() receiptInput.value?.click() } } -async function captureFrom(source) { +// 네이티브 전용 — 카메라 플러그인을 이 시점에만 동적 로드 +async function captureFrom(sourceKey) { try { + const { Camera, CameraResultType, CameraSource } = await import('@capacitor/camera') const photo = await Camera.getPhoto({ - source, + source: sourceKey === 'CAMERA' ? CameraSource.Camera : CameraSource.Photos, resultType: CameraResultType.DataUrl, quality: 70, correctOrientation: true,