refactor: 에러 처리 완료, 페이지 접근 api 미들웨어로 수정
This commit is contained in:
@@ -1,120 +0,0 @@
|
|||||||
# Dynamic I18n Routes - gameData.lang_codes 기반 언어 제외 설정
|
|
||||||
|
|
||||||
이 문서는 `gameDataFromServer.lang_codes`를 기반으로 특정 언어들을 제외하는 Ignoring Localized Routes 기능의 사용법을 설명합니다.
|
|
||||||
|
|
||||||
## 구현된 기능
|
|
||||||
|
|
||||||
### 1. 동적 언어 제외 설정
|
|
||||||
- `gameDataFromServer.lang_codes`에 포함된 언어만 허용
|
|
||||||
- 포함되지 않은 언어는 자동으로 제외 처리
|
|
||||||
- 런타임에 동적으로 언어 설정 변경
|
|
||||||
|
|
||||||
### 2. 구현된 파일들
|
|
||||||
|
|
||||||
#### `i18n.config.ts`
|
|
||||||
- `getI18n()` 함수에 `allowedLangCodes` 매개변수 추가
|
|
||||||
- `generatePageExclusions()` 함수로 언어 제외 설정 생성
|
|
||||||
- `customRoutes: 'config'` 설정으로 페이지별 언어 제외 지원
|
|
||||||
|
|
||||||
#### `layers/composables/useDynamicI18nRoutes.ts`
|
|
||||||
- `getAllowedLangCodes()`: 허용된 언어 목록 반환
|
|
||||||
- `isLangAllowed()`: 특정 언어 허용 여부 확인
|
|
||||||
- `getI18nRouteConfig()`: `defineI18nRoute`용 설정 생성
|
|
||||||
- `getExcludedLangConfig()`: 특정 언어 제외 설정 생성
|
|
||||||
|
|
||||||
#### `layers/plugins/dynamic-i18n-routes.client.ts`
|
|
||||||
- 클라이언트 사이드에서 gameData 변경 감지
|
|
||||||
- 언어 설정 동적 업데이트
|
|
||||||
|
|
||||||
#### `layers/plugins/dynamic-i18n-routes.server.ts`
|
|
||||||
- 서버 사이드에서 gameData 기반 언어 설정 적용
|
|
||||||
|
|
||||||
## 사용법
|
|
||||||
|
|
||||||
### 1. 페이지에서 동적 언어 제외 설정
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup lang="ts">
|
|
||||||
// 동적 i18n 라우트 설정
|
|
||||||
const { getI18nRouteConfig } = useDynamicI18nRoutes();
|
|
||||||
|
|
||||||
// gameData.lang_codes를 기반으로 동적 언어 제외 설정
|
|
||||||
const i18nRouteConfig = getI18nRouteConfig();
|
|
||||||
if (i18nRouteConfig) {
|
|
||||||
defineI18nRoute(i18nRouteConfig);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 특정 언어만 허용하는 경우
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup lang="ts">
|
|
||||||
const { getI18nRouteConfig } = useDynamicI18nRoutes();
|
|
||||||
|
|
||||||
// gameData.lang_codes에 포함된 언어만 허용
|
|
||||||
const i18nRouteConfig = getI18nRouteConfig();
|
|
||||||
if (i18nRouteConfig) {
|
|
||||||
defineI18nRoute(i18nRouteConfig);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 특정 언어를 제외하는 경우
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup lang="ts">
|
|
||||||
const { getExcludedLangConfig } = useDynamicI18nRoutes();
|
|
||||||
|
|
||||||
// 특정 언어들을 제외 (예: 'fr', 'de' 제외)
|
|
||||||
const excludedConfig = getExcludedLangConfig(['fr', 'de']);
|
|
||||||
if (excludedConfig) {
|
|
||||||
defineI18nRoute(excludedConfig);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 언어 허용 여부 확인
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup lang="ts">
|
|
||||||
const { isLangAllowed } = useDynamicI18nRoutes();
|
|
||||||
|
|
||||||
// 특정 언어가 허용되는지 확인
|
|
||||||
const isFrenchAllowed = isLangAllowed('fr');
|
|
||||||
const isKoreanAllowed = isLangAllowed('ko');
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 동작 원리
|
|
||||||
|
|
||||||
1. **서버 사이드**: `gameDataFromServer.lang_codes`를 기반으로 초기 언어 설정 적용
|
|
||||||
2. **클라이언트 사이드**: gameData 변경 시 언어 설정 동적 업데이트
|
|
||||||
3. **페이지 레벨**: `defineI18nRoute`를 통해 페이지별 언어 제외 설정
|
|
||||||
4. **자동 리다이렉트**: 허용되지 않은 언어로 접근 시 기본 언어로 리다이렉트
|
|
||||||
|
|
||||||
## 예시 시나리오
|
|
||||||
|
|
||||||
### 시나리오 1: 게임별 언어 제한
|
|
||||||
```typescript
|
|
||||||
// gameData.lang_codes = ['ko', 'en', 'ja']
|
|
||||||
// 결과: 한국어, 영어, 일본어만 허용, 나머지 언어는 자동 제외
|
|
||||||
```
|
|
||||||
|
|
||||||
### 시나리오 2: 특정 언어 제외
|
|
||||||
```typescript
|
|
||||||
// gameData.lang_codes = ['ko', 'en', 'ja', 'zh-tw', 'fr', 'de', 'es', 'pt', 'th', 'zh-cn']
|
|
||||||
// getExcludedLangConfig(['fr', 'de']) 호출
|
|
||||||
// 결과: 프랑스어, 독일어 제외, 나머지 언어 허용
|
|
||||||
```
|
|
||||||
|
|
||||||
## 주의사항
|
|
||||||
|
|
||||||
1. `defineI18nRoute`는 컴파일 타임에 실행되므로, 동적 설정이 필요한 경우 `watchEffect`나 `watch`를 사용
|
|
||||||
2. SSR과 클라이언트 사이드 모두에서 일관된 동작을 위해 플러그인 사용 권장
|
|
||||||
3. 언어 변경 시 사용자 경험을 고려한 적절한 리다이렉트 처리 필요
|
|
||||||
|
|
||||||
## 참고 문서
|
|
||||||
|
|
||||||
- [Nuxt i18n - Ignoring Localized Routes](https://i18n.nuxtjs.org/docs/guide/ignoring-localized-routes)
|
|
||||||
- [Nuxt i18n - defineI18nRoute](https://i18n.nuxtjs.org/docs/compiler-macros/define-i18n-route)
|
|
||||||
105
app/error.vue
Normal file
105
app/error.vue
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<template>
|
||||||
|
<div class="min-h-[100vh] flex flex-col bg-[#F0F0F0] pb-12">
|
||||||
|
<LayoutsStoveHeader />
|
||||||
|
|
||||||
|
<Transition name="fade">
|
||||||
|
<div v-if="!isLoading" class="flex-1 flex items-center justify-center p-25">
|
||||||
|
<div class="flex flex-col items-center gap-6 w-full">
|
||||||
|
<!-- Stove Logo -->
|
||||||
|
<div class="flex items-center justify-center h-7">
|
||||||
|
<img
|
||||||
|
src="/images/common/logo-stove.svg"
|
||||||
|
alt="Stove"
|
||||||
|
class="h-full w-auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error Icon and Text -->
|
||||||
|
<div class="flex flex-col items-center gap-4 w-full">
|
||||||
|
<!-- Error Icon -->
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<img
|
||||||
|
src="/images/common/img_error.png"
|
||||||
|
alt="Error"
|
||||||
|
class="w-40 h-40 md:w-60 md:h-60"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error Text -->
|
||||||
|
<div class="flex flex-col items-center gap-2 w-full">
|
||||||
|
<h1 class="font-medium text-xl md:text-2xl leading-[1.5] tracking-[-0.03em] text-center text-[#1F1F1F] m-0">
|
||||||
|
<span v-dompurify-html="errorTitle"></span>
|
||||||
|
</h1>
|
||||||
|
<p v-dompurify-html="errorMsg" class="font-normal text-sm md:text-base leading-[1.7] md:leading-[1.625] tracking-[-0.03em] text-center text-[#666666] m-0"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Home Button -->
|
||||||
|
<AtomsButton
|
||||||
|
type="action"
|
||||||
|
button-size="size-small md:size-large"
|
||||||
|
background-color="#FC4420"
|
||||||
|
text-color="#FFFFFF"
|
||||||
|
@click="handleError"
|
||||||
|
>
|
||||||
|
<span v-dompurify-html="tm('Error_Official_Page')"></span>
|
||||||
|
</AtomsButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
interface ErrorProps {
|
||||||
|
error?: {
|
||||||
|
statusCode?: number
|
||||||
|
statusMessage?: string
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tm } = useI18n()
|
||||||
|
const props = withDefaults(defineProps<ErrorProps>(), {
|
||||||
|
error: () => ({})
|
||||||
|
})
|
||||||
|
const nuxtError = useError()
|
||||||
|
const currentError = computed(() => props.error || nuxtError.value)
|
||||||
|
|
||||||
|
const isLoading = ref(true)
|
||||||
|
|
||||||
|
const errorTitle = ref('')
|
||||||
|
const errorMsg = ref('')
|
||||||
|
|
||||||
|
//error clear 함수 생성
|
||||||
|
|
||||||
|
const localePath = useLocalePath()
|
||||||
|
|
||||||
|
// const handleError = () => clearError({ redirect: '/' })
|
||||||
|
const handleError = () => {
|
||||||
|
window.location.href = localePath('/')
|
||||||
|
// clearError({ redirect: `${localePath('/')}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 500 에러 발생 시 /error 페이지로 리다이렉트
|
||||||
|
onMounted(() => {
|
||||||
|
const statusCode = currentError.value?.statusCode
|
||||||
|
console.log("🚀 ~ 2222 currentError==:", currentError.value?.message)
|
||||||
|
console.log("🚀 ~ 222222 statusCode:", statusCode)
|
||||||
|
|
||||||
|
if (statusCode === 500) {
|
||||||
|
errorTitle.value = tm('Error_500_Inconvenience')
|
||||||
|
errorMsg.value = tm('Error_500_Sorry')
|
||||||
|
} else {
|
||||||
|
errorTitle.value = tm('Error_404_Not_Found')
|
||||||
|
errorMsg.value = tm('Error_404_Not_Found2')
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
isLoading.value = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="min-h-[100vh] flex flex-col bg-[#F0F0F0] pb-12">
|
|
||||||
<LayoutsStoveHeader />
|
|
||||||
|
|
||||||
<Transition name="fade">
|
|
||||||
<div v-if="!isLoading" class="flex-1 flex items-center justify-center p-25">
|
|
||||||
<div class="flex flex-col items-center gap-6 w-full">
|
|
||||||
<!-- Stove Logo -->
|
|
||||||
<div class="flex items-center justify-center h-7">
|
|
||||||
<img
|
|
||||||
src="/images/common/logo-stove.svg"
|
|
||||||
alt="Stove"
|
|
||||||
class="h-full w-auto"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Error Icon and Text -->
|
|
||||||
<div class="flex flex-col items-center gap-4 w-full">
|
|
||||||
<!-- Error Icon -->
|
|
||||||
<div class="flex items-center justify-center">
|
|
||||||
<img
|
|
||||||
src="/images/common/img_error.png"
|
|
||||||
alt="Error"
|
|
||||||
class="w-40 h-40 md:w-60 md:h-60"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Error Text -->
|
|
||||||
<div class="flex flex-col items-center gap-2 w-full">
|
|
||||||
<h1 class="font-medium text-xl md:text-2xl leading-[1.5] tracking-[-0.03em] text-center text-[#1F1F1F] m-0">
|
|
||||||
<span v-dompurify-html="tm('Error_404_Not_Found')"></span>
|
|
||||||
</h1>
|
|
||||||
<p v-dompurify-html="tm('Error_404_Not_Found2')" class="font-normal text-sm md:text-base leading-[1.7] md:leading-[1.625] tracking-[-0.03em] text-center text-[#666666] m-0"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Home Button -->
|
|
||||||
<AtomsLocaleLink to="/brand" class="flex items-center justify-center font-medium text-sm leading-[1.4] tracking-[-0.03em] text-white bg-[#FC4420] border border-black/10 rounded-lg px-10 h-12 cursor-pointer transition-all duration-300 backdrop-blur-[30px] shadow-[0px_0.9131946563720703px_1.4666459560394287px_0px_rgba(0,0,0,0.06),0px_2.194533586502075px_3.5245540142059326px_0px_rgba(0,0,0,0.09)] hover:bg-[#E03D1C] hover:shadow-[0px_0.9131946563720703px_1.4666459560394287px_0px_rgba(0,0,0,0.06),0px_2.194533586502075px_3.5245540142059326px_0px_rgba(0,0,0,0.09),0px_4.1321120262146px_6.636422634124756px_0px_rgba(0,0,0,0.12)] active:translate-y-px">
|
|
||||||
<span v-dompurify-html="tm('Error_Official_Page')"></span>
|
|
||||||
</AtomsLocaleLink>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
const { tm } = useI18n()
|
|
||||||
|
|
||||||
const isLoading = ref(true)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
nextTick(() => {
|
|
||||||
isLoading.value = false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
||||||
118
error.vue
118
error.vue
@@ -1,14 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen flex flex-col bg-[#191919]">
|
<div class="min-h-[100vh] flex flex-col bg-[#F0F0F0] pb-12">
|
||||||
<LayoutsStoveHeader />
|
<LayoutsStoveHeader />
|
||||||
<div class="flex-1 flex items-center justify-center bg-[#F0F0F0] pt-[223px] pb-[223px] px-5 max-[374px]:px-5 md:px-[530px]">
|
|
||||||
<div class="flex flex-col items-center gap-6 w-full max-w-[513px] max-[374px]:max-w-[335px] md:max-w-[860px] py-10 max-[374px]:gap-6 md:gap-6 md:py-20">
|
<Transition name="fade">
|
||||||
|
<div v-if="!isLoading" class="flex-1 flex items-center justify-center p-25">
|
||||||
|
<div class="flex flex-col items-center gap-6 w-full">
|
||||||
<!-- Stove Logo -->
|
<!-- Stove Logo -->
|
||||||
<div class="flex items-center justify-center w-[123.2px] h-7">
|
<div class="flex items-center justify-center h-7">
|
||||||
<img
|
<img
|
||||||
src="/images/common/logo-stove.svg"
|
src="/images/common/logo-stove.svg"
|
||||||
alt="Stove"
|
alt="Stove"
|
||||||
class="w-full h-auto"
|
class="h-full w-auto"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -24,72 +26,70 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Error Text -->
|
<!-- Error Text -->
|
||||||
<div class="flex flex-col items-center gap-1 w-full">
|
<div class="flex flex-col items-center gap-2 w-full">
|
||||||
<h1 class="font-['Spoqa_Han_Sans_Neo',sans-serif] font-medium text-xl md:text-2xl leading-[1.5] md:leading-[1.4166666666666667] tracking-[-0.03em] text-center text-[#1F1F1F] m-0">
|
<h1 class="font-medium text-xl md:text-2xl leading-[1.5] tracking-[-0.03em] text-center text-[#1F1F1F] m-0">
|
||||||
{{ errorTitle }}
|
<span v-dompurify-html="errorTitle"></span>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="font-['Spoqa_Han_Sans_Neo',sans-serif] font-normal text-sm md:text-base leading-[1.7142857142857142] md:leading-[1.625] tracking-[-0.03em] text-center text-[#666666] m-0">
|
<p v-dompurify-html="errorMsg" class="font-normal text-sm md:text-base leading-[1.7] md:leading-[1.625] tracking-[-0.03em] text-center text-[#666666] m-0"></p>
|
||||||
{{ errorDescription }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Home Button -->
|
<!-- Home Button -->
|
||||||
<button
|
<AtomsButton class="flex items-center justify-center font-medium text-sm leading-[1.4] tracking-[-0.03em] text-white bg-[#FC4420] border border-black/10 rounded-lg px-10 h-12 cursor-pointer transition-all duration-300 backdrop-blur-[30px] shadow-[0px_0.9131946563720703px_1.4666459560394287px_0px_rgba(0,0,0,0.06),0px_2.194533586502075px_3.5245540142059326px_0px_rgba(0,0,0,0.09)] hover:bg-[#E03D1C] hover:shadow-[0px_0.9131946563720703px_1.4666459560394287px_0px_rgba(0,0,0,0.06),0px_2.194533586502075px_3.5245540142059326px_0px_rgba(0,0,0,0.09),0px_4.1321120262146px_6.636422634124756px_0px_rgba(0,0,0,0.12)] active:translate-y-px" @click="handleError">
|
||||||
class="flex items-center justify-center font-['Spoqa_Han_Sans_Neo',sans-serif] font-medium text-sm leading-[1.4285714285714286em] tracking-[-0.03em] text-white bg-[#FC4420] border border-black/10 rounded-lg px-10 h-12 cursor-pointer transition-all duration-300 backdrop-blur-[30px] shadow-[0px_0.9131946563720703px_1.4666459560394287px_0px_rgba(0,0,0,0.06),0px_2.194533586502075px_3.5245540142059326px_0px_rgba(0,0,0,0.09)] hover:bg-[#E03D1C] hover:shadow-[0px_0.9131946563720703px_1.4666459560394287px_0px_rgba(0,0,0,0.06),0px_2.194533586502075px_3.5245540142059326px_0px_rgba(0,0,0,0.09),0px_4.1321120262146px_6.636422634124756px_0px_rgba(0,0,0,0.12)] active:translate-y-px"
|
<span v-dompurify-html="tm('Error_Official_Page')"></span>
|
||||||
@click="handleGoHome"
|
</AtomsButton>
|
||||||
>
|
|
||||||
{{ homeButtonText }}
|
</div>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NuxtError } from '#app'
|
interface ErrorProps {
|
||||||
import { useGameDataStore } from '#layers/stores/useGameDataStore'
|
error?: {
|
||||||
import { useLocalePath } from '#i18n'
|
statusCode?: number
|
||||||
|
statusMessage?: string
|
||||||
const props = defineProps({
|
message?: string
|
||||||
error: Object as () => NuxtError,
|
}
|
||||||
})
|
|
||||||
|
|
||||||
const gameDataStore = useGameDataStore()
|
|
||||||
const { gameData } = storeToRefs(gameDataStore)
|
|
||||||
const localePath = useLocalePath()
|
|
||||||
|
|
||||||
const gameName = computed(() => gameData.value?.game_name || '게임')
|
|
||||||
const homeButtonText = computed(() => `${gameName.value} 홈페이지 가기`)
|
|
||||||
|
|
||||||
const handleGoHome = () => {
|
|
||||||
const homePath = localePath('/')
|
|
||||||
navigateTo(homePath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 에러 상태 코드에 따른 메시지 설정
|
const { tm } = useI18n()
|
||||||
const errorTitle = computed(() => {
|
const props = withDefaults(defineProps<ErrorProps>(), {
|
||||||
if (props.error?.statusCode === 404) {
|
error: () => ({})
|
||||||
return '페이지를 찾을 수 없어요.'
|
})
|
||||||
|
const nuxtError = useError()
|
||||||
|
const currentError = computed(() => props.error || nuxtError.value)
|
||||||
|
|
||||||
|
const isLoading = ref(true)
|
||||||
|
|
||||||
|
const errorTitle = ref('')
|
||||||
|
const errorMsg = ref('')
|
||||||
|
|
||||||
|
//error clear 함수 생성
|
||||||
|
|
||||||
|
const handleError = () => clearError({ redirect: '/' })
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 500 에러 발생 시 /error 페이지로 리다이렉트
|
||||||
|
onMounted(() => {
|
||||||
|
const statusCode = currentError.value?.statusCode
|
||||||
|
console.log("🚀 ~ 1111currentError==:", currentError.value?.message)
|
||||||
|
console.log("🚀 ~ 1111statusCode:", statusCode)
|
||||||
|
|
||||||
|
if (statusCode === 500) {
|
||||||
|
errorTitle.value = tm('Error_500_Inconvenience')
|
||||||
|
errorMsg.value = tm('Error_500_Sorry')
|
||||||
|
} else {
|
||||||
|
errorTitle.value = tm('Error_404_Not_Found')
|
||||||
|
errorMsg.value = tm('Error_404_Not_Found2')
|
||||||
}
|
}
|
||||||
return '페이지를 찾을 수 없어요.'
|
|
||||||
|
nextTick(() => {
|
||||||
|
isLoading.value = false
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const errorDescription = computed(() => {
|
</script>
|
||||||
if (props.error?.statusCode === 404) {
|
|
||||||
return '주소가 바뀌었거나 잘못 입력된 것 같아요. 주소를 다시 확인해 주세요.'
|
|
||||||
}
|
|
||||||
return '주소가 바뀌었거나 잘못 입력된 것 같아요. 주소를 다시 확인해 주세요.'
|
|
||||||
})
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
layout: 'only-stove'
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<style scoped>
|
|
||||||
/* :deep(.game-wrapper) {
|
|
||||||
display: none !important;
|
|
||||||
} */
|
|
||||||
</style>
|
|
||||||
@@ -66,10 +66,10 @@ const getI18n = (allowedLangCodes?: string[]): NuxtI18nOptions => {
|
|||||||
debug: false,
|
debug: false,
|
||||||
// 동적으로 언어 제외 설정을 위한 pages 설정
|
// 동적으로 언어 제외 설정을 위한 pages 설정
|
||||||
customRoutes: 'config',
|
customRoutes: 'config',
|
||||||
pages:
|
// pages:
|
||||||
allowedLangCodes && allowedLangCodes.length > 0
|
// allowedLangCodes && allowedLangCodes.length > 0
|
||||||
? generatePageExclusions(allowedLangCodes)
|
// ? generatePageExclusions(allowedLangCodes)
|
||||||
: {},
|
// : {},
|
||||||
// 추가적인 설정이 필요하다면 여기에 포함시킬 수 있습니다.
|
// 추가적인 설정이 필요하다면 여기에 포함시킬 수 있습니다.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,24 +7,29 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 현재 경로에서 언어 코드 추출
|
// 현재 경로에서 언어 코드 추출
|
||||||
// 예: /ko/about/story -> ko
|
|
||||||
// 예: /en/test/page -> en
|
|
||||||
|
|
||||||
const gameDataStore = useGameDataStore()
|
const gameDataStore = useGameDataStore()
|
||||||
const gameData = gameDataStore.gameData as GameDataValue
|
const gameData = gameDataStore.gameData as GameDataValue
|
||||||
const langCodes = gameData?.lang_codes
|
const langCodes = gameData?.lang_codes
|
||||||
const currentLangCode = csrGetFinalLocale(to.path, langCodes)
|
const currentLangCode = csrGetFinalLocale(to.path, langCodes)
|
||||||
|
const { getPathAfterLanguage } = usePathResolver()
|
||||||
|
const pageUrl = getPathAfterLanguage(to.path)
|
||||||
|
|
||||||
// const languagePattern = /^\/([a-z]{2})(?:\/|$)/
|
|
||||||
// const match = to.path.match(languagePattern)
|
|
||||||
// const currentLangCode = match ? match[1] : null
|
|
||||||
console.log("🚀 777777 ~ currentLangCode:", currentLangCode)
|
|
||||||
|
|
||||||
//현재 url에서 게임 도메인만 추출
|
//현재 url에서 게임 도메인만 추출
|
||||||
const currentDomain = window.location.hostname
|
const currentDomain = window.location.hostname
|
||||||
const runtimeConfig = useRuntimeConfig()
|
const runtimeConfig = useRuntimeConfig()
|
||||||
|
|
||||||
|
// 쿼리스트링에서 f 파라미터 값 추출 (CSR용)
|
||||||
|
const fValue = (to.query.f as string) || ''
|
||||||
|
|
||||||
|
// 미리보기 API 호출 처리
|
||||||
|
let finalGameDomain = currentDomain
|
||||||
|
if (fValue === 'preview') {
|
||||||
|
finalGameDomain = 'samplegame.onstove.com'
|
||||||
|
}
|
||||||
|
|
||||||
const req: GameDataRequest = {
|
const req: GameDataRequest = {
|
||||||
gameDomain: `${currentDomain}`,
|
gameDomain: `${finalGameDomain}`,
|
||||||
langCode: `${currentLangCode}`,
|
langCode: `${currentLangCode}`,
|
||||||
game_alias: '',
|
game_alias: '',
|
||||||
lang_code: `${currentLangCode}`,
|
lang_code: `${currentLangCode}`,
|
||||||
@@ -34,16 +39,19 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
|||||||
const { getGameDataExternal } = useGetGameDataExternal()
|
const { getGameDataExternal } = useGetGameDataExternal()
|
||||||
await getGameDataExternal(req)
|
await getGameDataExternal(req)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 허용된 언어 코드 목록≈≈
|
// 허용된 언어 코드 목록≈≈
|
||||||
const allowedLangCodes = langCodes || []
|
const allowedLangCodes = langCodes || []
|
||||||
|
|
||||||
|
// error 페이지는 API 호출하지 않음
|
||||||
|
if (pageUrl === '/error' || to.path.includes('/error')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
// 현재 언어가 허용된 언어 목록에 없으면 에러 페이지로 이동
|
// 현재 언어가 허용된 언어 목록에 없으면 에러 페이지로 이동
|
||||||
if (currentLangCode && !allowedLangCodes.includes(currentLangCode)) {
|
if (currentLangCode && !allowedLangCodes.includes(currentLangCode)) {
|
||||||
throw createError({
|
return navigateTo(`/${currentLangCode}/error`, { external: false })
|
||||||
statusCode: 404,
|
// throw createError({
|
||||||
statusMessage: 'Language not supported',
|
// statusCode: 404,
|
||||||
})
|
// statusMessage: 'Language not supported11',
|
||||||
|
// })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
export default defineNuxtRouteMiddleware(async to => {
|
export default defineNuxtRouteMiddleware(async to => {
|
||||||
try {
|
try {
|
||||||
|
//error 발생시에는 미들웨어 실행하지 않음
|
||||||
|
//error 객체 조회
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
|
|
||||||
|
const error = useError()
|
||||||
|
if(error.value?.statusCode){
|
||||||
|
return showError(createError({
|
||||||
|
statusCode: error.value?.statusCode,
|
||||||
|
statusMessage: error.value?.message,
|
||||||
|
fatal: true,
|
||||||
|
// data: { path: to.path }
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
const gameDataStore = useGameDataStore()
|
const gameDataStore = useGameDataStore()
|
||||||
const { gameData } = storeToRefs(gameDataStore)
|
const { gameData } = storeToRefs(gameDataStore)
|
||||||
console.log('🚀 ~ 00000 gameData:', gameData.value)
|
console.log('🚀 ~ 00000 gameData:', gameData.value)
|
||||||
@@ -40,6 +53,7 @@ export default defineNuxtRouteMiddleware(async to => {
|
|||||||
!to.path.includes('inspection') &&
|
!to.path.includes('inspection') &&
|
||||||
!to.path.includes('api')
|
!to.path.includes('api')
|
||||||
) {
|
) {
|
||||||
|
console.log('e111111111 eee')
|
||||||
// 점검 중인 경우
|
// 점검 중인 경우
|
||||||
return navigateTo(`/${finalLocale}/inspection`, { external: true })
|
return navigateTo(`/${finalLocale}/inspection`, { external: true })
|
||||||
} else if (
|
} else if (
|
||||||
@@ -47,8 +61,11 @@ export default defineNuxtRouteMiddleware(async to => {
|
|||||||
to.path?.indexOf('inspection') !== -1
|
to.path?.indexOf('inspection') !== -1
|
||||||
) {
|
) {
|
||||||
// 점검이 종료된 후 점검 페이지 접근시 메인으로 리다이렉트
|
// 점검이 종료된 후 점검 페이지 접근시 메인으로 리다이렉트
|
||||||
|
console.log('ddddd 222222')
|
||||||
return navigateTo(`/${finalLocale}`, { external: true })
|
return navigateTo(`/${finalLocale}`, { external: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('33333333 eee')
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[Exception] /middleware/inspection: ', e)
|
console.error('[Exception] /middleware/inspection: ', e)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { PageDataResponse } from '#layers/types/api/pageData'
|
|||||||
import type { GameDataValue } from '#layers/types/api/gameData'
|
import type { GameDataValue } from '#layers/types/api/gameData'
|
||||||
|
|
||||||
export default defineNuxtRouteMiddleware(async (to, _from) => {
|
export default defineNuxtRouteMiddleware(async (to, _from) => {
|
||||||
// [TODO] 하이드레이션 에러 처리
|
// client에서만 동작되도록 처리
|
||||||
if (!import.meta.client) return
|
if (!import.meta.client) return
|
||||||
|
|
||||||
const runtimeConfig = useRuntimeConfig()
|
const runtimeConfig = useRuntimeConfig()
|
||||||
@@ -28,10 +28,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 서버 사이드에서는 스킵
|
|
||||||
if (import.meta.server) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (to.path.includes('inspection')) {
|
if (to.path.includes('inspection')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -45,11 +42,26 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
|||||||
|
|
||||||
// pageUrl이 빈값이거나 null이면 /brand로 리다이렉트
|
// pageUrl이 빈값이거나 null이면 /brand로 리다이렉트
|
||||||
if (!pageUrl || pageUrl === '' || pageUrl === '/' || pageUrl === `/${langCode}/`) {
|
if (!pageUrl || pageUrl === '' || pageUrl === '/' || pageUrl === `/${langCode}/`) {
|
||||||
return navigateTo(`/${langCode}/brand`, { replace: true })
|
return navigateTo(`/${langCode}/brand`, { external: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessToken = csrGetAccessToken()
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 쿼리스트링에서 f 파라미터 값 추출 (CSR용)
|
||||||
|
const fValue = (to.query.f as string) || ''
|
||||||
|
|
||||||
|
// 미리보기 API 호출 처리
|
||||||
|
let finalGameDomain = gameDomain
|
||||||
|
if (fValue === 'preview') {
|
||||||
|
finalGameDomain = 'samplegame.onstove.com'
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryParams: Record<string, string> = {
|
const queryParams: Record<string, string> = {
|
||||||
game_domain: gameDomain,
|
game_domain: finalGameDomain,
|
||||||
lang_code: langCode,
|
lang_code: langCode,
|
||||||
page_url: pageUrl,
|
page_url: pageUrl,
|
||||||
_t: Date.now().toString(), // 캐시 무효화를 위한 타임스탬프
|
_t: Date.now().toString(), // 캐시 무효화를 위한 타임스탬프
|
||||||
@@ -57,13 +69,37 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
|||||||
// console.log('🚀 ~ queryParams:', queryParams)
|
// console.log('🚀 ~ queryParams:', queryParams)
|
||||||
|
|
||||||
const response = (await commonFetch('GET', apiUrl, {
|
const response = (await commonFetch('GET', apiUrl, {
|
||||||
|
headers,
|
||||||
query: queryParams,
|
query: queryParams,
|
||||||
loading: true,
|
loading: true,
|
||||||
})) as PageDataResponse | null
|
})) as PageDataResponse | null
|
||||||
console.log('🚀 ~ response?.code:', response?.code)
|
console.log('🚀 ~ response?.code:', response?.code)
|
||||||
|
|
||||||
|
// 페이지 접근 권한 설정(로그인 유무)
|
||||||
|
if(response?.value?.is_login_required === 1 && !accessToken) {
|
||||||
|
// 로그인 레이어 팝업 띄워주기
|
||||||
|
const nuxtApp = useNuxtApp()
|
||||||
|
const modalStore = useModalStore()
|
||||||
|
const $i18n = nuxtApp.$i18n as any
|
||||||
|
const {tm} = $i18n
|
||||||
|
modalStore.handleOpenConfirm({
|
||||||
|
contentText: tm('Alert_StoveLogin'),
|
||||||
|
confirmButtonText: tm('Text_StoveLogin'),
|
||||||
|
confirmButtonEvent: () => {
|
||||||
|
csrGoStoveLogin()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if(response?.code === 91003) {
|
if(response?.code === 91003) {
|
||||||
return navigateTo(`/${langCode}/error`, { external: false })
|
// return navigateTo(`/${langCode}/error`, { external: false })
|
||||||
|
showError(createError({
|
||||||
|
statusCode: 404,
|
||||||
|
statusMessage: '페이지를 찾을 수 없어요.',
|
||||||
|
fatal: false, // 즉시 에러 페이지로
|
||||||
|
data: { reason: 'post-not-found' }
|
||||||
|
}))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response?.code === 0 && 'value' in response) {
|
if (response?.code === 0 && 'value' in response) {
|
||||||
@@ -74,5 +110,12 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
store.clearPageData()
|
store.clearPageData()
|
||||||
|
|
||||||
|
showError(createError({
|
||||||
|
statusCode: error.statusCode,
|
||||||
|
statusMessage: error.message,
|
||||||
|
fatal: false, // 즉시 에러 페이지로
|
||||||
|
data: { reason: 'post-not-found' }
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
export default defineNuxtPlugin((nuxtApp) => {
|
|
||||||
nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
|
|
||||||
console.log("🚀 000000 ~ error:", error)
|
|
||||||
// handle error, e.g. report to a service
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also possible
|
|
||||||
nuxtApp.hook('vue:error', (error, instance, info) => {
|
|
||||||
console.log("🚀1111 ~ error:", error)
|
|
||||||
// handle error, e.g. report to a service
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -96,6 +96,7 @@ function fnLocaleMiddleware(event: any, finalLocale: string) {
|
|||||||
// 쿼리스트링 포함 시 순수 경로만 추출
|
// 쿼리스트링 포함 시 순수 경로만 추출
|
||||||
arrPath = path.split('?')[0].split('/')
|
arrPath = path.split('?')[0].split('/')
|
||||||
queryString = path.split('?')[1]
|
queryString = path.split('?')[1]
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
arrPath = path.split('/')
|
arrPath = path.split('/')
|
||||||
queryString = ''
|
queryString = ''
|
||||||
@@ -238,6 +239,33 @@ export default defineEventHandler(async event => {
|
|||||||
|
|
||||||
// 2. 언어 코드 추출
|
// 2. 언어 코드 추출
|
||||||
finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers, initLangCodes, initDefaultLocale)
|
finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers, initLangCodes, initDefaultLocale)
|
||||||
|
|
||||||
|
const path = event?.node.req.url || ''
|
||||||
|
let queryStringF = ''
|
||||||
|
let fValue = ''
|
||||||
|
let test500 = false
|
||||||
|
if (path.includes('?')) {
|
||||||
|
queryStringF = path.split('?')[1]
|
||||||
|
// 쿼리스트링에서 f 파라미터 값 추출
|
||||||
|
try {
|
||||||
|
const urlParams = new URLSearchParams(queryStringF)
|
||||||
|
fValue = urlParams.get('f') || ''
|
||||||
|
// 테스트용 500 에러 발생 (예: ?test500=true)
|
||||||
|
test500 = urlParams.get('test500') === 'true'
|
||||||
|
} catch (e) {
|
||||||
|
console.error('쿼리스트링 파싱 에러:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 테스트용 500 에러 발생
|
||||||
|
if (test500) {
|
||||||
|
throw new Error('테스트용 500 에러 발생')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 미리보기 API 호출 처리
|
||||||
|
if (fValue === 'preview') {
|
||||||
|
cleanHost = 'samplegame.onstove.com'
|
||||||
|
}
|
||||||
|
|
||||||
const queryParams: Record<string, string> = {
|
const queryParams: Record<string, string> = {
|
||||||
game_domain: cleanHost || '',
|
game_domain: cleanHost || '',
|
||||||
@@ -385,5 +413,35 @@ export default defineEventHandler(async event => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('gameData load error:', error)
|
console.error('gameData load error:', error)
|
||||||
|
|
||||||
|
// 500 에러 발생 시 /error 페이지로 리다이렉트
|
||||||
|
if (!event.node.res.headersSent && !event.node.res.writableEnded) {
|
||||||
|
// 언어 코드 추출 시도
|
||||||
|
let finalLocale = 'ko' // 기본값
|
||||||
|
try {
|
||||||
|
finalLocale = ssrGetFinalLocale(
|
||||||
|
event?.node.req.url,
|
||||||
|
event.node.req.headers,
|
||||||
|
initLangCodes,
|
||||||
|
initDefaultLocale
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Locale extraction error:', e)
|
||||||
|
}
|
||||||
|
// finalLocale이 undefined인 경우 기본값으로 'ko' 설정
|
||||||
|
|
||||||
|
console.log("🚀 ~ 여기도 타? error:", error)
|
||||||
|
throw createError({
|
||||||
|
statusCode: error.statusCode,
|
||||||
|
statusMessage: error.statusMessage,
|
||||||
|
})
|
||||||
|
// if (!finalLocale) {
|
||||||
|
// finalLocale = 'ko'
|
||||||
|
// }
|
||||||
|
// const errorPath = `/${finalLocale}/error?message=${error.message}`
|
||||||
|
// event.node.res.statusCode = 302
|
||||||
|
// event.node.res.setHeader('Location', errorPath)
|
||||||
|
// event.node.res.end()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ const checkLoginValidation = async () => {
|
|||||||
const validateTokenResult = await handleTokenValidation(
|
const validateTokenResult = await handleTokenValidation(
|
||||||
accessToken.value || ''
|
accessToken.value || ''
|
||||||
)
|
)
|
||||||
|
console.log("🚀 ~ checkLoginValidation ~ validateTokenResult:", validateTokenResult)
|
||||||
isLogin.value = validateTokenResult
|
isLogin.value = validateTokenResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export interface PageDataValue {
|
|||||||
page_name: string
|
page_name: string
|
||||||
page_name_en: string
|
page_name_en: string
|
||||||
page_ver: string
|
page_ver: string
|
||||||
|
is_login_required: number
|
||||||
meta_tag_type: number
|
meta_tag_type: number
|
||||||
fit_page_height: boolean
|
fit_page_height: boolean
|
||||||
use_top_btn: boolean
|
use_top_btn: boolean
|
||||||
|
|||||||
Reference in New Issue
Block a user