StoreManagementView.vue services/api.js edit
This commit is contained in:
parent
bfc4d600e2
commit
ccdc2c91bb
BIN
public/images/menu-placeholder.png
Normal file
BIN
public/images/menu-placeholder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
@ -1,4 +1,5 @@
|
|||||||
//* src/services/api.js - 수정된 API URL 설정
|
//* src/services/api.js - 수정된 버전 (createImageApiInstance 함수 추가)
|
||||||
|
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
// 런타임 환경 설정에서 API URL 가져오기
|
// 런타임 환경 설정에서 API URL 가져오기
|
||||||
@ -11,10 +12,9 @@ const getApiUrls = () => {
|
|||||||
STORE_URL: config.STORE_URL || 'http://localhost:8082/api/store',
|
STORE_URL: config.STORE_URL || 'http://localhost:8082/api/store',
|
||||||
CONTENT_URL: config.CONTENT_URL || 'http://localhost:8083/api/content',
|
CONTENT_URL: config.CONTENT_URL || 'http://localhost:8083/api/content',
|
||||||
MENU_URL: config.MENU_URL || 'http://localhost:8082/api/menu',
|
MENU_URL: config.MENU_URL || 'http://localhost:8082/api/menu',
|
||||||
// ⚠️ 수정: 매출 API는 store 서비스 (포트 8082)
|
|
||||||
SALES_URL: config.SALES_URL || 'http://localhost:8082/api/sales',
|
SALES_URL: config.SALES_URL || 'http://localhost:8082/api/sales',
|
||||||
// ⚠️ 수정: 추천 API는 ai-recommend 서비스 (포트 8084)
|
RECOMMEND_URL: config.RECOMMEND_URL || 'http://localhost:8084/api/recommendations',
|
||||||
RECOMMEND_URL: config.RECOMMEND_URL || 'http://localhost:8084/api/recommendations'
|
IMAGE_URL: config.IMAGE_URL || 'http://localhost:8082/api/images'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +37,6 @@ const createApiInstance = (baseURL) => {
|
|||||||
config.headers.Authorization = `Bearer ${token}`
|
config.headers.Authorization = `Bearer ${token}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⚠️ 추가: 요청 로깅 (개발 환경에서만)
|
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
console.log(`🌐 [API_REQ] ${config.method?.toUpperCase()} ${config.baseURL}${config.url}`)
|
console.log(`🌐 [API_REQ] ${config.method?.toUpperCase()} ${config.baseURL}${config.url}`)
|
||||||
}
|
}
|
||||||
@ -52,14 +51,12 @@ const createApiInstance = (baseURL) => {
|
|||||||
// 응답 인터셉터 - 토큰 갱신 및 에러 처리
|
// 응답 인터셉터 - 토큰 갱신 및 에러 처리
|
||||||
instance.interceptors.response.use(
|
instance.interceptors.response.use(
|
||||||
(response) => {
|
(response) => {
|
||||||
// ⚠️ 추가: 응답 로깅 (개발 환경에서만)
|
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
console.log(`✅ [API_RES] ${response.status} ${response.config?.method?.toUpperCase()} ${response.config?.url}`)
|
console.log(`✅ [API_RES] ${response.status} ${response.config?.method?.toUpperCase()} ${response.config?.url}`)
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
},
|
},
|
||||||
async (error) => {
|
async (error) => {
|
||||||
// ⚠️ 추가: 에러 로깅 (개발 환경에서만)
|
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
console.error(`❌ [API_ERR] ${error.response?.status || 'Network'} ${error.config?.method?.toUpperCase()} ${error.config?.url}`, error.response?.data)
|
console.error(`❌ [API_ERR] ${error.response?.status || 'Network'} ${error.config?.method?.toUpperCase()} ${error.config?.url}`, error.response?.data)
|
||||||
}
|
}
|
||||||
@ -102,10 +99,155 @@ const createApiInstance = (baseURL) => {
|
|||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ 이미지 업로드 전용 API 인스턴스 생성 함수 추가
|
||||||
|
const createImageApiInstance = (baseURL) => {
|
||||||
|
const instance = axios.create({
|
||||||
|
baseURL,
|
||||||
|
timeout: 60000, // 이미지 업로드는 시간이 더 걸릴 수 있음
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 요청 인터셉터 - JWT 토큰 자동 추가
|
||||||
|
instance.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
const token = localStorage.getItem('accessToken')
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
console.log(`🌐 [IMG_REQ] ${config.method?.toUpperCase()} ${config.baseURL}${config.url}`)
|
||||||
|
console.log('FormData 포함:', config.data instanceof FormData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// 응답 인터셉터
|
||||||
|
instance.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
console.log(`✅ [IMG_RES] ${response.status} ${response.config?.method?.toUpperCase()} ${response.config?.url}`)
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
},
|
||||||
|
async (error) => {
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
console.error(`❌ [IMG_ERR] ${error.response?.status || 'Network'} ${error.config?.method?.toUpperCase()} ${error.config?.url}`, error.response?.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 토큰 갱신 로직은 기존과 동일
|
||||||
|
const originalRequest = error.config
|
||||||
|
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||||
|
originalRequest._retry = true
|
||||||
|
try {
|
||||||
|
const refreshToken = localStorage.getItem('refreshToken')
|
||||||
|
if (refreshToken) {
|
||||||
|
const refreshResponse = await axios.post(`${getApiUrls().AUTH_URL}/refresh`, {
|
||||||
|
refreshToken,
|
||||||
|
})
|
||||||
|
const { accessToken, refreshToken: newRefreshToken } = refreshResponse.data.data
|
||||||
|
localStorage.setItem('accessToken', accessToken)
|
||||||
|
localStorage.setItem('refreshToken', newRefreshToken)
|
||||||
|
originalRequest.headers.Authorization = `Bearer ${accessToken}`
|
||||||
|
return instance(originalRequest)
|
||||||
|
}
|
||||||
|
} catch (refreshError) {
|
||||||
|
localStorage.removeItem('accessToken')
|
||||||
|
localStorage.removeItem('refreshToken')
|
||||||
|
localStorage.removeItem('userInfo')
|
||||||
|
window.location.href = '/login'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.reject(error)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 메뉴 이미지 업로드 전용 API 인스턴스 생성 함수 추가
|
||||||
|
const createMenuImageApiInstance = (baseURL) => {
|
||||||
|
const instance = axios.create({
|
||||||
|
baseURL,
|
||||||
|
timeout: 60000, // 이미지 업로드는 시간이 더 걸릴 수 있음
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 요청 인터셉터 - JWT 토큰 자동 추가
|
||||||
|
instance.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
const token = localStorage.getItem('accessToken')
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
console.log(`🌐 [MENU_IMG_REQ] ${config.method?.toUpperCase()} ${config.baseURL}${config.url}`)
|
||||||
|
console.log('FormData 포함:', config.data instanceof FormData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// 응답 인터셉터
|
||||||
|
instance.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
console.log(`✅ [MENU_IMG_RES] ${response.status} ${response.config?.method?.toUpperCase()} ${response.config?.url}`)
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
},
|
||||||
|
async (error) => {
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
console.error(`❌ [MENU_IMG_ERR] ${error.response?.status || 'Network'} ${error.config?.method?.toUpperCase()} ${error.config?.url}`, error.response?.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 토큰 갱신 로직은 기존과 동일
|
||||||
|
const originalRequest = error.config
|
||||||
|
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||||
|
originalRequest._retry = true
|
||||||
|
try {
|
||||||
|
const refreshToken = localStorage.getItem('refreshToken')
|
||||||
|
if (refreshToken) {
|
||||||
|
const refreshResponse = await axios.post(`${getApiUrls().AUTH_URL}/refresh`, {
|
||||||
|
refreshToken,
|
||||||
|
})
|
||||||
|
const { accessToken, refreshToken: newRefreshToken } = refreshResponse.data.data
|
||||||
|
localStorage.setItem('accessToken', accessToken)
|
||||||
|
localStorage.setItem('refreshToken', newRefreshToken)
|
||||||
|
originalRequest.headers.Authorization = `Bearer ${accessToken}`
|
||||||
|
return instance(originalRequest)
|
||||||
|
}
|
||||||
|
} catch (refreshError) {
|
||||||
|
localStorage.removeItem('accessToken')
|
||||||
|
localStorage.removeItem('refreshToken')
|
||||||
|
localStorage.removeItem('userInfo')
|
||||||
|
window.location.href = '/login'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.reject(error)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
// API 인스턴스들 생성
|
// API 인스턴스들 생성
|
||||||
const apiUrls = getApiUrls()
|
const apiUrls = getApiUrls()
|
||||||
|
|
||||||
// ⚠️ 추가: API URL 확인 로깅 (개발 환경에서만)
|
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
console.log('🔧 [API_CONFIG] API URLs 설정:', apiUrls)
|
console.log('🔧 [API_CONFIG] API URLs 설정:', apiUrls)
|
||||||
}
|
}
|
||||||
@ -115,8 +257,11 @@ export const authApi = createApiInstance(apiUrls.AUTH_URL)
|
|||||||
export const storeApi = createApiInstance(apiUrls.STORE_URL)
|
export const storeApi = createApiInstance(apiUrls.STORE_URL)
|
||||||
export const contentApi = createApiInstance(apiUrls.CONTENT_URL)
|
export const contentApi = createApiInstance(apiUrls.CONTENT_URL)
|
||||||
export const menuApi = createApiInstance(apiUrls.MENU_URL)
|
export const menuApi = createApiInstance(apiUrls.MENU_URL)
|
||||||
|
export const menuImageApi = createMenuImageApiInstance(apiUrls.MENU_URL) // ✅ 추가
|
||||||
export const salesApi = createApiInstance(apiUrls.SALES_URL)
|
export const salesApi = createApiInstance(apiUrls.SALES_URL)
|
||||||
export const recommendApi = createApiInstance(apiUrls.RECOMMEND_URL)
|
export const recommendApi = createApiInstance(apiUrls.RECOMMEND_URL)
|
||||||
|
export const imageApi = createApiInstance(apiUrls.IMAGE_URL)
|
||||||
|
export const apiWithImage = imageApi // 별칭 (기존 코드 호환성)
|
||||||
|
|
||||||
// 기본 API 인스턴스 (Gateway URL 사용)
|
// 기본 API 인스턴스 (Gateway URL 사용)
|
||||||
export const api = createApiInstance(apiUrls.GATEWAY_URL)
|
export const api = createApiInstance(apiUrls.GATEWAY_URL)
|
||||||
@ -185,7 +330,7 @@ export const formatSuccessResponse = (data, message = '요청이 성공적으로
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⚠️ 추가: API 상태 확인 함수
|
// API 상태 확인 함수
|
||||||
export const checkApiHealth = async () => {
|
export const checkApiHealth = async () => {
|
||||||
const results = {}
|
const results = {}
|
||||||
|
|
||||||
@ -214,11 +359,14 @@ export const checkApiHealth = async () => {
|
|||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⚠️ 추가: 개발 환경에서 전역 노출
|
// 개발 환경에서 전역 노출
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
window.__api_debug__ = {
|
window.__api_debug__ = {
|
||||||
urls: apiUrls,
|
urls: apiUrls,
|
||||||
instances: { memberApi, authApi, storeApi, contentApi, menuApi, salesApi, recommendApi },
|
instances: {
|
||||||
|
memberApi, authApi, storeApi, contentApi, menuApi, menuImageApi,
|
||||||
|
salesApi, recommendApi, imageApi
|
||||||
|
},
|
||||||
checkHealth: checkApiHealth
|
checkHealth: checkApiHealth
|
||||||
}
|
}
|
||||||
console.log('🔧 [DEBUG] API 인스턴스가 window.__api_debug__에 노출됨')
|
console.log('🔧 [DEBUG] API 인스턴스가 window.__api_debug__에 노출됨')
|
||||||
|
|||||||
@ -79,80 +79,88 @@ export const useStoreStore = defineStore('store', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
// src/store/index.js에서 fetchMenus 부분만 수정
|
||||||
* 메뉴 목록 조회 - 실제 API 연동 (매장 ID 필요) - ✅ ID 필드 보장
|
|
||||||
*/
|
/**
|
||||||
async fetchMenus() {
|
* 메뉴 목록 조회 - 실제 API 연동 (매장 ID 필요) - ✅ 이미지 필드 매핑 수정
|
||||||
console.log('=== Store 스토어: 메뉴 목록 조회 시작 ===')
|
*/
|
||||||
|
async fetchMenus() {
|
||||||
|
console.log('=== Store 스토어: 메뉴 목록 조회 시작 ===')
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 매장 정보에서 storeId 가져오기
|
||||||
|
const storeId = this.storeInfo?.storeId
|
||||||
|
if (!storeId) {
|
||||||
|
console.warn('매장 ID가 없습니다. 매장 정보를 먼저 조회해주세요.')
|
||||||
|
return { success: false, message: '매장 정보가 필요합니다', data: [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 메뉴 서비스 임포트
|
||||||
|
const { menuService } = await import('@/services/menu')
|
||||||
|
|
||||||
|
console.log('메뉴 목록 API 호출, 매장 ID:', storeId)
|
||||||
|
const result = await menuService.getMenus(storeId)
|
||||||
|
|
||||||
|
console.log('=== Store 스토어: 메뉴 API 응답 분석 ===')
|
||||||
|
console.log('Result:', result)
|
||||||
|
console.log('Result.success:', result.success)
|
||||||
|
console.log('Result.data:', result.data)
|
||||||
|
console.log('Result.message:', result.message)
|
||||||
|
|
||||||
|
if (result.success && result.data) {
|
||||||
|
// ✅ 백엔드 MenuResponse의 필드명에 맞게 매핑 수정
|
||||||
|
const menusWithId = (result.data || []).map(menu => {
|
||||||
|
// ID 필드가 확실히 있도록 보장
|
||||||
|
const menuId = menu.menuId || menu.id
|
||||||
|
|
||||||
|
if (!menuId) {
|
||||||
|
console.warn('⚠️ 메뉴 ID가 없는 항목 발견:', menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('메뉴 원본 데이터:', menu) // 디버깅용
|
||||||
|
|
||||||
|
return {
|
||||||
|
...menu,
|
||||||
|
id: menuId, // ✅ id 필드 확실히 설정
|
||||||
|
menuId: menuId, // ✅ menuId 필드도 설정
|
||||||
|
// 기타 필드들 보장
|
||||||
|
menuName: menu.menuName || menu.name || '이름 없음',
|
||||||
|
category: menu.category || '기타',
|
||||||
|
price: menu.price || 0,
|
||||||
|
description: menu.description || '',
|
||||||
|
available: menu.available !== undefined ? menu.available : true,
|
||||||
|
recommended: menu.recommended !== undefined ? menu.recommended : false,
|
||||||
|
// ✅ 이미지 필드 수정: 백엔드는 'image' 필드 사용
|
||||||
|
imageUrl: menu.image || menu.imageUrl || '/images/menu-placeholder.png',
|
||||||
|
image: menu.image || menu.imageUrl, // 백엔드 호환성
|
||||||
|
createdAt: menu.createdAt,
|
||||||
|
updatedAt: menu.updatedAt
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
// 메뉴 목록이 있는 경우
|
||||||
// 매장 정보에서 storeId 가져오기
|
console.log('✅ 메뉴 목록 설정 (이미지 필드 매핑 완료):', menusWithId)
|
||||||
const storeId = this.storeInfo?.storeId
|
this.menus = menusWithId
|
||||||
if (!storeId) {
|
return { success: true, data: menusWithId }
|
||||||
console.warn('매장 ID가 없습니다. 매장 정보를 먼저 조회해주세요.')
|
} else {
|
||||||
return { success: false, message: '매장 정보가 필요합니다', data: [] }
|
// 메뉴가 없거나 조회 실패한 경우
|
||||||
}
|
console.log('⚠️ 메뉴 목록 없음 또는 조회 실패')
|
||||||
|
this.menus = []
|
||||||
// 메뉴 서비스 임포트
|
|
||||||
const { menuService } = await import('@/services/menu')
|
if (result.message === '등록된 메뉴가 없습니다') {
|
||||||
|
return { success: false, message: '등록된 메뉴가 없습니다', data: [] }
|
||||||
console.log('메뉴 목록 API 호출, 매장 ID:', storeId)
|
} else {
|
||||||
const result = await menuService.getMenus(storeId)
|
return { success: false, message: result.message || '메뉴 목록 조회에 실패했습니다', data: [] }
|
||||||
|
|
||||||
console.log('=== Store 스토어: 메뉴 API 응답 분석 ===')
|
|
||||||
console.log('Result:', result)
|
|
||||||
console.log('Result.success:', result.success)
|
|
||||||
console.log('Result.data:', result.data)
|
|
||||||
console.log('Result.message:', result.message)
|
|
||||||
|
|
||||||
if (result.success && result.data) {
|
|
||||||
// ✅ 메뉴 데이터 ID 필드 보장 처리
|
|
||||||
const menusWithId = (result.data || []).map(menu => {
|
|
||||||
// ID 필드가 확실히 있도록 보장
|
|
||||||
const menuId = menu.menuId || menu.id
|
|
||||||
|
|
||||||
if (!menuId) {
|
|
||||||
console.warn('⚠️ 메뉴 ID가 없는 항목 발견:', menu)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...menu,
|
|
||||||
id: menuId, // ✅ id 필드 확실히 설정
|
|
||||||
menuId: menuId, // ✅ menuId 필드도 설정
|
|
||||||
// 기타 필드들 보장
|
|
||||||
menuName: menu.menuName || menu.name || '이름 없음',
|
|
||||||
category: menu.category || '기타',
|
|
||||||
price: menu.price || 0,
|
|
||||||
description: menu.description || '',
|
|
||||||
available: menu.available !== undefined ? menu.available : true,
|
|
||||||
recommended: menu.recommended !== undefined ? menu.recommended : false,
|
|
||||||
imageUrl: menu.imageUrl || '/images/menu-placeholder.png'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 메뉴 목록이 있는 경우
|
|
||||||
console.log('✅ 메뉴 목록 설정 (ID 보장됨):', menusWithId)
|
|
||||||
this.menus = menusWithId
|
|
||||||
return { success: true, data: menusWithId }
|
|
||||||
} else {
|
|
||||||
// 메뉴가 없거나 조회 실패한 경우
|
|
||||||
console.log('⚠️ 메뉴 목록 없음 또는 조회 실패')
|
|
||||||
this.menus = []
|
|
||||||
|
|
||||||
if (result.message === '등록된 메뉴가 없습니다') {
|
|
||||||
return { success: false, message: '등록된 메뉴가 없습니다', data: [] }
|
|
||||||
} else {
|
|
||||||
return { success: false, message: result.message || '메뉴 목록 조회에 실패했습니다', data: [] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('=== Store 스토어: 메뉴 목록 조회 실패 ===')
|
|
||||||
console.error('Error:', error)
|
|
||||||
|
|
||||||
this.menus = []
|
|
||||||
return { success: false, message: error.message || '메뉴 목록을 불러오는데 실패했습니다', data: [] }
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('=== Store 스토어: 메뉴 목록 조회 실패 ===')
|
||||||
|
console.error('Error:', error)
|
||||||
|
|
||||||
|
this.menus = []
|
||||||
|
return { success: false, message: error.message || '메뉴 목록을 불러오는데 실패했습니다', data: [] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 매장 등록
|
* 매장 등록
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user