diff --git a/layers/assets/css/app.css b/layers/assets/css/app.css index 86458e7..1b011e2 100644 --- a/layers/assets/css/app.css +++ b/layers/assets/css/app.css @@ -1,8 +1,10 @@ @import './base/_theme.css'; @import './base/_reset.css'; -@import './components/_swiper-pagination.css'; +@import './components/_splide.css'; @import './components/_button.css'; +@import '@splidejs/vue-splide/css'; + @tailwind base; @tailwind components; @tailwind utilities; diff --git a/layers/assets/css/components/_splide.css b/layers/assets/css/components/_splide.css new file mode 100644 index 0000000..6581cf4 --- /dev/null +++ b/layers/assets/css/components/_splide.css @@ -0,0 +1,102 @@ +/* 페이지네이션 버튼 - 모바일 퍼스트 */ +.splide-pagination-bullets { + width: 100%; + display: flex; + justify-content: center; + align-items: center; + gap: 12px; + margin-top: 24px; + z-index: 5; +} + +.splide-pagination-bullets.type-full { + position: absolute; + bottom: 32px; + left: 0; +} + +.splide-pagination-bullet { + position: relative; + width: 8px; + height: 8px; + background: var(--primary); + border-radius: 50%; + opacity: 1; +} + +.splide-pagination-bullet:after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: 50%; + background: rgba(0, 0, 0, 0.5); + transition: opacity 0.3s ease; +} + +.splide-pagination-bullet.is-active:after { + opacity: 0; +} + +/* 네비게이션 버튼 - 모바일 퍼스트 */ +.splide-arrow { + display: none; + /* position: absolute; + top: 50%; + width: 40px; + height: 40px; + transform: translateY(-50%); + background-size: cover; + background-position: center; + background-repeat: no-repeat; + cursor: pointer; + z-index: 5; + background-color: var(--primary); */ +} + +/* .type-full .arrow-prev { + left: 20px; +} +.type-full.arrow-next { + right: 20px; +} */ + +/* 데스크톱 스타일 */ +@media (min-width: 1024px) { + .splide-pagination-bullets { + gap: 24px; + margin-top: 32px; + } + + .splide-pagination-bullets.position-absolute { + bottom: 48px; + } + + .splide-pagination-bullet { + width: 12px; + height: 12px; + } + + .splide-arrow { + display: block; + position: absolute; + top: 50%; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + background-color: var(--primary); + transform: translateY(-50%); + cursor: pointer; + z-index: 5; + } + + .type-full .arrow-prev { + left: 40px; + } + + .type-full .arrow-next { + right: 40px; + } +} diff --git a/layers/assets/css/components/_swiper-pagination.css b/layers/assets/css/components/_swiper-pagination.css deleted file mode 100644 index af4e031..0000000 --- a/layers/assets/css/components/_swiper-pagination.css +++ /dev/null @@ -1,73 +0,0 @@ -/* 페이지네이션 버튼 */ -main .slide-pagination.swiper-pagination-bullets { - position: absolute; - bottom: 48px; - left: 0; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - gap: 24px; - z-index: 5; -} - -.slide-pagination .swiper-pagination-bullet { - position: relative; - width: 12px; - height: 12px; - background: var(--primary); - border-radius: 50%; - opacity: 1; -} - -.slide-pagination .swiper-pagination-bullet:after { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border-radius: 50%; - background: rgba(0, 0, 0, 0.5); -} - -.slide-pagination .swiper-pagination-bullet-active:after { - display: none; -} - -/* 네비게이션 버튼 */ -.slide-prev, -.slide-next { - position: absolute; - top: 50%; - transform: translateY(-50%); - width: 48px; - height: 48px; - background-size: cover; - background-position: center; - background-repeat: no-repeat; - cursor: pointer; - z-index: 5; - - background-color: var(--primary); -} - -.slide-prev { - left: 40px; -} - -.slide-next { - right: 40px; -} - -@media (max-width: 1023px) { - main .slide-pagination.swiper-pagination-bullets { - bottom: 32px; - gap: 12px; - } - - .slide-pagination .swiper-pagination-bullet { - width: 8px; - height: 8px; - } -} diff --git a/layers/components/widgets/BannerList.vue b/layers/components/widgets/BannerList.vue new file mode 100644 index 0000000..671c405 --- /dev/null +++ b/layers/components/widgets/BannerList.vue @@ -0,0 +1,230 @@ + + + + + + + + + + + {{ banner.title }} + + + {{ banner.option01 }} + + + + + + + + + + diff --git a/layers/components/widgets/ButtonList.vue b/layers/components/widgets/ButtonList.vue index d5d9830..c1568b7 100644 --- a/layers/components/widgets/ButtonList.vue +++ b/layers/components/widgets/ButtonList.vue @@ -1,16 +1,9 @@ @@ -21,7 +14,7 @@ const buttonSize = computed(() => { { + const getResourcesData = async ( + params: GetResourcesDataParams + ): Promise => { + const { pageSeq, pageVer, pageVerTmplSeq, langCode, q, qc } = params + + const config = useRuntimeConfig() + const stoveApiBaseUrl = config.public.stoveApiUrl + const apiUrl = `${stoveApiBaseUrl}/pub-comm/v1.0/template/resources` + + const queryParams: Record = { + page_seq: pageSeq, + page_ver: pageVer, + page_ver_tmpl_seq: pageVerTmplSeq, + lang_code: langCode, + q: q || '', + qc: qc || '', + _t: Date.now().toString(), // 캐시 무효화를 위한 타임스탬프 + } + + const response = (await commonFetch('GET', apiUrl, { + query: queryParams, + loading: true, + })) as ResourcesDataResponse | null + + if (response?.code === 0 && 'value' in response) { + return response.value + } + return null + } + + return { + getResourcesData, + } +} diff --git a/layers/server/middleware/gameInfo.ts b/layers/server/middleware/gameData.ts similarity index 100% rename from layers/server/middleware/gameInfo.ts rename to layers/server/middleware/gameData.ts diff --git a/layers/templates/GrVisual01/index.vue b/layers/templates/GrVisual01/index.vue index 037c1d8..14dc917 100644 --- a/layers/templates/GrVisual01/index.vue +++ b/layers/templates/GrVisual01/index.vue @@ -1,42 +1,55 @@ - + diff --git a/layers/templates/GrVisual02/index.vue b/layers/templates/GrVisual02/index.vue index c0d7e99..58448e6 100644 --- a/layers/templates/GrVisual02/index.vue +++ b/layers/templates/GrVisual02/index.vue @@ -1,11 +1,111 @@ - + + + + + + + + + + diff --git a/layers/templates/GrVisual03/index.vue b/layers/templates/GrVisual03/index.vue index b73fd31..6bf0bdb 100644 --- a/layers/templates/GrVisual03/index.vue +++ b/layers/templates/GrVisual03/index.vue @@ -1,84 +1,88 @@ - + - + - prev { return pageData?.page_type === 1 ? 'default' : 'promotion' } -// 이미지 호스트 리턴하는 함수 -// [TODO] 환경변수 처리 수정 +// ============================================================================ +// 컴포넌트 데이터 접근 관련 유틸리티 +// ============================================================================ + +/** + * 컴포넌트 그룹에 데이터가 존재하는지 확인합니다. + * @param source props.components 또는 group 객체 + * @param componentName 컴포넌트 이름 + * @returns 데이터 존재 여부 + */ +export const hasComponentGroup = ( + source: any, + componentName: string +): boolean => { + if (!source) return false + + const component = source[componentName] as PageDataComponent + return component?.groups && component.groups.length > 0 +} + +/** + * 컴포넌트 그룹의 첫 번째 데이터를 반환합니다. + * @param source props.components 또는 group 객체 + * @param componentName 컴포넌트 이름 + * @returns 첫 번째 그룹 데이터 또는 null + */ +export const getComponentGroup = (source: any, componentName: string) => { + if (!source) return null + + return source[componentName]?.groups?.[0] || null +} + +/** + * 컴포넌트 그룹의 모든 데이터를 반환합니다. + * @param source props.components 또는 group 객체 + * @param componentName 컴포넌트 이름 + * @returns 그룹 배열 데이터 + */ +export const getComponentGroupAry = (source: any, componentName: string) => { + if (!source) return [] + + return source[componentName]?.groups || [] +} + +// ============================================================================ +// 리소스/이미지 처리 관련 유틸리티 +// ============================================================================ + +/** + * 이미지 경로를 완전한 호스트 URL로 변환합니다. + * @param path 이미지 경로 + * @returns 완전한 이미지 URL + */ export const getResolvedHost = (path: string): string => { const config = useRuntimeConfig() // const isDev = process.env.NODE_ENV === "development"; @@ -22,12 +82,12 @@ export const getResolvedHost = (path: string): string => { return `${rootPath}${path}` } -// 반응형 클래스 리턴하는 함수 -export const getResponsiveClass = () => { - return ['bg-[image:var(--mobile-bg)]', 'sm:bg-[image:var(--pc-bg)]'] -} - -// 통합된 반응형 리소스 함수 +/** + * 반응형 리소스(이미지/비디오)를 처리하여 PC/모바일 버전을 반환합니다. + * @param pathArray 리소스 경로 배열 + * @param options 리소스 타입 옵션 + * @returns 반응형 리소스 객체 또는 null + */ export const getResponsiveSrc = ( pathArray: PageDataResourceGroupResPath, options: { @@ -59,3 +119,15 @@ export const getResponsiveSrc = ( pcSrc: resolvedImages.pc, } } + +// ============================================================================ +// 스타일링 관련 유틸리티 +// ============================================================================ + +/** + * 반응형 배경 이미지를 위한 CSS 클래스를 반환합니다. + * @returns 반응형 배경 클래스 배열 + */ +export const getResponsiveClass = () => { + return ['bg-[image:var(--mobile-bg)]', 'sm:bg-[image:var(--pc-bg)]'] +}
+ {{ banner.option01 }} +