fix. [SWV-866] 액션버튼 기능 개선 (이미지 타입 추가)
Made-with: Cursor
@@ -69,10 +69,9 @@
|
||||
type="single"
|
||||
platform="pc"
|
||||
class="inspection-launcher"
|
||||
:icon-component="AtomsIconsPlayRoundFill"
|
||||
:icon-props="{ size: 16, color: '#332C2A' }"
|
||||
>
|
||||
{{ tm('Txt_Game_Start') }}
|
||||
<AtomsIconsPlayRoundFill :size="16" color="#332C2A" />
|
||||
</BlocksButtonLauncher>
|
||||
</div>
|
||||
</div>
|
||||
@@ -358,6 +357,9 @@ definePageMeta({
|
||||
.button-group:deep(.inspection-launcher.btn-base) {
|
||||
@apply flex-1 bg-[var(--primary)] px-0 rounded md:rounded-lg;
|
||||
}
|
||||
.button-group:deep(.inspection-launcher) .text {
|
||||
@apply flex items-center justify-center gap-[2px] mr-0 md:gap-[4px];
|
||||
}
|
||||
.button-group:deep(.inspection-launcher.btn-base .icon-platform) {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
13
app/pages/teaser.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: false, // 동적 레이아웃을 위해 기본 레이아웃 비활성화
|
||||
middleware: ['inspection'],
|
||||
})
|
||||
|
||||
// 진입 시 /home으로 리다이렉트
|
||||
await navigateTo('/home', { replace: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div />
|
||||
</template>
|
||||
@@ -1,32 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
interface props {
|
||||
<<<<<<< HEAD
|
||||
type?: 'button' | 'link'
|
||||
to?: string
|
||||
target?: '_self' | '_blank'
|
||||
=======
|
||||
type?: 'internal' | 'external' | 'action'
|
||||
href?: string
|
||||
>>>>>>> feature/20250228_SWV-866
|
||||
backgroundColor?: string
|
||||
srOnly?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<props>(), {
|
||||
<<<<<<< HEAD
|
||||
type: 'button',
|
||||
to: '',
|
||||
target: '_self',
|
||||
=======
|
||||
type: 'action',
|
||||
>>>>>>> feature/20250228_SWV-866
|
||||
backgroundColor: '',
|
||||
srOnly: '',
|
||||
})
|
||||
|
||||
const componentTag = computed((): string => {
|
||||
switch (props.type) {
|
||||
case 'link':
|
||||
case 'internal':
|
||||
return 'AtomsLocaleLink'
|
||||
case 'external':
|
||||
return 'a'
|
||||
default:
|
||||
return 'button'
|
||||
}
|
||||
})
|
||||
const componentProps = computed(() => {
|
||||
switch (props.type) {
|
||||
case 'link':
|
||||
return { to: props.to, target: props.target }
|
||||
case 'internal':
|
||||
return { to: props.href, target: '_self' }
|
||||
case 'external':
|
||||
return { href: props.href, target: '_blank' }
|
||||
default:
|
||||
return {}
|
||||
}
|
||||
|
||||
87
layers/components/atoms/Button/Image.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
import type { ImageButtonType } from '#layers/types/components/button'
|
||||
|
||||
interface props {
|
||||
type?: ImageButtonType
|
||||
href?: string
|
||||
backgroundImage: string
|
||||
alt: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<props>(), {
|
||||
type: 'action',
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
const componentTag = computed((): string => {
|
||||
switch (props.type) {
|
||||
case 'external':
|
||||
return 'a'
|
||||
case 'internal':
|
||||
return 'AtomsLocaleLink'
|
||||
default:
|
||||
return 'button'
|
||||
}
|
||||
})
|
||||
|
||||
const componentProps = computed(() => {
|
||||
if (props.type === 'external') {
|
||||
return {
|
||||
href: props.href,
|
||||
target: '_blank',
|
||||
}
|
||||
}
|
||||
|
||||
if (props.type === 'internal') {
|
||||
if (props.href) {
|
||||
return {
|
||||
to: props.href,
|
||||
target: '_self',
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
return {}
|
||||
})
|
||||
const buttonStyle = computed(() => {
|
||||
return {
|
||||
backgroundImage: props.backgroundImage,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="componentTag"
|
||||
v-bind="{ ...componentProps }"
|
||||
:class="['btn-base', { disabled: props.disabled }]"
|
||||
:style="buttonStyle"
|
||||
:disabled="props.disabled"
|
||||
>
|
||||
<img
|
||||
v-if="props.backgroundImage"
|
||||
:src="props.backgroundImage"
|
||||
:alt="props.alt"
|
||||
class="btn-bg"
|
||||
/>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn-base {
|
||||
@apply overflow-hidden relative h-[48px] md:h-[64px] rounded-[8px] md:rounded-[10px] cursor-pointer
|
||||
after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:bg-white after:transition-opacity after:duration-300 after:ease-in-out after:opacity-0
|
||||
hover:after:opacity-20;
|
||||
}
|
||||
|
||||
.btn-base.disabled {
|
||||
@apply cursor-default pointer-events-none
|
||||
after:opacity-20 after:z-[2];
|
||||
}
|
||||
|
||||
.btn-bg {
|
||||
@apply w-full h-full object-contain;
|
||||
}
|
||||
</style>
|
||||
@@ -2,23 +2,23 @@
|
||||
import type { TrackingObject } from '#layers/types/api/common'
|
||||
|
||||
interface Props {
|
||||
category?: 'system' | 'image'
|
||||
variant?: 'videoPlay' | 'videoPlayImg'
|
||||
backgroundColor?: string
|
||||
tracking: TrackingObject
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), { category: 'system' })
|
||||
const props = withDefaults(defineProps<Props>(), { variant: 'videoPlay' })
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
const buttonClasses = computed(() => [
|
||||
'btn-play',
|
||||
props.category === 'system' ? 'play-icon' : 'play-image',
|
||||
props.variant === 'videoPlay' ? 'play-icon' : 'play-image',
|
||||
])
|
||||
|
||||
const buttonStyle = computed(() =>
|
||||
props.category === 'system' && props.backgroundColor
|
||||
props.variant === 'videoPlay' && props.backgroundColor
|
||||
? { backgroundColor: props.backgroundColor }
|
||||
: {}
|
||||
)
|
||||
@@ -28,7 +28,7 @@ const onClick = () => sendLog(locale.value, props.tracking)
|
||||
|
||||
<template>
|
||||
<button :class="buttonClasses" :style="buttonStyle" @click="onClick">
|
||||
<span v-if="props.category === 'system'" class="icon">
|
||||
<span v-if="props.variant === 'videoPlay'" class="icon">
|
||||
<AtomsIconsArrowRightFill />
|
||||
</span>
|
||||
<span class="sr-only">Play</span>
|
||||
@@ -37,12 +37,11 @@ const onClick = () => sendLog(locale.value, props.tracking)
|
||||
|
||||
<style scoped>
|
||||
.btn-play {
|
||||
@apply relative flex items-center justify-center;
|
||||
@apply relative flex items-center justify-center rounded-full w-[60px] h-[60px] md:w-[80px] md:h-[80px];
|
||||
}
|
||||
|
||||
.play-icon {
|
||||
@apply w-[60px] h-[60px] md:w-[80px] md:h-[80px] rounded-full
|
||||
before:content-[''] before:absolute before:top-0 before:left-0 before:w-full before:h-full before:border before:border-[rgba(255,255,255,0.5)] before:rounded-full
|
||||
@apply before:content-[''] before:absolute before:top-0 before:left-0 before:w-full before:h-full before:border before:border-[rgba(255,255,255,0.5)] before:rounded-full
|
||||
after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:bg-white after:rounded-[50%] after:opacity-0 after:transition-opacity after:duration-300 after:ease-in-out
|
||||
hover:after:opacity-10;
|
||||
}
|
||||
@@ -54,16 +53,13 @@ const onClick = () => sendLog(locale.value, props.tracking)
|
||||
}
|
||||
|
||||
.play-image {
|
||||
@apply w-[69px] h-[69px] md:w-[110px] md:h-[110px];
|
||||
@apply overflow-hidden;
|
||||
}
|
||||
.play-image::before {
|
||||
@apply content-[''] absolute inset-0 z-0 bg-no-repeat bg-center bg-cover bg-[url(/images/common/btn_play/btn_default.png)] transition-opacity duration-300 ease-out;
|
||||
@apply content-[''] absolute inset-0 z-0 rounded-full bg-no-repeat bg-center bg-cover bg-[url(/images/common/btn_play/btn_default_m.png)] md:bg-[url(/images/common/btn_play/btn_default.png)];
|
||||
}
|
||||
.play-image::after {
|
||||
@apply content-[''] absolute inset-0 z-0 bg-no-repeat bg-center bg-cover bg-[url(/images/common/btn_play/btn_hover.png)] opacity-0 transition-opacity duration-300 ease-out;
|
||||
}
|
||||
.play-image:hover::before {
|
||||
@apply opacity-0;
|
||||
@apply content-[''] absolute inset-0 z-0 rounded-full bg-no-repeat bg-center bg-cover bg-[url(/images/common/btn_play/btn_hover_m.png)] md:bg-[url(/images/common/btn_play/btn_hover.png)] opacity-0 transition-opacity duration-300 ease-out;
|
||||
}
|
||||
.play-image:hover::after {
|
||||
@apply opacity-100;
|
||||
|
||||
@@ -5,12 +5,11 @@ interface props {
|
||||
type?: ButtonType
|
||||
size?: string
|
||||
variant?: ButtonVariant
|
||||
target?: '_self' | '_blank'
|
||||
href?: string
|
||||
backgroundColor?: string
|
||||
textColor?: string
|
||||
disabled?: boolean
|
||||
gradient?: boolean
|
||||
useGradient?: boolean
|
||||
useGameFont?: boolean
|
||||
}
|
||||
|
||||
@@ -21,7 +20,7 @@ const props = withDefaults(defineProps<props>(), {
|
||||
target: '_self',
|
||||
textColor: 'var(--alternative-02)',
|
||||
disabled: false,
|
||||
gradient: false,
|
||||
useGradient: false,
|
||||
useGameFont: false,
|
||||
})
|
||||
|
||||
@@ -31,21 +30,20 @@ const { fontFamily } = storeToRefs(gameDataStore)
|
||||
const componentTag = computed((): string => {
|
||||
switch (props.type) {
|
||||
case 'external':
|
||||
case 'link':
|
||||
return 'a'
|
||||
case 'download':
|
||||
return props.href ? 'a' : 'button'
|
||||
case 'internal':
|
||||
return props.href ? 'AtomsLocaleLink' : 'button'
|
||||
return 'AtomsLocaleLink'
|
||||
default:
|
||||
return 'button'
|
||||
}
|
||||
})
|
||||
const componentProps = computed(() => {
|
||||
if (props.type === 'external' || props.type === 'link') {
|
||||
if (props.type === 'external') {
|
||||
return {
|
||||
href: props.href,
|
||||
target: props.target,
|
||||
target: '_blank',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +51,7 @@ const componentProps = computed(() => {
|
||||
if (props.href) {
|
||||
return {
|
||||
to: props.href,
|
||||
target: '_self',
|
||||
}
|
||||
}
|
||||
return {}
|
||||
@@ -106,7 +105,8 @@ const textStyle = computed(() => {
|
||||
:style="buttonStyle"
|
||||
:disabled="props.disabled"
|
||||
>
|
||||
<i v-if="props.gradient" class="btn-gradient"></i>
|
||||
<!-- 그라데이션 -->
|
||||
<i v-if="props.useGradient" class="btn-gradient"></i>
|
||||
<span class="btn-content" :style="textStyle">
|
||||
<slot />
|
||||
<AtomsIconsLongArrowRightLine
|
||||
|
||||
@@ -14,8 +14,8 @@ const analytics = {
|
||||
<template>
|
||||
<AtomsButtonCircle
|
||||
sr-only="home"
|
||||
type="link"
|
||||
to="/home"
|
||||
type="internal"
|
||||
href="/home"
|
||||
class="btn-home"
|
||||
background-color="rgb(0 0 0 / 0.2)"
|
||||
@click="sendLog(locale, analytics)"
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties, Component } from 'vue'
|
||||
import type { CSSProperties } from 'vue'
|
||||
import type { PlatformTransformType } from '#layers/types/api/gameData'
|
||||
import type {
|
||||
DownloadButtonType,
|
||||
LauncherButtonType,
|
||||
ButtonVariant,
|
||||
Platform,
|
||||
} from '#layers/types/components/button'
|
||||
|
||||
interface Props {
|
||||
type?: DownloadButtonType
|
||||
type?: LauncherButtonType
|
||||
platform: Platform
|
||||
variant?: ButtonVariant
|
||||
backgroundColor?: string
|
||||
textColor?: string
|
||||
iconComponent?: Component
|
||||
iconProps?: Record<string, any>
|
||||
disabled?: boolean
|
||||
useGameFont?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: 'default',
|
||||
variant: 'filled',
|
||||
disabled: false,
|
||||
useGameFont: false,
|
||||
})
|
||||
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
@@ -33,7 +29,7 @@ const gameDataStore = useGameDataStore()
|
||||
const modalStore = useModalStore()
|
||||
const { isProcessing, validateLauncher } = useCheckGameStart()
|
||||
|
||||
const { gameName, platformType, osType, marketJson, fontFamily } =
|
||||
const { gameName, platformType, osType, marketJson } =
|
||||
storeToRefs(gameDataStore)
|
||||
|
||||
const PLATFORM_ICON_MAP: Record<Platform, string> = {
|
||||
@@ -55,11 +51,6 @@ const componentTag = computed(() => {
|
||||
}
|
||||
return 'button'
|
||||
})
|
||||
const shouldShowPlatformIcon = computed(
|
||||
() =>
|
||||
(props.type === 'default' && props.variant !== 'custom') ||
|
||||
props.type === 'single'
|
||||
)
|
||||
const shouldShowDownloadIcon = computed(
|
||||
() =>
|
||||
props.platform === 'pc' &&
|
||||
@@ -91,9 +82,6 @@ const textStyle = computed<CSSProperties>(() => {
|
||||
if (props.textColor) {
|
||||
style.color = props.textColor
|
||||
}
|
||||
if (props.useGameFont && fontFamily.value) {
|
||||
style.fontFamily = fontFamily.value
|
||||
}
|
||||
|
||||
return style
|
||||
})
|
||||
@@ -158,24 +146,19 @@ const handleClick = () => {
|
||||
:is="componentTag"
|
||||
v-bind="$attrs"
|
||||
:class="['btn-base', props.type, { 'no-text': !$slots.default }]"
|
||||
:data-variant="props.variant"
|
||||
:data-platform="props.platform"
|
||||
:data-variant="props.variant"
|
||||
:style="buttonStyle"
|
||||
:disabled="disabled || isProcessing"
|
||||
@click="handleClick"
|
||||
>
|
||||
<span class="btn-content">
|
||||
<span v-if="shouldShowPlatformIcon" class="icon-platform">
|
||||
<span v-if="props.type !== 'duplication'" class="icon-platform">
|
||||
<component :is="platformIcon" />
|
||||
</span>
|
||||
<span class="text" :style="textStyle">
|
||||
<slot />
|
||||
</span>
|
||||
<component
|
||||
:is="props.iconComponent"
|
||||
v-if="props.iconComponent"
|
||||
v-bind="props.iconProps"
|
||||
/>
|
||||
<span v-if="shouldShowDownloadIcon" class="icon-download">
|
||||
<AtomsIconsDownloadLine />
|
||||
</span>
|
||||
@@ -193,9 +176,6 @@ const handleClick = () => {
|
||||
.icon-platform {
|
||||
@apply w-5 h-5 mr-2 flex-shrink-0;
|
||||
}
|
||||
.icon-download {
|
||||
@apply ml-auto pl-4;
|
||||
}
|
||||
|
||||
.btn-base[data-variant='filled'] {
|
||||
@apply bg-[#383838] text-[#ffffff]
|
||||
@@ -208,6 +188,9 @@ const handleClick = () => {
|
||||
@apply bg-white text-[#1F1F1F]
|
||||
before:content-[''] before:absolute before:top-0 before:left-0 before:w-full before:h-full before:border before:border-black/10 before:rounded-lg;
|
||||
}
|
||||
.btn-base[data-variant='outlined']:hover {
|
||||
@apply before:border-[#999];
|
||||
}
|
||||
.btn-base[data-variant='outlined'][data-platform='app_store'] svg,
|
||||
.btn-base[data-variant='outlined'][data-platform='pc'] svg,
|
||||
.btn-base[data-variant='outlined'][data-platform='stove'] svg {
|
||||
@@ -226,13 +209,15 @@ const handleClick = () => {
|
||||
@apply line-clamp-2 text-[14px]
|
||||
md:text-[16px];
|
||||
}
|
||||
|
||||
.btn-base.default[data-variant='outlined'] .icon-download {
|
||||
@apply border-black/10;
|
||||
}
|
||||
.btn-base.default[data-variant='outlined'] .icon-download svg {
|
||||
@apply fill-[#1F1F1F];
|
||||
}
|
||||
.icon-download {
|
||||
@apply ml-auto pl-4;
|
||||
}
|
||||
|
||||
/* duplication */
|
||||
.btn-base.duplication {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { ColorObject, TrackingObject } from '#layers/types/api/common'
|
||||
|
||||
interface Props {
|
||||
color: ColorObject
|
||||
backgroundColor: ColorObject
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -19,7 +19,7 @@ const analytics = {
|
||||
|
||||
const showBtn = computed(() => windowY.value > 0)
|
||||
const backgroundColor = computed(
|
||||
() => getColorCodeFromData(props.color, 'none') ?? 'var(--primary)'
|
||||
() => getColorCodeFromData(props.backgroundColor, 'none') ?? 'var(--primary)'
|
||||
)
|
||||
|
||||
const handleScrollToTop = () => {
|
||||
|
||||
@@ -10,7 +10,7 @@ const props = defineProps<Props>()
|
||||
const { locale } = useI18n()
|
||||
const { sendLog } = useAnalytics()
|
||||
|
||||
const getArrowBgColor = (direction: 'prev' | 'next') => {
|
||||
const getArrowBackgroundColor = (direction: 'prev' | 'next') => {
|
||||
return (
|
||||
getColorCodeFromData(
|
||||
props.arrowsData?.[direction === 'prev' ? 0 : 1]?.display,
|
||||
@@ -32,7 +32,7 @@ const handleArrowClick = (direction: 'prev' | 'next') => {
|
||||
<AtomsButtonCircle
|
||||
sr-only="Previous"
|
||||
class="splide-arrow splide__arrow--prev"
|
||||
:background-color="getArrowBgColor('prev')"
|
||||
:background-color="getArrowBackgroundColor('prev')"
|
||||
@click="handleArrowClick('prev')"
|
||||
>
|
||||
<AtomsIconsArrowRightLine color="#ffffff" />
|
||||
@@ -40,7 +40,7 @@ const handleArrowClick = (direction: 'prev' | 'next') => {
|
||||
<AtomsButtonCircle
|
||||
sr-only="Next"
|
||||
class="splide-arrow splide__arrow--next"
|
||||
:background-color="getArrowBgColor('next')"
|
||||
:background-color="getArrowBackgroundColor('next')"
|
||||
@click="handleArrowClick('next')"
|
||||
>
|
||||
<AtomsIconsArrowRightLine color="#ffffff" />
|
||||
|
||||
@@ -59,9 +59,8 @@ const handleCopy = async () => {
|
||||
<template v-for="(item, key) in snsJson" :key="key">
|
||||
<AtomsButtonCircle
|
||||
v-if="item.use_yn === 1 && item.url"
|
||||
type="link"
|
||||
:to="item.url"
|
||||
target="_blank"
|
||||
type="external"
|
||||
:href="item.url"
|
||||
:class="['btn-sns', key]"
|
||||
:sr-only="key"
|
||||
@click="sendLog(locale, { ...analytics, click_item: key })"
|
||||
@@ -79,6 +78,7 @@ const handleCopy = async () => {
|
||||
</AtomsButtonCircle>
|
||||
</template>
|
||||
<AtomsButtonCircle
|
||||
type="action"
|
||||
class="btn-sns link"
|
||||
sr-only="copy"
|
||||
@click="handleCopy"
|
||||
|
||||
@@ -586,6 +586,9 @@ onMounted(() => {
|
||||
.btn-start:deep(.btn-base.default[data-variant='custom']) {
|
||||
@apply w-full h-[48px] px-10 font-[700] text-[16px];
|
||||
}
|
||||
.btn-start:deep(.btn-base.default[data-variant='custom']) .icon-platform {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.btn-start .nav-2depth {
|
||||
@apply left-[unset] right-[-40px];
|
||||
|
||||
@@ -106,7 +106,7 @@ onMounted(() => {
|
||||
<div v-if="isShowTopBtn" class="utile-wrap">
|
||||
<BlocksButtonScrollTop
|
||||
v-if="isShowTopBtn"
|
||||
:color="pageData?.top_btn_color_json"
|
||||
:background-color="pageData?.top_btn_color_json"
|
||||
/>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
|
||||
@@ -5,6 +5,37 @@ import type {
|
||||
} from '#layers/types/api/pageData'
|
||||
import type { ButtonType } from '#layers/types/components/button'
|
||||
|
||||
/** 어드민 버튼 유형 (시스템 버튼 / 이미지 버튼) */
|
||||
const BUTTON_CATEGORY = {
|
||||
SYSTEM: 'SYSTEM', // 시스템 버튼
|
||||
IMAGE: 'IMAGE', // 이미지 버튼
|
||||
} as const
|
||||
|
||||
/** 어드민 버튼 타입 */
|
||||
const BUTTON_ACTION_TYPE = {
|
||||
URL: 'URL',
|
||||
RUN: 'RUN',
|
||||
POP: 'POP',
|
||||
DOWNLOAD: 'DOWNLOAD',
|
||||
ANCHOR: 'ANCHOR',
|
||||
MOV: 'MOV',
|
||||
DEACTIVE: 'DEACTIVE',
|
||||
} as const
|
||||
|
||||
const OS_TYPE = {
|
||||
PC: 1,
|
||||
} as const
|
||||
|
||||
const MARKET_TYPE = {
|
||||
PC: 'pc',
|
||||
GOOGLE_PLAY: 'google_play',
|
||||
APP_STORE: 'app_store',
|
||||
} as const
|
||||
|
||||
const LINK_TARGET = {
|
||||
BLANK: '_blank',
|
||||
} as const
|
||||
|
||||
interface Props {
|
||||
resourcesData: PageDataResourceGroup[]
|
||||
buttonSize?: string
|
||||
@@ -25,15 +56,35 @@ const buttonList = computed<PageDataResourceGroup[]>(
|
||||
() => props.resourcesData ?? []
|
||||
)
|
||||
|
||||
/** 버튼 유형이 '시스템 버튼'인지 확인 */
|
||||
const isSystemButton = (button: PageDataResourceGroup): boolean => {
|
||||
// [TODO] 어디민 개발 후 수정 필요
|
||||
return button.btn_info?.btn_category === BUTTON_CATEGORY.SYSTEM
|
||||
}
|
||||
|
||||
/** 버튼 타입이 '게임 실행'인지 확인 */
|
||||
const isLauncherButton = (button: PageDataResourceGroup): boolean => {
|
||||
return button.btn_info?.detail?.btn_type === BUTTON_ACTION_TYPE.RUN
|
||||
}
|
||||
|
||||
/** 버튼 타입이 '비활성화'인지 확인 */
|
||||
const isDisabled = (button: PageDataResourceGroup): boolean => {
|
||||
return button.btn_info?.detail?.btn_type === BUTTON_ACTION_TYPE.DEACTIVE
|
||||
}
|
||||
|
||||
const usesGameFont = (btnInfo?: PageDataResourceGroupBtnInfo): boolean => {
|
||||
return btnInfo?.use_game_font === 1
|
||||
}
|
||||
|
||||
const getButtonType = (btnInfo?: PageDataResourceGroupBtnInfo): ButtonType => {
|
||||
const btnType = btnInfo?.detail?.btn_type
|
||||
const target = btnInfo?.detail?.action?.link_target
|
||||
|
||||
if (btnType === 'URL' && target) {
|
||||
return target === '_blank' ? 'external' : 'internal'
|
||||
if (btnType === BUTTON_ACTION_TYPE.URL && target) {
|
||||
return target === LINK_TARGET.BLANK ? 'external' : 'internal'
|
||||
}
|
||||
|
||||
if (btnType === 'DOWNLOAD') return 'download'
|
||||
if (btnType === BUTTON_ACTION_TYPE.DOWNLOAD) return 'download'
|
||||
|
||||
return 'action'
|
||||
}
|
||||
@@ -44,19 +95,37 @@ const isRunButtonVisible = (btnInfo: PageDataResourceGroupBtnInfo): boolean => {
|
||||
const marketType = btnInfo?.detail?.market_type
|
||||
|
||||
switch (marketType) {
|
||||
case 'pc':
|
||||
case MARKET_TYPE.PC:
|
||||
return false
|
||||
case 'google_play':
|
||||
case MARKET_TYPE.GOOGLE_PLAY:
|
||||
return device.isAndroid
|
||||
case 'app_store':
|
||||
case MARKET_TYPE.APP_STORE:
|
||||
return device.isApple
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const downloadFile = async (url: string = '', osType: number = 0) => {
|
||||
if (osType === 1 && breakpoints.value?.isMobile) {
|
||||
const openPopupModal = (detail: Record<string, any>) => {
|
||||
modalStore.handleOpenContent({
|
||||
contentTitle: detail?.title,
|
||||
tabInfo: detail?.tab_info,
|
||||
})
|
||||
}
|
||||
|
||||
const scrollToAnchor = (detail: Record<string, any>) => {
|
||||
scrollStore.scrollToAnchor(detail?.page_ver_tmpl_name_en ?? '')
|
||||
}
|
||||
|
||||
const openYoutubeModal = (detail: Record<string, any>) => {
|
||||
modalStore.handleOpenYoutube({ youtubeUrl: detail?.url ?? '' })
|
||||
}
|
||||
|
||||
const downloadFile = async (detail: Record<string, any>) => {
|
||||
const url = detail?.file_path ?? ''
|
||||
const osType = detail?.os_type ?? 0
|
||||
|
||||
if (osType === OS_TYPE.PC && breakpoints.value?.isMobile) {
|
||||
modalStore.handleOpenAlert({ contentText: tm('Alert_Download_PC') })
|
||||
return
|
||||
}
|
||||
@@ -64,25 +133,22 @@ const downloadFile = async (url: string = '', osType: number = 0) => {
|
||||
const fileUrl = formatPathHost(url)
|
||||
|
||||
try {
|
||||
const res = await $fetch<Blob>(fileUrl, {
|
||||
const blob = await $fetch<Blob>(fileUrl, {
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
timeout: 10000,
|
||||
})
|
||||
|
||||
const blob = res
|
||||
const blobUrl = URL.createObjectURL(blob)
|
||||
const fileName = fileUrl.split('/').pop() ?? 'download'
|
||||
|
||||
const pathPart = fileUrl.split('/').pop() ?? 'download'
|
||||
const a = document.createElement('a')
|
||||
const anchor = document.createElement('a')
|
||||
anchor.href = blobUrl
|
||||
anchor.download = fileName
|
||||
document.body.appendChild(anchor)
|
||||
anchor.click()
|
||||
anchor.remove()
|
||||
|
||||
a.href = blobUrl
|
||||
a.download = pathPart
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
a.remove()
|
||||
|
||||
// 메모리 정리
|
||||
URL.revokeObjectURL(blobUrl)
|
||||
|
||||
modalStore.handleOpenAlert({ contentText: tm('Alert_Download_Success') })
|
||||
@@ -92,30 +158,24 @@ const downloadFile = async (url: string = '', osType: number = 0) => {
|
||||
}
|
||||
}
|
||||
|
||||
const buttonClickHandlers: Record<
|
||||
string,
|
||||
(detail: Record<string, any>) => void
|
||||
> = {
|
||||
[BUTTON_ACTION_TYPE.POP]: openPopupModal,
|
||||
[BUTTON_ACTION_TYPE.ANCHOR]: scrollToAnchor,
|
||||
[BUTTON_ACTION_TYPE.MOV]: openYoutubeModal,
|
||||
[BUTTON_ACTION_TYPE.DOWNLOAD]: downloadFile,
|
||||
}
|
||||
|
||||
const handleButtonClick = (button: PageDataResourceGroup) => {
|
||||
sendLog(locale.value, button.tracking)
|
||||
|
||||
const btnDetail = button.btn_info?.detail
|
||||
const btnType = btnDetail?.btn_type
|
||||
|
||||
switch (btnDetail?.btn_type) {
|
||||
case 'POP':
|
||||
modalStore.handleOpenContent({
|
||||
contentTitle: btnDetail?.title,
|
||||
tabInfo: btnDetail?.tab_info,
|
||||
})
|
||||
return
|
||||
case 'ANCHOR':
|
||||
scrollStore.scrollToAnchor(btnDetail?.page_ver_tmpl_name_en ?? '')
|
||||
return
|
||||
case 'MOV':
|
||||
modalStore.handleOpenYoutube({ youtubeUrl: btnDetail.url ?? '' })
|
||||
return
|
||||
case 'DOWNLOAD':
|
||||
downloadFile(btnDetail?.file_path, btnDetail?.os_type)
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
const handler = buttonClickHandlers[btnType]
|
||||
handler?.(btnDetail)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -126,35 +186,46 @@ const handleButtonClick = (button: PageDataResourceGroup) => {
|
||||
class="flex flex-wrap justify-center items-center gap-3 md:gap-4"
|
||||
>
|
||||
<template v-for="(button, index) in buttonList" :key="index">
|
||||
<template v-if="button.btn_info?.detail?.btn_type === 'RUN'">
|
||||
<BlocksButtonLauncher
|
||||
v-if="isRunButtonVisible(button.btn_info)"
|
||||
type="duplication"
|
||||
:platform="button.btn_info?.detail?.market_type"
|
||||
:background-color="getColorCodeFromData(button.btn_info, 'btn')"
|
||||
:text-color="getColorCodeFromData(button.btn_info, 'txt')"
|
||||
:use-game-font="button.btn_info?.use_game_font === 1"
|
||||
@click="handleButtonClick(button)"
|
||||
>
|
||||
{{ button.btn_info?.txt_btn_name }}
|
||||
</BlocksButtonLauncher>
|
||||
</template>
|
||||
|
||||
<!-- 버튼 유형: 시스템 버튼 -->
|
||||
<AtomsButton
|
||||
v-else
|
||||
v-if="isSystemButton(button)"
|
||||
:type="getButtonType(button.btn_info)"
|
||||
:size="buttonSize"
|
||||
:href="button.btn_info?.detail?.action?.url"
|
||||
:target="button.btn_info?.detail?.action?.link_target"
|
||||
:background-color="getColorCodeFromData(button.btn_info, 'btn')"
|
||||
:text-color="getColorCodeFromData(button.btn_info, 'txt')"
|
||||
:disabled="button.btn_info?.detail?.btn_type === 'DEACTIVE'"
|
||||
:gradient="true"
|
||||
:use-game-font="button.btn_info?.use_game_font === 1"
|
||||
:disabled="isDisabled(button)"
|
||||
:use-gradient="true"
|
||||
:use-game-font="usesGameFont(button.btn_info)"
|
||||
@click="handleButtonClick(button)"
|
||||
>
|
||||
{{ button.btn_info?.txt_btn_name }}
|
||||
</AtomsButton>
|
||||
|
||||
<!-- 버튼 유형: 이미지 버튼 + 타입: 게임 실행 -->
|
||||
<BlocksButtonLauncher
|
||||
v-else-if="
|
||||
isLauncherButton(button) && isRunButtonVisible(button.btn_info!)
|
||||
"
|
||||
type="duplication"
|
||||
:platform="button.btn_info?.detail?.market_type"
|
||||
:background-color="getColorCodeFromData(button.btn_info, 'btn')"
|
||||
:text-color="getColorCodeFromData(button.btn_info, 'txt')"
|
||||
@click="handleButtonClick(button)"
|
||||
>
|
||||
{{ button.btn_info?.txt_btn_name }}
|
||||
</BlocksButtonLauncher>
|
||||
|
||||
<!-- 버튼 유형: 이미지 버튼 + 타입: 기타 -->
|
||||
<!-- [TODO] api 개발 후 수정 필요 (background-image, alt 에 연결 필요) -->
|
||||
<AtomsButtonImage
|
||||
v-else-if="!isSystemButton(button) && !isLauncherButton(button)"
|
||||
:href="button.btn_info?.detail?.action?.url"
|
||||
:background-image="'/images/test.png'"
|
||||
alt="test"
|
||||
:disabled="isDisabled(button)"
|
||||
@click="handleButtonClick(button)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { PageDataResourceGroup } from '#layers/types/api/pageData'
|
||||
|
||||
interface Props {
|
||||
category?: 'system' | 'image'
|
||||
variant?: 'videoPlay' | 'videoPlayImg'
|
||||
resourcesData: PageDataResourceGroup
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ const handleVideoPlayClick = () => {
|
||||
<template>
|
||||
<AtomsButtonPlay
|
||||
v-motion-stagger
|
||||
:category="props.category"
|
||||
:variant="props.variant"
|
||||
:background-color="backgroundColor"
|
||||
:tracking="props.resourcesData.tracking"
|
||||
@click="handleVideoPlayClick"
|
||||
|
||||
@@ -444,12 +444,11 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
|
||||
</p>
|
||||
|
||||
<AtomsButton
|
||||
type="link"
|
||||
type="external"
|
||||
size="size-small"
|
||||
background-color="#383838"
|
||||
text-color="#FFFFFF"
|
||||
class="w-full px-0"
|
||||
target="_blank"
|
||||
class="btn-download"
|
||||
rel="noopener noreferrer"
|
||||
:href="tm(`Download_Driver_${driver.driverCode}_Url`)"
|
||||
@click="handleSendLog(`다운로드_${driver.driverText}`)"
|
||||
@@ -527,12 +526,10 @@ table td {
|
||||
md:h-[80px] md:py-[14px] md:px-[20px] md:text-[16px] md:leading-[26px] md:tracking-[-0.48px];
|
||||
}
|
||||
|
||||
/* 플랫폼별 다운로드 Mobile Overflow Visible 처리 */
|
||||
.splide :deep(.splide__track) {
|
||||
overflow: visible !important;
|
||||
.btn-download {
|
||||
@apply w-full px-0;
|
||||
}
|
||||
|
||||
::v-deep([data-platform='stove']) .icon-platform {
|
||||
display: none !important;
|
||||
.btn-download:deep(.icon-external) {
|
||||
@apply hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -20,7 +20,7 @@ const descriptionData = computed(() =>
|
||||
getComponentGroup(props.components, 'description')
|
||||
)
|
||||
const videoPlayData = computed(() =>
|
||||
getComponentGroup(props.components, 'videoPlay')
|
||||
getComponentGroup(props.components, 'videoPlayImg')
|
||||
)
|
||||
// [TODO] api 수정 후 사용
|
||||
// const videoPlayData = computed(() =>
|
||||
@@ -51,7 +51,7 @@ const buttonListData = computed(() =>
|
||||
/>
|
||||
<WidgetsVideoPlay
|
||||
v-if="videoPlayData"
|
||||
category="image"
|
||||
variant="videoPlayImg"
|
||||
:resources-data="videoPlayData"
|
||||
/>
|
||||
<WidgetsButtonList
|
||||
|
||||
@@ -105,7 +105,7 @@ const slideItemSize = {
|
||||
/>
|
||||
<WidgetsVideoPlay
|
||||
v-if="videoPlayData"
|
||||
category="image"
|
||||
variant="videoPlayImg"
|
||||
:resources-data="videoPlayData"
|
||||
/>
|
||||
<WidgetsSlideCenterHighlight
|
||||
|
||||
@@ -86,6 +86,7 @@ export interface PageDataResourceGroupResPath {
|
||||
}
|
||||
|
||||
export interface PageDataResourceGroupBtnInfo extends ColorObject {
|
||||
btn_category: string
|
||||
txt_btn_name: string
|
||||
detail: Record<string, any>
|
||||
disabled?: boolean
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
export type ButtonType =
|
||||
| 'internal'
|
||||
| 'external'
|
||||
| 'download'
|
||||
| 'action'
|
||||
| 'link'
|
||||
export type ButtonType = 'internal' | 'external' | 'download' | 'action'
|
||||
|
||||
export type DownloadButtonType = 'default' | 'single' | 'duplication'
|
||||
export type ImageButtonType = 'internal' | 'external' | 'action'
|
||||
|
||||
export type LauncherButtonType = 'default' | 'duplication' | 'single'
|
||||
|
||||
export type ButtonSize = 'large' | 'medium' | 'small' | 'extra-small'
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 582 KiB After Width: | Height: | Size: 7.4 KiB |
BIN
public/images/common/btn_play/btn_default_m.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 23 KiB |
BIN
public/images/common/btn_play/btn_hover_m.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
public/images/test.png
Normal file
|
After Width: | Height: | Size: 15 KiB |