Merge branch 'feature/20251001-gil' into feature/202501107-all
This commit is contained in:
@@ -8,11 +8,12 @@ onMounted(() => {
|
||||
const langCodes = gameData?.lang_codes
|
||||
const defaultLangCode = gameData?.default_lang_code
|
||||
const stoveGnbData = gameData?.stove_gnb_json
|
||||
const designTheme = gameData?.design_theme
|
||||
|
||||
const currentDomain =
|
||||
window.location.protocol + '//' + window.location.hostname
|
||||
if (typeof window !== 'undefined' && (window as any).StoveGnb) {
|
||||
mountedInstance = (window as any).StoveGnb.mount('#stove-wrap', {
|
||||
const stoveGnbOptions = {
|
||||
logArea: currentDomain,
|
||||
useLanguageCodeFromPath: true,
|
||||
serviceTitle: {
|
||||
@@ -38,14 +39,18 @@ onMounted(() => {
|
||||
},
|
||||
mode: {
|
||||
theme: {
|
||||
default:
|
||||
stoveGnbData?.skin_type === 'gnb-dark-mini' ? 'dark' : 'light',
|
||||
support: ['dark', 'light'],
|
||||
support: ['light', 'dark'],
|
||||
default: designTheme === 1 ? 'light' : 'dark',
|
||||
// support: designTheme === 1 ? ['light'] : ['dark'],
|
||||
},
|
||||
mini: true,
|
||||
layout: 'wide',
|
||||
fixed: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
mountedInstance = (window as any).StoveGnb.mount('#stove-wrap', stoveGnbOptions)
|
||||
|
||||
console.log("🚀 ~ onMounted ~ stoveGnbOptions:", stoveGnbOptions)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<template>
|
||||
<footer id="footer" ref="footerRef" class="bg-black">
|
||||
<div
|
||||
class="inner relative max-w-7xl mx-auto px-10 py-9 text-[12px] text-gray-400 md:px-4 md:py-9 md:text-[12px]"
|
||||
class="inner relative max-w-7xl mx-auto px-5 md:px-10 py-4 text-[12px] text-gray-400 md:px-4 md:py-9 md:text-[12px]"
|
||||
>
|
||||
<div class="menu-area">
|
||||
<ul class="flex items-center flex-wrap md:gap-6">
|
||||
<div class="menu-area py-4 pb-4">
|
||||
<ul class="flex items-center flex-wrap gap-x-6 gap-y-2">
|
||||
<li
|
||||
v-for="(footerMenuItem, index) in footerLinks"
|
||||
:key="index"
|
||||
class="text-sm md:text-[11px] md:tracking-[-0.5px] relative flex items-center"
|
||||
class="text-[15px] text-white/50 md:tracking-[-0.5px] relative flex items-center"
|
||||
>
|
||||
<NuxtLink
|
||||
:to="footerMenuItem.url"
|
||||
:target="footerMenuItem.target"
|
||||
:class="[
|
||||
footerMenuItem.active === 'y' && 'text-[#e04600]',
|
||||
footerMenuItem.active === 'y' && 'text-white/50',
|
||||
index === 2 && 'text-[#fff]',
|
||||
'hover:text-gray-600 transition-colors',
|
||||
]"
|
||||
@@ -23,13 +23,13 @@
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li class="relative">
|
||||
<button class="hover:text-gray-600 transition-colors" @click="toggleAgeRating">
|
||||
{{ footerAgeRating }}
|
||||
<button class="text-[15px] text-white/50 hover:text-gray-600 transition-colors" @click="toggleAgeRating">
|
||||
{{ tm('Footer_AgeRating') }}
|
||||
</button>
|
||||
<div v-if="showAgeRating" class="game-rating-card absolute bottom-6 left-1/2 -translate-x-1/2 bg-[#383838] rounded-lg w-[340px] mx-auto z-10">
|
||||
<div v-if="showAgeRating" class="game-rating-card absolute bottom-6 left-1 md:left-1/2 md:-translate-x-1/2 bg-[#383838] rounded-lg border border-white/30 w-[340px] mx-auto z-10">
|
||||
<!-- 헤더 -->
|
||||
<div class="px-6 py-4 rounded-t-lg flex justify-between items-center">
|
||||
<h3 class="text-white text-base">{{ footerAgeRating }}</h3>
|
||||
<h3 class="text-white text-base">{{ tm('Footer_AgeRating') }}</h3>
|
||||
<button class="text-white hover:text-gray-300 transition-colors" @click="toggleAgeRating">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
@@ -81,23 +81,27 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="address-area mt-6">
|
||||
<address class="not-italic text-gray-500">
|
||||
<div class="address-area mt-4 hidden sm:block">
|
||||
<address class="not-italic text-white/50">
|
||||
<div class="row my-1.5 leading-5">
|
||||
<span
|
||||
v-dompurify-html="footerAddress"
|
||||
class="[&_a]:cursor-pointer [&_a]:text-blue-500 [&_a]:underline"
|
||||
v-dompurify-html="tm('Footer_Address')"
|
||||
class="text-[13px] [&_a]:cursor-pointer [&_a]:text-white/50 [&_a]:underline"
|
||||
></span>
|
||||
</div>
|
||||
</address>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 md:mt-6">
|
||||
<div class="text-xs text-white/30">{{ footerCaution }}</div>
|
||||
<div class="language-area static md:absolute bottom-7 right-10 text-white mt-5 md:mt-0 md:bottom-5.5 md:right-4">
|
||||
<BlocksLanguageSwitcher />
|
||||
</div>
|
||||
|
||||
<div class="copyright-area mt-6 text-gray-500 md:mt-4">
|
||||
<span>© Smilegate. All rights reserved</span>
|
||||
<div class="mt-6 md:mt-6 hidden sm:block">
|
||||
<div v-dompurify-html="tm('Footer_caution')" class="text-xs text-white/30"></div>
|
||||
</div>
|
||||
|
||||
<div class="copyright-area mt-6 text-[13px] text-white/50 md:mt-4">
|
||||
<span>{{ tm('Footer_Copyright') }}</span>
|
||||
</div>
|
||||
|
||||
<div class="logo-area flex mt-6 md:mt-6">
|
||||
@@ -107,33 +111,26 @@
|
||||
class="smilegate"
|
||||
>
|
||||
<img
|
||||
src="https://static-pubcomm.gate8.com/local/template/l9/common/logo_smilegate.png"
|
||||
:src="getImageHost(`/images/common/logo_smilegate.png`, { imageType: 'common' })"
|
||||
alt="스마일게이트 로고"
|
||||
class="w-auto h-auto"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
v-if="setDevCi.dev_ci_yn"
|
||||
:href="footerData.use_dev_ci_url ? setDevCi.dev_ci_img_path : '#'"
|
||||
:href="footerData.use_dev_ci_url ? footerData.dev_ci_url : '#'"
|
||||
target="_blank"
|
||||
class="nx3 ml-2.5 md:ml-4"
|
||||
>
|
||||
<img
|
||||
:src="`${staticUrl}${setDevCi.dev_ci_img_path}`"
|
||||
:src="getImageHost(`${setDevCi.dev_ci_img_path}`, { imageType: 'game' })"
|
||||
alt="CI"
|
||||
class="w-auto h-[24px]"
|
||||
class="block w-auto h-[22px]"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="language-area absolute bottom-7 right-10 text-white md:bottom-5.5 md:right-4"
|
||||
>
|
||||
<BlocksLanguageSwitcher />
|
||||
|
||||
<!-- <SelectLanguage /> -->
|
||||
<!-- <AtomsLanguageSwitcher /> -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
@@ -141,20 +138,20 @@
|
||||
<script setup lang="ts">
|
||||
import type { FooterMenuItem, FooterData, DevCiConfig } from '#layers/types/Common'
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const staticUrl = config.public.staticUrl
|
||||
const runType = config.public.runType
|
||||
// Configuration
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
|
||||
const multilingualFileName = 'test_common_template.json'
|
||||
|
||||
const translationApi = `${runType}/test/multilingual/test_common_template.json`
|
||||
|
||||
const result = await useApiData({ baseApiUrl: staticUrl, url: translationApi })
|
||||
|
||||
const { tm } = useI18n({
|
||||
useScope: 'local',
|
||||
messages: result
|
||||
// Multilingual
|
||||
const resultGetMultilingual = await useGetMultilingual({
|
||||
baseApiUrl: dataResourcesUrl,
|
||||
fileName: multilingualFileName,
|
||||
})
|
||||
const { tm }: any = useI18n({
|
||||
useScope: 'local',
|
||||
messages: Object(resultGetMultilingual?.value?.multilingual),
|
||||
})
|
||||
|
||||
// const { tm } = useI18n()
|
||||
|
||||
const gameDataStore = useGameDataStore()
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
@@ -174,22 +171,16 @@ const setDevCi = ref<DevCiConfig>({
|
||||
///local/template/common/grades_age
|
||||
const getGameRatingImage = computed((): string[] => {
|
||||
const contentInfo = footerData.value.game_rating_info.rating_type.split(',')
|
||||
// rating_type 12, 15, 18, 19 에 따라 이미지명을 가져오고 이미지를 반환
|
||||
const ageTypeMap: Record<string, string> = {
|
||||
'12': 'Type12',
|
||||
'15': 'Type15',
|
||||
'19': 'Type19',
|
||||
'all': 'TypeAll',
|
||||
'e': 'TypeExempt'
|
||||
}
|
||||
return contentInfo.map(item => {
|
||||
switch (item) {
|
||||
case '12':
|
||||
return getImageHost('/images/common/grades_age/Type12.svg', { imageType: 'common' })
|
||||
case '15':
|
||||
return getImageHost('/images/common/grades_age/Type15.svg', { imageType: 'common' })
|
||||
case '19':
|
||||
return getImageHost('/images/common/grades_age/Type19.svg', { imageType: 'common' })
|
||||
case 'all':
|
||||
return getImageHost('/images/common/grades_age/TypeAll.svg', { imageType: 'common' })
|
||||
case 'e':
|
||||
return getImageHost('/images/common/grades_age/TypeExempt.svg', { imageType: 'common' })
|
||||
default:
|
||||
return getImageHost('/images/common/grades_age/TypeTest.svg', { imageType: 'common' })
|
||||
}
|
||||
const type = ageTypeMap[item] || 'TypeTest'
|
||||
return getImageHost(`/images/common/grades_age/${type}.svg`, { imageType: 'common' })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -197,24 +188,20 @@ const getContentInfoImage = computed((): string[] => {
|
||||
const contentInfo = footerData.value.game_rating_info.content_info.split(',')
|
||||
contentInfo.pop()
|
||||
|
||||
const contentTypeMap: Record<string, string> = {
|
||||
'1': 'Type-sexual',
|
||||
'2': 'Type-violence',
|
||||
'3': 'Type-fear',
|
||||
'4': 'Type-inapposite',
|
||||
'5': 'Type-drug',
|
||||
'6': 'Type-crime',
|
||||
'7': 'Type-speculation',
|
||||
}
|
||||
|
||||
return contentInfo.map(item => {
|
||||
switch (item) {
|
||||
case '1':
|
||||
return getImageHost('/images/common/grades_use/Type-sexual.svg', { imageType: 'common' })
|
||||
case '2':
|
||||
return getImageHost('/images/common/grades_use/Type-fear.svg', { imageType: 'common' })
|
||||
case '3':
|
||||
return getImageHost('/images/common/grades_use/Type-inapposite.svg', { imageType: 'common' })
|
||||
case '4':
|
||||
return getImageHost('/images/common/grades_use/Type-drug.svg', { imageType: 'common' })
|
||||
case '5':
|
||||
return getImageHost('/images/common/grades_use/Type-crime.svg', { imageType: 'common' })
|
||||
case '6':
|
||||
return getImageHost('/images/common/grades_use/Type-speculation.svg', { imageType: 'common' })
|
||||
case '7':
|
||||
return getImageHost('/images/common/grades_use/Type-violence.svg', { imageType: 'common' })
|
||||
}
|
||||
})
|
||||
const type = contentTypeMap[item]
|
||||
return type ? getImageHost(`/images/common/grades_use/${type}.svg`, { imageType: 'common' }) : ''
|
||||
}).filter(Boolean)
|
||||
})
|
||||
|
||||
const showAgeRating = ref<boolean>(false)
|
||||
@@ -222,37 +209,12 @@ const toggleAgeRating = (): void => {
|
||||
showAgeRating.value = !showAgeRating.value
|
||||
}
|
||||
|
||||
// 템플릿에서 사용할 다국어 텍스트들
|
||||
const footerAgeRating = computed((): string => {
|
||||
const text = (tm as any)('Footer_AgeRating')
|
||||
return typeof text === 'string' ? text : ''
|
||||
})
|
||||
const footerAgeRatingInfo = computed((): string[] => {
|
||||
const info = (tm as any)('Footer_AgeRating_Info')
|
||||
return Array.isArray(info) ? info : []
|
||||
})
|
||||
const footerAddress = computed((): string => {
|
||||
const address = (tm as any)('Footer_Address')
|
||||
return typeof address === 'string' ? address : ''
|
||||
})
|
||||
const footerCaution = computed((): string => {
|
||||
const caution = (tm as any)('Footer_caution')
|
||||
return typeof caution === 'string' ? caution : ''
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
/* 태국어 폰트 크기 조정 */
|
||||
@media (max-width: 411px) {
|
||||
:global(.lang-th) .menu-area li {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 321px) {
|
||||
:global(.lang-th) .menu-area li {
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
interface ReqApiData {
|
||||
baseApiUrl: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export const useApiData = async (req: ReqApiData): Promise<any> => {
|
||||
const dataUrl = `${req.baseApiUrl}/${req.url}` // 정상 URL 경로
|
||||
try {
|
||||
const fetch = await $fetch<any>(dataUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
}
|
||||
})
|
||||
return fetch
|
||||
} catch (error) {
|
||||
console.log('error', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ export const useGetGameDataExternal = () => {
|
||||
console.log('🚀 ~ getGameDataExternal ~ req:', req)
|
||||
// const config = useRuntimeConfig()
|
||||
const config = useRuntimeConfig()
|
||||
const stoveApiUrl = `${config.public.stoveApiUrl}`
|
||||
const apiUrl = `${stoveApiUrl}/pub-comm/v1.0/template/game?game_domain=${req.gameDomain}&lang_code=${req.langCode}`
|
||||
const stoveApiBaseUrl = config.public.stoveApiUrl
|
||||
const apiUrl = `${stoveApiBaseUrl}/pub-comm/v1.0/template/game?game_domain=${req.gameDomain}&lang_code=${req.langCode}`
|
||||
|
||||
try {
|
||||
const response = (await commonFetch('GET', apiUrl)) as GameDataResponse
|
||||
|
||||
@@ -11,6 +11,7 @@ import GrBoard01 from '#layers/templates/GrBoard01/index.vue'
|
||||
import GrContents01 from '#layers/templates/GrContents01/index.vue'
|
||||
import FxVideo01 from '#layers/templates/FxVideo01/index.vue'
|
||||
import FxDownload01 from '#layers/templates/FxDownload01/index.vue'
|
||||
import FxSecure01 from '#layers/templates/FxSecure01/index.vue'
|
||||
import FxPreregist01 from '#layers/templates/FxPreregist01/index.vue'
|
||||
|
||||
const templateRegistry = {
|
||||
@@ -27,6 +28,7 @@ const templateRegistry = {
|
||||
GR_CONTENTS_01: { component: GrContents01 },
|
||||
FX_VIDEO_01: { component: FxVideo01 },
|
||||
FX_DOWNLOAD_01: { component: FxDownload01 },
|
||||
FX_SECURE_01: { component: FxSecure01 },
|
||||
FX_PREREGIST_01: { component: FxPreregist01 },
|
||||
} as const
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ export const useTokenValidation = () => {
|
||||
const showLoginModal = (alertKey: string) => {
|
||||
modalStore.handleOpenConfirm({
|
||||
contentText: tm(alertKey),
|
||||
isShowDimmed: true,
|
||||
confirmButtonText: tm('Text_StoveLogin'),
|
||||
modalName: 'modal-login',
|
||||
confirmButtonEvent: () => {
|
||||
|
||||
@@ -7,8 +7,8 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
// const baseDomain = `${config.public.baseDomain}`
|
||||
const stoveApiUrl = `${config.public.stoveApiUrl}`
|
||||
const stoveGameId = `${gameData.value.game_id}`
|
||||
const stoveApiBaseUrl = config.public.stoveApiUrl
|
||||
const stoveGameId = gameData.value.game_id
|
||||
// const stoveMaintenanceApiUrl = `${config.public.stoveMaintenanceApiUrl}`
|
||||
|
||||
// const localeCookie = useCookie('LOCALE', {
|
||||
@@ -20,7 +20,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
||||
|
||||
// 웹 점검 -----
|
||||
const { isWebInspection, getInspectionDataExternal } = useGetInspectionDataExternal()
|
||||
await getInspectionDataExternal({ baseApiUrl: stoveApiUrl, gameId: stoveGameId })
|
||||
await getInspectionDataExternal({ baseApiUrl: stoveApiBaseUrl, gameId: stoveGameId })
|
||||
|
||||
|
||||
// 게임 점검 -----
|
||||
|
||||
@@ -3,17 +3,25 @@ import { usePageDataStore } from '#layers/stores/usePageDataStore'
|
||||
import { useGetGameDomain } from '#layers/composables/useGetGameDomain'
|
||||
import { usePathResolver } from '#layers/composables/usePathResolver'
|
||||
import type { PageDataResponse } from '#layers/types/api/pageData'
|
||||
import type {
|
||||
GameDataValue,
|
||||
} from '#layers/types/api/gameData'
|
||||
|
||||
export default defineNuxtRouteMiddleware(async (to, _from) => {
|
||||
if (!import.meta.client) return
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const stoveApiBaseUrl = config.public.stoveApiUrl
|
||||
const apiUrl = `${stoveApiBaseUrl}/pub-comm/v2.0/template/page`
|
||||
|
||||
const store = usePageDataStore()
|
||||
const gameDomain = useGetGameDomain()
|
||||
const { getPathAfterLanguage } = usePathResolver()
|
||||
const headers = useRequestHeaders()
|
||||
const langCode = ssrGetFinalLocale(to.path, headers)
|
||||
const gameDataStore = useGameDataStore()
|
||||
const gameData = gameDataStore.gameData as GameDataValue
|
||||
|
||||
const stoveApiBaseUrl = config.public.stoveApiUrl
|
||||
const apiUrl = `${stoveApiBaseUrl}/pub-comm/v2.0/template/page`
|
||||
const langCode = ssrGetFinalLocale(to.path, headers, gameData?.lang_codes, gameData?.default_lang_code)
|
||||
|
||||
try {
|
||||
if (to.path.includes('inspection')) {
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
getHeader,
|
||||
getRequestHost,
|
||||
defineEventHandler,
|
||||
getRequestURL,
|
||||
} from 'h3'
|
||||
import { ssrGetFinalLocale } from '../../utils/localeUtil'
|
||||
import type { GameDataResponse } from '../../types/api/gameData'
|
||||
@@ -123,10 +122,7 @@ function fnLocaleMiddleware(event: any, finalLocale: string) {
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
// const runType = `${config.public.runType}`
|
||||
const iBaseApiUrl = `${config.public.stoveApiUrlServer}`
|
||||
const baseDomain = `${config.public.baseDomain}`
|
||||
// console.log("🚀 ~ baseDomain:", config.public.baseDomain)
|
||||
// const url = getRequestURL(event)
|
||||
|
||||
@@ -147,6 +143,45 @@ export default defineEventHandler(async event => {
|
||||
// }
|
||||
// }
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const iBaseApiUrl = `${config.public.stoveApiUrlServer}`
|
||||
const baseDomain = `${config.public.baseDomain}`
|
||||
const stoveApiUrlBaseServer = config.public.stoveApiUrlServer
|
||||
const apiUrl = `${stoveApiUrlBaseServer}/pub-comm/v1.0/template/game`
|
||||
|
||||
const initGameData: GameDataResponse | null = null
|
||||
let initLangCodes: string[] | null = null
|
||||
let finalLocale
|
||||
let cleanHost
|
||||
let initDefaultLocale
|
||||
|
||||
const host =
|
||||
(getHeader(event, 'host') || getRequestHost(event)).toString() || ''
|
||||
const isGameDomainExtractable = host.includes(baseDomain)
|
||||
|
||||
if (isGameDomainExtractable) {
|
||||
cleanHost = host.split(':')[0]
|
||||
event.context.gameDomain = cleanHost
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
const queryParams: Record<string, string> = {
|
||||
game_domain: cleanHost || '',
|
||||
lang_code: '',
|
||||
}
|
||||
const initResponse = (await $fetch(apiUrl, {
|
||||
query: queryParams,
|
||||
})) as GameDataResponse | null
|
||||
// initGameData = initResponse || null
|
||||
// console.log("🚀 ~ 00000 initGameData:", initGameData)
|
||||
initLangCodes = initResponse?.value?.lang_codes || null
|
||||
initDefaultLocale = initResponse?.value?.default_lang_code || null
|
||||
console.log("🚀 ~ 000111 initLangCodes:", initLangCodes)
|
||||
} catch (error) {
|
||||
console.error('init gameData load error:', error)
|
||||
}
|
||||
|
||||
const fullPath = event.path
|
||||
|
||||
// 1-1. 정적 파일 패스
|
||||
@@ -157,8 +192,8 @@ export default defineEventHandler(async event => {
|
||||
// 1-2. /inspection 패스
|
||||
if (fullPath.includes('/inspection')) {
|
||||
// 리턴 되기 전 언어 쿠키 세팅
|
||||
// const finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers)
|
||||
// setFinalLocaleCookie(event, finalLocale, baseDomain)
|
||||
finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers, initLangCodes, initDefaultLocale)
|
||||
setFinalLocaleCookie(event, finalLocale, baseDomain)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -178,30 +213,16 @@ export default defineEventHandler(async event => {
|
||||
const cacheKey = 'inspection'
|
||||
// console.log("🚀 11111 ~ cacheKey:", cacheKey)
|
||||
|
||||
const host =
|
||||
(getHeader(event, 'host') || getRequestHost(event)).toString() || ''
|
||||
const isGameDomainExtractable = host.includes(baseDomain)
|
||||
|
||||
if (isGameDomainExtractable) {
|
||||
const cleanHost = host.split(':')[0]
|
||||
event.context.gameDomain = cleanHost
|
||||
}
|
||||
|
||||
// gameData를 직접 가져와서 context에 저장 (API 호출 없이)
|
||||
try {
|
||||
const config = useRuntimeConfig()
|
||||
const stoveApiUrlServer = config.public.stoveApiUrlServer
|
||||
const apiUrl = `${stoveApiUrlServer}/pub-comm/v1.0/template/game`
|
||||
|
||||
// console.log("🚀 ~ apiUrl:", apiUrl)
|
||||
|
||||
// 2. 언어 코드 추출
|
||||
// const finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers)
|
||||
|
||||
const finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers)
|
||||
finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers, initLangCodes, initDefaultLocale)
|
||||
console.log("🚀 222 finalLocale:", finalLocale)
|
||||
|
||||
const queryParams: Record<string, string> = {
|
||||
game_domain: event.context.gameDomain || '',
|
||||
game_domain: cleanHost || '',
|
||||
lang_code: finalLocale,
|
||||
}
|
||||
const response = (await $fetch(apiUrl, {
|
||||
@@ -209,10 +230,9 @@ export default defineEventHandler(async event => {
|
||||
})) as GameDataResponse | null
|
||||
|
||||
// 언어패스 쿠키 굽기 - 장기방안에서는 굽지않음
|
||||
// const langCoverages = response?.value?.lang_codes || []
|
||||
// if(langCoverages.includes(finalLocale)) {
|
||||
// setFinalLocaleCookie(event, finalLocale, baseDomain)
|
||||
// }
|
||||
if(initLangCodes?.includes(finalLocale)) {
|
||||
setFinalLocaleCookie(event, finalLocale, baseDomain)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
272
layers/templates/FxSecure01/index.vue
Normal file
272
layers/templates/FxSecure01/index.vue
Normal file
@@ -0,0 +1,272 @@
|
||||
<script setup lang="ts">
|
||||
import { getComponentGroup } from '#layers/utils/dataUtil'
|
||||
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
import { getImageHost } from '#layers/utils/styleUtil'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { handleTokenValidation } = useTokenValidation()
|
||||
|
||||
// Configuration
|
||||
const config = useRuntimeConfig()
|
||||
const dataResourcesUrl = config.public.dataResourcesUrl as string
|
||||
const multilingualFileName = 'test_homepage_brand_secure.json'
|
||||
const stoveApiBaseUrl = config.public.stoveApiUrl
|
||||
|
||||
// Multilingual
|
||||
const resultGetMultilingual = await useGetMultilingual({
|
||||
baseApiUrl: dataResourcesUrl,
|
||||
fileName: multilingualFileName,
|
||||
})
|
||||
const { tm }: any = useI18n({
|
||||
useScope: 'local',
|
||||
messages: Object(resultGetMultilingual?.value?.multilingual),
|
||||
})
|
||||
|
||||
const isLogin = ref(false)
|
||||
|
||||
const secureSetting = ref({
|
||||
otpLoginYn: 'N',
|
||||
abroadLoginBlockYn: 'N',
|
||||
pcRegisterYn: 'N'
|
||||
})
|
||||
|
||||
// 회원 보안 설정 설정
|
||||
const handleSecureSetting = (url: string) => {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
// 로그인 유효성 체크
|
||||
const checkLoginValidation = async () => {
|
||||
const accessToken = useCookie('SUAT')
|
||||
const validateTokenResult = await handleTokenValidation(
|
||||
accessToken.value || ''
|
||||
)
|
||||
isLogin.value = validateTokenResult
|
||||
}
|
||||
|
||||
// 회원 보안 설정 조회
|
||||
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'
|
||||
}
|
||||
try {
|
||||
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'
|
||||
|
||||
secureSetting.value = {
|
||||
otpLoginYn: getValue('OTP_LOGIN_YN'),
|
||||
abroadLoginBlockYn: getValue('ABROAD_LOGIN_BLOCK_YN'),
|
||||
pcRegisterYn: getValue('PC_REGISTER_YN')
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
// Data
|
||||
const backgroundData = computed(() =>
|
||||
getComponentGroup(props.components, 'background')
|
||||
)
|
||||
|
||||
// Computed
|
||||
const secureCards = computed(() => {
|
||||
return [
|
||||
{
|
||||
id: 'SECURE_CARD_0',
|
||||
title: tm('Secure_Stove_otp') || '스토브 인증기 (OTP)',
|
||||
description: tm('Secure_Stove_otp_desc') || '스토브 앱으로 인증 후 안전하게 로그인하세요.',
|
||||
status: secureSetting.value.otpLoginYn,
|
||||
benefitTitle: tm('Secure_Stove_otp_benefits') || '스토브 OTP 혜택',
|
||||
benefitDesc: tm('Secure_Defense_bonus_10') || '방어력 +10',
|
||||
benefitIcon: '/images/common/img_OTP.png',
|
||||
buttonDisabled: false,
|
||||
url: tm('Secure_OtpLogin_Url'),
|
||||
},
|
||||
{
|
||||
id: 'SECURE_CARD_1',
|
||||
title: tm('Secure_Block_foreign_login') || '해외 로그인 차단',
|
||||
description: tm('Secure_Block_foreign_login_desc') || '접속 국가를 제한하여 의심 로그인을 차단해요.',
|
||||
status: secureSetting.value.abroadLoginBlockYn,
|
||||
benefitTitle: '',
|
||||
benefitDesc: '',
|
||||
benefitIcon: '',
|
||||
buttonDisabled: true,
|
||||
url: tm('Secure_AbroadLogin_Url'),
|
||||
},
|
||||
{
|
||||
id: 'SECURE_CARD_2',
|
||||
title: tm('Secure_Trusted_pc_management') || '지정 PC 관리',
|
||||
description: tm('Secure_Trusted_pc_desc') || '지정 PC에서만 로그인할 수 있게 설정해 보세요.',
|
||||
status: secureSetting.value.pcRegisterYn,
|
||||
benefitTitle: '',
|
||||
benefitDesc: '',
|
||||
benefitIcon: '',
|
||||
buttonDisabled: false,
|
||||
url: tm('Secure_PcRegister_Url'),
|
||||
},
|
||||
]
|
||||
})
|
||||
// 유의사항 내용 다국어 조회
|
||||
const cautionText = computed(() => {
|
||||
return tm('Secure_Notice_Content') || []
|
||||
})
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
fnGetSecuritySetting()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WidgetsFixMainTitle
|
||||
:title="tm('Secure_Page_Title') || '보안 강화 캠페인'"
|
||||
:resources-data="backgroundData"
|
||||
class="mx-auto"
|
||||
/>
|
||||
|
||||
<div class="section-container static">
|
||||
<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]">
|
||||
{{ 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>
|
||||
</div>
|
||||
|
||||
<!-- Secure Cards -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 w-full gap-3 md:gap-5 mb-6">
|
||||
<div
|
||||
v-for="card in secureCards"
|
||||
:key="card.id"
|
||||
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">
|
||||
<!-- Badge -->
|
||||
<div class="inline-flex">
|
||||
<span
|
||||
:class="[
|
||||
'px-1.5 md:px-2 py-0.5 md:py-1 rounded-full text-[12px] md:text-[14px] font-medium leading-5',
|
||||
card.status === 'Y'
|
||||
? 'bg-[#E2EAFF] text-[#3C75FF]'
|
||||
: 'bg-[#EBEBEB] text-[#999999]',
|
||||
]"
|
||||
>
|
||||
{{ 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]">
|
||||
{{ 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]">
|
||||
{{ card.description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Benefit Section -->
|
||||
<div
|
||||
:class="[
|
||||
'self-stretch p-[10px] md:p-4 rounded-2xl flex flex-col gap-4',
|
||||
card.benefitTitle ? 'bg-[#F0F4FF]' : '',
|
||||
]"
|
||||
>
|
||||
<!-- Benefit Info -->
|
||||
<div
|
||||
v-if="card.benefitTitle"
|
||||
class="flex items-center gap-[12px]"
|
||||
>
|
||||
<div
|
||||
v-if="card.benefitIcon"
|
||||
class="w-[48px] h-[48px] bg-[#3C75FF] rounded-[8px] flex items-center justify-center"
|
||||
>
|
||||
<img
|
||||
:src="getImageHost(card.benefitIcon, { imageType: 'common' })"
|
||||
:alt="card.benefitTitle"
|
||||
class="w-[48px] h-[48px] object-contain rounded-2xl"
|
||||
loading="lazy"
|
||||
draggable="false"
|
||||
/>
|
||||
</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]">
|
||||
{{ card.benefitTitle }}
|
||||
</div>
|
||||
<div
|
||||
v-if="card.benefitDesc"
|
||||
class="text-[#3C75FF] text-[12px] md:text-[13px] font-[400] leading-[18px] md:leading-[22px] tracking-[-0.325px] opacity-90"
|
||||
>
|
||||
{{ card.benefitDesc }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Button -->
|
||||
<AtomsButton
|
||||
v-if="card.status === 'N'"
|
||||
type="external"
|
||||
button-size="size-small md:size-large"
|
||||
background-color="#000000"
|
||||
text-color="#FFFFFF"
|
||||
@click="isLogin ? handleSecureSetting(card.url) : checkLoginValidation()"
|
||||
>
|
||||
<span>{{ tm('Secure_Action_setup') }}</span>
|
||||
</AtomsButton>
|
||||
<AtomsButton
|
||||
v-else
|
||||
type="action"
|
||||
button-size="size-small md:size-large"
|
||||
background-color="#EBEBEB"
|
||||
text-color="#999999"
|
||||
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>
|
||||
</AtomsButton>
|
||||
</div>
|
||||
</div>
|
||||
</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]">
|
||||
{{ tm('Secure_Notice') }}
|
||||
</h5>
|
||||
<ul class="relative flex flex-col items-start justify-start w-full">
|
||||
<li
|
||||
v-for="caution in cautionText"
|
||||
: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>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -38,6 +38,11 @@ export const csrGetFinalLocale = (path = '', coveragesLocales: string[]) => {
|
||||
|
||||
let finalLocale = DEFAULT_LOCALE_CODE // 기본값 설정
|
||||
|
||||
// coveragesLocales가 빈 배열이거나 유효하지 않은 경우 기본 언어 반환
|
||||
if (!coveragesLocales || !Array.isArray(coveragesLocales) || coveragesLocales.length === 0) {
|
||||
return finalLocale
|
||||
}
|
||||
|
||||
// 1. URL 패스에 포함된 언어
|
||||
if (path && path !== '' && path.split('/').length > 1) {
|
||||
// 쿼리스트링 제거한 순수 path 검사
|
||||
@@ -80,9 +85,14 @@ export const csrGetFinalLocale = (path = '', coveragesLocales: string[]) => {
|
||||
* @param {string} path - 현재 URL 경로
|
||||
* @param {any} headers - 요청 헤더
|
||||
*/
|
||||
export const ssrGetFinalLocale = (path = '', headers: any) => {
|
||||
let finalLocale = DEFAULT_LOCALE_CODE // 기본값 설정
|
||||
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) {
|
||||
return finalLocale
|
||||
}
|
||||
|
||||
// 1. URL path에 포함된 언어 정보
|
||||
if (path && path !== '' && path.split('/').length > 1) {
|
||||
// 쿼리스트링 제거한 순수 path 검사
|
||||
@@ -91,7 +101,7 @@ export const ssrGetFinalLocale = (path = '', headers: any) => {
|
||||
}
|
||||
const pathLocalee = `${path.split('/')[1]}`.toLowerCase()
|
||||
// URL path에 포함된 언어 정보가 지원하는 언어인지 체크
|
||||
if (pathLocalee && pathLocalee !== '') {
|
||||
if (pathLocalee && pathLocalee !== '' && coveragesLocales.includes(pathLocalee)) {
|
||||
finalLocale = pathLocalee
|
||||
return finalLocale
|
||||
}
|
||||
@@ -101,7 +111,7 @@ export const ssrGetFinalLocale = (path = '', headers: any) => {
|
||||
const cookieHeader = headers.cookie || ''
|
||||
const cookies = parseCookies(cookieHeader)
|
||||
const cookieLanguage = cookies.LOCALE ? `${cookies.LOCALE}`.toLowerCase() : ''
|
||||
if (cookieLanguage && cookieLanguage !== '') {
|
||||
if (cookieLanguage && cookieLanguage !== '' && coveragesLocales.includes(cookieLanguage)) {
|
||||
finalLocale = cookieLanguage
|
||||
return finalLocale
|
||||
}
|
||||
@@ -116,21 +126,21 @@ export const ssrGetFinalLocale = (path = '', headers: any) => {
|
||||
const preferredLocale = getPreferredLanguage(acceptLanguage)
|
||||
if (preferredLocale) {
|
||||
// 선호 언어의 기본 코드와 일치하는 지원 로케일 찾기
|
||||
// const matchedLocale = coveragesLocales.find((locale: string) =>
|
||||
// preferredLocale.toLowerCase().startsWith(locale.toLowerCase())
|
||||
// )
|
||||
// if (matchedLocale) {
|
||||
// finalLocale = matchedLocale.toLowerCase()
|
||||
// return finalLocale
|
||||
// }
|
||||
const matchedLocale = coveragesLocales.find((locale: string) =>
|
||||
preferredLocale.toLowerCase().startsWith(locale.toLowerCase())
|
||||
)
|
||||
if (matchedLocale) {
|
||||
finalLocale = matchedLocale.toLowerCase()
|
||||
return finalLocale
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 서비스 기본 언어
|
||||
finalLocale = DEFAULT_LOCALE_CODE
|
||||
} catch (e) {
|
||||
finalLocale = DEFAULT_LOCALE_CODE
|
||||
finalLocale = defaultLocale
|
||||
} catch {
|
||||
finalLocale = defaultLocale
|
||||
}
|
||||
return finalLocale
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user