fix. 템플릿 데이터 수정에 따른 fe 소스 수정

This commit is contained in:
clkim
2025-10-14 14:34:31 +09:00
parent 277532f0ba
commit 355e059e90
9 changed files with 125 additions and 127 deletions

2
.nvmrc
View File

@@ -1 +1 @@
22.18.0
22.11.0

View File

@@ -27,6 +27,7 @@ const colorCode = computed(() => {
})
const currentImageSrc = computed(() => {
if (!imageSrc.value) return ''
return breakpoints.value.isMobile
? imageSrc.value.mobileSrc || ''
: imageSrc.value.pcSrc || ''
@@ -36,25 +37,20 @@ const currentImageSrc = computed(() => {
const sanitizedContent = computed(() => {
return displayText.value?.replace(/\n/g, '<br/>') || ''
})
// 이미지가 있는지 확인
const hasImage = computed(() => {
return imageSrc.value && (imageSrc.value.mobileSrc || imageSrc.value.pcSrc)
})
</script>
<template>
<!-- 이미지 -->
<img
v-if="hasImage && currentImageSrc"
v-if="isTypeImage(resourcesData?.resource_type) && currentImageSrc"
:src="currentImageSrc"
:alt="alt || displayText"
:class="`w-full h-full object-${objectFit}`"
loading="lazy"
/>
<!-- 텍스트 -->
<span
v-else-if="displayText"
v-else-if="isTypeText(resourcesData?.resource_type)"
v-dompurify-html="sanitizedContent"
:style="{ color: getColorCode({ colorName, colorCode }) }"
class="block"

View File

@@ -1,10 +1,15 @@
<script setup lang="ts">
import { Splide, SplideSlide } from '@splidejs/vue-splide'
import { getFirstGroup, isTypeVideo } from '#layers/utils/dataUtil'
import { getMediaSrc, getYouTubeEmbedUrl } from '#layers/utils/youtube'
import type { Splide as SplideType, Options } from '@splidejs/splide'
import type { PageDataResourceGroup } from '#layers/types/api/pageData'
import type {
PageDataResourceGroups,
PageDataResourceGroup,
} from '#layers/types/api/pageData'
interface Props {
slideData: { media: any; set_order: number }[]
slideData: { media: PageDataResourceGroups; set_order: number }[]
videoPlay?: PageDataResourceGroup
arrows?: boolean
pagination?: boolean
@@ -46,10 +51,27 @@ const thumbOptions = computed<Options>(() => ({
},
}))
const isPassVideo = (groups: PageDataResourceGroups, index: number) => {
const firstGroup = getFirstGroup(groups)
return (
firstGroup &&
isTypeVideo(firstGroup?.resource_type) &&
index !== playingSlideIndex.value
)
}
const handleVideoClick = (index: number) => {
playingSlideIndex.value = index
}
const getYouTubeEmbedUrlFromMedia = (
resourceGroups: PageDataResourceGroup[]
) => {
const resourceGroup = getFirstGroup(resourceGroups)
const mediaSrc = getMediaSrc(resourceGroup)
return mediaSrc ? getYouTubeEmbedUrl(mediaSrc, true) : ''
}
let mainInst: SplideType | null = null
let thumbsInst: SplideType | null = null
@@ -87,16 +109,14 @@ onBeforeUnmount(() => {
:class="{ 'opacity-0': playingSlideIndex === index }"
/>
<AtomsButtonPlay
v-if="
getMediaType(item.media) === 'video' && playingSlideIndex !== index
"
v-if="isPassVideo(item.media, index)"
:resources-data="videoPlay"
class="btn-play"
@click="handleVideoClick(index)"
/>
<iframe
v-if="playingSlideIndex === index"
:src="getYouTubeEmbedUrl(getMediaText(item.media), true)"
:src="getYouTubeEmbedUrlFromMedia(item.media)"
class="absolute top-0 left-0 w-full h-full"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"

View File

@@ -46,7 +46,7 @@ const currentPosterSrc = computed(() => {
<div class="absolute inset-0 w-full h-full">
<!-- 이미지 타입-->
<div
v-if="resourcesData?.group_type === 'image'"
v-if="isTypeImage(resourcesData?.resource_type)"
class="w-full h-full bg-cover bg-center bg-no-repeat"
:class="getResponsiveClass()"
:style="bgStyles"
@@ -54,7 +54,7 @@ const currentPosterSrc = computed(() => {
<!-- 비디오 타입 -->
<video
v-else-if="resourcesData?.group_type === 'video' && currentVideoSrc"
v-else-if="isTypeVideo(resourcesData?.resource_type) && currentVideoSrc"
class="w-full h-full object-cover"
:poster="currentPosterSrc"
autoplay

View File

@@ -61,7 +61,7 @@ const getButtonProps = (button: PageDataResourceGroup) => ({
>
<AtomsButton
v-for="(button, index) in props.resourcesData"
:key="`${button.group_code}-${index}`"
:key="index"
v-bind="getButtonProps(button)"
class="size-extra-small md:size-medium"
>

View File

@@ -36,12 +36,23 @@ export interface PageDataValue {
lnb_text_color_code_active: string
lnb_text_color_code_deactive: string
lnb_menus: PageDataLnbMenu[]
templates: Record<string, PageDataTemplate>
meta_tag: PageDataMetaTag
templates: Record<string, PageDataTemplate>
}
// ===== 세부 데이터 타입들 =====
// LNB 메뉴 타입
export interface PageDataLnbMenu {
path_code: string
depth: number
sort_order: number
menu_name: string
target_type: number
page_ver_tmpl_name_en: string
tracking: string
}
// 메타 태그 타입
export interface PageDataMetaTag {
x_desc: string
@@ -54,7 +65,14 @@ export interface PageDataMetaTag {
page_title: string
}
// 리소스 그룹 타입
export type PageDataResourceGroupType =
| 'TXT'
| 'BTN'
| 'VID'
| 'IMG_COMM'
| 'IMG_LANG'
| 'IMG_COMM_GLOBAL'
export interface PageDataResourceGroupResPath {
path_mo: string
path_pc?: string
@@ -72,8 +90,7 @@ export interface PageDataResourceGroupBtnInfo {
// 리소스 그룹 타입
export interface PageDataResourceGroup {
group_type?: string
group_code?: string
resource_type?: PageDataResourceGroupType
res_path?: PageDataResourceGroupResPath
btn_info?: PageDataResourceGroupBtnInfo
display?: {
@@ -81,29 +98,27 @@ export interface PageDataResourceGroup {
color_code?: string
color_name?: string
}
tracking: string // JSON 문자열
tracking: string
}
export type PageDataResourceGroups = PageDataResourceGroup[]
// 컴포넌트 타입
export interface PageDataComponent {
groups: PageDataResourceGroup[]
groups: PageDataResourceGroups
}
// 템플릿 컴포넌트 타입 - 두 가지 패턴 지원
export type PageDataTemplateComponent = Record<string, PageDataComponent>
// 그룹 세트 아이템 타입
export type PageDataTemplateComponentSet = PageDataTemplateComponent & {
set_order?: number
}
// 템플릿 컴포넌트 타입 - 두 가지 패턴
export type PageDataTemplateComponents =
| Record<string, PageDataComponent> // 직접 PageDataComponent가 들어있는 패턴
| { group_sets: Record<string, any> } // group_sets 안에 PageDataComponent가 들어있는 패턴
// LNB 메뉴 타입
export interface PageDataLnbMenu {
path_code: string
depth: number
sort_order: number
menu_name: string
target_type: number
page_ver_tmpl_name_en: string
tracking: string // JSON string
}
| PageDataTemplateComponent // 단일 컴포넌트 패턴
| { group_sets: PageDataTemplateComponentSet[] } // 그룹 세트 패턴
// 템플릿 타입
export interface PageDataTemplate {

View File

@@ -6,6 +6,7 @@
import type {
PageDataValue,
PageDataComponent,
PageDataResourceGroupType,
} from '#layers/types/api/pageData'
/**
@@ -19,6 +20,44 @@ export const getLayoutType = (
return pageData?.page_type === 1 ? 'default' : 'promotion'
}
/**
* 이미지 타입인지 확인합니다.
* @param type 리소스 그룹 타입
* @returns 이미지 타입 여부
*/
export const isTypeImage = (type: PageDataResourceGroupType): boolean => {
return (
type === 'IMG_COMM' || type === 'IMG_LANG' || type === 'IMG_COMM_GLOBAL'
)
}
/**
* 비디오 타입인지 확인합니다.
* @param type 리소스 그룹 타입
* @returns 비디오 타입 여부
*/
export const isTypeVideo = (type: PageDataResourceGroupType): boolean => {
return type === 'VID'
}
/**
* 텍스트 타입인지 확인합니다.
* @param type 리소스 그룹 타입
* @returns 텍스트 타입 여부
*/
export const isTypeText = (type: PageDataResourceGroupType): boolean => {
return type === 'TXT'
}
/**
* 버튼 타입인지 확인합니다.
* @param type 리소스 그룹 타입
* @returns 버튼 타입 여부
*/
export const isTypeButton = (type: PageDataResourceGroupType): boolean => {
return type === 'BTN'
}
/**
* 그룹의 첫 번째 데이터를 반환합니다.
* @param source props.components 또는 group 객체

View File

@@ -94,61 +94,6 @@ export const formatTimestamp = (
.replace('ss', seconds)
}
/**
* 타임스탬프를 상대적 시간으로 변환합니다 (예: "3일 전", "2시간 전")
* @param timestamp 타임스탬프 (밀리초 또는 초)
* @param locale 로케일 (기본값: 'ko-KR')
* @returns 상대적 시간 문자열
*/
export const formatRelativeTime = (
timestamp: number | string,
locale: string = 'ko-KR'
): string => {
if (!timestamp) return ''
let ts = typeof timestamp === 'string' ? parseInt(timestamp) : timestamp
if (ts < 10000000000) {
ts = ts * 1000
}
const date = new Date(ts)
const now = new Date()
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000)
if (diffInSeconds < 60) {
return locale === 'ko-KR' ? '방금 전' : 'just now'
}
const diffInMinutes = Math.floor(diffInSeconds / 60)
if (diffInMinutes < 60) {
return locale === 'ko-KR'
? `${diffInMinutes}분 전`
: `${diffInMinutes} minutes ago`
}
const diffInHours = Math.floor(diffInMinutes / 60)
if (diffInHours < 24) {
return locale === 'ko-KR'
? `${diffInHours}시간 전`
: `${diffInHours} hours ago`
}
const diffInDays = Math.floor(diffInHours / 24)
if (diffInDays < 30) {
return locale === 'ko-KR' ? `${diffInDays}일 전` : `${diffInDays} days ago`
}
const diffInMonths = Math.floor(diffInDays / 30)
if (diffInMonths < 12) {
return locale === 'ko-KR'
? `${diffInMonths}개월 전`
: `${diffInMonths} months ago`
}
const diffInYears = Math.floor(diffInMonths / 12)
return locale === 'ko-KR' ? `${diffInYears}년 전` : `${diffInYears} years ago`
}
/**
* 배열 또는 객체를 배열로 변환합니다.
* @param value 변환할 값 (배열, 객체, 또는 undefined/null)

View File

@@ -3,6 +3,12 @@
* @description 유튜브 관련 유틸리티 함수를 제공합니다.
*/
import { getFirstGroup, isTypeVideo } from '#layers/utils/dataUtil'
import type {
PageDataResourceGroups,
PageDataResourceGroup,
} from '#layers/types/api/pageData'
/**
* 유튜브 URL에서 비디오 ID를 추출합니다.
* @param url - 유튜브 URL (watch, embed, youtu.be 등 다양한 형태 지원)
@@ -95,11 +101,10 @@ export const getYouTubeThumbnailFromUrl = (
* @param source - 미디어 소스 객체
* @returns 미디어 text(src)
*/
export const getMediaText = (source: Record<string, any>): string => {
if (!source) return ''
const resource = source.groups?.[0]
const mediaUrl = resource?.display?.text
return mediaUrl || ''
export const getMediaSrc = (resourceGroup: PageDataResourceGroup): string => {
if (!resourceGroup) return ''
const mediaSrc = resourceGroup?.display?.text
return mediaSrc || ''
}
/**
@@ -109,42 +114,20 @@ export const getMediaText = (source: Record<string, any>): string => {
* @returns 미디어 이미지 소스
*/
export const getMediaImgSrc = (
source: Record<string, any>,
resourceGroups: PageDataResourceGroups,
quality: 'default' | 'medium' | 'high' | 'standard' | 'maxres' = 'high'
): string => {
if (!source) return ''
if (!resourceGroups) return ''
const resource = source.groups?.[0]
const mediaType = resource?.group_type
const mediaUrl = resource?.display?.text
const resourceGroup = getFirstGroup(resourceGroups)
const mediaSrc = getMediaSrc(resourceGroup)
const mediaType = resourceGroup?.resource_type
if (mediaType === 'video' && mediaUrl) {
const videoId = getYouTubeVideoId(mediaUrl)
if (isTypeVideo(mediaType) && mediaSrc) {
const videoId = getYouTubeVideoId(mediaSrc)
const thumbnailUrl = getYouTubeThumbnail(videoId, quality)
return thumbnailUrl
}
return mediaUrl || ''
}
/**
* 미디어 타입을 확인합니다.
* @param source - 미디어 소스 객체
* @returns 미디어 타입 ('video' | 'image' | '')
*/
export const getMediaType = (source: Record<string, any>): string => {
if (!source) return ''
const resource = source.groups?.[0]
const mediaType = resource?.group_type
return mediaType || ''
}
/**
* 비디오 아이템인지 확인합니다.
* @param source - 미디어 소스 객체
* @returns 비디오 여부
*/
export const isVideoItem = (source: Record<string, any>): boolean => {
return getMediaType(source) === 'video'
return mediaSrc || ''
}