fix. 임시 데이터 맞춰 수정
This commit is contained in:
17
app/app.vue
17
app/app.vue
@@ -8,6 +8,10 @@
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@import "#layers/assets/css/theme.css";
|
||||
</style>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useNuxtApp } from "nuxt/app";
|
||||
import { useGameDataStore } from "#layers/stores/useGameDataStore";
|
||||
@@ -32,9 +36,10 @@ if (gameDataFromServer) {
|
||||
setGameData(gameDataFromServer);
|
||||
}
|
||||
|
||||
const meta = gameDataFromServer?.meta_tag || null;
|
||||
const meta = gameDataFromServer?.meta_tag ?? null;
|
||||
const theme = gameDataFromServer?.design_theme === 1 ? "dark" : "light";
|
||||
|
||||
if (meta) {
|
||||
if (gameDataFromServer && meta) {
|
||||
metaData.value = meta;
|
||||
useSeoMeta({
|
||||
title: meta.page_title,
|
||||
@@ -48,9 +53,11 @@ if (meta) {
|
||||
twitterDescription: meta.x_desc,
|
||||
});
|
||||
useHead({
|
||||
// htmlAttrs: {
|
||||
// lang: locale.value
|
||||
// },
|
||||
htmlAttrs: {
|
||||
"data-game": gameDataFromServer.game_name || "",
|
||||
"data-theme": theme || "",
|
||||
lang: gameDataFromServer.default_lang_code,
|
||||
},
|
||||
//meta: [...(updatedMetaTags.value || [])],
|
||||
//link: [...(links.value || [])]
|
||||
});
|
||||
|
||||
49
layers/assets/css/theme.css
Normal file
49
layers/assets/css/theme.css
Normal file
@@ -0,0 +1,49 @@
|
||||
/* 라이트 테마 기본 색상 */
|
||||
:root {
|
||||
--foreground: #ffffff;
|
||||
--foreground-10: #ffffff;
|
||||
|
||||
--foreground-reversal: #1f1f1f;
|
||||
--foreground-reversal-10: rgba(0, 0, 0, 0.1);
|
||||
--foreground-reversal-30: #ebebeb; /* gray-80 */
|
||||
--foreground-reversal-40: rgba(0, 0, 0, 0.4);
|
||||
--foreground-reversal-70: #666666; /* gray-700 */
|
||||
|
||||
--primary: #3b82f6;
|
||||
--secondary: #64748b;
|
||||
|
||||
--surface: #f8fafc;
|
||||
|
||||
--textSecondary: #475569;
|
||||
--accent: #f59e0b;
|
||||
--border: #e2e8f0;
|
||||
}
|
||||
|
||||
/* 다크 테마 색상 */
|
||||
[data-theme="dark"] {
|
||||
--foreground: #191919;
|
||||
--foreground-10: #292929;
|
||||
|
||||
--foreground-reversal: #ebebeb;
|
||||
--foreground-reversal-10: rgba(255, 255, 255, 0.1);
|
||||
--foreground-reversal-30: #404040; /* gray-750 */
|
||||
--foreground-reversal-40: rgba(255, 255, 255, 0.4);
|
||||
--foreground-reversal-70: #b2b2b2; /* gray-300 */
|
||||
|
||||
--primary: #60a5fa;
|
||||
--secondary: #94a3b8;
|
||||
--surface: #1e293b;
|
||||
|
||||
--textSecondary: #cbd5e1;
|
||||
--accent: #fbbf24;
|
||||
--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;
|
||||
}
|
||||
15
layers/components/atoms/LocaleLink.vue
Normal file
15
layers/components/atoms/LocaleLink.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
to: {
|
||||
type: String,
|
||||
default: null,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const localePath = useLocalePath();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLink :to="localePath(props.to)"><slot></slot></NuxtLink>
|
||||
</template>
|
||||
28
layers/components/atoms/icons/ArrowDown.vue
Normal file
28
layers/components/atoms/icons/ArrowDown.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
size?: number | string;
|
||||
color?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
size: 12,
|
||||
color: "#7F7F7F",
|
||||
className: "",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 12 12"
|
||||
:fill="color"
|
||||
>
|
||||
<path
|
||||
d="M5.29499 7.715L2.39999 4.875C2.07499 4.555 2.29999 4 2.75999 4L9.23499 4C9.69499 4 9.91999 4.555 9.59499 4.875L6.69999 7.715C6.30999 8.095 5.68999 8.095 5.29999 7.715H5.29499Z"
|
||||
fill="#7F7F7F"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
32
layers/components/atoms/icons/LinkOut.vue
Normal file
32
layers/components/atoms/icons/LinkOut.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
size?: number | string;
|
||||
color?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
size: 16,
|
||||
color: "#B2B2B2",
|
||||
className: "",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 16 16"
|
||||
:fill="color"
|
||||
>
|
||||
<path
|
||||
d="M3.63636 3.33333C3.469 3.33333 3.33333 3.469 3.33333 3.63636L3.33333 12.3636C3.33333 12.531 3.469 12.6667 3.63636 12.6667H12.3636C12.531 12.6667 12.6667 12.531 12.6667 12.3636V9.93939C12.6667 9.5712 12.9651 9.27273 13.3333 9.27273C13.7015 9.27273 14 9.5712 14 9.93939V12.3636C14 13.2674 13.2674 14 12.3636 14H3.63636C2.73262 14 2 13.2674 2 12.3636L2 3.63636C2 2.73263 2.73262 2 3.63636 2L6.06061 2C6.4288 2 6.72727 2.29848 6.72727 2.66667C6.72727 3.03486 6.4288 3.33333 6.06061 3.33333H3.63636Z"
|
||||
fill="#B2B2B2"
|
||||
/>
|
||||
<path
|
||||
d="M12.6667 4.27614V6.54545C12.6667 6.91364 12.9651 7.21212 13.3333 7.21212C13.7015 7.21212 14 6.91364 14 6.54545V2.66667C14 2.29848 13.7015 2 13.3333 2L9.45455 2C9.08636 2 8.78788 2.29848 8.78788 2.66667C8.78788 3.03486 9.08636 3.33333 9.45455 3.33333L11.7239 3.33333L7.28616 7.77103C7.02581 8.03138 7.02581 8.45349 7.28616 8.71384C7.54651 8.97419 7.96862 8.97419 8.22897 8.71384L12.6667 4.27614Z"
|
||||
fill="#B2B2B2"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
29
layers/components/atoms/icons/Star.vue
Normal file
29
layers/components/atoms/icons/Star.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
size?: number | string;
|
||||
color?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
size: 12,
|
||||
color: "#FD3886",
|
||||
className: "",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
:class="className"
|
||||
>
|
||||
<path
|
||||
d="M6.37364 3.24966C6.22658 2.91678 5.77344 2.91678 5.62638 3.24966L4.95508 4.76916L3.36352 4.96618C3.01484 5.00934 2.87482 5.4593 3.1326 5.7082L4.30928 6.84431L3.99693 8.48558C3.9285 8.84514 4.2951 9.12323 4.60148 8.94417L6.00001 8.12684L7.39853 8.94417C7.70491 9.12323 8.07151 8.84514 8.00308 8.48558L7.69074 6.84431L8.8674 5.70819C9.12518 5.4593 8.98515 5.00934 8.63648 4.96618L7.04492 4.76916L6.37364 3.24966Z"
|
||||
:fill="color"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="header-stove"></div>
|
||||
<div id="header-stove" class="relative z-[5]"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import type { GameDataValue, GameDataGnb } from "#layers/types/api/gameData";
|
||||
|
||||
const gameDataStore = useGameDataStore();
|
||||
const gameData = computed(() => gameDataStore.gameData);
|
||||
|
||||
const gameData = gameDataStore.gameData as GameDataValue;
|
||||
const gnbList = gameData?.gnb?.menus as GameDataGnb["menus"];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="bg-black text-white relative z-50">
|
||||
<header
|
||||
class="bg-theme-foreground text-theme-foreground-reversal relative z-50"
|
||||
>
|
||||
<LayoutStoveGnb />
|
||||
<div class="px-[40px] h-16 flex items-center">
|
||||
<div data-name="header-game" class="px-[40px] h-16 flex items-center">
|
||||
<!-- 로고 -->
|
||||
<div class="mr-[40px]">
|
||||
<div data-name="header-logo" class="mr-[40px]">
|
||||
<img
|
||||
:src="gameData?.gnb?.bi_path"
|
||||
:alt="gameData?.game_name"
|
||||
@@ -17,126 +23,64 @@ const gameData = computed(() => gameDataStore.gameData);
|
||||
</div>
|
||||
|
||||
<!-- 메인 네비게이션 -->
|
||||
<nav class="flex items-center space-x-[32px]">
|
||||
<!-- 동적 메뉴 (비활성화) -->
|
||||
<template v-if="false">
|
||||
<nav data-name="header-nav" class="flex items-center space-x-[32px]">
|
||||
<template v-if="gnbList">
|
||||
<div
|
||||
v-for="menuList in gameData?.gnb?.menus"
|
||||
:key="menuList.path_code"
|
||||
v-for="(gnbItem, key) in gnbList"
|
||||
:key="key"
|
||||
class="relative group"
|
||||
>
|
||||
<a
|
||||
v-if="menuList.depth === 1"
|
||||
:href="menuList.url_path"
|
||||
:target="menuList.link_target"
|
||||
class="hover:text-yellow-400 transition-colors"
|
||||
<!-- Link 컴포넌트 사용 -->
|
||||
<MoleculesLink
|
||||
:to="gnbItem.url_path"
|
||||
:target="gnbItem.link_target"
|
||||
class="relative flex items-center h-[64px]"
|
||||
>
|
||||
{{ menuList.menu_name }}
|
||||
</a>
|
||||
{{ gnbItem.menu_name }}
|
||||
<AtomsIconsArrowDown v-if="gnbItem.children" class="ml-1" />
|
||||
<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>
|
||||
<div
|
||||
v-if="gnbItem.children"
|
||||
class="absolute top-full left-[-28px] min-w-[190px] pt-[4px]"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<li v-for="child in gnbItem.children" :key="child.menu_name">
|
||||
<!-- Link 컴포넌트 사용 -->
|
||||
<MoleculesLink
|
||||
: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"
|
||||
>
|
||||
{{ child.menu_name }}
|
||||
<AtomsIconsLinkOut
|
||||
v-if="child.link_target === '_blank'"
|
||||
class="ml-1"
|
||||
/>
|
||||
</MoleculesLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 고객지원 (2단계 드롭다운) -->
|
||||
<div class="relative group">
|
||||
<button
|
||||
class="flex items-center space-x-1 hover:text-yellow-400 transition-colors"
|
||||
>
|
||||
<span>고객지원</span>
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- 2단계 드롭다운 메뉴 -->
|
||||
<div
|
||||
class="absolute top-full left-0 mt-2 w-48 bg-gray-800 rounded-lg shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50"
|
||||
>
|
||||
<div class="py-2">
|
||||
<a
|
||||
href="#"
|
||||
class="block px-4 py-2 hover:bg-gray-700 hover:text-yellow-400 transition-colors"
|
||||
>다운로드</a
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="block px-4 py-2 hover:bg-gray-700 hover:text-yellow-400 transition-colors bg-gray-700 text-yellow-400"
|
||||
>쿠폰 등록</a
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="flex items-center justify-between px-4 py-2 hover:bg-gray-700 hover:text-yellow-400 transition-colors"
|
||||
>
|
||||
<span>고객센터</span>
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="flex items-center justify-between px-4 py-2 hover:bg-gray-700 hover:text-yellow-400 transition-colors"
|
||||
>
|
||||
<span>확률 정보</span>
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="block px-4 py-2 hover:bg-gray-700 hover:text-yellow-400 transition-colors"
|
||||
>보안 강화 캠페인</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 기부 -->
|
||||
<a href="#" class="hover:text-yellow-400 transition-colors">기부</a>
|
||||
|
||||
<!-- 구분선 -->
|
||||
<div class="w-px h-6 bg-gray-600"></div>
|
||||
<div class="w-px h-4 bg-theme-foreground-reversal-30"></div>
|
||||
|
||||
<!-- 이벤트 -->
|
||||
<a
|
||||
href="#"
|
||||
class="flex items-center space-x-1 text-pink-400 hover:text-pink-300 transition-colors"
|
||||
>
|
||||
<span class="text-pink-400">•</span>
|
||||
<a href="#" class="flex items-center space-x-[3px] text-gradient-pink">
|
||||
<AtomsIconsStar />
|
||||
<span>이벤트</span>
|
||||
<span class="text-pink-400">•</span>
|
||||
<AtomsIconsStar />
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<!-- 게임 시작 버튼 (2단계 드롭다운) -->
|
||||
<div class="relative group ml-auto">
|
||||
<!-- 오른쪽 영역 -->
|
||||
<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"
|
||||
>
|
||||
@@ -145,12 +89,12 @@ const gameData = computed(() => gameDataStore.gameData);
|
||||
|
||||
<!-- 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"
|
||||
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="py-2">
|
||||
<div class="px-4 py-2">
|
||||
<a
|
||||
href="#"
|
||||
class="flex items-center space-x-3 px-4 py-3 hover:bg-gray-700 transition-colors"
|
||||
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"
|
||||
@@ -159,9 +103,11 @@ const gameData = computed(() => gameDataStore.gameData);
|
||||
</div>
|
||||
<span>PC 버전 다운로드</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="px-4 py-2">
|
||||
<a
|
||||
href="#"
|
||||
class="flex items-center space-x-3 px-4 py-3 hover:bg-gray-700 transition-colors"
|
||||
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
|
||||
@@ -170,9 +116,11 @@ const gameData = computed(() => gameDataStore.gameData);
|
||||
</svg>
|
||||
<span>Google Store</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="px-4 py-2">
|
||||
<a
|
||||
href="#"
|
||||
class="flex items-center space-x-3 px-4 py-3 hover:bg-gray-700 transition-colors"
|
||||
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
|
||||
|
||||
27
layers/components/molecules/Link.vue
Normal file
27
layers/components/molecules/Link.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
to: string;
|
||||
target?: string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
target: "",
|
||||
class: "",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a
|
||||
v-if="props.target === '_blank'"
|
||||
v-bind="$attrs"
|
||||
:href="props.to"
|
||||
:target="props.target"
|
||||
:class="props.class"
|
||||
>
|
||||
<slot />
|
||||
</a>
|
||||
<AtomsLocaleLink v-else v-bind="$attrs" :to="props.to" :class="props.class">
|
||||
<slot />
|
||||
</AtomsLocaleLink>
|
||||
</template>
|
||||
@@ -1,8 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
getResponsiveResolvedSrc,
|
||||
getResponsiveClass,
|
||||
} from "#layers/utils/dataUtil";
|
||||
import { getResponsiveSrc, getResponsiveClass } from "#layers/utils/dataUtil";
|
||||
import type { PageDataComponent } from "#layers/types/api/pageData";
|
||||
|
||||
const props = defineProps<{ componentData: PageDataComponent }>();
|
||||
@@ -10,10 +7,15 @@ const props = defineProps<{ componentData: PageDataComponent }>();
|
||||
const assetType = computed(() => {
|
||||
return props.componentData?.component_type;
|
||||
});
|
||||
const assetPath = computed(() => {
|
||||
// [TODO] 이미지 그룹 여러개 처리
|
||||
// [TODO] comm 처리
|
||||
return props.componentData?.resources[0]["groups"][0]?.img_path?.comm;
|
||||
const resources = computed(() => {
|
||||
return props.componentData?.resources;
|
||||
});
|
||||
|
||||
const imageSrc = getResponsiveSrc({
|
||||
resources: resources.value,
|
||||
type: "bg",
|
||||
|
||||
test: "test2",
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -21,17 +23,17 @@ const assetPath = computed(() => {
|
||||
<div class="absolute inset-0 w-full h-full">
|
||||
<!-- 이미지 타입-->
|
||||
<div
|
||||
v-if="assetType === 'IMG'"
|
||||
class="absolute inset-0 w-full h-full bg-cover bg-center bg-no-repeat"
|
||||
v-if="assetType === 'IMG' && imageSrc"
|
||||
class="w-full h-full bg-cover bg-center bg-no-repeat"
|
||||
:class="getResponsiveClass()"
|
||||
:style="getResponsiveResolvedSrc(assetPath, 'bg')"
|
||||
:style="imageSrc"
|
||||
></div>
|
||||
|
||||
<!-- 비디오 타입 -->
|
||||
<video
|
||||
<!-- <video
|
||||
v-else-if="assetType === 'VIDEO'"
|
||||
class="absolute inset-0 w-full h-full object-cover"
|
||||
:poster="assetPoster"
|
||||
:poster="getResponsiveSrc({ resources: resources, type: 'img' })"
|
||||
autoplay
|
||||
muted
|
||||
loop
|
||||
@@ -39,7 +41,7 @@ const assetPath = computed(() => {
|
||||
>
|
||||
<source :src="assetPath" type="video/mp4" />
|
||||
<source :src="assetPath" type="video/webm" />
|
||||
</video>
|
||||
</video> -->
|
||||
<div
|
||||
class="absolute inset-0"
|
||||
style="
|
||||
|
||||
@@ -1,38 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { getResponsiveResolvedSrc } from "#layers/utils/dataUtil";
|
||||
import { getResponsiveSrc, getDisplayText } from "#layers/utils/dataUtil";
|
||||
import type { PageDataComponent } from "#layers/types/api/pageData";
|
||||
|
||||
const props = defineProps<{ componentData: PageDataComponent }>();
|
||||
|
||||
const displayText = computed(() => {
|
||||
return (
|
||||
props.componentData?.resources[0]?.["groups"]?.[0]?.display?.ko?.text || ""
|
||||
);
|
||||
const resources = computed(() => {
|
||||
return props.componentData?.resources;
|
||||
});
|
||||
const assetPath = computed(() => {
|
||||
// [TODO] 이미지 그룹 여러개 처리
|
||||
// [TODO] 언어 처리
|
||||
return props.componentData?.resources[0]?.["groups"]?.[0]?.img_path?.ko;
|
||||
|
||||
const displayText = getDisplayText({
|
||||
resources: resources.value,
|
||||
});
|
||||
const imageSrcs = computed(() => {
|
||||
if (!assetPath.value?.path_mo || !assetPath.value?.path_pc) {
|
||||
return { mobileSrc: "", pcSrc: "" };
|
||||
}
|
||||
return getResponsiveResolvedSrc(assetPath.value);
|
||||
const imageSrc = getResponsiveSrc({
|
||||
resources: resources.value,
|
||||
type: "img",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<img
|
||||
:src="imageSrcs.mobileSrc"
|
||||
:src="imageSrc?.mobileSrc"
|
||||
:alt="displayText"
|
||||
class="w-full sm:hidden"
|
||||
/>
|
||||
|
||||
<!-- PC 이미지 (sm 이상) -->
|
||||
<img
|
||||
:src="imageSrcs.pcSrc"
|
||||
:src="imageSrc?.pcSrc"
|
||||
:alt="displayText"
|
||||
class="w-full hidden sm:block"
|
||||
/>
|
||||
|
||||
@@ -1,38 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { getResponsiveResolvedSrc } from "#layers/utils/dataUtil";
|
||||
import { getResponsiveSrc, getDisplayText } from "#layers/utils/dataUtil";
|
||||
import type { PageDataComponent } from "#layers/types/api/pageData";
|
||||
|
||||
const props = defineProps<{ componentData: PageDataComponent }>();
|
||||
|
||||
const displayText = computed(() => {
|
||||
return (
|
||||
props.componentData?.resources[0]?.["groups"]?.[0]?.display?.ko?.text || ""
|
||||
);
|
||||
const resources = computed(() => {
|
||||
return props.componentData?.resources;
|
||||
});
|
||||
const assetPath = computed(() => {
|
||||
// [TODO] 이미지 그룹 여러개 처리
|
||||
// [TODO] 언어 처리
|
||||
return props.componentData?.resources[0]?.["groups"]?.[0]?.img_path?.ko;
|
||||
|
||||
const displayText = getDisplayText({
|
||||
resources: resources.value,
|
||||
});
|
||||
const imageSrcs = computed(() => {
|
||||
if (!assetPath.value?.path_mo || !assetPath.value?.path_pc) {
|
||||
return { mobileSrc: "", pcSrc: "" };
|
||||
}
|
||||
return getResponsiveResolvedSrc(assetPath.value);
|
||||
const imageSrc = getResponsiveSrc({
|
||||
resources: resources.value,
|
||||
type: "img",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>
|
||||
<img
|
||||
:src="imageSrcs.mobileSrc"
|
||||
:src="imageSrc?.mobileSrc"
|
||||
:alt="displayText"
|
||||
class="w-full sm:hidden"
|
||||
/>
|
||||
|
||||
<!-- PC 이미지 (sm 이상) -->
|
||||
<img
|
||||
:src="imageSrcs.pcSrc"
|
||||
:src="imageSrc?.pcSrc"
|
||||
:alt="displayText"
|
||||
class="w-full hidden sm:block"
|
||||
/>
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
getResponsiveResolvedSrc,
|
||||
getResponsiveClass,
|
||||
} from "#layers/utils/dataUtil";
|
||||
import { getResponsiveSrc, getResponsiveClass } from "#layers/utils/dataUtil";
|
||||
import type { PageDataComponent } from "#layers/types/api/pageData";
|
||||
|
||||
const props = defineProps<{ componentData: PageDataComponent }>();
|
||||
|
||||
const assetPath = computed(() => {
|
||||
// [TODO] 이미지 그룹 여러개 처리
|
||||
// [TODO] comm 처리
|
||||
return props.componentData?.resources[0]["groups"][0]?.img_path?.comm;
|
||||
const resources = computed(() => {
|
||||
return props.componentData?.resources;
|
||||
});
|
||||
|
||||
console.log("assetPath:", props.componentData);
|
||||
const imageSrc = getResponsiveSrc({
|
||||
resources: resources.value,
|
||||
type: "bg",
|
||||
|
||||
test: "test",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="bg-cover bg-center bg-no-repeat w-[66px] h-[66px] lg:w-[100px] lg:h-[100px]"
|
||||
:class="getResponsiveClass()"
|
||||
:style="getResponsiveResolvedSrc(assetPath, 'bg')"
|
||||
:style="imageSrc"
|
||||
>
|
||||
<span class="sr-only">videoPlay</span>
|
||||
</button>
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { commonFetch } from "#layers/utils/apiUtil";
|
||||
import { getHeader } from "h3";
|
||||
import type {
|
||||
GameDataResponse,
|
||||
GameDataValue,
|
||||
} from "#layers/types/api/gameData";
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig();
|
||||
const baseDomain = (config.public.baseDomain || ".onstove.com") as string;
|
||||
const stoveApiBaseUrl = config.public.stoveApiUrl;
|
||||
const apiUrl = `${stoveApiBaseUrl}/pub-comm/v1.0/template/game`;
|
||||
let gameAlias = "";
|
||||
|
||||
try {
|
||||
// 미들웨어에서 설정한 gameAlias가 있다면 우선 사용
|
||||
if (event.context.gameAlias) {
|
||||
gameAlias = event.context.gameAlias;
|
||||
} else {
|
||||
const host = getHeader(event, "host") || "";
|
||||
const isGameAliasExtractable = host.includes(baseDomain);
|
||||
|
||||
if (isGameAliasExtractable) {
|
||||
const subdomain = host.split(".")[0];
|
||||
if (subdomain && subdomain !== "www") {
|
||||
gameAlias = subdomain;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("gameAlias extraction error: ", error);
|
||||
}
|
||||
|
||||
try {
|
||||
const queryParams: Record<string, string> = {
|
||||
game_alias: gameAlias,
|
||||
lang_code: "ko",
|
||||
};
|
||||
|
||||
const response = (await commonFetch("GET", apiUrl, {
|
||||
query: queryParams,
|
||||
})) as GameDataResponse | null;
|
||||
|
||||
if (response?.code === 0 && "value" in response) {
|
||||
event.context.gameData = response.value;
|
||||
return response.value as GameDataValue;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {};
|
||||
}
|
||||
});
|
||||
@@ -18,31 +18,30 @@ export interface GameDataValue {
|
||||
game_id: string;
|
||||
game_code: number;
|
||||
s3_folder_name: string;
|
||||
default_lang_code: string;
|
||||
game_name: string;
|
||||
ga_code: string;
|
||||
favicon_path: string;
|
||||
design_theme: number;
|
||||
key_color_codes: string[];
|
||||
lang_codes: string[];
|
||||
lang_codes: string; // JSON 문자열로 변경
|
||||
key_color_codes: string; // JSON 문자열로 변경
|
||||
use_game_font: boolean;
|
||||
footer_dev_ci_img_yn: boolean;
|
||||
game_font: GameDataFont;
|
||||
global: GameDataGlobal;
|
||||
footer_dev_ci_img_path: string;
|
||||
game_font: string; // JSON 문자열로 변경
|
||||
globals: GameDataGlobal[]; // 배열로 변경
|
||||
gnb: GameDataGnb;
|
||||
quick_menus: GameDataQuickMenu[];
|
||||
stove_gnb: GameDataStoveGnb;
|
||||
comm_img: GameDataCommImg;
|
||||
meta_tag: GameDataMetaTag;
|
||||
youtube: GameDataYoutube;
|
||||
sns: GameDataSns;
|
||||
market: GameDataMarket;
|
||||
footer: GameDataFooter;
|
||||
intro: GameDataIntro;
|
||||
inspection: Record<string, any>; // 동적 객체
|
||||
stove_gnb: string; // JSON 문자열로 변경
|
||||
meta_tag: string; // JSON 문자열로 변경
|
||||
sns: string; // JSON 문자열로 변경
|
||||
footer: string; // JSON 문자열로 변경
|
||||
}
|
||||
|
||||
// Global 설정 타입
|
||||
export interface GameDataGlobal {
|
||||
system_font: GameDataFont;
|
||||
lang: GameDataLang;
|
||||
system_font: string; // JSON 문자열로 변경
|
||||
lang: string; // JSON 문자열로 변경
|
||||
}
|
||||
|
||||
// 폰트 타입
|
||||
@@ -61,12 +60,18 @@ export interface GameDataLang {
|
||||
// GNB 설정 타입
|
||||
export interface GameDataGnb {
|
||||
game_gnb_ver: string;
|
||||
display_start_dt: number;
|
||||
display_start_dt: string; // ISO 문자열로 변경
|
||||
theme_type: string;
|
||||
bi_path: string;
|
||||
lang_codes: string[];
|
||||
buttons: any[];
|
||||
menus: GameDataMenu[];
|
||||
lang_codes: string; // JSON 문자열로 변경
|
||||
buttons: GameDataButton[];
|
||||
menus: Record<string, GameDataMenu>; // 동적 객체로 변경
|
||||
}
|
||||
|
||||
// 버튼 타입
|
||||
export interface GameDataButton {
|
||||
depth_type: number;
|
||||
button: string; // JSON 문자열로 변경
|
||||
}
|
||||
|
||||
// 메뉴 타입
|
||||
@@ -78,7 +83,16 @@ export interface GameDataMenu {
|
||||
click_action_type: number;
|
||||
url_path: string;
|
||||
link_target: string;
|
||||
tracking: GameDataTracking;
|
||||
children: Record<string, GameDataMenu>; // 중첩 메뉴를 위한 children 속성 추가
|
||||
tracking: string; // JSON 문자열로 변경
|
||||
}
|
||||
|
||||
// 인트로 타입
|
||||
export interface GameDataIntro {
|
||||
seq: number;
|
||||
display_start_dt: string;
|
||||
display_end_dt: string;
|
||||
page_url: string;
|
||||
}
|
||||
|
||||
// 트래킹 타입
|
||||
@@ -108,6 +122,15 @@ export interface GameDataStoveGnb {
|
||||
stove_install_button_visible: string;
|
||||
}
|
||||
|
||||
// 파비콘 경로 타입
|
||||
export interface GameDataFaviconPath {
|
||||
"16_16": string;
|
||||
"32_32": string;
|
||||
"72_72": string;
|
||||
"180_180": string;
|
||||
"192_192": string;
|
||||
}
|
||||
|
||||
// 공통 이미지 타입
|
||||
export interface GameDataCommImg {
|
||||
groups: GameDataCommImgGroup[];
|
||||
@@ -226,5 +249,69 @@ export interface GameDataApiResult {
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
// JSON 문자열 파싱을 위한 유틸리티 타입들
|
||||
export interface ParsedKeyColorCodes {
|
||||
extra: string;
|
||||
primary: string;
|
||||
secondary: string;
|
||||
"text-primary": string;
|
||||
"text-secondary": string;
|
||||
}
|
||||
|
||||
export interface ParsedGameFont {
|
||||
"font-family": string;
|
||||
}
|
||||
|
||||
export interface ParsedGlobal {
|
||||
system_font: ParsedGameFont;
|
||||
lang: GameDataLang;
|
||||
}
|
||||
|
||||
export interface ParsedButton {
|
||||
google_play: {
|
||||
label: Record<string, string>;
|
||||
url: string;
|
||||
tracking: GameDataTracking;
|
||||
};
|
||||
app_store: {
|
||||
label: Record<string, string>;
|
||||
url: string;
|
||||
tracking: GameDataTracking;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ParsedStoveGnb {
|
||||
skin_type: string;
|
||||
stove_install_button_visible: string;
|
||||
}
|
||||
|
||||
export interface ParsedSns {
|
||||
kakao: GameDataSnsItem;
|
||||
twitter: GameDataSnsItem;
|
||||
discord: GameDataSnsItem;
|
||||
youtube: GameDataSnsItem;
|
||||
instagram: GameDataSnsItem;
|
||||
facebook: GameDataSnsItem;
|
||||
tiktok: GameDataSnsItem;
|
||||
}
|
||||
|
||||
export interface ParsedFooter {
|
||||
use_game_rating: boolean;
|
||||
game_rating_info: GameDataGameRatingInfo;
|
||||
use_dev_ci_url: boolean;
|
||||
dev_ci_url: string;
|
||||
fund_display_yn: boolean;
|
||||
fund_display_url: string;
|
||||
}
|
||||
|
||||
// 파비콘 경로 파싱 타입
|
||||
export interface ParsedFaviconPath {
|
||||
"16_16": string;
|
||||
"32_32": string;
|
||||
"72_72": string;
|
||||
"180_180": string;
|
||||
"192_192": string;
|
||||
}
|
||||
|
||||
// 기존 gameData 타입과의 호환성을 위한 별칭
|
||||
export type gameData = GameDataValue;
|
||||
|
||||
@@ -17,24 +17,78 @@ export const getResolvedSrc = (path: string): string => {
|
||||
return `${rootPath}${path}`;
|
||||
};
|
||||
|
||||
// [TODO] data 타입 정의
|
||||
export const getDisplayText = ({
|
||||
resources,
|
||||
groupsDepth = 0,
|
||||
}: {
|
||||
resources: any;
|
||||
groupsDepth?: number;
|
||||
}): string => {
|
||||
const group = resources[0]?.groups?.[groupsDepth];
|
||||
return group?.display?.text ?? "";
|
||||
};
|
||||
|
||||
// [TODO] data 타입 정의
|
||||
export const getAssetPath = ({
|
||||
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;
|
||||
};
|
||||
|
||||
// 반응형 클래스 리턴하는 함수
|
||||
export const getResponsiveClass = () => {
|
||||
return ["bg-[image:var(--mobile-bg)]", "sm:bg-[image:var(--pc-bg)]"];
|
||||
};
|
||||
|
||||
// 반응형 이미지 리턴하는 함수
|
||||
export const getResponsiveResolvedSrc = (
|
||||
path: ResponsiveImagePath,
|
||||
type: ImageType = "img"
|
||||
): ResponsiveImageResult => {
|
||||
// 필수 경로가 없으면 null 반환
|
||||
if (!path?.path_mo || !path?.path_pc) {
|
||||
export const getResponsiveSrc = ({
|
||||
resources,
|
||||
type = "img",
|
||||
groupsDepth = 0,
|
||||
|
||||
test,
|
||||
}: {
|
||||
resources: any;
|
||||
type?: ImageType;
|
||||
groupsDepth?: number;
|
||||
|
||||
test?: string;
|
||||
}): ResponsiveImageResult | ResponsiveImagePath => {
|
||||
const path = getAssetPath({ resources, groupsDepth, test });
|
||||
|
||||
if (!path?.path_mo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 이미지 경로 해석
|
||||
const resolvedImages = {
|
||||
pc: getResolvedSrc(path.path_pc),
|
||||
pc: getResolvedSrc(path.path_pc || path.path_mo),
|
||||
mobile: getResolvedSrc(path.path_mo),
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,23 @@ export default {
|
||||
lg: "1440px", // Large PC: 1440px+
|
||||
},
|
||||
spacing: {},
|
||||
colors: {
|
||||
"theme-foreground": "var(--foreground)",
|
||||
"theme-foreground-10": "var(--foreground-10)",
|
||||
|
||||
"theme-foreground-reversal": "var(--foreground-reversal)",
|
||||
"theme-foreground-reversal-10": "var(--foreground-reversal-10)",
|
||||
"theme-foreground-reversal-30": "var(--foreground-reversal-30)",
|
||||
"theme-foreground-reversal-40": "var(--foreground-reversal-40)",
|
||||
"theme-foreground-reversal-70": "var(--foreground-reversal-70)",
|
||||
|
||||
// "theme-primary": "var(--light-primary)",
|
||||
// "theme-secondary": "var(--light-secondary)",
|
||||
// "theme-surface": "var(--light-surface)",
|
||||
// "theme-text-secondary": "var(--light-textSecondary)",
|
||||
// "theme-accent": "var(--light-accent)",
|
||||
// "theme-border": "var(--light-border)",
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies Config;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"extends": "./.nuxt/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"types": ["nuxt", "node"],
|
||||
"types": ["nuxt", "node", "@nuxtjs/i18n"],
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"@/*": ["."],
|
||||
|
||||
Reference in New Issue
Block a user