feat. GR_DETAIL_01 템플릿 제작
This commit is contained in:
@@ -4,7 +4,6 @@ import { getLayoutType } from '#layers/utils/dataUtil'
|
||||
|
||||
const pageDataStore = usePageDataStore()
|
||||
const { pageData } = storeToRefs(pageDataStore)
|
||||
console.log("🚀 d2 index ~ pageData:", pageData)
|
||||
|
||||
const currentLayout = computed(() => getLayoutType(pageData.value))
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
pagination: true,
|
||||
})
|
||||
|
||||
const splideRef = ref()
|
||||
|
||||
const options = computed((): ResponsiveOptions => {
|
||||
return {
|
||||
type: 'fade',
|
||||
@@ -24,6 +26,8 @@ const options = computed((): ResponsiveOptions => {
|
||||
speed: 600,
|
||||
updateOnMove: true,
|
||||
autoplay: props.autoplay,
|
||||
pauseOnHover: false,
|
||||
pauseOnFocus: false,
|
||||
arrows: props.arrows,
|
||||
pagination: props.pagination,
|
||||
classes: {
|
||||
@@ -36,10 +40,14 @@ const options = computed((): ResponsiveOptions => {
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
splide: computed(() => splideRef.value?.splide),
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Splide :options="options" class="h-full">
|
||||
<Splide ref="splideRef" :options="options" class="h-full">
|
||||
<slot />
|
||||
</Splide>
|
||||
</template>
|
||||
|
||||
@@ -179,7 +179,7 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.thumbnail-splide {
|
||||
@apply overflow-hidden flex justify-center w-screen mt-[20px] mx-[-20px] sm:mx-[-40px] md:w-auto md:mx-0 md:px-[120px] md:mt-[28px];
|
||||
@apply overflow-hidden flex justify-center w-screen mt-[20px] mx-[-20px] sm:mx-[-40px] md:max-w-[100%] md:w-fit md:mx-auto md:px-[120px] md:mt-[28px];
|
||||
}
|
||||
.thumbnail-splide:deep(.splide__track) {
|
||||
@apply !px-[20px] sm:!px-[40px] md:!px-[0];
|
||||
|
||||
@@ -81,7 +81,6 @@ const findValueFromOption = (target: string, { options = {} }: any) => {
|
||||
* @param {object} options
|
||||
*/
|
||||
const sendGA = (analytics: AnalyticsDetailType, { options = {} }: any) => {
|
||||
console.log('🚀 ~ 1111 sendGA ~ analytics:', analytics)
|
||||
try {
|
||||
const { gtag } = useGtag()
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import GrVisual03 from '#layers/templates/GrVisual03/index.vue'
|
||||
import GrGallery01 from '#layers/templates/GrGallery01/index.vue'
|
||||
import GrGallery02 from '#layers/templates/GrGallery02/index.vue'
|
||||
import GrGallery03 from '#layers/templates/GrGallery03/index.vue'
|
||||
import GrDetail01 from '#layers/templates/GrDetail01/index.vue'
|
||||
|
||||
const templateRegistry = {
|
||||
GR_VISUAL_01: { component: GrVisual01 },
|
||||
@@ -13,7 +14,7 @@ const templateRegistry = {
|
||||
GR_GALLERY_02: { component: GrGallery02 },
|
||||
GR_GALLERY_03: { component: GrGallery03 },
|
||||
// GR_BOARD_01: { component: GrBoard01 },
|
||||
// GR_DETAIL_01: { component: GrDetail01 },
|
||||
GR_DETAIL_01: { component: GrDetail01 },
|
||||
// GR_DETAIL_02: { component: GrDetail02 },
|
||||
// GR_DETAIL_03: { component: GrDetail03 },
|
||||
// GR_CONTENTS_01: { component: GrContents01 },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
console.log("🚀 ~ promotion 22222222")
|
||||
console.log('🚀 ~ promotion')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -19,11 +19,9 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
||||
const languagePattern = /^\/([a-z]{2})(?:\/|$)/
|
||||
const match = to.path.match(languagePattern)
|
||||
const currentLangCode = match ? match[1] : null
|
||||
// console.log('🚀 3333~ currentLangCode:', currentLangCode)
|
||||
|
||||
// 허용된 언어 코드 목록
|
||||
const allowedLangCodes = gameDataStore.gameData.lang_codes || []
|
||||
// console.log('🚀 ~ allowedLangCodes:', allowedLangCodes)
|
||||
|
||||
// 현재 언어가 허용된 언어 목록에 없으면 404로 리다이렉트
|
||||
if (currentLangCode && !allowedLangCodes.includes(currentLangCode)) {
|
||||
|
||||
@@ -38,7 +38,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
||||
|
||||
if (response?.code === 0 && 'value' in response) {
|
||||
store.setPageData(response.value)
|
||||
console.log('🚀 ~ cleanData:', response.value)
|
||||
console.log('🚀 ~ pageData:', response.value)
|
||||
} else {
|
||||
store.clearPageData()
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ export default defineEventHandler(async event => {
|
||||
if (response?.code === 0 && 'value' in response) {
|
||||
event.context.gameData = response.value
|
||||
event.context.googleAnalyticsId = response.value?.ga_code
|
||||
|
||||
console.log('🚀 ~ gameData:', response.value)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('gameData load error:', error)
|
||||
|
||||
156
layers/templates/GrDetail01/index.vue
Normal file
156
layers/templates/GrDetail01/index.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<script setup lang="ts">
|
||||
import { SplideSlide } from '@splidejs/vue-splide'
|
||||
import { hasComponentGroup, getComponentGroup } from '#layers/utils/dataUtil'
|
||||
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const splideRef = ref<SplideSlide | null>(null)
|
||||
const currentSlide = ref<number | null>(null)
|
||||
|
||||
const slideData = computed(() => {
|
||||
return getComponentContainer(props.components, 'group_sets')
|
||||
})
|
||||
|
||||
const goToSlide = (index: number) => {
|
||||
const splide = splideRef.value?.splide
|
||||
if (splide) {
|
||||
splide.go(index)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
const splide = splideRef.value?.splide
|
||||
if (splide) {
|
||||
currentSlide.value = splide.index
|
||||
|
||||
splide.on('move', (newIndex: number) => {
|
||||
currentSlide.value = newIndex
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="section-container relative">
|
||||
<BlocksSlideFade
|
||||
v-if="slideData"
|
||||
ref="splideRef"
|
||||
:autoplay="true"
|
||||
:interval="5000"
|
||||
:arrows="false"
|
||||
:pagination="false"
|
||||
class="h-full"
|
||||
>
|
||||
<SplideSlide v-for="(item, index) in slideData" :key="index">
|
||||
<WidgetsBackground
|
||||
v-if="hasComponentGroup(item, 'background')"
|
||||
:resources-data="getComponentGroup(item, 'background')"
|
||||
/>
|
||||
<div class="section-content">
|
||||
<WidgetsMainTitle
|
||||
v-if="hasComponentGroup(item, 'mainTitle')"
|
||||
:resources-data="getComponentGroup(item, 'mainTitle')"
|
||||
class="title-md"
|
||||
/>
|
||||
<WidgetsSubTitle
|
||||
v-if="hasComponentGroup(item, 'subTitle')"
|
||||
:resources-data="getComponentGroup(item, 'subTitle')"
|
||||
class="title-sm mt-0.5 md:mt-1"
|
||||
/>
|
||||
<WidgetsDescription
|
||||
v-if="hasComponentGroup(item, 'description')"
|
||||
:resources-data="getComponentGroup(item, 'description')"
|
||||
class="description-lg mt-4 md:mt-6"
|
||||
/>
|
||||
</div>
|
||||
</SplideSlide>
|
||||
</BlocksSlideFade>
|
||||
<div v-if="slideData && slideData.length > 1" class="splide-pagination">
|
||||
<div
|
||||
v-for="(item, index) in slideData"
|
||||
:key="index"
|
||||
:class="[
|
||||
'pagination-item',
|
||||
{
|
||||
'is-active': currentSlide === index,
|
||||
'is-completed': index < currentSlide,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<button
|
||||
:class="['btn-pagination', { 'is-active': currentSlide === index }]"
|
||||
@click="goToSlide(index)"
|
||||
>
|
||||
<span class="item-bullet"></span>
|
||||
<span class="item-title">
|
||||
{{ getComponentGroup(item, 'naviTitle')?.display?.text || '' }}
|
||||
</span>
|
||||
</button>
|
||||
<div v-if="index !== slideData.length - 1" class="progress-bar">
|
||||
<span class="progress-fill"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.splide-pagination {
|
||||
@apply flex items-center absolute bottom-10 left-1/2 -translate-x-1/2 z-10 md:bottom-[96px];
|
||||
}
|
||||
.btn-pagination {
|
||||
@apply relative;
|
||||
}
|
||||
.pagination-item {
|
||||
@apply flex items-center;
|
||||
}
|
||||
.item-bullet {
|
||||
@apply block w-3 h-3 rounded-full bg-white/30 transition-all duration-300;
|
||||
}
|
||||
.item-title {
|
||||
@apply hidden absolute -bottom-[46px] left-1/2 -translate-x-1/2 whitespace-nowrap text-sm font-medium text-white/30 md:block;
|
||||
}
|
||||
.progress-bar {
|
||||
@apply relative w-[68px] h-0.5 bg-white/30 overflow-hidden md:w-[184px];
|
||||
}
|
||||
.progress-fill {
|
||||
@apply absolute inset-y-0 left-0 bg-white;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
/* 활성화 상태 (현재 슬라이드) */
|
||||
.is-active .item-bullet {
|
||||
@apply bg-white;
|
||||
}
|
||||
.is-active .item-title {
|
||||
@apply text-white;
|
||||
}
|
||||
.is-active .progress-fill {
|
||||
animation: progressFill 5000ms linear forwards;
|
||||
}
|
||||
|
||||
/* 완료 상태 (지나간 슬라이드) */
|
||||
.is-completed .item-bullet {
|
||||
@apply bg-white;
|
||||
}
|
||||
.is-completed .progress-fill {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@keyframes progressFill {
|
||||
from {
|
||||
width: 0%;
|
||||
}
|
||||
to {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
getComponentContainer,
|
||||
getComponentGroup,
|
||||
ensureMinimumSlideData,
|
||||
} from '#layers/utils/dataUtil'
|
||||
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
|
||||
@@ -18,9 +18,9 @@ const backgroundData = computed(() =>
|
||||
const mainTitleData = computed(() =>
|
||||
getComponentGroup(props.components, 'mainTitle')
|
||||
)
|
||||
const slideData = computed(() => {
|
||||
return ensureMinimumSlideData(props.components)
|
||||
})
|
||||
const slideData = computed(() =>
|
||||
getComponentContainer(props.components, 'group_sets')
|
||||
)
|
||||
const videoPlayData = computed(() =>
|
||||
getComponentGroup(props.components, 'videoPlay')
|
||||
)
|
||||
|
||||
@@ -13,20 +13,21 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const slideData = computed(() => {
|
||||
return getComponentContainer(props.components, 'group_sets')
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="section-container">
|
||||
<BlocksSlideFade
|
||||
v-if="props.components?.group_sets"
|
||||
v-if="slideData"
|
||||
:arrows="true"
|
||||
:pagination="true"
|
||||
class="h-full"
|
||||
>
|
||||
<SplideSlide
|
||||
v-for="(item, index) in props.components.group_sets"
|
||||
:key="index"
|
||||
>
|
||||
<SplideSlide v-for="(item, index) in slideData" :key="index">
|
||||
<WidgetsBackground
|
||||
v-if="hasComponentGroup(item, 'background')"
|
||||
:resources-data="getComponentGroup(item, 'background')"
|
||||
|
||||
@@ -65,13 +65,18 @@ export const isTypeButton = (type: PageDataResourceGroupType): boolean => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 그룹의 첫 번째 데이터를 반환합니다.
|
||||
* @param container 리소스 컨테이너
|
||||
* @returns 첫 번째 그룹 데이터 또는 null
|
||||
* 컴포넌트 컨테이너를 반환합니다.
|
||||
* @param components props.components
|
||||
* @param componentName 컴포넌트 이름
|
||||
* @returns 컴포넌트 컨테이너
|
||||
*/
|
||||
export const getFirstGroup = (container: PageDataResourceContainer) => {
|
||||
if (!container) return null
|
||||
return container.groups?.[0] || null
|
||||
export const getComponentContainer = (
|
||||
components: PageDataTemplateComponents | OperateComponents,
|
||||
componentName: string
|
||||
) => {
|
||||
if (!components) return []
|
||||
|
||||
return components[componentName] || []
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,8 +107,7 @@ export const getComponentGroup = (
|
||||
) => {
|
||||
if (!components) return null
|
||||
|
||||
const component = components[componentName] as PageDataResourceContainer
|
||||
return getFirstGroup(component)
|
||||
return components[componentName]?.groups?.[0] || null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,7 +133,7 @@ export const getComponentGroupAry = (
|
||||
*/
|
||||
export const ensureMinimumSlideData = (
|
||||
components: PageDataTemplateComponents,
|
||||
minCount: number = 3
|
||||
minCount: number = 4
|
||||
): PageDataTemplateComponentSet[] => {
|
||||
if (!components) return []
|
||||
|
||||
@@ -138,7 +142,7 @@ export const ensureMinimumSlideData = (
|
||||
: []
|
||||
|
||||
// 빈 배열이거나 이미 최소 개수를 만족하면 그대로 반환
|
||||
if (arrayData.length === 0 || arrayData.length >= minCount) {
|
||||
if (arrayData.length <= 1 || arrayData.length >= minCount) {
|
||||
return arrayData
|
||||
}
|
||||
|
||||
@@ -155,10 +159,10 @@ export const ensureMinimumSlideData = (
|
||||
*/
|
||||
export const ensureMinimumSlideOperateData = (
|
||||
data: OperateGroupItem[],
|
||||
minCount: number = 3
|
||||
minCount: number = 4
|
||||
): OperateGroupItem[] => {
|
||||
// 빈 배열이거나 이미 최소 개수를 만족하면 그대로 반환
|
||||
if (data.length === 0 || data.length >= minCount) {
|
||||
if (data.length <= 1 || data.length >= minCount) {
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user