This commit is contained in:
dyhj625 2025-03-07 13:37:54 +09:00
parent 39dd213949
commit dd855634a0
5 changed files with 72 additions and 12 deletions

View File

@ -1,6 +1,7 @@
<template> <template>
<component :is="layout"> <component :is="layout">
<template #content> <template #content>
<LoadingSpinner :isLoading="loadingStore.isLoading" />
<router-view></router-view> <router-view></router-view>
</template> </template>
</component> </component>
@ -12,7 +13,10 @@ import { useRoute } from 'vue-router';
import NormalLayout from './layouts/NormalLayout.vue'; import NormalLayout from './layouts/NormalLayout.vue';
import NoLayout from './layouts/NoLayout.vue'; import NoLayout from './layouts/NoLayout.vue';
import ToastModal from '@c/modal/ToastModal.vue'; import ToastModal from '@c/modal/ToastModal.vue';
import { useLoadingStore } from "@s/loadingStore";
import LoadingSpinner from "@v/LoadingPage.vue";
const loadingStore = useLoadingStore();
const route = useRoute(); const route = useRoute();
const layout = computed(() => { const layout = computed(() => {

View File

@ -1,6 +1,7 @@
import axios from 'axios'; import axios from 'axios';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useToastStore } from '@s/toastStore'; import { useToastStore } from '@s/toastStore';
import { useLoadingStore } from "@s/loadingStore";
const $api = axios.create({ const $api = axios.create({
baseURL: import.meta.env.VITE_API_URL, baseURL: import.meta.env.VITE_API_URL,
@ -14,6 +15,9 @@ const $api = axios.create({
*/ */
$api.interceptors.request.use( $api.interceptors.request.use(
function (config) { function (config) {
const loadingStore = useLoadingStore();
loadingStore.startLoading();
let contentType = 'application/json'; let contentType = 'application/json';
if (config.isFormData) contentType = 'multipart/form-data'; if (config.isFormData) contentType = 'multipart/form-data';
@ -24,6 +28,8 @@ $api.interceptors.request.use(
return config; return config;
}, },
function (error) { function (error) {
const loadingStore = useLoadingStore();
loadingStore.stopLoading();
// 요청 오류가 있는 작업 수행 // 요청 오류가 있는 작업 수행
return Promise.reject(error); return Promise.reject(error);
}, },
@ -32,10 +38,15 @@ $api.interceptors.request.use(
// 응답 인터셉터 추가하기 // 응답 인터셉터 추가하기
$api.interceptors.response.use( $api.interceptors.response.use(
function (response) { function (response) {
const loadingStore = useLoadingStore();
loadingStore.stopLoading();
// 2xx 범위의 응답 처리 // 2xx 범위의 응답 처리
return response; return response;
}, },
function (error) { function (error) {
const loadingStore = useLoadingStore();
loadingStore.stopLoading();
const toastStore = useToastStore(); const toastStore = useToastStore();
// 오류 응답 처리 // 오류 응답 처리
if (error.response) { if (error.response) {

View File

@ -63,8 +63,8 @@
margin-left: 260px; margin-left: 260px;
width: auto !important; width: auto !important;
min-width: auto !important; min-width: auto !important;
right: 24px !important; right: 26px !important;
left: 24px !important; left: 26px !important;
} }
/* 탑바 범위조정(1200px 이하) */ /* 탑바 범위조정(1200px 이하) */
@ -75,8 +75,8 @@
margin-left: 0px; margin-left: 0px;
width: auto !important; width: auto !important;
min-width: auto !important; min-width: auto !important;
right: 24px !important; right: 26px !important;
left: 24px !important; left: 26px !important;
} }
} }
@ -88,8 +88,8 @@
margin-left: 0px; margin-left: 0px;
width: auto !important; width: auto !important;
min-width: auto !important; min-width: auto !important;
right: 24px !important; right: 26px !important;
left: 24px !important; left: 26px !important;
} }
} }
</style> </style>

View File

@ -0,0 +1,24 @@
import { defineStore } from "pinia";
import { ref, computed } from "vue";
export const useLoadingStore = defineStore("loading", () => {
const loadingCount = ref(0); // 요청 개수를 추적
const startLoading = () => {
loadingCount.value++;
console.log(loadingCount.value)
};
const stopLoading = () => {
if (loadingCount.value > 0) {
setTimeout(() => {
loadingCount.value--;
console.log("Stop Loading: ", loadingCount.value);
}, 200); // 약간의 지연을 추가하여 응답이 동시에 도착해도 안정적으로 감소
}
};
const isLoading = computed(() => loadingCount.value > 0); // 하나라도 요청이 있으면 로딩 활성화
return { isLoading, startLoading, stopLoading };
});

View File

@ -1,24 +1,45 @@
<template> <template>
<div class="loading-container"> <div v-if="isLoading" class="loading-overlay">
<div class="spinner"> <div class="loading-container">
🐰 <div class="spinner">🐰</div>
<p class="loading-text">잠시만 기다려 주세요...</p>
</div> </div>
<p class="loading-text">잠시만 기다려 주세요...</p>
</div> </div>
</template> </template>
<script setup> <script setup>
import { defineProps } from "vue";
defineProps({
isLoading: Boolean, //
});
</script> </script>
<style scoped> <style scoped>
/* 회색 배경으로 클릭 방지 */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.4); /* 회색 반투명 */
display: flex;
align-items: center;
justify-content: center;
z-index: 9999; /* 가장 위에 위치 */
pointer-events: auto; /* 모든 클릭 방지 */
}
/* 로딩 컨테이너 */ /* 로딩 컨테이너 */
.loading-container { .loading-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 100vh; padding: 20px;
background-color: #f9f9f9; background: none;
border-radius: 10px;
} }
/* 빙글빙글 돌아가는 스피너 */ /* 빙글빙글 돌아가는 스피너 */