Merge commit 'be15192e5962a17508a2efe50b5aade943526b54' into feature/20250910-all

This commit is contained in:
clkim
2025-09-12 09:32:05 +09:00
14 changed files with 215 additions and 186 deletions

View File

@@ -36,7 +36,7 @@ const gnbList = gameData?.gnb?.menus as GameDataGnb["menus"];
class="relative group"
>
<!-- Link 컴포넌트 사용 -->
<MoleculesLink
<MoleculesHybridLink
:to="gnbItem.url_path"
:target="gnbItem.link_target"
class="relative flex items-center h-[64px]"
@@ -46,7 +46,7 @@ const gnbList = gameData?.gnb?.menus as GameDataGnb["menus"];
<span
class="absolute bottom-0 left-0 w-full h-2 border-b-2 border-transparent transition-border-color group-hover:border-theme-foreground-reversal group-active:border-theme-foreground-reversal-10"
></span>
</MoleculesLink>
</MoleculesHybridLink>
<div
v-if="gnbItem.children"
class="absolute top-full left-[-28px] min-w-[190px] pt-[4px] pointer-events-none group-hover:pointer-events-auto"
@@ -56,7 +56,7 @@ const gnbList = gameData?.gnb?.menus as GameDataGnb["menus"];
>
<li v-for="child in gnbItem.children" :key="child.menu_name">
<!-- Link 컴포넌트 사용 -->
<MoleculesLink
<MoleculesHybridLink
:to="child.url_path"
:target="child.link_target"
class="flex items-center px-4 py-[9px] rounded-[12px] transition-background hover:bg-theme-foreground-reversal-40 active:bg-theme-foreground-reversal-70"
@@ -66,7 +66,7 @@ const gnbList = gameData?.gnb?.menus as GameDataGnb["menus"];
v-if="child.link_target === '_blank'"
class="ml-1"
/>
</MoleculesLink>
</MoleculesHybridLink>
</li>
</ul>
</div>

View File

@@ -0,0 +1,32 @@
<script setup lang="ts">
interface Props {
tag: string;
text: string;
imageSrc?: any;
imageClass?: string;
}
const props = defineProps<Props>();
</script>
<template>
<component :is="tag" v-bind="$attrs">
<template v-if="imageSrc && 'mobileSrc' in imageSrc">
<!-- 모바일 이미지 (sm 미만) -->
<img
v-if="imageSrc.mobileSrc"
:src="imageSrc.mobileSrc"
:alt="text"
:class="`${props.imageClass} sm:hidden`"
/>
<!-- PC 이미지 (sm 이상) -->
<img
v-if="imageSrc.pcSrc"
:src="imageSrc.pcSrc"
:alt="text"
:class="`${props.imageClass} hidden sm:block`"
/>
</template>
<span v-else v-html="text?.replace(/\n/g, '<br/>') || ''" />
</component>
</template>

View File

