fix. config -> runtimeConfig 명칭 통일

This commit is contained in:
clkim
2025-11-07 12:55:00 +09:00
parent 2e55cda20e
commit aafc3ae9b9
19 changed files with 590 additions and 384 deletions

View File

@@ -1,28 +1,39 @@
<template>
<header class="header">
<BlocksStoveGnbNew class="min-h-[48px]" />
</header>
<section class="inspection-section">
<clientOnly>
<!-- 로고 -->
<div class="inspection-logo">
<img :src="logoImgUrl" alt="logo" class="w-full h-full object-contain" />
<img
:src="logoImgUrl"
alt="logo"
class="w-full h-full object-contain"
/>
</div>
<div class="inspection-content">
<!-- 점검 메시지 -->
<h1 class="inspection-title text-lg md:text-2xl">
{{ tm('Inspection_Now_Maintenance') }}
</h1>
<div class="inspection-cards">
<!-- 점검 시간 카드 -->
<div v-if="webInspectionData" class="inspection-card inspection-time-card">
<h2 class="card-title text-base text-md md:text-lg">{{ tm('Inspection_Maintenance_Time') }}</h2>
<div
v-if="webInspectionData"
class="inspection-card inspection-time-card"
>
<h2 class="card-title text-base text-md md:text-lg">
{{ tm('Inspection_Maintenance_Time') }}
</h2>
<div class="inspection-time text-sm md:text-base font-medium">
<div v-dompurify-html="getLocaleTimezone('', '')" class="time-row"></div>
<div
v-dompurify-html="getLocaleTimezone('', '')"
class="time-row"
></div>
</div>
</div>
@@ -30,12 +41,21 @@
<div class="inspection-bottom-cards">
<!-- 온스토브 카드 -->
<div class="inspection-card inspection-stove-card">
<h3 :class="{ 'text-center': !launchingStatus }" class="card-title text-base md:text-lg">
<span v-if="!launchingStatus" v-dompurify-html="inspectionGameDuringMaintenance"></span>
<span v-else v-dompurify-html="orgInspectionDuringMaintenance"></span>
<h3
:class="{ 'text-center': !launchingStatus }"
class="card-title text-base md:text-lg"
>
<span
v-if="!launchingStatus"
v-dompurify-html="inspectionGameDuringMaintenance"
></span>
<span
v-else
v-dompurify-html="orgInspectionDuringMaintenance"
></span>
</h3>
<div class="button-group justify-center">
<!-- <a
<!-- <a
:href="communityUrl"
target="_blank"
rel="noopener noreferrer"
@@ -46,49 +66,63 @@
<AtomsIconsLongArrowRightLine :size="16" color="#1F1F1F" />
</a> -->
<AtomsButtonVariant
type="custom"
class="inspection-btn inspection-btn-community color-black text-sm md:text-base"
:class="{ 'inspection-btn-outline flex-1' : launchingStatus }"
@click="handleCommunityClick"
<AtomsButtonVariant
type="custom"
class="inspection-btn inspection-btn-community color-black text-sm md:text-base"
:class="{ 'inspection-btn-outline flex-1': launchingStatus }"
@click="handleCommunityClick"
>
<span>{{ tm('Inspection_Community_Btn') }}</span>
<AtomsIconsLongArrowRightLine :size="16" color="#1F1F1F" />
</AtomsButtonVariant>
<AtomsButtonVariant
v-if="launchingStatus"
type="custom"
platform="pc"
class="inspection-btn inspection-btn-primary w-auto color-black text-sm md:text-base flex-1"
@click="handleGameStart"
>
<span>게임 시작</span>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<span>{{ tm('Inspection_Community_Btn') }}</span>
<AtomsIconsLongArrowRightLine :size="16" color="#1F1F1F" />
</AtomsButtonVariant>
<AtomsButtonVariant
v-if="launchingStatus"
type="custom"
platform="pc"
class="inspection-btn inspection-btn-primary w-auto color-black text-sm md:text-base flex-1"
@click="handleGameStart"
>
<span>게임 시작</span>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.3098 1.49172C8.86574 1.28049 7.15098 1.28055 5.70176 1.49167C3.50821 1.81315 1.81986 3.50786 1.49213 5.69168L1.4918 5.69391C1.38564 6.41904 1.33331 7.19181 1.33331 7.99998C1.33331 8.80815 1.3857 9.58136 1.49187 10.3065C1.81362 12.4934 3.50335 14.1805 5.69381 14.5079L5.69577 14.5082C6.42109 14.6143 7.19417 14.6666 7.99717 14.6666C8.80032 14.6666 9.57311 14.6143 10.3035 14.5083L10.3046 14.5082C12.4928 14.1865 14.1802 12.4917 14.5078 10.3083L14.5082 10.3061C14.6143 9.58092 14.6666 8.80815 14.6666 7.99998C14.6666 7.19527 14.6203 6.42148 14.5137 5.69347C14.1921 3.50726 12.4966 1.81316 10.3098 1.49172ZM6.38756 8.95267C6.39301 9.15365 6.40004 9.35195 6.40866 9.54742L6.40968 9.57054C6.41945 9.78867 6.43118 10.0033 6.44489 10.2141C6.45959 10.4433 6.68743 10.5836 6.88293 10.4823C7.22837 10.3029 7.58116 10.1096 7.9413 9.90379C8.20002 9.7541 8.46167 9.6013 8.72479 9.43914C8.98791 9.27854 9.24516 9.11482 9.49505 8.95111C9.84196 8.72502 10.1771 8.49581 10.4961 8.26817C10.6769 8.14031 10.6769 7.85965 10.4961 7.7318C10.1771 7.50415 9.84343 7.2765 9.49505 7.04886C9.24516 6.88514 8.98791 6.72298 8.72479 6.56082C8.46167 6.39866 8.20002 6.2443 7.9413 6.09618C7.58263 5.89036 7.22837 5.69702 6.88293 5.5177C6.68743 5.41636 6.45959 5.55669 6.44489 5.78589C6.43118 5.99671 6.41945 6.21129 6.40968 6.42943L6.40866 6.45254C6.40004 6.64801 6.39301 6.84631 6.38756 7.0473C6.37874 7.3607 6.37433 7.67722 6.37433 7.99998C6.37433 8.32274 6.37874 8.64082 6.38756 8.95267Z" fill="#332C2A"/>
</svg>
</AtomsButtonVariant>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M10.3098 1.49172C8.86574 1.28049 7.15098 1.28055 5.70176 1.49167C3.50821 1.81315 1.81986 3.50786 1.49213 5.69168L1.4918 5.69391C1.38564 6.41904 1.33331 7.19181 1.33331 7.99998C1.33331 8.80815 1.3857 9.58136 1.49187 10.3065C1.81362 12.4934 3.50335 14.1805 5.69381 14.5079L5.69577 14.5082C6.42109 14.6143 7.19417 14.6666 7.99717 14.6666C8.80032 14.6666 9.57311 14.6143 10.3035 14.5083L10.3046 14.5082C12.4928 14.1865 14.1802 12.4917 14.5078 10.3083L14.5082 10.3061C14.6143 9.58092 14.6666 8.80815 14.6666 7.99998C14.6666 7.19527 14.6203 6.42148 14.5137 5.69347C14.1921 3.50726 12.4966 1.81316 10.3098 1.49172ZM6.38756 8.95267C6.39301 9.15365 6.40004 9.35195 6.40866 9.54742L6.40968 9.57054C6.41945 9.78867 6.43118 10.0033 6.44489 10.2141C6.45959 10.4433 6.68743 10.5836 6.88293 10.4823C7.22837 10.3029 7.58116 10.1096 7.9413 9.90379C8.20002 9.7541 8.46167 9.6013 8.72479 9.43914C8.98791 9.27854 9.24516 9.11482 9.49505 8.95111C9.84196 8.72502 10.1771 8.49581 10.4961 8.26817C10.6769 8.14031 10.6769 7.85965 10.4961 7.7318C10.1771 7.50415 9.84343 7.2765 9.49505 7.04886C9.24516 6.88514 8.98791 6.72298 8.72479 6.56082C8.46167 6.39866 8.20002 6.2443 7.9413 6.09618C7.58263 5.89036 7.22837 5.69702 6.88293 5.5177C6.68743 5.41636 6.45959 5.55669 6.44489 5.78589C6.43118 5.99671 6.41945 6.21129 6.40968 6.42943L6.40866 6.45254C6.40004 6.64801 6.39301 6.84631 6.38756 7.0473C6.37874 7.3607 6.37433 7.67722 6.37433 7.99998C6.37433 8.32274 6.37874 8.64082 6.38756 8.95267Z"
fill="#332C2A"
/>
</svg>
</AtomsButtonVariant>
</div>
</div>
<!-- 다운로드 카드 -->
<div v-if="launchingStatus" class="inspection-card inspection-download-card">
<div
v-if="launchingStatus"
class="inspection-card inspection-download-card"
>
<h3 class="card-title text-base md:text-lg">
{{ tm('Inspection_Txt_Download') || '게임 다운로드' }}
</h3>
<div class="flex flex-row gap-3">
<AtomsButtonLauncher
v-for="(btn, index) in enabledMarkets" :key="index"
:class="getButtonClass(btn.platform)"
<AtomsButtonLauncher
v-for="(btn, index) in enabledMarkets"
:key="index"
:class="getButtonClass(btn.platform)"
class="h-[48px]"
:platform="btn.platform as any"
:url="btn.url"
:type="btn.platform === 'pc' ? 'default' : 'single'"
:platform="btn.platform as any"
:url="btn.url"
:type="btn.platform === 'pc' ? 'default' : 'single'"
variant="outlined"
>
{{ getButtonText(btn.platform) }}
{{ getButtonText(btn.platform) }}
</AtomsButtonLauncher>
</div>
</div>
@@ -97,18 +131,17 @@
</div>
</clientOnly>
</section>
</template>
<script setup lang="ts">
import { globalDateFormat } from '@seed-next/date';
import { globalDateFormat } from '@seed-next/date'
import { useCheckGameStart } from '#layers/composables/useGameStart'
const config = useRuntimeConfig()
const rootPath = config.public.staticUrl
const runType = config.public.runType
const runtimeConfig = useRuntimeConfig()
const rootPath = runtimeConfig.public.staticUrl
const runType = runtimeConfig.public.runType
const translationApi = `${rootPath}/${runType}/test`
// const isClient = import.meta.client
const inspectionStore = useInspectionStore()
@@ -116,14 +149,13 @@ const { webInspectionData } = storeToRefs(inspectionStore)
const gameDataStore = useGameDataStore()
const { gameData } = storeToRefs(gameDataStore)
const resultGetMultilingual = await useGetMultilingual({
baseApiUrl: translationApi,
fileName: 'test_common_inspection.json'
fileName: 'test_common_inspection.json',
})
const i18n = useI18n({
useScope: 'local',
messages: Object(resultGetMultilingual.value.multilingual)
const i18n = useI18n({
useScope: 'local',
messages: Object(resultGetMultilingual.value.multilingual),
})
const { locale } = i18n
@@ -133,8 +165,12 @@ const tm = ((key: string): string => {
}) as (key: string) => string
// 타입 오류 해결을 위한 computed 속성
const inspectionGameDuringMaintenance = computed(() => tm('Inspection_Game_During_Maintenance'))
const orgInspectionDuringMaintenance = computed(() => tm('org_Inspection_During_Maintenance'))
const inspectionGameDuringMaintenance = computed(() =>
tm('Inspection_Game_During_Maintenance')
)
const orgInspectionDuringMaintenance = computed(() =>
tm('org_Inspection_During_Maintenance')
)
// locale에 따라 뒤에 KST 또는 UTC 추가 ko, en, zh-tw, ja
// ko: (KST)
@@ -149,25 +185,25 @@ const getLocaleTimezone = (localeType: string, region) => {
switch (currentLocale) {
case 'ko':
return `
${globalDateFormat(new Date(tsStartDate), currentLocale, region || '', {useFullDate: true})} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), currentLocale, region || 'KR', {useFullDate: true})} (KST)<br>
${globalDateFormat(new Date(tsStartDate), 'en', region || '', {useFullDate: true})} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), 'en', region || '', {useFullDate: true})} (UTC)
${globalDateFormat(new Date(tsStartDate), currentLocale, region || '', { useFullDate: true })} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), currentLocale, region || 'KR', { useFullDate: true })} (KST)<br>
${globalDateFormat(new Date(tsStartDate), 'en', region || '', { useFullDate: true })} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), 'en', region || '', { useFullDate: true })} (UTC)
`
case 'en':
return `${globalDateFormat(new Date(tsStartDate), currentLocale, region || '', {useFullDate: true})} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), currentLocale, region || '', {useFullDate: true})} (UTC)`
return `${globalDateFormat(new Date(tsStartDate), currentLocale, region || '', { useFullDate: true })} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), currentLocale, region || '', { useFullDate: true })} (UTC)`
case 'zh-tw':
return `
${globalDateFormat(new Date(tsStartDate), currentLocale, region || '', {useFullDate: true})} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), currentLocale, region || '', {useFullDate: true})} (台灣時間)<br>
${globalDateFormat(new Date(tsStartDate), 'en', region || '', {useFullDate: true})} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), 'en', region || '', {useFullDate: true})} (UTC)
${globalDateFormat(new Date(tsStartDate), currentLocale, region || '', { useFullDate: true })} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), currentLocale, region || '', { useFullDate: true })} (台灣時間)<br>
${globalDateFormat(new Date(tsStartDate), 'en', region || '', { useFullDate: true })} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), 'en', region || '', { useFullDate: true })} (UTC)
`
case 'ja':
return `
${globalDateFormat(new Date(tsStartDate), currentLocale, region || '', {useFullDate: true})} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), currentLocale, region || '', {useFullDate: true})} (JST)<br>
${globalDateFormat(new Date(tsStartDate), 'en', region || '', {useFullDate: true})} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), 'en', region || '', {useFullDate: true})} (UTC)
${globalDateFormat(new Date(tsStartDate), currentLocale, region || '', { useFullDate: true })} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), currentLocale, region || '', { useFullDate: true })} (JST)<br>
${globalDateFormat(new Date(tsStartDate), 'en', region || '', { useFullDate: true })} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), 'en', region || '', { useFullDate: true })} (UTC)
`
default:
return `
${globalDateFormat(new Date(tsStartDate), currentLocale, region || '', {useFullDate: true})} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), currentLocale, region || '', {useFullDate: true})} (KST)<br>
${globalDateFormat(new Date(tsStartDate), 'en', region || '', {useFullDate: true})} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), 'en', region || '', {useFullDate: true})} (UTC)
${globalDateFormat(new Date(tsStartDate), currentLocale, region || '', { useFullDate: true })} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), currentLocale, region || '', { useFullDate: true })} (KST)<br>
${globalDateFormat(new Date(tsStartDate), 'en', region || '', { useFullDate: true })} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), 'en', region || '', { useFullDate: true })} (UTC)
`
}
}
@@ -176,7 +212,6 @@ const launchingStatus = computed(() => {
return webInspectionData.value?.launching_status
})
// const market_json = {
// pc: { url: 'https://apps.apple.com/app/id1234567890', use_yn: 0 },
// app_store: { url: 'https://apps.apple.com/app/id1234567890', use_yn: 0 },
@@ -185,21 +220,25 @@ const launchingStatus = computed(() => {
//gameData.value.market_json 값 중 use_yn === 1 인 항목만 배열로 변환
const enabledMarkets = computed(() => {
return Object.entries(gameData.value.market_json)
// return Object.entries(market_json)
.filter(([, info]: [string, any]) => info && info.use_yn === 1)
.map(([platform, info]: [string, any]) => ({ platform, url: info.url as string }))
return (
Object.entries(gameData.value.market_json)
// return Object.entries(market_json)
.filter(([, info]: [string, any]) => info && info.use_yn === 1)
.map(([platform, info]: [string, any]) => ({
platform,
url: info.url as string,
}))
)
})
const logoImgUrl = computed(() => {
const currentLocale = locale.value || 'ko'
const currentLocale = locale.value || 'ko'
const localeData = (webInspectionData.value as any)?.[currentLocale]
return getImageHost(localeData.img_json.bi_large)
})
const communityUrl = computed(() => {
const currentLocale = locale.value || 'ko'
const currentLocale = locale.value || 'ko'
const localeData = (webInspectionData.value as any)?.[currentLocale]
return localeData?.url_json?.community
})
@@ -211,7 +250,7 @@ const handleCommunityClick = () => {
const getButtonClass = (platform: string) => {
// pc가 있으면 pc만 flex-1, 나머지는 기본
const hasPc = enabledMarkets.value.some(btn => btn.platform === 'pc')
if(hasPc) {
if (hasPc) {
return platform === 'pc' ? `flex-1 btn-platform-pc` : ``
}
//pc가 없으면서 하나만 있으면 ''
@@ -234,7 +273,7 @@ const getButtonText = (platform: string) => {
if (platform === 'app_store') {
return tm('platform_app_store')
}
return ''
}
@@ -246,15 +285,14 @@ const handleGameStart = () => {
definePageMeta({
middleware: ['inspection'],
layout: 'only-stove',
showLoading: false
showLoading: false,
})
</script>
<style scoped>
.inspection-section {
@apply flex flex-col items-center gap-10 px-10 py-[120px] pb-[200px] min-h-[calc(100vh-48px)];
background-color: #F0F0F0;
background-color: #f0f0f0;
}
.inspection-logo {
@@ -283,7 +321,6 @@ definePageMeta({
.inspection-time-card {
@apply flex flex-col items-center gap-4;
}
.card-title {
@@ -328,7 +365,6 @@ definePageMeta({
@apply flex items-center justify-center gap-1 px-6 md:px-8 w-auto h-10 md:h-12 rounded-lg;
cursor: pointer;
transition: all 0.2s;
}
.inspection-btn span {

View File

@@ -1,95 +1,146 @@
<template>
<div class="select-language" :class="{ 'language-changing': isChanging }">
<button
:disabled="isChanging"
class="flex items-center gap-2 px-3 py-2 rounded-lg text-[#CCCCCC] transition-all duration-300 w-[180px] bg-[#292929] border border-[#595959]"
:class="{ 'opacity-50 cursor-not-allowed': isChanging }"
@click="toggleDropdown"
>
<!-- 지구본 아이콘 -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- 지구본 아이콘 -->
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_5964_1685)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.6666 8.00065C14.6666 11.6825 11.6818 14.6673 7.99992 14.6673C4.31802 14.6673 1.33325 11.6825 1.33325 8.00065C1.33325 4.31875 4.31802 1.33398 7.99992 1.33398C11.6818 1.33398 14.6666 4.31875 14.6666 8.00065ZM6.89756 13.2199C6.03596 11.8504 5.50924 10.2901 5.36895 8.66732H2.70785C2.99033 10.9326 4.69347 12.7567 6.89756 13.2199ZM2.70785 7.33398H5.36895C5.50924 5.71116 6.03596 4.15086 6.89756 2.78138C4.69347 3.24458 2.99033 5.06868 2.70785 7.33398ZM13.292 8.66732C13.0095 10.9326 11.3064 12.7567 9.10228 13.2199C9.96388 11.8504 10.4906 10.2901 10.6309 8.66732H13.292ZM13.292 7.33398C13.0095 5.06868 11.3064 3.24458 9.10228 2.78138C9.96388 4.15086 10.4906 5.71116 10.6309 7.33398H13.292ZM7.99992 12.468C7.28662 11.3201 6.84273 10.0202 6.70801 8.66732H9.29183C9.15711 10.0202 8.71322 11.3201 7.99992 12.468ZM6.70801 7.33398H9.29183C9.15711 5.98112 8.71322 4.68121 7.99992 3.5333C7.28662 4.68121 6.84273 5.98112 6.70801 7.33398Z" fill="#CCCCCC"/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14.6666 8.00065C14.6666 11.6825 11.6818 14.6673 7.99992 14.6673C4.31802 14.6673 1.33325 11.6825 1.33325 8.00065C1.33325 4.31875 4.31802 1.33398 7.99992 1.33398C11.6818 1.33398 14.6666 4.31875 14.6666 8.00065ZM6.89756 13.2199C6.03596 11.8504 5.50924 10.2901 5.36895 8.66732H2.70785C2.99033 10.9326 4.69347 12.7567 6.89756 13.2199ZM2.70785 7.33398H5.36895C5.50924 5.71116 6.03596 4.15086 6.89756 2.78138C4.69347 3.24458 2.99033 5.06868 2.70785 7.33398ZM13.292 8.66732C13.0095 10.9326 11.3064 12.7567 9.10228 13.2199C9.96388 11.8504 10.4906 10.2901 10.6309 8.66732H13.292ZM13.292 7.33398C13.0095 5.06868 11.3064 3.24458 9.10228 2.78138C9.96388 4.15086 10.4906 5.71116 10.6309 7.33398H13.292ZM7.99992 12.468C7.28662 11.3201 6.84273 10.0202 6.70801 8.66732H9.29183C9.15711 10.0202 8.71322 11.3201 7.99992 12.468ZM6.70801 7.33398H9.29183C9.15711 5.98112 8.71322 4.68121 7.99992 3.5333C7.28662 4.68121 6.84273 5.98112 6.70801 7.33398Z"
fill="#CCCCCC"
/>
</g>
<defs>
<clipPath id="clip0_5964_1685">
<rect width="16" height="16" fill="#CCCCCC"/>
</clipPath>
<clipPath id="clip0_5964_1685">
<rect width="16" height="16" fill="#CCCCCC" />
</clipPath>
</defs>
</svg>
<span class="flex-1 text-sm text-left transition-all duration-300">
{{ isChanging ? '언어 변경 중...' : getLanguageName(selectedLocale) }}
</span>
<!-- 로딩 스피너 -->
<svg v-if="isChanging" class="w-3 h-3 animate-spin" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" stroke="#CCCCCC" stroke-width="2" stroke-linecap="round" stroke-dasharray="31.416" stroke-dashoffset="31.416">
<animate attributeName="stroke-dasharray" dur="2s" values="0 31.416;15.708 15.708;0 31.416" repeatCount="indefinite"/>
<animate attributeName="stroke-dashoffset" dur="2s" values="0;-15.708;-31.416" repeatCount="indefinite"/>
<svg
v-if="isChanging"
class="w-3 h-3 animate-spin"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="12"
cy="12"
r="10"
stroke="#CCCCCC"
stroke-width="2"
stroke-linecap="round"
stroke-dasharray="31.416"
stroke-dashoffset="31.416"
>
<animate
attributeName="stroke-dasharray"
dur="2s"
values="0 31.416;15.708 15.708;0 31.416"
repeatCount="indefinite"
/>
<animate
attributeName="stroke-dashoffset"
dur="2s"
values="0;-15.708;-31.416"
repeatCount="indefinite"
/>
</circle>
</svg>
<!-- 드롭다운 화살표 -->
<svg
v-else
class="w-3 h-3 text-gray-300 transition-transform duration-200"
<svg
v-else
class="w-3 h-3 text-gray-300 transition-transform duration-200"
:class="{ 'rotate-180': isDropdownOpen }"
viewBox="0 0 12 12" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M6.69999 4.285L9.59499 7.125C9.91999 7.445 9.69499 8 9.23499 8H2.75999C2.29999 8 2.07499 7.445 2.39999 7.125L5.29499 4.285C5.68499 3.905 6.30499 3.905 6.69499 4.285H6.69999Z" fill="#EBEBEB"/>
</svg>
</button>
<div v-if="isDropdownOpen" class="dropdown-menu">
<div v-for="localeItem in availableLanguages" :key="localeItem.code" class="dropdown-menu-item">
<button
class="dropdown-menu-item-button"
:class="{ 'current': localeItem.code === selectedLocale }"
@click="selectLanguage(localeItem.code)"
viewBox="0 0 12 12"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<svg
width="15"
height="11"
viewBox="0 0 15 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="transition-opacity duration-200"
<path
d="M6.69999 4.285L9.59499 7.125C9.91999 7.445 9.69499 8 9.23499 8H2.75999C2.29999 8 2.07499 7.445 2.39999 7.125L5.29499 4.285C5.68499 3.905 6.30499 3.905 6.69499 4.285H6.69999Z"
fill="#EBEBEB"
/>
</svg>
</button>
<div v-if="isDropdownOpen" class="dropdown-menu">
<div
v-for="localeItem in availableLanguages"
:key="localeItem.code"
class="dropdown-menu-item"
>
<button
class="dropdown-menu-item-button"
:class="{ current: localeItem.code === selectedLocale }"
@click="selectLanguage(localeItem.code)"
>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.6339 0.366117C15.1221 0.854272 15.1221 1.64573 14.6339 2.13388L6.30057 10.4672C6.05437 10.7134 5.71664 10.8458 5.36872 10.8324C5.0208 10.8191 4.69421 10.6612 4.46762 10.3968L0.300952 5.53571C-0.148326 5.01155 -0.0876239 4.22243 0.436533 3.77315C0.960691 3.32387 1.74982 3.38458 2.19909 3.90873L5.48729 7.74496L12.8661 0.366117C13.3543 -0.122039 14.1458 -0.122039 14.6339 0.366117Z" fill="#FC4420"/>
</svg>
<span class="text-sm">{{ localeItem.name }}</span>
</button>
<svg
width="15"
height="11"
viewBox="0 0 15 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="transition-opacity duration-200"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14.6339 0.366117C15.1221 0.854272 15.1221 1.64573 14.6339 2.13388L6.30057 10.4672C6.05437 10.7134 5.71664 10.8458 5.36872 10.8324C5.0208 10.8191 4.69421 10.6612 4.46762 10.3968L0.300952 5.53571C-0.148326 5.01155 -0.0876239 4.22243 0.436533 3.77315C0.960691 3.32387 1.74982 3.38458 2.19909 3.90873L5.48729 7.74496L12.8661 0.366117C13.3543 -0.122039 14.1458 -0.122039 14.6339 0.366117Z"
fill="#FC4420"
/>
</svg>
<span class="text-sm">{{ localeItem.name }}</span>
</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const config = useRuntimeConfig()
const baseDomain = `${config.public.baseDomain}`
const runtimeConfig = useRuntimeConfig()
const baseDomain = `${runtimeConfig.public.baseDomain}`
const gameDataStore = useGameDataStore()
const { gameData } = storeToRefs(gameDataStore)
// 사용 가능한 언어 목록
const availableLanguages = computed(() => {
return gameData.value?.lang_codes?.map(localeCode => ({
code: localeCode,
name: getLanguageName(localeCode)
})) || [{ code: 'ko', name: '한국어' }]
return (
gameData.value?.lang_codes?.map(localeCode => ({
code: localeCode,
name: getLanguageName(localeCode),
})) || [{ code: 'ko', name: '한국어' }]
)
})
// 언어 코드를 한국어 이름으로 변환하는 함수
const getLanguageName = (localeCode: string) => {
const languageNames: Record<string, string> = {
'ko': '한국어',
'en': 'English',
'ja': '日本語',
ko: '한국어',
en: 'English',
ja: '日本語',
'zh-cn': '简体中文',
'zh-tw': '繁體中文',
'es': 'Español',
'fr': 'Français',
'de': 'Deutsch',
'pt': 'Português',
'th': 'ไทย',
'it': 'Italiano'
es: 'Español',
fr: 'Français',
de: 'Deutsch',
pt: 'Português',
th: 'ไทย',
it: 'Italiano',
}
return languageNames[localeCode] || localeCode
}
@@ -113,7 +164,7 @@ const selectLanguage = async (localeCode: string) => {
isDropdownOpen.value = false
return
}
selectedLocale.value = localeCode as any
isDropdownOpen.value = false
await switchLanguage()
@@ -121,38 +172,37 @@ const selectLanguage = async (localeCode: string) => {
// 언어 변경 함수 (서버 미드웨어를 통한 gameData 갱신)
const switchLanguage = async () => {
if (!selectedLocale.value || isChanging.value) return
isChanging.value = true
try {
// URL 경로를 통해 언어 변경
const path = switchLocalePath(selectedLocale.value as any)
if (path) {
// 언어 쿠키 설정 (클라이언트 사이드) - 페이지 이동 전에 설정
const localeCookie = useCookie('LOCALE', {
domain: baseDomain,
path: '/',
maxAge: 60 * 60 * 24 * 365, // 1년 (초 단위)
sameSite: 'lax'
})
localeCookie.value = selectedLocale.value.toUpperCase()
// 페이지 데이터 초기화 (새로운 언어로 다시 로드되도록)
pageDataStore.clearPageData()
window.location.href = path
// URL 경로를 통해 언어 변경
const path = switchLocalePath(selectedLocale.value as any)
if (path) {
// 언어 쿠키 설정 (클라이언트 사이드) - 페이지 이동 전에 설정
const localeCookie = useCookie('LOCALE', {
domain: baseDomain,
path: '/',
maxAge: 60 * 60 * 24 * 365, // 1년 (초 단위)
sameSite: 'lax',
})
localeCookie.value = selectedLocale.value.toUpperCase()
// 페이지 데이터 초기화 (새로운 언어로 다시 로드되도록)
pageDataStore.clearPageData()
window.location.href = path
// 언어 변경 및 라우팅
// await setLocale(selectedLocale.value as any)
// 전체 페이지에 페이드 아웃 효과 적용
// document.body.style.transition = 'opacity 0.1s ease-out'
// document.body.style.opacity = '0'
// // 페이드 아웃 완료 후 페이지 이동
// await new Promise(resolve => setTimeout(resolve, 100))
// 서버 미드웨어를 통해 gameData 갱신을 위해 페이지 새로고침
// 이렇게 하면 서버 미드웨어가 새로운 언어로 gameData를 다시 가져옴
}
} catch {
// 오류 발생 시 이전 언어로 복원
@@ -248,5 +298,4 @@ body {
.dropdown-menu-item-button.current svg {
opacity: 1;
}
</style>
</style>

View File

@@ -66,8 +66,8 @@ const useGds = () => {
},
} as ResGetGdsClientPolicyTotal
} else {
const config = useRuntimeConfig()
const stoveApiBaseUrl = config.public.stoveApiUrl
const runtimeConfig = useRuntimeConfig()
const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
const url = `${stoveApiBaseUrl}/gds/v2/client/policy/total`
const query = {

View File

@@ -13,9 +13,8 @@ export const useGetGameDataExternal = () => {
const getGameDataExternal = async (req: GameDataRequest) => {
console.log('🚀 ~ getGameDataExternal ~ req:', req)
// const config = useRuntimeConfig()
const config = useRuntimeConfig()
const stoveApiBaseUrl = config.public.stoveApiUrl
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 {
@@ -23,7 +22,7 @@ export const useGetGameDataExternal = () => {
console.log('🚀 ~ getGameDataExternal ~ response:', response)
// FIXME: 테스트용 데이터 ---------------------------------------------------
/* if (['local', 'local-gate8', 'dev'].includes(`${config.public.runType}`)) {
/* if (['local', 'local-gate8', 'dev'].includes(`${runtimeConfig.public.runType}`)) {
response.value = {
inspection_status: 1,
inspection: {

View File

@@ -1,4 +1,7 @@
import type { ReqGameMaintenance, ResGameMaintenance } from '#layers/types/GameMaintenanceType'
import type {
ReqGameMaintenance,
ResGameMaintenance,
} from '#layers/types/GameMaintenanceType'
/**
* 게임 점검
@@ -7,7 +10,7 @@ const useGetGameMaintenance = () => {
const inspectionStore = useInspectionStore()
const logPrefix = {
exception: '[Exception] /composables/useGetGameMaintenance',
failure: '[Failure] /composables/useGetGameMaintenance'
failure: '[Failure] /composables/useGetGameMaintenance',
}
const isGameMaintenance = ref(false) // 게임 서버 점검 여부
@@ -20,7 +23,11 @@ const useGetGameMaintenance = () => {
const setGameMaintenanceFalse = () => {
setIsGameMaintenance(false)
inspectionStore.setGameMaintenanceStatus(false)
inspectionStore.setGameMaintenanceData({ ts_start_date: 0, ts_end_date: 0, detail_link: '' })
inspectionStore.setGameMaintenanceData({
ts_start_date: 0,
ts_end_date: 0,
detail_link: '',
})
}
/**
@@ -45,8 +52,8 @@ const useGetGameMaintenance = () => {
if (res != null && res.code === 0) {
// FIXME: 테스트용 데이터 ---------------------------------------------------
/* const config = useRuntimeConfig()
if (['local', 'local-gate8', 'dev'].includes(`${config.public.runType}`)) {
/* const runtimeConfig = useRuntimeConfig()
if (['local', 'local-gate8', 'dev'].includes(`${runtimeConfig.public.runType}`)) {
res.value = {
total_count: 1,
list: [
@@ -65,12 +72,16 @@ const useGetGameMaintenance = () => {
}
} */
// ------------------------------------------------------------------------
if (Number(res.value?.total_count) > 0 && res.value?.list != null && res.value?.list.length > 0) {
if (
Number(res.value?.total_count) > 0 &&
res.value?.list != null &&
res.value?.list.length > 0
) {
setIsGameMaintenance(true) // 서버 1개 이상 점검일 경우 점검 중으로 간주
inspectionStore.setGameMaintenanceData({
ts_start_date: res.value?.list[0].start_at || 0,
ts_end_date: res.value?.list[0].end_at || 0,
detail_link: res.value?.list[0].languages[0].link || ''
detail_link: res.value?.list[0].languages[0].link || '',
})
inspectionStore.setGameMaintenanceStatus(true)
} else {

View File

@@ -1,4 +1,8 @@
import type { WebInspectionData, ReqGetInspectionData, ResGetInspectionData } from '#layers/types/InspectionType'
import type {
WebInspectionData,
ReqGetInspectionData,
ResGetInspectionData,
} from '#layers/types/InspectionType'
/**
* 웹 점검
@@ -7,7 +11,7 @@ export const useGetInspectionDataExternal = () => {
const inspectionStore = useInspectionStore()
const logPrefix = {
exception: '[Exception] /composables/useGetInspectionDataExternal',
failure: '[Failure] /composables/useGetInspectionDataExternal'
failure: '[Failure] /composables/useGetInspectionDataExternal',
}
const webInspectionData = ref<WebInspectionData | null>(null)
const isWebInspection = ref(false) // 웹 점검 여부
@@ -24,15 +28,18 @@ export const useGetInspectionDataExternal = () => {
* @description https://wiki.smilegate.net/pages/viewpage.action?pageId=563198067
*/
const getInspectionDataExternal = async (req: ReqGetInspectionData) => {
// const config = useRuntimeConfig()
// const runtimeConfig = useRuntimeConfig()
const apiUrl = `${req.baseApiUrl}/pub-comm/v3.0/inspection/${req.gameId}`
try {
const response = (await commonFetch('GET', apiUrl)) as ResGetInspectionData
console.log("🚀 ~ getInspectionDataExternal ~ response:", response)
const response = (await commonFetch(
'GET',
apiUrl
)) as ResGetInspectionData
console.log('🚀 ~ getInspectionDataExternal ~ response:', response)
// FIXME: 테스트용 데이터 ---------------------------------------------------
/* if (['local', 'local-gate8', 'dev'].includes(`${config.public.runType}`)) {
/* if (['local', 'local-gate8', 'dev'].includes(`${runtimeConfig.public.runType}`)) {
response.value = {
inspection_status: 1,
inspection: {

View File

@@ -37,8 +37,8 @@ const usePreregist = () => {
const getPreregist = async (req: ReqPreorderSelectEvent) => {
let res: ResPreorderSelectEvent = {} as ResPreorderSelectEvent
try {
const config = useRuntimeConfig()
const stoveApiBaseUrl = config.public.stoveApiUrl
const runtimeConfig = useRuntimeConfig()
const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
const url = `${stoveApiBaseUrl}/pub-comm/v1.0/Preorder/SelectEvent`
const headers = {
@@ -99,8 +99,8 @@ const usePreregist = () => {
const setPreregist = async (req: ReqPreorderReserveDataUpdate) => {
let res: ResPreorderReserveDataUpdate = {} as ResPreorderReserveDataUpdate
try {
const config = useRuntimeConfig()
const stoveApiBaseUrl = config.public.stoveApiUrl
const runtimeConfig = useRuntimeConfig()
const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
const url = `${stoveApiBaseUrl}/pub-comm/v1.0/Preorder/ReserveDataUpdate`
const headers = {

View File

@@ -19,8 +19,8 @@ export const useResourcesData = () => {
): Promise<OperateComponents | null> => {
const { pageSeq, pageVer, pageVerTmplSeq, langCode, q, qc } = params
const config = useRuntimeConfig()
const stoveApiBaseUrl = config.public.stoveApiUrl
const runtimeConfig = useRuntimeConfig()
const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
const apiUrl = `${stoveApiBaseUrl}/pub-comm/v1.0/template/operateResources`
const queryParams: Record<string, string | number> = {
@@ -68,8 +68,8 @@ export const useResourcesData = () => {
size,
} = params
const config = useRuntimeConfig()
const stoveApiBaseUrl = config.public.stoveApiUrl
const runtimeConfig = useRuntimeConfig()
const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
const apiUrl = `${stoveApiBaseUrl}/cwms/v3.0/article_group/${articleGroupCode}/${articleGroupSeq}/article/list`
const queryParams: Record<string, string | number | boolean> = {

View File

@@ -14,14 +14,14 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
const currentLangCode = match ? match[1] : null
//현재 url에서 게임 도메인만 추출
const currentDomain = window.location.hostname;
const config = useRuntimeConfig()
const currentDomain = window.location.hostname
const runtimeConfig = useRuntimeConfig()
const req: GameDataRequest = {
gameDomain: `${currentDomain}`,
langCode: `${currentLangCode}`,
game_alias: '',
lang_code: `${currentLangCode}`,
baseApiUrl: `${config.public.stoveApiUrl}`,
baseApiUrl: `${runtimeConfig.public.stoveApiUrl}`,
gameId: '',
}
const { getGameDataExternal } = useGetGameDataExternal()
@@ -30,7 +30,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
const gameDataStore = useGameDataStore()
const gameData = gameDataStore.gameData as GameDataValue
const langCodes = gameData?.lang_codes
// 허용된 언어 코드 목록
const allowedLangCodes = langCodes || []

View File

@@ -1,15 +1,15 @@
export default defineNuxtRouteMiddleware(async (to) => {
export default defineNuxtRouteMiddleware(async to => {
try {
if (import.meta.client) {
const gameDataStore = useGameDataStore()
const { gameData } = storeToRefs(gameDataStore)
console.log("🚀 ~ 00000 gameData:", gameData.value)
console.log('🚀 ~ 00000 gameData:', gameData.value)
const config = useRuntimeConfig()
// const baseDomain = `${config.public.baseDomain}`
const stoveApiBaseUrl = config.public.stoveApiUrl
const runtimeConfig = useRuntimeConfig()
// const baseDomain = `${runtimeConfig.public.baseDomain}`
const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
const stoveGameId = gameData.value.game_id
// const stoveMaintenanceApiUrl = `${config.public.stoveMaintenanceApiUrl}`
// const stoveMaintenanceApiUrl = `${runtimeConfig.public.stoveMaintenanceApiUrl}`
// const localeCookie = useCookie('LOCALE', {
// domain: baseDomain
@@ -19,9 +19,12 @@ export default defineNuxtRouteMiddleware(async (to) => {
// localeCookie.value = finalLocale.toUpperCase()
// 웹 점검 -----
const { isWebInspection, getInspectionDataExternal } = useGetInspectionDataExternal()
await getInspectionDataExternal({ baseApiUrl: stoveApiBaseUrl, gameId: stoveGameId })
const { isWebInspection, getInspectionDataExternal } =
useGetInspectionDataExternal()
await getInspectionDataExternal({
baseApiUrl: stoveApiBaseUrl,
gameId: stoveGameId,
})
// 게임 점검 -----
// const { checkGameMaintenance } = useGetGameMaintenance()
@@ -32,10 +35,17 @@ export default defineNuxtRouteMiddleware(async (to) => {
// lang: `${finalLocale}`.toLowerCase()
// })
if (isWebInspection.value && !to.path.includes('inspection') && !to.path.includes('api')) {
if (
isWebInspection.value &&
!to.path.includes('inspection') &&
!to.path.includes('api')
) {
// 점검 중인 경우
return navigateTo(`/${finalLocale}/inspection`, { external: true })
} else if (!isWebInspection.value && to.path?.indexOf('inspection') !== -1) {
} else if (
!isWebInspection.value &&
to.path?.indexOf('inspection') !== -1
) {
// 점검이 종료된 후 점검 페이지 접근시 메인으로 리다이렉트
return navigateTo(`/${finalLocale}`, { external: true })
}

View File

@@ -9,8 +9,8 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
// [TODO] 하이드레이션 에러 처리
if (!import.meta.client) return
const config = useRuntimeConfig()
const stoveApiBaseUrl = config.public.stoveApiUrl
const runtimeConfig = useRuntimeConfig()
const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
const apiUrl = `${stoveApiBaseUrl}/pub-comm/v2.0/template/page`
const store = usePageDataStore()

View File

@@ -2,7 +2,7 @@
* i18n 다국어 로더 플러그인
* S3에서 공통 다국어 파일을 로드하여 i18n 메시지에 주입합니다.
*/
export default defineNuxtPlugin(async (nuxtApp) => {
export default defineNuxtPlugin(async nuxtApp => {
const $i18n = nuxtApp.$i18n as any
const runtimeConfig = useRuntimeConfig()
@@ -31,7 +31,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
'zh-cn': 'zh-CN',
}
langCodes.forEach((langCode) => {
langCodes.forEach(langCode => {
// 로케일 코드 변환 (필요한 경우)
const normalizedLangCode = localeMap[langCode] || langCode
@@ -47,6 +47,9 @@ export default defineNuxtPlugin(async (nuxtApp) => {
}
})
} catch (error) {
console.error('[Exception] i18n-loader: Failed to load translations:', error)
console.error(
'[Exception] i18n-loader: Failed to load translations:',
error
)
}
})
})

View File

@@ -1,9 +1,5 @@
import { LRUCache } from 'lru-cache'
import {
getHeader,
getRequestHost,
defineEventHandler,
} from 'h3'
import { getHeader, getRequestHost, defineEventHandler } from 'h3'
import { ssrGetFinalLocale } from '../../utils/localeUtil'
import type { GameDataResponse } from '../../types/api/gameData'
import type { ResGetInspectionData } from '../../types/InspectionType'
@@ -17,7 +13,9 @@ import { isStaticFile } from '#layers/utils/commonUtil'
* @param customMaxAge - 커스텀 max-age 값 (초 단위)
*/
function setCacheHeaders(
event: { node: { res: { setHeader: (name: string, value: string) => void } } },
event: {
node: { res: { setHeader: (name: string, value: string) => void } }
},
cacheMode: 'no-cache' | 'short' | 'medium' | 'default',
customMaxAge?: number
): void {
@@ -59,7 +57,7 @@ function setCacheHeaders(
const cache = new LRUCache({
max: 100, // 캐시에 저장할 최대 항목 수
ttl: 1000 * 30 // 30초 동안 캐시 유지
ttl: 1000 * 30, // 30초 동안 캐시 유지
})
/**
@@ -69,11 +67,15 @@ const cache = new LRUCache({
* @param finalLocale - 최종 언어
* @param baseDomain - 기본 도메인
*/
function setFinalLocaleCookie(event: any, finalLocale: string, baseDomain: string) {
function setFinalLocaleCookie(
event: any,
finalLocale: string,
baseDomain: string
) {
setCookie(event, 'LOCALE', finalLocale.toUpperCase(), {
domain: baseDomain,
path: '/',
maxAge: 60 * 60 * 24 * 365 // 1년 (초 단위)
maxAge: 60 * 60 * 24 * 365, // 1년 (초 단위)
})
}
@@ -121,32 +123,30 @@ function fnLocaleMiddleware(event: any, finalLocale: string) {
}
export default defineEventHandler(async event => {
// const runType = `${config.public.runType}`
// console.log("🚀 ~ baseDomain:", config.public.baseDomain)
// const runType = `${runtimeConfig.public.runType}`
// const url = getRequestURL(event)
// if (['local', 'local-gate8', 'dev'].includes(runType)) {
// Sandbox 이상 환경에서만 동작 및 확인 가능 (local, dev는 통과 처리)
// try {
// 언어 코드 추출
// const finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers)
// console.log("🚀 ~ finalLocale:", finalLocale)
// setFinalLocaleCookie(event, finalLocale, baseDomain)
// Sandbox 이상 환경에서만 동작 및 확인 가능 (local, dev는 통과 처리)
// try {
// 언어 코드 추출
// const finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers)
// console.log("🚀 ~ finalLocale:", finalLocale)
// setFinalLocaleCookie(event, finalLocale, baseDomain)
// -------------------------------------------------------------------------------
// [Locale Middleware]
// -------------------------------------------------------------------------------
// fnLocaleMiddleware(event, finalLocale)
// } catch (e) {
// console.error('[Exception] /server/middleware/middleware-02-global: ', e)
// }
// -------------------------------------------------------------------------------
// [Locale Middleware]
// -------------------------------------------------------------------------------
// fnLocaleMiddleware(event, finalLocale)
// } catch (e) {
// console.error('[Exception] /server/middleware/middleware-02-global: ', e)
// }
// }
const config = useRuntimeConfig()
const iBaseApiUrl = `${config.public.stoveApiUrlServer}`
const baseDomain = `${config.public.baseDomain}`
const stoveApiUrlBaseServer = config.public.stoveApiUrlServer
const runtimeConfig = useRuntimeConfig()
const iBaseApiUrl = `${runtimeConfig.public.stoveApiUrlServer}`
const baseDomain = `${runtimeConfig.public.baseDomain}`
const stoveApiUrlBaseServer = runtimeConfig.public.stoveApiUrlServer
const apiUrl = `${stoveApiUrlBaseServer}/pub-comm/v1.0/template/game`
const initGameData: GameDataResponse | null = null
@@ -165,7 +165,6 @@ export default defineEventHandler(async event => {
}
try {
const queryParams: Record<string, string> = {
game_domain: cleanHost || '',
lang_code: '',
@@ -177,13 +176,13 @@ export default defineEventHandler(async event => {
// console.log("🚀 ~ 00000 initGameData:", initGameData)
initLangCodes = initResponse?.value?.lang_codes || null
initDefaultLocale = initResponse?.value?.default_lang_code || null
console.log("🚀 ~ 000111 initLangCodes:", initLangCodes)
console.log('🚀 ~ 000111 initLangCodes:', initLangCodes)
} catch (error) {
console.error('init gameData load error:', error)
}
const fullPath = event.path
// 1-1. 정적 파일 패스
if (isStaticFile(event.path)) {
return
@@ -192,7 +191,12 @@ export default defineEventHandler(async event => {
// 1-2. /inspection 패스
if (fullPath.includes('/inspection')) {
// 리턴 되기 전 언어 쿠키 세팅
finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers, initLangCodes, initDefaultLocale)
finalLocale = ssrGetFinalLocale(
event?.node.req.url,
event.node.req.headers,
initLangCodes,
initDefaultLocale
)
setFinalLocaleCookie(event, finalLocale, baseDomain)
return
}
@@ -213,14 +217,17 @@ export default defineEventHandler(async event => {
const cacheKey = 'inspection'
// console.log("🚀 11111 ~ cacheKey:", cacheKey)
// gameData를 직접 가져와서 context에 저장 (API 호출 없이)
try {
// 2. 언어 코드 추출
finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers, initLangCodes, initDefaultLocale)
console.log("🚀 222 finalLocale:", finalLocale)
// 2. 언어 코드 추출
finalLocale = ssrGetFinalLocale(
event?.node.req.url,
event.node.req.headers,
initLangCodes,
initDefaultLocale
)
console.log('🚀 222 finalLocale:', finalLocale)
const queryParams: Record<string, string> = {
game_domain: cleanHost || '',
lang_code: finalLocale,
@@ -230,18 +237,16 @@ export default defineEventHandler(async event => {
})) as GameDataResponse | null
// 언어패스 쿠키 굽기 - 장기방안에서는 굽지않음
if(initLangCodes?.includes(finalLocale)) {
if (initLangCodes?.includes(finalLocale)) {
setFinalLocaleCookie(event, finalLocale, baseDomain)
}
if (response?.code === 0 && 'value' in response) {
event.context.gameData = response.value
event.context.googleAnalyticsId = response.value?.ga_code
// console.log('🚀 ~ gameData:', response.value)
// 점검 데이터 조회
let inspectionData
if (cache.has(cacheKey)) {
@@ -251,104 +256,109 @@ export default defineEventHandler(async event => {
if (response?.value?.game_id) {
const inspectionApiUrl = `${iBaseApiUrl}/pub-comm/v3.0/inspection/${response?.value?.game_id}`
// 직접 $fetch 사용 (composable 사용하지 않음)
const inspectionResponse = await $fetch<ResGetInspectionData>(inspectionApiUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
const inspectionResponse = await $fetch<ResGetInspectionData>(
inspectionApiUrl,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
})
)
inspectionData = inspectionResponse?.value?.inspection
cache.set(cacheKey, inspectionData) // 캐시에 저장
// console.log("🚀 ~ inspectionData:", inspectionData)
}
}
// 4. 현재 시간과 점검 기간 비교
const currentTime = Date.now()
const tsStartDate = inspectionData?.ts_start_date || 0
const tsEndDate = inspectionData?.ts_end_date || 0
const timeUntilInspectionSeconds = Math.floor(
(tsStartDate - currentTime) / 1000
)
// 4. 현재 시간과 점검 기간 비교
const currentTime = Date.now()
const tsStartDate = inspectionData?.ts_start_date || 0
const tsEndDate = inspectionData?.ts_end_date || 0
const timeUntilInspectionSeconds = Math.floor((tsStartDate - currentTime) / 1000)
// 5. 점검 상태별 캐시 설정
if (
inspectionData?.inspection_status === 1 &&
currentTime >= tsStartDate &&
currentTime <= tsEndDate
) {
/**
* 점검 중인 경우
* - 점검 상태가 1이고 현재 시간이 점검 시작과 종료 사이에 있는지 확인ㄹ
* - 점검 URL 경로가 아닐 경우 no-cache 설정
* - 화이트 리스트 체크
*/
// 현재 경로가 점검 페이지가 아닐 경우 리다이렉트
const inspectionPath = `/${finalLocale}/inspection`
if (fullPath !== inspectionPath) {
setCacheHeaders(event, 'no-cache')
}
// 5. 점검 상태별 캐시 설정
if (inspectionData?.inspection_status === 1 && currentTime >= tsStartDate && currentTime <= tsEndDate) {
/**
* 점검 중인 경우
* - 점검 상태가 1이고 현재 시간이 점검 시작과 종료 사이에 있는지 확인ㄹ
* - 점검 URL 경로가 아닐 경우 no-cache 설정
* - 화이트 리스트 체크
*/
// 현재 경로가 점검 페이지가 아닐 경우 리다이렉트
const inspectionPath = `/${finalLocale}/inspection`
if (fullPath !== inspectionPath) {
setCacheHeaders(event, 'no-cache')
}
// 점검 중일 때 IP 필터링 활성화 여부 확인
if (inspectionData?.ip_filter_use_yn === 'Y') {
const clientIP = getTrueClientIp(event.node.req as any)
// 점검 중일 때 IP 필터링 활성화 여부 확인
if (inspectionData?.ip_filter_use_yn === 'Y') {
const clientIP = getTrueClientIp(event.node.req as any)
// 허용된 IP 목록 확인
if (!inspectionData?.ip_filter_list?.includes(clientIP)) {
// 허용되지 않은 IP인 경우 점검 페이지로 이동
event.node.res.statusCode = 302
event.node.res.setHeader('Location', inspectionPath)
event.node.res.end()
} else {
// 화이트 리스트인 경우
// -------------------------------------------------------------------------------
// [Locale Middleware]
// -------------------------------------------------------------------------------
fnLocaleMiddleware(event, finalLocale)
}
} else {
// 허용된 IP 목록 확인
if (!inspectionData?.ip_filter_list?.includes(clientIP)) {
// 허용되지 않은 IP인 경우 점검 페이지로 이동
event.node.res.statusCode = 302
event.node.res.setHeader('Location', inspectionPath)
event.node.res.end()
} else {
// 화이트 리스트인 경우
// -------------------------------------------------------------------------------
// [Locale Middleware]
// -------------------------------------------------------------------------------
fnLocaleMiddleware(event, finalLocale)
}
} else {
/**
* 점검이 아닌 경우
* - 홈 경로는 no-cache
* - 점검 예정 시간에 따른 캐시 설정
* - 점검 5분 전: 짧은 캐시 (10초)
* - 점검 30분 전: 중간 캐시 (15초)
* - 점검 30분 이후: 기본 캐시 (60초)
*/
// 홈 경로: 캐시 없음
const isHomePath = [
'',
'/'
//, ...Object.values(DEFAULT_LOCALE_COVERAGES).flatMap((locale) => [`/${locale}`, `/${locale}/`])
].includes(fullPath)
event.node.res.statusCode = 302
event.node.res.setHeader('Location', inspectionPath)
event.node.res.end()
}
} else {
/**
* 점검이 아닌 경우
* - 홈 경로는 no-cache
* - 점검 예정 시간에 따른 캐시 설정
* - 점검 5분 전: 짧은 캐시 (10초)
* - 점검 30분 전: 중간 캐시 (15초)
* - 점검 30분 이후: 기본 캐시 (60초)
*/
// 홈 경로: 캐시 없음
const isHomePath = [
'',
'/',
//, ...Object.values(DEFAULT_LOCALE_COVERAGES).flatMap((locale) => [`/${locale}`, `/${locale}/`])
].includes(fullPath)
if (isHomePath) {
setCacheHeaders(event, 'no-cache')
} else {
// 점검 예정 시간에 따른 캐시 설정
if (tsStartDate > 0 && timeUntilInspectionSeconds > 0) {
if (timeUntilInspectionSeconds < 300) {
// 점검 5분 전: 짧은 캐시 (10초)
setCacheHeaders(event, 'short', 10)
} else if (timeUntilInspectionSeconds < 1800) {
// 점검 30분 전: 중간 캐시 (15초)
setCacheHeaders(event, 'medium', 15)
} else {
// 점검 30분 이후: 기본 캐시 (60초)
setCacheHeaders(event, 'default')
}
if (isHomePath) {
setCacheHeaders(event, 'no-cache')
} else {
// 점검 예정 시간에 따른 캐시 설정
if (tsStartDate > 0 && timeUntilInspectionSeconds > 0) {
if (timeUntilInspectionSeconds < 300) {
// 점검 5분 전: 짧은 캐시 (10초)
setCacheHeaders(event, 'short', 10)
} else if (timeUntilInspectionSeconds < 1800) {
// 점검 30분 전: 중간 캐시 (15초)
setCacheHeaders(event, 'medium', 15)
} else {
// 점검 30분 이후: 기본 캐시 (60초)
setCacheHeaders(event, 'default')
}
}
// -------------------------------------------------------------------------------
// [Locale Middleware]
// -------------------------------------------------------------------------------
fnLocaleMiddleware(event, finalLocale)
}
// -------------------------------------------------------------------------------
// [Locale Middleware]
// -------------------------------------------------------------------------------
fnLocaleMiddleware(event, finalLocale)
}
}
} catch (error) {
console.error('gameData load error:', error)
}

View File

@@ -8,10 +8,6 @@ export const useGameDataStore = defineStore('gameData', () => {
gameData.value = data
}
const setLangCode = (data: string) => {
langCode.value = data
}
const clearGameData = () => {
gameData.value = null
}
@@ -20,7 +16,6 @@ export const useGameDataStore = defineStore('gameData', () => {
langCode,
gameData,
setGameData,
setLangCode,
clearGameData,
}
})

View File

@@ -13,10 +13,10 @@ const props = defineProps<Props>()
const { handleTokenValidation } = useTokenValidation()
// Configuration
const config = useRuntimeConfig()
const dataResourcesUrl = config.public.dataResourcesUrl as string
const runtimeConfig = useRuntimeConfig()
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json'
const stoveApiBaseUrl = config.public.stoveApiUrl
// Multilingual
const resultGetMultilingual = await useGetMultilingual({
@@ -33,7 +33,7 @@ const isLogin = ref(false)
const secureSetting = ref({
otpLoginYn: 'N',
abroadLoginBlockYn: 'N',
pcRegisterYn: 'N'
pcRegisterYn: 'N',
})
// 회원 보안 설정 설정
@@ -52,26 +52,28 @@ const checkLoginValidation = async () => {
// 회원 보안 설정 조회
const fnGetSecuritySetting = async () => {
const accessToken = useCookie('SUAT')
checkLoginValidation()
const apiBase = `${stoveApiBaseUrl}/auth-secure/v1.0`
const headers = {
Authorization: `Bearer ${accessToken.value}`,
'Content-Type': 'application/json;charset=UTF-8'
'Content-Type': 'application/json;charset=UTF-8',
}
try {
const result = await commonFetch('GET', `${apiBase}/security/setting`, { headers })
const result = await commonFetch('GET', `${apiBase}/security/setting`, {
headers,
})
if (result?.code === 0 && Array.isArray(result.value)) {
const arrSecure = result.value
const getValue = (key: string) => arrSecure.find((f: any) => f.key === key)?.value ?? 'N'
const getValue = (key: string) =>
arrSecure.find((f: any) => f.key === key)?.value ?? 'N'
secureSetting.value = {
otpLoginYn: getValue('OTP_LOGIN_YN'),
abroadLoginBlockYn: getValue('ABROAD_LOGIN_BLOCK_YN'),
pcRegisterYn: getValue('PC_REGISTER_YN')
pcRegisterYn: getValue('PC_REGISTER_YN'),
}
}
} catch (e) {
@@ -89,7 +91,9 @@ const secureCards = computed(() => {
{
id: 'SECURE_CARD_0',
title: tm('Secure_Stove_otp') || '스토브 인증기 (OTP)',
description: tm('Secure_Stove_otp_desc') || '스토브 앱으로 인증 후 안전하게 로그인하세요.',
description:
tm('Secure_Stove_otp_desc') ||
'스토브 앱으로 인증 후 안전하게 로그인하세요.',
status: secureSetting.value.otpLoginYn,
benefitTitle: tm('Secure_Stove_otp_benefits') || '스토브 OTP 혜택',
benefitDesc: tm('Secure_Defense_bonus_10') || '방어력 +10',
@@ -100,7 +104,9 @@ const secureCards = computed(() => {
{
id: 'SECURE_CARD_1',
title: tm('Secure_Block_foreign_login') || '해외 로그인 차단',
description: tm('Secure_Block_foreign_login_desc') || '접속 국가를 제한하여 의심 로그인을 차단해요.',
description:
tm('Secure_Block_foreign_login_desc') ||
'접속 국가를 제한하여 의심 로그인을 차단해요.',
status: secureSetting.value.abroadLoginBlockYn,
benefitTitle: '',
benefitDesc: '',
@@ -111,7 +117,9 @@ const secureCards = computed(() => {
{
id: 'SECURE_CARD_2',
title: tm('Secure_Trusted_pc_management') || '지정 PC 관리',
description: tm('Secure_Trusted_pc_desc') || '지정 PC에서만 로그인할 수 있게 설정해 보세요.',
description:
tm('Secure_Trusted_pc_desc') ||
'지정 PC에서만 로그인할 수 있게 설정해 보세요.',
status: secureSetting.value.pcRegisterYn,
benefitTitle: '',
benefitDesc: '',
@@ -126,7 +134,6 @@ const cautionText = computed(() => {
return tm('Secure_Notice_Content') || []
})
onMounted(() => {
fnGetSecuritySetting()
})
@@ -143,12 +150,21 @@ onMounted(() => {
<section class="section-secure bg-[#F0F0F0] pb-50">
<div class="section-static content-standa md:max-w-[1300px] mx-auto">
<!-- Title Section -->
<div class="flex flex-col md:flex-row w-full md:items-end justify-between gap-5 mb-6">
<h3 class="text-[#1F1F1F] text-2xl font-bold leading-8 tracking-[-0.72px]">
<div
class="flex flex-col md:flex-row w-full md:items-end justify-between gap-5 mb-6"
>
<h3
class="text-[#1F1F1F] text-2xl font-bold leading-8 tracking-[-0.72px]"
>
{{ tm('Secure_Section_Title') || '보안 서비스' }}
</h3>
<p class="text-gray-500 text-[14px] font-[400] leading-[24px] tracking-[-0.42px] text-left md:text-right">
{{ tm('Secure_Section_Description') || '*OTP / 해외 로그인 차단 / 지정 PC 관리 설정하고, 로드나인 계정을 보다 안전하게 보호하세요.' }}
<p
class="text-gray-500 text-[14px] font-[400] leading-[24px] tracking-[-0.42px] text-left md:text-right"
>
{{
tm('Secure_Section_Description') ||
'*OTP / 해외 로그인 차단 / 지정 PC 관리 설정하고, 로드나인 계정을 보다 안전하게 보호하세요.'
}}
</p>
</div>
@@ -160,7 +176,9 @@ onMounted(() => {
class="flex-1 min-h-[308px] md:min-h-[384px] p-[10px] md:p-4 bg-[#FFFFFF] rounded-2xl flex flex-col gap-3 transition-all duration-300 ease-in-out"
>
<!-- Card Content -->
<div class="flex-1 p-[10px] md:p-8 flex flex-col gap-[8px] md:gap-3 text-left">
<div
class="flex-1 p-[10px] md:p-8 flex flex-col gap-[8px] md:gap-3 text-left"
>
<!-- Badge -->
<div class="inline-flex">
<span
@@ -171,17 +189,25 @@ onMounted(() => {
: 'bg-[#EBEBEB] text-[#999999]',
]"
>
{{ card.status === 'Y' ? tm('Secure_Enabled') : tm('Secure_Disabled') }}
{{
card.status === 'Y'
? tm('Secure_Enabled')
: tm('Secure_Disabled')
}}
</span>
</div>
<!-- Title -->
<h4 class="text-[#1F1F1F] text-[18px] md:text-[24px] font-bold leading-[26px] md:leading-[34px] tracking-[-0.54px] md:tracking-[-0.72px]">
<h4
class="text-[#1F1F1F] text-[18px] md:text-[24px] font-bold leading-[26px] md:leading-[34px] tracking-[-0.54px] md:tracking-[-0.72px]"
>
{{ card.title }}
</h4>
<!-- Description -->
<p class="flex-1 text-[#999999] text-[14px] md:text-base font-[400] leading-[22px] md:leading-[26px] tracking-[-0.42px] md:tracking-[-0.48px]">
<p
class="flex-1 text-[#999999] text-[14px] md:text-base font-[400] leading-[22px] md:leading-[26px] tracking-[-0.42px] md:tracking-[-0.48px]"
>
{{ card.description }}
</p>
</div>
@@ -203,7 +229,9 @@ onMounted(() => {
class="w-[48px] h-[48px] bg-[#3C75FF] rounded-[8px] flex items-center justify-center"
>
<img
:src="getImageHost(card.benefitIcon, { imageType: 'common' })"
:src="
getImageHost(card.benefitIcon, { imageType: 'common' })
"
:alt="card.benefitTitle"
class="w-[48px] h-[48px] object-contain rounded-2xl"
loading="lazy"
@@ -211,7 +239,9 @@ onMounted(() => {
/>
</div>
<div class="flex-1 flex flex-col text-left">
<div class="text-[#3C75FF] text-[14px] md:text-[18px] font-bold leading-[22px] md:leading-[26px] tracking-[-0.42px] md:tracking-[-0.54px]">
<div
class="text-[#3C75FF] text-[14px] md:text-[18px] font-bold leading-[22px] md:leading-[26px] tracking-[-0.42px] md:tracking-[-0.54px]"
>
{{ card.benefitTitle }}
</div>
<div
@@ -230,7 +260,11 @@ onMounted(() => {
button-size="size-small md:size-large"
background-color="#000000"
text-color="#FFFFFF"
@click="isLogin ? handleSecureSetting(card.url) : checkLoginValidation()"
@click="
isLogin
? handleSecureSetting(card.url)
: checkLoginValidation()
"
>
<span>{{ tm('Secure_Action_setup') }}</span>
</AtomsButton>
@@ -243,8 +277,19 @@ onMounted(() => {
disabled
>
<span>{{ tm('Secure_Action_complete') }}</span>
<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.4298 2.80644L6.84645 0.240655C7.52385 -0.0802185 8.30948 -0.0802184 8.98688 0.240655L14.4035 2.80645C15.2767 3.22003 15.8333 4.09952 15.8333 5.06564V7.65038C15.8333 13.399 10.6191 16.1288 8.65401 16.9535C8.18024 17.1523 7.6531 17.1523 7.17932 16.9535C5.21423 16.1288 -0.000131724 13.399 2.49573e-09 7.65038L1.11287e-05 5.06566C6.95637e-06 4.09953 0.556675 3.22002 1.4298 2.80644ZM11.4226 7.4063C11.748 7.08086 11.748 6.55323 11.4226 6.22779C11.0972 5.90235 10.5695 5.90235 10.2441 6.22779L7.5 8.97187L6.00592 7.47779C5.68049 7.15235 5.15285 7.15235 4.82741 7.47779C4.50197 7.80323 4.50197 8.33086 4.82741 8.6563L6.91074 10.7396C7.23618 11.0651 7.76382 11.0651 8.08926 10.7396L11.4226 7.4063Z" fill="#999999"/>
<svg
width="16"
height="18"
viewBox="0 0 16 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1.4298 2.80644L6.84645 0.240655C7.52385 -0.0802185 8.30948 -0.0802184 8.98688 0.240655L14.4035 2.80645C15.2767 3.22003 15.8333 4.09952 15.8333 5.06564V7.65038C15.8333 13.399 10.6191 16.1288 8.65401 16.9535C8.18024 17.1523 7.6531 17.1523 7.17932 16.9535C5.21423 16.1288 -0.000131724 13.399 2.49573e-09 7.65038L1.11287e-05 5.06566C6.95637e-06 4.09953 0.556675 3.22002 1.4298 2.80644ZM11.4226 7.4063C11.748 7.08086 11.748 6.55323 11.4226 6.22779C11.0972 5.90235 10.5695 5.90235 10.2441 6.22779L7.5 8.97187L6.00592 7.47779C5.68049 7.15235 5.15285 7.15235 4.82741 7.47779C4.50197 7.80323 4.50197 8.33086 4.82741 8.6563L6.91074 10.7396C7.23618 11.0651 7.76382 11.0651 8.08926 10.7396L11.4226 7.4063Z"
fill="#999999"
/>
</svg>
</AtomsButton>
</div>
@@ -252,8 +297,12 @@ onMounted(() => {
</div>
<!-- Caution Section -->
<div class="self-stretch p-8 bg-[#FAFAFA] rounded-2xl flex flex-col gap-3 text-left">
<h5 class="text-[#333333] text-[20px] font-bold leading-[30px] tracking-[-0.6px]">
<div
class="self-stretch p-8 bg-[#FAFAFA] rounded-2xl flex flex-col gap-3 text-left"
>
<h5
class="text-[#333333] text-[20px] font-bold leading-[30px] tracking-[-0.6px]"
>
{{ tm('Secure_Notice') }}
</h5>
<ul class="relative flex flex-col items-start justify-start w-full">
@@ -262,11 +311,10 @@ onMounted(() => {
:key="caution"
v-dompurify-html="caution"
class="relative pl-[22px] before:content-[''] before:absolute before:top-[10px] before:left-[9px] before:w-[3px] before:h-[3px] before:rounded-full before:bg-[#999999] text-[#999999] text-[14px] font-[400] leading-[24px] tracking-[-0.42px]"
>
</li>
></li>
</ul>
</div>
</div>
</section>
</div>
</template>
</template>

View File

@@ -44,7 +44,9 @@ const csrDownloadFile = (fileUrl: string, fileName?: string) => {
*/
const csrGetMarketingCode = () => {
const route = useRoute()
const mcode = Number(`${route.query.mcode != null && route.query.mcode !== '' ? route.query.mcode : ''}`)
const mcode = Number(
`${route.query.mcode != null && route.query.mcode !== '' ? route.query.mcode : ''}`
)
return isNaN(mcode) ? undefined : mcode
}
@@ -82,15 +84,20 @@ const isNumeric = (str: string): boolean => {
* @param {Function} tm - i18n의 tm 함수 (예: (key) => ({ txt: string }))
* @param {any} query - 추가 쿼리 파라미터
*/
const getParsedCustomLink = (link: string, { tm, query = {} }: ParsedCustomLinkOptions) => {
const config = useRuntimeConfig()
const getParsedCustomLink = (
link: string,
{ tm, query = {} }: ParsedCustomLinkOptions
) => {
const runtimeConfig = useRuntimeConfig()
let result = `${link || ''}`
// @c{key} 패턴 치환 (예: @c{stoveCommunityUrl})
if (link.includes('@c')) {
result = result.replace(/@c\{(.*?)\}/g, (_, key) => {
// config.public에서 해당 key 값을 찾아 치환
return typeof config.public[key] === 'string' ? config.public[key] : ''
// runtimeConfig.public에서 해당 key 값을 찾아 치환
return typeof runtimeConfig.public[key] === 'string'
? runtimeConfig.public[key]
: ''
})
}
@@ -132,7 +139,7 @@ const setCookieForDay = (name: string, value: string, exp?: number) => {
const setCookie = useCookie(name, {
expires: new Date(date),
path: '/'
path: '/',
})
setCookie.value = value
@@ -140,7 +147,9 @@ const setCookieForDay = (name: string, value: string, exp?: number) => {
// 정적 파일인지 확인하는 함수
const isStaticFile = (path: string): boolean => {
return /\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|scss)$/i.test(path)
return /\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|scss)$/i.test(
path
)
}
/**
@@ -164,5 +173,5 @@ export {
getParsedCustomLink,
setCookieForDay,
isStaticFile,
isInRange
isInRange,
}

View File

@@ -4,7 +4,7 @@ import { DEFAULT_LOCALE_CODE } from '../../i18n.config'
export const getPreferredLanguage = (acceptLanguageHeader = '') => {
const languages = acceptLanguageHeader
.split(',')
.map((lang) => {
.map(lang => {
const [code, priority = 'q=1'] = lang.trim().split(';q=')
return { code, priority: parseFloat(priority) }
})
@@ -17,7 +17,7 @@ export const getPreferredLanguage = (acceptLanguageHeader = '') => {
const parseCookies = (cookieHeader: string) => {
const cookies: Record<string, string> = {}
if (cookieHeader) {
cookieHeader.split(';').forEach((cookie) => {
cookieHeader.split(';').forEach(cookie => {
const [name, value] = cookie.trim().split('=')
if (name && value) {
cookies[name] = decodeURIComponent(value)
@@ -33,13 +33,17 @@ const parseCookies = (cookieHeader: string) => {
* @param {string} path - 현재 URL 경로
*/
export const csrGetFinalLocale = (path = '', coveragesLocales: string[]) => {
const config = useRuntimeConfig()
const baseDomain = `${config.public.baseDomain}`
const runtimeConfig = useRuntimeConfig()
const baseDomain = `${runtimeConfig.public.baseDomain}`
let finalLocale = DEFAULT_LOCALE_CODE // 기본값 설정
// coveragesLocales가 빈 배열이거나 유효하지 않은 경우 기본 언어 반환
if (!coveragesLocales || !Array.isArray(coveragesLocales) || coveragesLocales.length === 0) {
if (
!coveragesLocales ||
!Array.isArray(coveragesLocales) ||
coveragesLocales.length === 0
) {
return finalLocale
}
@@ -60,15 +64,21 @@ export const csrGetFinalLocale = (path = '', coveragesLocales: string[]) => {
}
// 2. LOCALE 쿠키 언어
const cookieLanguage = `${useCookie('LOCALE', { domain: baseDomain }).value}`.toLowerCase()
const cookieLanguage =
`${useCookie('LOCALE', { domain: baseDomain }).value}`.toLowerCase()
if (cookieLanguage && cookieLanguage !== '') {
finalLocale = cookieLanguage
return finalLocale
}
// 3. 브라우저 언어
const browserLanguage = `${navigator.language || navigator.languages[0]}`.toLowerCase()
if (browserLanguage && browserLanguage !== '' && coveragesLocales.includes(browserLanguage)) {
const browserLanguage =
`${navigator.language || navigator.languages[0]}`.toLowerCase()
if (
browserLanguage &&
browserLanguage !== '' &&
coveragesLocales.includes(browserLanguage)
) {
finalLocale = browserLanguage
return finalLocale
}
@@ -85,11 +95,20 @@ export const csrGetFinalLocale = (path = '', coveragesLocales: string[]) => {
* @param {string} path - 현재 URL 경로
* @param {any} headers - 요청 헤더
*/
export const ssrGetFinalLocale = (path = '', headers: any, coveragesLocales: string[], defaultLocale: string) => {
export const ssrGetFinalLocale = (
path = '',
headers: any,
coveragesLocales: string[],
defaultLocale: string
) => {
let finalLocale = defaultLocale || DEFAULT_LOCALE_CODE // 기본값 설정
try {
// coveragesLocales가 빈 배열이거나 유효하지 않은 경우 기본 언어 반환
if (!coveragesLocales || !Array.isArray(coveragesLocales) || coveragesLocales.length === 0) {
if (
!coveragesLocales ||
!Array.isArray(coveragesLocales) ||
coveragesLocales.length === 0
) {
return finalLocale
}
@@ -101,7 +120,11 @@ export const ssrGetFinalLocale = (path = '', headers: any, coveragesLocales: str
}
const pathLocalee = `${path.split('/')[1]}`.toLowerCase()
// URL path에 포함된 언어 정보가 지원하는 언어인지 체크
if (pathLocalee && pathLocalee !== '' && coveragesLocales.includes(pathLocalee)) {
if (
pathLocalee &&
pathLocalee !== '' &&
coveragesLocales.includes(pathLocalee)
) {
finalLocale = pathLocalee
return finalLocale
}
@@ -110,8 +133,14 @@ export const ssrGetFinalLocale = (path = '', headers: any, coveragesLocales: str
// 2. LOCALE 쿠키 언어 (SSR에서는 headers에서 직접 파싱)
const cookieHeader = headers.cookie || ''
const cookies = parseCookies(cookieHeader)
const cookieLanguage = cookies.LOCALE ? `${cookies.LOCALE}`.toLowerCase() : ''
if (cookieLanguage && cookieLanguage !== '' && coveragesLocales.includes(cookieLanguage)) {
const cookieLanguage = cookies.LOCALE
? `${cookies.LOCALE}`.toLowerCase()
: ''
if (
cookieLanguage &&
cookieLanguage !== '' &&
coveragesLocales.includes(cookieLanguage)
) {
finalLocale = cookieLanguage
return finalLocale
}

View File

@@ -9,10 +9,10 @@ import { csrFormatJWT } from '#layers/utils/formatUtil'
* Stove 로그인
*/
export const csrGoStoveLogin = () => {
const config = useRuntimeConfig()
const runtimeConfig = useRuntimeConfig()
const gameDataStore = useGameDataStore()
const loginUrl = config.public.stoveLoginUrl
const loginUrl = runtimeConfig.public.stoveLoginUrl
const stoveGameId = gameDataStore.gameData?.game_id
const stoveGameNo = gameDataStore.gameData?.game_code
const redirectUrl = encodeURIComponent(location.href)

View File

@@ -23,8 +23,8 @@ export const getImageHost = (
if (/^(https?:\/\/|www\.)/.test(path)) return path
const config = useRuntimeConfig()
const { staticUrl, assetsUrl } = config.public
const runtimeConfig = useRuntimeConfig()
const { staticUrl, assetsUrl } = runtimeConfig.public
const { imageType = 'game' } = options
const isDevelopment = import.meta.dev