feat. 데이터 구조 변경에 따른 수정

This commit is contained in:
clkim
2025-09-11 14:38:02 +09:00
parent 060517e9ad
commit 457516a3a2
24 changed files with 213 additions and 250 deletions

View File

@@ -27,11 +27,10 @@ export default defineEventHandler(async (event) => {
}
}
} catch (error) {
console.log("gameAlias extraction error: ", error);
console.error("gameAlias extraction error: ", error);
}
try {
const queryParams: Record<string, string> = {
game_alias: gameAlias,
};
@@ -42,13 +41,19 @@ export default defineEventHandler(async (event) => {
if (response?.code === 0 && "value" in response) {
event.context.gameData = response.value;
// lang_codes를 사용해서 동적으로 i18n 설정 업데이트
if (response.value.lang_codes && Array.isArray(response.value.lang_codes)) {
if (
response.value.lang_codes &&
Array.isArray(response.value.lang_codes)
) {
event.context.availableLocales = response.value.lang_codes;
event.context.defaultLocale = response.value.default_lang_code || response.value.lang_codes[0] || 'ko';
event.context.defaultLocale =
response.value.default_lang_code ||
response.value.lang_codes[0] ||
"ko";
}
return response.value as GameDataValue;
}
} catch (error) {

View File

@@ -9,7 +9,7 @@
</template>
<style>
@import "#layers/assets/css/theme.css";
@import "#layers/assets/css/app.css";
</style>
<script setup lang="ts">

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { usePageDataStore } from "#layers/stores/usePageDataStore";
import Section from "#layers/components/molecules/Section.vue";
import Template from "#layers/components/molecules/Template.vue";
const pageDataStore = usePageDataStore();
const { pageData } = storeToRefs(pageDataStore);
@@ -34,7 +34,7 @@ watchEffect(() => {
<template>
<NuxtLayout :name="layout">
<ClientOnly>
<Section :templates="pageData?.templates ?? []" />
<Template :templates="pageData?.templates ?? []" />
</ClientOnly>
</NuxtLayout>
</template>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { usePageDataStore } from "#layers/stores/usePageDataStore";
import Section from "#layers/components/molecules/Section.vue";
import Template from "#layers/components/molecules/Template.vue";
const pageDataStore = usePageDataStore();
const { pageData } = storeToRefs(pageDataStore);
@@ -34,7 +34,7 @@ watchEffect(() => {
<template>
<NuxtLayout :name="layout">
<ClientOnly>
<Section :templates="pageData?.templates ?? []" />
<Template :templates="pageData?.templates ?? []" />
</ClientOnly>
</NuxtLayout>
</template>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { usePageDataStore } from "#layers/stores/usePageDataStore";
import Section from "#layers/components/molecules/Section.vue";
import Template from "#layers/components/molecules/Template.vue";
const pageDataStore = usePageDataStore();
const { pageData } = storeToRefs(pageDataStore);
@@ -9,6 +9,11 @@ const { pageData } = storeToRefs(pageDataStore);
// const layout = pageData.value?.meta?.layout ?? "default";
const layout = "default"; // 기본 레이아웃 사용
// definePageMeta를 사용하여 레이아웃을 미리 설정
definePageMeta({
layout: false, // 기본 레이아웃 비활성화
});
// definePageMeta를 사용하여 레이아웃을 미리 설정
watchEffect(() => {
if (pageData.value?.meta_tag) {
@@ -29,7 +34,7 @@ watchEffect(() => {
<template>
<NuxtLayout :name="layout">
<ClientOnly>
<Section :templates="pageData?.templates ?? []" />
<Template :templates="pageData?.templates ?? []" />
</ClientOnly>
</NuxtLayout>
</template>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { usePageDataStore } from "#layers/stores/usePageDataStore";
import Section from "#layers/components/molecules/Section.vue";
import Template from "#layers/components/molecules/Template.vue";
const pageDataStore = usePageDataStore();
const { pageData } = storeToRefs(pageDataStore);
@@ -43,7 +43,7 @@ watchEffect(() => {
<template>
<NuxtLayout :name="layout">
<ClientOnly>
<Section :templates="pageData?.templates ?? []" />
<Template :templates="pageData?.templates ?? []" />
</ClientOnly>
</NuxtLayout>
</template>

View File

@@ -1,3 +1,6 @@
@import "./base/_theme.css";
@import "./base/_reset.css";
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1,6 @@
/* CSS 리셋 및 기본 스타일 */
@layer base {
body {
background-color: #000;
}
}

View File

@@ -1,4 +1,4 @@
/* 라이트 테마 기본 색상 */
/* CSS 변수 정의 - @layer 밖에 위치 */
:root {
--foreground: #ffffff;
--foreground-10: #ffffff;
@@ -39,11 +39,13 @@
--border: #475569;
}
/* Gradient 텍스트 스타일 */
.text-gradient-pink {
background: linear-gradient(270deg, #e872ff 0%, #ff357e 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
color: transparent;
/* 커스텀 컴포넌트 스타일 */
@layer components {
.text-gradient-pink {
background: linear-gradient(270deg, #e872ff 0%, #ff357e 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
color: transparent;
}
}

View File

@@ -18,11 +18,13 @@ const gnbList = gameData?.gnb?.menus as GameDataGnb["menus"];
>
<!-- 로고 -->
<div data-name="header-logo" class="mr-[40px]">
<img
:src="gameData?.gnb?.bi_path"
:alt="gameData?.game_name"
class="h-[30px]"
/>
<AtomsLocaleLink to="/brand">
<img
:src="gameData?.gnb?.bi_path"
:alt="gameData?.game_name"
class="h-[30px]"
/>
</AtomsLocaleLink>
</div>
<!-- 메인 네비게이션 -->
@@ -47,7 +49,7 @@ const gnbList = gameData?.gnb?.menus as GameDataGnb["menus"];
</MoleculesLink>
<div
v-if="gnbItem.children"
class="absolute top-full left-[-28px] min-w-[190px] pt-[4px]"
class="absolute top-full left-[-28px] min-w-[190px] pt-[4px] pointer-events-none group-hover:pointer-events-auto"
>
<ul
class="bg-theme-foreground-10 rounded-[20px] shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50 p-3"
@@ -83,56 +85,9 @@ const gnbList = gameData?.gnb?.menus as GameDataGnb["menus"];
</nav>
<!-- 오른쪽 영역 -->
<div data-name="header-right" class="relative group ml-auto">
<button
class="bg-amber-600 hover:bg-amber-700 text-white px-6 py-3 rounded-lg font-medium transition-colors"
>
게임 시작
</button>
<!-- 2단계 드롭다운 메뉴 -->
<div
class="absolute top-full right-0 mt-2 w-64 bg-gray-800 rounded-lg shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50 p-3"
>
<div class="px-4 py-2">
<a
href="#"
class="flex items-center space-x-3 hover:bg-gray-700 transition-colors"
>
<div
class="w-8 h-8 bg-orange-500 rounded flex items-center justify-center"
>
<span class="text-white font-bold text-sm">S</span>
</div>
<span>PC 버전 다운로드</span>
</a>
</div>
<div class="px-4 py-2">
<a
href="#"
class="flex items-center space-x-3 hover:bg-gray-700 transition-colors"
>
<svg class="w-8 h-8" viewBox="0 0 24 24" fill="currentColor">
<path
d="M3,20.5V3.5C3,2.91 3.34,2.39 3.84,2.15L13.69,12L3.84,21.85C3.34,21.6 3,21.09 3,20.5M16.81,15.12L6.05,21.34L14.54,12.85L16.81,15.12M20.16,10.81C20.5,11.08 20.75,11.5 20.75,12C20.75,12.5 20.53,12.9 20.18,13.18L17.89,14.5L15.39,12L17.89,9.5L20.16,10.81M6.05,2.66L16.81,8.88L14.54,11.15L6.05,2.66Z"
/>
</svg>
<span>Google Store</span>
</a>
</div>
<div class="px-4 py-2">
<a
href="#"
class="flex items-center space-x-3 hover:bg-gray-700 transition-colors"
>
<svg class="w-8 h-8" viewBox="0 0 24 24" fill="currentColor">
<path
d="M18.71,19.5C17.88,20.74 17,21.95 15.66,21.97C14.32,22 13.89,21.18 12.37,21.18C10.84,21.18 10.37,21.95 9.1,22C7.79,22.05 6.8,20.68 5.96,19.47C4.25,17 2.94,12.45 4.7,9.39C5.57,7.87 7.13,6.91 8.82,6.88C10.1,6.86 11.32,7.75 12.11,7.75C12.89,7.75 14.37,6.68 15.92,6.84C16.57,6.87 18.39,7.1 19.56,8.82C19.47,8.88 17.39,10.1 17.41,12.63C17.44,15.65 20.06,16.66 20.09,16.67C20.06,16.74 19.67,18.11 18.71,19.5M13,3.5C13.73,2.67 14.94,2.04 15.94,2C16.07,3.17 15.6,4.35 14.9,5.19C14.21,6.04 13.07,6.7 11.95,6.61C11.8,5.46 12.36,4.26 13,3.5Z"
/>
</svg>
<span>App Store</span>
</a>
</div>
<div data-name="header-right" class="ml-auto">
<div class="relative group">
<AtomsButton size="small">게임 시작</AtomsButton>
</div>
</div>
</div>

View File

@@ -15,15 +15,10 @@ const isShowTemplate = (template: PageDataTemplate) => {
v-for="(template, index) in props.templates"
:key="template.template_code ?? index"
>
<section v-if="isShowTemplate(template)" class="relative">
<TemplatesBackground
v-if="template.components?.background"
:component-data="template.components?.background"
/>
<component
:is="registry[template.template_code]?.component"
:components="template.components"
/>
</section>
<component
v-if="isShowTemplate(template)"
:is="registry[template.template_code]?.component"
:components="template.components"
/>
</template>
</template>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import {
getAssetPathArray,
getResourcesData,
getResponsiveSrc,
getResponsiveClass,
} from "#layers/utils/dataUtil";
@@ -8,20 +8,24 @@ import type { PageDataComponent } from "#layers/types/api/pageData";
const props = defineProps<{ componentData: PageDataComponent }>();
const assetType = computed(() => {
return props.componentData?.component_type;
const resourcesData = computed(() => {
return getResourcesData({
resources: props.componentData?.resources,
});
});
const resources = computed(() => {
return 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 imgPathArray = getAssetPathArray({
resources: resources.value,
test: "test2",
});
const imageSrc = getResponsiveSrc({
pathArray: imgPathArray,
type: "bg",
const hasImage = computed(() => {
return (
resourcesData.value?.group_type === "image" && resourcesData.value?.res_path
);
});
</script>
@@ -29,10 +33,10 @@ const imageSrc = getResponsiveSrc({
<div class="absolute inset-0 w-full h-full">
<!-- 이미지 타입-->
<div
v-if="assetType === 'IMG' && imageSrc"
v-if="hasImage"
class="w-full h-full bg-cover bg-center bg-no-repeat"
:class="getResponsiveClass()"
:style="imageSrc"
:style="bgStyles"
></div>
<!-- 비디오 타입 -->

View File

@@ -1,47 +1,42 @@
<script setup lang="ts">
import {
getDisplayText,
getAssetPathArray,
getResponsiveSrc,
} from "#layers/utils/dataUtil";
import { getResourcesData, getResponsiveSrc } from "#layers/utils/dataUtil";
import type { PageDataComponent } from "#layers/types/api/pageData";
const props = defineProps<{ componentData: PageDataComponent }>();
const resources = computed(() => {
return props.componentData?.resources;
const resourcesData = computed(() => {
return getResourcesData({
resources: props.componentData?.resources,
});
});
const displayText = getDisplayText({
resources: resources.value,
});
const imgPathArray = getAssetPathArray({
resources: resources.value,
});
const displayText = resourcesData.value?.display?.text;
const imageSrc = getResponsiveSrc({
pathArray: imgPathArray,
type: "img",
pathArray: resourcesData.value?.res_path,
type: "image",
});
</script>
<template>
<div v-if="imgPathArray">
<div v-if="resourcesData">
<img
:src="imageSrc?.mobileSrc"
v-if="imageSrc && 'mobileSrc' in imageSrc"
:src="imageSrc.mobileSrc"
:alt="displayText"
class="w-full sm:hidden"
/>
<!-- PC 이미지 (sm 이상) -->
<img
:src="imageSrc?.pcSrc"
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/>')"
v-html="displayText?.replace(/\n/g, '<br/>') || ''"
class="text-center"
></p>
</template>

View File

@@ -1,48 +1,43 @@
<script setup lang="ts">
import {
getDisplayText,
getAssetPathArray,
getResponsiveSrc,
} from "#layers/utils/dataUtil";
import { getResourcesData, getResponsiveSrc } from "#layers/utils/dataUtil";
import type { PageDataComponent } from "#layers/types/api/pageData";
const props = defineProps<{ componentData: PageDataComponent }>();
const resources = computed(() => {
return props.componentData?.resources;
const resourcesData = computed(() => {
return getResourcesData({
resources: props.componentData?.resources,
});
});
const displayText = getDisplayText({
resources: resources.value,
});
const imgPathArray = getAssetPathArray({
resources: resources.value,
});
const displayText = resourcesData.value?.display?.text;
const imageSrc = getResponsiveSrc({
pathArray: imgPathArray,
type: "img",
pathArray: resourcesData.value?.res_path,
type: "image",
});
</script>
<template>
<h2>
<template v-if="imgPathArray">
<template v-if="resourcesData">
<img
:src="imageSrc?.mobileSrc"
v-if="imageSrc && 'mobileSrc' in imageSrc"
:src="imageSrc.mobileSrc"
:alt="displayText"
class="w-full sm:hidden"
/>
<!-- PC 이미지 (sm 이상) -->
<img
:src="imageSrc?.pcSrc"
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/>')"
v-html="displayText?.replace(/\n/g, '<br/>')"
class="text-center"
>
</span>

View File

@@ -1,48 +1,43 @@
<script setup lang="ts">
import {
getDisplayText,
getAssetPathArray,
getResponsiveSrc,
} from "#layers/utils/dataUtil";
import { getResourcesData, getResponsiveSrc } from "#layers/utils/dataUtil";
import type { PageDataComponent } from "#layers/types/api/pageData";
const props = defineProps<{ componentData: PageDataComponent }>();
const resources = computed(() => {
return props.componentData?.resources;
const resourcesData = computed(() => {
return getResourcesData({
resources: props.componentData?.resources,
});
});
const displayText = getDisplayText({
resources: resources.value,
});
const imgPathArray = getAssetPathArray({
resources: resources.value,
});
const displayText = resourcesData.value?.display?.text;
const imageSrc = getResponsiveSrc({
pathArray: imgPathArray,
type: "img",
pathArray: resourcesData.value?.res_path,
type: "image",
});
</script>
<template>
<h3>
<template v-if="imgPathArray">
<template v-if="resourcesData">
<img
:src="imageSrc?.mobileSrc"
v-if="imageSrc && 'mobileSrc' in imageSrc"
:src="imageSrc.mobileSrc"
:alt="displayText"
class="w-full sm:hidden"
/>
<!-- PC 이미지 (sm 이상) -->
<img
:src="imageSrc?.pcSrc"
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/>')"
v-html="displayText?.replace(/\n/g, '<br/>')"
class="text-center"
>
</span>

View File

@@ -1,28 +1,35 @@
<script setup lang="ts">
import { getAssetPathArray, getResponsiveSrc } from "#layers/utils/dataUtil";
import {
getResourcesData,
getResponsiveSrc,
getResponsiveClass,
} from "#layers/utils/dataUtil";
import type { PageDataComponent } from "#layers/types/api/pageData";
const props = defineProps<{ componentData: PageDataComponent }>();
const resources = computed(() => {
return props.componentData?.resources;
const resourcesData = computed(() => {
return getResourcesData({
resources: props.componentData?.resources,
});
});
const imgPathArray = getAssetPathArray({
resources: resources.value,
test: "test",
});
const imageSrc = getResponsiveSrc({
pathArray: imgPathArray,
type: "bg",
const bgStyles = computed(() => {
const result = getResponsiveSrc({
pathArray: resourcesData.value?.res_path,
type: "bg",
});
return result && typeof result === "object" && "--pc-bg" in result
? result
: {};
});
</script>
<template>
<button
v-if="resourcesData"
class="bg-cover bg-center bg-no-repeat w-[66px] h-[66px] lg:w-[100px] lg:h-[100px]"
:class="getResponsiveClass()"
:style="imageSrc"
:style="bgStyles"
>
<span class="sr-only">videoPlay</span>
</button>

View File

@@ -28,7 +28,7 @@ export const useGetGameAlias = () => {
}
}
} catch (error) {
console.log("useGetGameAlias server error: ", error);
console.error("useGetGameAlias server error: ", error);
}
}
@@ -46,7 +46,7 @@ export const useGetGameAlias = () => {
}
}
} catch (error) {
console.log("useGetGameAlias client error: ", error);
console.error("useGetGameAlias client error: ", error);
}
}
return "";

View File

@@ -6,7 +6,6 @@ interface Props {
}
const props = defineProps<Props>();
console.log("components:", props.components);
// YouTube 모달 상태 관리
const isYouTubeModalOpen = ref(false);
@@ -28,24 +27,30 @@ const handleCloseModal = () => {
</script>
<template>
<div
class="relative flex flex-col items-center justify-center h-[640px] lg:h-[1000px] gap-4"
>
<TemplatesMainTitle
v-if="props.components.mainTitle"
:component-data="props.components.mainTitle"
class="w-[355px] lg:w-[944px]"
<section class="relative h-[640px] lg:h-[1000px]">
<TemplatesBackground
v-if="props.components?.background"
:component-data="props.components?.background"
/>
<TemplatesDescription
v-if="props.components.description"
:component-data="props.components.description"
/>
<TemplatesVideoPlay
v-if="props.components.videoPlay"
:component-data="props.components.videoPlay"
@click="handleVideoPlayClick"
/>
</div>
<div
class="relative h-full flex flex-col items-center justify-center gap-4"
>
<TemplatesMainTitle
v-if="props.components.mainTitle"
:component-data="props.components.mainTitle"
class="w-[355px] lg:w-[944px]"
/>
<TemplatesDescription
v-if="props.components.description"
:component-data="props.components.description"
/>
<TemplatesVideoPlay
v-if="props.components.videoPlay"
:component-data="props.components.videoPlay"
@click="handleVideoPlayClick"
/>
</div>
</section>
<!-- YouTube 모달 -->
<YouTubeModal

View File

@@ -7,7 +7,5 @@ const props = defineProps<Props>();
</script>
<template>
<div
class="relative flex flex-col items-center justify-center h-[640px] lg:h-[1000px] gap-4"
></div>
<section class="template-section"></section>
</template>

View File

@@ -4,10 +4,34 @@ interface Props {
}
const props = defineProps<Props>();
console.log("components:", props.components);
</script>
<template>
<div
class="relative flex flex-col items-center justify-center h-[640px] lg:h-[1000px] gap-4"
></div>
<section class="relative h-[640px] lg:h-[1000px]">
<TemplatesBackground
v-if="props.components?.background"
:component-data="props.components?.background"
/>
<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"
class="text-[24px] font-[500] text-[#ffffff] leading-[34px]"
/>
<TemplatesMainTitle
v-if="props.components.mainTitle"
:component-data="props.components.mainTitle"
class="text-[50px] font-[700] text-[#c7a28b] leading-[70px]"
/>
<TemplatesDescription
v-if="props.components.cardDescription"
:component-data="props.components.cardDescription"
class="text-[20px] font-[500] text-white/70 leading-[30px]"
/>
</div>
</section>
</template>

View File

@@ -1,4 +1,4 @@
export type ImageType = "img" | "bg";
export type ImageType = "image" | "bg";
export interface ResponsiveImagePath {
path_mo?: string;

View File

@@ -18,49 +18,16 @@ export const getResolvedSrc = (path: string): string => {
};
// [TODO] data 타입 정의
export const getDisplayText = ({
export const getResourcesData = ({
resources,
groupsDepth = 0,
}: {
resources: any;
groupsDepth?: number;
}): string => {
// const group = resources[0]?.groups?.[groupsDepth];
const group = resources[groupsDepth];
return group?.resource_data?.display?.ko?.text ?? "";
};
}) => {
const group = resources[0]?.groups?.[groupsDepth];
// [TODO] data 타입 정의
export const getAssetPathArray = ({
resources,
groupsDepth = 0,
test,
}: {
resources: any;
groupsDepth?: number;
test?: string;
}): ResponsiveImagePath | null => {
// const group = resources[0]?.groups?.[groupsDepth].resource_data;
// const imgPath = group?.resource_data?.img_path;
const group = resources[groupsDepth];
if (test) {
if (test === "test") {
const tests = {
path_mo: group?.resource_data?.img_path.comm,
};
return tests ?? null;
}
if (test === "test2") {
return group?.resource_data?.img_path.comm ?? null;
}
}
const imgPath = group?.resource_data?.img_path?.ko;
return imgPath ?? null;
return group ?? null;
};
// 반응형 클래스 리턴하는 함수
@@ -71,12 +38,10 @@ export const getResponsiveClass = () => {
// 반응형 이미지 리턴하는 함수
export const getResponsiveSrc = ({
pathArray,
type = "img",
type = "image",
}: {
pathArray: any;
type?: ImageType;
test?: string;
}): ResponsiveImageResult | ResponsiveImagePath => {
if (!pathArray?.path_mo) {
return null;
@@ -87,17 +52,15 @@ export const getResponsiveSrc = ({
mobile: getResolvedSrc(pathArray.path_mo),
};
// 타입별 반환 객체 생성
const resultMap: Record<ImageType, ResponsiveImageResult> = {
img: {
if (type === "image") {
return {
mobileSrc: resolvedImages.mobile,
pcSrc: resolvedImages.pc,
},
bg: {
"--pc-bg": `url(${resolvedImages.pc})`,
"--mobile-bg": `url(${resolvedImages.mobile})`,
},
};
};
}
return resultMap[type];
return {
"--pc-bg": `url(${resolvedImages.pc})`,
"--mobile-bg": `url(${resolvedImages.mobile})`,
};
};

View File

@@ -93,6 +93,12 @@ export default defineNuxtConfig({
stove81Plug: process.env.STOVE_81PLUG,
},
},
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
},
vite: {
server: {
// 개발 환경에서는 모든 호스트 허용