feat. 공통 로그 변경, 고정 템플릿 로그 추가
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
@import '@splidejs/vue-splide/css';
|
||||
@import '@vuepic/vue-datepicker/dist/main.css';
|
||||
|
||||
@import './base/_reset.css';
|
||||
@import './base/_theme.css';
|
||||
@import './base/_font.css';
|
||||
@@ -8,11 +11,8 @@
|
||||
@import './components/_layout.css';
|
||||
@import './components/_modal.css';
|
||||
@import './components/_splide.css';
|
||||
/* @import './components/_base-content.css'; */
|
||||
@import './components/_froala-style.css';
|
||||
|
||||
@import '@splidejs/vue-splide/css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
/* 에디터 콘텐츠 기본 스타일 유틸리티 */
|
||||
/* use-base 클래스를 추가하면 하위 자식 요소들에 기본 스타일이 적용됩니다 */
|
||||
@layer components {
|
||||
.use-base ul,
|
||||
.use-base ol {
|
||||
@apply list-disc pl-6;
|
||||
}
|
||||
.use-base ol {
|
||||
@apply list-decimal;
|
||||
}
|
||||
.use-base ul ul,
|
||||
.use-base ol ol,
|
||||
.use-base ul ol,
|
||||
.use-base ol ul {
|
||||
@apply mb-0;
|
||||
}
|
||||
|
||||
.use-base table {
|
||||
@apply w-full border-collapse;
|
||||
}
|
||||
.use-base .fr-alternate-rows tr:nth-child(even),
|
||||
.use-base thead,
|
||||
.use-base tfoot {
|
||||
@apply bg-[#FAFAFA];
|
||||
}
|
||||
.use-base th,
|
||||
.use-base td {
|
||||
@apply border border-[#D9D9D9] px-4 py-2 text-left;
|
||||
}
|
||||
.use-base th {
|
||||
@apply font-semibold border-[#D9D9D9];
|
||||
}
|
||||
|
||||
.use-base blockquote {
|
||||
@apply border-l-4 border-[#D9D9D9] pl-4 italic;
|
||||
}
|
||||
|
||||
.use-base h1 {
|
||||
@apply text-2xl;
|
||||
}
|
||||
.use-base h2 {
|
||||
@apply text-xl;
|
||||
}
|
||||
.use-base h3 {
|
||||
@apply text-lg;
|
||||
}
|
||||
.use-base h4 {
|
||||
@apply text-base;
|
||||
}
|
||||
.use-base h5 {
|
||||
@apply text-sm;
|
||||
}
|
||||
.use-base h6 {
|
||||
@apply text-xs;
|
||||
}
|
||||
|
||||
.use-base strong,
|
||||
.use-base b {
|
||||
@apply font-bold;
|
||||
}
|
||||
.use-base em,
|
||||
.use-base i {
|
||||
@apply italic;
|
||||
}
|
||||
.use-base u {
|
||||
@apply underline;
|
||||
}
|
||||
.use-base s {
|
||||
@apply line-through;
|
||||
}
|
||||
|
||||
.use-base a {
|
||||
@apply text-[#3C75FF] underline;
|
||||
}
|
||||
|
||||
.use-base img {
|
||||
@apply max-w-full h-auto;
|
||||
}
|
||||
|
||||
.use-base iframe {
|
||||
@apply max-w-full h-auto aspect-video;
|
||||
}
|
||||
|
||||
.use-base pre {
|
||||
@apply bg-[#FAFAFA] p-4 rounded overflow-x-auto mb-4;
|
||||
}
|
||||
.use-base code {
|
||||
@apply bg-[#FAFAFA] px-1 py-0.5 rounded text-sm;
|
||||
}
|
||||
.use-base pre code {
|
||||
@apply bg-transparent p-0;
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/* 에디터 콘텐츠 기본 스타일 유틸리티 */
|
||||
/* use-base 클래스를 추가하면 하위 자식 요소들에 기본 스타일이 적용됩니다 */
|
||||
@layer components {
|
||||
.use-base ul,
|
||||
.use-base ol {
|
||||
@apply list-disc pl-6;
|
||||
}
|
||||
.use-base ol {
|
||||
@apply list-decimal;
|
||||
}
|
||||
.use-base ul ul,
|
||||
.use-base ol ol,
|
||||
.use-base ul ol,
|
||||
.use-base ol ul {
|
||||
@apply mb-0;
|
||||
}
|
||||
|
||||
.use-base table {
|
||||
@apply w-full border-collapse;
|
||||
}
|
||||
.use-base thead {
|
||||
@apply bg-gray-100;
|
||||
}
|
||||
.use-base th,
|
||||
.use-base td {
|
||||
@apply border border-gray-300 px-4 py-2 text-left;
|
||||
}
|
||||
.use-base th {
|
||||
@apply font-semibold bg-gray-50;
|
||||
}
|
||||
.use-base tbody tr:nth-child(even) {
|
||||
@apply bg-gray-50;
|
||||
}
|
||||
|
||||
.use-base blockquote {
|
||||
@apply border-l-4 border-gray-300 pl-4 italic text-gray-700;
|
||||
}
|
||||
|
||||
.use-base h1 {
|
||||
@apply text-2xl;
|
||||
}
|
||||
.use-base h2 {
|
||||
@apply text-xl;
|
||||
}
|
||||
.use-base h3 {
|
||||
@apply text-lg;
|
||||
}
|
||||
.use-base h4 {
|
||||
@apply text-base;
|
||||
}
|
||||
.use-base h5 {
|
||||
@apply text-sm;
|
||||
}
|
||||
.use-base h6 {
|
||||
@apply text-xs;
|
||||
}
|
||||
|
||||
.use-base strong,
|
||||
.use-base b {
|
||||
@apply font-bold;
|
||||
}
|
||||
.use-base em,
|
||||
.use-base i {
|
||||
@apply italic;
|
||||
}
|
||||
.use-base u {
|
||||
@apply underline;
|
||||
}
|
||||
.use-base s {
|
||||
@apply line-through;
|
||||
}
|
||||
|
||||
.use-base a {
|
||||
@apply text-blue-600 underline;
|
||||
}
|
||||
.use-base a:hover {
|
||||
@apply text-blue-800;
|
||||
}
|
||||
|
||||
.use-base img {
|
||||
@apply max-w-full h-auto my-4;
|
||||
}
|
||||
|
||||
.use-base pre {
|
||||
@apply bg-gray-100 p-4 rounded overflow-x-auto mb-4;
|
||||
}
|
||||
.use-base code {
|
||||
@apply bg-gray-100 px-1 py-0.5 rounded text-sm;
|
||||
}
|
||||
.use-base pre code {
|
||||
@apply bg-transparent p-0;
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,10 @@ interface Props {
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
const handlePlayClick = () => {
|
||||
sendLog(locale.value, useAnalyticsData(props.tracking))
|
||||
sendLog(locale.value, props.tracking)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -63,17 +63,17 @@ const onSelectOption = (option: { [key: string | number]: any }): void => {
|
||||
<div
|
||||
v-if="isActive"
|
||||
:data-placement="props.placement"
|
||||
class="absolute z-[10] top-full left-0 translate-y-[4px] w-full py-[8px] border border-solid border-[1px] border-[rgba(0,0,0,0.3)] rounded-[8px] bg-white shadow-[0_4px_10px_0_rgba(0,0,0,0.10)]"
|
||||
class="absolute z-[10] top-full left-0 translate-y-[4px] w-full py-[8px] border-[1px] border-solid border-[rgba(0,0,0,0.3)] rounded-[8px] bg-white shadow-[0_4px_10px_0_rgba(0,0,0,0.10)]"
|
||||
>
|
||||
<ul class="relative flex flex-col items-center justify-start w-full">
|
||||
<li
|
||||
v-for="(option, index) in props.options"
|
||||
v-for="option in props.options"
|
||||
:key="String(option[props.labelName])"
|
||||
class="relative flex items-center justify-left w-full"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="relative flex items-center justify-left w-full py-[8px] pl-[40px] pr-[16px] bg-white text-left text-left text-[14px] font-[400] leading-[24px] tracking-[-0.42px] hover:bg-[rgba(0,0,0,0.04)]"
|
||||
class="relative flex items-center justify-left w-full py-[8px] pl-[40px] pr-[16px] bg-white text-left text-[14px] font-[400] leading-[24px] tracking-[-0.42px] hover:bg-[rgba(0,0,0,0.04)]"
|
||||
:class="
|
||||
selectedColor ? `text-[${selectedColor}]` : 'text-[#333333]'
|
||||
"
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import type { ClassType } from '#layers/types/Common'
|
||||
|
||||
interface Props {
|
||||
src: string
|
||||
type?: 'mp4' | 'webm'
|
||||
@@ -9,7 +7,7 @@ interface Props {
|
||||
muted?: boolean
|
||||
loop?: boolean
|
||||
bordered?: boolean
|
||||
class?: ClassType
|
||||
class?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
import type { TrackingObject } from '#layers/types/api/common'
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
const analytics = {
|
||||
action_type: 'click',
|
||||
click_item: '홈으로가기',
|
||||
click_sarea: 'HOME', // TODO: 확인 필요 컴포넌트 id가 뭔가염 뭔가염
|
||||
click_sarea: 'Home',
|
||||
} as TrackingObject
|
||||
</script>
|
||||
|
||||
@@ -17,7 +17,7 @@ const analytics = {
|
||||
type="link"
|
||||
to="/home"
|
||||
class="btn-home"
|
||||
@click="sendLog(locale, useAnalyticsData(analytics))"
|
||||
@click="sendLog(locale, analytics)"
|
||||
>
|
||||
<AtomsIconsHomeFill />
|
||||
</AtomsButtonCircle>
|
||||
|
||||
@@ -22,12 +22,6 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
const PLATFORM_LABEL_KEY: Record<PlatformTransformType, string> = {
|
||||
pc: 'PC',
|
||||
google_play: 'Google Play',
|
||||
app_store: 'App Store',
|
||||
} as const
|
||||
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const { tm } = useI18n()
|
||||
const device = useDevice()
|
||||
@@ -37,6 +31,12 @@ const { isProcessing, validateLauncher } = useCheckGameStart()
|
||||
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
|
||||
const PLATFORM_LABEL_KEY: Record<PlatformTransformType, string> = {
|
||||
pc: 'PC',
|
||||
google_play: 'Google Play',
|
||||
app_store: 'App Store',
|
||||
} as const
|
||||
|
||||
const PLATFORM_ICON_MAP: Record<Platform, string> = {
|
||||
google_play: 'AtomsIconsLogoGoogle',
|
||||
app_store: 'AtomsIconsLogoApple',
|
||||
|
||||
@@ -3,19 +3,19 @@ import type { TrackingObject } from '#layers/types/api/common'
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { y: windowY } = useWindowScroll({ behavior: 'smooth' })
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
const analytics = {
|
||||
action_type: 'click',
|
||||
click_item: 'TOP버튼',
|
||||
click_sarea: 'TOP', // TODO: 확인 필요 컴포넌트 id가 뭔가염 뭔가염
|
||||
click_sarea: 'TOP',
|
||||
} as TrackingObject
|
||||
|
||||
const showBtn = computed(() => windowY.value > 0)
|
||||
|
||||
const handleScrollToTop = () => {
|
||||
windowY.value = 0
|
||||
sendLog(locale.value, useAnalyticsData(analytics))
|
||||
sendLog(locale.value, analytics)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import type { TrackingObject } from '#layers/types/api/common'
|
||||
import type { TrackingObject, ColorObject } from '#layers/types/api/common'
|
||||
|
||||
const showSnsList = ref(false)
|
||||
|
||||
const { locale, tm } = useI18n()
|
||||
const gameDataStore = useGameDataStore()
|
||||
const modalStore = useModalStore()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
const { handleOpenToast } = modalStore
|
||||
@@ -17,11 +17,9 @@ const analytics = {
|
||||
} as TrackingObject
|
||||
|
||||
const snsBackgroundColor = computed(() => {
|
||||
const colorData = gameData.value?.comm_sns_bg_color_json?.display
|
||||
const colorCode = getColorCode({
|
||||
colorName: colorData?.color_name,
|
||||
colorCode: colorData?.color_code,
|
||||
})
|
||||
const colorData = gameData.value?.comm_sns_bg_color_json
|
||||
?.display as ColorObject
|
||||
const colorCode = getColorCodeFromData(colorData, 'none')
|
||||
return colorCode
|
||||
})
|
||||
const snsList = computed(() => {
|
||||
@@ -43,10 +41,7 @@ const handleCopy = async () => {
|
||||
const url = window.location.href
|
||||
await navigator.clipboard.writeText(url)
|
||||
handleOpenToast({ contentText: tm('Alert_Copy_Complete') })
|
||||
sendLog(
|
||||
locale.value,
|
||||
useAnalyticsData({ ...analytics, click_item: 'URL복사' })
|
||||
)
|
||||
sendLog(locale.value, { ...analytics, click_item: 'URL복사' })
|
||||
} catch (error) {
|
||||
console.error('[handleCopy] Error:', error)
|
||||
}
|
||||
@@ -78,15 +73,11 @@ const handleCopy = async () => {
|
||||
:href="item.url"
|
||||
target="_blank"
|
||||
class="sns-item"
|
||||
rel="noopener noreferrer"
|
||||
:style="{
|
||||
backgroundImage: `url(${formatPathHost(`/images/common/ic-v2-logo-${key}-fill.png`, { imageType: 'common' })})`,
|
||||
}"
|
||||
@click="
|
||||
sendLog(
|
||||
locale,
|
||||
useAnalyticsData({ ...analytics, click_item: key })
|
||||
)
|
||||
"
|
||||
@click="sendLog(locale, { ...analytics, click_item: key })"
|
||||
>
|
||||
<span class="sr-only">{{ key }}</span>
|
||||
</a>
|
||||
|
||||
@@ -13,7 +13,13 @@ interface Props {
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
const analytics = {
|
||||
action_type: 'click',
|
||||
click_item: props.title,
|
||||
click_sarea: props.analyticsSarea,
|
||||
}
|
||||
|
||||
const isNoImage = computed(() => {
|
||||
return !props.imgPath || props.imgPath === null
|
||||
@@ -21,15 +27,6 @@ const isNoImage = computed(() => {
|
||||
const isShowOverlay = computed(() => {
|
||||
return props.title || props.description
|
||||
})
|
||||
|
||||
const handleLinkClick = (title: string) => {
|
||||
const analytics = {
|
||||
click_item: title,
|
||||
action_type: 'click',
|
||||
click_sarea: props.analyticsSarea,
|
||||
}
|
||||
sendLog(locale.value, useAnalyticsData(analytics))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -64,7 +61,7 @@ const handleLinkClick = (title: string) => {
|
||||
:to="props.url"
|
||||
:target="props.linkTarget || '_self'"
|
||||
class="card-link"
|
||||
@click="handleLinkClick(props.title)"
|
||||
@click="sendLog(locale, analytics)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -399,7 +399,6 @@ onMounted(() => {
|
||||
</span>
|
||||
</template>
|
||||
<template
|
||||
class="date-picker-calendar-wrap"
|
||||
#month-year="{
|
||||
month,
|
||||
year,
|
||||
|
||||
@@ -7,7 +7,7 @@ const { locale } = useI18n()
|
||||
const pageDataStore = usePageDataStore()
|
||||
const scrollStore = useScrollStore()
|
||||
const breakpoints = useResponsiveBreakpoints()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
const { pageData } = storeToRefs(pageDataStore)
|
||||
|
||||
@@ -108,10 +108,7 @@ const handleLnbClick = (lnbItem: PageDataLnbMenu) => {
|
||||
: lnbItem.page_ver_tmpl_name_en
|
||||
|
||||
scrollStore.scrollToAnchor(targetId)
|
||||
sendLog(
|
||||
locale.value,
|
||||
useAnalyticsData(lnbItem.tracking_json as unknown as TrackingObject)
|
||||
)
|
||||
sendLog(locale.value, lnbItem.tracking_json as TrackingObject)
|
||||
}
|
||||
|
||||
watch(directions, newVal => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { PageDataResourceGroup } from '#layers/types/api/pageData'
|
||||
import { getColorCode } from '#layers/utils/styleUtil'
|
||||
import { isTypeImage, isTypeText } from '#layers/utils/dataUtil'
|
||||
|
||||
interface Props {
|
||||
@@ -15,8 +14,9 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
|
||||
const imagePaths = computed(() => getResourceSrc(props.resourcesData))
|
||||
const displayText = computed(() => props.resourcesData?.display?.text)
|
||||
const colorName = computed(() => props.resourcesData?.display?.color_name)
|
||||
const colorCode = computed(() => props.resourcesData?.display?.color_code)
|
||||
const displayColor = computed(() =>
|
||||
getColorCodeFromData(props.resourcesData?.display, 'none')
|
||||
)
|
||||
|
||||
// HTML 콘텐츠 정리 (줄바꿈 처리)
|
||||
const sanitizedContent = computed(() => {
|
||||
@@ -35,7 +35,7 @@ const sanitizedContent = computed(() => {
|
||||
<span
|
||||
v-else-if="isTypeText(resourcesData?.resource_type)"
|
||||
v-dompurify-html="sanitizedContent"
|
||||
:style="{ color: getColorCode({ colorName, colorCode }) }"
|
||||
:style="{ color: displayColor }"
|
||||
class="block"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -8,15 +8,12 @@ interface Props {
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
const handleArrowClick = (direction: 'prev' | 'next') => {
|
||||
if (props.arrowsData) {
|
||||
const arrowIndex = direction === 'prev' ? 0 : 1
|
||||
sendLog(
|
||||
locale.value,
|
||||
useAnalyticsData(props.arrowsData[arrowIndex]?.tracking)
|
||||
)
|
||||
sendLog(locale.value, props.arrowsData[arrowIndex]?.tracking)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -7,11 +7,11 @@ import type {
|
||||
|
||||
const { locale } = useI18n()
|
||||
const gameDomain = useGetGameDomain()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
const analytics = {
|
||||
action_type: 'click',
|
||||
click_sarea: 'eventNavigation',
|
||||
click_sarea: 'EventNavigation',
|
||||
}
|
||||
|
||||
const isEventNavigationOpen = ref(true)
|
||||
@@ -48,7 +48,7 @@ const toggleEventNavigation = () => {
|
||||
...analytics,
|
||||
click_item: isEventNavigationOpen.value ? '열기' : '닫기',
|
||||
} as TrackingObject
|
||||
sendLog(locale.value, useAnalyticsData(navigationAnalytics))
|
||||
sendLog(locale.value, navigationAnalytics)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -77,15 +77,13 @@ onMounted(async () => {
|
||||
<AtomsLocaleLink
|
||||
:to="item.page_url"
|
||||
:target="item.link_type === 2 ? '_blank' : '_self'"
|
||||
:rel="item.link_type === 2 ? 'noopener noreferrer' : undefined"
|
||||
class="item-link"
|
||||
@click="
|
||||
sendLog(
|
||||
locale,
|
||||
useAnalyticsData({
|
||||
...analytics,
|
||||
click_item: item.banner_title || item.promotion_name,
|
||||
})
|
||||
)
|
||||
sendLog(locale, {
|
||||
...analytics,
|
||||
click_item: item.banner_title || item.promotion_name,
|
||||
})
|
||||
"
|
||||
>
|
||||
<div class="item-thumbnail">
|
||||
|
||||
@@ -1,3 +1,184 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
FooterMenuItem,
|
||||
FooterData,
|
||||
DevCiConfig,
|
||||
} from '#layers/types/components/footer'
|
||||
|
||||
// Configuration
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
|
||||
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_footer.json'
|
||||
|
||||
// Multilingual
|
||||
const resultGetMultilingual = await useGetMultilingual({
|
||||
baseApiUrl: dataResourcesUrl,
|
||||
fileName: multilingualFileName,
|
||||
})
|
||||
const { tm, locale }: any = useI18n({
|
||||
useScope: 'local',
|
||||
messages: Object(resultGetMultilingual?.value?.multilingual),
|
||||
})
|
||||
|
||||
// Footer_caution 값이 있고 빈 객체가 아닌지 체크
|
||||
const hasCautionText = computed(() => {
|
||||
const value = tm('Footer_caution')
|
||||
|
||||
// null, undefined 체크
|
||||
if (value === null || value === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 빈 객체 체크
|
||||
if (
|
||||
typeof value === 'object' &&
|
||||
!Array.isArray(value) &&
|
||||
Object.keys(value).length === 0
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 문자열로 변환하여 빈 문자열 또는 '{}' 문자열 체크
|
||||
const stringValue = String(value).trim()
|
||||
if (stringValue === '' || stringValue === '{}') {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
const gameDataStore = useGameDataStore()
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
// const path = ref<string>(`${staticUrl}/local/template/${gameData.value.s3_folder_name}`)
|
||||
|
||||
// 공통다국어 data
|
||||
const footerLinks = computed((): FooterMenuItem[] => {
|
||||
const menu = (tm as any)('Footer_Menu')
|
||||
const menuItems = Array.isArray(menu) ? (menu as FooterMenuItem[]) : []
|
||||
|
||||
// ja일 때 fund_display_yn에 따라 id가 footerFund인 항목 처리
|
||||
if (locale.value === 'ja') {
|
||||
const fundDisplayYn = footerData.value?.fund_display_yn
|
||||
const fundDisplayUrl = footerData.value?.fund_display_url
|
||||
|
||||
// id가 footerFund인 항목의 인덱스 찾기
|
||||
const fundIndex = menuItems.findIndex(
|
||||
(item: any) => item.id === 'footerFund'
|
||||
)
|
||||
|
||||
// fund_display_yn이 'y'가 아니면 id가 footerFund인 항목 제거
|
||||
if (!fundDisplayYn) {
|
||||
return menuItems.filter((item: any) => item.id !== 'footerFund')
|
||||
}
|
||||
|
||||
// fund_display_yn이 'y'이면 id가 footerFund인 항목의 url 설정
|
||||
if (fundDisplayYn && fundIndex !== -1) {
|
||||
const updatedMenuItems = [...menuItems]
|
||||
updatedMenuItems[fundIndex] = {
|
||||
...updatedMenuItems[fundIndex],
|
||||
url: fundDisplayUrl || updatedMenuItems[fundIndex].url,
|
||||
}
|
||||
return updatedMenuItems
|
||||
}
|
||||
}
|
||||
|
||||
return menuItems
|
||||
})
|
||||
const footerData = ref(gameData.value?.footer_json as unknown as FooterData)
|
||||
const setDevCi = ref<DevCiConfig>({
|
||||
dev_ci_yn: gameData.value?.footer_dev_ci_img_yn as boolean,
|
||||
dev_ci_img_path: gameData.value?.footer_dev_ci_img_path as string,
|
||||
})
|
||||
|
||||
const useGameRating = computed<boolean>(() => {
|
||||
return footerData.value.use_game_rating
|
||||
})
|
||||
|
||||
///local/template/common/grades_age
|
||||
const getGameRatingImage = computed((): { type: string; image: string } => {
|
||||
// 안전하게 rating_type 값 확인
|
||||
if (
|
||||
!footerData.value?.game_rating_info?.rating_type ||
|
||||
typeof footerData.value.game_rating_info.rating_type !== 'string'
|
||||
) {
|
||||
return { type: '', image: '' }
|
||||
}
|
||||
|
||||
const contentInfo = footerData.value.game_rating_info.rating_type
|
||||
const ratingType = contentInfo.trim()
|
||||
|
||||
// 빈 문자열인 경우 처리
|
||||
if (!ratingType) {
|
||||
return { type: '', image: '' }
|
||||
}
|
||||
|
||||
const ageTypeMap: Record<string, string> = {
|
||||
'6': 'Type6',
|
||||
'12': 'Type12',
|
||||
'15': 'Type15',
|
||||
'18': 'Type18',
|
||||
'19': 'Type19',
|
||||
all: 'TypeAll',
|
||||
e: 'TypeExempt',
|
||||
}
|
||||
|
||||
const type = ageTypeMap[ratingType as keyof typeof ageTypeMap] || 'TypeTest'
|
||||
return {
|
||||
type,
|
||||
image: formatPathHost(
|
||||
`/images/common/grades_age/${locale.value}/${type}.svg`,
|
||||
{
|
||||
imageType: 'common',
|
||||
}
|
||||
),
|
||||
}
|
||||
})
|
||||
|
||||
const getContentInfoImage = computed((): string[] => {
|
||||
// 안전하게 content_info 값 확인
|
||||
if (
|
||||
!footerData.value?.game_rating_info?.content_info ||
|
||||
typeof footerData.value.game_rating_info.content_info !== 'string'
|
||||
) {
|
||||
return []
|
||||
}
|
||||
|
||||
const contentInfo = footerData.value.game_rating_info.content_info.split(',')
|
||||
contentInfo.pop()
|
||||
|
||||
const contentTypeMap: Record<string, string> = {
|
||||
'1': 'Type-sexual',
|
||||
'2': 'Type-violence',
|
||||
'3': 'Type-fear',
|
||||
'4': 'Type-inapposite',
|
||||
'5': 'Type-drug',
|
||||
'6': 'Type-crime',
|
||||
'7': 'Type-speculation',
|
||||
}
|
||||
|
||||
return contentInfo
|
||||
.map(item => {
|
||||
const type = contentTypeMap[item]
|
||||
return type
|
||||
? formatPathHost(`/images/common/grades_use/${type}.svg`, {
|
||||
imageType: 'common',
|
||||
})
|
||||
: ''
|
||||
})
|
||||
.filter(Boolean)
|
||||
})
|
||||
|
||||
const showAgeRating = ref<boolean>(false)
|
||||
const toggleAgeRating = (): void => {
|
||||
showAgeRating.value = !showAgeRating.value
|
||||
}
|
||||
|
||||
const footerAgeRatingInfo = computed((): string[] => {
|
||||
const info = (tm as any)('Footer_AgeRating_Info')
|
||||
return Array.isArray(info) ? info : []
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer id="footer" ref="footerRef" class="relative bg-blac">
|
||||
<div
|
||||
@@ -91,13 +272,22 @@
|
||||
</div>
|
||||
|
||||
<!-- 정보 테이블 -->
|
||||
<div v-if="locale === 'zh-tw'" class="px-6 py-6 rounded-b-lg bg-[#292929]">
|
||||
<div
|
||||
v-if="locale === 'zh-tw'"
|
||||
class="px-6 py-6 rounded-b-lg bg-[#292929]"
|
||||
>
|
||||
<div class="space-y-2">
|
||||
<div v-dompurify-html="tm('Footer_AgeRating_Caution')"></div>
|
||||
<div
|
||||
v-dompurify-html="tm('Footer_AgeRating_Caution')"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="px-6 py-6 rounded-b-lg bg-[#A31639]" :class="`bg-${getGameRatingImage.type}`">
|
||||
<div
|
||||
v-else
|
||||
class="px-6 py-6 rounded-b-lg bg-[#A31639]"
|
||||
:class="`bg-${getGameRatingImage.type}`"
|
||||
>
|
||||
<div class="space-y-2">
|
||||
<div class="flex flex-start border-b border-white/10 pb-2">
|
||||
<span class="text-white text-sm flex-1">
|
||||
@@ -159,13 +349,12 @@
|
||||
<div
|
||||
class="language-area static md:absolute bottom-7 right-10 text-white mt-5 md:mt-0 md:bottom-5.5 md:right-4"
|
||||
>
|
||||
<BlocksLanguageSwitcher :language-order="tm('Footer_Language_Order')" />
|
||||
<BlocksLanguageSwitcher
|
||||
:language-order="tm('Footer_Language_Order')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="hasCautionText"
|
||||
class="mt-6 md:mt-6 hidden sm:block"
|
||||
>
|
||||
<div v-if="hasCautionText" class="mt-6 md:mt-6 hidden sm:block">
|
||||
<div
|
||||
v-dompurify-html="tm('Footer_caution')"
|
||||
class="text-xs text-white/30"
|
||||
@@ -178,7 +367,11 @@
|
||||
|
||||
<div class="logo-area flex items-center gap-7 mt-6 md:mt-6">
|
||||
<span>
|
||||
<a :href="tm('Footer_Smilegate_Link')" target="_blank" class="smilegate">
|
||||
<a
|
||||
:href="tm('Footer_Smilegate_Link')"
|
||||
target="_blank"
|
||||
class="smilegate"
|
||||
>
|
||||
<img
|
||||
:src="
|
||||
formatPathHost(`/images/common/logo_smilegate.png`, {
|
||||
@@ -225,183 +418,6 @@
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
FooterMenuItem,
|
||||
FooterData,
|
||||
DevCiConfig,
|
||||
} from '#layers/types/Common'
|
||||
|
||||
// Configuration
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
|
||||
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_footer.json'
|
||||
|
||||
// Multilingual
|
||||
const resultGetMultilingual = await useGetMultilingual({
|
||||
baseApiUrl: dataResourcesUrl,
|
||||
fileName: multilingualFileName,
|
||||
})
|
||||
const { tm, locale }: any = useI18n({
|
||||
useScope: 'local',
|
||||
messages: Object(resultGetMultilingual?.value?.multilingual),
|
||||
})
|
||||
|
||||
|
||||
// Footer_caution 값이 있고 빈 객체가 아닌지 체크
|
||||
const hasCautionText = computed(() => {
|
||||
const value = tm('Footer_caution')
|
||||
|
||||
// null, undefined 체크
|
||||
if (value === null || value === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 빈 객체 체크
|
||||
if (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 문자열로 변환하여 빈 문자열 또는 '{}' 문자열 체크
|
||||
const stringValue = String(value).trim()
|
||||
if (stringValue === '' || stringValue === '{}') {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
const gameDataStore = useGameDataStore()
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
// const path = ref<string>(`${staticUrl}/local/template/${gameData.value.s3_folder_name}`)
|
||||
|
||||
// 공통다국어 data
|
||||
const footerLinks = computed((): FooterMenuItem[] => {
|
||||
const menu = (tm as any)('Footer_Menu')
|
||||
const menuItems = Array.isArray(menu) ? (menu as FooterMenuItem[]) : []
|
||||
|
||||
// ja일 때 fund_display_yn에 따라 id가 footerFund인 항목 처리
|
||||
if (locale.value === 'ja') {
|
||||
const fundDisplayYn = footerData.value?.fund_display_yn
|
||||
const fundDisplayUrl = footerData.value?.fund_display_url
|
||||
|
||||
// id가 footerFund인 항목의 인덱스 찾기
|
||||
const fundIndex = menuItems.findIndex((item: any) => item.id === 'footerFund')
|
||||
|
||||
// fund_display_yn이 'y'가 아니면 id가 footerFund인 항목 제거
|
||||
if (!fundDisplayYn) {
|
||||
return menuItems.filter((item: any) => item.id !== 'footerFund')
|
||||
}
|
||||
|
||||
// fund_display_yn이 'y'이면 id가 footerFund인 항목의 url 설정
|
||||
if (fundDisplayYn && fundIndex !== -1) {
|
||||
const updatedMenuItems = [...menuItems]
|
||||
updatedMenuItems[fundIndex] = {
|
||||
...updatedMenuItems[fundIndex],
|
||||
url: fundDisplayUrl || updatedMenuItems[fundIndex].url
|
||||
}
|
||||
return updatedMenuItems
|
||||
}
|
||||
}
|
||||
|
||||
return menuItems
|
||||
})
|
||||
const footerData = ref(gameData.value?.footer_json as unknown as FooterData)
|
||||
const setDevCi = ref<DevCiConfig>({
|
||||
dev_ci_yn: gameData.value?.footer_dev_ci_img_yn as boolean,
|
||||
dev_ci_img_path: gameData.value?.footer_dev_ci_img_path as string,
|
||||
})
|
||||
|
||||
const useGameRating = computed<boolean>(() => {
|
||||
return footerData.value.use_game_rating
|
||||
})
|
||||
|
||||
///local/template/common/grades_age
|
||||
const getGameRatingImage = computed((): { type: string, image: string } => {
|
||||
// 안전하게 rating_type 값 확인
|
||||
if (
|
||||
!footerData.value?.game_rating_info?.rating_type ||
|
||||
typeof footerData.value.game_rating_info.rating_type !== 'string'
|
||||
) {
|
||||
return { type: '', image: '' }
|
||||
}
|
||||
|
||||
const contentInfo = footerData.value.game_rating_info.rating_type
|
||||
const ratingType = contentInfo.trim()
|
||||
|
||||
// 빈 문자열인 경우 처리
|
||||
if (!ratingType) {
|
||||
return { type: '', image: '' }
|
||||
}
|
||||
|
||||
const ageTypeMap: Record<string, string> = {
|
||||
'6': 'Type6',
|
||||
'12': 'Type12',
|
||||
'15': 'Type15',
|
||||
'18': 'Type18',
|
||||
'19': 'Type19',
|
||||
all: 'TypeAll',
|
||||
e: 'TypeExempt',
|
||||
}
|
||||
|
||||
const type = ageTypeMap[ratingType as keyof typeof ageTypeMap] || 'TypeTest'
|
||||
return {
|
||||
type,
|
||||
image: formatPathHost(
|
||||
`/images/common/grades_age/${locale.value}/${type}.svg`,
|
||||
{
|
||||
imageType: 'common',
|
||||
}
|
||||
),
|
||||
}
|
||||
})
|
||||
|
||||
const getContentInfoImage = computed((): string[] => {
|
||||
// 안전하게 content_info 값 확인
|
||||
if (
|
||||
!footerData.value?.game_rating_info?.content_info ||
|
||||
typeof footerData.value.game_rating_info.content_info !== 'string'
|
||||
) {
|
||||
return []
|
||||
}
|
||||
|
||||
const contentInfo = footerData.value.game_rating_info.content_info.split(',')
|
||||
contentInfo.pop()
|
||||
|
||||
const contentTypeMap: Record<string, string> = {
|
||||
'1': 'Type-sexual',
|
||||
'2': 'Type-violence',
|
||||
'3': 'Type-fear',
|
||||
'4': 'Type-inapposite',
|
||||
'5': 'Type-drug',
|
||||
'6': 'Type-crime',
|
||||
'7': 'Type-speculation',
|
||||
}
|
||||
|
||||
return contentInfo
|
||||
.map(item => {
|
||||
const type = contentTypeMap[item]
|
||||
return type
|
||||
? formatPathHost(`/images/common/grades_use/${type}.svg`, {
|
||||
imageType: 'common',
|
||||
})
|
||||
: ''
|
||||
})
|
||||
.filter(Boolean)
|
||||
})
|
||||
|
||||
const showAgeRating = ref<boolean>(false)
|
||||
const toggleAgeRating = (): void => {
|
||||
showAgeRating.value = !showAgeRating.value
|
||||
}
|
||||
|
||||
const footerAgeRatingInfo = computed((): string[] => {
|
||||
const info = (tm as any)('Footer_AgeRating_Info')
|
||||
return Array.isArray(info) ? info : []
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
em {
|
||||
font-style: normal;
|
||||
@@ -412,11 +428,11 @@ em {
|
||||
}
|
||||
|
||||
.bg-Type12 {
|
||||
background-color: #4369B1;
|
||||
background-color: #4369b1;
|
||||
}
|
||||
|
||||
.bg-Type15 {
|
||||
background-color: #F9B846;
|
||||
background-color: #f9b846;
|
||||
}
|
||||
|
||||
.bg-Type18 {
|
||||
@@ -424,11 +440,11 @@ em {
|
||||
}
|
||||
|
||||
.bg-Type19 {
|
||||
background-color: #A31639;
|
||||
background-color: #a31639;
|
||||
}
|
||||
|
||||
.bg-TypeAll {
|
||||
background-color: #44A342;
|
||||
background-color: #44a342;
|
||||
}
|
||||
|
||||
.bg-TypeExempt {
|
||||
|
||||
@@ -18,7 +18,7 @@ const gameDataStore = useGameDataStore()
|
||||
const pageDataStore = usePageDataStore()
|
||||
const scrollStore = useScrollStore()
|
||||
const breakpoints = useResponsiveBreakpoints()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
const { pageLayoutType } = storeToRefs(pageDataStore)
|
||||
@@ -145,14 +145,14 @@ const handleSendLog = (item: string) => {
|
||||
click_item: item,
|
||||
click_sarea: 'GNB',
|
||||
}
|
||||
sendLog(locale.value, useAnalyticsData(analytics))
|
||||
sendLog(locale.value, analytics)
|
||||
}
|
||||
|
||||
const handleGnbItemClick = (gnbItem: GameDataMenu) => {
|
||||
if (isNotClickable(gnbItem)) return
|
||||
|
||||
handleMenuClose()
|
||||
sendLog(locale.value, useAnalyticsData(gnbItem.tracking_json))
|
||||
sendLog(locale.value, gnbItem.tracking_json)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -389,9 +389,7 @@ onMounted(() => {
|
||||
:text-color="
|
||||
getColorCodeFromData(start1depthData?.btn_info, 'txt')
|
||||
"
|
||||
@click="
|
||||
sendLog(locale, useAnalyticsData(start1depthData.tracking))
|
||||
"
|
||||
@click="sendLog(locale, start1depthData.tracking)"
|
||||
>
|
||||
{{ start1depthData?.btn_info?.txt_btn_name }}
|
||||
</BlocksButtonLauncher>
|
||||
@@ -404,9 +402,7 @@ onMounted(() => {
|
||||
<BlocksButtonLauncher
|
||||
type="custom"
|
||||
:platform="key"
|
||||
@click="
|
||||
sendLog(locale, useAnalyticsData(item.tracking))
|
||||
"
|
||||
@click="sendLog(locale, item.tracking)"
|
||||
>
|
||||
{{ item.btn_info?.txt_btn_name }}
|
||||
</BlocksButtonLauncher>
|
||||
|
||||
@@ -75,7 +75,7 @@ watch(isPAssApiLoading, newVal => {
|
||||
|
||||
onMounted(() => {
|
||||
const { sendLog } = useAnalytics()
|
||||
sendLog(locale.value, useAnalyticsData('view'))
|
||||
sendLog(locale.value, 'view')
|
||||
|
||||
if (!hasApiCallStarted.value) {
|
||||
loadingStore.stopFullLoading()
|
||||
|
||||
@@ -16,7 +16,7 @@ const device = useDevice()
|
||||
const modalStore = useModalStore()
|
||||
const scrollStore = useScrollStore()
|
||||
const breakpoints = useResponsiveBreakpoints()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
const buttonList = computed<PageDataResourceGroup[]>(
|
||||
() => props.resourcesData ?? []
|
||||
@@ -90,7 +90,7 @@ const downloadFile = async (url: string = '', osType: number = 0) => {
|
||||
}
|
||||
|
||||
const handleButtonClick = (button: PageDataResourceGroup) => {
|
||||
sendLog(locale.value, useAnalyticsData(button.tracking))
|
||||
sendLog(locale.value, button.tracking)
|
||||
|
||||
const btnDetail = button.btn_info?.detail
|
||||
|
||||
|
||||
@@ -1,43 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import type { TrackingObject } from '#layers/types/api/common'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
description?: string
|
||||
link?: string
|
||||
target?: '_self' | '_blank'
|
||||
linkAnalytics?: TrackingObject
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
target: '_blank',
|
||||
})
|
||||
|
||||
const componentTag = computed((): string => {
|
||||
switch (props.target) {
|
||||
case '_self':
|
||||
return 'AtomsLocaleLink'
|
||||
case '_blank':
|
||||
return 'a'
|
||||
default:
|
||||
return 'a'
|
||||
}
|
||||
})
|
||||
|
||||
const componentProps = computed(() => {
|
||||
if (props.target === '_self') {
|
||||
return {
|
||||
to: props.link,
|
||||
}
|
||||
}
|
||||
|
||||
if (props.target === '_blank') {
|
||||
return {
|
||||
href: props.link,
|
||||
target: props.target,
|
||||
rel: 'noopener noreferrer',
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
})
|
||||
const { locale } = useI18n()
|
||||
const { sendLog } = useAnalytics()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -55,11 +32,12 @@ const componentProps = computed(() => {
|
||||
>
|
||||
{{ props.description }}
|
||||
</p>
|
||||
<component
|
||||
:is="componentTag"
|
||||
<AtomsLocaleLink
|
||||
v-else-if="props.description && props.link"
|
||||
v-bind="componentProps"
|
||||
:to="props.link"
|
||||
:target="props.target"
|
||||
class="relative flex items-center justify-center gap-[4px] w-auto h-auto text-[#3C75FF] text-[14px] font-[500] leading-[20px] tracking-[-0.42px] md:text-[16px] md:leading-[24px] md:tracking-[-0.48px] before:content-[''] before:absolute before:z-[2] before:top-0 before:left-0 before:w-full before:h-full before:bg-[#FFFFFF] before:transition-opacity before:duration-300 before:ease-in-out before:opacity-0 hover:before:opacity-20"
|
||||
@click="sendLog(locale, props.linkAnalytics)"
|
||||
>
|
||||
<span>{{ props.description }}</span>
|
||||
<AtomsIconsWebLinkLine
|
||||
@@ -68,7 +46,7 @@ const componentProps = computed(() => {
|
||||
color="#3C75FF"
|
||||
class="icon"
|
||||
/>
|
||||
</component>
|
||||
</AtomsLocaleLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { globalDateFormat } from '@seed-next/date'
|
||||
import { PREREGIST_ERROR_CODE } from '#layers/composables/usePreregist'
|
||||
import type { TrackingObject } from '#layers/types/api/common'
|
||||
|
||||
interface Props {
|
||||
preregistCode: string
|
||||
@@ -8,6 +9,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const pageVerTmplNameEn = inject('pageVerTmplNameEn')
|
||||
|
||||
// Composables
|
||||
const { locale } = useI18n()
|
||||
@@ -25,6 +27,7 @@ const {
|
||||
} = usePreregist()
|
||||
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
// Constants
|
||||
const stoveCs = runtimeConfig.public.stoveCs
|
||||
@@ -69,6 +72,16 @@ const toggleExpand = (event: Event) => {
|
||||
isExpandedMarketing.value = !isExpandedMarketing.value
|
||||
}
|
||||
|
||||
const handleSendLog = (item: string) => {
|
||||
const analytics = {
|
||||
action_type: 'click',
|
||||
click_item: item,
|
||||
click_sarea: pageVerTmplNameEn,
|
||||
} as TrackingObject
|
||||
|
||||
sendLog(locale.value, analytics, { useGA: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 에러 모달 표시
|
||||
*/
|
||||
@@ -204,6 +217,7 @@ const handleSubmit = async (): Promise<void> => {
|
||||
|
||||
if (result.code === PREREGIST_ERROR_CODE.SUCCESS) {
|
||||
currentStep.value = 2
|
||||
handleSendLog('사전 등록 하기')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -394,7 +408,9 @@ defineExpose({
|
||||
v-if="device.isDesktop ? true : device.isAndroid"
|
||||
href="https://play.google.com/store/search?q=stove&c=apps"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex h-12 w-full items-center justify-center gap-1.5 rounded-lg bg-white/[0.04] px-8 pl-8 pr-10 text-sm font-medium leading-5 tracking-[-0.42px] text-white no-underline backdrop-blur-[15px] transition-colors duration-200 hover:bg-white/[0.08] md:h-14 md:gap-2 md:text-base md:leading-6 md:tracking-[-0.48px]"
|
||||
@click="handleSendLog('Google Play')"
|
||||
>
|
||||
<AtomsIconsLogoGoogle />
|
||||
<span>Google Play</span>
|
||||
@@ -403,7 +419,9 @@ defineExpose({
|
||||
v-if="device.isDesktop ? true : device.isApple"
|
||||
href="https://apps.apple.com/app/stove-app-stove-app/id1342134971"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex h-12 w-full items-center justify-center gap-1.5 rounded-lg bg-white/[0.04] px-8 pl-8 pr-10 text-sm font-medium leading-5 tracking-[-0.42px] text-white no-underline backdrop-blur-[15px] transition-colors duration-200 hover:bg-white/[0.08] md:h-14 md:gap-2 md:text-base md:leading-6 md:tracking-[-0.48px]"
|
||||
@click="handleSendLog('App Store')"
|
||||
>
|
||||
<AtomsIconsLogoApple />
|
||||
<span>App Store</span>
|
||||
|
||||
@@ -25,7 +25,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
const emit = defineEmits(['mounted', 'move'])
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
const splideIndex = defineModel<number>('index', { required: false })
|
||||
|
||||
@@ -115,7 +115,7 @@ const handleThumbnailClick = (title: string) => {
|
||||
click_sarea: props.analyticsSarea,
|
||||
}
|
||||
|
||||
sendLog(locale.value, useAnalyticsData(paginationAnalytics))
|
||||
sendLog(locale.value, paginationAnalytics)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -69,7 +69,7 @@ export const useAnalyticsData = (
|
||||
const baseViewArea = pageNameEn.value
|
||||
let logData = {} as AnalyticsDetailType
|
||||
|
||||
// 문자열 'view'만 들어오는 경우 (페이지뷰)
|
||||
// 페이지뷰
|
||||
if (analytics === 'view') {
|
||||
logData = {
|
||||
actionType: analytics,
|
||||
@@ -218,28 +218,6 @@ const sendSA = (
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 기본 로그 일괄 전송
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 기본 로그 일괄 전송
|
||||
*
|
||||
* @param locale 언어 코드
|
||||
* @param analytics Partial<AnalyticsDetailType>
|
||||
*/
|
||||
const sendLog = (locale: string, analytics: Partial<AnalyticsDetailType>) => {
|
||||
if (isEmptyAnalytics(analytics)) return
|
||||
|
||||
// 언어 코드 대문자 변환
|
||||
analytics.eventLocale = locale.toUpperCase()
|
||||
|
||||
// SA 전송 : actionType, logSourceType 유무로 판별 (logSourceType 체크는 주석 처리 되어있던 로직 유지)
|
||||
if (analytics.actionType && analytics.actionType !== '') {
|
||||
sendSA(analytics, { mcode: analytics.mcode, options: analytics.options })
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Google Analytics
|
||||
// ============================================================================
|
||||
@@ -261,19 +239,6 @@ const withGA = (callback: (gtag: any) => void) => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Analytics 전송 (기본 이벤트만 전송)
|
||||
*
|
||||
* @param gaEventName 이벤트명
|
||||
*/
|
||||
const sendGAEventOnly = (gaEventName: string) => {
|
||||
if (!gaEventName) return
|
||||
|
||||
withGA(gtag => {
|
||||
gtag('event', `${gaEventName}`)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Analytics 전송
|
||||
*
|
||||
@@ -295,6 +260,49 @@ const sendGA = (analytics: Partial<AnalyticsDetailType>) => {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Analytics 전송 (기본 이벤트만 전송)
|
||||
*
|
||||
* @param gaEventName 이벤트명
|
||||
*/
|
||||
const sendGAEventOnly = (gaEventName: string) => {
|
||||
if (!gaEventName) return
|
||||
|
||||
withGA(gtag => {
|
||||
gtag('event', `${gaEventName}`)
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 기본 로그 일괄 전송
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 기본 로그 일괄 전송
|
||||
*
|
||||
* @param locale 언어 코드
|
||||
* @param analytics Partial<AnalyticsDetailType>
|
||||
*/
|
||||
const sendLog = (
|
||||
locale: string,
|
||||
analytics: string | TrackingObject,
|
||||
options: { useGA?: boolean } = { useGA: false }
|
||||
) => {
|
||||
const analyticsData = useAnalyticsData(analytics)
|
||||
analyticsData.eventLocale = locale.toUpperCase()
|
||||
|
||||
// SA 전송 : actionType, logSourceType 유무로 판별
|
||||
if (analyticsData.actionType && analyticsData.actionType !== '') {
|
||||
sendSA(analyticsData, {
|
||||
mcode: analyticsData?.mcode,
|
||||
options: analyticsData?.options,
|
||||
})
|
||||
if (options.useGA) {
|
||||
sendGA(analyticsData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Meta / Twitter / TikTok Pixel
|
||||
// ============================================================================
|
||||
@@ -364,19 +372,13 @@ const sendTiktokPixel = (ttEventName?: string) => {
|
||||
const sendMarketingLog = ({
|
||||
logName,
|
||||
eventName,
|
||||
analytics,
|
||||
}: {
|
||||
logName: 'ga' | 'meta' | 'twitter' | 'tiktok'
|
||||
eventName?: string
|
||||
analytics?: Partial<AnalyticsDetailType>
|
||||
}) => {
|
||||
switch (logName) {
|
||||
case 'ga': {
|
||||
if (eventName) {
|
||||
sendGAEventOnly(eventName)
|
||||
} else if (analytics) {
|
||||
sendGA(analytics)
|
||||
}
|
||||
sendGAEventOnly(eventName)
|
||||
break
|
||||
}
|
||||
case 'meta': {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import VueDatePicker from '@vuepic/vue-datepicker'
|
||||
import '@vuepic/vue-datepicker/dist/main.css'
|
||||
|
||||
export default defineNuxtPlugin(nuxtApp => {
|
||||
nuxtApp.vueApp.component('VueDatePicker', VueDatePicker)
|
||||
|
||||
@@ -3,22 +3,30 @@ import type { PageDataValue } from '#layers/types/api/pageData'
|
||||
export const usePageDataStore = defineStore('pageData', () => {
|
||||
const pageData = ref<PageDataValue | null>(null)
|
||||
const pageLayoutType = ref<'default' | 'promotion' | null>(null)
|
||||
const pageName = ref<string | null>(null)
|
||||
const pageNameEn = ref<string | null>(null)
|
||||
|
||||
const setPageData = (response: PageDataValue) => {
|
||||
clearPageData()
|
||||
|
||||
pageData.value = response
|
||||
pageLayoutType.value = getLayoutType(pageData.value)
|
||||
pageName.value = pageData.value?.page_name
|
||||
pageNameEn.value = pageData.value?.page_name_en
|
||||
}
|
||||
|
||||
const clearPageData = () => {
|
||||
pageData.value = null
|
||||
pageLayoutType.value = null
|
||||
pageName.value = null
|
||||
pageNameEn.value = null
|
||||
}
|
||||
|
||||
return {
|
||||
pageData,
|
||||
pageLayoutType,
|
||||
pageName,
|
||||
pageNameEn,
|
||||
setPageData,
|
||||
clearPageData,
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
import type { ReqCouponList } from '#layers/types/api/couponData'
|
||||
|
||||
interface Props {
|
||||
id?: string
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
@@ -38,6 +39,7 @@ const { t, tm, locale }: any = useI18n({
|
||||
})
|
||||
|
||||
// Composables
|
||||
const { sendLog } = useAnalytics()
|
||||
// const { isGameMaintenance, checkGameMaintenance } = useGetGameMaintenance()
|
||||
const { isWebInspection, getInspectionDataExternal } =
|
||||
useGetInspectionDataExternal()
|
||||
@@ -72,17 +74,15 @@ const { couponNo, isSelectCharacter, selectCharacter } =
|
||||
storeToRefs(couponStore)
|
||||
const { updateCouponNo, updateSelectCharacter, isEmptyCouponNo } = couponStore
|
||||
|
||||
// Data
|
||||
const backgroundData = computed(() =>
|
||||
getComponentGroup(props.components, 'background')
|
||||
)
|
||||
|
||||
// Refs
|
||||
const clientIp = ref('')
|
||||
const monthSelectList = ref<Array<number>>([1, 3, 6, 12])
|
||||
const isSelectCharacterModalOpen = ref(false)
|
||||
|
||||
// Computed
|
||||
const backgroundData = computed(() =>
|
||||
getComponentGroup(props.components, 'background')
|
||||
)
|
||||
const sortedCharacterList = computed(() => {
|
||||
return characterList.value
|
||||
.map(characterInfo => {
|
||||
@@ -100,6 +100,15 @@ const sortedCharacterList = computed(() => {
|
||||
})
|
||||
|
||||
// Functions
|
||||
const handleSendLog = (item: string) => {
|
||||
const analytics = {
|
||||
action_type: 'click',
|
||||
click_item: item,
|
||||
click_sarea: props.pageVerTmplNameEn,
|
||||
}
|
||||
|
||||
sendLog(locale.value, analytics)
|
||||
}
|
||||
/**
|
||||
* @description 기본 Alert 모달 팝업 함수입니다.
|
||||
* @param text - 모달 내용
|
||||
@@ -242,6 +251,8 @@ const validationCheckBefore = async () => {
|
||||
* @description FE 유효성 체크 후 캐릭터 선택 모달 팝업 노출합니다.
|
||||
*/
|
||||
const handleCouponUse = async () => {
|
||||
handleSendLog(t('Coupon_Registration', {}, { locale: 'ko' }))
|
||||
|
||||
const validationCheckBeforeResult = await validationCheckBefore()
|
||||
|
||||
if (validationCheckBeforeResult !== 0) {
|
||||
@@ -272,6 +283,7 @@ const handleCouponUse = async () => {
|
||||
* @description 캐릭터 선택 후, 확인 버튼 클릭 시 쿠폰 등록 API 호출 함수입니다.
|
||||
*/
|
||||
const handleCouponRegister = async () => {
|
||||
handleSendLog('캐릭터 선택 완료')
|
||||
closeSelectCharacterModal()
|
||||
|
||||
const validationCheckBeforeResult = await validationCheckBefore()
|
||||
@@ -328,6 +340,7 @@ const handlePeriodSelect = (month: number) => {
|
||||
|
||||
setCouponDate(newStartDate, 'start')
|
||||
setCouponDate(newEndDate, 'end')
|
||||
handleSendLog(t(`Coupon_Month${month}`, {}, { locale: 'ko' }))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,6 +351,8 @@ const handlePeriodSearch = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
handleSendLog(t('Coupon_Search', {}, { locale: 'ko' }))
|
||||
|
||||
const accessToken = csrGetAccessToken()
|
||||
const validateTokenResult = await handleTokenValidation(accessToken || '')
|
||||
if (validateTokenResult === false) {
|
||||
@@ -431,6 +446,8 @@ const goToCouponBox = () => {
|
||||
* @description 쿠폰 등록 내역의 사용하기 버튼의 클릭 이벤트 함수입니다.
|
||||
*/
|
||||
const handleGoToCouponBox = async () => {
|
||||
handleSendLog(t('Coupon_Item_Use', {}, { locale: 'ko' }))
|
||||
|
||||
const accessToken = csrGetAccessToken()
|
||||
const validateTokenResult = await handleTokenValidation(accessToken || '')
|
||||
|
||||
@@ -485,6 +502,7 @@ onMounted(async () => {
|
||||
|
||||
<template>
|
||||
<WidgetsFixMainTitle
|
||||
:id="props.id"
|
||||
:title="tm('Coupon_Page_Title')"
|
||||
:resources-data="backgroundData"
|
||||
/>
|
||||
@@ -765,8 +783,8 @@ onMounted(async () => {
|
||||
class="relative flex flex-col items-center justify-center gap-[24px] w-full"
|
||||
>
|
||||
<p
|
||||
class="relative flex items-start justify-center w-full text-center text-[#333333] text-[15px] font-[400] leading-[24px] tracking-[-0.45px]"
|
||||
v-dompurify-html="tm('Coupon_Alert_SelectCharacter')"
|
||||
class="relative flex items-start justify-center w-full text-center text-[#333333] text-[15px] font-[400] leading-[24px] tracking-[-0.45px]"
|
||||
></p>
|
||||
|
||||
<AtomsSelect
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { getComponentGroup, getComponentGroupAry } from '#layers/utils/dataUtil'
|
||||
import type { TrackingObject } from '#layers/types/api/common'
|
||||
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
import type { Platform } from '#layers/types/components/button'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
id?: string
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
@@ -13,6 +15,12 @@ const props = defineProps<Props>()
|
||||
|
||||
// Configuration
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const device = useDevice()
|
||||
const gameDataStore = useGameDataStore()
|
||||
const breakpoints = useResponsiveBreakpoints()
|
||||
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
|
||||
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_download.json'
|
||||
@@ -30,27 +38,8 @@ const { tm, locale }: any = useI18n({
|
||||
})
|
||||
|
||||
// Composables
|
||||
const device = useDevice()
|
||||
const breakpoints = useResponsiveBreakpoints()
|
||||
const { checkPCSpec } = useCheckPCSpec(tm)
|
||||
|
||||
// Store
|
||||
const gameDataStore = useGameDataStore()
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
|
||||
// Data
|
||||
const backgroundData = computed(() =>
|
||||
getComponentGroup(props.components, 'background')
|
||||
)
|
||||
const specCheckData = computed(() =>
|
||||
getComponentGroupAry(props.components, 'tablePcSpecTool')
|
||||
)
|
||||
const schemeFormatData = computed(
|
||||
() => specCheckData.value[0]?.display?.text || ''
|
||||
)
|
||||
const setupUrlData = computed(() => specCheckData.value[1]?.display?.text || '')
|
||||
|
||||
// Refs
|
||||
const specPCRef = ref<HTMLElement | null>(null)
|
||||
const specMobileRef = ref<HTMLElement | null>(null)
|
||||
const driverArray = ref<Array<string>>([
|
||||
@@ -71,7 +60,18 @@ const pcSpecArray = ref<Array<string>>([
|
||||
const mobileSpecArray = ref<Array<string>>(['Android', 'Ios'])
|
||||
const mobileOSArray = ref<Array<string>>(['AOS', 'iOS'])
|
||||
|
||||
// Computed
|
||||
// Data
|
||||
const backgroundData = computed(() =>
|
||||
getComponentGroup(props.components, 'background')
|
||||
)
|
||||
const specCheckData = computed(() =>
|
||||
getComponentGroupAry(props.components, 'tablePcSpecTool')
|
||||
)
|
||||
const schemeFormatData = computed(
|
||||
() => specCheckData.value[0]?.display?.text || ''
|
||||
)
|
||||
const setupUrlData = computed(() => specCheckData.value[1]?.display?.text || '')
|
||||
|
||||
const driverList = computed(() =>
|
||||
driverArray.value.map(driver => ({
|
||||
id: `DRIVER_${driver}`,
|
||||
@@ -108,6 +108,15 @@ const mobileOSList = computed(() =>
|
||||
)
|
||||
|
||||
// Functions
|
||||
const handleSendLog = (item: string) => {
|
||||
const analytics = {
|
||||
action_type: 'click',
|
||||
click_item: item,
|
||||
click_sarea: props.pageVerTmplNameEn,
|
||||
}
|
||||
|
||||
sendLog(locale.value, analytics)
|
||||
}
|
||||
/**
|
||||
* 입력한 pc, mobile 인자값에 따라 해당 최소사양&권장사양 테이블로 포커스 스크롤 이동합니다.
|
||||
* @param target<string> 'pc' | 'mobile'
|
||||
@@ -123,6 +132,7 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
|
||||
refs[target]?.getBoundingClientRect().top + window.scrollY - adjustedOffset
|
||||
|
||||
window.scrollTo({ top: adjustedOffsetTop, behavior: 'smooth' })
|
||||
handleSendLog(`${target === 'pc' ? 'PC' : '모바일'} 버전 사양 확인하기`)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -196,12 +206,8 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
|
||||
></p>
|
||||
</template>
|
||||
|
||||
<AtomsButton
|
||||
type="action"
|
||||
button-size="size-small"
|
||||
background-color="transparent"
|
||||
text-color="#1F1F1F"
|
||||
class="relative w-auto h-auto px-0 text-[14px] font-[500] leading-[20px] tracking-[-0.42px] md:text-[16px] md:leading-[24px] md:tracking-[-0.48px] before:content-[''] before:absolute before:z-[2] before:top-p before:left-0 before:w-full before:h-full before:bg-[#FFFFFF] before:transition-opacity before:duration-300 before:ease-in-out before:opacity-0 hover:before:opacity-20"
|
||||
<button
|
||||
class="relative flex gap-1 text-[14px] font-[500] text-[#1F1F1F] leading-[20px] tracking-[-0.42px] md:text-[16px] md:leading-[24px] md:tracking-[-0.48px] before:content-[''] before:absolute before:z-[2] before:top-p before:left-0 before:w-full before:h-full before:bg-[#FFFFFF] before:transition-opacity before:duration-300 before:ease-in-out before:opacity-0 hover:before:opacity-20"
|
||||
@click="handleMoveFocus('pc')"
|
||||
>
|
||||
<span>{{ tm('Download_Box_PC_SpecCheck') }}</span>
|
||||
@@ -210,7 +216,7 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
|
||||
color="#1F1F1F"
|
||||
class="relative rotate-90"
|
||||
/>
|
||||
</AtomsButton>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -224,6 +230,7 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
|
||||
v-else-if="breakpoints.isMd || breakpoints.isDesktop"
|
||||
platform="pc"
|
||||
class="!w-full !max-w-[300px]"
|
||||
@click="handleSendLog('PC 버전 다운로드')"
|
||||
>
|
||||
<span>{{ tm('Download_Button_PC') }}</span>
|
||||
</BlocksButtonLauncher>
|
||||
@@ -237,15 +244,16 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
|
||||
class="relative flex items-center justify-center w-full gap-[8px] text-[#999999] text-[16px] font-[400] leading-[26px] tracking-[-0.48px]"
|
||||
>
|
||||
<span>{{ tm('Download_Text_Not_STOVE_Client') }}</span>
|
||||
<NuxtLink
|
||||
<a
|
||||
:href="stoveClientDownloadUrl"
|
||||
target="_self"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center justify-start gap-[4px] text-[#3C75FF] text-[16px] font-[500] reading-[24px] tracking-[-0.48px]"
|
||||
@click="handleSendLog('스토브 PC 클라이언트 다운로드')"
|
||||
>
|
||||
<span>{{ tm('Download_Button_STOVE') }}</span>
|
||||
<AtomsIconsDownloadLine color="#3C75FF" />
|
||||
</NuxtLink>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -269,12 +277,8 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
|
||||
<span>{{ tm('Download_Box_MOBILE_Title') }}</span>
|
||||
</h4>
|
||||
|
||||
<AtomsButton
|
||||
type="action"
|
||||
button-size="size-small"
|
||||
background-color="transparent"
|
||||
text-color="#1F1F1F"
|
||||
class="relative w-auto h-auto px-0 text-[14px] font-[500] leading-[20px] tracking-[-0.42px] md:text-[16px] md:leading-[24px] md:tracking-[-0.48px] before:content-[''] before:absolute before:z-[2] before:top-p before:left-0 before:w-full before:h-full before:bg-[#FFFFFF] before:transition-opacity before:duration-300 before:ease-in-out before:opacity-0 hover:before:opacity-20"
|
||||
<button
|
||||
class="relative flex gap-1 text-[14px] font-[500] text-[#1F1F1F] leading-[20px] tracking-[-0.42px] md:text-[16px] md:leading-[24px] md:tracking-[-0.48px] before:content-[''] before:absolute before:z-[2] before:top-p before:left-0 before:w-full before:h-full before:bg-[#FFFFFF] before:transition-opacity before:duration-300 before:ease-in-out before:opacity-0 hover:before:opacity-20"
|
||||
@click="handleMoveFocus('mobile')"
|
||||
>
|
||||
<span>{{ tm('Download_Box_MOBILE_SpecCheck') }}</span>
|
||||
@@ -283,7 +287,7 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
|
||||
color="#1F1F1F"
|
||||
class="relative rotate-90"
|
||||
/>
|
||||
</AtomsButton>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -300,6 +304,7 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
|
||||
"
|
||||
:platform="`${os.platformCode as Platform}`"
|
||||
class="!w-full"
|
||||
@click="handleSendLog(os.platformCode)"
|
||||
>
|
||||
<span>{{ os.platformText }}</span>
|
||||
</BlocksButtonLauncher>
|
||||
@@ -376,14 +381,15 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
|
||||
background-color="#383838"
|
||||
text-color="#FFFFFF"
|
||||
class="shrink-0 w-[206px] px-0 text-[16px]"
|
||||
@click="
|
||||
@click="[
|
||||
checkPCSpec({
|
||||
schemeFormat: schemeFormatData,
|
||||
setupUrl: setupUrlData,
|
||||
gameNo: gameData?.game_code?.toString(),
|
||||
locale: locale as string,
|
||||
})
|
||||
"
|
||||
}),
|
||||
handleSendLog('내 PC 사양 확인하기'),
|
||||
]"
|
||||
>
|
||||
<em
|
||||
class="inline-flex items-center justify-center gap-[8px] not-italic"
|
||||
@@ -439,6 +445,7 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
:href="tm(`Download_Driver_${driver.driverCode}_Url`)"
|
||||
@click="handleSendLog(`다운로드_${driver.driverText}`)"
|
||||
>
|
||||
<span>{{ tm('Download_Button_Download') }}</span>
|
||||
</AtomsButton>
|
||||
|
||||
@@ -12,6 +12,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
provide('pageVerTmplNameEn', props.pageVerTmplNameEn)
|
||||
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const device = useDevice()
|
||||
@@ -27,14 +28,15 @@ const resultGetMultilingual = await useGetMultilingual({
|
||||
baseApiUrl: dataResourcesUrl,
|
||||
fileName: multilingualFileName,
|
||||
})
|
||||
const { tm, locale }: any = useI18n({
|
||||
const { t, tm, locale }: any = useI18n({
|
||||
useScope: 'local',
|
||||
messages: Object(resultGetMultilingual?.value?.multilingual),
|
||||
})
|
||||
|
||||
const { getOperateResources } = useOperateResources()
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
const { pageData } = storeToRefs(pageDataStore)
|
||||
const { getOperateResources } = useOperateResources()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
// Constants
|
||||
const COLOR_INDEX = { BACKGROUND: 0, TEXT: 1 } as const
|
||||
@@ -65,6 +67,10 @@ const preImgSnsData = computed(() =>
|
||||
const preDescriptionData = computed(() =>
|
||||
getComponentGroup(props.components, 'description')
|
||||
)
|
||||
const preregistSNS = computed(
|
||||
() => getSupportedPlatforms('2', gameData?.value?.os_type) as Platform[]
|
||||
)
|
||||
|
||||
// SNS Buttons
|
||||
const snsButtonsData = computed(() => {
|
||||
const buttons = getComponentGroupAry(props.components, 'imgSnsButton')
|
||||
@@ -73,9 +79,9 @@ const snsButtonsData = computed(() => {
|
||||
if (!buttons?.length) return []
|
||||
|
||||
return buttons.map((button, index) => ({
|
||||
image: button,
|
||||
link: links?.[index]?.display?.text ?? '',
|
||||
id: button.id ?? `sns-${index}`,
|
||||
link: links?.[index]?.display?.text ?? '',
|
||||
image: button,
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -86,18 +92,15 @@ const buttonColors = computed(() => {
|
||||
'preregistButtonColor'
|
||||
)
|
||||
|
||||
if (!colorData?.length)
|
||||
return { backgroundColor: undefined, textColor: undefined }
|
||||
|
||||
return {
|
||||
backgroundColor: getColorCode({
|
||||
colorName: colorData[COLOR_INDEX.BACKGROUND]?.display?.color_name,
|
||||
colorCode: colorData[COLOR_INDEX.BACKGROUND]?.display?.color_code,
|
||||
}),
|
||||
textColor: getColorCode({
|
||||
colorName: colorData[COLOR_INDEX.TEXT]?.display?.color_name,
|
||||
colorCode: colorData[COLOR_INDEX.TEXT]?.display?.color_code,
|
||||
}),
|
||||
backgroundColor: getColorCodeFromData(
|
||||
colorData[COLOR_INDEX.BACKGROUND]?.display,
|
||||
'none'
|
||||
),
|
||||
textColor: getColorCodeFromData(
|
||||
colorData[COLOR_INDEX.TEXT]?.display,
|
||||
'none'
|
||||
),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -214,9 +217,23 @@ const isRunButtonVisible = (marketType?: Platform): boolean => {
|
||||
}
|
||||
}
|
||||
|
||||
// Handler
|
||||
const handleSendLog = (item: string) => {
|
||||
const analytics = {
|
||||
action_type: 'click',
|
||||
click_item: item,
|
||||
click_sarea: props.pageVerTmplNameEn,
|
||||
}
|
||||
sendLog(locale.value, analytics)
|
||||
}
|
||||
|
||||
const handlePreregistClick = () => {
|
||||
preregistModalRef.value?.handleOpenPreregist()
|
||||
handleSendLog(`STOVE_${t('Preregist_Btn_Preegist', {}, { locale: 'ko' })}`)
|
||||
}
|
||||
|
||||
const handleSnsButtonClick = (btn: { id: string; link: string }) => {
|
||||
csrGoExternalLink(btn.link)
|
||||
handleSendLog(btn.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -272,7 +289,12 @@ const handlePreregistClick = () => {
|
||||
:key="btn.id"
|
||||
class="w-[48px] h-[40px] md:w-[72px] md:h-[56px]"
|
||||
>
|
||||
<a :href="btn.link" target="_blank" rel="noopener noreferrer">
|
||||
<a
|
||||
:href="btn.link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@click="handleSendLog(btn.id)"
|
||||
>
|
||||
<AtomsImg
|
||||
:src="getResourceSrc(btn.image)"
|
||||
:alt="btn.image?.display?.text"
|
||||
@@ -293,16 +315,18 @@ const handlePreregistClick = () => {
|
||||
{{ tm('Preregist_Btn_Preegist') }}
|
||||
</BlocksButtonLauncher>
|
||||
<template v-if="gameData?.platform_type !== '1'">
|
||||
<template
|
||||
v-for="platform in getSupportedPlatforms('2', gameData?.os_type)"
|
||||
:key="`preregist-${platform}`"
|
||||
>
|
||||
<template v-for="sns in preregistSNS" :key="`preregist-${sns}`">
|
||||
<BlocksButtonLauncher
|
||||
v-if="isRunButtonVisible(platform as Platform)"
|
||||
v-if="isRunButtonVisible(sns)"
|
||||
type="duplication"
|
||||
:platform="platform as Platform"
|
||||
:platform="sns"
|
||||
:background-color="buttonColors.backgroundColor"
|
||||
:text-color="buttonColors.textColor"
|
||||
@click="
|
||||
handleSendLog(
|
||||
`${sns}_${t('Preregist_Btn_Preegist', {}, { locale: 'ko' })}`
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ tm('Preregist_Btn_Preegist') }}
|
||||
</BlocksButtonLauncher>
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
id?: string
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
pageVerTmplNameEn: string
|
||||
@@ -11,6 +12,7 @@ interface Props {
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { handleTokenValidation } = useTokenValidation()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
// Configuration
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
@@ -23,43 +25,89 @@ const resultGetMultilingual = await useGetMultilingual({
|
||||
baseApiUrl: dataResourcesUrl,
|
||||
fileName: multilingualFileName,
|
||||
})
|
||||
const { tm, locale }: any = useI18n({
|
||||
const { t, tm, locale }: any = useI18n({
|
||||
useScope: 'local',
|
||||
messages: Object(resultGetMultilingual?.value?.multilingual),
|
||||
})
|
||||
|
||||
const isLogin = ref(false)
|
||||
|
||||
const secureSetting = ref({
|
||||
otpLoginYn: 'N',
|
||||
abroadLoginBlockYn: 'N',
|
||||
pcRegisterYn: 'N',
|
||||
})
|
||||
|
||||
// 회원 보안 설정 설정
|
||||
const handleSecureSetting = (url: string) => {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
// Computed
|
||||
const backgroundData = computed(() =>
|
||||
getComponentGroup(props.components, 'background')
|
||||
)
|
||||
const secureCards = computed(() => {
|
||||
const allCards = [
|
||||
{
|
||||
id: 'SECURE_CARD_0',
|
||||
title: tm('Secure_Stove_otp') || '스토브 인증기 (OTP)',
|
||||
description:
|
||||
tm('Secure_Stove_otp_desc') ||
|
||||
'스토브 앱으로 인증 후 안전하게 로그인하세요.',
|
||||
status: secureSetting.value.otpLoginYn,
|
||||
benefitTitle: tm('Secure_Stove_otp_benefits') || '스토브 OTP 혜택',
|
||||
benefitDesc: tm('Secure_Defense_bonus_10') || '방어력 +10',
|
||||
benefitIcon: '/images/common/img_OTP.png',
|
||||
buttonDisabled: false,
|
||||
url: tm('Secure_OtpLogin_Url'),
|
||||
analyticsItem: '스토브 인증기',
|
||||
},
|
||||
{
|
||||
id: 'SECURE_CARD_1',
|
||||
title: tm('Secure_Block_foreign_login') || '해외 로그인 차단',
|
||||
description:
|
||||
tm('Secure_Block_foreign_login_desc') ||
|
||||
'접속 국가를 제한하여 의심 로그인을 차단해요.',
|
||||
status: secureSetting.value.abroadLoginBlockYn,
|
||||
benefitTitle: '',
|
||||
benefitDesc: '',
|
||||
benefitIcon: '',
|
||||
buttonDisabled: false,
|
||||
url: tm('Secure_AbroadLogin_Url'),
|
||||
analyticsItem: '해외 로그인 차단',
|
||||
},
|
||||
{
|
||||
id: 'SECURE_CARD_2',
|
||||
title: tm('Secure_Trusted_pc_management') || '지정 PC 관리',
|
||||
description:
|
||||
tm('Secure_Trusted_pc_desc') ||
|
||||
'지정 PC에서만 로그인할 수 있게 설정해 보세요.',
|
||||
status: secureSetting.value.pcRegisterYn,
|
||||
benefitTitle: '',
|
||||
benefitDesc: '',
|
||||
benefitIcon: '',
|
||||
buttonDisabled: false,
|
||||
url: tm('Secure_PcRegister_Url'),
|
||||
analyticsItem: '지정 PC 관리',
|
||||
},
|
||||
]
|
||||
|
||||
// 로그인 유효성 체크
|
||||
const checkLoginValidation = async () => {
|
||||
const accessToken = useCookie('SUAT')
|
||||
const validateTokenResult = await handleTokenValidation(
|
||||
accessToken.value || ''
|
||||
)
|
||||
isLogin.value = validateTokenResult
|
||||
}
|
||||
// 한국어일 때는 모든 카드 노출, 그 외 언어일 때는 SECURE_CARD_0만 노출
|
||||
if (locale.value === 'ko') {
|
||||
return allCards
|
||||
}
|
||||
return allCards.filter(card => card.id === 'SECURE_CARD_0')
|
||||
})
|
||||
// 유의사항 내용 다국어 조회
|
||||
const cautionText = computed(() => {
|
||||
return tm('Secure_Notice_Content') || []
|
||||
})
|
||||
|
||||
// 회원 보안 설정 조회
|
||||
const fnGetSecuritySetting = async () => {
|
||||
const accessToken = useCookie('SUAT')
|
||||
checkLoginValidation()
|
||||
|
||||
const apiBase = `${stoveApiBaseUrl}/auth-secure/v1.0`
|
||||
const headers = {
|
||||
Authorization: `Bearer ${accessToken.value}`,
|
||||
'Content-Type': 'application/json;charset=UTF-8',
|
||||
}
|
||||
|
||||
if (accessToken) return
|
||||
|
||||
try {
|
||||
const result = await commonFetch('GET', `${apiBase}/security/setting`, {
|
||||
headers,
|
||||
@@ -81,65 +129,23 @@ const fnGetSecuritySetting = async () => {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
// Data
|
||||
const backgroundData = computed(() =>
|
||||
getComponentGroup(props.components, 'background')
|
||||
)
|
||||
|
||||
// Computed
|
||||
const secureCards = computed(() => {
|
||||
const allCards = [
|
||||
{
|
||||
id: 'SECURE_CARD_0',
|
||||
title: tm('Secure_Stove_otp') || '스토브 인증기 (OTP)',
|
||||
description:
|
||||
tm('Secure_Stove_otp_desc') ||
|
||||
'스토브 앱으로 인증 후 안전하게 로그인하세요.',
|
||||
status: secureSetting.value.otpLoginYn,
|
||||
benefitTitle: tm('Secure_Stove_otp_benefits') || '스토브 OTP 혜택',
|
||||
benefitDesc: tm('Secure_Defense_bonus_10') || '방어력 +10',
|
||||
benefitIcon: '/images/common/img_OTP.png',
|
||||
buttonDisabled: false,
|
||||
url: tm('Secure_OtpLogin_Url'),
|
||||
},
|
||||
{
|
||||
id: 'SECURE_CARD_1',
|
||||
title: tm('Secure_Block_foreign_login') || '해외 로그인 차단',
|
||||
description:
|
||||
tm('Secure_Block_foreign_login_desc') ||
|
||||
'접속 국가를 제한하여 의심 로그인을 차단해요.',
|
||||
status: secureSetting.value.abroadLoginBlockYn,
|
||||
benefitTitle: '',
|
||||
benefitDesc: '',
|
||||
benefitIcon: '',
|
||||
buttonDisabled: false,
|
||||
url: tm('Secure_AbroadLogin_Url'),
|
||||
},
|
||||
{
|
||||
id: 'SECURE_CARD_2',
|
||||
title: tm('Secure_Trusted_pc_management') || '지정 PC 관리',
|
||||
description:
|
||||
tm('Secure_Trusted_pc_desc') ||
|
||||
'지정 PC에서만 로그인할 수 있게 설정해 보세요.',
|
||||
status: secureSetting.value.pcRegisterYn,
|
||||
benefitTitle: '',
|
||||
benefitDesc: '',
|
||||
benefitIcon: '',
|
||||
buttonDisabled: false,
|
||||
url: tm('Secure_PcRegister_Url'),
|
||||
},
|
||||
]
|
||||
|
||||
// 한국어일 때는 모든 카드 노출, 그 외 언어일 때는 SECURE_CARD_0만 노출
|
||||
if (locale.value === 'ko') {
|
||||
return allCards
|
||||
const handleClickSetup = async card => {
|
||||
const analytics = {
|
||||
action_type: 'click',
|
||||
click_item: `${t('Secure_Action_setup', {}, { locale: 'ko' })}_${card.analyticsItem}`,
|
||||
click_sarea: props.pageVerTmplNameEn,
|
||||
}
|
||||
return allCards.filter(card => card.id === 'SECURE_CARD_0')
|
||||
})
|
||||
// 유의사항 내용 다국어 조회
|
||||
const cautionText = computed(() => {
|
||||
return tm('Secure_Notice_Content') || []
|
||||
})
|
||||
sendLog(locale.value, analytics)
|
||||
|
||||
const accessToken = useCookie('SUAT')
|
||||
const validateTokenResult = await handleTokenValidation(
|
||||
accessToken.value || ''
|
||||
)
|
||||
|
||||
if (validateTokenResult === false) return
|
||||
window.open(card.url, '_blank')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fnGetSecuritySetting()
|
||||
@@ -148,6 +154,7 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<WidgetsFixMainTitle
|
||||
:id="props.id"
|
||||
:title="tm('Secure_Page_Title') || '보안 강화 캠페인'"
|
||||
:resources-data="backgroundData"
|
||||
class="mx-auto"
|
||||
@@ -333,11 +340,7 @@ onMounted(() => {
|
||||
button-size="size-small md:size-large"
|
||||
background-color="#383838"
|
||||
text-color="#FFFFFF"
|
||||
@click="
|
||||
isLogin
|
||||
? handleSecureSetting(card.url)
|
||||
: checkLoginValidation()
|
||||
"
|
||||
@click="handleClickSetup(card)"
|
||||
>
|
||||
<span>{{ tm('Secure_Action_setup') }}</span>
|
||||
</AtomsButton>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
getComponentContainer,
|
||||
} from '#layers/utils/dataUtil'
|
||||
import { getYouTubeThumbnail } from '#layers/utils/youtubeUtil'
|
||||
import type { TrackingObject } from '#layers/types/api/common'
|
||||
import type {
|
||||
PageDataTemplateComponents,
|
||||
PageDataResourceGroups,
|
||||
@@ -25,6 +26,7 @@ const pageDataStore = usePageDataStore()
|
||||
const modalStore = useModalStore()
|
||||
const breakpoints = useResponsiveBreakpoints()
|
||||
const { getOperateResources } = useOperateResources()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
const { pageData } = storeToRefs(pageDataStore)
|
||||
|
||||
@@ -39,11 +41,30 @@ const resultGetMultilingual = await useGetMultilingual({
|
||||
fileName: multilingualFileName,
|
||||
})
|
||||
|
||||
const { tm, locale } = useI18n({
|
||||
const { tm, t, locale } = useI18n({
|
||||
useScope: 'local',
|
||||
messages: Object(resultGetMultilingual?.value?.multilingual),
|
||||
})
|
||||
|
||||
const analytics = {
|
||||
action_type: 'click',
|
||||
click_sarea: props.pageVerTmplNameEn,
|
||||
} as TrackingObject
|
||||
const arrowsData: PageDataResourceGroups = [
|
||||
{
|
||||
tracking: {
|
||||
...analytics,
|
||||
click_item: '1. 컨텐츠 리스트 - 네비게이션(좌)',
|
||||
},
|
||||
},
|
||||
{
|
||||
tracking: {
|
||||
...analytics,
|
||||
click_item: '1. 컨텐츠 리스트 - 네비게이션(우)',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const recommendHover = ref(false)
|
||||
|
||||
const backgroundData = computed(() =>
|
||||
@@ -53,23 +74,6 @@ 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 () => {
|
||||
@@ -131,13 +135,19 @@ const hasMore = computed(
|
||||
() => visibleVideos.value.length < recentVideos.value.length
|
||||
)
|
||||
|
||||
const handleVideoClick = (url: string) => {
|
||||
modalStore.handleOpenYoutube({ youtubeUrl: url })
|
||||
const handleVideoClick = (item: OperateGroupItem) => {
|
||||
modalStore.handleOpenYoutube({ youtubeUrl: item.url })
|
||||
sendLog(locale.value, { ...analytics, click_item: item.title })
|
||||
}
|
||||
|
||||
const handleLoadMoreRecent = () => {
|
||||
if (hasMore.value) {
|
||||
currentRecentPage.value++
|
||||
|
||||
sendLog(locale.value, {
|
||||
...analytics,
|
||||
click_item: t('Text_More', {}, { locale: 'ko' }),
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -154,6 +164,10 @@ const handleLoadMoreRecent = () => {
|
||||
:title="tm('Video_Section_Recommendation_Title')"
|
||||
:description="tm('Video_Page_Link')"
|
||||
:link="officialUrlData"
|
||||
:link-analytics="{
|
||||
...analytics,
|
||||
click_item: t('Video_Page_Link', {}, { locale: 'ko' }),
|
||||
}"
|
||||
/>
|
||||
<div
|
||||
class="relative content-static bg-[#fff] rounded-[12px] md:rounded-[16px]"
|
||||
@@ -173,7 +187,7 @@ const handleLoadMoreRecent = () => {
|
||||
>
|
||||
<div
|
||||
class="overflow-hidden relative aspect-[16/9] flex-shrink-0 w-full rounded-[4px] cursor-pointer group sm:w-[60.3%] sm:rounded-[8px] md:w-[56%] lg:w-[710px] lg:rounded-[12px]"
|
||||
@click="handleVideoClick(item.url)"
|
||||
@click="handleVideoClick(item)"
|
||||
>
|
||||
<img
|
||||
:src="getYouTubeThumbnail(item.url, 'maxres')"
|
||||
@@ -191,7 +205,7 @@ const handleLoadMoreRecent = () => {
|
||||
</div>
|
||||
<div
|
||||
class="w-full mx-[8px] pb-[20px] border-b border-[rgba(0,0,0,0.08)] cursor-pointer sm:mx-[0] sm:pt-[12px] sm:pr-[16px] sm:pb-[0] sm:border-none md:pt-[20px] md:pr-[28px] lg:pt-[40px] lg:pr-[48px]"
|
||||
@click="handleVideoClick(item.url)"
|
||||
@click="handleVideoClick(item)"
|
||||
@mouseenter="recommendHover = true"
|
||||
@mouseleave="recommendHover = false"
|
||||
>
|
||||
@@ -225,7 +239,7 @@ const handleLoadMoreRecent = () => {
|
||||
v-for="(item, index) in visibleVideos"
|
||||
:key="`recent-${item.url}-${index}`"
|
||||
class="p-3 rounded-[12px] bg-white md:p-4 md:rounded-[16px] lg:p-5 group cursor-pointer"
|
||||
@click="handleVideoClick(item.url)"
|
||||
@click="handleVideoClick(item)"
|
||||
>
|
||||
<div
|
||||
class="overflow-hidden relative aspect-[16/9] w-full rounded-[4px] sm:rounded-[8px] lg:rounded-[12px]"
|
||||
|
||||
@@ -141,6 +141,7 @@ const getArticleUrl = (articleId: string) => {
|
||||
:url="getArticleUrl(item.article_id)"
|
||||
:analytics-sarea="pageVerTmplNameEn"
|
||||
link-target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
/>
|
||||
</div>
|
||||
</SplideSlide>
|
||||
|
||||
@@ -15,7 +15,7 @@ interface Props {
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsData } = useAnalytics()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
const splideRef = ref<SplideSlide | null>(null)
|
||||
const currentSlideIndex = ref<number | null>(null)
|
||||
@@ -29,7 +29,7 @@ const paginationData = computed(() => {
|
||||
|
||||
const goToSlide = (index: number, title: string) => {
|
||||
const splide = splideRef.value?.splide
|
||||
const paginationAnalytics = {
|
||||
const analytics = {
|
||||
action_type: 'click',
|
||||
click_item: title,
|
||||
click_sarea: props.pageVerTmplNameEn,
|
||||
@@ -37,7 +37,7 @@ const goToSlide = (index: number, title: string) => {
|
||||
|
||||
if (splide) {
|
||||
splide.go(index)
|
||||
sendLog(locale.value, useAnalyticsData(paginationAnalytics))
|
||||
sendLog(locale.value, analytics)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { StoveJsService } from '#layers/types/Stove'
|
||||
|
||||
export type ClassType = HTMLAttributes['class']
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
stoveJsService?: StoveJsService
|
||||
@@ -27,39 +24,9 @@ interface ParsedCustomLinkOptions {
|
||||
query?: Record<string, any>
|
||||
}
|
||||
|
||||
|
||||
// 타입 정의
|
||||
interface FooterMenuItem {
|
||||
title: string
|
||||
url: string
|
||||
target?: string
|
||||
active?: string
|
||||
export type {
|
||||
CommonRequestType,
|
||||
CommonResponseType,
|
||||
CommonPeriodType,
|
||||
ParsedCustomLinkOptions,
|
||||
}
|
||||
|
||||
interface GameRatingInfo {
|
||||
title: string
|
||||
company_name: string
|
||||
rating_grade: string
|
||||
reg_no: string
|
||||
prod_date: string
|
||||
rating_class_no: string
|
||||
rating_type: string
|
||||
content_info: string
|
||||
}
|
||||
|
||||
interface FooterData {
|
||||
use_game_rating: boolean
|
||||
game_rating_info: GameRatingInfo
|
||||
use_dev_ci_url?: boolean
|
||||
dev_ci_url?: string
|
||||
dev_ci_img_path?: string
|
||||
fund_display_yn?: string
|
||||
fund_display_url?: string
|
||||
}
|
||||
|
||||
interface DevCiConfig {
|
||||
dev_ci_yn: boolean
|
||||
dev_ci_img_path: string
|
||||
}
|
||||
|
||||
export type { CommonRequestType, CommonResponseType, CommonPeriodType, ParsedCustomLinkOptions, FooterMenuItem, GameRatingInfo, FooterData, DevCiConfig }
|
||||
|
||||
@@ -49,3 +49,12 @@ export interface TrackingObject {
|
||||
click_item: string
|
||||
click_sarea: string
|
||||
}
|
||||
|
||||
export interface ColorObject {
|
||||
color_code?: string
|
||||
color_name?: string
|
||||
color_code_btn?: string
|
||||
color_name_btn?: string
|
||||
color_code_txt?: string
|
||||
color_name_txt?: string
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { TrackingObject } from '#layers/types/api/common'
|
||||
import type { TrackingObject, ColorObject } from '#layers/types/api/common'
|
||||
|
||||
// API 요청 파라미터 타입
|
||||
export interface GameDataRequest {
|
||||
@@ -31,10 +31,7 @@ export interface GameDataValue {
|
||||
key_color_json: GameDataKeyColors
|
||||
use_game_font: boolean
|
||||
comm_sns_bg_color_json: {
|
||||
display: {
|
||||
color_code: string
|
||||
color_name: string
|
||||
}
|
||||
display: ColorObject
|
||||
}
|
||||
comm_multilang_filename: string
|
||||
footer_dev_ci_img_yn: boolean
|
||||
@@ -134,11 +131,7 @@ export interface GameDataGlobal {
|
||||
lang_json: string // JSON 문자열로 변경
|
||||
}
|
||||
|
||||
export interface GameDataResourceGroupBtnInfo {
|
||||
color_code_btn: string
|
||||
color_name_btn: string
|
||||
color_code_txt: string
|
||||
color_name_txt: string
|
||||
export interface GameDataResourceGroupBtnInfo extends ColorObject {
|
||||
disabled: boolean
|
||||
txt_btn_name: string
|
||||
detail: Record<string, any>
|
||||
@@ -176,7 +169,7 @@ export interface GameDataMenu {
|
||||
url_path: string
|
||||
link_target: string
|
||||
children: GameDataMenuChildren
|
||||
tracking_json: string | TrackingObject // JSON 문자열 또는 객체로 변경
|
||||
tracking_json: TrackingObject
|
||||
}
|
||||
|
||||
// GNB 설정 타입
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { TrackingObject } from '#layers/types/api/common'
|
||||
import type { TrackingObject, ColorObject } from '#layers/types/api/common'
|
||||
|
||||
// API 요청 파라미터 타입
|
||||
export interface PageDataRequest {
|
||||
@@ -53,7 +53,7 @@ export interface PageDataLnbMenu {
|
||||
menu_name: string
|
||||
target_type: number
|
||||
page_ver_tmpl_name_en: string
|
||||
tracking_json: Record<string, TrackingObject>
|
||||
tracking_json: TrackingObject
|
||||
children?: Record<string, PageDataLnbMenu>
|
||||
}
|
||||
|
||||
@@ -83,27 +83,23 @@ export interface PageDataResourceGroupResPath {
|
||||
path_pc?: string
|
||||
}
|
||||
|
||||
export interface PageDataResourceGroupBtnInfo {
|
||||
color_code_btn: string
|
||||
color_name_btn: string
|
||||
color_code_txt: string
|
||||
color_name_txt: string
|
||||
export interface PageDataResourceGroupBtnInfo extends ColorObject {
|
||||
disabled: boolean
|
||||
txt_btn_name: string
|
||||
detail: Record<string, any>
|
||||
}
|
||||
|
||||
// 리소스 그룹 타입
|
||||
export interface PageDataResourceGroupDisplay extends ColorObject {
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface PageDataResourceGroup {
|
||||
group_label?: string
|
||||
resource_type?: PageDataResourceGroupType
|
||||
res_path?: PageDataResourceGroupResPath
|
||||
btn_info?: PageDataResourceGroupBtnInfo
|
||||
display?: {
|
||||
text: string
|
||||
color_code?: string
|
||||
color_name?: string
|
||||
}
|
||||
display?: PageDataResourceGroupDisplay
|
||||
tracking?: TrackingObject
|
||||
}
|
||||
|
||||
|
||||
34
layers/types/components/footer.ts
Normal file
34
layers/types/components/footer.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
interface FooterMenuItem {
|
||||
title: string
|
||||
url: string
|
||||
target?: string
|
||||
active?: string
|
||||
}
|
||||
|
||||
interface GameRatingInfo {
|
||||
title: string
|
||||
company_name: string
|
||||
rating_grade: string
|
||||
reg_no: string
|
||||
prod_date: string
|
||||
rating_class_no: string
|
||||
rating_type: string
|
||||
content_info: string
|
||||
}
|
||||
|
||||
interface FooterData {
|
||||
use_game_rating: boolean
|
||||
game_rating_info: GameRatingInfo
|
||||
use_dev_ci_url?: boolean
|
||||
dev_ci_url?: string
|
||||
dev_ci_img_path?: string
|
||||
fund_display_yn?: string
|
||||
fund_display_url?: string
|
||||
}
|
||||
|
||||
interface DevCiConfig {
|
||||
dev_ci_yn: boolean
|
||||
dev_ci_img_path: string
|
||||
}
|
||||
|
||||
export type { FooterMenuItem, GameRatingInfo, FooterData, DevCiConfig }
|
||||
@@ -3,11 +3,8 @@
|
||||
* @description ui 처리에 필요한 유틸리티 함수를 제공합니다.
|
||||
*/
|
||||
|
||||
import type { GameDataResourceGroupBtnInfo } from '#layers/types/api/gameData'
|
||||
import type {
|
||||
PageDataResourceGroups,
|
||||
PageDataResourceGroupBtnInfo,
|
||||
} from '#layers/types/api/pageData'
|
||||
import type { ColorObject } from '#layers/types/api/common'
|
||||
import type { PageDataResourceGroups } from '#layers/types/api/pageData'
|
||||
|
||||
/**
|
||||
* 색상값을 반환합니다.
|
||||
@@ -15,7 +12,7 @@ import type {
|
||||
* @param colorCode 색상 코드
|
||||
* @returns 색상 값
|
||||
*/
|
||||
export const getColorCode = ({
|
||||
const getColorCode = ({
|
||||
colorName,
|
||||
colorCode,
|
||||
}: {
|
||||
@@ -24,6 +21,7 @@ export const getColorCode = ({
|
||||
}) => {
|
||||
if (colorName) return `var(--${colorName})`
|
||||
else if (colorCode) return colorCode
|
||||
return 'var(--primary)'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,11 +31,15 @@ export const getColorCode = ({
|
||||
* @returns 색상 값
|
||||
*/
|
||||
export const getColorCodeFromData = (
|
||||
data: GameDataResourceGroupBtnInfo | PageDataResourceGroupBtnInfo,
|
||||
type: 'btn' | 'txt' = 'txt'
|
||||
data: ColorObject,
|
||||
type: 'btn' | 'txt' | 'none' = 'txt'
|
||||
) => {
|
||||
const suffix = type === 'btn' ? '_btn' : '_txt'
|
||||
|
||||
const suffixMap: Record<'btn' | 'txt' | 'none', string> = {
|
||||
btn: '_btn',
|
||||
txt: '_txt',
|
||||
none: '',
|
||||
}
|
||||
const suffix = suffixMap[type]
|
||||
const colorName = data?.[`color_name${suffix}` as keyof typeof data]
|
||||
const colorCode = data?.[`color_code${suffix}` as keyof typeof data]
|
||||
|
||||
@@ -61,15 +63,14 @@ export const getPaginationClass = (
|
||||
}
|
||||
|
||||
// 색상 추출 또는 기본값 사용
|
||||
const paginationActive = getColorCode({
|
||||
colorName: paginationGroups[0]?.display?.color_name,
|
||||
colorCode: paginationGroups[0]?.display?.color_code,
|
||||
})
|
||||
|
||||
const paginationDisabled = getColorCode({
|
||||
colorName: paginationGroups[1]?.display?.color_name,
|
||||
colorCode: paginationGroups[1]?.display?.color_code,
|
||||
})
|
||||
const paginationActive = getColorCodeFromData(
|
||||
paginationGroups[0]?.display,
|
||||
'none'
|
||||
)
|
||||
const paginationDisabled = getColorCodeFromData(
|
||||
paginationGroups[1]?.display,
|
||||
'none'
|
||||
)
|
||||
return {
|
||||
'--pagination-active': paginationActive,
|
||||
'--pagination-disabled': paginationDisabled,
|
||||
|
||||
Reference in New Issue
Block a user