feat.[SWV-812] 공통 리소스 이미지 관리 방식 변경(videoPlay, Arrow)

This commit is contained in:
clkim
2026-01-22 14:14:59 +09:00
parent 927f7ace3b
commit 37fdd251bd
32 changed files with 139 additions and 84 deletions

View File

@@ -96,7 +96,7 @@ const setupGameHead = (data: GameDataValue) => {
const designTheme = data.design_theme === 1 ? 'light' : 'dark'
const styleLinks = createStyleLinks(
data.favicon_json,
data?.game_font?.font_path
data?.game_font_key_json?.font_path
)
const styleCss = createStyleCss(data.key_color_json)

View File

@@ -38,8 +38,4 @@
.type-full .splide__arrow--next {
@apply right-10;
}
.splide-arrow svg {
@apply hidden;
}
}

View File

@@ -2,6 +2,7 @@
import type { TrackingObject } from '#layers/types/api/common'
interface Props {
bgColor?: string
tracking: TrackingObject
}
@@ -16,14 +17,20 @@ const handlePlayClick = () => {
</script>
<template>
<button class="btn-play" @click="handlePlayClick">
<button
class="btn-play"
:style="{ backgroundColor: props.bgColor }"
@click="handlePlayClick"
>
<AtomsIconsArrowRightFill />
<span class="sr-only">Play</span>
</button>
</template>
<style scoped>
.btn-play {
@apply relative w-[60px] h-[60px] bg-[image:var(--video-play)] bg-cover bg-center bg-no-repeat md:w-[80px] md:h-[80px]
@apply relative flex items-center justify-center rounded-full w-[60px] h-[60px] md:w-[80px] md:h-[80px]
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;
}

View File

@@ -50,11 +50,9 @@ const onSelectOption = (option: { [key: string | number]: any }): void => {
>
{{ selectedOption }}
</span>
<i
class="inline-flex items-center justify-center w-[14px] h-[14px] shrink-0"
>
<AtomsIconsSelectArrowDownFill
:size="12"
<i class="inline-flex items-center justify-center shrink-0">
<AtomsIconsArrowDownFill
:size="14"
color="#333333"
:class="isActive ? 'rotate-180' : ''"
/>

View File

@@ -0,0 +1,31 @@
<script setup lang="ts">
interface Props {
size?: number | string
color?: string
fillOpacity?: number
}
withDefaults(defineProps<Props>(), {
size: 24,
color: '#ffffff',
fillOpacity: 0.5,
})
</script>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
:width="size"
:height="size"
viewBox="0 0 24 24"
:fill="color"
:fill-opacity="fillOpacity"
>
<path
d="M13 9.41422L17.2929 13.7071C17.6834 14.0976 18.3166 14.0976 18.7071 13.7071C19.0976 13.3166 19.0976 12.6834 18.7071 12.2929L12.7078 6.29361C12.5289 6.1143 12.2822 6.00257 12.0094 6.00005C12.0063 6.00002 12.0032 6.00001 12 6.00001C11.9968 6.00001 11.9937 6.00002 11.9906 6.00005C11.7269 6.00249 11.4877 6.10694 11.3104 6.27585C11.3045 6.28145 11.2987 6.28714 11.2929 6.2929L5.29289 12.2929C4.90237 12.6834 4.90237 13.3166 5.29289 13.7071C5.68341 14.0976 6.31658 14.0976 6.7071 13.7071L11 9.41422L11 20C11 20.5523 11.4477 21 12 21C12.5523 21 13 20.5523 13 20L13 9.41422Z"
/>
<path
d="M19.5 4.00001C19.5 4.55229 19.0523 5 18.5 5L5.5 5C4.94771 5 4.5 4.55228 4.5 4C4.5 3.44772 4.94771 3 5.5 3L18.5 3C19.0523 3 19.5 3.44772 19.5 4.00001Z"
/>
</svg>
</template>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
interface Props {
size?: number | string
color?: string
fillOpacity?: number
}
withDefaults(defineProps<Props>(), {
size: 32,
color: '#ffffff',
fillOpacity: 0.5,
})
</script>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
:width="size"
:height="size"
viewBox="0 0 32 32"
:fill="color"
:fill-opacity="fillOpacity"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14.4839 25.0812C12.7639 26.0629 11.079 26.9852 9.42912 27.8405C8.49538 28.3239 7.40719 27.6545 7.33699 26.5612C7.21062 24.62 7.11935 22.6119 7.06318 20.5443C7.02106 19.0568 7 17.5396 7 16C7 14.4604 7.02106 12.9506 7.06318 11.4557C7.11935 9.38809 7.21062 7.37997 7.33699 5.43879C7.40719 4.34548 8.49538 3.6761 9.42912 4.15954C11.079 5.01485 12.7709 5.9371 14.4839 6.91884C15.7196 7.6254 16.9692 8.36171 18.2259 9.13521C19.4826 9.90871 20.7112 10.6822 21.9047 11.4631C23.5686 12.549 25.1622 13.6349 26.6857 14.7208C27.5492 15.3306 27.5492 16.6694 26.6857 17.2793C25.1622 18.3651 23.5615 19.4584 21.9047 20.5369C20.7112 21.3178 19.4826 22.0987 18.2259 22.8648C16.9692 23.6383 15.7196 24.3672 14.4839 25.0812Z"
/>
</svg>
</template>

View File

@@ -1,26 +0,0 @@
<script setup lang="ts">
interface Props {
size?: number | string
color?: string
}
withDefaults(defineProps<Props>(), {
size: 32,
color: '#EBEBEB',
})
</script>
<template>
<svg
:width="size"
:height="size"
viewBox="0 0 10 6"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.06454 4.95333L0.204544 1.16667C-0.228789 0.74 0.0712106 0 0.684544 0L9.31787 0C9.9312 0 10.2312 0.74 9.79787 1.16667L5.93787 4.95333C5.41787 5.46 4.59121 5.46 4.07121 4.95333H4.06454Z"
:fill="color"
/>
</svg>
</template>

View File

@@ -42,10 +42,10 @@ const PLATFORM_ICON_MAP: Record<Platform, string> = {
} as const
const DUP_IMAGE_MAP: Record<Platform, string> = {
google_play: '/images/common/btn_logo-google.svg',
app_store: '/images/common/btn_logo-app.svg',
pc: '/images/common/btn_logo-pc.svg',
stove: '/images/common/btn_logo-stove.svg',
google_play: '/images/common/btn_launcher/btn_logo-google.svg',
app_store: '/images/common/btn_launcher/btn_logo-app.svg',
pc: '/images/common/btn_launcher/btn_logo-pc.svg',
stove: '/images/common/btn_launcher/btn_logo-stove.svg',
} as const
const componentTag = computed(() => {

View File

@@ -26,7 +26,9 @@ const handleScrollToTop = () => {
class="btn-top"
sr-only="top"
@click="handleScrollToTop"
/>
>
<AtomsIconsArrowControlTopLine />
</AtomsButtonCircle>
</Transition>
</template>

View File

@@ -10,6 +10,13 @@ const props = defineProps<Props>()
const { locale } = useI18n()
const { sendLog } = useAnalytics()
const getArrowBgColor = (direction: 'prev' | 'next') => {
return getColorCodeFromData(
props.arrowsData?.[direction === 'prev' ? 0 : 1]?.display,
'none'
)
}
const handleArrowClick = (direction: 'prev' | 'next') => {
if (props.arrowsData) {
const arrowIndex = direction === 'prev' ? 0 : 1
@@ -23,12 +30,18 @@ const handleArrowClick = (direction: 'prev' | 'next') => {
<AtomsButtonCircle
sr-only="Previous"
class="splide-arrow splide__arrow--prev"
:style="{ backgroundColor: getArrowBgColor('prev') }"
@click="handleArrowClick('prev')"
/>
>
<AtomsIconsArrowRightLine color="#ffffff" />
</AtomsButtonCircle>
<AtomsButtonCircle
sr-only="Next"
class="splide-arrow splide__arrow--next"
:style="{ backgroundColor: getArrowBgColor('next') }"
@click="handleArrowClick('next')"
/>
>
<AtomsIconsArrowRightLine color="#ffffff" />
</AtomsButtonCircle>
</div>
</template>

View File

@@ -436,7 +436,7 @@ onMounted(() => {
>
{{ t('Text_MonthYear', { month: month + 1, year: year }) }}
</span>
<AtomsIconsSelectArrowDownFill :size="10" color="#333333" />
<AtomsIconsArrowDownFill :size="16" color="#333333" />
</button>
<button
type="button"

View File

@@ -105,7 +105,7 @@ const handlePagination = (page: number) => {
:class="[
'!w-full !h-full p-0 rounded-full text-center text-[14px] font-[500] leading-[24px] tracking-[-0.42px]',
page === currentPage
? '!bg-[#C7AE8B] !text-white cursor-default'
? '!bg-[var(--primary)] !text-white cursor-default'
: '',
]"
@click="handlePagination(page)"

View File

@@ -7,6 +7,10 @@ const props = defineProps<{
const modalStore = useModalStore()
const bgColor = computed(() => {
return getColorCodeFromData(props.resourcesData.display, 'none')
})
// 비디오 플레이 버튼 클릭 핸들러
const handleVideoPlayClick = () => {
const youtubeUrl = props.resourcesData?.display?.text ?? ''
@@ -18,6 +22,7 @@ const handleVideoPlayClick = () => {
<template>
<AtomsButtonPlay
:bg-color="bgColor"
:tracking="props.resourcesData.tracking"
@click="handleVideoPlayClick"
/>

View File

@@ -163,7 +163,7 @@ export default defineEventHandler(async event => {
event.context.gameDomain = gameDomain
} catch (error) {
// eslint-disable-next-line no-console
console.error('gameData load error:', error)
console.error('[API Error] gameData load error:', error)
}
if (gameDataResponse?.code === 0 && 'value' in gameDataResponse) {
@@ -344,6 +344,10 @@ export default defineEventHandler(async event => {
}
} else {
// ### 에러 응답 처리 -------------------------------------------------------------
// API 에러 코드를 명확하게 로깅하여 타입 에러와 구분
const apiErrorCode = gameDataResponse?.code
const apiErrorMessage = gameDataResponse?.message
// 언어 코드 추출 시도
let errorLocale = 'ko' // 기본값
try {
@@ -359,7 +363,7 @@ export default defineEventHandler(async event => {
}
// 91001 에러인 경우 바로 리다이렉트
if (gameDataResponse?.code === 91001) {
if (apiErrorCode === 91001) {
const errorPath = `/${errorLocale}/error`
event.node.res.statusCode = 302
event.node.res.setHeader('Location', errorPath)
@@ -369,8 +373,8 @@ export default defineEventHandler(async event => {
// 다른 에러는 기존대로 throw
throw createError({
statusCode: gameDataResponse?.code || 500,
statusMessage: gameDataResponse?.message,
statusCode: apiErrorCode || 500,
statusMessage: apiErrorMessage,
})
}
})

View File

@@ -18,7 +18,9 @@ export const useGameDataStore = defineStore('gameData', () => {
snsJson: null as GameDataValue['sns_json'] | null,
urlJson: null as GameDataValue['url_json'] | null,
marketJson: null as GameDataValue['market_json'] | null,
fontFamily: null as GameDataValue['game_font']['font_family'] | null,
fontFamily: null as
| GameDataValue['game_font_key_json']['font_family']
| null,
gnb: null as GameDataValue['gnb'] | null,
eventBanner: null as GameDataValue['event_banner'] | null,
})
@@ -42,7 +44,7 @@ export const useGameDataStore = defineStore('gameData', () => {
state.snsJson = data?.sns_json
state.urlJson = data?.url_json
state.marketJson = data?.market_json
state.fontFamily = data?.game_font?.font_family
state.fontFamily = data?.game_font_key_json?.font_family
state.gnb = data?.gnb
state.eventBanner = data?.event_banner
}

View File

@@ -424,7 +424,7 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
<img
:src="
formatPathHost(
`/images/common/grades_driver/Type-${driver.driverCode}.svg`,
`/images/common/download_driver/Type-${driver.driverCode}.svg`,
{ imageType: 'common' }
)
"

View File

@@ -50,20 +50,15 @@ const analytics = {
action_type: 'click',
click_sarea: props.pageVerTmplNameEn,
} as TrackingObject
const arrowsData: PageDataResourceGroups = [
{
tracking: {
...analytics,
click_item: '1. 컨텐츠 리스트 - 네비게이션(좌)',
},
const arrowsData: PageDataResourceGroups = ['좌', '우'].map(direction => ({
display: {
color_code: '#383838',
},
{
tracking: {
...analytics,
click_item: '1. 컨텐츠 리스트 - 네비게이션(우)',
},
tracking: {
...analytics,
click_item: `1. 컨텐츠 리스트 - 네비게이션(${direction})`,
},
]
}))
const recommendHover = ref(false)
@@ -291,21 +286,19 @@ const handleLoadMoreRecent = () => {
.splide {
@apply pb-[68px] sm:pb-[0];
}
.splide:deep(.splide__arrows) {
@apply block;
}
.splide:deep(.splide__arrows) .splide-arrow {
@apply block top-[unset] bottom-[20px] translate-y-0 bg-cover bg-center bg-no-repeat
@apply top-[unset] bottom-[20px] translate-y-0 bg-cover bg-center bg-no-repeat
sm:bottom-[24px] md:bottom-[36px] lg:bottom-[60px];
}
.splide:deep(.splide__arrows) .splide__arrow--prev {
@apply left-[20px] bg-[image:url('/images/common/btn_system_arrow_prev.png')]
@apply left-[20px]
sm:left-[calc(60.3%+21px)]
md:left-[calc(56%+39px)]
lg:left-[790px];
}
.splide:deep(.splide__arrows) .splide__arrow--next {
@apply right-[20px] bg-[image:url('/images/common/btn_system_arrow_next.png')]
@apply right-[20px]
sm:right-[28px]
md:right-[unset] md:left-[calc(56%+99px)]
lg:left-[850px];

View File

@@ -40,6 +40,9 @@ const arrowsData = computed(() => {
const paginationData = computed(() => {
return getComponentGroupAry(props.components, 'pagination')
})
const videoPlayData = computed(() => {
return getComponentGroup(props.components, 'videoPlay')
})
const getVideoPlayTracking = (item: string) => {
return {
@@ -115,6 +118,7 @@ onBeforeUnmount(() => {
<AtomsButtonPlay
v-if="playingSlideIndex !== index"
class="btn-play"
:bg-color="getColorCodeFromData(videoPlayData?.display, 'none')"
:tracking="getVideoPlayTracking(item?.group_label)"
@click="handleVideoClick(index)"
/>

View File

@@ -36,7 +36,7 @@ export interface GameDataValue {
footer_dev_ci_img_yn: boolean
footer_dev_ci_img_path: string
default_lang_code?: string
game_font: GameDataGameFont
game_font_key_json?: GameDataGameFont
globals: GameDataGlobal[]
gnb: GameDataGnb
intro: GameDataIntro

View File

@@ -84,16 +84,18 @@ export interface PageDataResourceGroupResPath {
}
export interface PageDataResourceGroupBtnInfo extends ColorObject {
disabled: boolean
txt_btn_name: string
detail: Record<string, any>
use_game_font: 0 | 1
disabled?: boolean
use_game_font?: 0 | 1
}
// 리소스 그룹 타입
export interface PageDataResourceGroupDisplay extends ColorObject {
text: string
use_game_font: 0 | 1
text?: string
color_code?: string
color_name?: string
use_game_font?: 0 | 1
}
export interface PageDataResourceGroup {
@@ -127,7 +129,6 @@ export type PageDataTemplateComponents =
| PageDataTemplateComponent // 단일 컴포넌트 패턴
| {
group_sets: PageDataTemplateComponentSet[]
arrow: PageDataArrowComponent
} // 그룹 세트 패턴
// 템플릿 타입
@@ -172,8 +173,3 @@ export interface PageDataApiResult {
data: PageDataResponse | null
error: string | null
}
// Arrow 컴포넌트 타입
export type PageDataArrowComponent = PageDataTemplateComponent & {
groups: PageDataResourceGroups
}

View File

@@ -31,7 +31,7 @@ const getColorCode = ({
* @returns 색상 값
*/
export const getColorCodeFromData = (
data: ColorObject,
data: ColorObject | undefined,
type: 'btn' | 'txt' | 'none' = 'txt'
) => {
const suffixMap: Record<'btn' | 'txt' | 'none', string> = {

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB