Files
web-temp/layers/components/layouts/Main.vue
2025-11-14 10:06:30 +09:00

132 lines
3.4 KiB
Vue

<script setup lang="ts">
import { useTemplateRegistry } from '#layers/composables/useTemplateRegistry'
import type {
PageDataValue,
PageDataTemplate,
PageDataMetaTag,
} from '#layers/types/api/pageData'
interface Props {
pageData: PageDataValue
}
const props = defineProps<Props>()
const mainContentRef = ref<HTMLElement>()
const { locale } = useI18n()
const { height: viewportH } = useWindowSize()
const { bottom: mainBottom } = useElementBounding(mainContentRef)
const { getTemplateComponent } = useTemplateRegistry()
const loadingStore = useLoadingStore()
const { isPAssApiLoading, hasApiCallStarted } = storeToRefs(loadingStore)
// 개별 메타 태그 표시 여부 확인
const shouldShowMetaTag = computed(() => props.pageData?.meta_tag_type === 2)
const pinToMain = computed(() => {
if (!mainBottom.value) return false
return mainBottom.value <= viewportH.value
})
// 템플릿 표시 여부 확인
const isTemplateVisible = (template: PageDataTemplate): boolean => {
return Boolean(
template?.page_ver_tmpl_json &&
Object.keys(template?.page_ver_tmpl_json).length > 0
)
}
// 템플릿 목록 계산
const visibleTemplates = computed(() =>
Object.values(props.pageData?.templates).filter(isTemplateVisible)
)
const isShowTopBtn = computed(() => props.pageData?.use_top_btn ?? false)
const isShowSnsBtn = computed(() => props.pageData?.use_sns_btn ?? false)
const isShowLnb = computed(() => props.pageData?.use_lnb ?? false)
// SEO 메타 태그 설정
const setupSeoMeta = (metaTag: PageDataMetaTag) => {
useSeoMeta({
title: metaTag?.page_title ?? '',
description: metaTag?.page_desc ?? '',
ogTitle: metaTag?.og_title ?? '',
ogDescription: metaTag?.og_desc ?? '',
ogImage: metaTag?.og_image ?? '',
twitterTitle: metaTag?.x_title ?? '',
twitterImage: metaTag?.x_image ?? '',
twitterDescription: metaTag?.x_desc ?? '',
})
}
provide('pinToMain', pinToMain)
// 메타 태그 설정 감시
watchEffect(() => {
if (shouldShowMetaTag.value && props.pageData?.meta_tag_json) {
setupSeoMeta(props.pageData?.meta_tag_json)
}
})
watch(isPAssApiLoading, newVal => {
if (newVal) {
loadingStore.stopFullLoading()
}
})
onMounted(() => {
const { sendLog } = useAnalytics()
sendLog(locale.value, useAnalyticsLogDataDirect('view', 1))
if (!hasApiCallStarted.value) {
loadingStore.stopFullLoading()
}
})
</script>
<template>
<div ref="mainContentRef" class="main-content">
<template
v-for="(template, index) in visibleTemplates"
:key="template.template_code ?? 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"
/>
</template>
</div>
<ClientOnly>
<BlocksLnb v-if="isShowLnb" />
<div
v-if="isShowTopBtn || isShowSnsBtn"
:class="['utile-wrap', { 'is-stop': pinToMain }]"
>
<BlocksButtonScrollTop v-if="isShowTopBtn" />
<BlocksButtonSns v-if="isShowSnsBtn" />
</div>
</ClientOnly>
</template>
<style scoped>
.main-content {
@apply relative pt-[48px] md:pt-[64px];
}
.utile-wrap {
@apply fixed flex flex-col z-[100]
bottom-[12px] right-[12px] gap-2 md:bottom-[40px] md:right-[40px] md:gap-3;
}
.utile-wrap.is-stop {
@apply absolute;
}
[data-theme='light'] {
.main-content {
@apply bg-theme-foreground;
}
}
</style>