feat. GR_BOARD_01 템플릿 제작

This commit is contained in:
clkim
2025-10-28 19:02:14 +09:00
parent dbe751e032
commit ccdac87c52
10 changed files with 359 additions and 96 deletions

File diff suppressed because one or more lines are too long

View File

@@ -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>

View File

@@ -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"
> >

View File

@@ -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,
})) }))
} }

View File

@@ -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,
} }
} }

View File

@@ -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 },

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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 }>

View File

@@ -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
}