Merge branch 'feature/20251024_cl_GR_BOARD_01' into feature/202501107-all

This commit is contained in:
clkim
2025-10-28 19:05:14 +09:00
63 changed files with 392 additions and 117 deletions

View File

@@ -19,8 +19,8 @@
/* 라이트 테마 색상 */
[data-theme='light'] {
body {
@apply bg-white;
.main {
@apply bg-theme-foreground;
}
}
}

File diff suppressed because one or more lines are too long

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 415 KiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 772 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 772 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

View File

Before

Width:  |  Height:  |  Size: 415 KiB

After

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

View File

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 773 KiB

After

Width:  |  Height:  |  Size: 794 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB