fix. [QA] 액션 버튼 동작 이슈 수정 (PWT-53, PWT-67)
This commit is contained in:
@@ -6,7 +6,6 @@ interface props {
|
||||
buttonSize?: string
|
||||
target?: '_self' | '_blank'
|
||||
href?: string
|
||||
rel?: string
|
||||
backgroundColor?: string
|
||||
textColor?: string
|
||||
disabled?: boolean
|
||||
@@ -24,7 +23,6 @@ const props = withDefaults(defineProps<props>(), {
|
||||
const componentTag = computed((): string => {
|
||||
switch (props.type) {
|
||||
case 'external':
|
||||
case 'download':
|
||||
case 'link':
|
||||
return 'a'
|
||||
case 'internal':
|
||||
@@ -34,29 +32,20 @@ const componentTag = computed((): string => {
|
||||
}
|
||||
})
|
||||
const componentProps = computed(() => {
|
||||
const baseProps = { disabled: props.disabled }
|
||||
|
||||
if (
|
||||
props.type === 'external' ||
|
||||
props.type === 'download' ||
|
||||
props.type === 'link'
|
||||
) {
|
||||
if (props.type === 'external' || props.type === 'link') {
|
||||
return {
|
||||
...baseProps,
|
||||
href: props.href,
|
||||
target: props.target,
|
||||
rel: props.rel,
|
||||
}
|
||||
}
|
||||
|
||||
if (props.type === 'internal') {
|
||||
return {
|
||||
...baseProps,
|
||||
to: props.href,
|
||||
}
|
||||
}
|
||||
|
||||
return baseProps
|
||||
return {}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -64,12 +53,13 @@ const componentProps = computed(() => {
|
||||
<component
|
||||
:is="componentTag"
|
||||
v-bind="{ ...componentProps }"
|
||||
:class="['btn-base', props.buttonSize]"
|
||||
:class="['btn-base', props.buttonSize, { disabled: props.disabled }]"
|
||||
:style="{
|
||||
backgroundColor: props.backgroundColor,
|
||||
color: props.textColor,
|
||||
'--text-color': props.textColor,
|
||||
}"
|
||||
:disabled="props.disabled"
|
||||
>
|
||||
<span class="btn-content">
|
||||
<slot />
|
||||
@@ -101,12 +91,12 @@ const componentProps = computed(() => {
|
||||
.btn-base:hover {
|
||||
@apply after:opacity-20;
|
||||
}
|
||||
.btn-base:disabled {
|
||||
@apply cursor-default
|
||||
after:bg-[var(--text-color)] after:opacity-20 after:z-[2];
|
||||
.btn-base.disabled {
|
||||
@apply cursor-default pointer-events-none
|
||||
after:bg-[var(--text-color)] after:opacity-20 after:z-[2];
|
||||
}
|
||||
|
||||
.btn-base:disabled .btn-content {
|
||||
.btn-base.disabled .btn-content {
|
||||
@apply opacity-50;
|
||||
}
|
||||
.btn-base .btn-content {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import type { PageDataLnbMenu } from '#layers/types/api/pageData'
|
||||
|
||||
const { y: windowY, directions } = useWindowScroll({ behavior: 'smooth' })
|
||||
const { directions } = useWindowScroll({ behavior: 'smooth' })
|
||||
const pageDataStore = usePageDataStore()
|
||||
const scrollStore = useScrollStore()
|
||||
const breakpoints = useResponsiveBreakpoints()
|
||||
|
||||
const { pageData } = storeToRefs(pageDataStore)
|
||||
|
||||
// 상수 정의
|
||||
const HEADER_HEIGHT = 64
|
||||
const OBSERVER_OPTIONS = {
|
||||
root: null,
|
||||
rootMargin: '-20% 0px -60% 0px', // 상단 20%, 하단 60% 마진
|
||||
@@ -104,15 +104,7 @@ const handleLnbClick = (lnbItem: PageDataLnbMenu) => {
|
||||
? lnbItem?.children?.['1']?.page_ver_tmpl_name_en
|
||||
: lnbItem.page_ver_tmpl_name_en
|
||||
|
||||
const targetElement = document.getElementById(targetId)
|
||||
if (!targetElement) return
|
||||
|
||||
// 헤더 높이를 고려한 스크롤 위치 계산
|
||||
const elementTop = targetElement.getBoundingClientRect().top
|
||||
const currentScrollY = window.scrollY
|
||||
const targetScrollY = currentScrollY + elementTop - HEADER_HEIGHT
|
||||
|
||||
windowY.value = targetScrollY
|
||||
scrollStore.scrollToAnchor(targetId)
|
||||
}
|
||||
|
||||
watch(directions, newVal => {
|
||||
|
||||
@@ -9,33 +9,93 @@ interface Props {
|
||||
resourcesData: PageDataResourceGroup[]
|
||||
pageVerTmplSeq: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { locale } = useI18n()
|
||||
const modalStore = useModalStore()
|
||||
const scrollStore = useScrollStore()
|
||||
const breakpoints = useResponsiveBreakpoints()
|
||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||
const { tm } = useI18n()
|
||||
|
||||
const getBtnType = (item?: PageDataResourceGroupBtnInfo): ButtonType => {
|
||||
const type = item?.detail?.btn_type
|
||||
const target = item?.detail?.action?.link_target
|
||||
if (type === 'URL' && target)
|
||||
const buttonList = computed<PageDataResourceGroup[]>(
|
||||
() => props.resourcesData ?? []
|
||||
)
|
||||
|
||||
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 (type === 'DOWNLOAD') return 'download'
|
||||
}
|
||||
|
||||
if (btnType === 'DOWNLOAD') return 'download'
|
||||
|
||||
return 'action'
|
||||
}
|
||||
|
||||
const handleLogClick = (button: PageDataResourceGroup) => {
|
||||
sendLog(locale.value, useAnalyticsLogDataDirect(button, props.pageVerTmplSeq))
|
||||
if (button.btn_info?.detail?.btn_type === 'POP') {
|
||||
modalStore.handleOpenContent({
|
||||
contentTitle: button.btn_info?.detail?.title,
|
||||
tabInfo: button.btn_info?.detail?.tab_info,
|
||||
const downloadZip = async (url: string, osType: number) => {
|
||||
if (osType === 1 && breakpoints.value?.isMobile) {
|
||||
modalStore.handleOpenAlert({ contentText: tm('Alert_Download_PC') })
|
||||
return
|
||||
}
|
||||
|
||||
const fileUrl = getResourceHost(url)
|
||||
|
||||
try {
|
||||
const res = await $fetch(fileUrl, {
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
timeout: 10000,
|
||||
})
|
||||
|
||||
const blob = new Blob([res as BlobPart], { type: 'application/zip' })
|
||||
const blobUrl = URL.createObjectURL(blob)
|
||||
const pathPart = fileUrl.split('/').pop()
|
||||
const a = document.createElement('a')
|
||||
|
||||
a.href = blobUrl
|
||||
a.download = pathPart ?? 'download.zip'
|
||||
a.click()
|
||||
|
||||
// 메모리 정리
|
||||
URL.revokeObjectURL(blobUrl)
|
||||
|
||||
modalStore.handleOpenAlert({ contentText: tm('Alert_Download_Success') })
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
modalStore.handleOpenAlert({ contentText: tm('Alert_Download_Error') })
|
||||
}
|
||||
}
|
||||
|
||||
// 편의상
|
||||
const buttonList = computed(() => props.resourcesData || [])
|
||||
const handleButtonClick = (button: PageDataResourceGroup) => {
|
||||
// 로그
|
||||
sendLog(locale.value, useAnalyticsLogDataDirect(button, props.pageVerTmplSeq))
|
||||
|
||||
const btnDetail = button.btn_info?.detail
|
||||
|
||||
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':
|
||||
downloadZip(btnDetail?.file_path ?? '', btnDetail?.os_type ?? 0)
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -50,21 +110,20 @@ const buttonList = computed(() => props.resourcesData || [])
|
||||
:platform="button.btn_info?.detail?.market_type"
|
||||
:background-color="getColorCodeFromData(button.btn_info, 'btn')"
|
||||
:text-color="getColorCodeFromData(button.btn_info, 'txt')"
|
||||
:disabled="button?.btn_info?.disabled"
|
||||
@click="handleLogClick(button)"
|
||||
@click="handleButtonClick(button)"
|
||||
>
|
||||
{{ button.btn_info?.txt_btn_name }}
|
||||
</BlocksButtonLauncher>
|
||||
|
||||
<AtomsButton
|
||||
v-else
|
||||
:type="getBtnType(button.btn_info)"
|
||||
:type="getButtonType(button.btn_info)"
|
||||
:href="button.btn_info?.detail?.action?.url"
|
||||
:target="button.btn_info?.detail?.action?.link_target"
|
||||
:rel="button.btn_info?.detail?.action?.rel"
|
||||
:background-color="getColorCodeFromData(button.btn_info, 'btn')"
|
||||
:text-color="getColorCodeFromData(button.btn_info, 'txt')"
|
||||
:disabled="button?.btn_info?.disabled"
|
||||
@click="handleLogClick(button)"
|
||||
:disabled="button.btn_info?.detail?.btn_type === 'DEACTIVE'"
|
||||
@click="handleButtonClick(button)"
|
||||
>
|
||||
{{ button.btn_info?.txt_btn_name }}
|
||||
</AtomsButton>
|
||||
|
||||
@@ -3,7 +3,11 @@ import { useWindowScroll } from '@vueuse/core'
|
||||
|
||||
export const useScrollStore = defineStore('scrollStore', () => {
|
||||
const { y: windowY } = useWindowScroll({ behavior: 'smooth' })
|
||||
const breakpoints = useResponsiveBreakpoints()
|
||||
|
||||
const headerHeight = computed(() => {
|
||||
return breakpoints.value.isMobile ? 48 : 64
|
||||
})
|
||||
const stoveGnbHeight = 48 as number
|
||||
const isPassedStoveGnb = ref(false)
|
||||
const scrollGnbPosition = ref(stoveGnbHeight)
|
||||
@@ -24,7 +28,7 @@ export const useScrollStore = defineStore('scrollStore', () => {
|
||||
|
||||
const controlScrollLock = (state: boolean) => {
|
||||
if (!import.meta.client) return
|
||||
|
||||
|
||||
if (state) {
|
||||
document.body.classList.add('scroll-lock')
|
||||
} else {
|
||||
@@ -32,6 +36,17 @@ export const useScrollStore = defineStore('scrollStore', () => {
|
||||
}
|
||||
}
|
||||
|
||||
const scrollToAnchor = (targetId: string) => {
|
||||
const targetElement = document.getElementById(targetId)
|
||||
if (!targetElement) return
|
||||
|
||||
const elementTop = targetElement.getBoundingClientRect().top
|
||||
const currentScrollY = window.scrollY
|
||||
const targetScrollY = currentScrollY + elementTop - headerHeight.value
|
||||
|
||||
windowY.value = targetScrollY
|
||||
}
|
||||
|
||||
return {
|
||||
stoveGnbHeight,
|
||||
isPassedStoveGnb,
|
||||
@@ -39,5 +54,6 @@ export const useScrollStore = defineStore('scrollStore', () => {
|
||||
|
||||
updateScrollValue,
|
||||
controlScrollLock,
|
||||
scrollToAnchor,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user