useApi.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import axios from 'axios'
  2. import { useLoading } from './useLoading'
  3. export const useApi = () => {
  4. const config = useRuntimeConfig()
  5. const API_BASE_URL = config.public.apiBase + '/api'
  6. const { showLoading, hideLoading } = useLoading()
  7. let pendingRequests = 0
  8. // Axios 인스턴스 생성
  9. const apiClient = axios.create({
  10. baseURL: API_BASE_URL,
  11. timeout: 30000,
  12. headers: {
  13. 'Content-Type': 'application/json'
  14. }
  15. })
  16. // Request Interceptor (토큰 자동 추가 + 로딩 시작)
  17. apiClient.interceptors.request.use(
  18. (config) => {
  19. // 로딩 카운터 증가
  20. pendingRequests++
  21. if (pendingRequests === 1) {
  22. showLoading()
  23. }
  24. if (typeof window !== 'undefined' && typeof localStorage !== 'undefined') {
  25. const token = localStorage.getItem('admin_token')
  26. if (token) {
  27. config.headers.Authorization = `Bearer ${token}`
  28. console.log('[useApi] Request with token:', config.url, token.substring(0, 20) + '...')
  29. } else {
  30. console.log('[useApi] Request without token:', config.url)
  31. }
  32. }
  33. return config
  34. },
  35. (error) => {
  36. // 에러 발생시에도 카운터 감소
  37. pendingRequests--
  38. if (pendingRequests === 0) {
  39. hideLoading()
  40. }
  41. return Promise.reject(error)
  42. }
  43. )
  44. // Response Interceptor (에러 처리 + 로딩 종료)
  45. apiClient.interceptors.response.use(
  46. (response) => {
  47. // 로딩 카운터 감소
  48. pendingRequests--
  49. if (pendingRequests === 0) {
  50. hideLoading()
  51. }
  52. console.log('[useApi] Response:', response.config.url, response.status)
  53. return response
  54. },
  55. (error) => {
  56. // 에러 시에도 카운터 감소
  57. pendingRequests--
  58. if (pendingRequests === 0) {
  59. hideLoading()
  60. }
  61. const status = error.response?.status
  62. const url = error.config?.url
  63. console.log('[useApi] Error:', {
  64. url,
  65. status,
  66. message: error.message,
  67. hasToken: typeof window !== 'undefined' && typeof localStorage !== 'undefined' ? !!localStorage.getItem('admin_token') : false
  68. })
  69. // 401 에러만 토큰 삭제 및 로그아웃 처리
  70. if (status === 401) {
  71. console.log('[useApi] 401 Unauthorized - 토큰 삭제 및 로그아웃')
  72. if (typeof window !== 'undefined') {
  73. localStorage.removeItem('admin_token')
  74. localStorage.removeItem('admin_user')
  75. window.location.href = '/site-manager'
  76. }
  77. } else {
  78. console.log('[useApi] 401이 아닌 에러 - 토큰 유지')
  79. }
  80. return Promise.reject(error)
  81. }
  82. )
  83. // GET 요청
  84. const get = async (url, config = {}) => {
  85. try {
  86. const response = await apiClient.get(url, config)
  87. // 전체 응답 반환: { success, data, message }
  88. return { data: response.data, error: null }
  89. } catch (error) {
  90. return { data: null, error: error.response?.data || error.message }
  91. }
  92. }
  93. // POST 요청
  94. const post = async (url, data = {}) => {
  95. try {
  96. const response = await apiClient.post(url, data)
  97. // 전체 응답 반환: { success, data, message }
  98. return { data: response.data, error: null }
  99. } catch (error) {
  100. return { data: null, error: error.response?.data || error.message }
  101. }
  102. }
  103. // PUT 요청
  104. const put = async (url, data = {}) => {
  105. try {
  106. const response = await apiClient.put(url, data)
  107. // 전체 응답 반환: { success, data, message }
  108. return { data: response.data, error: null }
  109. } catch (error) {
  110. return { data: null, error: error.response?.data || error.message }
  111. }
  112. }
  113. // PATCH 요청
  114. const patch = async (url, data = {}) => {
  115. try {
  116. const response = await apiClient.patch(url, data)
  117. // 전체 응답 반환: { success, data, message }
  118. return { data: response.data, error: null }
  119. } catch (error) {
  120. return { data: null, error: error.response?.data || error.message }
  121. }
  122. }
  123. // DELETE 요청
  124. const del = async (url) => {
  125. try {
  126. const response = await apiClient.delete(url)
  127. // 전체 응답 반환: { success, data, message }
  128. return { data: response.data, error: null }
  129. } catch (error) {
  130. return { data: null, error: error.response?.data || error.message }
  131. }
  132. }
  133. // 파일 업로드
  134. const upload = async (url, formData) => {
  135. try {
  136. const response = await apiClient.post(url, formData, {
  137. headers: {
  138. 'Content-Type': 'multipart/form-data'
  139. }
  140. })
  141. // 전체 응답 반환: { success, data, message }
  142. return { data: response.data, error: null }
  143. } catch (error) {
  144. return { data: null, error: error.response?.data || error.message }
  145. }
  146. }
  147. return {
  148. get,
  149. post,
  150. put,
  151. patch,
  152. del,
  153. upload
  154. }
  155. }