Merge branch 'feature/20251024_cl_GR_BOARD_01' into feature/202501107-all
@@ -19,8 +19,8 @@
|
||||
|
||||
/* 라이트 테마 색상 */
|
||||
[data-theme='light'] {
|
||||
body {
|
||||
@apply bg-white;
|
||||
.main {
|
||||
@apply bg-theme-foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
layers/components/atoms/icons/Stove.vue
Normal file
@@ -3,7 +3,7 @@ interface Props {
|
||||
title: string
|
||||
description: string | number
|
||||
imgPath: string
|
||||
linkTarget?: string
|
||||
linkTarget?: '_blank' | '_self'
|
||||
url?: string
|
||||
alt?: string
|
||||
class?: string
|
||||
@@ -13,6 +13,10 @@ const props = defineProps<Props>()
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||
|
||||
const isShowOverlay = computed(() => {
|
||||
return props.title || props.description
|
||||
})
|
||||
|
||||
const handleLinkClick = (title: string) => {
|
||||
const trackingData = {
|
||||
tracking: {
|
||||
@@ -26,17 +30,18 @@ const handleLinkClick = (title: string) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="props.title || props.description"
|
||||
:class="`card-news ${props.class || ''}`"
|
||||
>
|
||||
<div :class="['card-news', { 'no-image': !props.imgPath }, props.class]">
|
||||
<img
|
||||
v-if="props.imgPath"
|
||||
:src="props.imgPath"
|
||||
:alt="props.title || props.alt"
|
||||
class="card-image"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="card-overlay">
|
||||
<span v-else class="card-stove">
|
||||
<AtomsIconsStove />
|
||||
</span>
|
||||
<div v-if="isShowOverlay" class="card-overlay">
|
||||
<h3 v-if="props.title" class="card-title">
|
||||
{{ props.title }}
|
||||
</h3>
|
||||
@@ -47,7 +52,7 @@ const handleLinkClick = (title: string) => {
|
||||
<a
|
||||
v-if="props.url"
|
||||
:href="props.url"
|
||||
:target="props.linkTarget"
|
||||
:target="props.linkTarget || '_self'"
|
||||
class="card-link"
|
||||
@click="handleLinkClick(props.title)"
|
||||
/>
|
||||
@@ -56,7 +61,7 @@ const handleLinkClick = (title: string) => {
|
||||
|
||||
<style scoped>
|
||||
.card-news {
|
||||
@apply overflow-hidden relative flex items-center justify-center h-full rounded-lg
|
||||
@apply overflow-hidden relative flex flex-col items-center justify-center h-full rounded-lg
|
||||
after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full
|
||||
after:border after:border-white/10 after:rounded-lg;
|
||||
}
|
||||
@@ -64,10 +69,10 @@ const handleLinkClick = (title: string) => {
|
||||
@apply transition-transform duration-300 w-full h-full object-cover;
|
||||
}
|
||||
.card-overlay {
|
||||
@apply absolute bottom-0 left-0 right-0 pt-[14px] px-[18px] pb-[16px] flex flex-col justify-end border-t border-white/10 bg-black/40 shadow-[0_-10px_10px_0_rgba(0,0,0,0.25)] backdrop-blur-[25px] md:pt-[20px] text-left md:px-[26px] md:pb-[26px];
|
||||
@apply absolute bottom-0 left-0 w-full pt-[14px] px-[18px] pb-[16px] flex flex-col justify-end border-t border-white/10 bg-black/40 shadow-[0_-10px_10px_0_rgba(0,0,0,0.25)] backdrop-blur-[25px] md:pt-[20px] text-left md:px-[26px] md:pb-[26px];
|
||||
}
|
||||
.card-title {
|
||||
@apply text-[14px] leading-[20px] font-medium text-white md:text-lg md:leading-[26px];
|
||||
@apply text-[14px] leading-[20px] font-medium text-white md:text-[18px] md:leading-[26px];
|
||||
}
|
||||
.card-description {
|
||||
@apply mt-[6px] text-[12px] leading-[18px] text-white/50 md:mt-1 md:text-[14px] md:leading-[24px];
|
||||
@@ -75,4 +80,15 @@ const handleLinkClick = (title: string) => {
|
||||
.card-link {
|
||||
@apply absolute top-0 left-0 w-full h-full z-[5];
|
||||
}
|
||||
|
||||
.card-stove {
|
||||
@apply relative w-full h-full bg-[#333333];
|
||||
}
|
||||
.card-stove svg {
|
||||
@apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2;
|
||||
}
|
||||
|
||||
.no-image .card-overlay {
|
||||
@apply relative bg-[#1f1f1f];
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,6 +7,8 @@ interface Props {
|
||||
type?: ResponsiveOptions['type']
|
||||
slideItemLength?: number
|
||||
autoplay?: boolean
|
||||
perPage?: number
|
||||
drag?: boolean
|
||||
arrows?: boolean
|
||||
pagination?: boolean
|
||||
paginationData?: PageDataResourceGroups
|
||||
@@ -15,6 +17,8 @@ interface Props {
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
autoplay: false,
|
||||
perPage: 1,
|
||||
drag: true,
|
||||
arrows: true,
|
||||
pagination: true,
|
||||
})
|
||||
@@ -24,22 +28,25 @@ const emit = defineEmits(['mounted', 'move', 'arrowClick'])
|
||||
// Splide 화살표 로직을 위한 composable 사용
|
||||
const { addArrowClickListeners } = useSplideArrow()
|
||||
|
||||
const isMultipleItems = computed(() => (props.slideItemLength ?? 0) > 1)
|
||||
const resolvedType = computed<ResponsiveOptions['type']>(() => {
|
||||
const slideType = computed(() => {
|
||||
if (props.type) return props.type
|
||||
return isMultipleItems.value ? 'loop' : 'slide'
|
||||
|
||||
return (props.slideItemLength ?? 0) > 1 ? 'loop' : 'slide'
|
||||
})
|
||||
|
||||
const options = computed((): ResponsiveOptions => {
|
||||
return {
|
||||
type: resolvedType.value,
|
||||
type: slideType.value,
|
||||
autoWidth: true,
|
||||
autoHeight: true,
|
||||
perPage: props.perPage,
|
||||
speed: 500,
|
||||
updateOnMove: true,
|
||||
autoplay: props.autoplay,
|
||||
arrows: props.arrows && isMultipleItems.value,
|
||||
pagination: props.pagination && isMultipleItems.value,
|
||||
drag: props.drag,
|
||||
trimSpace: false,
|
||||
arrows: props.arrows,
|
||||
pagination: props.pagination,
|
||||
classes: {
|
||||
arrows: 'splide-arrows',
|
||||
arrow: 'splide-arrow',
|
||||
@@ -77,7 +84,7 @@ const handleMove = (
|
||||
<Splide
|
||||
:options="options"
|
||||
class="w-full"
|
||||
:style="getPaginationClass(props.paginationData)"
|
||||
:style="getPaginationClass(props?.paginationData)"
|
||||
@splide:mounted="handleSplideMounted"
|
||||
@splide:move="handleMove"
|
||||
>
|
||||
|
||||
@@ -154,21 +154,23 @@ onBeforeUnmount(() => {
|
||||
.thumbnail-splide {
|
||||
@apply overflow-hidden flex justify-center;
|
||||
}
|
||||
.thumbnail-splide:deep(.splide__track) {
|
||||
@apply md:w-[calc(100%-16px)];
|
||||
}
|
||||
.thumbnail-slide {
|
||||
@apply overflow-hidden relative mr-[12px] !border-none rounded-[4px] md:mr-[16px]
|
||||
@apply overflow-hidden relative mr-[12px] !border-none rounded-[4px] bg-[var(--pagination-disabled)] md:mr-[16px] md:bg-transparent
|
||||
after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:border after:rounded-[4px];
|
||||
background-color: var(--pagination-disabled);
|
||||
}
|
||||
.thumbnail-slide:hover,
|
||||
.thumbnail-slide.is-active {
|
||||
background-color: var(--pagination-active);
|
||||
@apply bg-[var(--pagination-active)] md:bg-transparent;
|
||||
}
|
||||
.thumbnail-slide::after {
|
||||
border-color: var(--pagination-disabled);
|
||||
@apply border-[var(--pagination-disabled)];
|
||||
}
|
||||
.thumbnail-slide:hover::after,
|
||||
.thumbnail-slide.is-active::after {
|
||||
border-color: var(--pagination-active);
|
||||
@apply border-[var(--pagination-active)];
|
||||
}
|
||||
|
||||
/* 기본 버전 스타일 */
|
||||
@@ -178,7 +180,7 @@ onBeforeUnmount(() => {
|
||||
@apply h-full;
|
||||
}
|
||||
.thumbnail-carousel.thumbnail-default .thumbnail-splide {
|
||||
@apply absolute bottom-[32px] left-1/2 -translate-x-1/2 max-w-[100%] md:bottom-[48px] md:max-w-[896px] md:px-[72px];
|
||||
@apply absolute bottom-[32px] left-1/2 -translate-x-1/2 max-w-[100%] md:bottom-[48px] md:max-w-[896px] md:px-[64px];
|
||||
}
|
||||
.thumbnail-carousel.thumbnail-default:deep(.arrow-prev) {
|
||||
@apply left-0;
|
||||
@@ -199,8 +201,11 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
/* 미디어 버전 스타일 */
|
||||
.thumbnail-carousel.thumbnail-media {
|
||||
@apply flex flex-col items-center;
|
||||
}
|
||||
.thumbnail-carousel.thumbnail-media .thumbnail-splide {
|
||||
@apply w-screen mt-[20px] mx-[-20px] sm:mx-[-40px] md:w-fit md:max-w-[100%] md:mt-[28px] md:mx-auto md:px-[120px];
|
||||
@apply w-screen mt-[20px] mx-[-20px] sm:mx-[-40px] md:w-auto md:max-w-[100%] md:mt-[28px] md:mx-auto md:px-[112px];
|
||||
}
|
||||
.thumbnail-carousel.thumbnail-media .thumbnail-splide:deep(.splide__track) {
|
||||
@apply !px-[20px] sm:!px-[40px] md:!px-[0];
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useMediaQuery } from '@vueuse/core'
|
||||
import { useBreakpoints } from '@vueuse/core'
|
||||
import { getDeviceSrc } from '#layers/utils/styleUtil'
|
||||
import type { PageDataResourceGroupResPath } from '#layers/types/api/pageData'
|
||||
|
||||
const BREAKPOINTS = {
|
||||
export const BREAKPOINTS = {
|
||||
xs: 360,
|
||||
sm: 768,
|
||||
md: 1024,
|
||||
@@ -10,24 +10,16 @@ const BREAKPOINTS = {
|
||||
} as const
|
||||
|
||||
/**
|
||||
* useMediaQuery 기반 반응형 브레이크포인트
|
||||
* useBreakpoints 기반 반응형 브레이크포인트
|
||||
*/
|
||||
export const useResponsiveBreakpoints = () => {
|
||||
const ssrWidth = BREAKPOINTS.xs
|
||||
const isXs = useMediaQuery(`(min-width: ${BREAKPOINTS.xs}px)`, { ssrWidth })
|
||||
const isSm = useMediaQuery(`(min-width: ${BREAKPOINTS.sm}px)`, { ssrWidth })
|
||||
const isMd = useMediaQuery(`(min-width: ${BREAKPOINTS.md}px)`, { ssrWidth })
|
||||
const isLg = useMediaQuery(`(min-width: ${BREAKPOINTS.lg}px)`, { ssrWidth })
|
||||
const isMobile = useMediaQuery(`(max-width: ${BREAKPOINTS.md - 1}px)`, {
|
||||
ssrWidth,
|
||||
})
|
||||
const isTablet = useMediaQuery(
|
||||
`(min-width: ${BREAKPOINTS.sm}px) and (max-width: ${BREAKPOINTS.md - 1}px)`,
|
||||
{ ssrWidth }
|
||||
)
|
||||
const isDesktop = useMediaQuery(`(min-width: ${BREAKPOINTS.md}px)`, {
|
||||
ssrWidth,
|
||||
})
|
||||
const breakpoints = useBreakpoints(BREAKPOINTS)
|
||||
const isXs = breakpoints.smaller('sm') // < 768px
|
||||
const isSm = breakpoints.smaller('md') // < 1024px
|
||||
const isMd = breakpoints.smaller('lg') // < 1440px
|
||||
const isLg = breakpoints.greater('lg') // >= 1440px
|
||||
const isMobile = isXs || isSm
|
||||
const isDesktop = isLg || isMd
|
||||
|
||||
return computed(() => ({
|
||||
isXs: isXs.value,
|
||||
@@ -35,7 +27,6 @@ export const useResponsiveBreakpoints = () => {
|
||||
isMd: isMd.value,
|
||||
isLg: isLg.value,
|
||||
isMobile: isMobile.value,
|
||||
isTablet: isTablet.value,
|
||||
isDesktop: isDesktop.value,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import type {
|
||||
GetResourcesDataParams,
|
||||
ResourcesDataResponse,
|
||||
getOperateResourcesDataParams,
|
||||
OperateResourcesDataResponse,
|
||||
OperateComponents,
|
||||
getCwmsArticleDataParams,
|
||||
CwmsArticleDataResponse,
|
||||
CwmsArticleData,
|
||||
} from '#layers/types/api/resourcesData'
|
||||
|
||||
export const useResourcesData = () => {
|
||||
const getResourcesData = async (
|
||||
params: GetResourcesDataParams
|
||||
/**
|
||||
*
|
||||
* @param params getOperateResourcesDataParams
|
||||
* @returns OperateComponents | null
|
||||
* @description 운영 리소스 데이터 조회
|
||||
*/
|
||||
const getOperateResourcesData = async (
|
||||
params: getOperateResourcesDataParams
|
||||
): Promise<OperateComponents | null> => {
|
||||
const { pageSeq, pageVer, pageVerTmplSeq, langCode, q, qc } = params
|
||||
|
||||
@@ -27,7 +36,59 @@ export const useResourcesData = () => {
|
||||
const response = (await commonFetch('GET', apiUrl, {
|
||||
query: queryParams,
|
||||
loading: true,
|
||||
})) as ResourcesDataResponse | null
|
||||
})) as OperateResourcesDataResponse | null
|
||||
|
||||
if (response?.code === 0 && 'value' in response) {
|
||||
return response.value
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param articleGroupCode 게시판 그룹 코드 (예: 128093)
|
||||
* @param options 옵션 객체
|
||||
* @returns 게시판 글 목록 응답
|
||||
*/
|
||||
const getCwmsArticleData = async (
|
||||
articleGroupCode: string,
|
||||
articleGroupSeq: number,
|
||||
params: getCwmsArticleDataParams
|
||||
): Promise<CwmsArticleData | null> => {
|
||||
const {
|
||||
lang,
|
||||
sortTypeCode,
|
||||
interactionTypeCodes,
|
||||
handleCode,
|
||||
contentYn,
|
||||
summaryYn,
|
||||
headlineTitleYn,
|
||||
translationYn,
|
||||
page,
|
||||
size,
|
||||
} = params
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const stoveApiBaseUrl = config.public.stoveApiUrl
|
||||
const apiUrl = `${stoveApiBaseUrl}/cwms/v3.0/article_group/${articleGroupCode}/${articleGroupSeq}/article/list`
|
||||
|
||||
const queryParams: Record<string, string | number | boolean> = {
|
||||
lang: lang,
|
||||
sort_type_code: sortTypeCode,
|
||||
interaction_type_code: interactionTypeCodes?.join(','),
|
||||
handle_code: handleCode ? 'Y' : 'N',
|
||||
content_yn: contentYn ? 'Y' : 'N',
|
||||
summary_yn: summaryYn ? 'Y' : 'N',
|
||||
headline_title_yn: headlineTitleYn ? 'Y' : 'N',
|
||||
translation_yn: translationYn ? 'Y' : 'N',
|
||||
page: page,
|
||||
size: size,
|
||||
}
|
||||
|
||||
const response = (await commonFetch('GET', apiUrl, {
|
||||
query: queryParams,
|
||||
loading: true,
|
||||
})) as CwmsArticleDataResponse | null
|
||||
|
||||
if (response?.code === 0 && 'value' in response) {
|
||||
return response.value
|
||||
@@ -36,6 +97,7 @@ export const useResourcesData = () => {
|
||||
}
|
||||
|
||||
return {
|
||||
getResourcesData,
|
||||
getOperateResourcesData,
|
||||
getCwmsArticleData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import GrGallery03 from '#layers/templates/GrGallery03/index.vue'
|
||||
import GrDetail01 from '#layers/templates/GrDetail01/index.vue'
|
||||
import GrDetail02 from '#layers/templates/GrDetail02/index.vue'
|
||||
import GrDetail03 from '#layers/templates/GrDetail03/index.vue'
|
||||
// import GrBoard01 from '#layers/templates/GrBoard01/index.vue'
|
||||
import GrBoard01 from '#layers/templates/GrBoard01/index.vue'
|
||||
import GrContents01 from '#layers/templates/GrContents01/index.vue'
|
||||
|
||||
const templateRegistry = {
|
||||
@@ -17,7 +17,7 @@ const templateRegistry = {
|
||||
GR_GALLERY_01: { component: GrGallery01 },
|
||||
GR_GALLERY_02: { component: GrGallery02 },
|
||||
GR_GALLERY_03: { component: GrGallery03 },
|
||||
// GR_BOARD_01: { component: GrBoard01 },
|
||||
GR_BOARD_01: { component: GrBoard01 },
|
||||
GR_DETAIL_01: { component: GrDetail01 },
|
||||
GR_DETAIL_02: { component: GrDetail02 },
|
||||
GR_DETAIL_03: { component: GrDetail03 },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export function useTokenValidation() {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const apiBaseUrl = `${runtimeConfig.public.stoveApiUrl}`
|
||||
const config = useRuntimeConfig()
|
||||
const apiBaseUrl = `${config.public.stoveApiUrl}`
|
||||
const isTokenValid = ref(false)
|
||||
|
||||
const validateToken = async (token: string) => {
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
<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 { getComponentGroup } from '#layers/utils/dataUtil'
|
||||
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
import type { OperateGroupItem } from '#layers/types/api/resourcesData'
|
||||
import type { CwmsArticleItem } from '#layers/types/api/resourcesData'
|
||||
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
@@ -15,46 +11,54 @@ interface Props {
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const gameDataStore = useGameDataStore()
|
||||
const pageDataStore = usePageDataStore()
|
||||
const { getResourcesData } = useResourcesData()
|
||||
const { getCwmsArticleData } = useResourcesData()
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
const { pageData } = storeToRefs(pageDataStore)
|
||||
|
||||
const defaultSlideItem = {
|
||||
article_id: '',
|
||||
media_thumbnail_url: '',
|
||||
title: '',
|
||||
create_datetime: 0,
|
||||
}
|
||||
|
||||
const boardId = computed(
|
||||
() => getComponentGroup(props.components, 'boardId')?.display?.text
|
||||
)
|
||||
const paginationData = computed(() => {
|
||||
return getComponentGroupAry(props.components, 'pagination')
|
||||
})
|
||||
const backgroundData = computed(() =>
|
||||
getComponentGroup(props.components, 'background')
|
||||
)
|
||||
const mainTitleData = computed(() =>
|
||||
getComponentGroup(props.components, 'mainTitle')
|
||||
)
|
||||
|
||||
// const buttonListData = ref(
|
||||
// getComponentGroupAry(slideData?.value[0], 'buttonList')
|
||||
// )
|
||||
const buttonListData = ref(getComponentGroupAry(props.components, 'buttonList'))
|
||||
const descriptionData = computed(() =>
|
||||
getComponentGroup(props.components, 'description')
|
||||
)
|
||||
|
||||
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) {
|
||||
if (!boardId.value) {
|
||||
return []
|
||||
}
|
||||
|
||||
const operateGroupList = await getResourcesData({
|
||||
pageSeq: pageData.value.page_seq,
|
||||
pageVer: pageData.value.page_ver,
|
||||
pageVerTmplSeq: props.pageVerTmplSeq,
|
||||
langCode: locale.value,
|
||||
const cwmsArticleData = await getCwmsArticleData('BOARD', boardId.value, {
|
||||
sortTypeCode: 'LATEST',
|
||||
translationYn: true,
|
||||
page: 1,
|
||||
size: 20,
|
||||
})
|
||||
|
||||
console.log('operateGroupList', operateGroupList)
|
||||
|
||||
const bannerList = getComponentContainer(operateGroupList, 'bannerList', {
|
||||
hasGroup: true,
|
||||
minLength: 4,
|
||||
}) as OperateGroupItem[]
|
||||
|
||||
return bannerList
|
||||
return cwmsArticleData?.list as CwmsArticleItem[]
|
||||
},
|
||||
{
|
||||
default: () => [],
|
||||
@@ -62,6 +66,36 @@ const { data: slideData } = await useAsyncData(
|
||||
}
|
||||
)
|
||||
|
||||
const getResponsiveMultiple = (): number => {
|
||||
const breakpoints = useResponsiveBreakpoints()
|
||||
|
||||
if (breakpoints.value.isSm) return 1 // sm: 1개 단위
|
||||
if (breakpoints.value.isMd) return 2 // md: 2개 단위
|
||||
return 4 // 그 외: 4개 단위
|
||||
}
|
||||
|
||||
const fillToMultipleOfFour = (arr: CwmsArticleItem[]): CwmsArticleItem[] => {
|
||||
const remainderSize = getResponsiveMultiple()
|
||||
const remainder = arr.length % remainderSize
|
||||
const fillCount = remainder === 0 ? 0 : remainderSize - remainder
|
||||
return [
|
||||
...arr,
|
||||
...Array(fillCount).fill({ ...defaultSlideItem } as CwmsArticleItem),
|
||||
]
|
||||
}
|
||||
|
||||
const filledSlideData = computed(() => {
|
||||
return fillToMultipleOfFour(slideData.value)
|
||||
})
|
||||
|
||||
const getArticleUrl = (articleId: string) => {
|
||||
const communityUrl = gameData.value?.url_json?.community
|
||||
if (!communityUrl || !articleId || !boardId.value) {
|
||||
return ''
|
||||
}
|
||||
return `${communityUrl}/view/${articleId}?boardKey=${boardId.value}`
|
||||
}
|
||||
|
||||
const onArrowClick = (direction, targetIndex) => {
|
||||
const arrowGroupAry = getComponentGroupAry(props.components, 'arrow')
|
||||
const logTracking = arrowGroupAry?.[direction === 'prev' ? 0 : 1]
|
||||
@@ -70,7 +104,10 @@ const onArrowClick = (direction, targetIndex) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="relative py-[80px] md:py-[120px]">
|
||||
<section
|
||||
class="relative py-[80px] md:py-[120px]"
|
||||
:style="getPaginationClass(paginationData)"
|
||||
>
|
||||
<WidgetsBackground v-if="backgroundData" :resources-data="backgroundData" />
|
||||
<div class="section-content px-0">
|
||||
<WidgetsMainTitle
|
||||
@@ -78,37 +115,84 @@ const onArrowClick = (direction, targetIndex) => {
|
||||
: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
|
||||
<ClientOnly>
|
||||
<BlocksSlideDefault
|
||||
v-if="slideData.length > 0"
|
||||
:slide-item-length="slideData.length"
|
||||
:per-page="4"
|
||||
:drag="false"
|
||||
:arrows="slideData.length > 4"
|
||||
:pagination="slideData.length > 4"
|
||||
:breakpoints="{
|
||||
[BREAKPOINTS.lg - 1]: {
|
||||
perPage: 2,
|
||||
arrows: slideData.length > 2,
|
||||
pagination: slideData.length > 2,
|
||||
},
|
||||
[BREAKPOINTS.md - 1]: {
|
||||
drag: true,
|
||||
perPage: 1,
|
||||
arrows: false,
|
||||
pagination: false,
|
||||
},
|
||||
}"
|
||||
:class="{ 'is-single': slideData.length === 1 }"
|
||||
@arrow-click="onArrowClick"
|
||||
>
|
||||
<SplideSlide v-for="(item, index) in filledSlideData" :key="index">
|
||||
<div class="slide-inner">
|
||||
<BlocksCardNews
|
||||
:title="item.title"
|
||||
:description="
|
||||
formatTimestamp(item?.create_datetime, 'YYYY.MM.DD')
|
||||
"
|
||||
:img-path="getResolvedHost(item?.media_thumbnail_url)"
|
||||
:url="getArticleUrl(item?.article_id)"
|
||||
link-target="_blank"
|
||||
/>
|
||||
</div>
|
||||
</SplideSlide>
|
||||
</BlocksSlideDefault>
|
||||
</ClientOnly>
|
||||
<WidgetsButtonList
|
||||
v-if="buttonListData"
|
||||
:resources-data="buttonListData"
|
||||
:page-ver-tmpl-seq="props.pageVerTmplSeq"
|
||||
class="mt-[40px] md:mt-[56px]"
|
||||
/> -->
|
||||
class="mt-[48px] md:mt-[64px]"
|
||||
/>
|
||||
<WidgetsDescription
|
||||
v-if="descriptionData"
|
||||
:resources-data="descriptionData"
|
||||
class="mt-[32px] md:mt-[48px]"
|
||||
/>
|
||||
</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)];
|
||||
.splide {
|
||||
@apply mt-[24px] md:max-w-[776px] md:mt-[48px] md:mx-auto md:px-[72px] lg:max-w-[1428px];
|
||||
}
|
||||
.splide:deep(.splide__track) {
|
||||
@apply !px-[20px] md:max-w-[632px] lg:max-w-[1284px] md:mx-auto md:!px-[0] sm:!px-[40px];
|
||||
}
|
||||
.splide:deep(.arrow-prev) {
|
||||
@apply top-[calc(50%-28px)] left-[0];
|
||||
}
|
||||
.splide:deep(.arrow-next) {
|
||||
@apply top-[calc(50%-28px)] right-[0];
|
||||
}
|
||||
.splide__slide {
|
||||
@apply mr-[20px];
|
||||
}
|
||||
.slide-inner {
|
||||
@apply w-[275px] aspect-[1/1] md:w-[306px] md:box-border;
|
||||
}
|
||||
|
||||
.splide.is-single {
|
||||
@apply flex justify-center w-auto;
|
||||
}
|
||||
.splide.is-single:deep(.splide__track) {
|
||||
@apply w-[calc(100%-20px)] md:w-auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -114,7 +114,6 @@ const onArrowClick = (direction, targetIndex) => {
|
||||
ref="slideThumbnailRef"
|
||||
:slide-data="slideData"
|
||||
variant="media"
|
||||
:drag="false"
|
||||
class="mt-[24px] md:mt-[32px]"
|
||||
:pagination-data="paginationData"
|
||||
@move="stopVideo"
|
||||
|
||||
@@ -99,7 +99,10 @@ const onArrowClick = (direction, targetIndex) => {
|
||||
|
||||
<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)]
|
||||
@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)] before:z-[5]
|
||||
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)];
|
||||
}
|
||||
.slide-inner {
|
||||
@apply bg-black;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,7 +17,7 @@ interface Props {
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const pageDataStore = usePageDataStore()
|
||||
const { getResourcesData } = useResourcesData()
|
||||
const { getOperateResourcesData } = useResourcesData()
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||
|
||||
@@ -43,7 +43,7 @@ const { data: slideData } = await useAsyncData(
|
||||
return []
|
||||
}
|
||||
|
||||
const operateGroupList = await getResourcesData({
|
||||
const operateGroupList = await getOperateResourcesData({
|
||||
pageSeq: pageData.value.page_seq,
|
||||
pageVer: pageData.value.page_ver,
|
||||
pageVerTmplSeq: props.pageVerTmplSeq,
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
getComponentGroup,
|
||||
hasComponentGroup,
|
||||
} from '#layers/utils/dataUtil'
|
||||
import { getPaginationClass } from '#layers/utils/styleUtil'
|
||||
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface GameDataValue {
|
||||
favicon_json: GameDataFavicon
|
||||
meta_tag_json: GameDataMetaTag
|
||||
sns_json: GameDataSns
|
||||
url_json: Record<string, { url: string }>
|
||||
footer_json: string // JSON 문자열로 변경
|
||||
comm_img_json: GameDataCommImg
|
||||
market_json: Record<string, { url: string }>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Resources Data API 타입 정의
|
||||
*/
|
||||
|
||||
// 리스트 운영 그룹 아이템
|
||||
// OperateResources
|
||||
export interface OperateGroupItem {
|
||||
seq: number
|
||||
flag_type?: number
|
||||
@@ -25,15 +25,13 @@ export interface OperateComponents {
|
||||
[key: string]: OperateGroupList
|
||||
}
|
||||
|
||||
// Resources Data API 응답
|
||||
export interface ResourcesDataResponse {
|
||||
export interface OperateResourcesDataResponse {
|
||||
code: number
|
||||
message: string
|
||||
value: OperateComponents
|
||||
}
|
||||
|
||||
// getResourcesData 함수 파라미터
|
||||
export interface GetResourcesDataParams {
|
||||
export interface getOperateResourcesDataParams {
|
||||
pageSeq: string
|
||||
pageVer: string
|
||||
pageVerTmplSeq: number
|
||||
@@ -41,3 +39,83 @@ export interface GetResourcesDataParams {
|
||||
q?: string
|
||||
qc?: string
|
||||
}
|
||||
|
||||
// CwmsArticle
|
||||
export interface CwmsArticleAdminOptionSummary {
|
||||
notice_cancel_reservation_yn: 'Y' | 'N'
|
||||
publish_reservation_yn: 'Y' | 'N'
|
||||
}
|
||||
|
||||
export interface CwmsArticleAttachSummary {
|
||||
image_yn: 'Y' | 'N'
|
||||
movie_yn: 'Y' | 'N'
|
||||
poll_yn: 'Y' | 'N'
|
||||
attach_file_yn: 'Y' | 'N'
|
||||
music_yn: 'Y' | 'N'
|
||||
script_yn: 'Y' | 'N'
|
||||
}
|
||||
|
||||
export interface CwmsArticleSnapshotUserInfo {
|
||||
nickname: string
|
||||
profile_img: string
|
||||
}
|
||||
export interface CwmsArticleUserInfo {
|
||||
stove_nickname?: string
|
||||
user_game_info?: Record<string, unknown>
|
||||
snapshot_user_info?: CwmsArticleSnapshotUserInfo
|
||||
}
|
||||
|
||||
export interface CwmsArticleItem {
|
||||
board_seq: number
|
||||
channel_key: string
|
||||
channel_seq: number
|
||||
article_id: string
|
||||
headline_info?: {
|
||||
headline_seq: number
|
||||
headline_name: string
|
||||
}
|
||||
fixed_yn?: 'Y' | 'N'
|
||||
title: string
|
||||
subtitle: string
|
||||
content?: string
|
||||
summary?: string
|
||||
media_thumbnail_url: string | null
|
||||
media_count: number
|
||||
user_interaction_score_info: Record<string, unknown>
|
||||
create_datetime: number
|
||||
update_datetime: number
|
||||
article_status_code: string
|
||||
user_info: CwmsArticleUserInfo
|
||||
attach_summary_info: CwmsArticleAttachSummary
|
||||
admin_option_summary_info: CwmsArticleAdminOptionSummary
|
||||
view_mode: string
|
||||
source_lang: string | null
|
||||
target_lang?: string | null
|
||||
translation_status: string | null
|
||||
}
|
||||
|
||||
export interface CwmsArticleData {
|
||||
total: number
|
||||
page: number
|
||||
size: number
|
||||
list: CwmsArticleItem[]
|
||||
}
|
||||
|
||||
export interface CwmsArticleDataResponse {
|
||||
code: number
|
||||
message: string
|
||||
value: CwmsArticleData
|
||||
}
|
||||
|
||||
export interface getCwmsArticleDataParams {
|
||||
lang?: string
|
||||
sortTypeCode?: string
|
||||
interactionTypeCodes?: string[]
|
||||
handleCode?: boolean
|
||||
contentYn?: boolean
|
||||
summaryYn?: boolean
|
||||
headlineTitleYn?: boolean
|
||||
translationYn?: boolean
|
||||
page?: number
|
||||
size?: number
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ import { csrFormatJWT } from '#layers/utils/formatUtil'
|
||||
* Stove 로그인
|
||||
*/
|
||||
export const csrGoStoveLogin = () => {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const config = useRuntimeConfig()
|
||||
const gameDataStore = useGameDataStore()
|
||||
|
||||
const loginUrl = runtimeConfig.public.stoveLoginUrl
|
||||
const loginUrl = config.public.stoveLoginUrl
|
||||
const stoveGameId = gameDataStore.gameData?.game_id
|
||||
const stoveGameNo = gameDataStore.gameData?.game_code
|
||||
const redirectUrl = encodeURIComponent(location.href)
|
||||
|
||||
|
Before Width: | Height: | Size: 415 KiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 1.4 MiB |
BIN
public/images/sample/GR_DETAIL_03/common/video02_m.mp4
Normal file
BIN
public/images/sample/GR_DETAIL_03/common/video03_m.mp4
Normal file
BIN
public/images/sample/GR_GALLERY_01/common/bg.jpg
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
public/images/sample/GR_GALLERY_01/common/bg_m.jpg
Normal file
|
After Width: | Height: | Size: 698 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 772 KiB |
|
Before Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 772 KiB |
|
Before Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 415 KiB After Width: | Height: | Size: 415 KiB |
|
Before Width: | Height: | Size: 415 KiB |
|
Before Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
BIN
public/images/sample/GR_GALLERY_02/ko/img_slide01_m.jpg
Normal file
|
After Width: | Height: | Size: 255 KiB |
BIN
public/images/sample/GR_GALLERY_02/ko/img_slide02_m.jpg
Normal file
|
After Width: | Height: | Size: 255 KiB |
BIN
public/images/sample/GR_GALLERY_02/ko/img_slide03_m .jpg
Normal file
|
After Width: | Height: | Size: 255 KiB |
BIN
public/images/sample/GR_GALLERY_02/zh-tw/img_slide01_m.jpg
Normal file
|
After Width: | Height: | Size: 255 KiB |
BIN
public/images/sample/GR_GALLERY_02/zh-tw/img_slide02_m.jpg
Normal file
|
After Width: | Height: | Size: 255 KiB |
BIN
public/images/sample/GR_GALLERY_02/zh-tw/img_slide03_m .jpg
Normal file
|
After Width: | Height: | Size: 255 KiB |
BIN
public/images/sample/GR_GALLERY_03/common/bg.jpg
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 415 KiB |
|
Before Width: | Height: | Size: 171 KiB |
BIN
public/images/sample/GR_GALLERY_03/common/bg_m.jpg
Normal file
|
After Width: | Height: | Size: 484 KiB |
|
Before Width: | Height: | Size: 264 KiB |
BIN
public/images/sample/GR_VISUAL_01/ko/title_main_m.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
BIN
public/images/sample/GR_VISUAL_01/ko/title_sub_m.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
public/images/sample/GR_VISUAL_01/zh-tw/title_main_m.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
BIN
public/images/sample/GR_VISUAL_01/zh-tw/title_sub_m.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 773 KiB After Width: | Height: | Size: 794 KiB |
BIN
public/images/sample/GR_VISUAL_02/ko/title_main_m.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
BIN
public/images/sample/GR_VISUAL_02/ko/title_sub_m.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
public/images/sample/GR_VISUAL_02/zh-tw/title_main_m.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
BIN
public/images/sample/GR_VISUAL_02/zh-tw/title_sub_m.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
public/images/sample/GR_VISUAL_03/common/bg01_m.jpg
Normal file
|
After Width: | Height: | Size: 425 KiB |
BIN
public/images/sample/GR_VISUAL_03/common/bg02_m.jpg
Normal file
|
After Width: | Height: | Size: 425 KiB |
BIN
public/images/sample/GR_VISUAL_03/common/bg03_m.jpg
Normal file
|
After Width: | Height: | Size: 425 KiB |