feat. 페이지 호출시 로딩 적용
This commit is contained in:
93
layers/assets/css/components/_baseContent.css
Normal file
93
layers/assets/css/components/_baseContent.css
Normal file
@@ -0,0 +1,93 @@
|
||||
/* 에디터 콘텐츠 기본 스타일 유틸리티 */
|
||||
/* use-base 클래스를 추가하면 하위 자식 요소들에 기본 스타일이 적용됩니다 */
|
||||
@layer components {
|
||||
.use-base ul,
|
||||
.use-base ol {
|
||||
@apply list-disc pl-6;
|
||||
}
|
||||
.use-base ol {
|
||||
@apply list-decimal;
|
||||
}
|
||||
.use-base ul ul,
|
||||
.use-base ol ol,
|
||||
.use-base ul ol,
|
||||
.use-base ol ul {
|
||||
@apply mb-0;
|
||||
}
|
||||
|
||||
.use-base table {
|
||||
@apply w-full border-collapse;
|
||||
}
|
||||
.use-base thead {
|
||||
@apply bg-gray-100;
|
||||
}
|
||||
.use-base th,
|
||||
.use-base td {
|
||||
@apply border border-gray-300 px-4 py-2 text-left;
|
||||
}
|
||||
.use-base th {
|
||||
@apply font-semibold bg-gray-50;
|
||||
}
|
||||
.use-base tbody tr:nth-child(even) {
|
||||
@apply bg-gray-50;
|
||||
}
|
||||
|
||||
.use-base blockquote {
|
||||
@apply border-l-4 border-gray-300 pl-4 italic text-gray-700;
|
||||
}
|
||||
|
||||
.use-base h1 {
|
||||
@apply text-2xl;
|
||||
}
|
||||
.use-base h2 {
|
||||
@apply text-xl;
|
||||
}
|
||||
.use-base h3 {
|
||||
@apply text-lg;
|
||||
}
|
||||
.use-base h4 {
|
||||
@apply text-base;
|
||||
}
|
||||
.use-base h5 {
|
||||
@apply text-sm;
|
||||
}
|
||||
.use-base h6 {
|
||||
@apply text-xs;
|
||||
}
|
||||
|
||||
.use-base strong,
|
||||
.use-base b {
|
||||
@apply font-bold;
|
||||
}
|
||||
.use-base em,
|
||||
.use-base i {
|
||||
@apply italic;
|
||||
}
|
||||
.use-base u {
|
||||
@apply underline;
|
||||
}
|
||||
.use-base s {
|
||||
@apply line-through;
|
||||
}
|
||||
|
||||
.use-base a {
|
||||
@apply text-blue-600 underline;
|
||||
}
|
||||
.use-base a:hover {
|
||||
@apply text-blue-800;
|
||||
}
|
||||
|
||||
.use-base img {
|
||||
@apply max-w-full h-auto my-4;
|
||||
}
|
||||
|
||||
.use-base pre {
|
||||
@apply bg-gray-100 p-4 rounded overflow-x-auto mb-4;
|
||||
}
|
||||
.use-base code {
|
||||
@apply bg-gray-100 px-1 py-0.5 rounded text-sm;
|
||||
}
|
||||
.use-base pre code {
|
||||
@apply bg-transparent p-0;
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,8 @@ const { fullLoading } = storeToRefs(loadingStore)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
<div v-if="fullLoading" class="spinner-wrap">
|
||||
<Transition name="fade-out">
|
||||
<div v-show="fullLoading" class="spinner-wrap">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
</Transition>
|
||||
@@ -15,7 +15,7 @@ const { fullLoading } = storeToRefs(loadingStore)
|
||||
|
||||
<style scoped>
|
||||
.spinner-wrap {
|
||||
@apply fixed inset-0 bg-black/90 flex items-center justify-center z-[150];
|
||||
@apply fixed inset-0 bg-black pt-[96px] flex items-center justify-center sm:pt-[112px] z-[150];
|
||||
}
|
||||
.spinner {
|
||||
@apply w-[80px] h-[80px] bg-cover bg-center bg-no-repeat bg-[url('/images/common/publisning_template_loader_black.png')];
|
||||
|
||||
@@ -15,18 +15,21 @@ const props = defineProps<Props>()
|
||||
const mainContentRef = ref<HTMLElement>()
|
||||
|
||||
const { locale } = useI18n()
|
||||
const { getTemplateComponent } = useTemplateRegistry()
|
||||
const { height: viewportH } = useWindowSize()
|
||||
const { bottom: mainBottom } = useElementBounding(mainContentRef)
|
||||
const { getTemplateComponent } = useTemplateRegistry()
|
||||
const loadingStore = useLoadingStore()
|
||||
|
||||
const { isPAssApiLoading, hasApiCallStarted } = storeToRefs(loadingStore)
|
||||
|
||||
// 개별 메타 태그 표시 여부 확인
|
||||
const shouldShowMetaTag = computed(() => props.pageData?.meta_tag_type === 2)
|
||||
|
||||
const pinToMain = computed(() => {
|
||||
if (!mainBottom.value) return false
|
||||
return mainBottom.value <= viewportH.value
|
||||
})
|
||||
|
||||
// 개별 메타 태그 표시 여부 확인
|
||||
const shouldShowMetaTag = computed(() => props.pageData?.meta_tag_type === 2)
|
||||
|
||||
// 템플릿 표시 여부 확인
|
||||
const isTemplateVisible = (template: PageDataTemplate): boolean => {
|
||||
return Boolean(
|
||||
@@ -59,11 +62,6 @@ const setupSeoMeta = (metaTag: PageDataMetaTag) => {
|
||||
|
||||
provide('pinToMain', pinToMain)
|
||||
|
||||
onMounted(() => {
|
||||
const { sendLog } = useAnalytics()
|
||||
sendLog(locale.value, useAnalyticsLogDataDirect('view', 1))
|
||||
})
|
||||
|
||||
// 메타 태그 설정 감시
|
||||
watchEffect(() => {
|
||||
if (shouldShowMetaTag.value && props.pageData?.meta_tag_json) {
|
||||
@@ -71,8 +69,20 @@ watchEffect(() => {
|
||||
}
|
||||
})
|
||||
|
||||
// const loadingStore = useLoadingStore()
|
||||
// loadingStore.startFullLoading()
|
||||
watch(isPAssApiLoading, newVal => {
|
||||
if (newVal) {
|
||||
loadingStore.stopFullLoading()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
const { sendLog } = useAnalytics()
|
||||
sendLog(locale.value, useAnalyticsLogDataDirect('view', 1))
|
||||
|
||||
if (!hasApiCallStarted.value) {
|
||||
loadingStore.stopFullLoading()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { commonFetch } from '#layers/utils/apiUtil'
|
||||
import { usePageDataStore } from '#layers/stores/usePageDataStore'
|
||||
import { useLoadingStore } from '#layers/stores/useLoadingStore'
|
||||
import { useGetGameDomain } from '#layers/composables/useGetGameDomain'
|
||||
import { usePathResolver } from '#layers/composables/usePathResolver'
|
||||
import type { PageDataResponse } from '#layers/types/api/pageData'
|
||||
import type { GameDataValue } from '#layers/types/api/gameData'
|
||||
|
||||
export default defineNuxtRouteMiddleware(async (to, _from) => {
|
||||
// client에서만 동작되도록 처리
|
||||
@@ -13,24 +13,25 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
||||
const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
|
||||
const apiUrl = `${stoveApiBaseUrl}/pub-comm/v2.0/template/page`
|
||||
|
||||
const store = usePageDataStore()
|
||||
const gameDomain = useGetGameDomain()
|
||||
const { getPathAfterLanguage } = usePathResolver()
|
||||
const headers = useRequestHeaders()
|
||||
const gameDataStore = useGameDataStore()
|
||||
const gameData = gameDataStore.gameData as GameDataValue
|
||||
const pageDataStore = usePageDataStore()
|
||||
const loadingStore = useLoadingStore()
|
||||
const gameDomain = useGetGameDomain()
|
||||
const headers = useRequestHeaders()
|
||||
const { getPathAfterLanguage } = usePathResolver()
|
||||
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
|
||||
const langCode = ssrGetFinalLocale(
|
||||
to.path,
|
||||
headers,
|
||||
gameData?.lang_codes,
|
||||
gameData?.default_lang_code
|
||||
gameData.value?.lang_codes,
|
||||
gameData.value?.default_lang_code
|
||||
)
|
||||
|
||||
try {
|
||||
|
||||
if (to.path.includes('inspection')) {
|
||||
console.log("🚀 ~ 점검페이지 접근 pageData.global")
|
||||
console.log('🚀 ~ 점검페이지 접근 pageData.global')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -42,10 +43,18 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
||||
}
|
||||
|
||||
// pageUrl이 빈값이거나 null이면 /brand로 리다이렉트
|
||||
if (!pageUrl || pageUrl === '' || pageUrl === '/' || pageUrl === `/${langCode}/`) {
|
||||
if (
|
||||
!pageUrl ||
|
||||
pageUrl === '' ||
|
||||
pageUrl === '/' ||
|
||||
pageUrl === `/${langCode}/`
|
||||
) {
|
||||
return navigateTo(`/${langCode}/brand`, { external: false })
|
||||
}
|
||||
|
||||
// 페이지 이동 시 로딩 시작
|
||||
loadingStore.startFullLoading()
|
||||
|
||||
const accessToken = csrGetAccessToken()
|
||||
|
||||
const headers = {
|
||||
@@ -54,7 +63,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
||||
|
||||
// 쿼리스트링에서 f 파라미터 값 추출 (CSR용)
|
||||
const fValue = (to.query.f as string) || ''
|
||||
|
||||
|
||||
// 미리보기 API 호출 처리
|
||||
let finalGameDomain = gameDomain
|
||||
if (fValue === 'preview') {
|
||||
@@ -71,17 +80,16 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
||||
const response = (await commonFetch('GET', apiUrl, {
|
||||
headers,
|
||||
query: queryParams,
|
||||
loading: true,
|
||||
})) as PageDataResponse | null
|
||||
console.log('🚀 ~ pageData.global response:', response.value)
|
||||
|
||||
// 페이지 접근 권한 설정(로그인 유무)
|
||||
if(response?.value?.is_login_required === 1 && !accessToken) {
|
||||
if (response?.value?.is_login_required === 1 && !accessToken) {
|
||||
// 로그인 레이어 팝업 띄워주기
|
||||
const nuxtApp = useNuxtApp()
|
||||
const modalStore = useModalStore()
|
||||
const $i18n = nuxtApp.$i18n as any
|
||||
const {tm} = $i18n
|
||||
const { tm } = $i18n
|
||||
modalStore.handleOpenConfirm({
|
||||
contentText: tm('Alert_StoveLogin'),
|
||||
confirmButtonText: tm('Text_StoveLogin'),
|
||||
@@ -91,34 +99,37 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
||||
})
|
||||
}
|
||||
|
||||
if(response?.code === 91003) {
|
||||
if (response?.code === 91003) {
|
||||
// return navigateTo(`/${langCode}/error`, { external: false })
|
||||
//클릭한 주소는 주소표시줄에 표시하도록 수정
|
||||
window.history.replaceState({}, '', to.path)
|
||||
// 뒤로가기 이동 시 이전 페이지로 이동되도록 수정
|
||||
showError(createError({
|
||||
statusCode: 404,
|
||||
statusMessage: '페이지를 찾을 수 없어요.',
|
||||
fatal: false, // 즉시 에러 페이지로
|
||||
data: { reason: 'post-not-found' }
|
||||
}))
|
||||
|
||||
// 뒤로가기 이동 시 이전 페이지로 이동되도록 수정
|
||||
showError(
|
||||
createError({
|
||||
statusCode: 404,
|
||||
statusMessage: '페이지를 찾을 수 없어요.',
|
||||
fatal: false, // 즉시 에러 페이지로
|
||||
data: { reason: 'post-not-found' },
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (response?.code === 0 && 'value' in response) {
|
||||
store.setPageData(response.value)
|
||||
pageDataStore.setPageData(response.value)
|
||||
} else {
|
||||
store.clearPageData()
|
||||
pageDataStore.clearPageData()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
store.clearPageData()
|
||||
pageDataStore.clearPageData()
|
||||
|
||||
showError(createError({
|
||||
statusCode: error.statusCode,
|
||||
statusMessage: error.message,
|
||||
fatal: false, // 즉시 에러 페이지로
|
||||
data: { reason: 'post-not-found' }
|
||||
}))
|
||||
showError(
|
||||
createError({
|
||||
statusCode: error.statusCode,
|
||||
statusMessage: error.message,
|
||||
fatal: false, // 즉시 에러 페이지로
|
||||
data: { reason: 'post-not-found' },
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,29 +2,42 @@ import { defineStore } from 'pinia'
|
||||
|
||||
export const useLoadingStore = defineStore('loadingStore', () => {
|
||||
// 글로벌 로딩 표기
|
||||
const fullLoading = ref(false)
|
||||
const fullLoading = ref(true)
|
||||
const hasApiCallStarted = ref(false)
|
||||
const isPAssApiLoading = ref(false)
|
||||
// 컴포넌트별 로딩 표기 - Map 대신 일반 객체 사용
|
||||
const localLoadings = ref<Record<string, { active: boolean }>>({})
|
||||
// 로딩 상태만 표기
|
||||
const isLoading = ref(false)
|
||||
|
||||
/**
|
||||
* 모든 로딩 상태 초기화
|
||||
* 로딩 상태 초기화
|
||||
*/
|
||||
const initializeStore = () => {
|
||||
fullLoading.value = false
|
||||
localLoadings.value = {}
|
||||
hasApiCallStarted.value = false
|
||||
isPAssApiLoading.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Full 로딩
|
||||
*/
|
||||
const startFullLoading = () => {
|
||||
startApiLoading()
|
||||
fullLoading.value = true
|
||||
}
|
||||
const startApiLoading = () => {
|
||||
hasApiCallStarted.value = true
|
||||
isPAssApiLoading.value = false
|
||||
}
|
||||
|
||||
const stopFullLoading = () => {
|
||||
fullLoading.value = false
|
||||
if (!hasApiCallStarted.value || isPAssApiLoading.value) {
|
||||
fullLoading.value = false
|
||||
}
|
||||
}
|
||||
const finishApiLoading = () => {
|
||||
setTimeout(() => {
|
||||
isPAssApiLoading.value = true
|
||||
}, 300)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,24 +57,19 @@ export const useLoadingStore = defineStore('loadingStore', () => {
|
||||
return !!localLoadings.value[localId]?.active
|
||||
}
|
||||
|
||||
/**
|
||||
* 로딩 상태 변경
|
||||
*/
|
||||
const setLoading = (state: boolean) => {
|
||||
isLoading.value = state
|
||||
}
|
||||
|
||||
return {
|
||||
fullLoading,
|
||||
localLoadings,
|
||||
isLoading,
|
||||
hasApiCallStarted,
|
||||
isPAssApiLoading,
|
||||
|
||||
startApiLoading,
|
||||
finishApiLoading,
|
||||
initializeStore,
|
||||
startFullLoading,
|
||||
stopFullLoading,
|
||||
startLocalLoading,
|
||||
stopLocalLoading,
|
||||
isLocalLoading,
|
||||
setLoading,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -68,6 +68,7 @@ const fnGetSecuritySetting = async () => {
|
||||
try {
|
||||
const result = await commonFetch('GET', `${apiBase}/security/setting`, {
|
||||
headers,
|
||||
loading: true,
|
||||
})
|
||||
|
||||
if (result?.code === 0 && Array.isArray(result.value)) {
|
||||
|
||||
@@ -20,12 +20,8 @@ const startLoading = (
|
||||
) => {
|
||||
if (!loadingStore) return
|
||||
|
||||
loadingStore.setLoading(true)
|
||||
|
||||
if (typeof loading === 'object' && loading.localId) {
|
||||
loadingStore.startLocalLoading(loading.localId)
|
||||
} else if (typeof loading === 'boolean') {
|
||||
loadingStore.startFullLoading()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,12 +34,12 @@ const stopLoading = (
|
||||
) => {
|
||||
if (!loadingStore) return
|
||||
|
||||
loadingStore.setLoading(false)
|
||||
|
||||
if (typeof loading === 'object' && loading.localId) {
|
||||
loadingStore.stopLocalLoading(loading.localId)
|
||||
} else if (typeof loading === 'boolean') {
|
||||
loadingStore.stopFullLoading()
|
||||
return
|
||||
}
|
||||
if (loading === true) {
|
||||
loadingStore.finishApiLoading()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user