Merge branch 'feature/202501107-all' into feature/20251001-gil

This commit is contained in:
“hyeonggkim”
2025-10-31 15:45:17 +09:00
27 changed files with 247 additions and 186 deletions

View File

@@ -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
})

View File

@@ -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"
>

View File

@@ -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,

View File

@@ -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>

View File

@@ -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"

View 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>

View File

@@ -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>

View File

@@ -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"

View 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>

View File

@@ -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];
}