diff --git a/app/app.vue b/app/app.vue index ed8813b..18f86a5 100644 --- a/app/app.vue +++ b/app/app.vue @@ -3,87 +3,130 @@ import { useNuxtApp } from 'nuxt/app' import type { GameDataMetaTag, GameDataValue, + GameDataKeyColors, GameDataImg, } from '#layers/types/api/gameData' const nuxtApp = useNuxtApp() const { locale } = useI18n() - const gameDataStore = useGameDataStore() const modalStore = useModalStore() const scrollStore = useScrollStore() const { setGameData } = gameDataStore -const { gameName, gaCode } = storeToRefs(gameDataStore) const { confirm, alert } = modalStore +const { gameName, gaCode } = storeToRefs(gameDataStore) const { scrollGnbPosition } = storeToRefs(scrollStore) -// 통합 메타데이터 설정 -const setupAllMetaData = (data: GameDataValue) => { - const meta = data.meta_tag_json ?? ({} as GameDataMetaTag) - const faviconPath = data.favicon_json ?? ({} as GameDataImg) - const theme = data.design_theme === 1 ? 'light' : 'dark' +// favicon 링크 생성 헬퍼 +const createStyleLinks = (faviconJson: GameDataImg, fontPath: string = '') => { + const links = [] + const iconUrl = faviconJson[0] + const appleTouchIconUrl = faviconJson[1] + const pngIconUrl = faviconJson[2] - // 파비콘 링크 생성 - const faviconLinks = [ - { + if (iconUrl) { + links.push({ rel: 'icon', type: 'image/x-icon', - href: formatPathHost(faviconPath[0]), - }, - { + href: formatPathHost(iconUrl), + }) + } + if (appleTouchIconUrl) { + links.push({ rel: 'apple-touch-icon', - href: formatPathHost(faviconPath[1]), - }, - { + href: formatPathHost(appleTouchIconUrl), + }) + } + if (pngIconUrl) { + links.push({ rel: 'icon', type: 'image/png', - href: formatPathHost(faviconPath[2]), + href: formatPathHost(pngIconUrl), + }) + } + if (fontPath) { + links.push({ + rel: 'stylesheet', + href: formatPathHost(fontPath), + }) + } + + return links +} + +// 메타 태그 생성 헬퍼 +const createMetaTags = (metaTag: Partial = {}) => { + const metaList = [ + { name: 'description', content: metaTag.page_desc }, + { property: 'og:title', content: metaTag.og_title }, + { property: 'og:description', content: metaTag.og_desc }, + { + property: 'og:image', + content: formatPathHost(metaTag.og_image), + }, + { name: 'twitter:title', content: metaTag.x_title }, + { name: 'twitter:description', content: metaTag.x_desc }, + { + name: 'twitter:image', + content: formatPathHost(metaTag.x_image), }, ] - // 색상 CSS 변수 생성 - const cssColorVariables = Object.entries(data.key_color_json ?? {}) + // content가 유효한 메타 태그만 필터링 + return metaList.filter( + meta => meta.content && String(meta.content).trim() !== '' + ) +} + +// CSS 변수 생성 헬퍼 +const createStyleCss = (keyColorJson: GameDataKeyColors) => { + const colorVariables = Object.entries(keyColorJson) + .filter(([key, value]) => key && value != null) .map(([key, value]) => `--${key}: ${value};`) .join('\n ') - const cssContent = ` - :root { - ${cssColorVariables} - } - ` + return `:root {${colorVariables}}` +} - useHead({ - title: meta?.page_title ?? '', - meta: [ - { name: 'description', content: meta.page_desc }, - { property: 'og:title', content: meta.og_title }, - { property: 'og:description', content: meta.og_desc }, - { property: 'og:image', content: formatPathHost(meta.og_image) }, - { name: 'twitter:title', content: meta.x_title }, - { name: 'twitter:description', content: meta.x_desc }, - { name: 'twitter:image', content: formatPathHost(meta.x_image) }, - ], - htmlAttrs: { - 'data-game': data.game_name || '', - 'data-theme': theme, - lang: locale.value ?? data.default_lang_code, - }, - link: faviconLinks, - style: [ - { - innerHTML: cssContent, - id: 'game-css-variables', +// 게임 헤드 설정 +const setupGameHead = (data: GameDataValue) => { + try { + const metaTag: Partial = data.meta_tag_json ?? {} + const designTheme = data.design_theme === 1 ? 'light' : 'dark' + const styleLinks = createStyleLinks( + data.favicon_json, + data?.game_font?.font_path + ) + const styleCss = createStyleCss(data.key_color_json) + + useHead({ + title: metaTag.page_title ?? '', + meta: createMetaTags(metaTag), + htmlAttrs: { + 'data-game': data.game_name ?? '', + 'data-theme': designTheme, + lang: locale.value ?? data.default_lang_code ?? 'ko', }, - ], - }) + link: styleLinks, + 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) - setupAllMetaData(gameData) + setupGameHead(gameData) } } diff --git a/app/pages/inspection/index.vue b/app/pages/inspection/index.vue index 4c55e25..86d570b 100644 --- a/app/pages/inspection/index.vue +++ b/app/pages/inspection/index.vue @@ -56,80 +56,24 @@ >
- - - - {{ tm('Inspection_Community_Btn') }} + {{ tm('Inspection_Community_Btn') }} - - -
- - {{ tm('Txt_Game_Start') }} - - - - - - - - {{ tm('Txt_Game_Start') }} - - - - - -
+ + + {{ tm('Txt_Game_Start') }} +
@@ -137,7 +81,6 @@

