fix: 게임 시작 버튼에 OS 및 지원 플랫폼 검사 로직 추가 (미지원 시 얼럿 표시)

This commit is contained in:
clkim
2025-11-12 11:11:34 +09:00
parent badb6f3b0e
commit a29e0a5583
8 changed files with 167 additions and 64 deletions

View File

@@ -19,4 +19,8 @@
.content-text {
@apply text-center text-[15px] text-[#333333] leading-6 tracking-[-0.45px];
}
.content-text .highlight {
@apply text-[#FC4420];
}
}

View File

@@ -45,6 +45,6 @@ const componentProps = computed(() => {
w-[40px] h-[40px] md:w-[48px] md:h-[48px]
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.06)] before:rounded-full
after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:bg-white after:rounded-full after:opacity-0 after:transition-all after:duration-300 after:ease-in-out
hover:after:opacity-10;
hover:after:opacity-10;
}
</style>

View File

@@ -6,12 +6,17 @@ import type {
GameDataMenuChildren,
GameDataResourceGroup,
GameDataResourceGroupSet,
PlatformTransformType,
} from '#layers/types/api/gameData'
const route = useRoute()
const { tm } = useI18n()
const { width } = useWindowSize()
const device = useDevice()
const gameDataStore = useGameDataStore()
const scrollStore = useScrollStore()
const breakpoints = useResponsiveBreakpoints()
const modalStore = useModalStore()
const { gameData } = storeToRefs(gameDataStore)
const { isPassedStoveGnb } = storeToRefs(scrollStore)
@@ -33,6 +38,12 @@ const gnb2depthButtonData = computed(
() => gnbData?.buttons[1]?.button_json as GameDataResourceGroupSet
)
const currentPath = computed(() => formatPathWithoutLocale(route.path))
const supportedPlatforms = computed(
() =>
getSupportedPlatforms(gameData.value?.os_type, {
platformType: gameData.value?.platform_type,
}) as PlatformTransformType[]
)
const pathMatches = (base: string, current: string) => {
if (!base || base === '/') return current === '/'
@@ -151,6 +162,57 @@ const has2depthButton = (gnbItem: GameDataMenu) => {
return gnbItem.children && Object.keys(gnbItem.children).length > 0
}
const highlight = (text: string) => `<span class="highlight">${text}</span>`
const PLATFORM_LABEL_KEY: Record<PlatformTransformType, string> = {
pc: 'PC',
google_play: 'Google Play',
app_store: 'App Store',
}
const tmWithGameName = (key: string): string => {
const raw = tm(key)
if (typeof raw !== 'string') return ''
const withName = raw.replace(
/%게임명%/g,
highlight(gameData.value?.game_name || '')
)
const platformLines = supportedPlatforms.value
.map(platform => highlight(PLATFORM_LABEL_KEY[platform] as string))
.filter(Boolean)
return platformLines.length
? `${withName}<br><br>${platformLines.join('<br>')}`
: withName
}
const showNotSupportedOSAlert = () => {
return modalStore.handleOpenAlert({
contentText: tmWithGameName('Alert_Not_SupportedOS'),
})
}
const handleStartClick = () => {
if (breakpoints.value.isDesktop) return
const target = device.isAndroid
? 'google_play'
: device.isApple
? 'app_store'
: null
if (!target || !supportedPlatforms.value.includes(target)) {
return showNotSupportedOSAlert()
}
const url = gameData.value?.market_json?.[target]?.url || ''
if (!url) return showNotSupportedOSAlert()
window.open(url, '_blank')
}
const stopClickOutside = onClickOutside(navAreaRef, () => handleMenuClose())
// 화면 크기 변경 시 오버플로우 재계산
@@ -319,33 +381,37 @@ onBeforeUnmount(() => {
</div>
</nav>
<div ref="startRef" class="btn-start">
<BlocksButtonLauncher
type="custom"
platform="pc"
:background-color="
getColorCode({
colorName: gnb1depthButtonData?.btn_info?.color_name_btn,
colorCode: gnb1depthButtonData?.btn_info?.color_code_btn,
})
"
:text-color="
getColorCode({
colorName: gnb1depthButtonData?.btn_info?.color_name_txt,
colorCode: gnb1depthButtonData?.btn_info?.color_code_txt,
})
"
>
{{ gnb1depthButtonData?.btn_info?.txt_btn_name }}
</BlocksButtonLauncher>
<div v-if="gnb2depthButtonData" class="nav-2depth hidden md:block">
<ul>
<li v-for="(item, key) in gnb2depthButtonData" :key="key">
<BlocksButtonLauncher type="custom" :platform="key">
{{ item.btn_info?.txt_btn_name }}
</BlocksButtonLauncher>
</li>
</ul>
</div>
<ClientOnly>
<component
:is="
breakpoints.isDesktop ? 'BlocksButtonLauncher' : 'AtomsButton'
"
type="custom"
platform="pc"
:background-color="
getColorCodeFromData(gnb1depthButtonData?.btn_info, 'btn')
"
:text-color="
getColorCodeFromData(gnb1depthButtonData?.btn_info, 'txt')
"
@click="handleStartClick"
>
{{ gnb1depthButtonData?.btn_info?.txt_btn_name }}
</component>
<div
v-if="breakpoints.isDesktop && gnb2depthButtonData"
class="nav-2depth"
>
<ul>
<li v-for="(item, key) in gnb2depthButtonData" :key="key">
<BlocksButtonLauncher type="custom" :platform="key">
{{ item.btn_info?.txt_btn_name }}
</BlocksButtonLauncher>
</li>
</ul>
</div>
</ClientOnly>
</div>
<button class="btn-close" @click="handleMenuClose">
<AtomsIconsCloseLine

View File

@@ -24,18 +24,6 @@ const getBtnType = (item?: PageDataResourceGroupBtnInfo): ButtonType => {
return 'action'
}
const getBgColor = (item?: PageDataResourceGroupBtnInfo): string =>
getColorCode({
colorName: item?.color_name_btn,
colorCode: item?.color_code_btn,
})
const getTextColor = (item?: PageDataResourceGroupBtnInfo): string =>
getColorCode({
colorName: item?.color_name_txt,
colorCode: item?.color_code_txt,
})
const handleLogClick = (button: PageDataResourceGroup) => {
sendLog(locale.value, useAnalyticsLogDataDirect(button, props.pageVerTmplSeq))
if (button.btn_info?.detail?.btn_type === 'POP') {
@@ -62,8 +50,8 @@ const buttonList = computed(() => props.resourcesData || [])
v-if="button.btn_info?.detail?.btn_type === 'RUN'"
type="duplication"
:platform="button.btn_info?.detail?.market_type"
:background-color="getBgColor(button.btn_info)"
:text-color="getTextColor(button.btn_info)"
:background-color="getColorCodeFromData(button.btn_info, 'btn')"
:text-color="getColorCodeFromData(button.btn_info, 'txt')"
:disabled="button?.btn_info?.disabled"
@click="handleLogClick(button)"
>
@@ -75,8 +63,8 @@ const buttonList = computed(() => props.resourcesData || [])
:href="button.btn_info?.detail?.action?.url"
:target="button.btn_info?.detail?.action?.link_target"
:rel="button.btn_info?.detail?.action?.rel"
:background-color="getBgColor(button.btn_info)"
:text-color="getTextColor(button.btn_info)"
:background-color="getColorCodeFromData(button.btn_info, 'btn')"
:text-color="getColorCodeFromData(button.btn_info, 'txt')"
:disabled="button?.btn_info?.disabled"
@click="handleLogClick(button)"
>

View File

@@ -33,11 +33,6 @@ const { pageData } = storeToRefs(usePageDataStore())
// Constants
const COLOR_INDEX = { BACKGROUND: 0, TEXT: 1 } as const
const OS_TYPE_MAP: Record<string, Platform[]> = {
'1': ['google_play'],
'2': ['app_store'],
'3': ['google_play', 'app_store'],
}
const preregistModalRef = ref<{
handleOpenPreregist: () => Promise<void>
@@ -101,12 +96,6 @@ const buttonColors = computed(() => {
}
})
// Platform Buttons
const platformButtons = computed<Platform[]>(() => {
const osType = String(gameData.value?.os_type ?? '')
return OS_TYPE_MAP[osType] ?? []
})
// Reward Section
const accBackgroundData = computed(() =>
getComponentGroup(props.components, 'backgroundAccReward')
@@ -280,10 +269,10 @@ const handlePreregistClick = () => {
{{ tm('Preregist_Btn_Preegist') }}
</BlocksButtonLauncher>
<BlocksButtonLauncher
v-for="platform in platformButtons"
v-for="platform in getSupportedPlatforms(gameData?.os_type)"
:key="`preregist-${platform}`"
type="duplication"
:platform="platform"
:platform="platform as Platform"
:background-color="buttonColors.backgroundColor"
:text-color="buttonColors.textColor"
>

View File

@@ -52,12 +52,16 @@ export interface GameDataValue {
comm_img_json: GameDataCommImg
market_json: Record<string, { url: string }>
event_banner: GameDataEventBanner
os_type: string // 1:AOS, 2:IOS, 3:둘다
platform_type: string // 1:PC, 2:MOBILE, 3:둘다
os_type: OsType
platform_type: PlatformType
}
// ===== 세부 데이터 타입들 =====
export type OsType = '1' | '2' | '3' // 1:AOS, 2:IOS, 3:둘다
export type PlatformType = '1' | '2' | '3' // 1:PC, 2:MOBILE, 3:둘다
export type PlatformTransformType = 'pc' | 'google_play' | 'app_store'
// 키 코드 코드 타입
export interface GameDataKeyColors {
primary: string

View File

@@ -3,6 +3,7 @@
* @description gameData, pageData 처리에 필요한 유틸리티 함수를 제공합니다.
*/
import type { PlatformType } from '#layers/types/api/gameData'
import type {
PageDataValue,
PageDataResourceContainer,
@@ -12,6 +13,37 @@ import type {
} from '#layers/types/api/pageData'
import type { OperateComponents } from '#layers/types/api/operateResources'
const OS_TYPE_MAP: Record<string, string[]> = {
'1': ['google_play'],
'2': ['app_store'],
'3': ['google_play', 'app_store'],
}
/**
* OS 타입에 따라 가능한 플랫폼 목록을 반환합니다.
*/
export const getSupportedPlatforms = (
osType: string | number,
options?: { platformType?: PlatformType }
): string[] => {
const type = String(osType)
const platformType = String(options?.platformType ?? '0')
const storePlatforms = OS_TYPE_MAP[type] ?? []
switch (platformType) {
case '1': // PC 전용
return ['pc']
case '2': // 모바일 스토어 전용
return storePlatforms
case '3': // PC + 모바일 스토어 모두
return ['pc', ...storePlatforms]
default: // 기본: OS_TYPE_MAP
return storePlatforms
}
}
/**
* 페이지 데이터를 기반으로 레이아웃 타입을 결정합니다.
* @param pageData 페이지 데이터

View File

@@ -4,10 +4,12 @@
*/
import { isTypeVideo } from '#layers/utils/dataUtil'
import type { GameDataResourceGroupBtnInfo } from '#layers/types/api/gameData'
import type {
PageDataResourceGroups,
PageDataResourceGroup,
PageDataResourceGroupResPath,
PageDataResourceGroupBtnInfo,
} from '#layers/types/api/pageData'
/**
@@ -92,11 +94,29 @@ export const getColorCode = ({
colorName: string
colorCode: string
}) => {
if (colorName) {
return `var(--${colorName})`
} else if (colorCode) {
return colorCode
}
if (colorName) return `var(--${colorName})`
else if (colorCode) return colorCode
}
/**
* 색상데이터를 받아 색상값을 반환합니다.
* @param colorData 색상 데이터
* @param type 색상 타입 (btn: 버튼, txt: 텍스트)
* @returns 색상 값
*/
export const getColorCodeFromData = (
data: GameDataResourceGroupBtnInfo | PageDataResourceGroupBtnInfo,
type: 'btn' | 'txt' = 'txt'
) => {
const suffix = type === 'btn' ? '_btn' : '_txt'
const colorName = data?.[`color_name${suffix}` as keyof typeof data]
const colorCode = data?.[`color_code${suffix}` as keyof typeof data]
return getColorCode({
colorName: colorName as string | undefined,
colorCode: colorCode as string | undefined,
})
}
/**