Files
web-temp/app/app.vue
clkim 02ef9f9aa5 refactor. 게임 데이터 관련 상태 및 메타 태그 처리 개선
- 게임 데이터 스토어에서 상태 변수 이름 변경 및 추가
- 메타 태그 및 스타일 링크 생성 로직 간소화
- 코드 가독성 향상을 위한 함수 인자 제거
2026-03-18 16:35:27 +09:00

202 lines
5.0 KiB
Vue

<script setup lang="ts">
import { useNuxtApp } from 'nuxt/app'
const nuxtApp = useNuxtApp()
const { locale } = useI18n()
const gameDataStore = useGameDataStore()
const modalStore = useModalStore()
const scrollStore = useScrollStore()
const { setGameData } = gameDataStore
const { confirm, alert } = modalStore
const {
gameName,
gaCode,
gameTheme,
gameMetaTag,
faviconJson,
defaultLangCode,
gameFontJson,
keyColorJson,
} = storeToRefs(gameDataStore)
const { scrollGnbPosition } = storeToRefs(scrollStore)
// favicon 링크 생성 헬퍼
const createStyleLinks = () => {
const links = []
const iconUrl = faviconJson.value[0]
const appleTouchIconUrl = faviconJson.value[1]
const pngIconUrl = faviconJson.value[2]
const fontPath = gameFontJson.value?.font_path
if (iconUrl) {
links.push({
rel: 'icon',
type: 'image/x-icon',
href: formatPathHost(iconUrl),
})
}
if (appleTouchIconUrl) {
links.push({
rel: 'apple-touch-icon',
href: formatPathHost(appleTouchIconUrl),
})
}
if (pngIconUrl) {
links.push({
rel: 'icon',
type: 'image/png',
href: formatPathHost(pngIconUrl),
})
}
if (fontPath) {
links.push({
rel: 'stylesheet',
href: formatPathHost(fontPath),
})
}
return links
}
// 메타 태그 생성 헬퍼
const createMetaTags = () => {
if (!gameMetaTag.value) return []
const metaList = [
{ name: 'description', content: gameMetaTag.value.page_desc },
{ property: 'og:title', content: gameMetaTag.value.og_title },
{ property: 'og:description', content: gameMetaTag.value.og_desc },
{
property: 'og:image',
content: formatPathHost(gameMetaTag.value.og_image),
},
{ name: 'twitter:title', content: gameMetaTag.value.x_title },
{ name: 'twitter:description', content: gameMetaTag.value.x_desc },
{
name: 'twitter:image',
content: formatPathHost(gameMetaTag.value.x_image),
},
]
// content가 유효한 메타 태그만 필터링
return metaList.filter(
meta => meta.content && String(meta.content).trim() !== ''
)
}
// CSS 변수 생성 헬퍼
const createStyleCss = () => {
const colorVariables = Object.entries(keyColorJson.value)
.filter(([key, value]) => key && value != null)
.map(([key, value]) => `--${key}: ${value};`)
.join('\n ')
return `:root {${colorVariables}}`
}
// 게임 헤드 설정
const setupGameHead = () => {
if (!gameMetaTag.value) return
try {
const styleCss = createStyleCss()
useHead({
title: gameMetaTag.value?.page_title ?? '',
meta: createMetaTags(),
htmlAttrs: {
'data-game': gameName.value ?? '',
'data-theme': gameTheme.value,
lang: locale.value ?? defaultLangCode.value ?? 'ko',
},
link: createStyleLinks(),
style: [
{
innerHTML: styleCss,
id: 'game-css-variables',
},
],
})
} catch (error) {
// eslint-disable-next-line no-console
console.error('[setupGameHead] Failed to setup game head:', error)
}
}
if (import.meta.server) {
const gameData = nuxtApp.ssrContext?.event?.context?.gameData
if (gameData) {
setGameData(gameData)
}
}
setupGameHead()
let rafId: number | null = null
onMounted(() => {
useEventListener('scroll', scrollStore.updateScrollValue, { passive: true })
const { gtag, initialize } = useGtag()
initialize(gaCode.value)
gtag('event', 'screen_view', {
app_name: 'My App',
screen_name: 'Home',
})
watch(
scrollGnbPosition,
newValue => {
if (rafId) cancelAnimationFrame(rafId)
rafId = requestAnimationFrame(() => {
document.documentElement.style.setProperty(
'--scroll-stove-position',
`${newValue}px`
)
rafId = null
})
},
{ immediate: true }
)
})
onBeforeUnmount(() => {
// requestAnimationFrame 정리
if (rafId) cancelAnimationFrame(rafId)
})
</script>
<template>
<h1 class="sr-only">{{ gameName }}</h1>
<NuxtPage />
<!-- 공통 모달 컴포넌트 -->
<WidgetsModalClient />
<BlocksModalYouTube />
<BlocksModalContent />
<BlocksModalConfirm
v-model:is-open="confirm.storeIsOpen"
:is-show-dimmed="confirm.storeIsShowDimmed"
:content-text="confirm.storeContentText"
:confirm-button-text="confirm.storeConfirmButtonText"
:cancel-button-text="confirm.storeCancelButtonText"
:is-outside-close="confirm.storeIsOutsideClose"
:modal-name="confirm.storeModalName"
@confirm-button-event="confirm.storeConfirmButtonEvent"
@cancel-button-event="confirm.storeCancelButtonEvent"
/>
<BlocksModalAlert
v-model:is-open="alert.storeIsOpen"
:is-show-dimmed="alert.storeIsShowDimmed"
:content-text="alert.storeContentText"
:confirm-button-text="alert.storeConfirmButtonText"
:is-outside-close="alert.storeIsOutsideClose"
:modal-name="alert.storeModalName"
@confirm-button-event="alert.storeConfirmButtonEvent"
/>
<BlocksModalToast />
<!-- 로딩 컴포넌트 -->
<AtomsLoadingFull />
<AtomsLoadingLocal />
</template>