{{ @@ -147,18 +90,23 @@ }}

- - {{ getButtonText(btn.platform) }} - + + {{ getButtonText(market) }} + +
@@ -170,6 +118,8 @@ diff --git a/layers/components/blocks/slide/Arrows.vue b/layers/components/blocks/Button/SlideArrows.vue similarity index 100% rename from layers/components/blocks/slide/Arrows.vue rename to layers/components/blocks/Button/SlideArrows.vue diff --git a/layers/components/blocks/DatePicker.vue b/layers/components/blocks/DatePicker.vue index d68dc50..28de42d 100644 --- a/layers/components/blocks/DatePicker.vue +++ b/layers/components/blocks/DatePicker.vue @@ -365,7 +365,7 @@ onMounted(() => { > diff --git a/layers/components/blocks/modal/Alert.vue b/layers/components/blocks/modal/Alert.vue index 1658dbb..a99446a 100644 --- a/layers/components/blocks/modal/Alert.vue +++ b/layers/components/blocks/modal/Alert.vue @@ -42,11 +42,11 @@ const handleOutsideClick = () => { >

- {{ props.confirmButtonText || tm('Text_Confirm') }} - +
diff --git a/layers/components/blocks/modal/Confirm.vue b/layers/components/blocks/modal/Confirm.vue index 2438d77..6088e7d 100644 --- a/layers/components/blocks/modal/Confirm.vue +++ b/layers/components/blocks/modal/Confirm.vue @@ -42,17 +42,17 @@ const handleOutsideClick = () => { >

- {{ props.cancelButtonText || tm('Text_Cancel') }} - - + {{ props.confirmButtonText || tm('Text_Confirm') }} - +
diff --git a/layers/components/layouts/Header.vue b/layers/components/layouts/Header.vue index afa1d77..5ff43a4 100644 --- a/layers/components/layouts/Header.vue +++ b/layers/components/layouts/Header.vue @@ -374,7 +374,6 @@ onMounted(() => {
diff --git a/layers/components/widgets/slide/CenterHighlight.vue b/layers/components/widgets/slide/CenterHighlight.vue index e66efff..fcde0d8 100644 --- a/layers/components/widgets/slide/CenterHighlight.vue +++ b/layers/components/widgets/slide/CenterHighlight.vue @@ -118,7 +118,10 @@ const handleMove = ( - +
diff --git a/layers/components/widgets/slide/Default.vue b/layers/components/widgets/slide/Default.vue index 44c89ad..b2fc11e 100644 --- a/layers/components/widgets/slide/Default.vue +++ b/layers/components/widgets/slide/Default.vue @@ -98,6 +98,9 @@ const handleMove = ( - + diff --git a/layers/components/widgets/slide/Fade.vue b/layers/components/widgets/slide/Fade.vue index e35cfd2..4dc4136 100644 --- a/layers/components/widgets/slide/Fade.vue +++ b/layers/components/widgets/slide/Fade.vue @@ -92,7 +92,7 @@ defineExpose({ - { /> - + diff --git a/layers/middleware/init.route.global.ts b/layers/middleware/init.route.global.ts index 31887c2..d79e98e 100644 --- a/layers/middleware/init.route.global.ts +++ b/layers/middleware/init.route.global.ts @@ -6,7 +6,7 @@ export default defineNuxtRouteMiddleware(to => { if (to.path.includes('/error')) return // inspection 페이지는 실행X ----- - if (to.path.includes('inspection')) return + if (to.path.includes('/inspection')) return const gameDataStore = useGameDataStore() const { langCodes, intro } = storeToRefs(gameDataStore) diff --git a/layers/middleware/pageData.global.ts b/layers/middleware/pageData.global.ts index 9fd5fc0..a6e7662 100644 --- a/layers/middleware/pageData.global.ts +++ b/layers/middleware/pageData.global.ts @@ -14,7 +14,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => { if (to.path.includes('/error')) return // inspection 페이지는 실행X ----- - if (to.path.includes('inspection')) return + if (to.path.includes('/inspection')) return const gameDataStore = useGameDataStore() const pageDataStore = usePageDataStore() diff --git a/layers/server/middleware/gameData.ts b/layers/server/middleware/gameData.ts index 978901b..a1ac9b2 100644 --- a/layers/server/middleware/gameData.ts +++ b/layers/server/middleware/gameData.ts @@ -140,7 +140,6 @@ export default defineEventHandler(async event => { } const stoveApiServerBaseUrl = runtimeConfig.public.stoveApiUrlServer - const baseDomain = runtimeConfig.public.baseDomain let gameDataResponse: GameDataResponse | null = null let gameDataLangCodes: string[] | null = null @@ -182,7 +181,10 @@ export default defineEventHandler(async event => { // 1-1. 정적 파일 패스 if (isStaticFile(event.path)) return - // 1-2. 특정 경로 패스 (API, 리소스) + // 1-2. /inspection 패스 + if (fullPath.includes('/inspection')) return + + // 1-3. 특정 경로 패스 (API, 리소스) if (shouldSkipPath(fullPath)) return // 캐시 키 생성 (게임 ID 포함하여 충돌 방지) diff --git a/layers/stores/useGameDataStore.ts b/layers/stores/useGameDataStore.ts index d63c5fa..72fab36 100644 --- a/layers/stores/useGameDataStore.ts +++ b/layers/stores/useGameDataStore.ts @@ -6,17 +6,22 @@ export const useGameDataStore = defineStore('gameData', () => { gameId: null as GameDataValue['game_id'] | null, gameCode: null as GameDataValue['game_code'] | null, gameName: null as GameDataValue['game_name'] | null, + stoveGnbJson: null as GameDataValue['stove_gnb_json'] | null, langCodes: null as GameDataValue['lang_codes'] | null, defaultLangCode: null as GameDataValue['default_lang_code'] | null, gaCode: null as GameDataValue['ga_code'] | null, platformType: null as GameDataValue['platform_type'] | null, osType: null as GameDataValue['os_type'] | null, + theme: null as 'light' | 'dark' | null, intro: null as GameDataValue['intro'] | null, imgJson: null as GameDataValue['img_json'] | null, snsJson: null as GameDataValue['sns_json'] | null, urlJson: null as GameDataValue['url_json'] | null, + marketJson: null as GameDataValue['market_json'] | null, + fontFamily: null as GameDataValue['game_font']['font_family'] | null, gnb: null as GameDataValue['gnb'] | null, eventBanner: null as GameDataValue['event_banner'] | null, + fontFamily: null as GameDataValue['game_font']['font_family'] | null, }) const state = reactive(getInitialState()) @@ -26,17 +31,22 @@ export const useGameDataStore = defineStore('gameData', () => { state.gameId = data?.game_id state.gameCode = data?.game_code state.gameName = data?.game_name + state.stoveGnbJson = data?.stove_gnb_json state.langCodes = data?.lang_codes state.defaultLangCode = data?.default_lang_code state.gaCode = data?.ga_code state.platformType = data?.platform_type state.osType = data?.os_type + state.theme = data?.design_theme === 1 ? 'light' : 'dark' state.intro = data?.intro state.imgJson = data?.img_json state.snsJson = data?.sns_json state.urlJson = data?.url_json + state.marketJson = data?.market_json + state.fontFamily = data?.game_font?.font_family state.gnb = data?.gnb state.eventBanner = data?.event_banner + state.fontFamily = data?.game_font?.font_family } const clearGameData = () => { diff --git a/layers/templates/FxCoupon01/index.vue b/layers/templates/FxCoupon01/index.vue index 7acd69e..7d582bf 100644 --- a/layers/templates/FxCoupon01/index.vue +++ b/layers/templates/FxCoupon01/index.vue @@ -530,7 +530,7 @@ onMounted(async () => { /> {