feat. 일반 템플릿 로그 추가
This commit is contained in:
@@ -18,27 +18,25 @@
|
||||
background-color: var(--pagination-active);
|
||||
}
|
||||
|
||||
.splide-arrows {
|
||||
.splide__arrows {
|
||||
@apply hidden md:block;
|
||||
}
|
||||
.splide-arrow {
|
||||
.splide__arrows .splide-arrow.btn-circle {
|
||||
@apply absolute top-1/2 w-[40px] h-[40px] bg-cover bg-center bg-no-repeat -translate-y-1/2 cursor-pointer z-[5]
|
||||
md:w-[48px] md:h-[48px]
|
||||
after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:rounded-full after:bg-white after:transition-opacity after:duration-300 after:ease-in-out after:opacity-0
|
||||
hover:after:opacity-10;
|
||||
md:w-[48px] md:h-[48px];
|
||||
}
|
||||
.arrow-prev {
|
||||
.splide__arrow--prev {
|
||||
@apply bg-[image:var(--arrow-prev)];
|
||||
}
|
||||
.arrow-next {
|
||||
.splide__arrow--next {
|
||||
@apply bg-[image:var(--arrow-next)];
|
||||
}
|
||||
|
||||
.type-full .arrow-prev {
|
||||
.type-full .splide__arrow--prev {
|
||||
@apply left-10;
|
||||
}
|
||||
|
||||
.type-full .arrow-next {
|
||||
.type-full .splide__arrow--next {
|
||||
@apply right-10;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits<{
|
||||
(e: 'click'): void
|
||||
}>()
|
||||
import type { TrackingObject } from '#layers/types/api/common'
|
||||
|
||||
interface Props {
|
||||
tracking: TrackingObject
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
|
||||
const handlePlayClick = () => {
|
||||
sendLog(locale.value, useAnalyticsData(props.tracking))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button class="btn-play" @click="emit('click')">
|
||||
<button class="btn-play" @click="handlePlayClick">
|
||||
<span class="sr-only">Play</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -3,6 +3,7 @@ interface Props {
|
||||
title: string
|
||||
description: string | number
|
||||
imgPath: string | null
|
||||
analyticsSarea: string
|
||||
linkTarget?: '_blank' | '_self'
|
||||
url?: string
|
||||
alt?: string
|
||||
@@ -10,8 +11,9 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
|
||||
const isNoImage = computed(() => {
|
||||
return !props.imgPath || props.imgPath === null
|
||||
@@ -21,14 +23,12 @@ const isShowOverlay = computed(() => {
|
||||
})
|
||||
|
||||
const handleLinkClick = (title: string) => {
|
||||
const trackingData = {
|
||||
tracking: {
|
||||
click_item: title,
|
||||
action_type: 'click',
|
||||
click_sarea: '',
|
||||
},
|
||||
const analytics = {
|
||||
click_item: title,
|
||||
action_type: 'click',
|
||||
click_sarea: props.analyticsSarea,
|
||||
}
|
||||
sendLog(locale.value, useAnalyticsLogDataDirect(trackingData, 1))
|
||||
sendLog(locale.value, useAnalyticsData(analytics))
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
37
layers/components/blocks/slide/Arrows.vue
Normal file
37
layers/components/blocks/slide/Arrows.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import type { PageDataResourceGroups } from '#layers/types/api/pageData'
|
||||
|
||||
interface Props {
|
||||
arrowsData?: PageDataResourceGroups
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
|
||||
const handleArrowClick = (direction: 'prev' | 'next') => {
|
||||
if (props.arrowsData) {
|
||||
const arrowIndex = direction === 'prev' ? 0 : 1
|
||||
sendLog(
|
||||
locale.value,
|
||||
useAnalyticsData(props.arrowsData[arrowIndex]?.tracking)
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="splide__arrows">
|
||||
<AtomsButtonCircle
|
||||
sr-only="Previous"
|
||||
class="splide-arrow splide__arrow--prev"
|
||||
@click="handleArrowClick('prev')"
|
||||
/>
|
||||
<AtomsButtonCircle
|
||||
sr-only="Next"
|
||||
class="splide-arrow splide__arrow--next"
|
||||
@click="handleArrowClick('next')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -9,15 +9,16 @@ import type {
|
||||
} from '#layers/types/api/gameData'
|
||||
|
||||
const MORE_WIDTH = 72
|
||||
const START_WIDTH_MARGIN = 40
|
||||
const START_MARGIN = 40
|
||||
|
||||
const route = useRoute()
|
||||
const { tm } = useI18n()
|
||||
const { locale, tm } = useI18n()
|
||||
const { width } = useWindowSize()
|
||||
const gameDataStore = useGameDataStore()
|
||||
const pageDataStore = usePageDataStore()
|
||||
const scrollStore = useScrollStore()
|
||||
const breakpoints = useResponsiveBreakpoints()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
const { pageLayoutType } = storeToRefs(pageDataStore)
|
||||
@@ -95,7 +96,7 @@ const calculateOverflow = () => {
|
||||
}
|
||||
|
||||
const screenWidth = width.value
|
||||
const totalNavWidth = navWidth.value + startWidth.value + START_WIDTH_MARGIN
|
||||
const totalNavWidth = navWidth.value + startWidth.value + START_MARGIN
|
||||
|
||||
// 해상도가 navWidth + startWidth보다 작은 경우
|
||||
if (screenWidth < totalNavWidth) {
|
||||
@@ -120,18 +121,6 @@ const calculateOverflow = () => {
|
||||
// 100ms마다 최대 1회 실행
|
||||
const throttledCalculateOverflow = useThrottleFn(calculateOverflow, 100)
|
||||
|
||||
const handleMenuOpen = () => {
|
||||
isMenuOpen.value = true
|
||||
scrollStore.controlScrollLock(true)
|
||||
}
|
||||
|
||||
const handleMenuClose = (isPassing: boolean = false) => {
|
||||
if (isPassing) return
|
||||
|
||||
isMenuOpen.value = false
|
||||
scrollStore.controlScrollLock(false)
|
||||
}
|
||||
|
||||
const isNotClickable = (gnbItem: GameDataMenu) => {
|
||||
return gnbItem.click_action_type === 0
|
||||
}
|
||||
@@ -140,6 +129,32 @@ const has2depthButton = (gnbItem: GameDataMenu) => {
|
||||
return gnbItem.children && Object.keys(gnbItem.children).length > 0
|
||||
}
|
||||
|
||||
const handleMenuOpen = () => {
|
||||
isMenuOpen.value = true
|
||||
scrollStore.controlScrollLock(true)
|
||||
}
|
||||
|
||||
const handleMenuClose = () => {
|
||||
isMenuOpen.value = false
|
||||
scrollStore.controlScrollLock(false)
|
||||
}
|
||||
|
||||
const handleSendLog = (item: string) => {
|
||||
const analytics = {
|
||||
action_type: 'click',
|
||||
click_item: item,
|
||||
click_sarea: 'GNB',
|
||||
}
|
||||
sendLog(locale.value, useAnalyticsData(analytics))
|
||||
}
|
||||
|
||||
const handleGnbItemClick = (gnbItem: GameDataMenu) => {
|
||||
if (isNotClickable(gnbItem)) return
|
||||
|
||||
handleMenuClose()
|
||||
sendLog(locale.value, useAnalyticsData(gnbItem.tracking_json))
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
overflowCount.value = 0
|
||||
isMounted.value = true
|
||||
@@ -170,7 +185,11 @@ onMounted(() => {
|
||||
v-if="gnbData"
|
||||
:class="['game-wrap', { 'is-fixed': isPassedStoveGnb }]"
|
||||
>
|
||||
<AtomsLocaleLink to="/home" class="mx-auto md:hidden">
|
||||
<AtomsLocaleLink
|
||||
to="/home"
|
||||
class="mx-auto md:hidden"
|
||||
@click="handleSendLog('BI')"
|
||||
>
|
||||
<img
|
||||
:src="formatPathHost(gnbData?.bi_path)"
|
||||
:alt="gameData?.game_name"
|
||||
@@ -183,11 +202,14 @@ onMounted(() => {
|
||||
</button>
|
||||
<div
|
||||
:class="['nav-wrap', { 'is-open': isMenuOpen }]"
|
||||
@click="handleMenuClose()"
|
||||
@click="handleMenuClose"
|
||||
>
|
||||
<div ref="navAreaRef" class="nav-area" @click.stop>
|
||||
<div class="nav-logo">
|
||||
<AtomsLocaleLink to="/home" @click="handleMenuClose">
|
||||
<AtomsLocaleLink
|
||||
to="/home"
|
||||
@click="[handleMenuClose(), handleSendLog('BI')]"
|
||||
>
|
||||
<img
|
||||
:src="formatPathHost(gnbData?.bi_path)"
|
||||
:alt="gameData?.game_name"
|
||||
@@ -219,7 +241,7 @@ onMounted(() => {
|
||||
'router-link-active': hasActiveChild(gnbItem.children),
|
||||
},
|
||||
]"
|
||||
@click="handleMenuClose(isNotClickable(gnbItem))"
|
||||
@click="handleGnbItemClick(gnbItem)"
|
||||
>
|
||||
<span>{{ gnbItem.menu_name }}</span>
|
||||
<AtomsIconsWebLinkLine
|
||||
@@ -248,7 +270,7 @@ onMounted(() => {
|
||||
:to="child.url_path"
|
||||
:target="child.link_target"
|
||||
class="item-link"
|
||||
@click="handleMenuClose(isNotClickable(child))"
|
||||
@click="handleGnbItemClick(child)"
|
||||
>
|
||||
<span>{{ child.menu_name }}</span>
|
||||
<AtomsIconsWebLinkLine
|
||||
@@ -293,7 +315,7 @@ onMounted(() => {
|
||||
),
|
||||
},
|
||||
]"
|
||||
@click="handleMenuClose(isNotClickable(gnbItem))"
|
||||
@click="handleGnbItemClick(gnbItem)"
|
||||
>
|
||||
<span>{{ gnbItem.menu_name }}</span>
|
||||
</component>
|
||||
@@ -340,7 +362,7 @@ onMounted(() => {
|
||||
'nav-1depth',
|
||||
{ 'router-link-active': pageLayoutType === 'promotion' },
|
||||
]"
|
||||
@click="handleMenuClose"
|
||||
@click="[handleMenuClose(), handleSendLog('이벤트')]"
|
||||
>
|
||||
<span
|
||||
class="flex items-center gap-1 flex-1 text-gradient-pink"
|
||||
@@ -367,6 +389,9 @@ onMounted(() => {
|
||||
:text-color="
|
||||
getColorCodeFromData(start1depthData?.btn_info, 'txt')
|
||||
"
|
||||
@click="
|
||||
sendLog(locale, useAnalyticsData(start1depthData.tracking))
|
||||
"
|
||||
>
|
||||
{{ start1depthData?.btn_info?.txt_btn_name }}
|
||||
</BlocksButtonLauncher>
|
||||
@@ -376,7 +401,13 @@ onMounted(() => {
|
||||
>
|
||||
<ul>
|
||||
<li v-for="(item, key) in start2depthData" :key="key">
|
||||
<BlocksButtonLauncher type="custom" :platform="key">
|
||||
<BlocksButtonLauncher
|
||||
type="custom"
|
||||
:platform="key"
|
||||
@click="
|
||||
sendLog(locale, useAnalyticsData(item.tracking))
|
||||
"
|
||||
>
|
||||
{{ item.btn_info?.txt_btn_name }}
|
||||
</BlocksButtonLauncher>
|
||||
</li>
|
||||
@@ -385,7 +416,7 @@ onMounted(() => {
|
||||
</template>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
<button class="btn-close" @click="handleMenuClose()">
|
||||
<button class="btn-close" @click="handleMenuClose">
|
||||
<AtomsIconsCloseLine
|
||||
size="24"
|
||||
color="var(--foreground-reversal)"
|
||||
|
||||
@@ -75,7 +75,7 @@ watch(isPAssApiLoading, newVal => {
|
||||
|
||||
onMounted(() => {
|
||||
const { sendLog } = useAnalytics()
|
||||
sendLog(locale.value, useAnalyticsLogDataDirect('view', 1))
|
||||
sendLog(locale.value, useAnalyticsData('view'))
|
||||
|
||||
if (!hasApiCallStarted.value) {
|
||||
loadingStore.stopFullLoading()
|
||||
@@ -94,6 +94,7 @@ onMounted(() => {
|
||||
: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>
|
||||
|
||||
@@ -7,7 +7,6 @@ import type { ButtonType } from '#layers/types/components/button'
|
||||
|
||||
interface Props {
|
||||
resourcesData: PageDataResourceGroup[]
|
||||
pageVerTmplSeq: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -16,7 +15,7 @@ const { locale } = useI18n()
|
||||
const modalStore = useModalStore()
|
||||
const scrollStore = useScrollStore()
|
||||
const breakpoints = useResponsiveBreakpoints()
|
||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
const { tm } = useI18n()
|
||||
const device = useDevice()
|
||||
|
||||
@@ -92,8 +91,7 @@ const downloadFile = async (url: string = '', osType: number = 0) => {
|
||||
}
|
||||
|
||||
const handleButtonClick = (button: PageDataResourceGroup) => {
|
||||
// 로그
|
||||
sendLog(locale.value, useAnalyticsLogDataDirect(button, props.pageVerTmplSeq))
|
||||
sendLog(locale.value, useAnalyticsData(button.tracking))
|
||||
|
||||
const btnDetail = button.btn_info?.detail
|
||||
|
||||
|
||||
@@ -3,27 +3,22 @@ import type { PageDataResourceGroup } from '#layers/types/api/pageData'
|
||||
|
||||
const props = defineProps<{
|
||||
resourcesData: PageDataResourceGroup
|
||||
pageVerTmplSeq: number
|
||||
}>()
|
||||
|
||||
const modalStore = useModalStore()
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||
|
||||
// 비디오 플레이 버튼 클릭 핸들러
|
||||
const handleVideoPlayClick = () => {
|
||||
const youtubeUrl = props.resourcesData?.display?.text ?? ''
|
||||
if (youtubeUrl) {
|
||||
modalStore.handleOpenYoutube({ youtubeUrl })
|
||||
|
||||
sendLog(
|
||||
locale.value,
|
||||
useAnalyticsLogDataDirect(props.resourcesData, props.pageVerTmplSeq)
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AtomsButtonPlay @click="handleVideoPlayClick" />
|
||||
<AtomsButtonPlay
|
||||
:tracking="props.resourcesData.tracking"
|
||||
@click="handleVideoPlayClick"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { Splide } from '@splidejs/vue-splide'
|
||||
import { Splide, SplideTrack } from '@splidejs/vue-splide'
|
||||
import type { Splide as SplideType, ResponsiveOptions } from '@splidejs/splide'
|
||||
import type { SlideItemSize } from '#layers/types/components/slide'
|
||||
import { useSplideArrow } from '#layers/composables/useSplideArrow'
|
||||
import type { PageDataResourceGroups } from '#layers/types/api/pageData'
|
||||
|
||||
interface Props {
|
||||
slideItemSize: SlideItemSize
|
||||
@@ -10,6 +10,7 @@ interface Props {
|
||||
autoplay?: boolean
|
||||
interval?: number
|
||||
arrows?: boolean
|
||||
arrowsData?: PageDataResourceGroups
|
||||
pagination?: boolean
|
||||
class?: string
|
||||
}
|
||||
@@ -21,13 +22,9 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
pagination: true,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['mounted', 'move', 'arrowClick'])
|
||||
const emit = defineEmits(['mounted', 'move'])
|
||||
|
||||
const splideIndex = defineModel<number>('index', { required: false })
|
||||
|
||||
// Splide 화살표 로직을 위한 composable 사용
|
||||
const { addArrowClickListeners } = useSplideArrow()
|
||||
|
||||
const isMultipleItems = computed(() => (props.slideItemLength ?? 0) > 1)
|
||||
|
||||
const options = computed((): ResponsiveOptions => {
|
||||
@@ -45,10 +42,6 @@ const options = computed((): ResponsiveOptions => {
|
||||
interval: props.interval,
|
||||
flickPower: 300,
|
||||
classes: {
|
||||
arrows: 'splide-arrows',
|
||||
arrow: 'splide-arrow',
|
||||
prev: 'arrow-prev',
|
||||
next: 'arrow-next',
|
||||
pagination: 'splide-pagination-bullets',
|
||||
page: 'splide-pagination-bullet',
|
||||
},
|
||||
@@ -95,13 +88,6 @@ const handleSplideMounted = (splide: SplideType) => {
|
||||
if (splideIndex.value !== undefined) {
|
||||
splideIndex.value = splide.index
|
||||
}
|
||||
|
||||
// 화살표 버튼 클릭 이벤트 리스너 추가
|
||||
nextTick(() => {
|
||||
addArrowClickListeners(splide, (direction, targetIndex) => {
|
||||
emit('arrowClick', direction, targetIndex)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const handleMove = (
|
||||
@@ -122,10 +108,14 @@ const handleMove = (
|
||||
<div :class="`center-focus ${props.class || ''}`" :style="style">
|
||||
<Splide
|
||||
:options="options"
|
||||
:has-track="false"
|
||||
@splide:mounted="handleSplideMounted"
|
||||
@splide:move="handleMove"
|
||||
>
|
||||
<slot />
|
||||
<SplideTrack>
|
||||
<slot />
|
||||
</SplideTrack>
|
||||
<BlocksSlideArrows v-if="props.arrows" :arrows-data="props.arrowsData" />
|
||||
</Splide>
|
||||
</div>
|
||||
</template>
|
||||
@@ -192,10 +182,10 @@ const handleMove = (
|
||||
width: var(--banner-width-pc-active);
|
||||
height: var(--banner-height-pc-active);
|
||||
}
|
||||
.center-focus:deep(.arrow-prev) {
|
||||
.center-focus:deep(.splide__arrow--prev) {
|
||||
left: 28px;
|
||||
}
|
||||
.center-focus:deep(.arrow-next) {
|
||||
.center-focus:deep(.splide__arrow--next) {
|
||||
right: 28px;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { Splide } from '@splidejs/vue-splide'
|
||||
import { Splide, SplideTrack } from '@splidejs/vue-splide'
|
||||
import type { Splide as SplideType, ResponsiveOptions } from '@splidejs/splide'
|
||||
import type { SlideItemSize } from '#layers/types/components/slide'
|
||||
import type { PageDataResourceGroups } from '#layers/types/api/pageData'
|
||||
|
||||
interface Props {
|
||||
slideItemSize: SlideItemSize
|
||||
@@ -9,6 +10,7 @@ interface Props {
|
||||
autoplay?: boolean
|
||||
interval?: number
|
||||
arrows?: boolean
|
||||
arrowsData?: PageDataResourceGroups
|
||||
pagination?: boolean
|
||||
class?: string
|
||||
}
|
||||
@@ -20,13 +22,9 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
pagination: true,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['mounted', 'move', 'arrowClick'])
|
||||
const emit = defineEmits(['mounted', 'move'])
|
||||
|
||||
const splideIndex = defineModel<number>('index', { required: false })
|
||||
|
||||
// Splide 화살표 로직을 위한 composable 사용
|
||||
const { addArrowClickListeners } = useSplideArrow()
|
||||
|
||||
const isMultipleItems = computed(() => (props.slideItemLength ?? 0) > 1)
|
||||
|
||||
const options = computed((): ResponsiveOptions => {
|
||||
@@ -44,10 +42,6 @@ const options = computed((): ResponsiveOptions => {
|
||||
interval: props.interval,
|
||||
flickPower: 300,
|
||||
classes: {
|
||||
arrows: 'splide-arrows',
|
||||
arrow: 'splide-arrow',
|
||||
prev: 'arrow-prev',
|
||||
next: 'arrow-next',
|
||||
pagination: 'splide-pagination-bullets',
|
||||
page: 'splide-pagination-bullet',
|
||||
},
|
||||
@@ -97,13 +91,6 @@ const handleSplideMounted = (splide: SplideType) => {
|
||||
if (splideIndex.value !== undefined) {
|
||||
splideIndex.value = splide.index
|
||||
}
|
||||
|
||||
// 화살표 버튼 클릭 이벤트 리스너 추가
|
||||
nextTick(() => {
|
||||
addArrowClickListeners(splide, (direction, targetIndex) => {
|
||||
emit('arrowClick', direction, targetIndex)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const handleMove = (
|
||||
@@ -124,10 +111,14 @@ const handleMove = (
|
||||
<div :class="`center-highlight ${props.class || ''}`" :style="style">
|
||||
<Splide
|
||||
:options="options"
|
||||
:has-track="false"
|
||||
@splide:mounted="handleSplideMounted"
|
||||
@splide:move="handleMove"
|
||||
>
|
||||
<slot />
|
||||
<SplideTrack>
|
||||
<slot />
|
||||
</SplideTrack>
|
||||
<BlocksSlideArrows v-if="props.arrows" :arrows-data="props.arrowsData" />
|
||||
</Splide>
|
||||
</div>
|
||||
</template>
|
||||
@@ -179,10 +170,10 @@ const handleMove = (
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.center-highlight:deep(.arrow-prev) {
|
||||
.center-highlight:deep(.splide__arrow--prev) {
|
||||
margin-left: calc(-1 * var(--banner-arrow-pc));
|
||||
}
|
||||
.center-highlight:deep(.arrow-next) {
|
||||
.center-highlight:deep(.splide__arrow--next) {
|
||||
margin-left: var(--banner-arrow-pc);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Splide } from '@splidejs/vue-splide'
|
||||
import { Splide, SplideTrack } from '@splidejs/vue-splide'
|
||||
import type { Splide as SplideType, ResponsiveOptions } from '@splidejs/splide'
|
||||
import type { PageDataResourceGroups } from '#layers/types/api/pageData'
|
||||
|
||||
@@ -12,6 +12,7 @@ interface Props {
|
||||
perPage?: number
|
||||
drag?: boolean
|
||||
arrows?: boolean
|
||||
arrowsData?: PageDataResourceGroups
|
||||
pagination?: boolean
|
||||
paginationData?: PageDataResourceGroups
|
||||
destroy?: boolean
|
||||
@@ -29,13 +30,10 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
destroy: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['mounted', 'move', 'arrowClick'])
|
||||
const emit = defineEmits(['mounted', 'move'])
|
||||
|
||||
const splideIndex = defineModel<number>('index', { required: false })
|
||||
|
||||
// Splide 화살표 로직을 위한 composable 사용
|
||||
const { addArrowClickListeners } = useSplideArrow()
|
||||
|
||||
const slideType = computed(() => {
|
||||
if (props.type) return props.type
|
||||
|
||||
@@ -60,10 +58,6 @@ const options = computed((): ResponsiveOptions => {
|
||||
destroy: props.destroy,
|
||||
flickPower: 300,
|
||||
classes: {
|
||||
arrows: 'splide-arrows',
|
||||
arrow: 'splide-arrow',
|
||||
prev: 'arrow-prev',
|
||||
next: 'arrow-next',
|
||||
pagination: 'splide-pagination-bullets',
|
||||
page: 'splide-pagination-bullet',
|
||||
},
|
||||
@@ -77,13 +71,6 @@ const handleSplideMounted = (splide: SplideType) => {
|
||||
if (splideIndex.value !== undefined) {
|
||||
splideIndex.value = splide.index
|
||||
}
|
||||
|
||||
// 화살표 버튼 클릭 이벤트 리스너 추가
|
||||
nextTick(() => {
|
||||
addArrowClickListeners(splide, (direction, targetIndex) => {
|
||||
emit('arrowClick', direction, targetIndex)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const handleMove = (
|
||||
@@ -103,10 +90,14 @@ const handleMove = (
|
||||
<template>
|
||||
<Splide
|
||||
:options="options"
|
||||
:has-track="false"
|
||||
:style="getPaginationClass(props?.paginationData)"
|
||||
@splide:mounted="handleSplideMounted"
|
||||
@splide:move="handleMove"
|
||||
>
|
||||
<slot />
|
||||
<SplideTrack>
|
||||
<slot />
|
||||
</SplideTrack>
|
||||
<BlocksSlideArrows v-if="props.arrows" :arrows-data="props.arrowsData" />
|
||||
</Splide>
|
||||
</template>
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Splide } from '@splidejs/vue-splide'
|
||||
import { useSplideArrow } from '#layers/composables/useSplideArrow'
|
||||
import { Splide, SplideTrack } from '@splidejs/vue-splide'
|
||||
import { getPaginationClass } from '#layers/utils/styleUtil'
|
||||
import type { Splide as SplideType, ResponsiveOptions } from '@splidejs/splide'
|
||||
import type { PageDataResourceGroups } from '#layers/types/api/pageData'
|
||||
@@ -10,6 +9,7 @@ interface Props {
|
||||
autoplay?: boolean
|
||||
interval?: number
|
||||
arrows?: boolean
|
||||
arrowsData?: PageDataResourceGroups
|
||||
pagination?: boolean
|
||||
paginationData?: PageDataResourceGroups
|
||||
}
|
||||
@@ -22,13 +22,11 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
pagination: true,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['mounted', 'move', 'arrowClick'])
|
||||
const emit = defineEmits(['mounted', 'move'])
|
||||
|
||||
const splideIndex = defineModel<number>('index', { required: false })
|
||||
|
||||
const splideRef = ref()
|
||||
// Splide 화살표 로직을 위한 composable 사용
|
||||
const { addArrowClickListeners } = useSplideArrow()
|
||||
|
||||
const options = computed((): ResponsiveOptions => {
|
||||
return {
|
||||
@@ -48,10 +46,6 @@ const options = computed((): ResponsiveOptions => {
|
||||
pagination: props.pagination,
|
||||
flickPower: 300,
|
||||
classes: {
|
||||
arrows: 'splide-arrows type-full',
|
||||
arrow: 'splide-arrow',
|
||||
prev: 'arrow-prev',
|
||||
next: 'arrow-next',
|
||||
pagination: 'splide-pagination-bullets type-full',
|
||||
page: 'splide-pagination-bullet',
|
||||
},
|
||||
@@ -64,13 +58,6 @@ const handleSplideMounted = (splide: SplideType) => {
|
||||
if (splideIndex.value !== undefined) {
|
||||
splideIndex.value = splide.index
|
||||
}
|
||||
|
||||
// 화살표 버튼 클릭 이벤트 리스너 추가
|
||||
nextTick(() => {
|
||||
addArrowClickListeners(splide, (direction, targetIndex) => {
|
||||
emit('arrowClick', direction, targetIndex)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const handleMove = (
|
||||
@@ -95,12 +82,16 @@ defineExpose({
|
||||
<Splide
|
||||
ref="splideRef"
|
||||
:options="options"
|
||||
:has-track="false"
|
||||
class="h-full"
|
||||
:style="getPaginationClass(props.paginationData)"
|
||||
@splide:mounted="handleSplideMounted"
|
||||
@splide:move="handleMove"
|
||||
>
|
||||
<slot />
|
||||
<SplideTrack>
|
||||
<slot />
|
||||
</SplideTrack>
|
||||
<BlocksSlideArrows v-if="props.arrows" :arrows-data="props.arrowsData" />
|
||||
</Splide>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Splide, SplideSlide } from '@splidejs/vue-splide'
|
||||
import { useSplideArrow } from '#layers/composables/useSplideArrow'
|
||||
import { Splide, SplideTrack, SplideSlide } from '@splidejs/vue-splide'
|
||||
import type { Splide as SplideType, Options } from '@splidejs/splide'
|
||||
import type {
|
||||
PageDataResourceGroups,
|
||||
@@ -8,10 +7,12 @@ import type {
|
||||
} from '#layers/types/api/pageData'
|
||||
|
||||
interface Props {
|
||||
drag?: boolean
|
||||
thumbnailData: PageDataResourceGroup[]
|
||||
paginationData?: PageDataResourceGroups
|
||||
paginationData: PageDataResourceGroups
|
||||
analyticsSarea: string
|
||||
drag?: boolean
|
||||
arrows?: boolean
|
||||
arrowsData?: PageDataResourceGroups
|
||||
variant?: 'default' | 'media'
|
||||
}
|
||||
|
||||
@@ -21,13 +22,13 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
variant: 'default',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['mounted', 'move', 'arrowClick'])
|
||||
const emit = defineEmits(['mounted', 'move'])
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
|
||||
const splideIndex = defineModel<number>('index', { required: false })
|
||||
|
||||
// Splide 화살표 로직을 위한 composable 사용
|
||||
const { addArrowClickListeners } = useSplideArrow()
|
||||
|
||||
let mainInst: SplideType | null = null
|
||||
let thumbsInst: SplideType | null = null
|
||||
let removeArrowListeners: (() => void) | null = null
|
||||
@@ -62,12 +63,6 @@ const thumbOptions = computed<Options>(() => ({
|
||||
isNavigation: true,
|
||||
updateOnMove: true,
|
||||
flickPower: 300,
|
||||
classes: {
|
||||
arrows: 'splide-arrows',
|
||||
arrow: 'splide-arrow',
|
||||
prev: 'arrow-prev',
|
||||
next: 'arrow-next',
|
||||
},
|
||||
breakpoints: {
|
||||
[BREAKPOINTS.md - 1]: {
|
||||
padding: {
|
||||
@@ -113,21 +108,22 @@ const handleMove = (
|
||||
}
|
||||
}
|
||||
|
||||
const handleThumbnailClick = (title: string) => {
|
||||
const paginationAnalytics = {
|
||||
action_type: 'click',
|
||||
click_item: title,
|
||||
click_sarea: props.analyticsSarea,
|
||||
}
|
||||
|
||||
sendLog(locale.value, useAnalyticsData(paginationAnalytics))
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
mainInst = mainRef.value?.splide ?? null
|
||||
thumbsInst = thumbsRef.value?.splide ?? null
|
||||
|
||||
if (mainInst && thumbsInst) {
|
||||
mainInst.sync(thumbsInst)
|
||||
// 썸네일 슬라이드의 화살표 버튼에 이벤트 리스너 추가
|
||||
nextTick(() => {
|
||||
removeArrowListeners = addArrowClickListeners(
|
||||
thumbsInst,
|
||||
(direction, targetIndex) => {
|
||||
emit('arrowClick', direction, targetIndex)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -163,20 +159,26 @@ onBeforeUnmount(() => {
|
||||
v-if="props.thumbnailData.length > 1"
|
||||
ref="thumbsRef"
|
||||
:options="thumbOptions"
|
||||
:arrows-data="props.arrowsData"
|
||||
:has-track="false"
|
||||
class="thumbnail-splide"
|
||||
:style="getPaginationClass(paginationData)"
|
||||
>
|
||||
<SplideSlide
|
||||
v-for="(item, index) in props.thumbnailData"
|
||||
:key="index"
|
||||
class="thumbnail-slide"
|
||||
>
|
||||
<AtomsImg
|
||||
:src="getThumbnailSrc(item)"
|
||||
alt="thumbnail image"
|
||||
class="slide-image"
|
||||
/>
|
||||
</SplideSlide>
|
||||
<SplideTrack>
|
||||
<SplideSlide
|
||||
v-for="(item, index) in props.thumbnailData"
|
||||
:key="index"
|
||||
class="thumbnail-slide"
|
||||
@click="handleThumbnailClick(item?.group_label ?? `${index + 1}`)"
|
||||
>
|
||||
<AtomsImg
|
||||
:src="getThumbnailSrc(item)"
|
||||
alt="thumbnail image"
|
||||
class="slide-image"
|
||||
/>
|
||||
</SplideSlide>
|
||||
</SplideTrack>
|
||||
<BlocksSlideArrows v-if="props.arrows" :arrows-data="props.arrowsData" />
|
||||
</Splide>
|
||||
</div>
|
||||
</template>
|
||||
@@ -226,10 +228,10 @@ onBeforeUnmount(() => {
|
||||
.thumbnail-carousel.thumbnail-default .thumbnail-splide {
|
||||
@apply absolute bottom-[32px] left-1/2 -translate-x-1/2 max-w-[100%] md:bottom-[48px] md:max-w-[896px] md:px-[64px];
|
||||
}
|
||||
.thumbnail-carousel.thumbnail-default:deep(.arrow-prev) {
|
||||
.thumbnail-carousel.thumbnail-default:deep(.splide__arrow--prev) {
|
||||
@apply left-0;
|
||||
}
|
||||
.thumbnail-carousel.thumbnail-default:deep(.arrow-next) {
|
||||
.thumbnail-carousel.thumbnail-default:deep(.splide__arrow--next) {
|
||||
@apply right-0;
|
||||
}
|
||||
.thumbnail-carousel.thumbnail-default .thumbnail-slide {
|
||||
@@ -253,10 +255,10 @@ onBeforeUnmount(() => {
|
||||
sm:max-w-[calc(100%+80px)] sm:mx-[-40px]
|
||||
md:max-w-[100%] md:mt-[28px] md:mx-auto md:px-[64px];
|
||||
}
|
||||
.thumbnail-carousel.thumbnail-media:deep(.arrow-prev) {
|
||||
.thumbnail-carousel.thumbnail-media:deep(.splide__arrow--prev) {
|
||||
@apply left-[0];
|
||||
}
|
||||
.thumbnail-carousel.thumbnail-media:deep(.arrow-next) {
|
||||
.thumbnail-carousel.thumbnail-media:deep(.splide__arrow--next) {
|
||||
@apply right-[0];
|
||||
}
|
||||
.thumbnail-carousel.thumbnail-media .thumbnail-slide {
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as amplitude from '@amplitude/analytics-browser'
|
||||
import type { PageDataResourceGroup } from '#layers/types/api/pageData'
|
||||
import type { TrackingObject } from '#layers/types/api/common'
|
||||
import type { AnalyticsDetailType } from '#layers/types/AnalyticsType'
|
||||
import type {
|
||||
IdentityInfo,
|
||||
@@ -11,63 +11,9 @@ declare const svcLog: any
|
||||
declare const twq: any
|
||||
declare const ttq: any
|
||||
|
||||
/**
|
||||
* 페이지 데이터와 템플릿 정보를 기반으로 분석용 로그 데이터를 생성하는 composable (직접 객체 반환)
|
||||
* @param resourcesData 페이지 리소스 데이터
|
||||
* @param pageVerTmplSeq 템플릿 시퀀스 번호
|
||||
* @returns 분석용 로그 데이터 객체 (ref 없이)
|
||||
*/
|
||||
export const useAnalyticsLogDataDirect = (
|
||||
resourcesData: PageDataResourceGroup | string,
|
||||
pageVerTmplSeq: number | null
|
||||
) => {
|
||||
const store = usePageDataStore()
|
||||
const pageData = store.pageData
|
||||
|
||||
if (!pageData) {
|
||||
return {} as AnalyticsDetailType
|
||||
}
|
||||
|
||||
// resourcesData가 문자열인 경우 (예: 'view')
|
||||
if (typeof resourcesData === 'string') {
|
||||
const logData = {
|
||||
actionType: resourcesData,
|
||||
event: pageData.page_name,
|
||||
eventCategory: pageData.page_name,
|
||||
} as unknown as AnalyticsDetailType
|
||||
|
||||
if (resourcesData === 'view') {
|
||||
logData.viewArea = pageData.page_name_en
|
||||
logData.viewType = 'pageView'
|
||||
}
|
||||
|
||||
return logData
|
||||
}
|
||||
|
||||
// resourcesData가 객체인 경우 (기존 로직)
|
||||
if (!resourcesData || !resourcesData.tracking) {
|
||||
return {} as AnalyticsDetailType
|
||||
}
|
||||
|
||||
const pageDataTrack = resourcesData.tracking
|
||||
|
||||
const logData = {
|
||||
actionType: pageDataTrack?.action_type,
|
||||
event: pageData.page_name,
|
||||
eventCategory: `${pageData.page_name}_${pageDataTrack?.click_sarea}_${pageDataTrack?.click_item}`,
|
||||
} as unknown as AnalyticsDetailType
|
||||
|
||||
if (pageDataTrack.action_type === 'click') {
|
||||
logData.clickArea = pageData.page_name_en
|
||||
logData.clickSarea = pageDataTrack.click_sarea
|
||||
logData.clickItem = pageDataTrack.click_item
|
||||
} else if (pageDataTrack.action_type === 'view') {
|
||||
logData.viewArea = pageData.page_name_en
|
||||
logData.viewType = 'view_frame'
|
||||
}
|
||||
|
||||
return logData
|
||||
}
|
||||
// ============================================================================
|
||||
// 유틸 함수
|
||||
// ============================================================================
|
||||
|
||||
// target에 {XX1, XX2}와 같은 형태가 포함되어 있을 경우 options.clickItem으로부터 값 추출하여 세팅
|
||||
const findValueFromOption = (target: string, { options = {} }: any) => {
|
||||
@@ -92,69 +38,112 @@ const findValueFromOption = (target: string, { options = {} }: any) => {
|
||||
return target
|
||||
}
|
||||
|
||||
/** 브라우저 환경인지 체크 */
|
||||
const isClient = () => typeof window !== 'undefined' && import.meta.client
|
||||
|
||||
/** Analytics 객체가 비어있는지 체크 */
|
||||
const isEmptyAnalytics = (analytics?: Partial<AnalyticsDetailType>) =>
|
||||
!analytics || Object.keys(analytics).length === 0
|
||||
|
||||
// ============================================================================
|
||||
// Analytics 데이터 생성
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Google Analytics 전송
|
||||
*
|
||||
* @param {AnalyticsDetailType} analytics
|
||||
* @param {object} options
|
||||
* 로그 데이터를 생성하는 composable (직접 객체 반환)
|
||||
* @param analytics 트래킹 데이터
|
||||
* @param type 'page' | 'template'
|
||||
* @returns 분석용 로그 데이터 객체 (ref 없이)
|
||||
*/
|
||||
const sendGA = (analytics: AnalyticsDetailType, { options = {} }: any) => {
|
||||
if (!import.meta.client) return
|
||||
export const useAnalyticsData = (
|
||||
analytics: string | TrackingObject,
|
||||
type: 'page' | 'template' = 'template'
|
||||
): Partial<AnalyticsDetailType> => {
|
||||
const pageDataStore = usePageDataStore()
|
||||
const { pageName, pageNameEn } = storeToRefs(pageDataStore)
|
||||
|
||||
try {
|
||||
const { gtag } = useGtag()
|
||||
|
||||
const eventName = analytics.event || ''
|
||||
const eventLocale = analytics.eventLocale || ''
|
||||
const eventCategory = `${analytics.eventCategory}`
|
||||
|
||||
// GA 클릭 이벤트 명 뒤에 언어 값 추가 노출되도록 개발. ex) GNB_자유게시판_KO
|
||||
const eventLabel = `${eventCategory}_${eventLocale}`
|
||||
|
||||
gtag('set', 'cookie_domain', `${window?.location?.hostname || ''}`) // env 값으로 설정 시 쿠키 생성 안 돼서 window.location.hostname으로 설정
|
||||
gtag('set', 'cookie_expires', '0') // 0으로 설정 시 쿠키가 Session 기반 쿠키로 전환
|
||||
gtag('event', `${eventName}`, {
|
||||
event_category: eventLabel,
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('[Exception] useAnalytics.sendGA: ', e)
|
||||
if (!analytics || !pageName.value || !pageNameEn.value) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const baseEvent = pageName.value
|
||||
const baseViewArea = pageNameEn.value
|
||||
let logData = {} as AnalyticsDetailType
|
||||
|
||||
// 문자열 'view'만 들어오는 경우 (페이지뷰)
|
||||
if (analytics === 'view') {
|
||||
logData = {
|
||||
actionType: analytics,
|
||||
event: baseEvent,
|
||||
eventCategory: baseEvent,
|
||||
viewArea: baseViewArea,
|
||||
viewType: 'pageView',
|
||||
} as AnalyticsDetailType
|
||||
|
||||
return logData
|
||||
}
|
||||
|
||||
// 객체인 경우
|
||||
if (typeof analytics === 'object') {
|
||||
const { action_type, click_sarea, click_item } = analytics
|
||||
|
||||
logData = {
|
||||
actionType: action_type,
|
||||
event: baseEvent,
|
||||
eventCategory: `${baseEvent}_${click_sarea}_${click_item}`,
|
||||
} as AnalyticsDetailType
|
||||
|
||||
if (action_type === 'click') {
|
||||
logData.clickArea = baseViewArea
|
||||
logData.clickSarea = click_sarea
|
||||
logData.clickItem = click_item
|
||||
} else if (action_type === 'view') {
|
||||
logData.viewArea = baseViewArea
|
||||
logData.viewType = 'view_frame'
|
||||
}
|
||||
}
|
||||
|
||||
return logData
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Stove Analytics(81 Plug) + Amplitude
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Stove Analytics(81 Plug) 전송
|
||||
*
|
||||
* @param {AnalyticsDetailType} analytics
|
||||
* @param {string} mcode
|
||||
* @param {object} options
|
||||
* @param analytics Partial<AnalyticsDetailType>
|
||||
* @param param1 mcode, options
|
||||
*/
|
||||
const sendSA = (
|
||||
analytics: AnalyticsDetailType,
|
||||
{ mcode = '', options = {} }: any
|
||||
analytics: Partial<AnalyticsDetailType>,
|
||||
{ mcode = '', options = {} }: { mcode?: string; options?: any } = {}
|
||||
) => {
|
||||
if (!import.meta.client) return
|
||||
if (!isClient()) return
|
||||
if (isEmptyAnalytics(analytics)) return
|
||||
|
||||
const gameDataStore = useGameDataStore()
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
|
||||
try {
|
||||
const gameNo = gameData.value.game_code
|
||||
|
||||
const device = useDevice()
|
||||
const deviceType = device.isDesktop ? 'pcweb' : 'mobileweb'
|
||||
|
||||
const gameNo = gameData.value?.game_code
|
||||
const deviceType = device.isDesktop ? 'pcweb' : 'mobileweb'
|
||||
const country = `${csrGetCountry()}`
|
||||
const memberNo = `${csrGetStoveMemberNo()}`
|
||||
|
||||
const actionType = analytics.actionType || ''
|
||||
const logSourceType = analytics.logSourceType || ''
|
||||
const viewArea = analytics.viewArea || ''
|
||||
const viewType = analytics.viewType || ''
|
||||
const clickArea = analytics.clickArea || ''
|
||||
const clickSarea = findValueFromOption(analytics.clickSarea || '', {
|
||||
options,
|
||||
})
|
||||
const eventLocale = analytics.eventLocale || ''
|
||||
const {
|
||||
actionType = '',
|
||||
logSourceType = '',
|
||||
viewArea = '',
|
||||
viewType = '',
|
||||
clickArea = '',
|
||||
clickSarea = '',
|
||||
eventLocale = '',
|
||||
clickItem,
|
||||
} = analytics
|
||||
|
||||
const identityInfo: IdentityInfo = {
|
||||
app_id: 'stove',
|
||||
@@ -173,7 +162,7 @@ const sendSA = (
|
||||
media_page: '',
|
||||
}
|
||||
|
||||
let actionParam = {}
|
||||
let actionParam: ActionInfo['action_param'] = {}
|
||||
|
||||
if (actionType === 'view') {
|
||||
actionParam = {
|
||||
@@ -190,7 +179,7 @@ const sendSA = (
|
||||
click_area: clickArea,
|
||||
click_sarea: clickSarea,
|
||||
click_item: {
|
||||
pwt_click_item: analytics.clickItem,
|
||||
pwt_click_item: clickItem,
|
||||
game_no: gameNo,
|
||||
lang_cd: eventLocale,
|
||||
...options?.clickItem,
|
||||
@@ -206,8 +195,8 @@ const sendSA = (
|
||||
|
||||
const amplitudeActionInfo = {
|
||||
...actionInfo,
|
||||
url: import.meta.client ? `${location?.href || ''}` : '',
|
||||
agent: import.meta.client ? `${navigator?.userAgent || ''}` : '',
|
||||
url: isClient() ? `${location?.href || ''}` : '',
|
||||
agent: isClient() ? `${navigator?.userAgent || ''}` : '',
|
||||
}
|
||||
|
||||
const amplitudeActionParams: {
|
||||
@@ -218,66 +207,107 @@ const sendSA = (
|
||||
event_properties: amplitudeActionInfo,
|
||||
}
|
||||
|
||||
// 81plug
|
||||
svcLog.identity(identityInfo)
|
||||
svcLog.action(actionInfo, {}, {}) // 81plug warning log 제거를 위해 2번째 인자부터 빈 객체 세팅
|
||||
// 81plug warning log 제거를 위해 2번째 인자부터 빈 객체 세팅
|
||||
svcLog.action(actionInfo, {}, {})
|
||||
|
||||
// Amplitude
|
||||
amplitude.track(amplitudeActionParams)
|
||||
} catch (e) {
|
||||
console.error('[Exception] useAnalytics.sendSA: ', e)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 기본 로그 일괄 전송
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 기본 로그 일괄 전송
|
||||
*
|
||||
* @param {string} locale
|
||||
* @param {AnalyticsDetailType} analytics
|
||||
* @param locale 언어 코드
|
||||
* @param analytics Partial<AnalyticsDetailType>
|
||||
*/
|
||||
const sendLog = (locale: string, analytics: AnalyticsDetailType) => {
|
||||
const sendLog = (locale: string, analytics: Partial<AnalyticsDetailType>) => {
|
||||
if (isEmptyAnalytics(analytics)) return
|
||||
|
||||
// 언어 코드 대문자 변환
|
||||
analytics.eventLocale = locale.toUpperCase()
|
||||
|
||||
if (analytics) {
|
||||
// GA 전송 : eventCategory 유무로 판별
|
||||
sendGA(analytics, { options: analytics.options })
|
||||
// SA 전송 : actionType, logSourceType 유무로 판별
|
||||
if (
|
||||
analytics.actionType &&
|
||||
analytics.actionType !== ''
|
||||
// analytics.logSourceType &&
|
||||
// analytics.logSourceType !== ''
|
||||
) {
|
||||
sendSA(analytics, { mcode: analytics.mcode, options: analytics.options })
|
||||
}
|
||||
// SA 전송 : actionType, logSourceType 유무로 판별 (logSourceType 체크는 주석 처리 되어있던 로직 유지)
|
||||
if (analytics.actionType && analytics.actionType !== '') {
|
||||
sendSA(analytics, { mcode: analytics.mcode, options: analytics.options })
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Google Analytics
|
||||
// ============================================================================
|
||||
|
||||
/** GA 공통 설정 래퍼 */
|
||||
const withGA = (callback: (gtag: any) => void) => {
|
||||
if (!isClient()) return
|
||||
|
||||
try {
|
||||
const { gtag } = useGtag()
|
||||
const hostname = window?.location?.hostname || ''
|
||||
|
||||
gtag('set', 'cookie_domain', hostname) // env 값으로 설정 시 쿠키 생성 안 돼서 window.location.hostname으로 설정
|
||||
gtag('set', 'cookie_expires', '0') // 0으로 설정 시 쿠키가 Session 기반 쿠키로 전환
|
||||
|
||||
callback(gtag)
|
||||
} catch (e) {
|
||||
console.error('[Exception] useAnalytics.withGA: ', e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Analytics 전송 (기본 이벤트만 전송)
|
||||
*
|
||||
* @param {string} gaEventName
|
||||
* @param gaEventName 이벤트명
|
||||
*/
|
||||
const sendGAEventOnly = (gaEventName: string) => {
|
||||
if (!import.meta.client) return
|
||||
if (!gaEventName) return
|
||||
|
||||
try {
|
||||
const { gtag } = useGtag()
|
||||
|
||||
gtag('set', 'cookie_domain', `${window?.location?.hostname || ''}`) // env 값으로 설정 시 쿠키 생성 안 돼서 window.location.hostname으로 설정
|
||||
gtag('set', 'cookie_expires', '0') // 0으로 설정 시 쿠키가 Session 기반 쿠키로 전환
|
||||
withGA(gtag => {
|
||||
gtag('event', `${gaEventName}`)
|
||||
} catch (e) {
|
||||
console.error('[Exception] useAnalytics.sendGAEventOnly: ', e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Analytics 전송
|
||||
*
|
||||
* @param analytics Partial<AnalyticsDetailType>
|
||||
*/
|
||||
const sendGA = (analytics: Partial<AnalyticsDetailType>) => {
|
||||
if (isEmptyAnalytics(analytics)) return
|
||||
|
||||
withGA(gtag => {
|
||||
const eventName = analytics.event || ''
|
||||
const eventLocale = analytics.eventLocale || ''
|
||||
const eventCategory = analytics.eventCategory || ''
|
||||
// GA 클릭 이벤트 명 뒤에 언어 값 추가 노출되도록 개발. ex) GNB_자유게시판_KO
|
||||
const eventLabel = `${eventCategory}_${eventLocale}`
|
||||
|
||||
gtag('event', `${eventName}`, {
|
||||
event_category: eventLabel,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Meta / Twitter / TikTok Pixel
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 메타 픽셀 전송
|
||||
*
|
||||
* @param {string} fbEventName
|
||||
* @param fbEventName 이벤트명
|
||||
* @description 수집 대상 페이지에 useHead({ meta: [loadMetaPixelMeta()] }) 선언
|
||||
*/
|
||||
const sendMetaPixel = (fbEventName: string) => {
|
||||
if (!import.meta.client) return
|
||||
const sendMetaPixel = (fbEventName?: string) => {
|
||||
if (!isClient() || !fbEventName) return
|
||||
|
||||
try {
|
||||
const { $fbq } = useNuxtApp()
|
||||
@@ -292,11 +322,11 @@ const sendMetaPixel = (fbEventName: string) => {
|
||||
/**
|
||||
* X(트위터) 픽셀 전송
|
||||
*
|
||||
* @param {string} twEventName
|
||||
* @param twEventName 이벤트명
|
||||
* @description 수집 대상 페이지에 useHead({ script: [loadTwitterPixelScript()] }) 선언
|
||||
*/
|
||||
const sendTwitterPixel = (twEventName: string) => {
|
||||
if (!import.meta.client) return
|
||||
const sendTwitterPixel = (twEventName?: string) => {
|
||||
if (!isClient() || !twEventName) return
|
||||
|
||||
try {
|
||||
twq('event', twEventName, {})
|
||||
@@ -308,11 +338,11 @@ const sendTwitterPixel = (twEventName: string) => {
|
||||
/**
|
||||
* 틱톡 픽셀 전송
|
||||
*
|
||||
* @param {string} ttEventName
|
||||
* @param ttEventName 이벤트명
|
||||
* @description 수집 대상 페이지에 onMounted(() => { loadTikTokPixelScript() }) 선언
|
||||
*/
|
||||
const sendTiktokPixel = (ttEventName: string) => {
|
||||
if (!import.meta.client) return
|
||||
const sendTiktokPixel = (ttEventName?: string) => {
|
||||
if (!isClient() || !ttEventName) return
|
||||
|
||||
try {
|
||||
ttq.track(ttEventName)
|
||||
@@ -321,45 +351,61 @@ const sendTiktokPixel = (ttEventName: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 마케팅 스크립트 일괄 전송
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 마케팅 인텔리전스 팀 요청 마케팅 스크립트 일괄 전송
|
||||
*
|
||||
* @param {string} gaEventName
|
||||
* @param {string} fbEventName
|
||||
* @param {string} twEventName
|
||||
* @param {string} ttEventName
|
||||
* @param logName 'ga' | 'meta' | 'twitter' | 'tiktok'
|
||||
* @param eventName 이벤트명 (ga: eventOnly, meta/twitter/tiktok: 필수)
|
||||
* @param analytics GA용 analytics 데이터 (eventName 없을 때만 사용)
|
||||
*/
|
||||
const sendMarketingScript = ({
|
||||
gaEventName,
|
||||
fbEventName,
|
||||
twEventName,
|
||||
ttEventName,
|
||||
const sendMarketingLog = ({
|
||||
logName,
|
||||
eventName,
|
||||
analytics,
|
||||
}: {
|
||||
gaEventName?: string
|
||||
fbEventName?: string
|
||||
twEventName?: string
|
||||
ttEventName?: string
|
||||
logName: 'ga' | 'meta' | 'twitter' | 'tiktok'
|
||||
eventName?: string
|
||||
analytics?: Partial<AnalyticsDetailType>
|
||||
}) => {
|
||||
if (gaEventName) {
|
||||
sendGAEventOnly(gaEventName)
|
||||
}
|
||||
if (fbEventName) {
|
||||
sendMetaPixel(fbEventName)
|
||||
}
|
||||
if (twEventName) {
|
||||
sendTwitterPixel(twEventName)
|
||||
}
|
||||
if (ttEventName) {
|
||||
sendTiktokPixel(ttEventName)
|
||||
switch (logName) {
|
||||
case 'ga': {
|
||||
if (eventName) {
|
||||
sendGAEventOnly(eventName)
|
||||
} else if (analytics) {
|
||||
sendGA(analytics)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'meta': {
|
||||
sendMetaPixel(eventName)
|
||||
break
|
||||
}
|
||||
case 'twitter': {
|
||||
sendTwitterPixel(eventName)
|
||||
break
|
||||
}
|
||||
case 'tiktok': {
|
||||
sendTiktokPixel(eventName)
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// export
|
||||
// ============================================================================
|
||||
|
||||
export default () => {
|
||||
return {
|
||||
sendGA,
|
||||
sendSA,
|
||||
sendLog,
|
||||
sendMarketingScript,
|
||||
useAnalyticsLogDataDirect,
|
||||
sendMarketingLog,
|
||||
useAnalyticsData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import type { Splide as SplideType } from '@splidejs/splide'
|
||||
|
||||
/**
|
||||
* Splide 슬라이더의 화살표 클릭 로직을 처리하는 composable
|
||||
*/
|
||||
export const useSplideArrow = () => {
|
||||
/**
|
||||
* 화살표 클릭 시 슬라이드 인덱스를 계산하는 함수
|
||||
* @param direction - 이동 방향 ('prev' | 'next')
|
||||
* @param splide - Splide 인스턴스
|
||||
* @returns 다음 슬라이드 인덱스
|
||||
*/
|
||||
const calculateTargetIndex = (direction: 'prev' | 'next', splide: SplideType): number => {
|
||||
const currentIndex = splide.index
|
||||
const totalSlides = splide.length
|
||||
|
||||
if (direction === 'next') {
|
||||
return currentIndex + 1 >= totalSlides ? 0 : currentIndex + 1
|
||||
} else {
|
||||
return currentIndex - 1 < 0 ? totalSlides - 1 : currentIndex - 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 화살표 클릭 핸들러
|
||||
* @param direction - 이동 방향
|
||||
* @param splide - Splide 인스턴스
|
||||
* @param onArrowClick - 화살표 클릭 시 실행될 콜백 함수
|
||||
*/
|
||||
const handleArrowClick = (
|
||||
direction: 'prev' | 'next',
|
||||
splide: SplideType,
|
||||
onArrowClick?: (direction: 'prev' | 'next', targetIndex: number) => void
|
||||
) => {
|
||||
const targetIndex = calculateTargetIndex(direction, splide)
|
||||
|
||||
if (onArrowClick) {
|
||||
onArrowClick(direction, targetIndex)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 화살표 버튼에 클릭 이벤트 리스너를 추가하는 함수
|
||||
* @param splide - Splide 인스턴스
|
||||
* @param onArrowClick - 화살표 클릭 시 실행될 콜백 함수
|
||||
* @returns 이벤트 리스너 제거 함수
|
||||
*/
|
||||
const addArrowClickListeners = (
|
||||
splide: SplideType,
|
||||
onArrowClick?: (direction: 'prev' | 'next', targetIndex: number) => void
|
||||
) => {
|
||||
const prevArrow = splide.root.querySelector('.arrow-prev')
|
||||
const nextArrow = splide.root.querySelector('.arrow-next')
|
||||
|
||||
const prevHandler = () => handleArrowClick('prev', splide, onArrowClick)
|
||||
const nextHandler = () => handleArrowClick('next', splide, onArrowClick)
|
||||
|
||||
if (prevArrow) {
|
||||
prevArrow.addEventListener('click', prevHandler)
|
||||
}
|
||||
|
||||
if (nextArrow) {
|
||||
nextArrow.addEventListener('click', nextHandler)
|
||||
}
|
||||
|
||||
// 이벤트 리스너 제거 함수 반환
|
||||
return () => {
|
||||
if (prevArrow) {
|
||||
prevArrow.removeEventListener('click', prevHandler)
|
||||
}
|
||||
if (nextArrow) {
|
||||
nextArrow.removeEventListener('click', nextHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
calculateTargetIndex,
|
||||
handleArrowClick,
|
||||
addArrowClickListeners
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,16 +14,17 @@ import {
|
||||
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
import type { ReqCouponList } from '#layers/types/api/couponData'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
id?: string
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// Configuration
|
||||
provide('pageVerTmplNameEn', props.pageVerTmplNameEn)
|
||||
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
|
||||
const stoveApiUrl = runtimeConfig.public.stoveApiUrl as string
|
||||
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
|
||||
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_coupon.json'
|
||||
@@ -486,7 +487,6 @@ onMounted(async () => {
|
||||
|
||||
<template>
|
||||
<WidgetsFixMainTitle
|
||||
:id="props.id"
|
||||
:title="tm('Coupon_Page_Title')"
|
||||
:resources-data="backgroundData"
|
||||
/>
|
||||
|
||||
@@ -5,14 +5,15 @@ import type { Platform } from '#layers/types/components/button'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
id?: string
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// Configuration
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
|
||||
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
|
||||
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_download.json'
|
||||
const stoveClientDownloadUrl = runtimeConfig.public
|
||||
@@ -127,7 +128,6 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
|
||||
|
||||
<template>
|
||||
<WidgetsFixMainTitle
|
||||
:id="props.id"
|
||||
:title="tm('Download_Page_Title')"
|
||||
:resources-data="backgroundData"
|
||||
/>
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { Platform } from '#layers/types/components/button'
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -362,7 +363,7 @@ const handlePreregistClick = () => {
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<BlocksSlideDefault v-bind="splideOptions">
|
||||
<WidgetsSlideDefault v-bind="splideOptions">
|
||||
<SplideSlide
|
||||
v-for="item in rewardImages"
|
||||
:key="item.id"
|
||||
@@ -380,7 +381,7 @@ const handlePreregistClick = () => {
|
||||
class="w-full h-full object-contain"
|
||||
/>
|
||||
</SplideSlide>
|
||||
</BlocksSlideDefault>
|
||||
</WidgetsSlideDefault>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
<WidgetsDescription
|
||||
|
||||
@@ -4,9 +4,9 @@ import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
id?: string
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
@@ -148,7 +148,6 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<WidgetsFixMainTitle
|
||||
:id="props.id"
|
||||
:title="tm('Secure_Page_Title') || '보안 강화 캠페인'"
|
||||
:resources-data="backgroundData"
|
||||
class="mx-auto"
|
||||
@@ -156,7 +155,9 @@ onMounted(() => {
|
||||
|
||||
<div class="section-container static">
|
||||
<section class="section-secure bg-[#F0F0F0] pb-50">
|
||||
<div class="section-static content-standa flex-wrap md:max-w-[1300px] mx-auto">
|
||||
<div
|
||||
class="section-static content-standa flex-wrap md:max-w-[1300px] mx-auto"
|
||||
>
|
||||
<!-- Title Section (Non-Korean only) -->
|
||||
<div
|
||||
v-if="locale !== 'ko'"
|
||||
@@ -170,7 +171,10 @@ onMounted(() => {
|
||||
<p
|
||||
class="text-[#666666] text-[13px] md:text-[14px] font-normal leading-[22px] md:leading-[24px] tracking-[-0.39px] md:tracking-[-0.42px]"
|
||||
>
|
||||
*{{ tm('Secure_Global_Desc') || 'Set up the STOVE Authenticator to better protect your account.' }}
|
||||
*{{
|
||||
tm('Secure_Global_Desc') ||
|
||||
'Set up the STOVE Authenticator to better protect your account.'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -199,167 +203,169 @@ onMounted(() => {
|
||||
: 'w-full md:w-[420px] h-auto md:h-[392px] p-[10px] md:p-4 gap-3',
|
||||
]"
|
||||
>
|
||||
<!-- Card Content -->
|
||||
<div
|
||||
:class="[
|
||||
'flex flex-col text-left',
|
||||
locale === 'ko'
|
||||
? 'flex-1 p-[10px] md:p-3 lg:p-4 gap-[8px] md:gap-3'
|
||||
: 'flex-1 p-[10px] md:p-4 gap-2 md:gap-3',
|
||||
]"
|
||||
>
|
||||
<!-- Badge -->
|
||||
<div class="inline-flex">
|
||||
<span
|
||||
<!-- Card Content -->
|
||||
<div
|
||||
:class="[
|
||||
'flex flex-col text-left',
|
||||
locale === 'ko'
|
||||
? 'flex-1 p-[10px] md:p-3 lg:p-4 gap-[8px] md:gap-3'
|
||||
: 'flex-1 p-[10px] md:p-4 gap-2 md:gap-3',
|
||||
]"
|
||||
>
|
||||
<!-- Badge -->
|
||||
<div class="inline-flex">
|
||||
<span
|
||||
:class="[
|
||||
locale === 'ko'
|
||||
? 'px-1.5 md:px-2 py-0.5 md:py-1 rounded-full text-[12px] md:text-[14px] font-medium leading-5'
|
||||
: 'px-[6px] md:px-2 py-[2px] md:py-1 rounded-full text-[12px] md:text-[14px] font-medium leading-[18px] md: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] font-bold',
|
||||
locale === 'ko'
|
||||
? 'px-1.5 md:px-2 py-0.5 md:py-1 rounded-full text-[12px] md:text-[14px] font-medium leading-5'
|
||||
: 'px-[6px] md:px-2 py-[2px] md:py-1 rounded-full text-[12px] md:text-[14px] font-medium leading-[18px] md:leading-5',
|
||||
card.status === 'Y'
|
||||
? 'bg-[#E2EAFF] text-[#3C75FF]'
|
||||
: 'bg-[#EBEBEB] text-[#999999]',
|
||||
? 'text-[18px] md:text-[24px] leading-[26px] md:leading-[34px] tracking-[-0.54px] md:tracking-[-0.72px]'
|
||||
: 'text-[18px] md:text-[24px] leading-[26px] md:leading-[34px] tracking-[-0.54px] md:tracking-[-0.72px]',
|
||||
]"
|
||||
>
|
||||
{{
|
||||
card.status === 'Y'
|
||||
? tm('Secure_Enabled')
|
||||
: tm('Secure_Disabled')
|
||||
}}
|
||||
</span>
|
||||
{{ card.title }}
|
||||
</h4>
|
||||
|
||||
<!-- Description -->
|
||||
<p
|
||||
:class="[
|
||||
'flex-1 text-[#999999] font-[400]',
|
||||
locale === 'ko'
|
||||
? 'text-[14px] md:text-base leading-[22px] md:leading-[26px] tracking-[-0.42px] md:tracking-[-0.48px]'
|
||||
: 'text-[14px] md:text-base leading-[24px] md:leading-[26px] tracking-[-0.42px] md:tracking-[-0.48px]',
|
||||
]"
|
||||
>
|
||||
{{ card.description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
<h4
|
||||
:class="[
|
||||
'text-[#1F1F1F] font-bold',
|
||||
locale === 'ko'
|
||||
? 'text-[18px] md:text-[24px] leading-[26px] md:leading-[34px] tracking-[-0.54px] md:tracking-[-0.72px]'
|
||||
: 'text-[18px] md:text-[24px] leading-[26px] md:leading-[34px] tracking-[-0.54px] md:tracking-[-0.72px]',
|
||||
]"
|
||||
>
|
||||
{{ card.title }}
|
||||
</h4>
|
||||
|
||||
<!-- Description -->
|
||||
<p
|
||||
:class="[
|
||||
'flex-1 text-[#999999] font-[400]',
|
||||
locale === 'ko'
|
||||
? 'text-[14px] md:text-base leading-[22px] md:leading-[26px] tracking-[-0.42px] md:tracking-[-0.48px]'
|
||||
: 'text-[14px] md:text-base leading-[24px] md:leading-[26px] tracking-[-0.42px] md:tracking-[-0.48px]',
|
||||
]"
|
||||
>
|
||||
{{ card.description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Benefit Section -->
|
||||
<div
|
||||
:class="[
|
||||
'rounded-2xl flex flex-col',
|
||||
locale === 'ko'
|
||||
? 'self-stretch p-[10px] md:p-4 gap-4'
|
||||
: 'p-[10px] md:p-4 gap-3 md:gap-4',
|
||||
card.benefitTitle ? 'bg-[#F0F4FF]' : '',
|
||||
]"
|
||||
>
|
||||
<!-- Benefit Info -->
|
||||
<!-- Benefit Section -->
|
||||
<div
|
||||
v-if="card.benefitTitle"
|
||||
:class="[
|
||||
'flex items-center',
|
||||
locale === 'ko' ? 'gap-[12px]' : 'gap-2 md:gap-[12px]',
|
||||
'rounded-2xl flex flex-col',
|
||||
locale === 'ko'
|
||||
? 'self-stretch p-[10px] md:p-4 gap-4'
|
||||
: 'p-[10px] md:p-4 gap-3 md:gap-4',
|
||||
card.benefitTitle ? 'bg-[#F0F4FF]' : '',
|
||||
]"
|
||||
>
|
||||
<!-- Benefit Info -->
|
||||
<div
|
||||
v-if="card.benefitIcon"
|
||||
v-if="card.benefitTitle"
|
||||
:class="[
|
||||
'bg-[#3C75FF] rounded-[8px] flex items-center justify-center',
|
||||
locale === 'ko'
|
||||
? 'w-[48px] h-[48px]'
|
||||
: 'w-[40px] h-[40px] md:w-[48px] md:h-[48px]',
|
||||
'flex items-center',
|
||||
locale === 'ko' ? 'gap-[12px]' : 'gap-2 md:gap-[12px]',
|
||||
]"
|
||||
>
|
||||
<img
|
||||
:src="
|
||||
formatPathHost(card.benefitIcon, { imageType: 'common' })
|
||||
"
|
||||
:alt="card.benefitTitle"
|
||||
<div
|
||||
v-if="card.benefitIcon"
|
||||
:class="[
|
||||
'object-contain rounded-2xl',
|
||||
'bg-[#3C75FF] rounded-[8px] flex items-center justify-center',
|
||||
locale === 'ko'
|
||||
? 'w-[48px] h-[48px]'
|
||||
: 'w-[40px] h-[40px] md:w-[48px] md:h-[48px]',
|
||||
]"
|
||||
loading="lazy"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1 flex flex-col text-left">
|
||||
<div
|
||||
:class="[
|
||||
'text-[#3C75FF] font-bold',
|
||||
locale === 'ko'
|
||||
? 'text-[14px] md:text-[18px] leading-[22px] md:leading-[26px] tracking-[-0.42px] md:tracking-[-0.54px]'
|
||||
: 'text-[14px] md:text-[18px] leading-[20px] md:leading-[26px] tracking-[-0.42px] md:tracking-[-0.54px]',
|
||||
]"
|
||||
>
|
||||
{{ card.benefitTitle }}
|
||||
<img
|
||||
:src="
|
||||
formatPathHost(card.benefitIcon, {
|
||||
imageType: 'common',
|
||||
})
|
||||
"
|
||||
:alt="card.benefitTitle"
|
||||
:class="[
|
||||
'object-contain rounded-2xl',
|
||||
locale === 'ko'
|
||||
? 'w-[48px] h-[48px]'
|
||||
: 'w-[40px] h-[40px] md:w-[48px] md:h-[48px]',
|
||||
]"
|
||||
loading="lazy"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="card.benefitDesc"
|
||||
:class="[
|
||||
'text-[#3C75FF] font-[400] opacity-90',
|
||||
locale === 'ko'
|
||||
? 'text-[12px] md:text-[13px] leading-[18px] md:leading-[22px] tracking-[-0.325px]'
|
||||
: 'text-[12px] md:text-[13px] leading-[18px] md:leading-[22px] tracking-[-0.24px] md:tracking-[-0.325px]',
|
||||
]"
|
||||
>
|
||||
{{ card.benefitDesc }}
|
||||
<div class="flex-1 flex flex-col text-left">
|
||||
<div
|
||||
:class="[
|
||||
'text-[#3C75FF] font-bold',
|
||||
locale === 'ko'
|
||||
? 'text-[14px] md:text-[18px] leading-[22px] md:leading-[26px] tracking-[-0.42px] md:tracking-[-0.54px]'
|
||||
: 'text-[14px] md:text-[18px] leading-[20px] md:leading-[26px] tracking-[-0.42px] md:tracking-[-0.54px]',
|
||||
]"
|
||||
>
|
||||
{{ card.benefitTitle }}
|
||||
</div>
|
||||
<div
|
||||
v-if="card.benefitDesc"
|
||||
:class="[
|
||||
'text-[#3C75FF] font-[400] opacity-90',
|
||||
locale === 'ko'
|
||||
? 'text-[12px] md:text-[13px] leading-[18px] md:leading-[22px] tracking-[-0.325px]'
|
||||
: 'text-[12px] md:text-[13px] leading-[18px] md:leading-[22px] tracking-[-0.24px] md:tracking-[-0.325px]',
|
||||
]"
|
||||
>
|
||||
{{ card.benefitDesc }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Button -->
|
||||
<AtomsButton
|
||||
v-if="card.status === 'N'"
|
||||
type="external"
|
||||
button-size="size-small md:size-large"
|
||||
background-color="#383838"
|
||||
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"
|
||||
<!-- Button -->
|
||||
<AtomsButton
|
||||
v-if="card.status === 'N'"
|
||||
type="external"
|
||||
button-size="size-small md:size-large"
|
||||
background-color="#383838"
|
||||
text-color="#FFFFFF"
|
||||
@click="
|
||||
isLogin
|
||||
? handleSecureSetting(card.url)
|
||||
: checkLoginValidation()
|
||||
"
|
||||
>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
@@ -372,29 +378,29 @@ onMounted(() => {
|
||||
: 'w-full md:flex-1 p-5 md:p-8 gap-2 md:gap-3',
|
||||
]"
|
||||
>
|
||||
<h5
|
||||
:class="[
|
||||
'text-[#333333] font-bold',
|
||||
locale === 'ko'
|
||||
? 'text-[20px] leading-[30px] tracking-[-0.6px]'
|
||||
: 'text-[16px] md:text-[20px] leading-[24px] md:leading-[30px] tracking-[-0.48px] md: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"
|
||||
<h5
|
||||
: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] font-[400]',
|
||||
'text-[#333333] font-bold',
|
||||
locale === 'ko'
|
||||
? 'text-[14px] leading-[24px] tracking-[-0.42px]'
|
||||
: 'text-[14px] leading-[24px] tracking-[-0.42px]',
|
||||
? 'text-[20px] leading-[30px] tracking-[-0.6px]'
|
||||
: 'text-[16px] md:text-[20px] leading-[24px] md:leading-[30px] tracking-[-0.48px] md:tracking-[-0.6px]',
|
||||
]"
|
||||
></li>
|
||||
</ul>
|
||||
>
|
||||
{{ 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] font-[400]',
|
||||
locale === 'ko'
|
||||
? 'text-[14px] leading-[24px] tracking-[-0.42px]'
|
||||
: 'text-[14px] leading-[24px] tracking-[-0.42px]',
|
||||
]"
|
||||
></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,13 +6,17 @@ import {
|
||||
getComponentContainer,
|
||||
} from '#layers/utils/dataUtil'
|
||||
import { getYouTubeThumbnail } from '#layers/utils/youtubeUtil'
|
||||
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
import type {
|
||||
PageDataTemplateComponents,
|
||||
PageDataResourceGroups,
|
||||
} from '#layers/types/api/pageData'
|
||||
import type { OperateGroupItem } from '#layers/types/api/operateResources'
|
||||
|
||||
interface Props {
|
||||
id?: string
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -49,6 +53,23 @@ const officialUrlData = computed(
|
||||
() => getComponentGroup(props.components, 'officialUrl')?.display?.text ?? ''
|
||||
)
|
||||
|
||||
const arrowsData: PageDataResourceGroups = [
|
||||
{
|
||||
tracking: {
|
||||
action_type: 'click',
|
||||
click_item: '1. 컨텐츠 리스트 - 네비게이션(좌)',
|
||||
click_sarea: 'Home_GameInfo__arrow',
|
||||
},
|
||||
},
|
||||
{
|
||||
tracking: {
|
||||
action_type: 'click',
|
||||
click_item: '1. 컨텐츠 리스트 - 네비게이션(우)',
|
||||
click_sarea: 'Home_GameInfo__arrow',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const { data: slideData } = await useAsyncData(
|
||||
`fx-video-01-resources-${pageData.value?.page_seq}-${pageData.value?.page_ver}-${props.pageVerTmplSeq}`,
|
||||
async () => {
|
||||
@@ -137,11 +158,12 @@ const handleLoadMoreRecent = () => {
|
||||
<div
|
||||
class="relative content-static bg-[#fff] rounded-[12px] md:rounded-[16px]"
|
||||
>
|
||||
<BlocksSlideFade
|
||||
<WidgetsSlideFade
|
||||
v-model:index="currentRecommendedIndex"
|
||||
:autoplay="recommendedVideos.length > 1"
|
||||
:interval="3000"
|
||||
:arrows="recommendedVideos.length > 1"
|
||||
:arrows-data="arrowsData"
|
||||
:pagination="false"
|
||||
:drag="false"
|
||||
>
|
||||
@@ -185,7 +207,7 @@ const handleLoadMoreRecent = () => {
|
||||
</p>
|
||||
</div>
|
||||
</SplideSlide>
|
||||
</BlocksSlideFade>
|
||||
</WidgetsSlideFade>
|
||||
<div v-if="recommendedVideos.length > 1" class="splide-pagination">
|
||||
<span class="font-[700] text-[#1F1F1F]">
|
||||
{{ currentRecommendedIndex + 1 }}
|
||||
@@ -253,20 +275,20 @@ const handleLoadMoreRecent = () => {
|
||||
.splide {
|
||||
@apply pb-[68px] sm:pb-[0];
|
||||
}
|
||||
.splide:deep(.splide-arrows) {
|
||||
.splide:deep(.splide__arrows) {
|
||||
@apply block;
|
||||
}
|
||||
.splide:deep(.splide-arrows) .splide-arrow {
|
||||
.splide:deep(.splide__arrows) .splide-arrow {
|
||||
@apply block top-[unset] bottom-[20px] translate-y-0 bg-cover bg-center bg-no-repeat
|
||||
sm:bottom-[24px] md:bottom-[36px] lg:bottom-[60px];
|
||||
}
|
||||
.splide:deep(.splide-arrows) .arrow-prev {
|
||||
.splide:deep(.splide__arrows) .splide__arrow--prev {
|
||||
@apply left-[20px] bg-[image:url('/images/common/btn_system_arrow_prev.png')]
|
||||
sm:left-[calc(60.3%+21px)]
|
||||
md:left-[calc(56%+39px)]
|
||||
lg:left-[790px];
|
||||
}
|
||||
.splide:deep(.splide-arrows) .arrow-next {
|
||||
.splide:deep(.splide__arrows) .splide__arrow--next {
|
||||
@apply right-[20px] bg-[image:url('/images/common/btn_system_arrow_next.png')]
|
||||
sm:right-[28px]
|
||||
md:right-[unset] md:left-[calc(56%+99px)]
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { CwmsArticleItem } from '#layers/types/api/cwmsArticle'
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -15,7 +16,6 @@ const props = defineProps<Props>()
|
||||
const gameDataStore = useGameDataStore()
|
||||
const pageDataStore = usePageDataStore()
|
||||
const { getCwmsArticle } = useCwmsArticle()
|
||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||
const { locale } = useI18n()
|
||||
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
@@ -24,19 +24,23 @@ const { pageData } = storeToRefs(pageDataStore)
|
||||
const boardId = computed(
|
||||
() => getComponentGroup(props.components, 'boardId')?.display?.text
|
||||
)
|
||||
const paginationData = computed(() => {
|
||||
return getComponentGroupAry(props.components, 'pagination')
|
||||
})
|
||||
const backgroundData = computed(() =>
|
||||
getComponentGroup(props.components, 'background')
|
||||
)
|
||||
const mainTitleData = computed(() =>
|
||||
getComponentGroup(props.components, 'mainTitle')
|
||||
)
|
||||
const buttonListData = ref(getComponentGroupAry(props.components, 'buttonList'))
|
||||
const descriptionData = computed(() =>
|
||||
getComponentGroup(props.components, 'description')
|
||||
)
|
||||
const arrowsData = computed(() =>
|
||||
getComponentGroupAry(props.components, 'arrow')
|
||||
)
|
||||
const paginationData = computed(() => {
|
||||
return getComponentGroupAry(props.components, 'pagination')
|
||||
})
|
||||
|
||||
const buttonListData = ref(getComponentGroupAry(props.components, 'buttonList'))
|
||||
|
||||
const { data: slideData } = await useAsyncData(
|
||||
`gr-board-01-resources-${pageData.value?.page_seq}-${pageData.value?.page_ver}-${props.pageVerTmplSeq}`,
|
||||
@@ -103,12 +107,6 @@ const getArticleUrl = (articleId: string) => {
|
||||
}
|
||||
return `${communityUrl}/view/${articleId}`
|
||||
}
|
||||
|
||||
const onArrowClick = (direction, targetIndex) => {
|
||||
const arrowGroupAry = getComponentGroupAry(props.components, 'arrow')
|
||||
const logTracking = arrowGroupAry?.[direction === 'prev' ? 0 : 1]
|
||||
sendLog(locale.value, useAnalyticsLogDataDirect(logTracking, 1))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -124,12 +122,12 @@ const onArrowClick = (direction, targetIndex) => {
|
||||
class="title-md max-w-[944px] mx-[20px] sm:mx-[40px]"
|
||||
/>
|
||||
<ClientOnly>
|
||||
<BlocksSlideDefault
|
||||
<WidgetsSlideDefault
|
||||
v-if="slideLength > 0"
|
||||
:slide-item-length="slideLength"
|
||||
v-bind="splideOptions"
|
||||
:slide-item-length="slideLength"
|
||||
:arrows-data="arrowsData"
|
||||
class="w-full"
|
||||
@arrow-click="onArrowClick"
|
||||
>
|
||||
<SplideSlide
|
||||
v-for="(item, index) in slideData"
|
||||
@@ -141,16 +139,16 @@ const onArrowClick = (direction, targetIndex) => {
|
||||
:description="globalDateFormat(item.create_datetime, locale)"
|
||||
:img-path="item.media_thumbnail_url"
|
||||
:url="getArticleUrl(item.article_id)"
|
||||
:analytics-sarea="pageVerTmplNameEn"
|
||||
link-target="_blank"
|
||||
/>
|
||||
</div>
|
||||
</SplideSlide>
|
||||
</BlocksSlideDefault>
|
||||
</WidgetsSlideDefault>
|
||||
</ClientOnly>
|
||||
<WidgetsButtonList
|
||||
v-if="buttonListData"
|
||||
:resources-data="buttonListData"
|
||||
:page-ver-tmpl-seq="props.pageVerTmplSeq"
|
||||
class="mt-[48px] md:mt-[64px]"
|
||||
/>
|
||||
<WidgetsDescription
|
||||
@@ -166,10 +164,10 @@ const onArrowClick = (direction, targetIndex) => {
|
||||
.splide {
|
||||
@apply mt-[24px] md:max-w-[776px] md:mt-[48px] md:mx-auto md:px-[72px] lg:max-w-[1428px];
|
||||
}
|
||||
.splide:deep(.arrow-prev) {
|
||||
.splide:deep(.arsplide__arrow--prev) {
|
||||
@apply top-[calc(50%-28px)] left-[0];
|
||||
}
|
||||
.splide:deep(.arrow-next) {
|
||||
.splide:deep(.splide__arrow--next) {
|
||||
@apply top-[calc(50%-28px)] right-[0];
|
||||
}
|
||||
.slide-inner {
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -56,7 +57,6 @@ const buttonListData = computed(() => {
|
||||
<WidgetsButtonList
|
||||
v-if="buttonListData"
|
||||
:resources-data="buttonListData"
|
||||
:page-ver-tmpl-seq="props.pageVerTmplSeq"
|
||||
class="mt-[56px]"
|
||||
/>
|
||||
<WidgetsDescription
|
||||
|
||||
@@ -9,10 +9,14 @@ import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
|
||||
const splideRef = ref<SplideSlide | null>(null)
|
||||
const currentSlideIndex = ref<number | null>(null)
|
||||
|
||||
@@ -23,17 +27,24 @@ const paginationData = computed(() => {
|
||||
return getComponentGroupAry(props.components, 'pagination')
|
||||
})
|
||||
|
||||
const goToSlide = (index: number) => {
|
||||
const goToSlide = (index: number, title: string) => {
|
||||
const splide = splideRef.value?.splide
|
||||
const paginationAnalytics = {
|
||||
action_type: 'click',
|
||||
click_item: title,
|
||||
click_sarea: props.pageVerTmplNameEn,
|
||||
}
|
||||
|
||||
if (splide) {
|
||||
splide.go(index)
|
||||
sendLog(locale.value, useAnalyticsData(paginationAnalytics))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="section-standard">
|
||||
<BlocksSlideFade
|
||||
<WidgetsSlideFade
|
||||
v-if="slideData"
|
||||
ref="splideRef"
|
||||
v-model:index="currentSlideIndex"
|
||||
@@ -66,7 +77,7 @@ const goToSlide = (index: number) => {
|
||||
/>
|
||||
</div>
|
||||
</SplideSlide>
|
||||
</BlocksSlideFade>
|
||||
</WidgetsSlideFade>
|
||||
<div
|
||||
v-if="slideData && slideData.length > 1"
|
||||
class="splide-pagination"
|
||||
@@ -88,7 +99,12 @@ const goToSlide = (index: number) => {
|
||||
'btn-pagination',
|
||||
{ 'is-active': currentSlideIndex === index },
|
||||
]"
|
||||
@click="goToSlide(index)"
|
||||
@click="
|
||||
goToSlide(
|
||||
index,
|
||||
getComponentGroup(item, 'pagenaviTitle')?.display?.text || ''
|
||||
)
|
||||
"
|
||||
>
|
||||
<span class="item-bullet"></span>
|
||||
<span class="item-title">
|
||||
@@ -105,7 +121,7 @@ const goToSlide = (index: number) => {
|
||||
|
||||
<style scoped>
|
||||
.splide-pagination {
|
||||
@apply overflow-hidden flex items-center justify-center absolute bottom-10 left-1/2 max-w-full -translate-x-1/2 z-[5] md:bottom-[96px];
|
||||
@apply flex items-center justify-center absolute bottom-10 left-1/2 max-w-full -translate-x-1/2 z-[5] md:bottom-[96px];
|
||||
}
|
||||
.btn-pagination {
|
||||
@apply relative;
|
||||
@@ -122,7 +138,7 @@ const goToSlide = (index: number) => {
|
||||
color: var(--pagination-disabled);
|
||||
}
|
||||
.progress-bar {
|
||||
@apply relative w-[68px] h-[1px] overflow-hidden md:w-[184px];
|
||||
@apply relative w-[62px] h-[1px] overflow-hidden xs:w-[68px] md:w-[184px];
|
||||
background-color: var(--pagination-disabled);
|
||||
}
|
||||
.progress-fill {
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -23,6 +24,9 @@ const thumbnailData = computed(() => {
|
||||
.map(item => item?.pagenaviThumbnail?.groups?.[0])
|
||||
.filter((group): group is NonNullable<typeof group> => group != null)
|
||||
})
|
||||
const arrowsData = computed(() => {
|
||||
return getComponentGroupAry(props.components, 'arrow')
|
||||
})
|
||||
const paginationData = computed(() => {
|
||||
return getComponentGroupAry(props.components, 'pagination')
|
||||
})
|
||||
@@ -30,10 +34,12 @@ const paginationData = computed(() => {
|
||||
|
||||
<template>
|
||||
<section class="section-standard">
|
||||
<BlocksSlideThumbnail
|
||||
<WidgetsSlideThumbnail
|
||||
v-model:index="currentSlideIndex"
|
||||
:thumbnail-data="thumbnailData"
|
||||
:pagination-data="paginationData"
|
||||
:arrows-data="arrowsData"
|
||||
:analytics-sarea="props.pageVerTmplNameEn"
|
||||
>
|
||||
<SplideSlide v-for="(item, index) in slideData" :key="index">
|
||||
<WidgetsBackground
|
||||
@@ -59,6 +65,6 @@ const paginationData = computed(() => {
|
||||
/>
|
||||
</div>
|
||||
</SplideSlide>
|
||||
</BlocksSlideThumbnail>
|
||||
</WidgetsSlideThumbnail>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -32,6 +33,9 @@ const thumbnailData = computed(() => {
|
||||
.map(item => item?.pagenaviThumbnail?.groups?.[0])
|
||||
.filter((group): group is NonNullable<typeof group> => group != null)
|
||||
})
|
||||
const arrowsData = computed(() => {
|
||||
return getComponentGroupAry(props.components, 'arrow')
|
||||
})
|
||||
const paginationData = computed(() => {
|
||||
return getComponentGroupAry(props.components, 'pagination')
|
||||
})
|
||||
@@ -44,10 +48,12 @@ const getVideoSrc = (item: PageDataTemplateComponent) => {
|
||||
|
||||
<template>
|
||||
<section class="section-standard">
|
||||
<BlocksSlideThumbnail
|
||||
<WidgetsSlideThumbnail
|
||||
v-model:index="currentSlideIndex"
|
||||
:thumbnail-data="thumbnailData"
|
||||
:pagination-data="paginationData"
|
||||
:arrows-data="arrowsData"
|
||||
:analytics-sarea="props.pageVerTmplNameEn"
|
||||
>
|
||||
<SplideSlide v-for="(item, index) in slideData" :key="index">
|
||||
<WidgetsBackground
|
||||
@@ -91,6 +97,6 @@ const getVideoSrc = (item: PageDataTemplateComponent) => {
|
||||
/>
|
||||
</div>
|
||||
</SplideSlide>
|
||||
</BlocksSlideThumbnail>
|
||||
</WidgetsSlideThumbnail>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -12,13 +12,11 @@ import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||
|
||||
const playingSlideIndex = ref<number | null>(null)
|
||||
let stopVideoTimeoutId: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
@@ -36,32 +34,26 @@ const slideData = computed(() => {
|
||||
.map(item => item.media?.groups?.[0])
|
||||
.filter((group): group is NonNullable<typeof group> => group != null)
|
||||
})
|
||||
const arrowsData = computed(() => {
|
||||
return getComponentGroupAry(props.components, 'arrow')
|
||||
})
|
||||
const paginationData = computed(() => {
|
||||
return getComponentGroupAry(props.components, 'pagination')
|
||||
})
|
||||
|
||||
const getVideoPlayTracking = (item: string) => {
|
||||
return {
|
||||
action_type: 'click',
|
||||
click_item: `${item}_영상재생`,
|
||||
click_sarea: props.pageVerTmplNameEn,
|
||||
}
|
||||
}
|
||||
|
||||
const handleVideoClick = (index: number) => {
|
||||
playingSlideIndex.value = index
|
||||
|
||||
const group = getComponentGroup(props.components, 'videoPlay')
|
||||
if (!group || !group.tracking) return
|
||||
|
||||
const base = group.tracking.click_item || ''
|
||||
const next = base
|
||||
? base.replace(/(^.*_)(\d+)$/, `$1${index}`) === base
|
||||
? `${base}_${index}`
|
||||
: base.replace(/(^.*_)(\d+)$/, `$1${index}`)
|
||||
: `${index}`
|
||||
|
||||
const sendingGroup = {
|
||||
...group,
|
||||
tracking: { ...group.tracking, click_item: next },
|
||||
}
|
||||
|
||||
sendLog(
|
||||
locale.value,
|
||||
useAnalyticsLogDataDirect(sendingGroup, props.pageVerTmplSeq)
|
||||
)
|
||||
}
|
||||
|
||||
const handleSplideMove = () => {
|
||||
@@ -78,12 +70,6 @@ const handleSplideMove = () => {
|
||||
}, 600)
|
||||
}
|
||||
|
||||
const onArrowClick = (direction, _targetIndex) => {
|
||||
const arrowGroupAry = getComponentGroupAry(props.components, 'arrow')
|
||||
const logTracking = arrowGroupAry?.[direction === 'prev' ? 0 : 1]
|
||||
sendLog(locale.value, useAnalyticsLogDataDirect(logTracking, 1))
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// 타이머 정리
|
||||
if (stopVideoTimeoutId) {
|
||||
@@ -102,14 +88,15 @@ onBeforeUnmount(() => {
|
||||
:resources-data="mainTitleData"
|
||||
class="title-md max-w-[944px]"
|
||||
/>
|
||||
<BlocksSlideThumbnail
|
||||
:drag="false"
|
||||
<WidgetsSlideThumbnail
|
||||
:thumbnail-data="slideData"
|
||||
:pagination-data="paginationData"
|
||||
:drag="false"
|
||||
variant="media"
|
||||
class="mt-[24px] md:mt-[32px]"
|
||||
:pagination-data="paginationData"
|
||||
:arrows="slideData.length > 5"
|
||||
@arrow-click="onArrowClick"
|
||||
:arrows-data="arrowsData"
|
||||
:analytics-sarea="props.pageVerTmplNameEn"
|
||||
@move="handleSplideMove"
|
||||
>
|
||||
<SplideSlide
|
||||
@@ -128,6 +115,7 @@ onBeforeUnmount(() => {
|
||||
<AtomsButtonPlay
|
||||
v-if="playingSlideIndex !== index"
|
||||
class="btn-play"
|
||||
:tracking="getVideoPlayTracking(item?.group_label)"
|
||||
@click="handleVideoClick(index)"
|
||||
/>
|
||||
<transition name="fade">
|
||||
@@ -142,7 +130,7 @@ onBeforeUnmount(() => {
|
||||
</transition>
|
||||
</template>
|
||||
</SplideSlide>
|
||||
</BlocksSlideThumbnail>
|
||||
</WidgetsSlideThumbnail>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -23,6 +24,9 @@ const mainTitleData = computed(() =>
|
||||
const slideData = computed(() => {
|
||||
return getComponentContainer(props.components, 'group_sets', { minLength: 4 })
|
||||
})
|
||||
const arrowsData = computed(() =>
|
||||
getComponentGroupAry(props.components, 'arrow')
|
||||
)
|
||||
|
||||
const buttonListData = ref(
|
||||
getComponentGroupAry(slideData?.value[0], 'buttonList')
|
||||
@@ -49,13 +53,6 @@ const handleSplideMove = (_splide: SplideType, newIndex: number) => {
|
||||
'buttonList'
|
||||
)
|
||||
}
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||
const onArrowClick = (direction, targetIndex) => {
|
||||
const arrowGroupAry = getComponentGroupAry(props.components, 'arrow')
|
||||
const logTracking = arrowGroupAry?.[direction === 'prev' ? 0 : 1]
|
||||
sendLog(locale.value, useAnalyticsLogDataDirect(logTracking, 1))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -71,14 +68,14 @@ const onArrowClick = (direction, targetIndex) => {
|
||||
:resources-data="mainTitleData"
|
||||
class="title-md max-w-[944px] mx-[20px] sm:mx-[40px]"
|
||||
/>
|
||||
<BlocksSlideCenterFocus
|
||||
<WidgetsSlideCenterFocus
|
||||
v-if="slideData"
|
||||
:slide-item-size="slideItemSize"
|
||||
:slide-item-length="slideData?.length"
|
||||
:arrows-data="arrowsData"
|
||||
:pagination="false"
|
||||
class="mt-[24px] md:mt-[48px]"
|
||||
@move="handleSplideMove"
|
||||
@arrow-click="onArrowClick"
|
||||
>
|
||||
<SplideSlide v-for="(item, index) in slideData" :key="index">
|
||||
<div class="slide-inner border-line mt-auto">
|
||||
@@ -89,11 +86,10 @@ const onArrowClick = (direction, targetIndex) => {
|
||||
/>
|
||||
</div>
|
||||
</SplideSlide>
|
||||
</BlocksSlideCenterFocus>
|
||||
</WidgetsSlideCenterFocus>
|
||||
<WidgetsButtonList
|
||||
v-if="buttonListData"
|
||||
:resources-data="buttonListData"
|
||||
:page-ver-tmpl-seq="props.pageVerTmplSeq"
|
||||
class="mt-[40px] md:mt-[56px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -23,6 +24,10 @@ const mainTitleData = computed(() =>
|
||||
const slideData = computed(() =>
|
||||
getComponentContainer(props.components, 'group_sets', { minLength: 4 })
|
||||
)
|
||||
const arrowsData = computed(() =>
|
||||
getComponentGroupAry(props.components, 'arrow')
|
||||
)
|
||||
|
||||
const imgTitleData = ref(getComponentGroup(slideData?.value[0], 'imgTitle'))
|
||||
const descriptionData = ref(
|
||||
getComponentGroup(slideData?.value[0], 'description')
|
||||
@@ -60,13 +65,6 @@ const handleSplideMove = (
|
||||
'buttonList'
|
||||
)
|
||||
}
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||
const onArrowClick = (direction, targetIndex) => {
|
||||
const arrowGroupAry = getComponentGroupAry(props.components, 'arrow')
|
||||
const logTracking = arrowGroupAry?.[direction === 'prev' ? 0 : 1]
|
||||
sendLog(locale.value, useAnalyticsLogDataDirect(logTracking, 1))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -80,14 +78,14 @@ const onArrowClick = (direction, targetIndex) => {
|
||||
:resources-data="mainTitleData"
|
||||
class="title-md max-w-[944px] mx-[20px] sm:mx-[40px]"
|
||||
/>
|
||||
<BlocksSlideCenterHighlight
|
||||
<WidgetsSlideCenterHighlight
|
||||
v-if="slideData"
|
||||
:slide-item-size="slideItemSize"
|
||||
:slide-item-length="slideData?.length"
|
||||
:arrows-data="arrowsData"
|
||||
:pagination="false"
|
||||
class="mt-[24px] md:mt-[48px]"
|
||||
@move="handleSplideMove"
|
||||
@arrow-click="onArrowClick"
|
||||
>
|
||||
<SplideSlide v-for="(item, index) in slideData" :key="index">
|
||||
<div class="slide-inner border-line">
|
||||
@@ -98,7 +96,7 @@ const onArrowClick = (direction, targetIndex) => {
|
||||
/>
|
||||
</div>
|
||||
</SplideSlide>
|
||||
</BlocksSlideCenterHighlight>
|
||||
</WidgetsSlideCenterHighlight>
|
||||
<WidgetsSubTitle
|
||||
v-if="imgTitleData"
|
||||
:resources-data="imgTitleData"
|
||||
@@ -112,7 +110,6 @@ const onArrowClick = (direction, targetIndex) => {
|
||||
<WidgetsButtonList
|
||||
v-if="buttonListData"
|
||||
:resources-data="buttonListData"
|
||||
:page-ver-tmpl-seq="props.pageVerTmplSeq"
|
||||
class="mt-[32px] mx-[20px] sm:mx-[40px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -44,15 +45,10 @@ const buttonListData = computed(() =>
|
||||
:resources-data="descriptionData"
|
||||
class="w-full max-w-[355px] md:max-w-[944px]"
|
||||
/>
|
||||
<WidgetsVideoPlay
|
||||
v-if="videoPlayData"
|
||||
:resources-data="videoPlayData"
|
||||
:page-ver-tmpl-seq="props.pageVerTmplSeq"
|
||||
/>
|
||||
<WidgetsVideoPlay v-if="videoPlayData" :resources-data="videoPlayData" />
|
||||
<WidgetsButtonList
|
||||
v-if="buttonListData"
|
||||
:resources-data="buttonListData"
|
||||
:page-ver-tmpl-seq="props.pageVerTmplSeq"
|
||||
class="mt-[22px] md:mt-[52px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ import type { OperateGroupItem } from '#layers/types/api/operateResources'
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -19,7 +20,6 @@ const props = defineProps<Props>()
|
||||
const pageDataStore = usePageDataStore()
|
||||
const { getOperateResources } = useOperateResources()
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||
|
||||
const { pageData } = storeToRefs(pageDataStore)
|
||||
|
||||
@@ -35,6 +35,9 @@ const descriptionData = computed(() =>
|
||||
const videoPlayData = computed(() =>
|
||||
getComponentGroup(props.components, 'videoPlay')
|
||||
)
|
||||
const arrowsData = computed(() =>
|
||||
getComponentGroupAry(props.components, 'arrow')
|
||||
)
|
||||
|
||||
const { data: slideData } = await useAsyncData(
|
||||
`gr-visual-02-resources-${pageData.value?.page_seq}-${pageData.value?.page_ver}-${props.pageVerTmplSeq}`,
|
||||
@@ -75,12 +78,6 @@ const slideItemSize = {
|
||||
gap: 32,
|
||||
},
|
||||
}
|
||||
|
||||
const onArrowClick = direction => {
|
||||
const arrowGroupAry = getComponentGroupAry(props.components, 'arrow')
|
||||
const logTracking = arrowGroupAry?.[direction === 'prev' ? 0 : 1]
|
||||
sendLog(locale.value, useAnalyticsLogDataDirect(logTracking, 1))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -101,18 +98,14 @@ const onArrowClick = direction => {
|
||||
:resources-data="descriptionData"
|
||||
class="w-full max-w-[355px] mx-[20px] sm:mx-[40px] md:max-w-[944px]"
|
||||
/>
|
||||
<WidgetsVideoPlay
|
||||
v-if="videoPlayData"
|
||||
:resources-data="videoPlayData"
|
||||
:page-ver-tmpl-seq="props.pageVerTmplSeq"
|
||||
/>
|
||||
<BlocksSlideCenterHighlight
|
||||
<WidgetsVideoPlay v-if="videoPlayData" :resources-data="videoPlayData" />
|
||||
<WidgetsSlideCenterHighlight
|
||||
v-if="slideData && slideData.length > 0"
|
||||
:slide-item-size="slideItemSize"
|
||||
:slide-item-length="slideData.length"
|
||||
:arrows-data="arrowsData"
|
||||
:pagination="false"
|
||||
class="mt-[36px] md:mt-[60px]"
|
||||
@arrow-click="onArrowClick"
|
||||
>
|
||||
<SplideSlide v-for="(item, index) in slideData" :key="index">
|
||||
<BlocksCardNews
|
||||
@@ -121,10 +114,11 @@ const onArrowClick = direction => {
|
||||
:img-path="formatPathHost(item.img_path)"
|
||||
:url="item.url"
|
||||
:link-target="item.link_target"
|
||||
:analytics-sarea="`${pageVerTmplNameEn}_bannerList`"
|
||||
class="slide-inner"
|
||||
/>
|
||||
</SplideSlide>
|
||||
</BlocksSlideCenterHighlight>
|
||||
</WidgetsSlideCenterHighlight>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -11,39 +11,34 @@ import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||
|
||||
const currentSlideIndex = ref<number | null>(null)
|
||||
|
||||
const slideData = computed(() => {
|
||||
return getComponentContainer(props.components, 'group_sets')
|
||||
})
|
||||
const arrowsData = computed(() => {
|
||||
return getComponentGroupAry(props.components, 'arrow')
|
||||
})
|
||||
const paginationData = computed(() => {
|
||||
return getComponentGroupAry(props.components, 'pagination')
|
||||
})
|
||||
|
||||
const onArrowClick = direction => {
|
||||
const arrowGroupAry = getComponentGroupAry(props.components, 'arrow')
|
||||
const logTracking = arrowGroupAry?.[direction === 'prev' ? 0 : 1]
|
||||
sendLog(locale.value, useAnalyticsLogDataDirect(logTracking, 1))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="section-standard">
|
||||
<BlocksSlideFade
|
||||
<WidgetsSlideFade
|
||||
v-if="slideData"
|
||||
v-model:index="currentSlideIndex"
|
||||
:arrows="slideData.length > 1"
|
||||
:pagination="slideData.length > 1"
|
||||
class="h-full"
|
||||
:pagination-data="paginationData"
|
||||
@arrow-click="onArrowClick"
|
||||
:arrows-data="arrowsData"
|
||||
class="h-full"
|
||||
>
|
||||
<SplideSlide v-for="(item, index) in slideData" :key="index">
|
||||
<WidgetsBackground
|
||||
@@ -70,11 +65,10 @@ const onArrowClick = direction => {
|
||||
<WidgetsButtonList
|
||||
v-if="hasComponentGroup(item, 'buttonList')"
|
||||
:resources-data="getComponentGroupAry(item, 'buttonList')"
|
||||
:page-ver-tmpl-seq="props.pageVerTmplSeq"
|
||||
class="mt-[28px] md:mt-[52px]"
|
||||
/>
|
||||
</div>
|
||||
</SplideSlide>
|
||||
</BlocksSlideFade>
|
||||
</WidgetsSlideFade>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -14,7 +14,7 @@ interface AnalyticsMetaType {
|
||||
|
||||
interface AnalyticsType {
|
||||
analyticsNo: number
|
||||
gameId: string
|
||||
gameId?: string
|
||||
analyticsType?: AnalyticsMetaType
|
||||
analyticsVersion?: AnalyticsMetaType
|
||||
analyticsCode?: string
|
||||
@@ -25,8 +25,7 @@ interface AnalyticsType {
|
||||
}
|
||||
|
||||
interface AnalyticsDetailType extends AnalyticsType {
|
||||
detailNo: number
|
||||
// analyticsNo in AnalyticsType
|
||||
detailNo?: number
|
||||
sbNo?: string
|
||||
areaNo?: string
|
||||
event?: string
|
||||
@@ -81,7 +80,6 @@ interface AnalyticsLogDataTracking {
|
||||
click_sarea: string
|
||||
}
|
||||
|
||||
|
||||
export type {
|
||||
AnalyticsType,
|
||||
AnalyticsDetailType,
|
||||
@@ -89,5 +87,5 @@ export type {
|
||||
AnalyticsLogDataTracking,
|
||||
// [API] Req / Res -----
|
||||
ReqGetAnalytics,
|
||||
ResGetAnalytics
|
||||
ResGetAnalytics,
|
||||
}
|
||||
|
||||
@@ -43,3 +43,9 @@ export interface RequestObject {
|
||||
headers: Record<string, string>
|
||||
socket: { remoteAddress?: string }
|
||||
}
|
||||
|
||||
export interface TrackingObject {
|
||||
action_type: string
|
||||
click_item: string
|
||||
click_sarea: string
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { TrackingObject } from '#layers/types/api/common'
|
||||
|
||||
// API 요청 파라미터 타입
|
||||
export interface GameDataRequest {
|
||||
game_alias: string
|
||||
@@ -142,16 +144,9 @@ export interface GameDataResourceGroupBtnInfo {
|
||||
detail: Record<string, any>
|
||||
}
|
||||
|
||||
// 트래킹 타입
|
||||
export interface GameDataTracking {
|
||||
viewType: string
|
||||
actionType: string
|
||||
clickSarea: string
|
||||
}
|
||||
|
||||
export interface GameDataResourceGroup {
|
||||
btn_info?: GameDataResourceGroupBtnInfo
|
||||
tracking: GameDataTracking
|
||||
tracking: TrackingObject
|
||||
}
|
||||
|
||||
type MarketPlatform = 'pc' | 'app_store' | 'google_play'
|
||||
@@ -181,7 +176,7 @@ export interface GameDataMenu {
|
||||
url_path: string
|
||||
link_target: string
|
||||
children: GameDataMenuChildren
|
||||
tracking_json: string | GameDataTracking // JSON 문자열 또는 객체로 변경
|
||||
tracking_json: string | TrackingObject // JSON 문자열 또는 객체로 변경
|
||||
}
|
||||
|
||||
// GNB 설정 타입
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { TrackingObject } from '#layers/types/api/common'
|
||||
|
||||
// API 요청 파라미터 타입
|
||||
export interface PageDataRequest {
|
||||
game_alias: string
|
||||
@@ -51,7 +53,7 @@ export interface PageDataLnbMenu {
|
||||
menu_name: string
|
||||
target_type: number
|
||||
page_ver_tmpl_name_en: string
|
||||
tracking_json: Record<string, PageDataTracking>
|
||||
tracking_json: Record<string, TrackingObject>
|
||||
children?: Record<string, PageDataLnbMenu>
|
||||
}
|
||||
|
||||
@@ -93,6 +95,7 @@ export interface PageDataResourceGroupBtnInfo {
|
||||
|
||||
// 리소스 그룹 타입
|
||||
export interface PageDataResourceGroup {
|
||||
group_label?: string
|
||||
resource_type?: PageDataResourceGroupType
|
||||
res_path?: PageDataResourceGroupResPath
|
||||
btn_info?: PageDataResourceGroupBtnInfo
|
||||
@@ -101,7 +104,7 @@ export interface PageDataResourceGroup {
|
||||
color_code?: string
|
||||
color_name?: string
|
||||
}
|
||||
tracking?: PageDataTracking
|
||||
tracking?: TrackingObject
|
||||
}
|
||||
|
||||
export type PageDataResourceGroups = PageDataResourceGroup[]
|
||||
@@ -171,15 +174,8 @@ export interface PageDataApiResult {
|
||||
data: PageDataResponse | null
|
||||
error: string | null
|
||||
}
|
||||
// Tracking 타입
|
||||
export interface PageDataTracking {
|
||||
click_item: string
|
||||
action_type: string
|
||||
click_sarea: string
|
||||
}
|
||||
|
||||
// Arrow 컴포넌트 타입
|
||||
|
||||
export type PageDataArrowComponent = PageDataTemplateComponent & {
|
||||
groups: PageDataResourceGroups
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ export default {
|
||||
theme: {
|
||||
extend: {
|
||||
screens: {
|
||||
xxs: '0px', // 0px ~ 359px
|
||||
xs: '360px', // Mobile: 360px ~ 767px
|
||||
sm: '768px', // Tablet: 768px ~ 1023px
|
||||
md: '1024px', // PC: 1024px ~ 1439px
|
||||
|
||||
Reference in New Issue
Block a user