@@ -1,31 +1,35 @@
<script setup lang="ts">
import {
getResourcesData,
getResponsiveSrc,
getResponsiveClass,
getResponsiveSrc,
} from "#layers/utils/dataUtil";
import type { PageDataComponent } from "#layers/types/api/pageData";
const props = defineProps<{ componentData: PageDataComponent }>();
const props = defineProps<{
componentData: PageDataComponent;
gradientClass?: string;
groupSets?: boolean;
}>();
const resourcesData = computed(() => {
return getResourcesData({
resources: props.componentData?.resources,
groupSets: props.groupSets,
});
});
const bgStyles = computed(() => {
const result = getResponsiveSrc({
pathArray: resourcesData.value?.res_path,
type: "bg",
return getResponsiveSrc(resourcesData.value?.res_path, {
resourcesType: "bg",
});
return result && typeof result === "object" && "--pc-bg" in result
? result
: {};
});
const hasImage = computed(() => {
return (
resourcesData.value?.group_type === "image" && resourcesData.value?.res_path
);
const videoSrc = computed(() => {
return getResponsiveSrc(resourcesData.value?.res_path, {
resourcesType: "video",
});
});
const posterSrc = computed(() => {
return getResponsiveSrc(resourcesData.value?.res_path);
});
</script>
@@ -33,34 +37,42 @@ const hasImage = computed(() => {
<div class="absolute inset-0 w-full h-full">
<!-- 이미지 타입-->
<div
v-if="hasImage"
v-if="resourcesData?.group_type === 'image'"
class="w-full h-full bg-cover bg-center bg-no-repeat"
:class="getResponsiveClass()"
:style="bgStyles"
></div>
<!-- 비디오 타입 -->
<!-- <video
v-else-if="assetType === 'VIDEO'"
class="absolute inset-0 w-full h-full object-cover"
:poster="getResponsiveSrc({ resources: resources, type: 'img' })"
autoplay
muted
loop
playsinline
>
<source :src="assetPath" type="video/mp4" />
<source :src="assetPath" type="video/webm" />
</video> -->
<div
class="absolute inset-0"
style="
background: linear-gradient(
180deg,
rgba(16, 13, 15, 0) 0%,
#100d0f 95%
);
"
></div>
<template v-else-if="resourcesData?.group_type === 'video'">
<!-- 모바일 비디오 (sm 미만) -->
<video
v-if="videoSrc?.mobileSrc"
class="w-full h-full object-cover sm:hidden"
:poster="posterSrc?.mobileSrc"
autoplay
muted
loop
playsinline
>
<source :src="videoSrc.mobileSrc" type="video/mp4" />
<source :src="videoSrc.mobileSrc" type="video/webm" />
</video>
<!-- PC 비디오 (sm 이상) -->
<video
v-if="videoSrc?.pcSrc"
class="w-full h-full object-cover hidden sm:block"
:poster="posterSrc?.pcSrc"
autoplay
muted
loop
playsinline
>
<source :src="videoSrc.pcSrc" type="video/mp4" />
<source :src="videoSrc.pcSrc" type="video/webm" />
</video>
</template>
<div class="absolute inset-0" :class="gradientClass"></div>
</div>
</template>

View File

@@ -0,0 +1,36 @@
<script setup lang="ts">
import { getResourcesData } from "#layers/utils/dataUtil";
import type { PageDataComponent } from "#layers/types/api/pageData";
const props = defineProps<{
componentData: PageDataComponent;
groupSets?: boolean;
}>();
const resourcesData = computed(() => {
return getResourcesData({
resources: props.componentData?.resources,
isMultiple: true,
groupSets: props.groupSets,
});
});
console.log("ButtonList resourcesData:", resourcesData.value);
</script>
<template>
<template
v-if="resourcesData"
v-for="button in resourcesData"
:key="button.group_label"
>
<AtomsButton>
{{ button.btn_info?.txt_btn_name }}
</AtomsButton>
<!-- :style="{
backgroundColor: button.btn_info?.color_code_btn,
color: button.btn_info?.color_code_txt,
}" -->
</template>
</template>

View File

@@ -2,41 +2,30 @@
import { getResourcesData, getResponsiveSrc } from "#layers/utils/dataUtil";
import type { PageDataComponent } from "#layers/types/api/pageData";
const props = defineProps<{ componentData: PageDataComponent }>();
const props = defineProps<{
componentData: PageDataComponent;
groupSets?: boolean;
}>();
const resourcesData = computed(() => {
return getResourcesData({
resources: props.componentData?.resources,
groupSets: props.groupSets,
});
});
const displayText = resourcesData.value?.display?.text;
const imageSrc = getResponsiveSrc({
pathArray: resourcesData.value?.res_path,
type: "image",
});
const displayText = resourcesData.value?.display?.txt;
const imageSrc = getResponsiveSrc(resourcesData.value?.res_path);
</script>
<template>
<div v-if="resourcesData">
<img
v-if="imageSrc && 'mobileSrc' in imageSrc"
:src="imageSrc.mobileSrc"
:alt="displayText"
class="w-full sm:hidden"
/>
<!-- PC 이미지 (sm 이상) -->
<img
v-if="imageSrc && 'pcSrc' in imageSrc"
:src="imageSrc.pcSrc"
:alt="displayText"
class="w-full hidden sm:block"
/>
</div>
<p
v-else
v-html="displayText?.replace(/\n/g, '<br/>') || ''"
class="text-center"
></p>
<MoleculesVisualContent
tag="p"
:text="displayText"
:image-src="imageSrc as any"
image-class="w-full"
:style="{
color: '#000000',
}"
/>
</template>

View File

@@ -2,44 +2,27 @@
import { getResourcesData, getResponsiveSrc } from "#layers/utils/dataUtil";
import type { PageDataComponent } from "#layers/types/api/pageData";
const props = defineProps<{ componentData: PageDataComponent }>();
const props = defineProps<{
componentData: PageDataComponent;
groupSets?: boolean;
}>();
const resourcesData = computed(() => {
return getResourcesData({
resources: props.componentData?.resources,
groupSets: props.groupSets,
});
});
const displayText = resourcesData.value?.display?.text;
const imageSrc = getResponsiveSrc({
pathArray: resourcesData.value?.res_path,
type: "image",
});
const displayText = resourcesData.value?.display?.txt;
const imageSrc = getResponsiveSrc(resourcesData.value?.res_path);
</script>
<template>
<h2>
<template v-if="resourcesData">
<img
v-if="imageSrc && 'mobileSrc' in imageSrc"
:src="imageSrc.mobileSrc"
:alt="displayText"
class="w-full sm:hidden"
/>
<!-- PC 이미지 (sm 이상) -->
<img
v-if="imageSrc && 'pcSrc' in imageSrc"
:src="imageSrc.pcSrc"
:alt="displayText"
class="w-full hidden sm:block"
/>
</template>
<span
v-else
v-html="displayText?.replace(/\n/g, '<br/>')"
class="text-center"
>
</span>
</h2>
<MoleculesVisualContent
tag="h2"
:text="displayText"
:image-src="imageSrc as any"
image-class="w-full"
/>
</template>

View File

@@ -2,44 +2,30 @@
import { getResourcesData, getResponsiveSrc } from "#layers/utils/dataUtil";
import type { PageDataComponent } from "#layers/types/api/pageData";
const props = defineProps<{ componentData: PageDataComponent }>();
const props = defineProps<{
componentData: PageDataComponent;
groupSets?: boolean;
}>();
const resourcesData = computed(() => {
return getResourcesData({
resources: props.componentData?.resources,
groupSets: props.groupSets,
});
});
const displayText = resourcesData.value?.display?.text;
const imageSrc = getResponsiveSrc({
pathArray: resourcesData.value?.res_path,
type: "image",
});
const displayText = resourcesData.value?.display?.txt;
const imageSrc = getResponsiveSrc(resourcesData.value?.res_path);
</script>
<template>
<h3>
<template v-if="resourcesData">
<img
v-if="imageSrc && 'mobileSrc' in imageSrc"
:src="imageSrc.mobileSrc"
:alt="displayText"
class="w-full sm:hidden"
/>
<!-- PC 이미지 (sm 이상) -->
<img
v-if="imageSrc && 'pcSrc' in imageSrc"
:src="imageSrc.pcSrc"
:alt="displayText"
class="w-full hidden sm:block"
/>
</template>
<span
v-else
v-html="displayText?.replace(/\n/g, '<br/>')"
class="text-center"
>
</span>
</h3>
<MoleculesVisualContent
tag="h3"
:text="displayText"
:image-src="imageSrc as any"
image-class="w-full"
:style="{
color: '#000000',
}"
/>
</template>

View File

@@ -13,14 +13,8 @@ const resourcesData = computed(() => {
resources: props.componentData?.resources,
});
});
const bgStyles = computed(() => {
const result = getResponsiveSrc({
pathArray: resourcesData.value?.res_path,
type: "bg",
});
return result && typeof result === "object" && "--pc-bg" in result
? result
: {};
const bgStyles = getResponsiveSrc(resourcesData.value?.res_path, {
resourcesType: "bg",
});
</script>

View File

@@ -31,6 +31,7 @@ const handleCloseModal = () => {
<TemplatesBackground
v-if="props.components?.background"
:component-data="props.components?.background"
gradient-class="bg-gradient-to-b from-[#100d0f]/0 to-[#100d0f]"
/>
<div
class="relative h-full flex flex-col items-center justify-center gap-4"

View File

@@ -11,27 +11,36 @@ console.log("components:", props.components);
<template>
<section class="relative h-[640px] lg:h-[1000px]">
<TemplatesBackground
v-if="props.components?.background"
:component-data="props.components?.background"
v-if="props.components?.cardBackground"
:component-data="props.components?.cardBackground"
:group-sets="true"
/>
<div
class="relative h-full flex flex-col items-center justify-center gap-4"
>
<TemplatesSubTitle
v-if="props.components.subTitle"
:component-data="props.components.subTitle"
v-if="props.components.cardSubTitle"
:component-data="props.components.cardSubTitle"
:group-sets="true"
class="text-[24px] font-[500] text-[#ffffff] leading-[34px]"
/>
<TemplatesMainTitle
v-if="props.components.mainTitle"
:component-data="props.components.mainTitle"
v-if="props.components.cardMainTitle"
:component-data="props.components.cardMainTitle"
:group-sets="true"
class="text-[50px] font-[700] text-[#c7a28b] leading-[70px]"
/>
<TemplatesDescription
v-if="props.components.cardDescription"
:component-data="props.components.cardDescription"
:group-sets="true"
class="text-[20px] font-[500] text-white/70 leading-[30px]"
/>
<TemplatesButtonList
v-if="props.components.cardButtonList"
:component-data="props.components.cardButtonList"
:group-sets="true"
/>
</div>
</section>
</template>

View File

@@ -1,17 +0,0 @@
export type ImageType = "image" | "bg";
export interface ResponsiveImagePath {
path_mo?: string;
path_pc?: string;
}
export type ResponsiveImageResult =
| {
mobileSrc: string;
pcSrc: string;
}
| {
"--pc-bg": string;
"--mobile-bg": string;
}
| null;

View File

@@ -1,13 +1,6 @@
// 타입 import
import type {
ResponsiveImagePath,
ImageType,
ResponsiveImageResult,
} from "../types/utils/dataUtil";
// 이미지 경로 리턴하는 함수
// 이미지 호스트 리턴하는 함수
// [TODO] 환경변수 처리 수정
export const getResolvedSrc = (path: string): string => {
export const getResolvedHost = (path: string): string => {
const config = useRuntimeConfig();
// const isDev = process.env.NODE_ENV === "development";
// const rootPath = isDev ? "/images" : `${config.public.staticUrl}`;
@@ -17,17 +10,25 @@ export const getResolvedSrc = (path: string): string => {
return `${rootPath}${path}`;
};
// 리소스 데이터 리턴하는 함수
// [TODO] data 타입 정의
export const getResourcesData = ({
resources,
groupsDepth = 0,
isMultiple = false,
groupSets = false,
}: {
resources: any;
groupsDepth?: number;
isMultiple?: boolean;
groupSets?: boolean;
}) => {
const group = resources[0]?.groups?.[groupsDepth];
const groups = groupSets
? resources[0]?.group_sets[0]?.groups
: resources[0]?.groups;
return group ?? null;
if (isMultiple) {
return groups;
}
return groups?.[0] ?? null;
};
// 반응형 클래스 리턴하는 함수
@@ -35,32 +36,35 @@ export const getResponsiveClass = () => {
return ["bg-[image:var(--mobile-bg)]", "sm:bg-[image:var(--pc-bg)]"];
};
// 반응형 이미지 리턴하는 함수
export const getResponsiveSrc = ({
pathArray,
type = "image",
}: {
pathArray: any;
type?: ImageType;
}): ResponsiveImageResult | ResponsiveImagePath => {
if (!pathArray?.path_mo) {
// 통합된 반응형 리소스 함수
export const getResponsiveSrc = (
pathArray: any,
options: {
resourcesType?: "image" | "bg" | "video";
} = {}
) => {
const { resourcesType = "image" } = options;
const pcField = resourcesType === "video" ? "path_vid_pc" : "path_pc";
const mobileField = resourcesType === "video" ? "path_vid_mo" : "path_mo";
if (!pathArray?.[mobileField]) {
return null;
}
const resolvedImages = {
pc: getResolvedSrc(pathArray.path_pc || pathArray.path_mo),
mobile: getResolvedSrc(pathArray.path_mo),
pc: getResolvedHost(pathArray[pcField] || pathArray[mobileField]),
mobile: getResolvedHost(pathArray[mobileField]),
};
if (type === "image") {
if (resourcesType === "bg") {
return {
mobileSrc: resolvedImages.mobile,
pcSrc: resolvedImages.pc,
"--pc-bg": `url(${resolvedImages.pc})`,
"--mobile-bg": `url(${resolvedImages.mobile})`,
};
}
return {
"--pc-bg": `url(${resolvedImages.pc})`,
"--mobile-bg": `url(${resolvedImages.mobile})`,
mobileSrc: resolvedImages.mobile,
pcSrc: resolvedImages.pc,
};
};