fix. 코드 정리 (format 적용, 불필요한 파일 제거)
This commit is contained in:
@@ -1,92 +0,0 @@
|
||||
# 메모리 누수 분석 및 개선 리포트
|
||||
|
||||
## 🔴 심각한 메모리 누수 (즉시 수정 필요)
|
||||
|
||||
### 1. **app.vue - watch가 정리되지 않음**
|
||||
- **위치**: `app/app.vue:131-146`
|
||||
- **문제**: `onMounted` 내부에서 생성된 `watch`가 `onBeforeUnmount`에서 정리되지 않음
|
||||
- **영향**: 컴포넌트가 언마운트되어도 watch가 계속 실행되어 메모리 누수 발생
|
||||
|
||||
### 2. **useSplideArrow.ts - 이벤트 리스너가 정리되지 않음**
|
||||
- **위치**: `layers/composables/useSplideArrow.ts:55, 59`
|
||||
- **문제**: `addEventListener`로 추가된 이벤트 리스너가 제거되지 않음
|
||||
- **영향**: 컴포넌트가 언마운트되어도 이벤트 리스너가 남아있어 메모리 누수 발생
|
||||
|
||||
### 3. **useModalStore.ts - setTimeout이 정리되지 않음**
|
||||
- **위치**: `layers/stores/useModalStore.ts:125`
|
||||
- **문제**: `setTimeout`이 사용되지만 컴포넌트 언마운트 시 정리되지 않음
|
||||
- **영향**: 컴포넌트가 언마운트된 후에도 타이머가 실행되어 메모리 누수 발생
|
||||
|
||||
### 4. **useLoadingStore.ts - setTimeout이 정리되지 않음**
|
||||
- **위치**: `layers/stores/useLoadingStore.ts:38`
|
||||
- **문제**: `setTimeout`이 사용되지만 정리되지 않음
|
||||
- **영향**: 스토어가 초기화되어도 타이머가 실행되어 메모리 누수 발생
|
||||
|
||||
### 5. **Video.vue - setTimeout이 정리되지 않음**
|
||||
- **위치**: `layers/components/atoms/Video.vue:34`
|
||||
- **문제**: `setTimeout`이 사용되지만 컴포넌트 언마운트 시 정리되지 않음
|
||||
- **영향**: 컴포넌트가 언마운트된 후에도 타이머가 실행되어 메모리 누수 발생
|
||||
|
||||
### 6. **GrGallery01/index.vue - setTimeout이 정리되지 않음**
|
||||
- **위치**: `layers/templates/GrGallery01/index.vue:93`
|
||||
- **문제**: `setTimeout`이 사용되지만 컴포넌트 언마운트 시 정리되지 않음
|
||||
- **영향**: 컴포넌트가 언마운트된 후에도 타이머가 실행되어 메모리 누수 발생
|
||||
|
||||
### 7. **useGameStart.ts - setTimeout이 정리되지 않음**
|
||||
- **위치**: `layers/composables/useGameStart.ts:70`
|
||||
- **문제**: `setTimeout`이 사용되지만 정리되지 않음
|
||||
- **영향**: 컴포저블이 사용되지 않아도 타이머가 실행되어 메모리 누수 발생
|
||||
|
||||
### 8. **Thumbnail.vue - 이벤트 리스너가 정리되지 않음**
|
||||
- **위치**: `layers/components/blocks/slide/Thumbnail.vue:114`
|
||||
- **문제**: `addArrowClickListeners`로 추가된 이벤트 리스너가 제거되지 않음
|
||||
- **영향**: 컴포넌트가 언마운트되어도 이벤트 리스너가 남아있어 메모리 누수 발생
|
||||
|
||||
## 🟡 중간 수준 이슈 (개선 권장)
|
||||
|
||||
### 9. **amplitude.client.ts - 이벤트 리스너가 정리되지 않음**
|
||||
- **위치**: `layers/plugins/amplitude.client.ts:34`
|
||||
- **문제**: `window.addEventListener('pagehide')`가 추가되지만 제거되지 않음
|
||||
- **영향**: 플러그인은 앱 전체 생명주기 동안 유지되므로 큰 문제는 아니지만, 명시적으로 정리하는 것이 좋음
|
||||
|
||||
### 10. **app.vue - removeEventListener 사용 오류**
|
||||
- **위치**: `app/app.vue:150`
|
||||
- **문제**: `useEventListener`로 추가한 이벤트 리스너를 `removeEventListener`로 제거하려고 함
|
||||
- **영향**: `useEventListener`는 자동으로 정리되므로 불필요한 코드 (메모리 누수는 아님)
|
||||
|
||||
## 📋 수정 우선순위
|
||||
|
||||
1. ✅ **즉시 수정** (메모리 누수): #1, #2, #3, #4, #5, #6, #7, #8 - **완료**
|
||||
2. **단기 개선** (안정성): #9, #10 - 추후 검토
|
||||
|
||||
## ✅ 수정 완료 내역
|
||||
|
||||
### 수정된 파일 목록
|
||||
1. `app/app.vue` - watch 정리 추가, removeEventListener 제거
|
||||
2. `layers/composables/useSplideArrow.ts` - 이벤트 리스너 제거 함수 반환하도록 수정
|
||||
3. `layers/components/blocks/slide/Thumbnail.vue` - 이벤트 리스너 정리 추가
|
||||
4. `layers/stores/useModalStore.ts` - setTimeout 정리 추가
|
||||
5. `layers/stores/useLoadingStore.ts` - setTimeout 정리 추가
|
||||
6. `layers/components/atoms/Video.vue` - setTimeout 정리 추가
|
||||
7. `layers/templates/GrGallery01/index.vue` - setTimeout 정리 추가
|
||||
8. `layers/composables/useGameStart.ts` - setTimeout 정리 추가
|
||||
|
||||
### 수정 방법 요약
|
||||
- **watch 정리**: `watch` 반환값을 저장하고 `onBeforeUnmount`에서 호출
|
||||
- **setTimeout 정리**: 타이머 ID를 저장하고 `onBeforeUnmount` 또는 함수 재호출 시 `clearTimeout`으로 정리
|
||||
- **이벤트 리스너 정리**: 이벤트 리스너 제거 함수를 반환하고 `onBeforeUnmount`에서 호출
|
||||
|
||||
## 🎯 개선 효과
|
||||
|
||||
1. **메모리 누수 방지**: 컴포넌트 언마운트 시 모든 리소스가 정리되어 메모리 누수 방지
|
||||
2. **성능 향상**: 불필요한 타이머 및 이벤트 리스너 제거로 성능 향상
|
||||
3. **안정성 향상**: 컴포넌트 생명주기 관리가 명확해져 버그 발생 가능성 감소
|
||||
4. **코드 품질 향상**: 리소스 정리 패턴이 일관되어 유지보수성 향상
|
||||
|
||||
## 📝 추가 권장 사항
|
||||
|
||||
1. **자동 정리 유틸 함수**: 공통 패턴을 유틸 함수로 추출하여 재사용성 향상
|
||||
2. **테스트 강화**: 컴포넌트 언마운트 시 리소스 정리 테스트 추가
|
||||
3. **ESLint 규칙**: 메모리 누수 방지를 위한 ESLint 규칙 추가 고려
|
||||
4. **성능 모니터링**: 수정 후 메모리 사용량 모니터링
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
# SSR/CSR 동작 분석 및 개선 리포트
|
||||
|
||||
## 🔴 심각한 버그 (✅ 수정 완료)
|
||||
|
||||
### 1. ✅ **app.vue - Google Analytics 초기화가 SSR에서 실행됨**
|
||||
- **위치**: `app/app.vue:115-120`
|
||||
- **문제**: `useGtag()` 초기화가 SSR에서 실행되어 서버 사이드 에러 발생 가능
|
||||
- **영향**: 서버 렌더링 시 에러 발생, 성능 저하
|
||||
- **수정**: `if (import.meta.client)` 체크 추가
|
||||
|
||||
### 2. ✅ **useAnalytics.ts - window/navigator/location 사용 시 클라이언트 체크 없음**
|
||||
- **위치**: `layers/composables/useAnalytics.ts:108, 201-202, 255`
|
||||
- **문제**: `window.location`, `navigator.userAgent`, `location.href` 사용 시 `import.meta.client` 체크 없음
|
||||
- **영향**: SSR에서 `window is not defined` 에러 발생
|
||||
- **수정**: 모든 분석 함수에 `if (!import.meta.client) return` 체크 추가
|
||||
|
||||
### 3. ✅ **useScrollStore.ts - document.body 사용 시 클라이언트 체크 없음**
|
||||
- **위치**: `layers/stores/useScrollStore.ts:27, 29`
|
||||
- **문제**: `document.body.classList` 조작 시 `import.meta.client` 체크 없음
|
||||
- **영향**: SSR에서 `document is not defined` 에러 발생
|
||||
- **수정**: `controlScrollLock` 함수에 `if (!import.meta.client) return` 체크 추가
|
||||
|
||||
### 4. ✅ **useModalStore.ts - document.body 사용 시 클라이언트 체크 없음**
|
||||
- **위치**: `layers/stores/useModalStore.ts:29, 31`
|
||||
- **문제**: `document.body.classList` 조작 시 `import.meta.client` 체크 없음
|
||||
- **영향**: SSR에서 `document is not defined` 에러 발생
|
||||
- **수정**: `handleControlDimmed` 함수에 `if (!import.meta.client) return` 체크 추가
|
||||
|
||||
### 5. ✅ **localeUtil.ts - navigator 사용 시 클라이언트 체크 없음**
|
||||
- **위치**: `layers/utils/localeUtil.ts:76`
|
||||
- **문제**: `navigator.language` 사용 시 `import.meta.client` 체크 없음
|
||||
- **영향**: SSR에서 `navigator is not defined` 에러 발생
|
||||
- **수정**: 브라우저 언어 체크 부분을 `if (import.meta.client)` 블록으로 감쌈
|
||||
|
||||
### 6. ✅ **stoveUtil.ts - location 사용 시 클라이언트 체크 없음**
|
||||
- **위치**: `layers/utils/stoveUtil.ts:18, 21`
|
||||
- **문제**: `location.href` 사용 시 `import.meta.client` 체크 없음
|
||||
- **영향**: SSR에서 `location is not defined` 에러 발생
|
||||
- **수정**: `csrGoStoveLogin` 함수에 `if (!import.meta.client) return` 체크 추가
|
||||
|
||||
## 🟡 중간 수준 이슈 (개선 권장)
|
||||
|
||||
### 7. ✅ **pageData.global.ts - window.history 사용**
|
||||
- **위치**: `layers/middleware/pageData.global.ts:103`
|
||||
- **문제**: `window.history.replaceState` 사용은 체크되어 있지만, 더 안전한 처리 필요
|
||||
- **영향**: 미미하지만 타입 안정성 개선 가능
|
||||
- **수정**: `if (import.meta.client)` 체크 추가
|
||||
|
||||
### 8. **Header.vue - 불필요한 클라이언트 체크**
|
||||
- **위치**: `layers/components/layouts/Header.vue:55, 66`
|
||||
- **문제**: `hasActiveChild`, `isNavItemActive` 함수에서 클라이언트 체크가 있지만, computed에서 사용 시 불필요한 체크
|
||||
- **영향**: 성능 미미한 저하
|
||||
- **상태**: 현재 동작에는 문제 없으나, 성능 최적화를 위해 추후 개선 가능
|
||||
|
||||
### 9. ✅ **app.vue - scroll 이벤트 리스너**
|
||||
- **위치**: `app/app.vue:125`
|
||||
- **문제**: `useEventListener`는 클라이언트에서만 동작하지만, 명시적 체크 없음
|
||||
- **영향**: 미미하지만 명확성 개선 가능
|
||||
- **수정**: `onMounted` 내부에 `if (!import.meta.client) return` 체크 추가
|
||||
|
||||
## 🟢 성능 개선 사항
|
||||
|
||||
### 10. **ClientOnly 컴포넌트 최적화**
|
||||
- **위치**: `layers/components/layouts/Header.vue:386-416`
|
||||
- **개선**: 이미 `ClientOnly`로 감싸져 있으나, 더 세밀한 최적화 가능
|
||||
- **영향**: 초기 렌더링 성능 개선
|
||||
|
||||
### 11. **불필요한 SSR 실행 방지**
|
||||
- **위치**: 여러 컴포저블 및 유틸 함수
|
||||
- **개선**: 클라이언트 전용 함수는 명시적으로 `.client.ts` 확장자 사용 권장
|
||||
- **영향**: 번들 크기 및 초기 로드 시간 개선
|
||||
|
||||
### 12. **useWindowSize 사용 최적화**
|
||||
- **위치**: `layers/components/layouts/Header.vue:14, 121`
|
||||
- **개선**: `useWindowSize`는 클라이언트에서만 동작하지만, SSR에서도 초기화됨
|
||||
- **영향**: 미미한 성능 개선
|
||||
|
||||
## 📋 수정 우선순위
|
||||
|
||||
1. ✅ **즉시 수정** (버그): #1, #2, #3, #4, #5, #6 - **완료**
|
||||
2. ✅ **단기 개선** (안정성): #7, #9 - **완료** (#8은 성능 최적화로 추후 검토)
|
||||
3. **중기 개선** (성능): #10, #11, #12 - 추후 검토
|
||||
|
||||
## ✅ 수정 완료 내역
|
||||
|
||||
### 수정된 파일 목록
|
||||
1. `app/app.vue` - Google Analytics 초기화 및 scroll 이벤트 리스너 클라이언트 체크 추가
|
||||
2. `layers/composables/useAnalytics.ts` - 모든 분석 함수에 클라이언트 체크 추가
|
||||
3. `layers/stores/useScrollStore.ts` - `controlScrollLock` 함수에 클라이언트 체크 추가
|
||||
4. `layers/stores/useModalStore.ts` - `handleControlDimmed` 함수에 클라이언트 체크 추가
|
||||
5. `layers/utils/localeUtil.ts` - 브라우저 언어 체크에 클라이언트 체크 추가
|
||||
6. `layers/utils/stoveUtil.ts` - `csrGoStoveLogin` 함수에 클라이언트 체크 추가
|
||||
7. `layers/middleware/pageData.global.ts` - `window.history` 사용 시 클라이언트 체크 추가
|
||||
|
||||
### 수정 방법 요약
|
||||
- 모든 브라우저 전용 API (`window`, `document`, `navigator`, `location`) 사용 시 `import.meta.client` 체크 추가
|
||||
- 클라이언트 전용 함수는 함수 시작 부분에 `if (!import.meta.client) return` 추가
|
||||
- 클라이언트 전용 코드 블록은 `if (import.meta.client) { ... }` 로 감쌈
|
||||
|
||||
## 🎯 개선 효과
|
||||
|
||||
1. **SSR 안정성 향상**: 서버 사이드에서 브라우저 API 접근 시 발생하던 에러 방지
|
||||
2. **타입 안정성 개선**: 클라이언트 전용 코드의 명확한 구분
|
||||
3. **성능 개선**: 불필요한 서버 사이드 실행 방지로 초기 렌더링 성능 향상
|
||||
4. **코드 품질 향상**: SSR/CSR 경계가 명확해져 유지보수성 향상
|
||||
|
||||
## 📝 추가 권장 사항
|
||||
|
||||
1. **클라이언트 전용 유틸 함수 분리**: `csr` 접두사가 붙은 함수들은 `.client.ts` 확장자로 분리 고려
|
||||
2. **타입 가드 활용**: TypeScript 타입 가드를 활용하여 클라이언트 전용 코드 타입 안정성 향상
|
||||
3. **테스트 강화**: SSR/CSR 환경에서의 동작 테스트 추가
|
||||
4. **성능 모니터링**: 수정 후 SSR 성능 및 에러 로그 모니터링
|
||||
|
||||
@@ -50,7 +50,9 @@
|
||||
text-color="#FFFFFF"
|
||||
@click="handleError"
|
||||
>
|
||||
<span v-dompurify-html="`${gameName} ${tm('Error_Official_Page')}`"></span>
|
||||
<span
|
||||
v-dompurify-html="`${gameName} ${tm('Error_Official_Page')}`"
|
||||
></span>
|
||||
</AtomsButton>
|
||||
</div>
|
||||
</div>
|
||||
@@ -125,7 +127,7 @@ const handleKeydown = (e: KeyboardEvent) => {
|
||||
// 500 에러 발생 시 /error 페이지로 리다이렉트
|
||||
onMounted(() => {
|
||||
const statusCode = currentError.value?.statusCode
|
||||
console.log("🚀 ~ statusCode:", nuxtError)
|
||||
console.log('🚀 ~ statusCode:', nuxtError)
|
||||
|
||||
if (statusCode === 500) {
|
||||
errorTitle.value = tm('Error_500_Inconvenience')
|
||||
|
||||
@@ -15,8 +15,8 @@ module.exports = {
|
||||
merge_logs: true, // 클러스터 모드 사용 시 각 클러스터에서 생성되는 로그를 한 파일로 병합
|
||||
env: {
|
||||
PORT: 3000,
|
||||
NODE_ENV: 'dev'
|
||||
}
|
||||
NODE_ENV: 'dev',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'dev2',
|
||||
@@ -33,8 +33,8 @@ module.exports = {
|
||||
merge_logs: true, // 클러스터 모드 사용 시 각 클러스터에서 생성되는 로그를 한 파일로 병합
|
||||
env: {
|
||||
PORT: 3000,
|
||||
NODE_ENV: 'dev'
|
||||
}
|
||||
NODE_ENV: 'dev',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'qa',
|
||||
@@ -51,8 +51,8 @@ module.exports = {
|
||||
merge_logs: true, // 클러스터 모드 사용 시 각 클러스터에서 생성되는 로그를 한 파일로 병합
|
||||
env: {
|
||||
PORT: 3000,
|
||||
NODE_ENV: 'qa'
|
||||
}
|
||||
NODE_ENV: 'qa',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'qa2',
|
||||
@@ -69,8 +69,8 @@ module.exports = {
|
||||
merge_logs: true, // 클러스터 모드 사용 시 각 클러스터에서 생성되는 로그를 한 파일로 병합
|
||||
env: {
|
||||
PORT: 3000,
|
||||
NODE_ENV: 'qa'
|
||||
}
|
||||
NODE_ENV: 'qa',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'perf',
|
||||
@@ -87,8 +87,8 @@ module.exports = {
|
||||
merge_logs: true, // 클러스터 모드 사용 시 각 클러스터에서 생성되는 로그를 한 파일로 병합
|
||||
env: {
|
||||
PORT: 3000,
|
||||
NODE_ENV: 'perf'
|
||||
}
|
||||
NODE_ENV: 'perf',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'sandbox',
|
||||
@@ -105,8 +105,8 @@ module.exports = {
|
||||
merge_logs: true, // 클러스터 모드 사용 시 각 클러스터에서 생성되는 로그를 한 파일로 병합
|
||||
env: {
|
||||
PORT: 3000,
|
||||
NODE_ENV: 'sandbox'
|
||||
}
|
||||
NODE_ENV: 'sandbox',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'live',
|
||||
@@ -123,8 +123,8 @@ module.exports = {
|
||||
merge_logs: true, // 클러스터 모드 사용 시 각 클러스터에서 생성되는 로그를 한 파일로 병합
|
||||
env: {
|
||||
PORT: 3000,
|
||||
NODE_ENV: 'live'
|
||||
}
|
||||
}
|
||||
]
|
||||
NODE_ENV: 'live',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,514 +0,0 @@
|
||||
{
|
||||
"ko": {
|
||||
"common": {
|
||||
"loading": "로딩 중...",
|
||||
"error": "오류가 발생했습니다",
|
||||
"success": "성공했습니다",
|
||||
"cancel": "취소",
|
||||
"confirm": "확인",
|
||||
"save": "저장",
|
||||
"delete": "삭제",
|
||||
"edit": "편집",
|
||||
"close": "닫기",
|
||||
"back": "뒤로",
|
||||
"next": "다음",
|
||||
"previous": "이전",
|
||||
"search": "검색",
|
||||
"filter": "필터",
|
||||
"sort": "정렬",
|
||||
"refresh": "새로고침",
|
||||
"download": "다운로드",
|
||||
"upload": "업로드",
|
||||
"copy": "복사",
|
||||
"paste": "붙여넣기",
|
||||
"cut": "잘라내기",
|
||||
"undo": "실행 취소",
|
||||
"redo": "다시 실행"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "홈",
|
||||
"about": "소개",
|
||||
"contact": "연락처",
|
||||
"services": "서비스",
|
||||
"products": "제품",
|
||||
"news": "뉴스",
|
||||
"support": "지원",
|
||||
"login": "로그인",
|
||||
"logout": "로그아웃",
|
||||
"register": "회원가입",
|
||||
"profile": "프로필",
|
||||
"settings": "설정"
|
||||
},
|
||||
"messages": {
|
||||
"title_test_lang": "언어 설정 테스트!",
|
||||
"welcome": "환영합니다!",
|
||||
"GameData_load_status": "GameData 로드 상태",
|
||||
"current_language": "현재 언어",
|
||||
"default_language": "기본 언어",
|
||||
"available_languages": "사용 가능한 언어",
|
||||
"current_url": "현재 URL",
|
||||
"no_results": "검색 결과가 없습니다",
|
||||
"try_again": "다시 시도해주세요"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"common": {
|
||||
"loading": "Loading...",
|
||||
"error": "An error occurred",
|
||||
"success": "Success",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"save": "Save",
|
||||
"delete": "Delete",
|
||||
"edit": "Edit",
|
||||
"close": "Close",
|
||||
"back": "Back",
|
||||
"next": "Next",
|
||||
"previous": "Previous",
|
||||
"search": "Search",
|
||||
"filter": "Filter",
|
||||
"sort": "Sort",
|
||||
"refresh": "Refresh",
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"copy": "Copy",
|
||||
"paste": "Paste",
|
||||
"cut": "Cut",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Home",
|
||||
"about": "About",
|
||||
"contact": "Contact",
|
||||
"services": "Services",
|
||||
"products": "Products",
|
||||
"news": "News",
|
||||
"support": "Support",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"register": "Register",
|
||||
"profile": "Profile",
|
||||
"settings": "Settings"
|
||||
},
|
||||
"messages": {
|
||||
"welcome": "Welcome!",
|
||||
"goodbye": "Goodbye",
|
||||
"thank_you": "Thank you",
|
||||
"sorry": "Sorry",
|
||||
"please_wait": "Please wait",
|
||||
"no_data": "No data available",
|
||||
"no_results": "No results found",
|
||||
"try_again": "Please try again"
|
||||
}
|
||||
},
|
||||
"zh-tw": {
|
||||
"common": {
|
||||
"loading": "載入中...",
|
||||
"error": "發生錯誤",
|
||||
"success": "成功",
|
||||
"cancel": "取消",
|
||||
"confirm": "確認",
|
||||
"save": "儲存",
|
||||
"delete": "刪除",
|
||||
"edit": "編輯",
|
||||
"close": "關閉",
|
||||
"back": "返回",
|
||||
"next": "下一步",
|
||||
"previous": "上一步",
|
||||
"search": "搜尋",
|
||||
"filter": "篩選",
|
||||
"sort": "排序",
|
||||
"refresh": "重新整理",
|
||||
"download": "下載",
|
||||
"upload": "上傳",
|
||||
"copy": "複製",
|
||||
"paste": "貼上",
|
||||
"cut": "剪下",
|
||||
"undo": "復原",
|
||||
"redo": "重做"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "首頁",
|
||||
"about": "關於我們",
|
||||
"contact": "聯絡我們",
|
||||
"services": "服務",
|
||||
"products": "產品",
|
||||
"news": "新聞",
|
||||
"support": "支援",
|
||||
"login": "登入",
|
||||
"logout": "登出",
|
||||
"register": "註冊",
|
||||
"profile": "個人資料",
|
||||
"settings": "設定"
|
||||
},
|
||||
"messages": {
|
||||
"title_test_lang": "語言設定測試!",
|
||||
"welcome": "歡迎!",
|
||||
"GameData_load_status": "GameData 載入狀態",
|
||||
"current_language": "目前語言",
|
||||
"default_language": "預設語言",
|
||||
"available_languages": "可用語言",
|
||||
"current_url": "目前網址",
|
||||
"no_results": "找不到結果",
|
||||
"try_again": "請再試一次"
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"common": {
|
||||
"loading": "読み込み中...",
|
||||
"error": "エラーが発生しました",
|
||||
"success": "成功",
|
||||
"cancel": "キャンセル",
|
||||
"confirm": "確認",
|
||||
"save": "保存",
|
||||
"delete": "削除",
|
||||
"edit": "編集",
|
||||
"close": "閉じる",
|
||||
"back": "戻る",
|
||||
"next": "次へ",
|
||||
"previous": "前へ",
|
||||
"search": "検索",
|
||||
"filter": "フィルター",
|
||||
"sort": "並び替え",
|
||||
"refresh": "更新",
|
||||
"download": "ダウンロード",
|
||||
"upload": "アップロード",
|
||||
"copy": "コピー",
|
||||
"paste": "貼り付け",
|
||||
"cut": "切り取り",
|
||||
"undo": "元に戻す",
|
||||
"redo": "やり直し"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "ホーム",
|
||||
"about": "概要",
|
||||
"contact": "お問い合わせ",
|
||||
"services": "サービス",
|
||||
"products": "製品",
|
||||
"news": "ニュース",
|
||||
"support": "サポート",
|
||||
"login": "ログイン",
|
||||
"logout": "ログアウト",
|
||||
"register": "登録",
|
||||
"profile": "プロフィール",
|
||||
"settings": "設定"
|
||||
},
|
||||
"messages": {
|
||||
"welcome": "ようこそ!",
|
||||
"goodbye": "さようなら",
|
||||
"thank_you": "ありがとうございます",
|
||||
"sorry": "申し訳ありません",
|
||||
"please_wait": "お待ちください",
|
||||
"no_data": "データがありません",
|
||||
"no_results": "結果が見つかりません",
|
||||
"try_again": "もう一度お試しください"
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"common": {
|
||||
"loading": "Chargement...",
|
||||
"error": "Une erreur s'est produite",
|
||||
"success": "Succès",
|
||||
"cancel": "Annuler",
|
||||
"confirm": "Confirmer",
|
||||
"save": "Enregistrer",
|
||||
"delete": "Supprimer",
|
||||
"edit": "Modifier",
|
||||
"close": "Fermer",
|
||||
"back": "Retour",
|
||||
"next": "Suivant",
|
||||
"previous": "Précédent",
|
||||
"search": "Rechercher",
|
||||
"filter": "Filtrer",
|
||||
"sort": "Trier",
|
||||
"refresh": "Actualiser",
|
||||
"download": "Télécharger",
|
||||
"upload": "Téléverser",
|
||||
"copy": "Copier",
|
||||
"paste": "Coller",
|
||||
"cut": "Couper",
|
||||
"undo": "Annuler",
|
||||
"redo": "Rétablir"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Accueil",
|
||||
"about": "À propos",
|
||||
"contact": "Contact",
|
||||
"services": "Services",
|
||||
"products": "Produits",
|
||||
"news": "Actualités",
|
||||
"support": "Support",
|
||||
"login": "Connexion",
|
||||
"logout": "Déconnexion",
|
||||
"register": "S'inscrire",
|
||||
"profile": "Profil",
|
||||
"settings": "Paramètres"
|
||||
},
|
||||
"messages": {
|
||||
"welcome": "Bienvenue !",
|
||||
"goodbye": "Au revoir",
|
||||
"thank_you": "Merci",
|
||||
"sorry": "Désolé",
|
||||
"please_wait": "Veuillez patienter",
|
||||
"no_data": "Aucune donnée disponible",
|
||||
"no_results": "Aucun résultat trouvé",
|
||||
"try_again": "Veuillez réessayer"
|
||||
}
|
||||
},
|
||||
"de": {
|
||||
"common": {
|
||||
"loading": "Laden...",
|
||||
"error": "Ein Fehler ist aufgetreten",
|
||||
"success": "Erfolg",
|
||||
"cancel": "Abbrechen",
|
||||
"confirm": "Bestätigen",
|
||||
"save": "Speichern",
|
||||
"delete": "Löschen",
|
||||
"edit": "Bearbeiten",
|
||||
"close": "Schließen",
|
||||
"back": "Zurück",
|
||||
"next": "Weiter",
|
||||
"previous": "Vorherige",
|
||||
"search": "Suchen",
|
||||
"filter": "Filtern",
|
||||
"sort": "Sortieren",
|
||||
"refresh": "Aktualisieren",
|
||||
"download": "Herunterladen",
|
||||
"upload": "Hochladen",
|
||||
"copy": "Kopieren",
|
||||
"paste": "Einfügen",
|
||||
"cut": "Ausschneiden",
|
||||
"undo": "Rückgängig",
|
||||
"redo": "Wiederholen"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Startseite",
|
||||
"about": "Über uns",
|
||||
"contact": "Kontakt",
|
||||
"services": "Dienstleistungen",
|
||||
"products": "Produkte",
|
||||
"news": "Nachrichten",
|
||||
"support": "Support",
|
||||
"login": "Anmelden",
|
||||
"logout": "Abmelden",
|
||||
"register": "Registrieren",
|
||||
"profile": "Profil",
|
||||
"settings": "Einstellungen"
|
||||
},
|
||||
"messages": {
|
||||
"welcome": "Willkommen!",
|
||||
"goodbye": "Auf Wiedersehen",
|
||||
"thank_you": "Danke",
|
||||
"sorry": "Entschuldigung",
|
||||
"please_wait": "Bitte warten",
|
||||
"no_data": "Keine Daten verfügbar",
|
||||
"no_results": "Keine Ergebnisse gefunden",
|
||||
"try_again": "Bitte versuchen Sie es erneut"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"common": {
|
||||
"loading": "Cargando...",
|
||||
"error": "Ocurrió un error",
|
||||
"success": "Éxito",
|
||||
"cancel": "Cancelar",
|
||||
"confirm": "Confirmar",
|
||||
"save": "Guardar",
|
||||
"delete": "Eliminar",
|
||||
"edit": "Editar",
|
||||
"close": "Cerrar",
|
||||
"back": "Atrás",
|
||||
"next": "Siguiente",
|
||||
"previous": "Anterior",
|
||||
"search": "Buscar",
|
||||
"filter": "Filtrar",
|
||||
"sort": "Ordenar",
|
||||
"refresh": "Actualizar",
|
||||
"download": "Descargar",
|
||||
"upload": "Subir",
|
||||
"copy": "Copiar",
|
||||
"paste": "Pegar",
|
||||
"cut": "Cortar",
|
||||
"undo": "Deshacer",
|
||||
"redo": "Rehacer"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Inicio",
|
||||
"about": "Acerca de",
|
||||
"contact": "Contacto",
|
||||
"services": "Servicios",
|
||||
"products": "Productos",
|
||||
"news": "Noticias",
|
||||
"support": "Soporte",
|
||||
"login": "Iniciar sesión",
|
||||
"logout": "Cerrar sesión",
|
||||
"register": "Registrarse",
|
||||
"profile": "Perfil",
|
||||
"settings": "Configuración"
|
||||
},
|
||||
"messages": {
|
||||
"welcome": "¡Bienvenido!",
|
||||
"goodbye": "Adiós",
|
||||
"thank_you": "Gracias",
|
||||
"sorry": "Lo siento",
|
||||
"please_wait": "Por favor espere",
|
||||
"no_data": "No hay datos disponibles",
|
||||
"no_results": "No se encontraron resultados",
|
||||
"try_again": "Por favor intente de nuevo"
|
||||
}
|
||||
},
|
||||
"pt": {
|
||||
"common": {
|
||||
"loading": "Carregando...",
|
||||
"error": "Ocorreu um erro",
|
||||
"success": "Sucesso",
|
||||
"cancel": "Cancelar",
|
||||
"confirm": "Confirmar",
|
||||
"save": "Salvar",
|
||||
"delete": "Excluir",
|
||||
"edit": "Editar",
|
||||
"close": "Fechar",
|
||||
"back": "Voltar",
|
||||
"next": "Próximo",
|
||||
"previous": "Anterior",
|
||||
"search": "Pesquisar",
|
||||
"filter": "Filtrar",
|
||||
"sort": "Ordenar",
|
||||
"refresh": "Atualizar",
|
||||
"download": "Baixar",
|
||||
"upload": "Enviar",
|
||||
"copy": "Copiar",
|
||||
"paste": "Colar",
|
||||
"cut": "Cortar",
|
||||
"undo": "Desfazer",
|
||||
"redo": "Refazer"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Início",
|
||||
"about": "Sobre",
|
||||
"contact": "Contato",
|
||||
"services": "Serviços",
|
||||
"products": "Produtos",
|
||||
"news": "Notícias",
|
||||
"support": "Suporte",
|
||||
"login": "Entrar",
|
||||
"logout": "Sair",
|
||||
"register": "Registrar",
|
||||
"profile": "Perfil",
|
||||
"settings": "Configurações"
|
||||
},
|
||||
"messages": {
|
||||
"welcome": "Bem-vindo!",
|
||||
"goodbye": "Tchau",
|
||||
"thank_you": "Obrigado",
|
||||
"sorry": "Desculpe",
|
||||
"please_wait": "Por favor aguarde",
|
||||
"no_data": "Nenhum dado disponível",
|
||||
"no_results": "Nenhum resultado encontrado",
|
||||
"try_again": "Por favor tente novamente"
|
||||
}
|
||||
},
|
||||
"th": {
|
||||
"common": {
|
||||
"loading": "กำลังโหลด...",
|
||||
"error": "เกิดข้อผิดพลาด",
|
||||
"success": "สำเร็จ",
|
||||
"cancel": "ยกเลิก",
|
||||
"confirm": "ยืนยัน",
|
||||
"save": "บันทึก",
|
||||
"delete": "ลบ",
|
||||
"edit": "แก้ไข",
|
||||
"close": "ปิด",
|
||||
"back": "กลับ",
|
||||
"next": "ถัดไป",
|
||||
"previous": "ก่อนหน้า",
|
||||
"search": "ค้นหา",
|
||||
"filter": "กรอง",
|
||||
"sort": "เรียงลำดับ",
|
||||
"refresh": "รีเฟรช",
|
||||
"download": "ดาวน์โหลด",
|
||||
"upload": "อัปโหลด",
|
||||
"copy": "คัดลอก",
|
||||
"paste": "วาง",
|
||||
"cut": "ตัด",
|
||||
"undo": "เลิกทำ",
|
||||
"redo": "ทำซ้ำ"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "หน้าแรก",
|
||||
"about": "เกี่ยวกับ",
|
||||
"contact": "ติดต่อ",
|
||||
"services": "บริการ",
|
||||
"products": "ผลิตภัณฑ์",
|
||||
"news": "ข่าวสาร",
|
||||
"support": "สนับสนุน",
|
||||
"login": "เข้าสู่ระบบ",
|
||||
"logout": "ออกจากระบบ",
|
||||
"register": "สมัครสมาชิก",
|
||||
"profile": "โปรไฟล์",
|
||||
"settings": "การตั้งค่า"
|
||||
},
|
||||
"messages": {
|
||||
"welcome": "ยินดีต้อนรับ!",
|
||||
"goodbye": "ลาก่อน",
|
||||
"thank_you": "ขอบคุณ",
|
||||
"sorry": "ขออภัย",
|
||||
"please_wait": "กรุณารอสักครู่",
|
||||
"no_data": "ไม่มีข้อมูล",
|
||||
"no_results": "ไม่พบผลลัพธ์",
|
||||
"try_again": "กรุณาลองใหม่อีกครั้ง"
|
||||
}
|
||||
},
|
||||
"zh-cn": {
|
||||
"common": {
|
||||
"loading": "加载中...",
|
||||
"error": "发生错误",
|
||||
"success": "成功",
|
||||
"cancel": "取消",
|
||||
"confirm": "确认",
|
||||
"save": "保存",
|
||||
"delete": "删除",
|
||||
"edit": "编辑",
|
||||
"close": "关闭",
|
||||
"back": "返回",
|
||||
"next": "下一步",
|
||||
"previous": "上一步",
|
||||
"search": "搜索",
|
||||
"filter": "筛选",
|
||||
"sort": "排序",
|
||||
"refresh": "刷新",
|
||||
"download": "下载",
|
||||
"upload": "上传",
|
||||
"copy": "复制",
|
||||
"paste": "粘贴",
|
||||
"cut": "剪切",
|
||||
"undo": "撤销",
|
||||
"redo": "重做"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "首页",
|
||||
"about": "关于我们",
|
||||
"contact": "联系我们",
|
||||
"services": "服务",
|
||||
"products": "产品",
|
||||
"news": "新闻",
|
||||
"support": "支持",
|
||||
"login": "登录",
|
||||
"logout": "退出",
|
||||
"register": "注册",
|
||||
"profile": "个人资料",
|
||||
"settings": "设置"
|
||||
},
|
||||
"messages": {
|
||||
"welcome": "欢迎!",
|
||||
"goodbye": "再见",
|
||||
"thank_you": "谢谢",
|
||||
"sorry": "抱歉",
|
||||
"please_wait": "请稍候",
|
||||
"no_data": "无可用数据",
|
||||
"no_results": "未找到结果",
|
||||
"try_again": "请重试"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ const imagePaths = computed(() => {
|
||||
<source media="(min-width: 1024px)" :srcset="imagePaths.pc" />
|
||||
<source media="(max-width: 1023px)" :srcset="imagePaths.mo" />
|
||||
<img
|
||||
:src="imagePaths.pc"
|
||||
:src="imagePaths.mo"
|
||||
:alt="alt"
|
||||
v-bind="$attrs"
|
||||
:loading="priority === 'high' ? 'eager' : 'lazy'"
|
||||
|
||||
@@ -121,9 +121,9 @@ const calculateTooltipPosition = (trigger: HTMLElement) => {
|
||||
Object.values(isPositions.value).filter(Boolean).length === 1
|
||||
|
||||
let topPosition: number | null = null
|
||||
let bottomPosition: number | null = null
|
||||
const bottomPosition: number | null = null
|
||||
let leftPosition: number | null = null
|
||||
let rightPosition: number | null = null
|
||||
const rightPosition: number | null = null
|
||||
|
||||
if (isLefts) {
|
||||
if (isOnlyLeft) {
|
||||
|
||||
@@ -52,7 +52,7 @@ const mainOptions = computed<Options>(() => ({
|
||||
pagination: false,
|
||||
drag: props.drag,
|
||||
updateOnMove: true,
|
||||
lazyLoad: 'nearby', // 성능 최적화: 이미지 지연 로딩
|
||||
lazyLoad: 'nearby',
|
||||
}))
|
||||
|
||||
const thumbOptions = computed<Options>(() => ({
|
||||
|
||||
@@ -15,29 +15,6 @@ declare const ttq: any
|
||||
// 유틸 함수
|
||||
// ============================================================================
|
||||
|
||||
// target에 {XX1, XX2}와 같은 형태가 포함되어 있을 경우 options.clickItem으로부터 값 추출하여 세팅 [TODO: ]
|
||||
const findValueFromOption = (target: string, { options = {} }: any) => {
|
||||
if (target.includes('{') && target.includes('}')) {
|
||||
const strTargetClickItem = target.substring(
|
||||
target.indexOf('{') + 1,
|
||||
target.indexOf('}')
|
||||
)
|
||||
|
||||
const arrTargetClickItem = strTargetClickItem.split(',')
|
||||
const arrTargetClickItemValue = []
|
||||
|
||||
for (let targetClickItem of arrTargetClickItem) {
|
||||
targetClickItem = targetClickItem.trim()
|
||||
arrTargetClickItemValue.push(options.clickItem[targetClickItem])
|
||||
}
|
||||
target = target.replaceAll(
|
||||
`{${strTargetClickItem}}`,
|
||||
arrTargetClickItemValue.join(',')
|
||||
)
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
/** 브라우저 환경인지 체크 */
|
||||
const isClient = () => typeof window !== 'undefined' && import.meta.client
|
||||
|
||||
@@ -404,7 +381,6 @@ const sendMarketingLog = ({
|
||||
|
||||
export default () => {
|
||||
return {
|
||||
sendSA,
|
||||
sendLog,
|
||||
sendMarketingLog,
|
||||
useAnalyticsData,
|
||||
|
||||
@@ -91,7 +91,7 @@ export const useCheckGameStart = () => {
|
||||
clearTimeout(launcherTimeoutId)
|
||||
launcherTimeoutId = null
|
||||
}
|
||||
|
||||
|
||||
isShowCheckLauncher.value = false
|
||||
isShowDownloadLauncher.value = false
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import type {
|
||||
GameDataResponse,
|
||||
GameDataRequest,
|
||||
} from '#layers/types/api/gameData'
|
||||
|
||||
export const useGetGameDataExternal = () => {
|
||||
const { setGameData } = useGameDataStore()
|
||||
const logPrefix = {
|
||||
exception: '[Exception] /composables/useGetGameDataExternal',
|
||||
failure: '[Failure] /composables/useGetGameDataExternal',
|
||||
}
|
||||
const webGameData = ref<GameDataResponse | null>(null)
|
||||
|
||||
const getGameDataExternal = async (req: GameDataRequest) => {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
|
||||
const apiUrl = `${stoveApiBaseUrl}/pub-comm/v1.0/template/game?game_domain=${req.gameDomain}&lang_code=${req.langCode}`
|
||||
|
||||
try {
|
||||
const response = (await commonFetch('GET', apiUrl)) as GameDataResponse
|
||||
console.log('🚀 ~ getGameDataExternal:', response.value)
|
||||
|
||||
// FIXME: 테스트용 데이터 ---------------------------------------------------
|
||||
/* if (['local', 'local-gate8', 'dev'].includes(`${runtimeConfig.public.runType}`)) {
|
||||
response.value = {
|
||||
inspection_status: 1,
|
||||
inspection: {
|
||||
inspection_status: 1,
|
||||
start_date: '2025-09-19 10:00:00',
|
||||
end_date: '2025-09-19 12:00:00',
|
||||
ts_start_date: new Date().getTime(),
|
||||
ts_end_date: new Date().getTime(),
|
||||
back_ground_image_type: 'image',
|
||||
back_ground_image_url: 'https://www.onstove.com',
|
||||
inspection_title1: '',
|
||||
inspection_title2: ''
|
||||
}
|
||||
}
|
||||
} */
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
if (response?.value) {
|
||||
webGameData.value = response
|
||||
setGameData(response.value)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`${logPrefix.exception}.getGameDataExternal: `, e)
|
||||
}
|
||||
}
|
||||
|
||||
return { webGameData, getGameDataExternal }
|
||||
}
|
||||
@@ -9,5 +9,5 @@ export default defineNuxtConfig({
|
||||
components: {
|
||||
dirs: ['components'],
|
||||
global: true,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as amplitude from '@amplitude/analytics-browser'
|
||||
|
||||
// Nuxt 플러그인 정의
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
export default defineNuxtPlugin(nuxtApp => {
|
||||
// const { memberNo } = useAnalytics() as { memberNo: string }
|
||||
const memberNo = csrGetStoveMemberNo()
|
||||
|
||||
@@ -13,11 +13,11 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
pageViews: true, // 페이지 뷰 추적 활성화
|
||||
sessions: false, // 세션 추적 비활성화
|
||||
formInteractions: false, // 폼 상호작용 추적 비활성화
|
||||
fileDownloads: false // 파일 다운로드 추적 비활성화
|
||||
fileDownloads: false, // 파일 다운로드 추적 비활성화
|
||||
},
|
||||
autocapture: {
|
||||
attribution: true
|
||||
}
|
||||
attribution: true,
|
||||
},
|
||||
})
|
||||
|
||||
// Identify 이벤트 생성 및 설정
|
||||
@@ -27,7 +27,6 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
// Identify 이벤트 전송 및 사용자 ID 설정
|
||||
amplitude.identify(identifyEvent)
|
||||
amplitude.setUserId(`${memberNo}`)
|
||||
|
||||
;(window as any).amplitude = amplitude // amplitude 객체 전역으로 설정(Stove GNB에서 사용)
|
||||
|
||||
// 페이지가 숨겨질 때 이벤트 리스너 추가
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { getTrueClientIp } from '#layers/utils/apiUtil'
|
||||
|
||||
export default defineEventHandler((event) => {
|
||||
export default defineEventHandler(event => {
|
||||
let clientIP = ''
|
||||
try {
|
||||
clientIP = getTrueClientIp(event.node.req as any)
|
||||
} catch (e) {
|
||||
console.error('[Exception] /server/api/clientIp - Cannot Get Client IP: ', e)
|
||||
console.error(
|
||||
'[Exception] /server/api/clientIp - Cannot Get Client IP: ',
|
||||
e
|
||||
)
|
||||
}
|
||||
return clientIP || ''
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export default defineEventHandler(() => {
|
||||
return {
|
||||
code: 200,
|
||||
message: 'success'
|
||||
message: 'success',
|
||||
}
|
||||
})
|
||||
|
||||
@@ -11,7 +11,7 @@ function getIpAddress(event: H3Event): string {
|
||||
return getTrueClientIp(event.node.req as any) || 'unknown'
|
||||
}
|
||||
|
||||
export default defineNitroPlugin((nitroApp) => {
|
||||
export default defineNitroPlugin(nitroApp => {
|
||||
// 정적 파일 체크 함수 추가
|
||||
const isStaticFile = (path: string): boolean => {
|
||||
return /\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$/i.test(path)
|
||||
@@ -22,7 +22,7 @@ export default defineNitroPlugin((nitroApp) => {
|
||||
return path === '/health'
|
||||
}
|
||||
|
||||
nitroApp.hooks.hook('request', (event) => {
|
||||
nitroApp.hooks.hook('request', event => {
|
||||
// 정적 파일 요청은 로깅 제외
|
||||
if (isStaticFile(event.path) || isHealthCheck(event.path)) {
|
||||
return
|
||||
@@ -51,18 +51,21 @@ export default defineNitroPlugin((nitroApp) => {
|
||||
}
|
||||
})
|
||||
|
||||
nitroApp.hooks.hook('error', (error) => {
|
||||
nitroApp.hooks.hook('error', error => {
|
||||
console.error('[Nitro Error]', {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
timestamp: new Date().toISOString()
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
})
|
||||
|
||||
// 응답 헤더에서 'x-powered-by' 제거
|
||||
nitroApp.hooks.hook('render:response', (response: Partial<RenderResponse>) => {
|
||||
if (response?.headers) {
|
||||
delete response.headers['x-powered-by']
|
||||
nitroApp.hooks.hook(
|
||||
'render:response',
|
||||
(response: Partial<RenderResponse>) => {
|
||||
if (response?.headers) {
|
||||
delete response.headers['x-powered-by']
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@@ -31,6 +31,6 @@ export const useInspectionStore = defineStore('inspection', () => {
|
||||
setWebInspectionData,
|
||||
setWebInspectionStatus,
|
||||
setGameMaintenanceData,
|
||||
setGameMaintenanceStatus
|
||||
setGameMaintenanceStatus,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
export const useCallerInfoStore = defineStore('callerInfoStore', () => {
|
||||
const callerId = ref<string | null>('')
|
||||
const callerDetail = ref<string | null>('')
|
||||
|
||||
const setCallerId = (paramCallerId: string | null) => {
|
||||
callerId.value = paramCallerId
|
||||
}
|
||||
const setCallerDetail = (paramCalleDetail: string | null) => {
|
||||
callerDetail.value = paramCalleDetail
|
||||
}
|
||||
|
||||
return { callerId, callerDetail, setCallerId, setCallerDetail }
|
||||
})
|
||||
@@ -38,7 +38,6 @@ const { pageData } = storeToRefs(pageDataStore)
|
||||
const { getOperateResources } = useOperateResources()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
// Constants
|
||||
const COLOR_INDEX = { BACKGROUND: 0, TEXT: 1 } as const
|
||||
|
||||
const preregistModalRef = ref<{
|
||||
@@ -71,7 +70,6 @@ const preregistSNS = computed(
|
||||
() => getSupportedPlatforms('2', gameData?.value?.os_type) as Platform[]
|
||||
)
|
||||
|
||||
// SNS Buttons
|
||||
const snsButtonsData = computed(() => {
|
||||
const buttons = getComponentGroupAry(props.components, 'imgSnsButton')
|
||||
const links = getComponentGroupAry(props.components, 'txtSnsLink')
|
||||
@@ -85,7 +83,6 @@ const snsButtonsData = computed(() => {
|
||||
}))
|
||||
})
|
||||
|
||||
// Button Colors
|
||||
const buttonColors = computed(() => {
|
||||
const colorData = getComponentGroupAry(
|
||||
props.components,
|
||||
@@ -124,7 +121,6 @@ const accPaginationData = computed(() =>
|
||||
getComponentGroupAry(props.components, 'pagination')
|
||||
)
|
||||
|
||||
// Async Data - 리워드 완료 데이터
|
||||
const { data: rewardCompletedData } = await useAsyncData(
|
||||
`fx-preregist-resources-${pageData.value?.page_seq}-${pageData.value?.page_ver}-${props.pageVerTmplSeq}`,
|
||||
async () => {
|
||||
|
||||
@@ -96,5 +96,5 @@ export type {
|
||||
// [E] Type in czn_homepage_brand_siteConfig.json ----------------------------------------
|
||||
DataizationType,
|
||||
ReqGetDataization,
|
||||
ResGetDataization
|
||||
ResGetDataization,
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ export const hasComponentGroup = (
|
||||
|
||||
/**
|
||||
* 컴포넌트 컨테이너를 반환합니다.
|
||||
* @param components props.components
|
||||
* @param components props.components 또는 group 객체
|
||||
* @param componentName 컴포넌트 이름
|
||||
* @param options 옵션
|
||||
* - isGroup: groups 속성에서 데이터 가져오기 (기본값: false)
|
||||
|
||||
@@ -78,11 +78,13 @@ export const getYouTubeUrl = (
|
||||
* 유튜브 URL에서 비디오 ID를 추출하고, 비디오 ID로부터 썸네일 URL을 생성합니다.
|
||||
* @param url - 유튜브 URL
|
||||
* @param quality - 썸네일 품질 ('default', 'medium', 'high', 'standard', 'maxres')
|
||||
* @param format - 이미지 포맷 ('jpg' | 'webp'), 기본값은 'webp'
|
||||
* @returns 썸네일 URL
|
||||
*/
|
||||
export const getYouTubeThumbnail = (
|
||||
url: string,
|
||||
quality: 'default' | 'medium' | 'high' | 'standard' | 'maxres' = 'standard'
|
||||
quality: 'default' | 'medium' | 'high' | 'standard' | 'maxres' = 'standard',
|
||||
format: 'jpg' | 'webp' = 'webp'
|
||||
): string => {
|
||||
const videoId = getYouTubeId(url)
|
||||
if (!videoId) return ''
|
||||
@@ -95,5 +97,8 @@ export const getYouTubeThumbnail = (
|
||||
maxres: 'maxresdefault',
|
||||
}
|
||||
|
||||
return `https://img.youtube.com/vi/${videoId}/${qualityMap[quality]}.jpg`
|
||||
const basePath = format === 'webp' ? 'vi_webp' : 'vi'
|
||||
const extension = format === 'webp' ? 'webp' : 'jpg'
|
||||
|
||||
return `https://img.youtube.com/${basePath}/${videoId}/${qualityMap[quality]}.${extension}`
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,9 @@
|
||||
"types/**/*",
|
||||
"i18n/**/*",
|
||||
"layers/**/*",
|
||||
"app/**/*"
|
||||
, "temp/inspection.ts", "temp/middleware.ts" ],
|
||||
"app/**/*",
|
||||
"temp/inspection.ts",
|
||||
"temp/middleware.ts"
|
||||
],
|
||||
"exclude": [".nuxt/types/**/*", "node_modules"]
|
||||
}
|
||||
|
||||
5
types/i18n.d.ts
vendored
5
types/i18n.d.ts
vendored
@@ -1,8 +1,9 @@
|
||||
declare global {
|
||||
function defineI18nLocale(
|
||||
loader: (locale: string) => Promise<Record<string, any>> | Record<string, any>
|
||||
loader: (
|
||||
locale: string
|
||||
) => Promise<Record<string, any>> | Record<string, any>
|
||||
): any
|
||||
}
|
||||
|
||||
export {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user