feat. GR_BOARD_01 템플릿 제작
This commit is contained in:
30
layers/components/atoms/icons/Stove.vue
Normal file
30
layers/components/atoms/icons/Stove.vue
Normal file
File diff suppressed because one or more lines are too long
@@ -3,7 +3,7 @@ interface Props {
|
|||||||
title: string
|
title: string
|
||||||
description: string | number
|
description: string | number
|
||||||
imgPath: string
|
imgPath: string
|
||||||
linkTarget?: string
|
linkTarget?: '_blank' | '_self'
|
||||||
url?: string
|
url?: string
|
||||||
alt?: string
|
alt?: string
|
||||||
class?: string
|
class?: string
|
||||||
@@ -13,6 +13,10 @@ const props = defineProps<Props>()
|
|||||||
const { locale } = useI18n()
|
const { locale } = useI18n()
|
||||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||||
|
|
||||||
|
const isShowOverlay = computed(() => {
|
||||||
|
return props.title || props.description
|
||||||
|
})
|
||||||
|
|
||||||
const handleLinkClick = (title: string) => {
|
const handleLinkClick = (title: string) => {
|
||||||
const trackingData = {
|
const trackingData = {
|
||||||
tracking: {
|
tracking: {
|
||||||
@@ -26,17 +30,18 @@ const handleLinkClick = (title: string) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div :class="['card-news', { 'no-image': !props.imgPath }, props.class]">
|
||||||
v-if="props.title || props.description"
|
|
||||||
:class="`card-news ${props.class || ''}`"
|
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
|
v-if="props.imgPath"
|
||||||
:src="props.imgPath"
|
:src="props.imgPath"
|
||||||
:alt="props.title || props.alt"
|
:alt="props.title || props.alt"
|
||||||
class="card-image"
|
class="card-image"
|
||||||
loading="lazy"
|
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">
|
<h3 v-if="props.title" class="card-title">
|
||||||
{{ props.title }}
|
{{ props.title }}
|
||||||
</h3>
|
</h3>
|
||||||
@@ -47,7 +52,7 @@ const handleLinkClick = (title: string) => {
|
|||||||
<a
|
<a
|
||||||
v-if="props.url"
|
v-if="props.url"
|
||||||
:href="props.url"
|
:href="props.url"
|
||||||
:target="props.linkTarget"
|
:target="props.linkTarget || '_self'"
|
||||||
class="card-link"
|
class="card-link"
|
||||||
@click="handleLinkClick(props.title)"
|
@click="handleLinkClick(props.title)"
|
||||||
/>
|
/>
|
||||||
@@ -56,7 +61,7 @@ const handleLinkClick = (title: string) => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.card-news {
|
.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:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full
|
||||||
after:border after:border-white/10 after:rounded-lg;
|
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;
|
@apply transition-transform duration-300 w-full h-full object-cover;
|
||||||
}
|
}
|
||||||
.card-overlay {
|
.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 {
|
.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 {
|
.card-description {
|
||||||
@apply mt-[6px] text-[12px] leading-[18px] text-white/50 md:mt-1 md:text-[14px] md:leading-[24px];
|
@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 {
|
.card-link {
|
||||||
@apply absolute top-0 left-0 w-full h-full z-[5];
|
@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>
|
</style>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ interface Props {
|
|||||||
slideItemLength?: number
|
slideItemLength?: number
|
||||||
autoplay?: boolean
|
autoplay?: boolean
|
||||||
perPage?: number
|
perPage?: number
|
||||||
|
drag?: boolean
|
||||||
arrows?: boolean
|
arrows?: boolean
|
||||||
pagination?: boolean
|
pagination?: boolean
|
||||||
paginationData?: PageDataResourceGroups
|
paginationData?: PageDataResourceGroups
|
||||||
@@ -16,6 +17,8 @@ interface Props {
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
autoplay: false,
|
autoplay: false,
|
||||||
|
perPage: 1,
|
||||||
|
drag: true,
|
||||||
arrows: true,
|
arrows: true,
|
||||||
pagination: true,
|
pagination: true,
|
||||||
})
|
})
|
||||||
@@ -25,26 +28,25 @@ const emit = defineEmits(['mounted', 'move', 'arrowClick'])
|
|||||||
// Splide 화살표 로직을 위한 composable 사용
|
// Splide 화살표 로직을 위한 composable 사용
|
||||||
const { addArrowClickListeners } = useSplideArrow()
|
const { addArrowClickListeners } = useSplideArrow()
|
||||||
|
|
||||||
const perPage = computed(() => props.perPage ?? 1)
|
const slideType = computed(() => {
|
||||||
const isMultipleItems = computed(
|
|
||||||
() => (props.slideItemLength ?? 0) > perPage.value
|
|
||||||
)
|
|
||||||
const resolvedType = computed<ResponsiveOptions['type']>(() => {
|
|
||||||
if (props.type) return props.type
|
if (props.type) return props.type
|
||||||
return isMultipleItems.value ? 'loop' : 'slide'
|
|
||||||
|
return (props.slideItemLength ?? 0) > 1 ? 'loop' : 'slide'
|
||||||
})
|
})
|
||||||
|
|
||||||
const options = computed((): ResponsiveOptions => {
|
const options = computed((): ResponsiveOptions => {
|
||||||
return {
|
return {
|
||||||
type: resolvedType.value,
|
type: slideType.value,
|
||||||
autoWidth: true,
|
autoWidth: true,
|
||||||
autoHeight: true,
|
autoHeight: true,
|
||||||
perPage: perPage.value,
|
perPage: props.perPage,
|
||||||
speed: 500,
|
speed: 500,
|
||||||
updateOnMove: true,
|
updateOnMove: true,
|
||||||
autoplay: props.autoplay,
|
autoplay: props.autoplay,
|
||||||
arrows: props.arrows && isMultipleItems.value,
|
drag: props.drag,
|
||||||
pagination: props.pagination && isMultipleItems.value,
|
trimSpace: false,
|
||||||
|
arrows: props.arrows,
|
||||||
|
pagination: props.pagination,
|
||||||
classes: {
|
classes: {
|
||||||
arrows: 'splide-arrows',
|
arrows: 'splide-arrows',
|
||||||
arrow: 'splide-arrow',
|
arrow: 'splide-arrow',
|
||||||
@@ -82,7 +84,7 @@ const handleMove = (
|
|||||||
<Splide
|
<Splide
|
||||||
:options="options"
|
:options="options"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
:style="getPaginationClass(props.paginationData)"
|
:style="getPaginationClass(props?.paginationData)"
|
||||||
@splide:mounted="handleSplideMounted"
|
@splide:mounted="handleSplideMounted"
|
||||||
@splide:move="handleMove"
|
@splide:move="handleMove"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useMediaQuery } from '@vueuse/core'
|
import { useBreakpoints } from '@vueuse/core'
|
||||||
import { getDeviceSrc } from '#layers/utils/styleUtil'
|
import { getDeviceSrc } from '#layers/utils/styleUtil'
|
||||||
import type { PageDataResourceGroupResPath } from '#layers/types/api/pageData'
|
import type { PageDataResourceGroupResPath } from '#layers/types/api/pageData'
|
||||||
|
|
||||||
const BREAKPOINTS = {
|
export const BREAKPOINTS = {
|
||||||
xs: 360,
|
xs: 360,
|
||||||
sm: 768,
|
sm: 768,
|
||||||
md: 1024,
|
md: 1024,
|
||||||
@@ -10,24 +10,16 @@ const BREAKPOINTS = {
|
|||||||
} as const
|
} as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* useMediaQuery 기반 반응형 브레이크포인트
|
* useBreakpoints 기반 반응형 브레이크포인트
|
||||||
*/
|
*/
|
||||||
export const useResponsiveBreakpoints = () => {
|
export const useResponsiveBreakpoints = () => {
|
||||||
const ssrWidth = BREAKPOINTS.xs
|
const breakpoints = useBreakpoints(BREAKPOINTS)
|
||||||
const isXs = useMediaQuery(`(min-width: ${BREAKPOINTS.xs}px)`, { ssrWidth })
|
const isXs = breakpoints.smaller('sm') // < 768px
|
||||||
const isSm = useMediaQuery(`(min-width: ${BREAKPOINTS.sm}px)`, { ssrWidth })
|
const isSm = breakpoints.smaller('md') // < 1024px
|
||||||
const isMd = useMediaQuery(`(min-width: ${BREAKPOINTS.md}px)`, { ssrWidth })
|
const isMd = breakpoints.smaller('lg') // < 1440px
|
||||||
const isLg = useMediaQuery(`(min-width: ${BREAKPOINTS.lg}px)`, { ssrWidth })
|
const isLg = breakpoints.greater('lg') // >= 1440px
|
||||||
const isMobile = useMediaQuery(`(max-width: ${BREAKPOINTS.md - 1}px)`, {
|
const isMobile = isXs || isSm
|
||||||
ssrWidth,
|
const isDesktop = isLg || isMd
|
||||||
})
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
|
|
||||||
return computed(() => ({
|
return computed(() => ({
|
||||||
isXs: isXs.value,
|
isXs: isXs.value,
|
||||||
@@ -35,7 +27,6 @@ export const useResponsiveBreakpoints = () => {
|
|||||||
isMd: isMd.value,
|
isMd: isMd.value,
|
||||||
isLg: isLg.value,
|
isLg: isLg.value,
|
||||||
isMobile: isMobile.value,
|
isMobile: isMobile.value,
|
||||||
isTablet: isTablet.value,
|
|
||||||
isDesktop: isDesktop.value,
|
isDesktop: isDesktop.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,18 @@ import type {
|
|||||||
getOperateResourcesDataParams,
|
getOperateResourcesDataParams,
|
||||||
OperateResourcesDataResponse,
|
OperateResourcesDataResponse,
|
||||||
OperateComponents,
|
OperateComponents,
|
||||||
|
getCwmsArticleDataParams,
|
||||||
|
CwmsArticleDataResponse,
|
||||||
|
CwmsArticleData,
|
||||||
} from '#layers/types/api/resourcesData'
|
} from '#layers/types/api/resourcesData'
|
||||||
|
|
||||||
export const useResourcesData = () => {
|
export const useResourcesData = () => {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param params getOperateResourcesDataParams
|
||||||
|
* @returns OperateComponents | null
|
||||||
|
* @description 운영 리소스 데이터 조회
|
||||||
|
*/
|
||||||
const getOperateResourcesData = async (
|
const getOperateResourcesData = async (
|
||||||
params: getOperateResourcesDataParams
|
params: getOperateResourcesDataParams
|
||||||
): Promise<OperateComponents | null> => {
|
): Promise<OperateComponents | null> => {
|
||||||
@@ -35,7 +44,60 @@ export const useResourcesData = () => {
|
|||||||
return null
|
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
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getOperateResourcesData,
|
getOperateResourcesData,
|
||||||
|
getCwmsArticleData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import GrGallery03 from '#layers/templates/GrGallery03/index.vue'
|
|||||||
import GrDetail01 from '#layers/templates/GrDetail01/index.vue'
|
import GrDetail01 from '#layers/templates/GrDetail01/index.vue'
|
||||||
import GrDetail02 from '#layers/templates/GrDetail02/index.vue'
|
import GrDetail02 from '#layers/templates/GrDetail02/index.vue'
|
||||||
import GrDetail03 from '#layers/templates/GrDetail03/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'
|
import GrContents01 from '#layers/templates/GrContents01/index.vue'
|
||||||
|
|
||||||
const templateRegistry = {
|
const templateRegistry = {
|
||||||
@@ -17,7 +17,7 @@ const templateRegistry = {
|
|||||||
GR_GALLERY_01: { component: GrGallery01 },
|
GR_GALLERY_01: { component: GrGallery01 },
|
||||||
GR_GALLERY_02: { component: GrGallery02 },
|
GR_GALLERY_02: { component: GrGallery02 },
|
||||||
GR_GALLERY_03: { component: GrGallery03 },
|
GR_GALLERY_03: { component: GrGallery03 },
|
||||||
// GR_BOARD_01: { component: GrBoard01 },
|
GR_BOARD_01: { component: GrBoard01 },
|
||||||
GR_DETAIL_01: { component: GrDetail01 },
|
GR_DETAIL_01: { component: GrDetail01 },
|
||||||
GR_DETAIL_02: { component: GrDetail02 },
|
GR_DETAIL_02: { component: GrDetail02 },
|
||||||
GR_DETAIL_03: { component: GrDetail03 },
|
GR_DETAIL_03: { component: GrDetail03 },
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { SplideSlide } from '@splidejs/vue-splide'
|
import { SplideSlide } from '@splidejs/vue-splide'
|
||||||
import {
|
import { getComponentGroup } from '#layers/utils/dataUtil'
|
||||||
getComponentContainer,
|
|
||||||
getComponentGroup,
|
|
||||||
} from '#layers/utils/dataUtil'
|
|
||||||
import type { Splide as SplideType } from '@splidejs/splide'
|
|
||||||
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
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 {
|
interface Props {
|
||||||
components: PageDataTemplateComponents
|
components: PageDataTemplateComponents
|
||||||
@@ -15,46 +11,54 @@ interface Props {
|
|||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const gameDataStore = useGameDataStore()
|
||||||
const pageDataStore = usePageDataStore()
|
const pageDataStore = usePageDataStore()
|
||||||
const { getResourcesData } = useResourcesData()
|
const { getCwmsArticleData } = useResourcesData()
|
||||||
const { locale } = useI18n()
|
const { locale } = useI18n()
|
||||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||||
|
|
||||||
|
const { gameData } = storeToRefs(gameDataStore)
|
||||||
const { pageData } = storeToRefs(pageDataStore)
|
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(() =>
|
const backgroundData = computed(() =>
|
||||||
getComponentGroup(props.components, 'background')
|
getComponentGroup(props.components, 'background')
|
||||||
)
|
)
|
||||||
const mainTitleData = computed(() =>
|
const mainTitleData = computed(() =>
|
||||||
getComponentGroup(props.components, 'mainTitle')
|
getComponentGroup(props.components, 'mainTitle')
|
||||||
)
|
)
|
||||||
|
const buttonListData = ref(getComponentGroupAry(props.components, 'buttonList'))
|
||||||
// const buttonListData = ref(
|
const descriptionData = computed(() =>
|
||||||
// getComponentGroupAry(slideData?.value[0], 'buttonList')
|
getComponentGroup(props.components, 'description')
|
||||||
// )
|
)
|
||||||
|
|
||||||
const { data: slideData } = await useAsyncData(
|
const { data: slideData } = await useAsyncData(
|
||||||
`gr-board-01-resources-${pageData.value?.page_seq}-${pageData.value?.page_ver}-${props.pageVerTmplSeq}`,
|
`gr-board-01-resources-${pageData.value?.page_seq}-${pageData.value?.page_ver}-${props.pageVerTmplSeq}`,
|
||||||
async () => {
|
async () => {
|
||||||
if (!pageData.value?.page_seq || !pageData.value?.page_ver) {
|
if (!boardId.value) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const operateGroupList = await getResourcesData({
|
const cwmsArticleData = await getCwmsArticleData('BOARD', boardId.value, {
|
||||||
pageSeq: pageData.value.page_seq,
|
sortTypeCode: 'LATEST',
|
||||||
pageVer: pageData.value.page_ver,
|
translationYn: true,
|
||||||
pageVerTmplSeq: props.pageVerTmplSeq,
|
page: 1,
|
||||||
langCode: locale.value,
|
size: 20,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('operateGroupList', operateGroupList)
|
return cwmsArticleData?.list as CwmsArticleItem[]
|
||||||
|
|
||||||
const bannerList = getComponentContainer(operateGroupList, 'bannerList', {
|
|
||||||
hasGroup: true,
|
|
||||||
minLength: 4,
|
|
||||||
}) as OperateGroupItem[]
|
|
||||||
|
|
||||||
return bannerList
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
default: () => [],
|
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 onArrowClick = (direction, targetIndex) => {
|
||||||
const arrowGroupAry = getComponentGroupAry(props.components, 'arrow')
|
const arrowGroupAry = getComponentGroupAry(props.components, 'arrow')
|
||||||
const logTracking = arrowGroupAry?.[direction === 'prev' ? 0 : 1]
|
const logTracking = arrowGroupAry?.[direction === 'prev' ? 0 : 1]
|
||||||
@@ -70,7 +104,10 @@ const onArrowClick = (direction, targetIndex) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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" />
|
<WidgetsBackground v-if="backgroundData" :resources-data="backgroundData" />
|
||||||
<div class="section-content px-0">
|
<div class="section-content px-0">
|
||||||
<WidgetsMainTitle
|
<WidgetsMainTitle
|
||||||
@@ -78,37 +115,84 @@ const onArrowClick = (direction, targetIndex) => {
|
|||||||
:resources-data="mainTitleData"
|
:resources-data="mainTitleData"
|
||||||
class="title-md"
|
class="title-md"
|
||||||
/>
|
/>
|
||||||
<BlocksSlideDefault
|
<ClientOnly>
|
||||||
v-if="slideData"
|
<BlocksSlideDefault
|
||||||
:slide-item-length="slideData?.length"
|
v-if="slideData.length > 0"
|
||||||
:pagination="false"
|
:slide-item-length="slideData.length"
|
||||||
class="mt-[24px] md:mt-[48px]"
|
:per-page="4"
|
||||||
@arrow-click="onArrowClick"
|
:drag="false"
|
||||||
>
|
:arrows="slideData.length > 4"
|
||||||
<SplideSlide v-for="(item, index) in slideData" :key="index">
|
:pagination="slideData.length > 4"
|
||||||
<div class="slide-inner border-line">
|
:breakpoints="{
|
||||||
<BlocksVisualContent
|
[BREAKPOINTS.lg - 1]: {
|
||||||
:resources-data="getComponentGroup(item, 'imgList')"
|
perPage: 2,
|
||||||
:page-ver-tmpl-seq="props.pageVerTmplSeq"
|
arrows: slideData.length > 2,
|
||||||
object-fit="cover"
|
pagination: slideData.length > 2,
|
||||||
:alt="getComponentGroup(item, 'subTitle')?.display?.text"
|
},
|
||||||
/>
|
[BREAKPOINTS.md - 1]: {
|
||||||
</div>
|
drag: true,
|
||||||
</SplideSlide>
|
perPage: 1,
|
||||||
</BlocksSlideDefault>
|
arrows: false,
|
||||||
<!-- <WidgetsButtonList
|
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"
|
v-if="buttonListData"
|
||||||
:resources-data="buttonListData"
|
:resources-data="buttonListData"
|
||||||
:page-ver-tmpl-seq="props.pageVerTmplSeq"
|
: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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.section-container {
|
.splide {
|
||||||
@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 mt-[24px] md:max-w-[776px] md:mt-[48px] md:mx-auto md:px-[72px] lg:max-w-[1428px];
|
||||||
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: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>
|
</style>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
getComponentGroup,
|
getComponentGroup,
|
||||||
hasComponentGroup,
|
hasComponentGroup,
|
||||||
} from '#layers/utils/dataUtil'
|
} from '#layers/utils/dataUtil'
|
||||||
import { getPaginationClass } from '#layers/utils/styleUtil'
|
|
||||||
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export interface GameDataValue {
|
|||||||
favicon_json: GameDataFavicon
|
favicon_json: GameDataFavicon
|
||||||
meta_tag_json: GameDataMetaTag
|
meta_tag_json: GameDataMetaTag
|
||||||
sns_json: GameDataSns
|
sns_json: GameDataSns
|
||||||
|
url_json: Record<string, { url: string }>
|
||||||
footer_json: string // JSON 문자열로 변경
|
footer_json: string // JSON 문자열로 변경
|
||||||
comm_img_json: GameDataCommImg
|
comm_img_json: GameDataCommImg
|
||||||
market_json: Record<string, { url: string }>
|
market_json: Record<string, { url: string }>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Resources Data API 타입 정의
|
* Resources Data API 타입 정의
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 리스트 운영 그룹 아이템
|
// OperateResources
|
||||||
export interface OperateGroupItem {
|
export interface OperateGroupItem {
|
||||||
seq: number
|
seq: number
|
||||||
flag_type?: number
|
flag_type?: number
|
||||||
@@ -25,14 +25,12 @@ export interface OperateComponents {
|
|||||||
[key: string]: OperateGroupList
|
[key: string]: OperateGroupList
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resources Data API 응답
|
|
||||||
export interface OperateResourcesDataResponse {
|
export interface OperateResourcesDataResponse {
|
||||||
code: number
|
code: number
|
||||||
message: string
|
message: string
|
||||||
value: OperateComponents
|
value: OperateComponents
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOperateResourcesData 함수 파라미터
|
|
||||||
export interface getOperateResourcesDataParams {
|
export interface getOperateResourcesDataParams {
|
||||||
pageSeq: string
|
pageSeq: string
|
||||||
pageVer: string
|
pageVer: string
|
||||||
@@ -41,3 +39,83 @@ export interface getOperateResourcesDataParams {
|
|||||||
q?: string
|
q?: string
|
||||||
qc?: 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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user