Merge branch 'feature/202501107-all' into feature/20251001-gil
This commit is contained in:
31
app/app.vue
31
app/app.vue
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useNuxtApp } from 'nuxt/app'
|
||||
import { getResolvedHost } from '#layers/utils/styleUtil'
|
||||
import { getImageHost } from '#layers/utils/styleUtil'
|
||||
import type {
|
||||
GameDataFavicon,
|
||||
GameDataMetaTag,
|
||||
@@ -15,7 +15,7 @@ const scrollStore = useScrollStore()
|
||||
|
||||
const { setGameData } = gameDataStore
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
const { youtube, confirm, alert, handleResetYoutube } = modalStore
|
||||
const { youtube, confirm, alert, toast, handleResetYoutube } = modalStore
|
||||
|
||||
const metaData = ref<GameDataMetaTag | null>(null)
|
||||
|
||||
@@ -37,31 +37,16 @@ const setupAllMetaData = (data: GameDataValue) => {
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/x-icon',
|
||||
sizes: '16x16',
|
||||
href: faviconPath['16_16'],
|
||||
},
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/x-icon',
|
||||
sizes: '32x32',
|
||||
href: faviconPath['32_32'],
|
||||
},
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
sizes: '72x72',
|
||||
href: faviconPath['72_72'],
|
||||
href: getImageHost(faviconPath[0]),
|
||||
},
|
||||
{
|
||||
rel: 'apple-touch-icon',
|
||||
sizes: '180x180',
|
||||
href: faviconPath['180_180'],
|
||||
href: getImageHost(faviconPath[1]),
|
||||
},
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
sizes: '192x192',
|
||||
href: faviconPath['192_192'],
|
||||
href: getImageHost(faviconPath[2]),
|
||||
},
|
||||
]
|
||||
|
||||
@@ -130,7 +115,7 @@ onMounted(() => {
|
||||
groups.forEach(({ img_name, img_path }) => {
|
||||
document.documentElement.style.setProperty(
|
||||
`--${img_name}`,
|
||||
`url(${getResolvedHost(img_path?.comm ?? '')})`
|
||||
`url(${getImageHost(img_path?.comm ?? '')})`
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -174,6 +159,10 @@ onBeforeUnmount(() => {
|
||||
:modal-name="alert.storeModalName"
|
||||
@confirm-button-event="alert.storeConfirmButtonEvent"
|
||||
/>
|
||||
<BlocksModalToast
|
||||
v-model:is-open="toast.storeIsOpen"
|
||||
:content-text="toast.storeContentText"
|
||||
/>
|
||||
|
||||
<!-- 로딩 컴포넌트 -->
|
||||
<AtomsLoadingFull />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* Layout Utility Classes */
|
||||
@layer components {
|
||||
.section-container {
|
||||
.section-wrap {
|
||||
@apply relative h-[640px] md:h-[1000px];
|
||||
}
|
||||
|
||||
|
||||
@@ -49,9 +49,6 @@ const componentTag = computed(() => {
|
||||
const isDuplication = computed(() => props.type === 'duplication')
|
||||
const isSingle = computed(() => props.type === 'single')
|
||||
const platformIcon = computed(() => PLATFORM_ICON_MAP[props.platform])
|
||||
const duplicationImage = computed(() =>
|
||||
isDuplication.value ? DUP_IMAGE_MAP[props.platform] : ''
|
||||
)
|
||||
|
||||
const inlineStyle = computed<CSSProperties>(() => {
|
||||
const style: CSSProperties = {}
|
||||
@@ -63,7 +60,7 @@ const inlineStyle = computed<CSSProperties>(() => {
|
||||
style.color = props.textColor
|
||||
}
|
||||
if (props.type === 'duplication') {
|
||||
style.backgroundImage = `url(${duplicationImage.value})`
|
||||
style.backgroundImage = `url(${getImageHost(DUP_IMAGE_MAP[props.platform], { imageType: 'common' })})`
|
||||
}
|
||||
return style
|
||||
})
|
||||
|
||||
@@ -3,6 +3,9 @@ const showSnsList = ref(false)
|
||||
const isForceClosed = ref(false)
|
||||
|
||||
const { gameData } = useGameDataStore()
|
||||
const modalStore = useModalStore()
|
||||
|
||||
const { handleOpenToast } = modalStore
|
||||
|
||||
const snsBackgroundColor = computed(() => {
|
||||
const colorData = gameData?.comm_sns_bg_color_json?.display
|
||||
@@ -42,9 +45,9 @@ const handleCopy = async () => {
|
||||
try {
|
||||
const url = window.location.href
|
||||
await navigator.clipboard.writeText(url)
|
||||
console.log('✅ 복사 성공:', url)
|
||||
} catch (err) {
|
||||
console.error('❌ 복사 실패:', err)
|
||||
handleOpenToast('복사 성공')
|
||||
} catch (error) {
|
||||
console.error('복사 실패:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -74,7 +77,7 @@ const handleCopy = async () => {
|
||||
target="_blank"
|
||||
class="sns-item"
|
||||
:style="{
|
||||
backgroundImage: `url(/images/common/ic-v2-logo-${key}-fill.svg)`,
|
||||
backgroundImage: `url(${getImageHost(`/images/common/ic-v2-logo-${key}-fill.svg`, { imageType: 'common' })})`,
|
||||
}"
|
||||
>
|
||||
<span class="sr-only">{{ key }}</span>
|
||||
@@ -84,7 +87,7 @@ const handleCopy = async () => {
|
||||
type="button"
|
||||
class="sns-item"
|
||||
:style="{
|
||||
backgroundImage: `url(/images/common/ic-v2-community-link-line.svg)`,
|
||||
backgroundImage: `url(${getImageHost('/images/common/ic-v2-community-link-line.svg', { imageType: 'common' })})`,
|
||||
}"
|
||||
@click="handleCopy"
|
||||
>
|
||||
|
||||
@@ -15,6 +15,7 @@ interface props {
|
||||
const props = withDefaults(defineProps<props>(), {
|
||||
type: 'action',
|
||||
buttonSize: 'size-small md:size-large',
|
||||
target: '_blank',
|
||||
backgroundColor: 'var(--primary)',
|
||||
textColor: 'var(--alternative-02)',
|
||||
disabled: false,
|
||||
@@ -22,9 +23,9 @@ const props = withDefaults(defineProps<props>(), {
|
||||
|
||||
const componentTag = computed((): string => {
|
||||
switch (props.type) {
|
||||
case 'link':
|
||||
case 'download':
|
||||
case 'external':
|
||||
case 'download':
|
||||
case 'link':
|
||||
return 'a'
|
||||
case 'internal':
|
||||
return 'AtomsLocaleLink'
|
||||
@@ -35,7 +36,11 @@ const componentTag = computed((): string => {
|
||||
const componentProps = computed(() => {
|
||||
const baseProps = { disabled: props.disabled }
|
||||
|
||||
if (props.type === 'external') {
|
||||
if (
|
||||
props.type === 'external' ||
|
||||
props.type === 'download' ||
|
||||
props.type === 'link'
|
||||
) {
|
||||
return {
|
||||
...baseProps,
|
||||
href: props.href,
|
||||
|
||||
@@ -1,41 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import { getResolvedHost } from '#layers/utils/styleUtil'
|
||||
import type { PageDataResourceGroup } from '#layers/types/api/pageData'
|
||||
|
||||
interface Props {
|
||||
resourcesData?: PageDataResourceGroup
|
||||
objectFit?: 'contain' | 'cover'
|
||||
src: string | { pc?: string; mo?: string }
|
||||
alt?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
objectFit: 'contain',
|
||||
alt: 'image',
|
||||
})
|
||||
|
||||
const imagePaths = computed(() => {
|
||||
if (!props.resourcesData?.res_path) return null
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
const rootPath = isDev ? '' : '/templates/brand'
|
||||
|
||||
const isResponsiveMode = computed(() => {
|
||||
return typeof props.src === 'object' && !!props.src.pc && !!props.src.mo
|
||||
})
|
||||
const imagePaths = computed(() => {
|
||||
if (typeof props.src === 'string') {
|
||||
const resolved = getImageHost(`${rootPath}${props.src}`)
|
||||
return { pc: resolved, mo: '' }
|
||||
}
|
||||
|
||||
const pc =
|
||||
props.resourcesData.res_path.path_pc ?? props.resourcesData.res_path.path_mo
|
||||
const mo =
|
||||
props.resourcesData.res_path.path_mo ?? props.resourcesData.res_path.path_pc
|
||||
return {
|
||||
pc: pc ? getResolvedHost(pc) : '',
|
||||
mo: mo ? getResolvedHost(mo) : '',
|
||||
pc: props.src.pc ? getImageHost(`${rootPath}${props.src.pc}`) : '',
|
||||
mo: props.src.mo ? getImageHost(`${rootPath}${props.src.mo}`) : '',
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<picture v-if="imagePaths" ref="pictureRef">
|
||||
<picture v-if="isResponsiveMode">
|
||||
<source media="(min-width: 1024px)" :srcset="imagePaths.pc" />
|
||||
<source media="(max-width: 1023px)" :srcset="imagePaths.mo" />
|
||||
<img
|
||||
:src="imagePaths.pc"
|
||||
:alt="alt"
|
||||
:class="`w-full h-full object-${objectFit}`"
|
||||
loading="lazy"
|
||||
/>
|
||||
<img :src="imagePaths.pc" :alt="alt" v-bind="$attrs" loading="lazy" />
|
||||
</picture>
|
||||
|
||||
<img
|
||||
v-else
|
||||
:src="imagePaths.pc || imagePaths.mo"
|
||||
:alt="alt"
|
||||
v-bind="$attrs"
|
||||
loading="lazy"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -16,7 +16,6 @@ withDefaults(defineProps<Props>(), {
|
||||
: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"
|
||||
|
||||
27
layers/components/atoms/icons/ArrowRightLine.vue
Normal file
27
layers/components/atoms/icons/ArrowRightLine.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
size?: number | string
|
||||
color?: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
size: 20,
|
||||
color: 'var(--foreground-gray-500)',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M6.91073 3.57757C6.5853 3.90301 6.5853 4.43065 6.91073 4.75609L12.1548 10.0002L6.91073 15.2442C6.5853 15.5697 6.5853 16.0973 6.91073 16.4228C7.23617 16.7482 7.76381 16.7482 8.08924 16.4228L13.9226 10.5894C14.248 10.264 14.248 9.73634 13.9226 9.41091L8.08924 3.57757C7.76381 3.25214 7.23617 3.25214 6.91073 3.57757Z"
|
||||
:fill="color"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -1,26 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
size?: number | string
|
||||
color?: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
size: 16,
|
||||
color: 'var(--foreground-reversal)',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M15.7071 1.70711C16.0976 1.31658 16.0976 0.683417 15.7071 0.292893C15.3166 -0.0976311 14.6834 -0.0976311 14.2929 0.292893L8 6.58579L1.70711 0.292893C1.31658 -0.0976311 0.683417 -0.0976311 0.292894 0.292893C-0.0976304 0.683417 -0.0976304 1.31658 0.292894 1.70711L6.58579 8L0.292893 14.2929C-0.0976311 14.6834 -0.0976311 15.3166 0.292893 15.7071C0.683417 16.0976 1.31658 16.0976 1.70711 15.7071L8 9.41421L14.2929 15.7071C14.6834 16.0976 15.3166 16.0976 15.7071 15.7071C16.0976 15.3166 16.0976 14.6834 15.7071 14.2929L9.41421 8L15.7071 1.70711Z"
|
||||
:fill="color"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { PageDataResourceGroup } from '#layers/types/api/pageData'
|
||||
import { getResolvedHost, getColorCode } from '#layers/utils/styleUtil'
|
||||
import { getColorCode } from '#layers/utils/styleUtil'
|
||||
import { isTypeImage, isTypeText } from '#layers/utils/dataUtil'
|
||||
|
||||
interface Props {
|
||||
@@ -13,28 +13,12 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
objectFit: 'contain',
|
||||
})
|
||||
|
||||
const imagePaths = computed(() => {
|
||||
if (!props.resourcesData?.res_path) return null
|
||||
|
||||
const pcPath =
|
||||
props.resourcesData.res_path.path_pc ?? props.resourcesData.res_path.path_mo
|
||||
const moPath =
|
||||
props.resourcesData.res_path.path_mo ?? props.resourcesData.res_path.path_pc
|
||||
|
||||
return {
|
||||
pc: pcPath ? getResolvedHost(pcPath) : '',
|
||||
mo: moPath ? getResolvedHost(moPath) : '',
|
||||
}
|
||||
})
|
||||
const displayText = computed(() => {
|
||||
return props.resourcesData?.display?.text || 'image'
|
||||
})
|
||||
const colorName = computed(() => {
|
||||
return props.resourcesData?.display?.color_name
|
||||
})
|
||||
const colorCode = computed(() => {
|
||||
return props.resourcesData?.display?.color_code
|
||||
})
|
||||
const imagePaths = computed(() => getImagePaths(props.resourcesData))
|
||||
const displayText = computed(
|
||||
() => props.resourcesData?.display?.text || 'image'
|
||||
)
|
||||
const colorName = computed(() => props.resourcesData?.display?.color_name)
|
||||
const colorCode = computed(() => props.resourcesData?.display?.color_code)
|
||||
|
||||
// HTML 콘텐츠 정리 (줄바꿈 처리)
|
||||
const sanitizedContent = computed(() => {
|
||||
@@ -43,18 +27,13 @@ const sanitizedContent = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 이미지 -->
|
||||
<picture v-if="isTypeImage(resourcesData?.resource_type) && imagePaths">
|
||||
<source :srcset="imagePaths.pc" media="(min-width: 1024px)" />
|
||||
<source :srcset="imagePaths.mo" media="(max-width: 1023px)" />
|
||||
<img
|
||||
:src="imagePaths.pc"
|
||||
:alt="alt || displayText"
|
||||
:class="`w-full h-full object-${objectFit}`"
|
||||
loading="lazy"
|
||||
/>
|
||||
</picture>
|
||||
<!-- 텍스트 -->
|
||||
<AtomsImg
|
||||
v-if="isTypeImage(resourcesData?.resource_type) && imagePaths"
|
||||
:src="imagePaths"
|
||||
:alt="alt || displayText"
|
||||
:class="`w-full h-full object-${objectFit}`"
|
||||
/>
|
||||
|
||||
<span
|
||||
v-else-if="isTypeText(resourcesData?.resource_type)"
|
||||
v-dompurify-html="sanitizedContent"
|
||||
|
||||
50
layers/components/blocks/modal/Toast.vue
Normal file
50
layers/components/blocks/modal/Toast.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean
|
||||
contentText: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
isOpen: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const isVisible = ref(false)
|
||||
|
||||
watch(
|
||||
() => props.isOpen,
|
||||
newVal => {
|
||||
if (newVal) {
|
||||
isVisible.value = true
|
||||
|
||||
setTimeout(() => {
|
||||
isVisible.value = false
|
||||
emit('close')
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
<div v-if="isVisible" class="toast-container">
|
||||
<p class="toast-text">
|
||||
{{ contentText }}
|
||||
</p>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.toast-container {
|
||||
@apply fixed left-1/2 max-w-[328px] py-3 px-6 rounded-[8px] bg-[rgba(85,85,85,0.4)] backdrop-blur-[25px] -translate-x-1/2 bottom-[20px] md:bottom-[40px] z-[800]
|
||||
before:content-[''] before:absolute before:inset-0 before:border before:border-white/10 before:rounded-[8px];
|
||||
}
|
||||
.toast-text {
|
||||
@apply text-center text-[16px] text-white leading-[26px] tracking-[-0.48px];
|
||||
}
|
||||
</style>
|
||||
@@ -176,7 +176,7 @@ onBeforeUnmount(() => {
|
||||
<div class="game-wrapper" :class="{ 'is-fixed': isPassedStoveGnb }">
|
||||
<AtomsLocaleLink to="/brand" class="mx-auto md:hidden">
|
||||
<img
|
||||
:src="getResolvedHost(gnbData?.bi_path)"
|
||||
:src="getImageHost(gnbData?.bi_path)"
|
||||
:alt="gameData?.game_name"
|
||||
class="h-[30px]"
|
||||
/>
|
||||
@@ -193,7 +193,7 @@ onBeforeUnmount(() => {
|
||||
<div class="nav-logo">
|
||||
<AtomsLocaleLink to="/brand" @click="handleMenuClose">
|
||||
<img
|
||||
:src="getResolvedHost(gnbData?.bi_path)"
|
||||
:src="getImageHost(gnbData?.bi_path)"
|
||||
:alt="gameData?.game_name"
|
||||
class="h-[30px]"
|
||||
/>
|
||||
@@ -215,13 +215,21 @@ onBeforeUnmount(() => {
|
||||
<AtomsLocaleLink
|
||||
:to="gnbItem.url_path"
|
||||
:target="gnbItem.link_target"
|
||||
:class="['nav-1depth', { active: isNavItemActive(gnbItem) }]"
|
||||
:class="[
|
||||
'nav-1depth',
|
||||
{ 'has-link': !!gnbItem.url_path },
|
||||
{ active: isNavItemActive(gnbItem) },
|
||||
]"
|
||||
>
|
||||
<span>{{ gnbItem.menu_name }}</span>
|
||||
<AtomsIconsArrowDownFill
|
||||
v-if="has2depthButton(gnbItem)"
|
||||
class="hidden md:block"
|
||||
/>
|
||||
<AtomsIconsArrowRightLine
|
||||
v-if="!has2depthButton(gnbItem)"
|
||||
class="ml-auto md:hidden"
|
||||
/>
|
||||
</AtomsLocaleLink>
|
||||
<Transition name="fade">
|
||||
<div v-if="has2depthButton(gnbItem)" class="nav-2depth">
|
||||
@@ -322,7 +330,6 @@ onBeforeUnmount(() => {
|
||||
colorCode: gnb1depthButtonData?.btn_info?.color_code_txt,
|
||||
})
|
||||
"
|
||||
class="nav-1depth"
|
||||
>
|
||||
{{ gnb1depthButtonData?.btn_info?.txt_btn_name }}
|
||||
</AtomsButtonLauncher>
|
||||
@@ -337,7 +344,11 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-close" @click="handleMenuClose">
|
||||
<AtomsIconsMenuCloseLine class="mx-auto" />
|
||||
<AtomsIconsCloseLine
|
||||
size="24"
|
||||
color="var(--foreground-reversal)"
|
||||
class="mx-auto"
|
||||
/>
|
||||
<span class="sr-only">menu close</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -426,6 +437,9 @@ onBeforeUnmount(() => {
|
||||
.nav-1depth.active {
|
||||
@apply bg-theme-foreground-reversal-8 md:bg-transparent;
|
||||
}
|
||||
.nav-1depth.has-link {
|
||||
@apply hover:bg-theme-foreground-reversal-4 active:bg-theme-foreground-reversal-10 md:hover:bg-transparent md:active:bg-transparent;
|
||||
}
|
||||
|
||||
.nav-2depth {
|
||||
@apply text-[15px] md:hidden md:absolute md:top-[64px] md:left-[-28px] md:pt-1;
|
||||
@@ -480,15 +494,16 @@ onBeforeUnmount(() => {
|
||||
.btn-start:hover .nav-2depth {
|
||||
@apply md:block;
|
||||
}
|
||||
.btn-start:deep(.nav-1depth) {
|
||||
@apply w-full h-[48px] px-10 md:w-auto;
|
||||
.btn-start:deep(> .btn-base) {
|
||||
@apply w-full h-[48px] px-10;
|
||||
}
|
||||
.btn-start:deep(.nav-1depth) .icon-platform {
|
||||
.btn-start:deep(> .btn-base) .icon-platform {
|
||||
@apply hidden;
|
||||
}
|
||||
.btn-start:deep(.nav-1depth) .btn-content {
|
||||
@apply justify-center text-center;
|
||||
.btn-start:deep(> .btn-base) .btn-content {
|
||||
@apply justify-center;
|
||||
}
|
||||
|
||||
.btn-start .nav-2depth {
|
||||
@apply left-[unset] right-[-40px];
|
||||
}
|
||||
|
||||
@@ -14,12 +14,13 @@ export const BREAKPOINTS = {
|
||||
*/
|
||||
export const useResponsiveBreakpoints = () => {
|
||||
const breakpoints = useBreakpoints(BREAKPOINTS)
|
||||
const isXs = breakpoints.smaller('sm') // < 768px
|
||||
const isSm = breakpoints.smaller('md') // < 1024px
|
||||
const isMd = breakpoints.smaller('lg') // < 1440px
|
||||
const isLg = breakpoints.greater('lg') // >= 1440px
|
||||
const isMobile = isXs || isSm
|
||||
const isDesktop = isLg || isMd
|
||||
const isXs = breakpoints.smaller('sm') // < 768
|
||||
const isSm = breakpoints.between('sm', 'md') // 768 <= x < 1024
|
||||
const isMd = breakpoints.between('md', 'lg') // 1024 <= x < 1440
|
||||
const isLg = breakpoints.greaterOrEqual('lg') // >= 1440
|
||||
|
||||
const isMobile = breakpoints.smaller('md') // < 1024
|
||||
const isDesktop = breakpoints.greaterOrEqual('md') // >= 1024
|
||||
|
||||
return computed(() => ({
|
||||
isXs: isXs.value,
|
||||
|
||||
@@ -95,13 +95,32 @@ export const useModalStore = defineStore('modalStore', () => {
|
||||
scrollStore.controlScrollLock(false)
|
||||
}
|
||||
|
||||
// toast ------------------
|
||||
const toast = {
|
||||
storeIsOpen: ref(false),
|
||||
storeContentText: ref(''),
|
||||
}
|
||||
|
||||
const handleOpenToast = (contentText: string) => {
|
||||
toast.storeIsOpen.value = true
|
||||
toast.storeContentText.value = contentText
|
||||
}
|
||||
|
||||
const handleCloseToast = () => {
|
||||
toast.storeIsOpen.value = false
|
||||
toast.storeContentText.value = ''
|
||||
}
|
||||
|
||||
return {
|
||||
alert,
|
||||
confirm,
|
||||
youtube,
|
||||
toast,
|
||||
handleOpenAlert,
|
||||
handleOpenConfirm,
|
||||
handleOpenYoutube,
|
||||
handleResetYoutube,
|
||||
handleOpenToast,
|
||||
handleCloseToast,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -131,7 +131,7 @@ const onArrowClick = (direction, targetIndex) => {
|
||||
:description="
|
||||
formatTimestamp(item.create_datetime, 'YYYY.MM.DD')
|
||||
"
|
||||
:img-path="getResolvedHost(item.media_thumbnail_url)"
|
||||
:img-path="getImageHost(item.media_thumbnail_url)"
|
||||
:url="getArticleUrl(item.article_id)"
|
||||
link-target="_blank"
|
||||
/>
|
||||
|
||||
@@ -46,8 +46,8 @@ const buttonListData = computed(() => {
|
||||
<div v-if="imgListData" class="img-container">
|
||||
<div v-for="(item, index) in imgListData" :key="index" class="img-item">
|
||||
<AtomsImg
|
||||
:resources-data="item"
|
||||
object-fit="contain"
|
||||
v-if="getImagePaths(item)"
|
||||
:src="getImagePaths(item)"
|
||||
:alt="item?.group_label"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@ const handleSplideMove = (_splide: SplideType, newIndex: number) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="section-container relative">
|
||||
<section class="section-wrap">
|
||||
<BlocksSlideFade
|
||||
v-if="slideData"
|
||||
ref="splideRef"
|
||||
|
||||
@@ -22,7 +22,7 @@ const paginationData = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="section-container relative">
|
||||
<section class="section-wrap">
|
||||
<BlocksSlideThumbnail
|
||||
:slide-data="slideData"
|
||||
:pagination-data="paginationData"
|
||||
|
||||
@@ -43,7 +43,7 @@ const handleSplideMove = (_splide: SplideType, newIndex: number) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="section-container relative">
|
||||
<section class="section-wrap">
|
||||
<BlocksSlideThumbnail
|
||||
:slide-data="slideData"
|
||||
:pagination-data="paginationData"
|
||||
|
||||
@@ -102,7 +102,7 @@ const onArrowClick = (direction, targetIndex) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="section-container min-h-[700px]">
|
||||
<section class="section-wrap min-h-[700px]">
|
||||
<WidgetsBackground v-if="backgroundData" :resources-data="backgroundData" />
|
||||
<div class="section-content">
|
||||
<WidgetsMainTitle
|
||||
|
||||
@@ -59,7 +59,7 @@ const onArrowClick = (direction, targetIndex) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="section-container">
|
||||
<section class="section-wrap">
|
||||
<WidgetsBackground v-if="backgroundData" :resources-data="backgroundData" />
|
||||
<div class="section-content px-0">
|
||||
<WidgetsMainTitle
|
||||
@@ -98,7 +98,7 @@ const onArrowClick = (direction, targetIndex) => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.section-container {
|
||||
.section-wrap {
|
||||
@apply before:hidden md:before:block before:content-[''] before:absolute before:top-0 before:left-0 before:w-[104px] before:h-full before:bg-gradient-to-l from-transparent to-[rgba(0,0,0,0.7)] before:z-[5]
|
||||
after:hidden md:after:block after:content-[''] after:absolute after:top-0 after:right-0 after:w-[104px] after:h-full after:bg-gradient-to-r from-transparent to-[rgba(0,0,0,0.7)];
|
||||
}
|
||||
|
||||
@@ -9,9 +9,6 @@ interface Props {
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||
|
||||
const backgroundData = computed(() =>
|
||||
getComponentGroup(props.components, 'background')
|
||||
)
|
||||
@@ -27,17 +24,10 @@ const videoPlayData = computed(() =>
|
||||
const buttonListData = computed(() =>
|
||||
getComponentGroupAry(props.components, 'buttonList')
|
||||
)
|
||||
|
||||
const handleSendLog = (index: number) => {
|
||||
sendLog(
|
||||
locale.value,
|
||||
useAnalyticsLogDataDirect(buttonListData.value[index], props.pageVerTmplSeq)
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="section-container">
|
||||
<section class="section-wrap">
|
||||
<WidgetsBackground
|
||||
v-if="backgroundData"
|
||||
:resources-data="backgroundData"
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
getComponentContainer,
|
||||
} from '#layers/utils/dataUtil'
|
||||
import { formatTimestamp } from '#layers/utils/formatUtil'
|
||||
import { getResolvedHost } from '#layers/utils/styleUtil'
|
||||
import { getImageHost } from '#layers/utils/styleUtil'
|
||||
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
import type { OperateGroupItem } from '#layers/types/api/resourcesData'
|
||||
|
||||
@@ -118,7 +118,7 @@ const onArrowClick = direction => {
|
||||
<BlocksCardNews
|
||||
:title="item.title"
|
||||
:description="formatTimestamp(item.reg_dt, 'YYYY.MM.DD')"
|
||||
:img-path="getResolvedHost(item.img_path)"
|
||||
:img-path="getImageHost(item.img_path)"
|
||||
:url="item.url"
|
||||
:link-target="item.link_target"
|
||||
class="slide-inner"
|
||||
|
||||
@@ -33,7 +33,7 @@ const onArrowClick = direction => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="section-container">
|
||||
<section class="section-wrap">
|
||||
<BlocksSlideFade
|
||||
v-if="slideData"
|
||||
:arrows="true"
|
||||
|
||||
@@ -70,11 +70,7 @@ export interface GameDataGameFont {
|
||||
|
||||
// 파비콘 경로 타입
|
||||
export interface GameDataFavicon {
|
||||
'16_16': string
|
||||
'32_32': string
|
||||
'72_72': string
|
||||
'180_180': string
|
||||
'192_192': string
|
||||
[index: number]: string
|
||||
}
|
||||
|
||||
// 메타 태그 타입
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
PageDataValue,
|
||||
PageDataResourceContainer,
|
||||
PageDataTemplateComponents,
|
||||
PageDataResourceGroup,
|
||||
PageDataResourceGroupType,
|
||||
} from '#layers/types/api/pageData'
|
||||
import type { OperateComponents } from '#layers/types/api/resourcesData'
|
||||
@@ -145,14 +146,18 @@ export const getComponentGroupAry = (
|
||||
return components[componentName]?.groups || []
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 시간의 타임스탬프를 반환합니다.
|
||||
* @param unit 단위 ('ms' | 's') - 밀리초 또는 초
|
||||
* @returns 타임스탬프
|
||||
*/
|
||||
export const getCurrentTimestamp = (unit: 'ms' | 's' = 'ms'): number => {
|
||||
const now = Date.now()
|
||||
return unit === 's' ? Math.floor(now / 1000) : now
|
||||
export const getImagePaths = (resourcesData: PageDataResourceGroup) => {
|
||||
if (!resourcesData?.res_path) return null
|
||||
|
||||
const pcPath = resourcesData.res_path.path_pc
|
||||
const moPath = resourcesData.res_path.path_mo
|
||||
|
||||
if (!pcPath || !moPath) return pcPath || moPath
|
||||
|
||||
return {
|
||||
pc: pcPath,
|
||||
mo: moPath,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,30 +9,39 @@ import type {
|
||||
} from '#layers/types/api/pageData'
|
||||
|
||||
/**
|
||||
* [TODO] 수정 필요
|
||||
* 이미지 경로를 완전한 호스트 URL로 변환합니다.
|
||||
* @param path 이미지 경로
|
||||
* @returns 완전한 이미지 URL
|
||||
*/
|
||||
export const getResolvedHost = (path: string): string => {
|
||||
// path가 없으면 빈 문자열 반환
|
||||
if (!path || typeof path !== 'string') return ''
|
||||
export const getImageHost = (
|
||||
path: string,
|
||||
options: { imageType?: 'common' | 'game' } = {}
|
||||
): string => {
|
||||
if (!path) return ''
|
||||
|
||||
if (
|
||||
path.startsWith('http://') ||
|
||||
path.startsWith('https://') ||
|
||||
path.startsWith('www.')
|
||||
) {
|
||||
return path
|
||||
}
|
||||
if (/^(https?:\/\/|www\.)/.test(path)) return path
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
// const isDev = process.env.NODE_ENV === "development";
|
||||
// const rootPath = isDev ? "/images" : `${config.public.staticUrl}`;
|
||||
const { imageType = 'game' } = options
|
||||
const { staticUrl, runType } = config.public
|
||||
|
||||
const rootPath = config.public.staticUrl
|
||||
const isDevelopment = process.env.NODE_ENV === 'development'
|
||||
const isCommon = imageType === 'common'
|
||||
|
||||
return `${rootPath}${path}`
|
||||
// * [TODO] 수정 필요 : 게임별 이미지 로컬에 추가 필요.
|
||||
if (!isCommon) {
|
||||
return `${staticUrl}${path}`
|
||||
}
|
||||
|
||||
// 개발 환경일 때는 루트 경로 생략
|
||||
if (isDevelopment) return path
|
||||
|
||||
// 공통/게임 여부에 따른 경로 결정
|
||||
const basePath = isCommon
|
||||
? `${staticUrl}/${runType}/templates/brand`
|
||||
: staticUrl
|
||||
|
||||
return `${basePath}${path}`
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,8 +70,8 @@ export const getDeviceSrc = (
|
||||
if (!pcPath && !mobilePath) return null
|
||||
|
||||
const resolvedImages = {
|
||||
pc: pcPath ? getResolvedHost(pcPath) : '',
|
||||
mobile: mobilePath ? getResolvedHost(mobilePath) : '',
|
||||
pc: pcPath ? getImageHost(pcPath) : '',
|
||||
mobile: mobilePath ? getImageHost(mobilePath) : '',
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user