feat: 점검 페이지 추가, 언어 정책 추가

This commit is contained in:
“hyeonggkim”
2025-10-31 15:30:06 +09:00
parent 7f040d432b
commit 74851e01ed
13 changed files with 629 additions and 356 deletions

View File

@@ -1,101 +1,107 @@
<template>
<section class="inspection-section">
<!-- 로고 -->
<div v-if="isClient" class="inspection-logo">
<img :src="logoImgUrl" alt="logo" class="w-full h-full object-contain" />
</div>
<clientOnly>
<!-- 로고 -->
<div class="inspection-logo">
<img :src="logoImgUrl" alt="logo" class="w-full h-full object-contain" />
</div>
<div class="inspection-content">
<!-- 점검 메시지 -->
<h1 class="inspection-title">
<template v-if="isClient && webInspectionData?.inspection_title1">
{{ webInspectionData.inspection_title1 }}
</template>
<template v-else>
<div class="inspection-content">
<!-- 점검 메시지 -->
<h1 class="inspection-title text-lg md:text-2xl">
{{ tm('Inspection_Now_Maintenance') }}
</template>
</h1>
</h1>
<div class="inspection-cards">
<!-- 점검 시간 카드 -->
<div v-if="isClient && webInspectionData" class="inspection-card inspection-time-card">
<h2 class="card-title">{{ tm('Inspection_Maintenance_Time') }}</h2>
<div class="inspection-time">
<div class="time-row">
{{ getLocaleTimezone(locale) }}
</div>
<div class="time-row">
{{ getLocaleTimezone('en', 'US') }}
</div>
<div class="time-row">
{{ getLocaleTimezone('zh-tw', '') }}
</div>
<div class="time-row">
{{ getLocaleTimezone('ja', '') }}
</div>
</div>
</div>
<!-- 온스토브 & 다운로드 카드 -->
<div class="inspection-bottom-cards">
<!-- 온스토브 카드 -->
<div class="inspection-card inspection-stove-card">
<h3 v-dompurify-html="tm('Inspection_Game_During_Maintenance') || '홈페이지 점검 중에도 게임과 공식 커뮤니티는 그대로 이용할 수 있어요!'" :class="{ 'text-center': !launchingStatus }" class="card-title"></h3>
<div class="button-group justify-center">
<a
:href="communityUrl"
target="_blank"
rel="noopener noreferrer"
class="inspection-btn inspection-btn-outline inspection-btn-community"
>
<span>{{ tm('Inspection_Community_Btn') || '공식 커뮤니티' }}</span>
<AtomsIconsLongArrowRightLine :size="16" color="#1F1F1F" />
</a>
<AtomsButtonVariant
v-if="launchingStatus"
type="custom"
platform="pc"
class="inspection-btn inspection-btn-primary w-full md:w-auto color-black"
@click="handleGameStart"
>
<span>게임 시작</span>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.3098 1.49172C8.86574 1.28049 7.15098 1.28055 5.70176 1.49167C3.50821 1.81315 1.81986 3.50786 1.49213 5.69168L1.4918 5.69391C1.38564 6.41904 1.33331 7.19181 1.33331 7.99998C1.33331 8.80815 1.3857 9.58136 1.49187 10.3065C1.81362 12.4934 3.50335 14.1805 5.69381 14.5079L5.69577 14.5082C6.42109 14.6143 7.19417 14.6666 7.99717 14.6666C8.80032 14.6666 9.57311 14.6143 10.3035 14.5083L10.3046 14.5082C12.4928 14.1865 14.1802 12.4917 14.5078 10.3083L14.5082 10.3061C14.6143 9.58092 14.6666 8.80815 14.6666 7.99998C14.6666 7.19527 14.6203 6.42148 14.5137 5.69347C14.1921 3.50726 12.4966 1.81316 10.3098 1.49172ZM6.38756 8.95267C6.39301 9.15365 6.40004 9.35195 6.40866 9.54742L6.40968 9.57054C6.41945 9.78867 6.43118 10.0033 6.44489 10.2141C6.45959 10.4433 6.68743 10.5836 6.88293 10.4823C7.22837 10.3029 7.58116 10.1096 7.9413 9.90379C8.20002 9.7541 8.46167 9.6013 8.72479 9.43914C8.98791 9.27854 9.24516 9.11482 9.49505 8.95111C9.84196 8.72502 10.1771 8.49581 10.4961 8.26817C10.6769 8.14031 10.6769 7.85965 10.4961 7.7318C10.1771 7.50415 9.84343 7.2765 9.49505 7.04886C9.24516 6.88514 8.98791 6.72298 8.72479 6.56082C8.46167 6.39866 8.20002 6.2443 7.9413 6.09618C7.58263 5.89036 7.22837 5.69702 6.88293 5.5177C6.68743 5.41636 6.45959 5.55669 6.44489 5.78589C6.43118 5.99671 6.41945 6.21129 6.40968 6.42943L6.40866 6.45254C6.40004 6.64801 6.39301 6.84631 6.38756 7.0473C6.37874 7.3607 6.37433 7.67722 6.37433 7.99998C6.37433 8.32274 6.37874 8.64082 6.38756 8.95267Z" fill="#332C2A"/>
</svg>
</AtomsButtonVariant>
<div class="inspection-cards">
<!-- 점검 시간 카드 -->
<div v-if="webInspectionData" class="inspection-card inspection-time-card">
<h2 class="card-title text-base text-md md:text-lg">{{ tm('Inspection_Maintenance_Time') }}</h2>
<div class="inspection-time text-sm md:text-base font-medium">
<div v-dompurify-html="getLocaleTimezone('ko', '')" class="time-row"></div>
<div v-dompurify-html="getLocaleTimezone('en', 'US')" class="time-row"></div>
<div v-dompurify-html="getLocaleTimezone('zh-tw', '')" class="time-row"></div>
<div v-dompurify-html="getLocaleTimezone('ja', '')" class="time-row"></div>
</div>
</div>
<!-- 다운로드 카드 -->
<div v-if="isClient" class="inspection-card inspection-download-card">
<h3 class="card-title">
{{ tm('Inspection_Txt_Download') || '게임 다운로드' }}
</h3>
<div v-if="enabledMarkets.length > 1" class="flex flex-row gap-3">
<template v-for="btn in enabledMarkets" :key="btn.platform">
<BlocksButtonDownload
:class="getButtonClass(btn.platform)"
:platform="btn.platform as any"
:url="btn.url"
type="single"
variant="outlined"
<!-- 온스토브 & 다운로드 카드 -->
<div class="inspection-bottom-cards">
<!-- 온스토브 카드 -->
<div class="inspection-card inspection-stove-card">
<h3 :class="{ 'text-center': !launchingStatus }" class="card-title text-base md:text-lg">
<span v-if="!launchingStatus" v-dompurify-html="tm('Inspection_Game_During_Maintenance')"></span>
<span v-else v-dompurify-html="tm('org_Inspection_During_Maintenance')"></span>
</h3>
<div class="button-group justify-center">
<!-- <a
:href="communityUrl"
target="_blank"
rel="noopener noreferrer"
class="inspection-btn inspection-btn-outline inspection-btn-community"
:class="{ 'bg-[var(--primary)]' : !launchingStatus }"
>
{{ getButtonText(btn.platform) }}
</BlocksButtonDownload>
</template>
<span>{{ tm('Inspection_Community_Btn') || '공식 커뮤니티' }}</span>
<AtomsIconsLongArrowRightLine :size="16" color="#1F1F1F" />
</a> -->
<AtomsButtonVariant
type="custom"
class="inspection-btn inspection-btn-community color-black text-sm md:text-base"
:class="{ 'inspection-btn-outline flex-1' : launchingStatus }"
@click="handleCommunityClick"
>
<span>{{ tm('Inspection_Community_Btn') }}</span>
<AtomsIconsLongArrowRightLine :size="16" color="#1F1F1F" />
</AtomsButtonVariant>
<AtomsButtonVariant
v-if="launchingStatus"
type="custom"
platform="pc"
class="inspection-btn inspection-btn-primary w-auto color-black text-sm md:text-base flex-1"
@click="handleGameStart"
>
<span>게임 시작</span>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.3098 1.49172C8.86574 1.28049 7.15098 1.28055 5.70176 1.49167C3.50821 1.81315 1.81986 3.50786 1.49213 5.69168L1.4918 5.69391C1.38564 6.41904 1.33331 7.19181 1.33331 7.99998C1.33331 8.80815 1.3857 9.58136 1.49187 10.3065C1.81362 12.4934 3.50335 14.1805 5.69381 14.5079L5.69577 14.5082C6.42109 14.6143 7.19417 14.6666 7.99717 14.6666C8.80032 14.6666 9.57311 14.6143 10.3035 14.5083L10.3046 14.5082C12.4928 14.1865 14.1802 12.4917 14.5078 10.3083L14.5082 10.3061C14.6143 9.58092 14.6666 8.80815 14.6666 7.99998C14.6666 7.19527 14.6203 6.42148 14.5137 5.69347C14.1921 3.50726 12.4966 1.81316 10.3098 1.49172ZM6.38756 8.95267C6.39301 9.15365 6.40004 9.35195 6.40866 9.54742L6.40968 9.57054C6.41945 9.78867 6.43118 10.0033 6.44489 10.2141C6.45959 10.4433 6.68743 10.5836 6.88293 10.4823C7.22837 10.3029 7.58116 10.1096 7.9413 9.90379C8.20002 9.7541 8.46167 9.6013 8.72479 9.43914C8.98791 9.27854 9.24516 9.11482 9.49505 8.95111C9.84196 8.72502 10.1771 8.49581 10.4961 8.26817C10.6769 8.14031 10.6769 7.85965 10.4961 7.7318C10.1771 7.50415 9.84343 7.2765 9.49505 7.04886C9.24516 6.88514 8.98791 6.72298 8.72479 6.56082C8.46167 6.39866 8.20002 6.2443 7.9413 6.09618C7.58263 5.89036 7.22837 5.69702 6.88293 5.5177C6.68743 5.41636 6.45959 5.55669 6.44489 5.78589C6.43118 5.99671 6.41945 6.21129 6.40968 6.42943L6.40866 6.45254C6.40004 6.64801 6.39301 6.84631 6.38756 7.0473C6.37874 7.3607 6.37433 7.67722 6.37433 7.99998C6.37433 8.32274 6.37874 8.64082 6.38756 8.95267Z" fill="#332C2A"/>
</svg>
</AtomsButtonVariant>
</div>
</div>
<!-- 다운로드 카드 -->
<div v-if="launchingStatus" class="inspection-card inspection-download-card">
<h3 class="card-title text-base md:text-lg">
{{ tm('Inspection_Txt_Download') || '게임 다운로드' }}
</h3>
<div class="flex flex-row gap-3">
<AtomsButtonLauncher
v-for="(btn, index) in enabledMarkets" :key="index"
:class="getButtonClass(btn.platform)"
class="h-[48px]"
:platform="btn.platform as any"
:url="btn.url"
:type="btn.platform === 'pc' ? 'default' : 'single'"
variant="outlined"
>
{{ getButtonText(btn.platform) }}
</AtomsButtonLauncher>
</div>
</div>
</div>
</div>
</div>
</div>
</clientOnly>
</section>
</template>
<script setup lang="ts">
import { globalDateFormat } from '@seed-next/date';
import { formatDateOffset } from '#layers/utils/dataUtil'
import { useCheckGameStart } from '#layers/composables/useGameStart'
const config = useRuntimeConfig()
@@ -120,9 +126,6 @@ const { tm, locale } = useI18n({
messages: Object(resultGetMultilingual.value.multilingual)
})
console.log("🚀 ~ globalDateFormat(new Date(webInspectionData.value?.ts_start_date || 0), 'ko'):", )
// locale에 따라 뒤에 KST 또는 UTC 추가 ko, en, zh-tw, ja
// ko: (KST)
// en: (UTC)
@@ -134,74 +137,73 @@ const getLocaleTimezone = (localeType: string, region) => {
const tsEndDate = webInspectionData.value?.end_date || 0
switch (localeType) {
case 'ko':
return `${globalDateFormat(new Date(tsStartDate), localeType, region || '', {useFullDate: true})} ~ ${globalDateFormat(new Date(tsEndDate), localeType, region || 'KR', {useFullDate: true})} (KST)`
return `${globalDateFormat(new Date(tsStartDate), localeType, region || '', {useFullDate: true})} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), localeType, region || 'KR', {useFullDate: true})} (KST)`
case 'en':
return `${globalDateFormat(new Date(tsStartDate), localeType, region || '', {useFullDate: true})} ~ ${globalDateFormat(new Date(tsEndDate), localeType, region || '', {useFullDate: true})} (UTC)`
return `${globalDateFormat(new Date(tsStartDate), localeType, region || '', {useFullDate: true})} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), localeType, region || '', {useFullDate: true})} (UTC)`
case 'zh-tw':
return `${globalDateFormat(new Date(tsStartDate), localeType, region || '', {useFullDate: true})} ~ ${globalDateFormat(new Date(tsEndDate), localeType, region || '', {useFullDate: true})} (台灣時間)`
return `${globalDateFormat(new Date(tsStartDate), localeType, region || '', {useFullDate: true})} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), localeType, region || '', {useFullDate: true})} (台灣時間)`
case 'ja':
return `${globalDateFormat(new Date(tsStartDate), localeType, region || '', {useFullDate: true})} ~ ${globalDateFormat(new Date(tsEndDate), localeType, region || '', {useFullDate: true})} (JST)`
return `${globalDateFormat(new Date(tsStartDate), localeType, region || '', {useFullDate: true})} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), localeType, region || '', {useFullDate: true})} (JST)`
default:
return `${globalDateFormat(new Date(tsStartDate), localeType, region || '', {useFullDate: true})} ~ ${globalDateFormat(new Date(tsEndDate), localeType, region || '', {useFullDate: true})} (KST)`
return `${globalDateFormat(new Date(tsStartDate), localeType, region || '', {useFullDate: true})} <br class="md:hidden">~ ${globalDateFormat(new Date(tsEndDate), localeType, region || '', {useFullDate: true})} (KST)`
}
}
const launchingStatus = computed(() => {
return !!(webInspectionData.value?.launching_status)
return webInspectionData.value?.launching_status
})
// market_json에서 use_yn === 1 인 항목만 배열로 변환
// market_json:
// { pc: { url: 'https://apps.apple.com/app/id1234567890', use_yn: 0 },
// app_store: { url: 'https://apps.apple.com/app/id1234567890', use_yn: 1 },
// google_play: { url: 'https://play.google.com/store/apps/details?id=example', use_yn: 1 } },
// const market_json = {
// pc: { url: 'https://apps.apple.com/app/id1234567890', use_yn: 0 },
// app_store: { url: 'https://apps.apple.com/app/id1234567890', use_yn: 0 },
// google_play: { url: 'https://play.google.com/store/apps/details?id=example', use_yn: 1 }
// }
//gameData.value.market_json 값 중 use_yn === 1 인 항목만 배열로 변환
const enabledMarkets = computed(() => {
return Object.entries(gameData.value.market_json)
// return Object.entries(market_json)
.filter(([, info]: [string, any]) => info && info.use_yn === 1)
.map(([platform, info]: [string, any]) => ({ platform, url: info.url as string }))
})
const logoImgUrl = computed(() => {
// CSR에서만 처리
if (!import.meta.client || !webInspectionData.value) return ''
const currentLocale = locale.value || 'ko'
const localeData = (webInspectionData.value as any)?.[currentLocale]
if (localeData?.img_json?.bi_large) {
return localeData.img_json.bi_large
}
return webInspectionData.value.back_ground_image_url || ''
return getResolvedHost(localeData.img_json.bi_large)
})
const communityUrl = computed(() => {
const currentLocale = locale.value || 'ko'
const localeData = (webInspectionData.value as any)?.[currentLocale]
return localeData?.url_json?.community
})
const handleCommunityClick = () => {
window.open(communityUrl.value, '_blank')
}
// 버튼 클래스 결정 함수
const getButtonClass = (platform: string) => {
const hasPc = enabledMarkets.value.some(btn => btn.platform === 'pc')
// pc가 있으면 pc만 flex-1, 나머지는 기본
if (hasPc) {
return platform === 'pc' ? 'flex-1' : ''
const hasPc = enabledMarkets.value.some(btn => btn.platform === 'pc')
if(hasPc) {
return platform === 'pc' ? `flex-1 btn-platform-pc` : ``
}
// pc가 없으면 모든 버튼에 flex-1
return 'flex-1'
//pc가 없으면서 하나만 있으면 ''
if (enabledMarkets.value.length === 1) {
return ``
}
return `flex-1`
}
const getButtonText = (platform: string) => {
const hasPc = enabledMarkets.value.some(btn => btn.platform === 'pc')
// pc가 있으면 pc만 텍스트 노출
if (hasPc) {
return platform === 'pc' ? tm('platform_pc') : ''
}
// pc가 없으면 google_play와 app_store만 텍스트 노출
if (platform === 'google_play') {
return tm('platform_google_play')
@@ -213,7 +215,6 @@ const getButtonText = (platform: string) => {
return ''
}
const { validateLauncher } = useCheckGameStart()
const handleGameStart = () => {
validateLauncher()
@@ -246,8 +247,7 @@ definePageMeta({
}
.inspection-title {
@apply text-center text-[24px] leading-[34px] font-bold tracking-[-0.72px] text-[#1F1F1F];
font-family: 'Spoqa Han Sans Neo', sans-serif;
@apply text-center font-bold tracking-[-0.72px] text-[#1F1F1F];
}
.inspection-cards {
@@ -255,27 +255,24 @@ definePageMeta({
}
.inspection-card {
@apply bg-white rounded-2xl p-8;
@apply bg-white rounded-2xl p-6 md:p-8;
}
.inspection-time-card {
@apply flex flex-col items-center gap-4;
width: 944px;
min-height: 162px;
}
.card-title {
@apply text-center text-[20px] leading-[30px] font-bold tracking-[-0.6px] text-[#1F1F1F];
font-family: 'Spoqa Han Sans Neo', sans-serif;
@apply text-center leading-[30px] font-bold tracking-[-0.6px] text-[#1F1F1F];
}
.inspection-time {
@apply flex flex-col items-center gap-2;
@apply flex flex-col items-center gap-0 md:gap-2;
}
.time-row {
@apply text-center text-[16px] leading-[26px] font-medium tracking-[-0.48px] text-[#1F1F1F];
font-family: 'Spoqa Han Sans Neo', sans-serif;
@apply text-center leading-[26px] font-medium tracking-[-0.48px] text-[#1F1F1F];
}
.inspection-bottom-cards {
@@ -288,16 +285,15 @@ definePageMeta({
}
.inspection-stove-card .card-title {
@apply text-left text-[18px] leading-[26px] font-bold tracking-[-0.54px] text-[#1F1F1F];
@apply leading-[26px] font-bold tracking-[-0.54px] text-[#1F1F1F];
}
.inspection-download-card .card-title {
@apply text-left text-[18px] leading-[26px] font-bold tracking-[-0.54px] text-[#1F1F1F];
@apply text-left leading-[26px] font-bold tracking-[-0.54px] text-[#1F1F1F];
}
.inspection-content-text {
@apply text-left text-[16px] leading-[26px] font-medium tracking-[-0.48px] text-[#1F1F1F];
font-family: 'Spoqa Han Sans Neo', sans-serif;
@apply text-left leading-[26px] font-medium tracking-[-0.48px] text-[#1F1F1F];
white-space: pre-line;
}
@@ -306,15 +302,10 @@ definePageMeta({
}
.inspection-btn {
@apply flex items-center justify-center gap-1 px-10 h-12 rounded-lg border border-black/10;
font-family: 'Spoqa Han Sans Neo', sans-serif;
font-size: 14px;
line-height: 20px;
font-weight: 500;
letter-spacing: -0.42px;
@apply flex items-center justify-center gap-1 px-6 md:px-8 w-auto h-10 md:h-12 rounded-lg;
cursor: pointer;
transition: all 0.2s;
flex: 1;
}
.inspection-btn span {
@@ -364,6 +355,13 @@ definePageMeta({
}
}
.inspection-btn-community {
@apply min-w-[170px] px-4 rounded-lg flex-none;
@apply rounded-lg;
}
.inspection-btn-community.inspection-btn-outline {
@apply bg-white border-none;
}
.inspection-btn-community.inspection-btn-outline:hover {
@apply bg-gray-50;
}
</style>