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

This commit is contained in:
“hyeonggkim”
2025-10-24 18:51:10 +09:00
16 changed files with 291 additions and 119 deletions

View File

@@ -12,6 +12,11 @@
@apply outline-none;
}
img {
border: none;
outline: none;
}
/* 라이트 테마 색상 */
[data-theme='light'] {
body {

View File

@@ -1,7 +1,7 @@
/* Button Size Classes */
@layer components {
.modal-wrap {
@apply fixed inset-0 flex p-5 z-[500];
@apply fixed inset-0 flex p-5 z-[800];
}
.modal-wrap.dimmed {

View File

@@ -0,0 +1,42 @@
<script setup lang="ts">
const { y: windowY } = useWindowScroll({ behavior: 'smooth' })
const showBtn = computed(() => windowY.value > 0)
const handleScrollToTop = () => {
windowY.value = 0
}
</script>
<template>
<Transition name="fade">
<button v-if="showBtn" class="btn-top" @click="handleScrollToTop">
<AtomsIconsTopLine class="icon-top" />
<span class="sr-only">top</span>
</button>
</Transition>
</template>
<style scoped>
.btn-top {
@apply relative rounded-full flex items-center justify-center bg-black/20 z-[100]
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 before:transition-all before:duration-300 before:ease-in-out
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;
}
.btn-top:hover {
@apply after:opacity-10;
}
.btn-top:hover .icon-top {
@apply opacity-100;
}
.icon-top {
@apply opacity-50 transition-opacity duration-300 ease-in-out;
}
[data-theme='light'] {
.btn-top {
@apply bg-white/10;
}
}
</style>

View File

@@ -10,25 +10,25 @@ interface Props {
const props = withDefaults(defineProps<Props>(), {
objectFit: 'contain',
alt: 'image',
})
const imagePaths = computed(() => {
if (!props.resourcesData?.res_path) return null
const pcPath =
const pc =
props.resourcesData.res_path.path_pc ?? props.resourcesData.res_path.path_mo
const moPath =
const mo =
props.resourcesData.res_path.path_mo ?? props.resourcesData.res_path.path_pc
return {
pc: pcPath ? getResolvedHost(pcPath) : '',
mo: moPath ? getResolvedHost(moPath) : '',
pc: pc ? getResolvedHost(pc) : '',
mo: mo ? getResolvedHost(mo) : '',
}
})
</script>
<template>
<picture v-if="imagePaths">
<picture v-if="imagePaths" ref="pictureRef">
<source media="(min-width: 1024px)" :srcset="imagePaths.pc" />
<source media="(max-width: 1023px)" :srcset="imagePaths.mo" />
<img
@@ -39,24 +39,3 @@ const imagePaths = computed(() => {
/>
</picture>
</template>
<style scoped>
/* 이미지 깨짐 시 보더 및 아이콘 제거 */
img {
border: none;
outline: none;
}
/* 깨진 이미지 아이콘과 alt 텍스트 숨김 */
img::before,
img::after {
display: none;
}
/* alt 텍스트 영역 숨김 */
img[alt] {
text-indent: -9999px;
overflow: hidden;
display: block;
}
</style>

View File

@@ -0,0 +1,28 @@
<script setup lang="ts">
interface Props {
size?: number | string
color?: string
}
withDefaults(defineProps<Props>(), {
size: 24,
color: 'white',
})
</script>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
:width="size"
:height="size"
viewBox="0 0 24 24"
fill="none"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M16.75 2C14.6789 2 13 3.67893 13 5.75C13 6.04651 13.0344 6.33499 13.0995 6.61165C13.06 6.6295 13.0213 6.65017 12.9836 6.67375L8.98359 9.17375C8.93046 9.20696 8.88163 9.24445 8.83728 9.2855C8.16435 8.64391 7.25318 8.25 6.25 8.25C4.17893 8.25 2.5 9.92893 2.5 12C2.5 14.0711 4.17893 15.75 6.25 15.75C7.25317 15.75 8.16434 15.3561 8.83728 14.7145C8.88163 14.7556 8.93046 14.793 8.98359 14.8263L12.9836 17.3263C12.996 17.334 13.0086 17.3415 13.0212 17.3486C13.0072 17.4805 13 17.6144 13 17.75C13 19.8211 14.6789 21.5 16.75 21.5C18.8211 21.5 20.5 19.8211 20.5 17.75C20.5 15.6789 18.8211 14 16.75 14C15.521 14 14.43 14.5912 13.7461 15.5048L10.0164 13.1737C9.95959 13.1382 9.90054 13.1093 9.84012 13.0867C9.9441 12.7428 10 12.3779 10 12C10 11.6221 9.9441 11.2572 9.84012 10.9133C9.90054 10.8907 9.95959 10.8618 10.0164 10.8263L14.0164 8.32626L14.0218 8.32286C14.7056 9.04763 15.675 9.5 16.75 9.5C18.8211 9.5 20.5 7.82107 20.5 5.75C20.5 3.67893 18.8211 2 16.75 2ZM15 5.75C15 4.7835 15.7835 4 16.75 4C17.7165 4 18.5 4.7835 18.5 5.75C18.5 6.7165 17.7165 7.5 16.75 7.5C15.7835 7.5 15 6.7165 15 5.75ZM6.25 10.25C5.2835 10.25 4.5 11.0335 4.5 12C4.5 12.9665 5.2835 13.75 6.25 13.75C7.2165 13.75 8 12.9665 8 12C8 11.0335 7.2165 10.25 6.25 10.25ZM16.75 16C15.7835 16 15 16.7835 15 17.75C15 18.7165 15.7835 19.5 16.75 19.5C17.7165 19.5 18.5 18.7165 18.5 17.75C18.5 16.7835 17.7165 16 16.75 16Z"
:fill="color"
/>
</svg>
</template>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
interface Props {
size?: number | string
color?: string
}
withDefaults(defineProps<Props>(), {
size: 24,
color: 'white',
})
</script>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
:width="size"
:height="size"
viewBox="0 0 24 24"
fill="none"
>
<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"
:fill="color"
/>
<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"
:fill="color"
/>
</svg>
</template>

View File

@@ -0,0 +1,39 @@
<script setup lang="ts">
interface Props {
parentRef: HTMLElement | null
isShowTopBtn: boolean
isShowSnsBtn: boolean
}
const { height: viewportH } = useWindowSize()
const props = withDefaults(defineProps<Props>(), {
isShowTopBtn: false,
isShowSnsBtn: false,
})
const parentEl = toRef(props, 'parentRef')
const { bottom: parentBottom } = useElementBounding(parentEl)
const pinToParent = computed(() => {
if (!parentBottom.value) return false
return parentBottom.value <= viewportH.value
})
</script>
<template>
<div :class="['utile-container', { 'is-fixed': pinToParent }]">
<AtomsButtonScrollTop v-if="props.isShowTopBtn" />
<!-- <AtomsButtonSns v-if="props.isShowSnsBtn" /> -->
</div>
</template>
<style scoped>
.utile-container {
@apply fixed flex flex-col
bottom-[12px] right-[12px] gap-2 md:bottom-[40px] md:right-[40px] md:gap-3;
}
.utile-container.is-fixed {
@apply absolute;
}
</style>

View File

@@ -25,16 +25,13 @@ const canTeleport = (localId: string) => {
<style scoped>
.spinner-container {
@apply fixed inset-0 bg-black/90 flex items-center justify-center z-[900];
@apply fixed inset-0 flex items-center justify-center z-[900];
}
.spinner {
@apply w-[80px] h-[80px] bg-cover bg-center bg-no-repeat bg-[url('/images/common/publisning_template_loader_black.png')];
}
[data-theme='light'] {
.spinner-container {
@apply bg-white/90;
}
.spinner {
@apply bg-[url('/images/common/publisning_template_loader_white.png')];
}

View File

@@ -51,7 +51,7 @@ onUnmounted(() => {
<Transition name="fade">
<div
v-if="isOpen"
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-75"
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-75 z-[800]"
:class="props.modalName"
@click="handleOutsideClick"
>

View File

@@ -312,7 +312,7 @@ onBeforeUnmount(() => {
<style scoped>
.header {
@apply bg-theme-foreground text-theme-foreground-reversal relative z-50;
@apply bg-theme-foreground text-theme-foreground-reversal relative z-[100];
}
.game-wrapper {
@apply absolute flex w-full h-[48px] items-center whitespace-nowrap px-[52px] bg-theme-foreground sm:px-[72px] md:h-16 md:pl-0 md:pr-[40px]

View File

@@ -13,6 +13,8 @@ interface Props {
const props = defineProps<Props>()
const { locale } = useI18n()
const mainRef = ref<HTMLElement>()
// 템플릿 레지스트리 사용
const { getTemplateComponent } = useTemplateRegistry()
@@ -61,7 +63,7 @@ watchEffect(() => {
</script>
<template>
<main class="main">
<main ref="mainRef" class="main">
<template
v-for="(template, index) in visibleTemplates"
:key="template.template_code ?? index"
@@ -72,11 +74,16 @@ watchEffect(() => {
:page-ver-tmpl-seq="template.page_ver_tmpl_seq"
/>
</template>
<BlocksUtileContainer
:parent-ref="mainRef"
:is-show-top-btn="pageData.use_top_btn ?? false"
:is-show-sns-btn="pageData.use_sns_btn ?? false"
/>
</main>
</template>
<style scoped>
.main {
@apply pt-[48px] md:pt-[64px];
@apply relative pt-[48px] md:pt-[64px];
}
</style>

View File

@@ -0,0 +1,114 @@
<script setup lang="ts">
import { SplideSlide } from '@splidejs/vue-splide'
import {
getComponentContainer,
getComponentGroup,
} from '#layers/utils/dataUtil'
import type { Splide as SplideType } from '@splidejs/splide'
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
import type { OperateGroupItem } from '#layers/types/api/resourcesData'
interface Props {
components: PageDataTemplateComponents
pageVerTmplSeq: number
}
const props = defineProps<Props>()
const pageDataStore = usePageDataStore()
const { getResourcesData } = useResourcesData()
const { locale } = useI18n()
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
const { pageData } = storeToRefs(pageDataStore)
const backgroundData = computed(() =>
getComponentGroup(props.components, 'background')
)
const mainTitleData = computed(() =>
getComponentGroup(props.components, 'mainTitle')
)
// const buttonListData = ref(
// getComponentGroupAry(slideData?.value[0], 'buttonList')
// )
const { data: slideData } = await useAsyncData(
`gr-board-01-resources-${pageData.value?.page_seq}-${pageData.value?.page_ver}-${props.pageVerTmplSeq}`,
async () => {
if (!pageData.value?.page_seq || !pageData.value?.page_ver) {
return []
}
const operateGroupList = await getResourcesData({
pageSeq: pageData.value.page_seq,
pageVer: pageData.value.page_ver,
pageVerTmplSeq: props.pageVerTmplSeq,
langCode: locale.value,
})
console.log('operateGroupList', operateGroupList)
const bannerList = getComponentContainer(operateGroupList, 'bannerList', {
hasGroup: true,
minLength: 4,
}) as OperateGroupItem[]
return bannerList
},
{
default: () => [],
server: false,
}
)
const onArrowClick = (direction, targetIndex) => {
const arrowGroupAry = getComponentGroupAry(props.components, 'arrow')
const logTracking = arrowGroupAry?.[direction === 'prev' ? 0 : 1]
sendLog(locale.value, useAnalyticsLogDataDirect(logTracking, 1))
}
</script>
<template>
<section class="relative py-[80px] md:py-[120px]">
<WidgetsBackground v-if="backgroundData" :resources-data="backgroundData" />
<div class="section-content px-0">
<WidgetsMainTitle
v-if="mainTitleData"
:resources-data="mainTitleData"
class="title-md"
/>
<BlocksSlideDefault
v-if="slideData"
:slide-item-length="slideData?.length"
:pagination="false"
class="mt-[24px] md:mt-[48px]"
@arrow-click="onArrowClick"
>
<SplideSlide v-for="(item, index) in slideData" :key="index">
<div class="slide-inner border-line">
<BlocksVisualContent
:resources-data="getComponentGroup(item, 'imgList')"
:page-ver-tmpl-seq="props.pageVerTmplSeq"
object-fit="cover"
:alt="getComponentGroup(item, 'subTitle')?.display?.text"
/>
</div>
</SplideSlide>
</BlocksSlideDefault>
<!-- <WidgetsButtonList
v-if="buttonListData"
:resources-data="buttonListData"
:page-ver-tmpl-seq="props.pageVerTmplSeq"
class="mt-[40px] md:mt-[56px]"
/> -->
</div>
</section>
</template>
<style scoped>
.section-container {
@apply before:hidden md:before:block before:content-[''] before:absolute before:top-0 before:left-0 before:w-[104px] before:h-full before:bg-gradient-to-l from-transparent to-[rgba(0,0,0,0.7)]
after:hidden md:after:block after:content-[''] after:absolute after:top-0 after:right-0 after:w-[104px] after:h-full after:bg-gradient-to-r from-transparent to-[rgba(0,0,0,0.7)];
}
</style>

View File

@@ -1,10 +1,7 @@
<script setup lang="ts">
import { SplideSlide } from '@splidejs/vue-splide'
import {
getComponentContainer,
getComponentGroup,
} from '#layers/utils/dataUtil'
import { getComponentGroup } from '#layers/utils/dataUtil'
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
import AtomsImg from '#layers/components/atoms/Img.vue'
interface Props {
components: PageDataTemplateComponents
@@ -13,9 +10,6 @@ interface Props {
const props = defineProps<Props>()
const { locale } = useI18n()
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
const backgroundData = computed(() =>
getComponentGroup(props.components, 'background')
)
@@ -28,21 +22,12 @@ const subTitleData = computed(() =>
const descriptionData = computed(() =>
getComponentGroup(props.components, 'description')
)
const slideData = computed(() => {
return getComponentContainer(props.components, 'group_sets')
const imgListData = computed(() => {
return getComponentGroupAry(props.components, 'imgList')
})
const buttonListData = computed(() => {
return getComponentGroupAry(props.components, 'buttonList')
})
const paginationData = computed(() => {
return getComponentGroupAry(props.components, 'pagination')
})
const onArrowClick = (direction, targetIndex) => {
const arrowGroupAry = getComponentGroupAry(props.components, 'arrow')
const logTracking = arrowGroupAry?.[direction === 'prev' ? 0 : 1]
sendLog(locale.value, useAnalyticsLogDataDirect(logTracking, 1))
}
</script>
<template>
@@ -59,51 +44,15 @@ const onArrowClick = (direction, targetIndex) => {
:resources-data="subTitleData"
class="title-sm mt-2 mx-[20px] sm:mx-[40px]"
/>
<template v-if="slideData">
<div v-if="slideData.length <= 2" class="img-container">
<div
v-for="(item, index) in slideData"
:key="index"
:class="[{ 'slide-2': slideData.length === 2 }]"
>
<AtomsImg
:resources-data="getComponentGroup(item, 'imgList')"
:page-ver-tmpl-seq="props.pageVerTmplSeq"
:alt="getComponentGroup(item, 'subTitle')?.display?.text"
/>
</div>
<div v-if="imgListData" class="img-container">
<div v-for="(item, index) in imgListData" :key="index" class="img-item">
<AtomsImg
:resources-data="item"
object-fit="contain"
:alt="item?.group_label"
/>
</div>
<BlocksSlideDefault
v-else
:type="slideData.length === 3 ? 'slide' : 'loop'"
:slide-item-length="slideData?.length"
:arrows="slideData.length === 3 ? false : true"
:pagination="slideData.length === 3 ? false : true"
:pagination-data="paginationData"
:breakpoints="{
1023: {
type: 'loop',
pagination: false,
},
}"
class="mt-[32px]"
@arrow-click="onArrowClick"
>
<SplideSlide
v-for="(item, index) in slideData"
:key="index"
class="mr-4"
>
<div class="slide-inner w-[295px] sm:w-[304px]">
<AtomsImg
:resources-data="getComponentGroup(item, 'imgList')"
:page-ver-tmpl-seq="props.pageVerTmplSeq"
:alt="getComponentGroup(item, 'subTitle')?.display?.text"
/>
</div>
</SplideSlide>
</BlocksSlideDefault>
</template>
</div>
<WidgetsButtonList
v-if="buttonListData"
:resources-data="buttonListData"
@@ -121,26 +70,8 @@ const onArrowClick = (direction, targetIndex) => {
<style scoped>
.img-container {
@apply flex flex-wrap justify-center gap-4 box-content mx-auto mt-[32px]
@apply flex flex-wrap items-center justify-center gap-4 box-content mx-auto mt-[32px]
max-w-[688px] px-[20px]
md:max-w-[944px] md:px-[40px];
}
.splide {
@apply md:max-w-[1088px];
}
.splide:deep(.splide__track) {
@apply md:w-[944px] md:mx-auto;
}
.splide:deep(.splide__track) {
@apply !px-[20px] sm:!px-[40px] md:!px-[0];
}
.splide:deep(.arrow-next) {
@apply md:-right-[0];
}
.splide:deep(.arrow-prev) {
@apply md:-left-[0];
}
.slide-2 {
@apply max-w-[335px] md:max-w-[464px];
}
</style>

View File

@@ -115,7 +115,7 @@ const handleSplideMove = (_splide: SplideType, newIndex: number) => {
<style scoped>
.splide-pagination {
@apply flex items-center absolute bottom-10 left-1/2 -translate-x-1/2 z-10 md:bottom-[96px];
@apply flex items-center absolute bottom-10 left-1/2 -translate-x-1/2 z-[5] md:bottom-[96px];
}
.btn-pagination {
@apply relative;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 B