Files
web-temp/layers/components/layouts/Main.vue
clkim 7a3be1754f refactor. SEO 메타 태그 처리 로직 개선 및 불필요한 코드 제거
- app.vue에서 SEO 메타 태그 생성 헬퍼 함수 제거
- Main.vue에서 SEO 메타 태그 설정 일원화
2026-03-25 13:57:30 +09:00

135 lines
3.7 KiB
Vue

<script setup lang="ts">
import { useTemplateRegistry } from '#layers/composables/useTemplateRegistry'
import { useGameDataStore } from '#layers/stores/useGameDataStore'
import type {
PageDataMetaTag,
PageDataTemplate,
} from '#layers/types/api/pageData'
const { tm, locale } = useI18n()
const { getTemplateComponent } = useTemplateRegistry()
const loadingStore = useLoadingStore()
const pageDataStore = usePageDataStore()
const gameDataStore = useGameDataStore()
const modalStore = useModalStore()
const { isPAssApiLoading, hasApiCallStarted } = storeToRefs(loadingStore)
const { gameMetaTag } = storeToRefs(gameDataStore)
const {
pageTemplates,
pageMetaTag,
isUseMetaTag,
isUseTopBtn,
isUseSnsBtn,
isUseLnb,
isLoginRequired,
} = storeToRefs(pageDataStore)
const getTemplateKey = (template: PageDataTemplate, index: number) => {
return `${template.page_ver_tmpl_name_en}-${Number.isFinite(Number(template.tmpl_sort_order)) ? Number(template.tmpl_sort_order) : index}`
}
// SEO 메타 태그 설정
const buildSeoMetaPayload = (metaTag: PageDataMetaTag) => ({
title: metaTag?.page_title ?? '',
description: metaTag?.page_desc ?? '',
ogTitle: metaTag?.og_title ?? '',
ogDescription: metaTag?.og_desc ?? '',
ogImage: formatPathHost(metaTag?.og_image) ?? '',
twitterTitle: metaTag?.x_title ?? '',
twitterImage: formatPathHost(metaTag?.x_image) ?? '',
twitterDescription: metaTag?.x_desc ?? '',
})
// SEO 메타 태그 설정
const setupSeoMeta = (metaTag: PageDataMetaTag) => {
useSeoMeta(buildSeoMetaPayload(metaTag))
}
// 메타 태그 설정 감시
let lastSeoMetaFingerprint = ''
watch(
[gameMetaTag, isUseMetaTag, pageMetaTag],
([gameMeta, usePageMeta, pageMeta]) => {
// 개별 메타 사용 페이지면 pageMeta로, 아니면 게임 메타로 fallback
const metaToUse = usePageMeta && pageMeta ? pageMeta : gameMeta
if (!metaToUse) return
const fingerprint = JSON.stringify(buildSeoMetaPayload(metaToUse))
if (fingerprint === lastSeoMetaFingerprint) return
lastSeoMetaFingerprint = fingerprint
setupSeoMeta(metaToUse)
},
{ immediate: true }
)
watch(isPAssApiLoading, newVal => {
if (newVal) {
loadingStore.stopFullLoading()
}
})
onMounted(() => {
const { sendLog } = useAnalytics()
sendLog(locale.value, 'view')
if (!hasApiCallStarted.value) {
loadingStore.stopFullLoading()
}
// 페이지 접근 권한 설정(로그인 유무)
if (isLoginRequired.value && !csrGetAccessToken()) {
modalStore.handleOpenConfirm({
contentText: tm('Alert_StoveLogin'),
confirmButtonText: tm('Text_StoveLogin'),
confirmButtonEvent: () => {
csrGoStoveLogin()
},
})
}
})
</script>
<template>
<div class="content-wrap">
<template
v-for="(template, index) in pageTemplates"
:key="getTemplateKey(template, index)"
>
<component
:is="getTemplateComponent(template.template_code)"
:id="template.page_ver_tmpl_name_en"
:components="template.page_ver_tmpl_json"
:page-ver-tmpl-seq="template.page_ver_tmpl_seq"
:page-ver-tmpl-name-en="template.page_ver_tmpl_name_en"
/>
</template>
</div>
<ClientOnly>
<BlocksLnb v-if="isUseLnb" />
<div v-if="isUseTopBtn" class="utile-wrap">
<BlocksButtonScrollTop v-if="isUseTopBtn" />
</div>
</ClientOnly>
<BlocksSns v-if="isUseSnsBtn" />
</template>
<style scoped>
.empty-game + main .content-wrap {
@apply pt-0;
}
.content-wrap {
@apply relative pt-[48px] md:pt-[64px];
}
.utile-wrap {
@apply fixed flex flex-col items-end z-[100]
bottom-[12px] right-[12px] gap-2 md:bottom-[40px] md:right-[40px] md:gap-3;
}
[data-theme='light'] {
.content-wrap {
@apply bg-theme-foreground;
}
}
</style>