424 lines
12 KiB
Vue
424 lines
12 KiB
Vue
<script setup lang="ts">
|
|
import { SplideSlide } from '@splidejs/vue-splide'
|
|
import { getComponentGroup, getComponentGroupAry } from '#layers/utils/dataUtil'
|
|
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
|
import type { OperateGroupItem } from '#layers/types/api/operateResources'
|
|
import type { Platform } from '#layers/types/components/button'
|
|
|
|
interface Props {
|
|
components: PageDataTemplateComponents
|
|
pageVerTmplSeq: number
|
|
}
|
|
|
|
const props = defineProps<Props>()
|
|
|
|
const runtimeConfig = useRuntimeConfig()
|
|
const device = useDevice()
|
|
|
|
const gameDataStore = useGameDataStore()
|
|
const pageDataStore = usePageDataStore()
|
|
|
|
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
|
|
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_preregist.json'
|
|
|
|
// Multilingual
|
|
const resultGetMultilingual = await useGetMultilingual({
|
|
baseApiUrl: dataResourcesUrl,
|
|
fileName: multilingualFileName,
|
|
})
|
|
const { tm, locale }: any = useI18n({
|
|
useScope: 'local',
|
|
messages: Object(resultGetMultilingual?.value?.multilingual),
|
|
})
|
|
|
|
const { getOperateResources } = useOperateResources()
|
|
const { gameData } = storeToRefs(gameDataStore)
|
|
const { pageData } = storeToRefs(pageDataStore)
|
|
|
|
// Constants
|
|
const COLOR_INDEX = { BACKGROUND: 0, TEXT: 1 } as const
|
|
|
|
const preregistModalRef = ref<{
|
|
handleOpenPreregist: () => Promise<void>
|
|
} | null>(null)
|
|
|
|
// Preregist Section
|
|
const preregistCode = computed(
|
|
() => getComponentGroup(props.components, 'eventKey')?.display?.text
|
|
)
|
|
const prdBackgroundData = computed(() =>
|
|
getComponentGroup(props.components, 'background')
|
|
)
|
|
const preMainTitleData = computed(() =>
|
|
getComponentGroup(props.components, 'mainTitle')
|
|
)
|
|
const preSubTitleData = computed(() =>
|
|
getComponentGroup(props.components, 'subTitle')
|
|
)
|
|
const preImgPreregistdData = computed(() =>
|
|
getComponentGroup(props.components, 'imgPreregistReward')
|
|
)
|
|
const preImgSnsData = computed(() =>
|
|
getComponentGroup(props.components, 'imgSnsReward')
|
|
)
|
|
const preDescriptionData = computed(() =>
|
|
getComponentGroup(props.components, 'description')
|
|
)
|
|
// SNS Buttons
|
|
const snsButtonsData = computed(() => {
|
|
const buttons = getComponentGroupAry(props.components, 'imgSnsButton')
|
|
const links = getComponentGroupAry(props.components, 'txtSnsLink')
|
|
|
|
if (!buttons?.length) return []
|
|
|
|
return buttons.map((button, index) => ({
|
|
image: button,
|
|
link: links?.[index]?.display?.text ?? '',
|
|
id: button.id ?? `sns-${index}`,
|
|
}))
|
|
})
|
|
|
|
// Button Colors
|
|
const buttonColors = computed(() => {
|
|
const colorData = getComponentGroupAry(
|
|
props.components,
|
|
'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,
|
|
}),
|
|
}
|
|
})
|
|
|
|
// Reward Section
|
|
const accBackgroundData = computed(() =>
|
|
getComponentGroup(props.components, 'backgroundAccReward')
|
|
)
|
|
const accMainTitleData = computed(() =>
|
|
getComponentGroup(props.components, 'mainTitleAccReward')
|
|
)
|
|
const accSubTitleData = computed(() =>
|
|
getComponentGroup(props.components, 'subTitleAccReward')
|
|
)
|
|
const accRewardTitleData = computed(() =>
|
|
getComponentGroup(props.components, 'rewardTitleAccReward')
|
|
)
|
|
const accDescriptionData = computed(() =>
|
|
getComponentGroup(props.components, 'descriptionAccReward')
|
|
)
|
|
const accPaginationData = computed(() =>
|
|
getComponentGroupAry(props.components, 'pagination')
|
|
)
|
|
|
|
// Async Data - 리워드 완료 데이터
|
|
const { data: rewardCompletedData } = await useAsyncData(
|
|
`fx-preregist-resources-${pageData.value?.page_seq}-${pageData.value?.page_ver}-${props.pageVerTmplSeq}`,
|
|
async () => {
|
|
const { page_seq, page_ver } = pageData.value ?? {}
|
|
|
|
if (!page_seq || !page_ver) return []
|
|
|
|
try {
|
|
const operateGroupList = await getOperateResources({
|
|
pageSeq: page_seq,
|
|
pageVer: page_ver,
|
|
pageVerTmplSeq: props.pageVerTmplSeq,
|
|
langCode: locale.value,
|
|
})
|
|
|
|
return getComponentContainer(operateGroupList, 'isAccRewardCompleted', {
|
|
isGroup: true,
|
|
}) as OperateGroupItem[]
|
|
} catch (error) {
|
|
if (import.meta.dev) {
|
|
// eslint-disable-next-line no-console
|
|
console.error('[FxPreregist01] Failed to fetch reward data:', error)
|
|
}
|
|
return []
|
|
}
|
|
},
|
|
{
|
|
default: () => [],
|
|
server: false,
|
|
}
|
|
)
|
|
|
|
// Reward Images
|
|
const rewardImages = computed(() => {
|
|
const completeList = getComponentGroupAry(props.components, 'imgAccReward')
|
|
const incompleteList = getComponentGroupAry(
|
|
props.components,
|
|
'imgAccRewardIncomplete'
|
|
)
|
|
|
|
if (!completeList?.length || !incompleteList?.length) return []
|
|
|
|
return completeList.map((completeItem, index) => ({
|
|
id: completeItem.id ?? `reward-${index}`,
|
|
incomplete: incompleteList?.[index],
|
|
complete: completeItem,
|
|
flagType: rewardCompletedData.value?.[index]?.flag_type ?? 0,
|
|
}))
|
|
})
|
|
|
|
// Splide Options
|
|
const splideOptions = computed(() => {
|
|
const length = rewardImages.value.length
|
|
|
|
return {
|
|
type: 'slide' as const,
|
|
gap: 16,
|
|
arrows: false,
|
|
pagination: false,
|
|
destroy: true,
|
|
breakpoints: {
|
|
937: {
|
|
destroy: length < 5,
|
|
gap: 12,
|
|
padding: { left: 40, right: 40 },
|
|
},
|
|
[BREAKPOINTS.sm - 1]: {
|
|
padding: { left: 20, right: 20 },
|
|
},
|
|
723: {
|
|
destroy: length < 4,
|
|
},
|
|
561: {
|
|
destroy: false,
|
|
},
|
|
},
|
|
}
|
|
})
|
|
|
|
const isRunButtonVisible = (marketType?: Platform): boolean => {
|
|
if (device.isDesktop) return true
|
|
|
|
switch (marketType) {
|
|
case 'google_play':
|
|
return device.isAndroid
|
|
case 'app_store':
|
|
return device.isApple
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Handler
|
|
const handlePreregistClick = () => {
|
|
preregistModalRef.value?.handleOpenPreregist()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="section-container">
|
|
<!-- Preregist Section -->
|
|
<section class="relative py-[80px] md:py-[120px]">
|
|
<WidgetsBackground
|
|
v-if="prdBackgroundData"
|
|
:resources-data="prdBackgroundData"
|
|
/>
|
|
<div class="content-standard">
|
|
<WidgetsMainTitle
|
|
v-if="preMainTitleData"
|
|
:resources-data="preMainTitleData"
|
|
class="title-xlg"
|
|
/>
|
|
<WidgetsSubTitle
|
|
v-if="preSubTitleData"
|
|
:resources-data="preSubTitleData"
|
|
class="title-sm mt-2"
|
|
/>
|
|
<div class="flex flex-col gap-4 mt-8 sm:flex-row">
|
|
<div
|
|
v-if="preImgPreregistdData"
|
|
class="max-w-[336px] md:max-w-[446px]"
|
|
>
|
|
<AtomsImg
|
|
:src="getResourceSrc(preImgPreregistdData)"
|
|
:alt="preImgPreregistdData?.display?.text"
|
|
loading="lazy"
|
|
decoding="async"
|
|
class="w-full h-full object-contain"
|
|
/>
|
|
</div>
|
|
<div
|
|
v-if="preImgSnsData"
|
|
class="relative max-w-[336px] md:max-w-[446px]"
|
|
>
|
|
<AtomsImg
|
|
:src="getResourceSrc(preImgSnsData)"
|
|
:alt="preImgSnsData?.display?.text"
|
|
loading="lazy"
|
|
decoding="async"
|
|
class="w-full h-full object-contain"
|
|
/>
|
|
<ul
|
|
v-if="snsButtonsData.length"
|
|
class="absolute bottom-[20px] left-0 w-full flex items-center justify-center gap-2 md:bottom-[24px] md:gap-3"
|
|
>
|
|
<li
|
|
v-for="btn in snsButtonsData"
|
|
:key="btn.id"
|
|
class="w-[48px] h-[40px] md:w-[72px] md:h-[56px]"
|
|
>
|
|
<a :href="btn.link" target="_blank" rel="noopener noreferrer">
|
|
<AtomsImg
|
|
:src="getResourceSrc(btn.image)"
|
|
:alt="btn.image?.display?.text"
|
|
/>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="flex gap-3 justify-center flex-wrap mt-8 md:gap-2.5">
|
|
<BlocksButtonLauncher
|
|
type="duplication"
|
|
platform="stove"
|
|
:background-color="buttonColors.backgroundColor"
|
|
:text-color="buttonColors.textColor"
|
|
@click="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}`"
|
|
>
|
|
<BlocksButtonLauncher
|
|
v-if="isRunButtonVisible(platform as Platform)"
|
|
type="duplication"
|
|
:platform="platform as Platform"
|
|
:background-color="buttonColors.backgroundColor"
|
|
:text-color="buttonColors.textColor"
|
|
>
|
|
{{ tm('Preregist_Btn_Preegist') }}
|
|
</BlocksButtonLauncher>
|
|
</template>
|
|
</template>
|
|
</div>
|
|
<WidgetsDescription
|
|
v-if="preDescriptionData"
|
|
:resources-data="preDescriptionData"
|
|
class="mt-8"
|
|
/>
|
|
</div>
|
|
</section>
|
|
<!-- Reward Section -->
|
|
<section class="relative py-[80px] md:py-[120px]">
|
|
<WidgetsBackground
|
|
v-if="accBackgroundData"
|
|
:resources-data="accBackgroundData"
|
|
/>
|
|
<div class="content-standard">
|
|
<WidgetsMainTitle
|
|
v-if="accMainTitleData"
|
|
:resources-data="accMainTitleData"
|
|
class="title-xlg"
|
|
/>
|
|
<WidgetsSubTitle
|
|
v-if="accSubTitleData"
|
|
:resources-data="accSubTitleData"
|
|
class="title-sm mt-2"
|
|
/>
|
|
<WidgetsSubTitle
|
|
v-if="accRewardTitleData"
|
|
:resources-data="accRewardTitleData"
|
|
class="mt-[48px] text-[18px] font-[700] leading-[26px] tracking-[-0.54px] drop-shadow-[0_2px_2px_rgba(0,0,0,0.6)] md:mt-[72px] md:text-[24px] md:leading-[34px] md:tracking-[0.72px]"
|
|
/>
|
|
<div
|
|
v-if="rewardImages.length"
|
|
class="overflow-hidden w-[calc(100%+40px)] min-h-[228px] mt-6 mx-[-20px] sm:w-[calc(100%+80px)] sm:mx-[-40px] md:w-full md:min-h-[281px] md:mt-8 md:mx-auto"
|
|
>
|
|
<ClientOnly>
|
|
<ul
|
|
class="hidden md:flex justify-center md:mb-[20px]"
|
|
:style="getPaginationClass(accPaginationData)"
|
|
>
|
|
<li
|
|
v-for="(item, index) in rewardImages"
|
|
:key="item.id"
|
|
:class="[
|
|
'flex items-center',
|
|
{ 'is-completed': item.flagType === 2 },
|
|
]"
|
|
>
|
|
<span class="progress-bullet"></span>
|
|
<div
|
|
v-if="index < rewardImages.length - 1"
|
|
class="progress-bar"
|
|
></div>
|
|
</li>
|
|
</ul>
|
|
|
|
<BlocksSlideDefault v-bind="splideOptions">
|
|
<SplideSlide
|
|
v-for="item in rewardImages"
|
|
:key="item.id"
|
|
class="w-[162px] h-[228px] md:w-[172px] md:h-[245px]"
|
|
>
|
|
<AtomsImg
|
|
:src="
|
|
getResourceSrc(
|
|
item.flagType === 2 ? item.complete : item.incomplete
|
|
)
|
|
"
|
|
:alt="item.default?.display?.text"
|
|
loading="lazy"
|
|
decoding="async"
|
|
class="w-full h-full object-contain"
|
|
/>
|
|
</SplideSlide>
|
|
</BlocksSlideDefault>
|
|
</ClientOnly>
|
|
</div>
|
|
<WidgetsDescription
|
|
v-if="accDescriptionData"
|
|
:resources-data="accDescriptionData"
|
|
class="mt-6 md:mt-8"
|
|
/>
|
|
</div>
|
|
</section>
|
|
|
|
<WidgetsModalPreregist
|
|
ref="preregistModalRef"
|
|
:tm="tm"
|
|
:preregist-code="preregistCode"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.content-standard {
|
|
@apply max-w-[1024px] mx-auto;
|
|
}
|
|
|
|
.splide:not(.is-active):deep(.splide__list) {
|
|
@apply flex justify-center gap-3 md:gap-4;
|
|
}
|
|
|
|
.progress-bullet {
|
|
@apply block w-3 h-3 rounded-full transition-all duration-300;
|
|
background-color: var(--pagination-disabled);
|
|
}
|
|
.progress-bar {
|
|
@apply w-[180px] h-0.5 overflow-hidden;
|
|
background-color: var(--pagination-disabled);
|
|
}
|
|
.is-completed .progress-bullet,
|
|
.is-completed .progress-bar {
|
|
background-color: var(--pagination-active);
|
|
}
|
|
</style>
|