feat. FX_PREREGIST_01 템플릿 제작

This commit is contained in:
clkim
2025-11-04 21:14:45 +09:00
parent e76ac480b7
commit 6518695096
37 changed files with 678 additions and 59 deletions

View File

@@ -0,0 +1,627 @@
<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'
interface Props {
components: PageDataTemplateComponents
pageVerTmplSeq: number
}
const props = defineProps<Props>()
const { tm } = useI18n()
const test = {
subTitle: {
groups: [
{
display: {
text: '사전 등록 기간 + 2024.06.04 ~ 정식 오픈 전까지',
color_code: '',
color_name: 'primary',
},
resource_type: 'TXT',
},
],
},
mainTitle: {
groups: [
{
display: {
text: '로드나인 사전 등록',
color_code: '',
color_name: 'primary',
},
resource_type: 'TXT',
},
],
},
background: {
groups: [
{
res_path: {
path_mo: '/local/template/l9/16/1/1/FX_PREREGIST_01/common/bg_m.jpg',
path_pc: '/local/template/l9/16/1/1/FX_PREREGIST_01/common/bg.jpg',
},
resource_type: 'IMG_COMM',
},
],
},
txtSnsLink: {
groups: [
{
display: {
text: 'https://pf.kakao.com/_xmyxjpG',
},
resource_type: 'TXT',
},
{
display: {
text: 'https://www.youtube.com/@LORDNINE_KR',
},
resource_type: 'TXT',
},
],
},
description: {
groups: [
{
display: {
text: '로드나인은 PC, GooglePlay, AppStore에서 즐기실 수 있습니다',
color_code: '',
color_name: 'text-primary',
},
resource_type: 'TXT',
},
],
},
imgAccReward: {
groups: [
{
display: {
text: '골드 100,000',
},
res_path: {
path_mo:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward01_m.png',
path_pc:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward01.png',
},
resource_type: 'IMG_LANG',
},
{
display: {
text: '중급 물약 x100',
},
res_path: {
path_mo:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward02_m.png',
path_pc:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward02.png',
},
resource_type: 'IMG_LANG',
},
{
display: {
text: '방어구 강화석 x3',
},
res_path: {
path_mo:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward03_m.png',
path_pc:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward03.png',
},
resource_type: 'IMG_LANG',
},
{
display: {
text: '무기 강화석 x3',
},
res_path: {
path_mo:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward04_m.png',
path_pc:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward04.png',
},
resource_type: 'IMG_LANG',
},
// {
// display: {
// text: '순수한 무기 강화석 x1',
// },
// res_path: {
// path_mo:
// '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward05_m.png',
// path_pc:
// '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward05.png',
// },
// resource_type: 'IMG_LANG',
// },
],
},
imgSnsButton: {
groups: [
{
res_path: {
path_mo:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/sns_button01_m.jpg',
path_pc:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/sns_button01.jpg',
},
tracking: {
click_item: 'imgSnsButton_7. 사전등록-SNS버튼이미지(1)',
action_type: 'click',
click_sarea: 'promotionPreregist_tmpl_01__imgSnsButton',
},
resource_type: 'IMG_LANG',
},
{
res_path: {
path_mo:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/sns_button02_m.jpg',
path_pc:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/sns_button02.jpg',
},
tracking: {
click_item: 'imgSnsButton_7. 사전등록-SNS버튼이미지(2)',
action_type: 'click',
click_sarea: 'promotionPreregist_tmpl_01__imgSnsButton',
},
resource_type: 'IMG_LANG',
},
],
},
imgSnsReward: {
groups: [
{
res_path: {
path_mo:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_preregist_reward_sns_m.png',
path_pc:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_preregist_reward_sns.png',
},
resource_type: 'IMG_LANG',
},
],
},
subTitleAccReward: {
groups: [
{
display: {
text: '이벤트 기간 + 2024.06.04 ~ 정식 오픈 전까지',
color_code: '#c5902f',
color_name: '',
},
resource_type: 'TXT',
},
],
},
imgPreregistReward: {
groups: [
{
res_path: {
path_mo:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_preregist_reward_m.png',
path_pc:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_preregist_reward.png',
},
resource_type: 'IMG_LANG',
},
],
},
mainTitleAccReward: {
groups: [
{
display: {
text: '카카오톡 공식 채널 구독 이벤트',
color_code: '#c5902f',
color_name: '',
},
resource_type: 'TXT',
},
],
},
backgroundAccReward: {
groups: [
{
res_path: {
path_mo:
'/local/template/l9/16/1/1/FX_PREREGIST_01/common/bg_acc_reward_m.jpg',
path_pc:
'/local/template/l9/16/1/1/FX_PREREGIST_01/common/bg_acc_reward.jpg',
},
resource_type: 'IMG_COMM',
},
],
},
descriptionAccReward: {
groups: [
{
display: {
text: '※ 달성 여부는 실시간으로 반영되지 않을 수 있습니다.',
color_code: '#737474',
color_name: '',
},
resource_type: 'TXT',
},
],
},
isAccRewardCompleted: {},
preregistButtonColor: {
groups: [
{
display: {
color_code: '#cc0000',
color_name: '',
},
resource_type: 'COLOR',
},
{
display: {
color_code: '#ccff00',
color_name: '',
},
resource_type: 'COLOR',
},
],
},
rewardTitleAccReward: {
groups: [
{
display: {
text: '누적 목표를 달성할수록 더 많은 보상을 드립니다!',
color_code: '#33312e',
color_name: '',
},
resource_type: 'TXT',
},
],
},
imgAccRewardIncomplete: {
groups: [
{
display: {
text: '골드 100,000',
},
res_path: {
path_mo:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete01_m.png',
path_pc:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete01.png',
},
resource_type: 'IMG_LANG',
},
{
display: {
text: '중급 물약 x100',
},
res_path: {
path_mo:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete02_m.png',
path_pc:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete02.png',
},
resource_type: 'IMG_LANG',
},
{
display: {
text: '방어구 강화석 x3',
},
res_path: {
path_mo:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete03_m.png',
path_pc:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete03.png',
},
resource_type: 'IMG_LANG',
},
{
display: {
text: '무기 강화석 x3',
},
res_path: {
path_mo:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete04_m.png',
path_pc:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete04.png',
},
resource_type: 'IMG_LANG',
},
{
display: {
text: '순수한 무기 강화석 x1',
},
res_path: {
path_mo:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete05_m.png',
path_pc:
'/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete05.png',
},
resource_type: 'IMG_LANG',
},
],
},
} as any
// Constants
const PREREGIST_PLATFORMS = [
{ platform: 'stove', label: '사전 등록 하기' },
{ platform: 'google_play', label: '사전 등록 하기' },
{ platform: 'app_store', label: '사전 등록 하기' },
] as const
const COLOR_INDEX = {
BACKGROUND: 0,
TEXT: 1,
} as const
// Preregist Section
const backgroundData = computed(() =>
getComponentGroup(props.components, 'background')
)
const mainTitleData = computed(() =>
getComponentGroup(props.components, 'mainTitle')
)
const subTitleData = computed(() =>
getComponentGroup(props.components, 'subTitle')
)
const imgPreregistRewardData = computed(() =>
getComponentGroup(props.components, 'imgPreregistReward')
)
const imgSnsRewardData = computed(() =>
getComponentGroup(props.components, 'imgSnsReward')
)
const preregistButtonColorData = computed(() =>
getComponentGroupAry(props.components, 'preregistButtonColor')
)
const imgSnsButtonData = computed(() =>
getComponentGroupAry(props.components, 'imgSnsButton')
)
const txtSnsLinkData = computed(() =>
getComponentGroupAry(props.components, 'txtSnsLink')
)
const descriptionData = computed(() =>
getComponentGroup(props.components, 'description')
)
const snsButtons = computed(() => {
const buttons = imgSnsButtonData.value
const links = txtSnsLinkData.value
if (!buttons?.length) return []
return buttons.map((button, index) => ({
image: button,
link: links?.[index]?.display?.text ?? '',
}))
})
const buttonColors = computed(() => {
const colorData = preregistButtonColorData.value
if (!colorData?.length) {
return { background: undefined, text: undefined }
}
return {
background: getColorCode({
colorName: colorData[COLOR_INDEX.BACKGROUND]?.display?.color_name,
colorCode: colorData[COLOR_INDEX.BACKGROUND]?.display?.color_code,
}),
text: getColorCode({
colorName: colorData[COLOR_INDEX.TEXT]?.display?.color_name,
colorCode: colorData[COLOR_INDEX.TEXT]?.display?.color_code,
}),
}
})
// Reward Section
const backgroundAccRewardData = computed(() =>
getComponentGroup(props.components, 'backgroundAccReward')
)
const mainTitleAccRewardData = computed(() =>
getComponentGroup(props.components, 'mainTitleAccReward')
)
const subTitleAccRewardData = computed(() =>
getComponentGroup(props.components, 'subTitleAccReward')
)
const rewardTitleAccRewardData = computed(() =>
getComponentGroup(props.components, 'rewardTitleAccReward')
)
const imgAccRewardData = computed(() =>
getComponentGroupAry(test, 'imgAccReward')
)
const imgAccRewardLength = computed(() => imgAccRewardData.value?.length ?? 0)
const descriptionAccRewardData = computed(() =>
getComponentGroup(props.components, 'descriptionAccReward')
)
const splideOptions = computed(() => {
return {
type: 'slide',
gap: 16,
arrows: false,
pagination: false,
destroy: true,
breakpoints: {
[BREAKPOINTS.md - 1]: {
destroy: imgAccRewardLength.value <= 3,
perPage: 'auto',
focus: 'center',
drag: imgAccRewardLength.value > 3,
padding: {
left: 40,
right: 40,
},
},
[BREAKPOINTS.sm - 1]: {
destroy: false,
gap: 12,
drag: true,
padding: {
left: 20,
right: 20,
},
},
},
}
})
</script>
<template>
<section class="relative py-[80px] md:py-[120px]">
<WidgetsBackground v-if="backgroundData" :resources-data="backgroundData" />
<div class="content-standard">
<WidgetsMainTitle
v-if="mainTitleData"
:resources-data="mainTitleData"
class="title-xlg"
/>
<WidgetsSubTitle
v-if="subTitleData"
:resources-data="subTitleData"
class="title-sm mt-2"
/>
<div class="flex flex-col gap-4 mt-8 md:flex-row">
<div v-if="imgPreregistRewardData" class="w-full max-w-[446px]">
<AtomsImg
:src="getImagePaths(imgPreregistRewardData)"
loading="lazy"
decoding="async"
class="w-full h-full object-contain"
/>
</div>
<div v-if="imgSnsRewardData" class="relative w-full max-w-[446px]">
<AtomsImg
:src="getImagePaths(imgSnsRewardData)"
loading="lazy"
decoding="async"
class="w-full h-full object-contain"
/>
<ul
v-if="snsButtons.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="(snsButton, index) in snsButtons"
:key="`sns-${snsButton.link}-${index}`"
>
<a
:href="snsButton.link"
target="_blank"
rel="noopener noreferrer"
>
<AtomsImg :src="getImagePaths(snsButton.image)" />
<span class="sr-only">
{{ snsButton.link }}
</span>
</a>
</li>
</ul>
</div>
</div>
<div
v-if="PREREGIST_PLATFORMS.length"
class="flex gap-3 justify-center flex-wrap mt-8 md:gap-2.5"
>
<AtomsButtonLauncher
v-for="item in PREREGIST_PLATFORMS"
:key="`preregist-${item.platform}`"
type="duplication"
:platform="item.platform"
:background-color="buttonColors.background"
:text-color="buttonColors.text"
>
{{ item.label }}
</AtomsButtonLauncher>
</div>
<WidgetsDescription
v-if="descriptionData"
:resources-data="descriptionData"
class="mt-8"
/>
</div>
</section>
<section class="relative py-[80px] md:py-[120px]">
<WidgetsBackground
v-if="backgroundAccRewardData"
:resources-data="backgroundAccRewardData"
/>
<div class="content-standard">
<WidgetsMainTitle
v-if="mainTitleAccRewardData"
:resources-data="mainTitleAccRewardData"
class="title-xlg"
/>
<WidgetsSubTitle
v-if="subTitleAccRewardData"
:resources-data="subTitleAccRewardData"
class="title-sm mt-2"
/>
<WidgetsSubTitle
v-if="rewardTitleAccRewardData"
tag="h4"
:resources-data="rewardTitleAccRewardData"
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="imgAccRewardLength" class="mt-6 md:mt-8">
<ul class="hidden md:flex justify-center md:mb-[20px]">
<li
v-for="index in imgAccRewardLength"
:key="index"
class="flex items-center"
>
<span class="progress-bullet"></span>
<div v-if="index !== imgAccRewardLength" class="progress-bar">
<span class="progress-fill"></span>
</div>
</li>
</ul>
<BlocksSlideDefault
v-bind="splideOptions"
class="w-[100vw] mx-[-20px] sm:mx-[-40px] md:w-auto md:mx-auto"
>
<SplideSlide
v-for="(item, index) in imgAccRewardData"
:key="`reward-${item.id ?? index}`"
class="w-[162px] h-[228px] md:w-[176px] md:h-[249px]"
>
<AtomsImg
:src="getImagePaths(item)"
:alt="item?.display?.text ?? `Reward ${index + 1}`"
loading="lazy"
decoding="async"
class="w-full h-full object-contain"
/>
</SplideSlide>
</BlocksSlideDefault>
</div>
<WidgetsDescription
v-if="descriptionAccRewardData"
:resources-data="descriptionAccRewardData"
class="mt-6 md:mt-8"
/>
</div>
</section>
</template>
<style scoped>
/* destroy되었을 때 (슬라이드 비활성화) 중앙 정렬 */
.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: red;
/* background-color: var(--pagination-disabled); */
}
.progress-bar {
@apply relative w-[180px] h-0.5 overflow-hidden;
/* background-color: var(--pagination-disabled); */
background-color: red;
}
.progress-fill {
@apply absolute inset-y-0 left-0 w-[0];
background-color: var(--pagination-active);
}
</style>