source edit
This commit is contained in:
parent
dc87099df7
commit
de983127a3
@ -1,9 +1,9 @@
|
|||||||
//* src/services/content.js
|
//* src/services/content.js - 기존 파일 수정 (API 설계서 기준)
|
||||||
import { contentApi, handleApiError, formatSuccessResponse } from './api.js'
|
import { contentApi, handleApiError, formatSuccessResponse } from './api.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 마케팅 콘텐츠 관련 API 서비스
|
* 마케팅 콘텐츠 관련 API 서비스
|
||||||
* 유저스토리: CON-005, CON-015, CON-020, CON-025, CON-030
|
* API 설계서 기준으로 수정됨
|
||||||
*/
|
*/
|
||||||
class ContentService {
|
class ContentService {
|
||||||
/**
|
/**
|
||||||
@ -14,16 +14,22 @@ class ContentService {
|
|||||||
async generateSnsContent(contentData) {
|
async generateSnsContent(contentData) {
|
||||||
try {
|
try {
|
||||||
const response = await contentApi.post('/sns/generate', {
|
const response = await contentApi.post('/sns/generate', {
|
||||||
targetType: contentData.targetType, // 메뉴, 매장, 이벤트
|
storeId: contentData.storeId,
|
||||||
platform: contentData.platform, // 인스타그램, 네이버블로그
|
platform: contentData.platform,
|
||||||
images: contentData.images,
|
title: contentData.title,
|
||||||
|
category: contentData.category,
|
||||||
|
requirement: contentData.requirement || contentData.requirements,
|
||||||
|
toneAndManner: contentData.toneAndManner,
|
||||||
|
emotionalIntensity: contentData.emotionalIntensity || contentData.emotionIntensity,
|
||||||
|
targetAudience: contentData.targetAudience,
|
||||||
|
promotionalType: contentData.promotionalType || contentData.promotionType,
|
||||||
eventName: contentData.eventName,
|
eventName: contentData.eventName,
|
||||||
startDate: contentData.startDate,
|
eventDate: contentData.eventDate,
|
||||||
endDate: contentData.endDate,
|
hashtagStyle: contentData.hashtagStyle,
|
||||||
toneAndManner: contentData.toneAndManner, // 친근함, 전문적, 유머러스, 고급스러운
|
hashtagCount: contentData.hashtagCount || 10,
|
||||||
promotionType: contentData.promotionType, // 할인정보, 이벤트정보, 신메뉴알림, 없음
|
includeCallToAction: contentData.includeCallToAction || false,
|
||||||
emotionIntensity: contentData.emotionIntensity, // 차분함, 보통, 열정적, 과장된
|
includeEmoji: contentData.includeEmoji || true,
|
||||||
requirements: contentData.requirements || '',
|
contentLength: contentData.contentLength || '보통'
|
||||||
})
|
})
|
||||||
|
|
||||||
return formatSuccessResponse(response.data.data, 'SNS 게시물이 생성되었습니다.')
|
return formatSuccessResponse(response.data.data, 'SNS 게시물이 생성되었습니다.')
|
||||||
@ -33,7 +39,7 @@ class ContentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SNS 게시물 저장
|
* SNS 게시물 저장 (CON-010: SNS 게시물 저장)
|
||||||
* @param {Object} saveData - 저장할 SNS 콘텐츠 정보
|
* @param {Object} saveData - 저장할 SNS 콘텐츠 정보
|
||||||
* @returns {Promise<Object>} 저장 결과
|
* @returns {Promise<Object>} 저장 결과
|
||||||
*/
|
*/
|
||||||
@ -43,10 +49,14 @@ class ContentService {
|
|||||||
title: saveData.title,
|
title: saveData.title,
|
||||||
content: saveData.content,
|
content: saveData.content,
|
||||||
hashtags: saveData.hashtags,
|
hashtags: saveData.hashtags,
|
||||||
images: saveData.images,
|
|
||||||
platform: saveData.platform,
|
platform: saveData.platform,
|
||||||
status: saveData.status || 'DRAFT',
|
category: saveData.category,
|
||||||
publishSchedule: saveData.publishSchedule,
|
toneAndManner: saveData.toneAndManner,
|
||||||
|
targetAudience: saveData.targetAudience,
|
||||||
|
promotionalType: saveData.promotionalType,
|
||||||
|
eventName: saveData.eventName,
|
||||||
|
eventDate: saveData.eventDate,
|
||||||
|
status: saveData.status || 'DRAFT'
|
||||||
})
|
})
|
||||||
|
|
||||||
return formatSuccessResponse(response.data.data, 'SNS 게시물이 저장되었습니다.')
|
return formatSuccessResponse(response.data.data, 'SNS 게시물이 저장되었습니다.')
|
||||||
@ -63,15 +73,19 @@ class ContentService {
|
|||||||
async generatePoster(posterData) {
|
async generatePoster(posterData) {
|
||||||
try {
|
try {
|
||||||
const response = await contentApi.post('/poster/generate', {
|
const response = await contentApi.post('/poster/generate', {
|
||||||
|
storeId: posterData.storeId,
|
||||||
|
title: posterData.title,
|
||||||
targetType: posterData.targetType,
|
targetType: posterData.targetType,
|
||||||
images: posterData.images,
|
|
||||||
eventName: posterData.eventName,
|
eventName: posterData.eventName,
|
||||||
startDate: posterData.startDate,
|
eventDate: posterData.eventDate,
|
||||||
endDate: posterData.endDate,
|
discountInfo: posterData.discountInfo,
|
||||||
photoStyle: posterData.photoStyle, // 모던, 클래식, 감성적
|
designStyle: posterData.designStyle,
|
||||||
promotionType: posterData.promotionType,
|
colorScheme: posterData.colorScheme,
|
||||||
emotionIntensity: posterData.emotionIntensity,
|
includeQrCode: posterData.includeQrCode || false,
|
||||||
sizes: posterData.sizes || ['1:1', '9:16', '16:9'], // SNS용, 스토리용, 블로그용
|
includeContact: posterData.includeContact || true,
|
||||||
|
imageStyle: posterData.imageStyle || posterData.photoStyle,
|
||||||
|
layoutType: posterData.layoutType,
|
||||||
|
sizes: posterData.sizes || ['1:1', '9:16', '16:9']
|
||||||
})
|
})
|
||||||
|
|
||||||
return formatSuccessResponse(response.data.data, '홍보 포스터가 생성되었습니다.')
|
return formatSuccessResponse(response.data.data, '홍보 포스터가 생성되었습니다.')
|
||||||
@ -81,7 +95,7 @@ class ContentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 홍보 포스터 저장
|
* 홍보 포스터 저장 (CON-016: 홍보 포스터 저장)
|
||||||
* @param {Object} saveData - 저장할 포스터 정보
|
* @param {Object} saveData - 저장할 포스터 정보
|
||||||
* @returns {Promise<Object>} 저장 결과
|
* @returns {Promise<Object>} 저장 결과
|
||||||
*/
|
*/
|
||||||
@ -93,7 +107,7 @@ class ContentService {
|
|||||||
posterSizes: saveData.posterSizes,
|
posterSizes: saveData.posterSizes,
|
||||||
targetType: saveData.targetType,
|
targetType: saveData.targetType,
|
||||||
eventName: saveData.eventName,
|
eventName: saveData.eventName,
|
||||||
status: saveData.status || 'DRAFT',
|
status: saveData.status || 'DRAFT'
|
||||||
})
|
})
|
||||||
|
|
||||||
return formatSuccessResponse(response.data.data, '홍보 포스터가 저장되었습니다.')
|
return formatSuccessResponse(response.data.data, '홍보 포스터가 저장되었습니다.')
|
||||||
@ -171,7 +185,7 @@ class ContentService {
|
|||||||
hashtags: updateData.hashtags,
|
hashtags: updateData.hashtags,
|
||||||
startDate: updateData.startDate,
|
startDate: updateData.startDate,
|
||||||
endDate: updateData.endDate,
|
endDate: updateData.endDate,
|
||||||
status: updateData.status,
|
status: updateData.status
|
||||||
})
|
})
|
||||||
|
|
||||||
return formatSuccessResponse(response.data.data, '콘텐츠가 수정되었습니다.')
|
return formatSuccessResponse(response.data.data, '콘텐츠가 수정되었습니다.')
|
||||||
@ -194,54 +208,6 @@ class ContentService {
|
|||||||
return handleApiError(error)
|
return handleApiError(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 다중 콘텐츠 삭제
|
|
||||||
* @param {number[]} contentIds - 삭제할 콘텐츠 ID 배열
|
|
||||||
* @returns {Promise<Object>} 삭제 결과
|
|
||||||
*/
|
|
||||||
async deleteContents(contentIds) {
|
|
||||||
try {
|
|
||||||
const deletePromises = contentIds.map((contentId) => this.deleteContent(contentId))
|
|
||||||
await Promise.all(deletePromises)
|
|
||||||
|
|
||||||
return formatSuccessResponse(null, `${contentIds.length}개의 콘텐츠가 삭제되었습니다.`)
|
|
||||||
} catch (error) {
|
|
||||||
return handleApiError(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 콘텐츠 재생성
|
|
||||||
* @param {number} contentId - 원본 콘텐츠 ID
|
|
||||||
* @param {Object} regenerateOptions - 재생성 옵션
|
|
||||||
* @returns {Promise<Object>} 재생성된 콘텐츠
|
|
||||||
*/
|
|
||||||
async regenerateContent(contentId, regenerateOptions = {}) {
|
|
||||||
try {
|
|
||||||
const response = await contentApi.post(`/${contentId}/regenerate`, regenerateOptions)
|
|
||||||
|
|
||||||
return formatSuccessResponse(response.data.data, '콘텐츠가 재생성되었습니다.')
|
|
||||||
} catch (error) {
|
|
||||||
return handleApiError(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 콘텐츠 발행 상태 변경
|
|
||||||
* @param {number} contentId - 콘텐츠 ID
|
|
||||||
* @param {string} status - 변경할 상태 (DRAFT, PUBLISHED, ARCHIVED)
|
|
||||||
* @returns {Promise<Object>} 상태 변경 결과
|
|
||||||
*/
|
|
||||||
async updateContentStatus(contentId, status) {
|
|
||||||
try {
|
|
||||||
const response = await contentApi.patch(`/${contentId}/status`, { status })
|
|
||||||
|
|
||||||
return formatSuccessResponse(response.data.data, '콘텐츠 상태가 변경되었습니다.')
|
|
||||||
} catch (error) {
|
|
||||||
return handleApiError(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const contentService = new ContentService()
|
export const contentService = new ContentService()
|
||||||
|
|||||||
65
src/services/memberService.js
Normal file
65
src/services/memberService.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
//* src/services/memberService.js - 새로 생성
|
||||||
|
import { memberApi, handleApiError, formatSuccessResponse } from './api.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회원 관리 API 서비스
|
||||||
|
* API 설계서 기준
|
||||||
|
*/
|
||||||
|
class MemberService {
|
||||||
|
/**
|
||||||
|
* 회원가입 (USR-030: 회원등록)
|
||||||
|
* @param {Object} userData - 회원 정보
|
||||||
|
* @returns {Promise<Object>} 회원가입 결과
|
||||||
|
*/
|
||||||
|
async register(userData) {
|
||||||
|
try {
|
||||||
|
const response = await memberApi.post('/register', {
|
||||||
|
userId: userData.userId,
|
||||||
|
password: userData.password,
|
||||||
|
name: userData.name,
|
||||||
|
businessNumber: userData.businessNumber,
|
||||||
|
email: userData.email
|
||||||
|
})
|
||||||
|
|
||||||
|
return formatSuccessResponse(response.data.data, '회원가입이 완료되었습니다.')
|
||||||
|
} catch (error) {
|
||||||
|
return handleApiError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID 중복 확인 (USR-035: 중복ID검사)
|
||||||
|
* @param {string} userId - 확인할 사용자 ID
|
||||||
|
* @returns {Promise<Object>} 중복 확인 결과
|
||||||
|
*/
|
||||||
|
async checkDuplicate(userId) {
|
||||||
|
try {
|
||||||
|
const response = await memberApi.get('/check-duplicate', {
|
||||||
|
params: { userId }
|
||||||
|
})
|
||||||
|
|
||||||
|
return formatSuccessResponse(response.data.data, response.data.data.message)
|
||||||
|
} catch (error) {
|
||||||
|
return handleApiError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 암호 유효성 검증 (USR-040: 암호유효성 검사)
|
||||||
|
* @param {string} password - 검증할 암호
|
||||||
|
* @returns {Promise<Object>} 유효성 검증 결과
|
||||||
|
*/
|
||||||
|
async validatePassword(password) {
|
||||||
|
try {
|
||||||
|
const response = await memberApi.post('/validate-password', { password })
|
||||||
|
|
||||||
|
return formatSuccessResponse(response.data.data, response.data.data.message)
|
||||||
|
} catch (error) {
|
||||||
|
return handleApiError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const memberService = new MemberService()
|
||||||
|
export default memberService
|
||||||
|
|
||||||
34
src/services/recommendationService.js
Normal file
34
src/services/recommendationService.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
//* src/services/recommendationService.js - 새로 생성
|
||||||
|
import { recommendApi, handleApiError, formatSuccessResponse } from './api.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI 추천 관련 API 서비스
|
||||||
|
* API 설계서 기준
|
||||||
|
*/
|
||||||
|
class RecommendationService {
|
||||||
|
/**
|
||||||
|
* AI 마케팅 팁 생성 (REC-005: AI 마케팅 팁 생성)
|
||||||
|
* @param {Object} requestData - 마케팅 팁 요청 정보
|
||||||
|
* @returns {Promise<Object>} 생성된 마케팅 팁
|
||||||
|
*/
|
||||||
|
async generateMarketingTips(requestData) {
|
||||||
|
try {
|
||||||
|
const response = await recommendApi.post('/marketing-tips', {
|
||||||
|
storeId: requestData.storeId,
|
||||||
|
businessType: requestData.businessType,
|
||||||
|
targetSeason: requestData.targetSeason,
|
||||||
|
currentChallenges: requestData.currentChallenges,
|
||||||
|
marketingGoals: requestData.marketingGoals,
|
||||||
|
budget: requestData.budget,
|
||||||
|
preferredChannels: requestData.preferredChannels
|
||||||
|
})
|
||||||
|
|
||||||
|
return formatSuccessResponse(response.data.data, 'AI 마케팅 팁이 생성되었습니다.')
|
||||||
|
} catch (error) {
|
||||||
|
return handleApiError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const recommendationService = new RecommendationService()
|
||||||
|
export default recommendationService
|
||||||
@ -1,9 +1,9 @@
|
|||||||
//* src/services/store.js
|
//* src/services/store.js - 기존 파일 수정 (API 설계서 기준)
|
||||||
import { storeApi, handleApiError, formatSuccessResponse } from './api.js'
|
import { storeApi, menuApi, salesApi, handleApiError, formatSuccessResponse } from './api.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 매장 관련 API 서비스
|
* 매장 관련 API 서비스
|
||||||
* 유저스토리: STR-005, STR-010, STR-015, STR-020, STR-025, STR-030, STR-035, STR-040
|
* API 설계서 기준으로 수정됨
|
||||||
*/
|
*/
|
||||||
class StoreService {
|
class StoreService {
|
||||||
/**
|
/**
|
||||||
@ -15,16 +15,14 @@ class StoreService {
|
|||||||
try {
|
try {
|
||||||
const response = await storeApi.post('/register', {
|
const response = await storeApi.post('/register', {
|
||||||
storeName: storeData.storeName,
|
storeName: storeData.storeName,
|
||||||
storeImage: storeData.storeImage,
|
|
||||||
businessType: storeData.businessType,
|
businessType: storeData.businessType,
|
||||||
address: storeData.address,
|
address: storeData.address,
|
||||||
phoneNumber: storeData.phoneNumber,
|
phoneNumber: storeData.phoneNumber,
|
||||||
businessNumber: storeData.businessNumber,
|
businessHours: storeData.businessHours || storeData.operatingHours,
|
||||||
instaAccount: storeData.instaAccount,
|
|
||||||
naverBlogAccount: storeData.naverBlogAccount,
|
|
||||||
operatingHours: storeData.operatingHours,
|
|
||||||
closedDays: storeData.closedDays,
|
closedDays: storeData.closedDays,
|
||||||
seatCount: storeData.seatCount,
|
seatCount: storeData.seatCount,
|
||||||
|
snsAccounts: storeData.snsAccounts || `인스타그램: ${storeData.instaAccount || ''}, 네이버블로그: ${storeData.naverBlogAccount || ''}`,
|
||||||
|
description: storeData.description || ''
|
||||||
})
|
})
|
||||||
|
|
||||||
return formatSuccessResponse(response.data.data, '매장이 등록되었습니다.')
|
return formatSuccessResponse(response.data.data, '매장이 등록되었습니다.')
|
||||||
@ -34,7 +32,7 @@ class StoreService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 매장 정보 조회 (STR-005: 매장 정보 관리)
|
* 매장 정보 조회 (STR-005: 매장 조회)
|
||||||
* @returns {Promise<Object>} 매장 정보
|
* @returns {Promise<Object>} 매장 정보
|
||||||
*/
|
*/
|
||||||
async getStore() {
|
async getStore() {
|
||||||
@ -49,13 +47,22 @@ class StoreService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 매장 정보 수정 (STR-010: 매장 수정)
|
* 매장 정보 수정 (STR-010: 매장 수정)
|
||||||
* @param {number} storeId - 매장 ID
|
|
||||||
* @param {Object} storeData - 수정할 매장 정보
|
* @param {Object} storeData - 수정할 매장 정보
|
||||||
* @returns {Promise<Object>} 매장 수정 결과
|
* @returns {Promise<Object>} 매장 수정 결과
|
||||||
*/
|
*/
|
||||||
async updateStore(storeId, storeData) {
|
async updateStore(storeData) {
|
||||||
try {
|
try {
|
||||||
const response = await storeApi.put(`/${storeId}`, storeData)
|
const response = await storeApi.put('/', {
|
||||||
|
storeName: storeData.storeName,
|
||||||
|
businessType: storeData.businessType,
|
||||||
|
address: storeData.address,
|
||||||
|
phoneNumber: storeData.phoneNumber,
|
||||||
|
businessHours: storeData.businessHours || storeData.operatingHours,
|
||||||
|
closedDays: storeData.closedDays,
|
||||||
|
seatCount: storeData.seatCount,
|
||||||
|
snsAccounts: storeData.snsAccounts || `인스타그램: ${storeData.instaAccount || ''}, 네이버블로그: ${storeData.naverBlogAccount || ''}`,
|
||||||
|
description: storeData.description || ''
|
||||||
|
})
|
||||||
|
|
||||||
return formatSuccessResponse(response.data.data, '매장 정보가 수정되었습니다.')
|
return formatSuccessResponse(response.data.data, '매장 정보가 수정되었습니다.')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -64,13 +71,13 @@ class StoreService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 매출 정보 조회 (STR-020: 대시보드)
|
* 매출 정보 조회 (SAL-005: 매출 조회)
|
||||||
* @param {string} period - 조회 기간 (today, week, month, year)
|
* @param {number} storeId - 매장 ID
|
||||||
* @returns {Promise<Object>} 매출 정보
|
* @returns {Promise<Object>} 매출 정보
|
||||||
*/
|
*/
|
||||||
async getSales(period = 'today') {
|
async getSales(storeId) {
|
||||||
try {
|
try {
|
||||||
const response = await storeApi.get(`/sales?period=${period}`)
|
const response = await salesApi.get(`/${storeId}`)
|
||||||
|
|
||||||
return formatSuccessResponse(response.data.data, '매출 정보를 조회했습니다.')
|
return formatSuccessResponse(response.data.data, '매출 정보를 조회했습니다.')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -79,16 +86,16 @@ class StoreService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 메뉴 등록 (STR-030: 메뉴 등록)
|
* 메뉴 등록 (MNU-010: 메뉴 등록)
|
||||||
* @param {Object} menuData - 메뉴 정보
|
* @param {Object} menuData - 메뉴 정보
|
||||||
* @returns {Promise<Object>} 메뉴 등록 결과
|
* @returns {Promise<Object>} 메뉴 등록 결과
|
||||||
*/
|
*/
|
||||||
async registerMenu(menuData) {
|
async registerMenu(menuData) {
|
||||||
try {
|
try {
|
||||||
const response = await storeApi.post('/menu/register', {
|
const response = await menuApi.post('/register', {
|
||||||
menuName: menuData.menuName,
|
menuName: menuData.menuName,
|
||||||
menuCategory: menuData.menuCategory,
|
menuCategory: menuData.menuCategory || menuData.category,
|
||||||
menuImage: menuData.menuImage,
|
menuImage: menuData.menuImage || menuData.image,
|
||||||
price: menuData.price,
|
price: menuData.price,
|
||||||
description: menuData.description,
|
description: menuData.description,
|
||||||
isPopular: menuData.isPopular || false,
|
isPopular: menuData.isPopular || false,
|
||||||
@ -102,19 +109,13 @@ class StoreService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 메뉴 목록 조회 (STR-025: 메뉴 조회)
|
* 메뉴 목록 조회 (MNU-005: 메뉴 조회)
|
||||||
* @param {Object} filters - 필터링 옵션
|
* @param {number} storeId - 매장 ID
|
||||||
* @returns {Promise<Object>} 메뉴 목록
|
* @returns {Promise<Object>} 메뉴 목록
|
||||||
*/
|
*/
|
||||||
async getMenus(filters = {}) {
|
async getMenus(storeId) {
|
||||||
try {
|
try {
|
||||||
const queryParams = new URLSearchParams()
|
const response = await menuApi.get(`/${storeId}`)
|
||||||
|
|
||||||
if (filters.category) queryParams.append('category', filters.category)
|
|
||||||
if (filters.sortBy) queryParams.append('sortBy', filters.sortBy)
|
|
||||||
if (filters.isPopular !== undefined) queryParams.append('isPopular', filters.isPopular)
|
|
||||||
|
|
||||||
const response = await storeApi.get(`/menu?${queryParams.toString()}`)
|
|
||||||
|
|
||||||
return formatSuccessResponse(response.data.data, '메뉴 목록을 조회했습니다.')
|
return formatSuccessResponse(response.data.data, '메뉴 목록을 조회했습니다.')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -123,14 +124,22 @@ class StoreService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 메뉴 수정 (STR-035: 메뉴 수정)
|
* 메뉴 수정 (MNU-015: 메뉴 수정)
|
||||||
* @param {number} menuId - 메뉴 ID
|
* @param {number} menuId - 메뉴 ID
|
||||||
* @param {Object} menuData - 수정할 메뉴 정보
|
* @param {Object} menuData - 수정할 메뉴 정보
|
||||||
* @returns {Promise<Object>} 메뉴 수정 결과
|
* @returns {Promise<Object>} 메뉴 수정 결과
|
||||||
*/
|
*/
|
||||||
async updateMenu(menuId, menuData) {
|
async updateMenu(menuId, menuData) {
|
||||||
try {
|
try {
|
||||||
const response = await storeApi.put(`/menu/${menuId}`, menuData)
|
const response = await menuApi.put(`/${menuId}`, {
|
||||||
|
menuName: menuData.menuName,
|
||||||
|
menuCategory: menuData.menuCategory || menuData.category,
|
||||||
|
menuImage: menuData.menuImage || menuData.image,
|
||||||
|
price: menuData.price,
|
||||||
|
description: menuData.description,
|
||||||
|
isPopular: menuData.isPopular || false,
|
||||||
|
isRecommended: menuData.isRecommended || false,
|
||||||
|
})
|
||||||
|
|
||||||
return formatSuccessResponse(response.data.data, '메뉴가 수정되었습니다.')
|
return formatSuccessResponse(response.data.data, '메뉴가 수정되었습니다.')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -139,13 +148,13 @@ class StoreService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 메뉴 삭제 (STR-040: 메뉴 삭제)
|
* 메뉴 삭제 (MNU-020: 메뉴 삭제)
|
||||||
* @param {number} menuId - 메뉴 ID
|
* @param {number} menuId - 메뉴 ID
|
||||||
* @returns {Promise<Object>} 메뉴 삭제 결과
|
* @returns {Promise<Object>} 메뉴 삭제 결과
|
||||||
*/
|
*/
|
||||||
async deleteMenu(menuId) {
|
async deleteMenu(menuId) {
|
||||||
try {
|
try {
|
||||||
await storeApi.delete(`/menu/${menuId}`)
|
await menuApi.delete(`/${menuId}`)
|
||||||
|
|
||||||
return formatSuccessResponse(null, '메뉴가 삭제되었습니다.')
|
return formatSuccessResponse(null, '메뉴가 삭제되었습니다.')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -168,20 +177,6 @@ class StoreService {
|
|||||||
return handleApiError(error)
|
return handleApiError(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 매장 통계 정보 조회
|
|
||||||
* @returns {Promise<Object>} 매장 통계
|
|
||||||
*/
|
|
||||||
async getStoreStatistics() {
|
|
||||||
try {
|
|
||||||
const response = await storeApi.get('/statistics')
|
|
||||||
|
|
||||||
return formatSuccessResponse(response.data.data, '매장 통계를 조회했습니다.')
|
|
||||||
} catch (error) {
|
|
||||||
return handleApiError(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const storeService = new StoreService()
|
export const storeService = new StoreService()
|
||||||
|
|||||||
@ -1,155 +1,82 @@
|
|||||||
//* src/store/auth.js
|
//* src/store/auth.js 수정 - 기존 구조 유지하고 API 연동만 추가
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, computed } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import authService from '@/services/auth'
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
// 상태
|
// 기존 상태들 유지
|
||||||
const user = ref(null)
|
const user = ref(null)
|
||||||
const token = ref(null)
|
const token = ref(null)
|
||||||
const refreshToken = ref(null)
|
const isAuthenticated = ref(false)
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
|
||||||
// 컴퓨티드 - 더 엄격한 인증 체크
|
// 기존 checkAuthState 메서드 유지
|
||||||
const isAuthenticated = computed(() => {
|
const checkAuthState = () => {
|
||||||
return !!(token.value && user.value)
|
const storedToken = localStorage.getItem('accessToken')
|
||||||
})
|
const storedUser = localStorage.getItem('userInfo')
|
||||||
|
|
||||||
// 액션
|
if (storedToken && storedUser) {
|
||||||
|
token.value = storedToken
|
||||||
|
user.value = JSON.parse(storedUser)
|
||||||
|
isAuthenticated.value = true
|
||||||
|
} else {
|
||||||
|
token.value = null
|
||||||
|
user.value = null
|
||||||
|
isAuthenticated.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// login 메서드를 실제 API 호출로 수정
|
||||||
const login = async (credentials) => {
|
const login = async (credentials) => {
|
||||||
console.log('=== AUTH STORE 로그인 시작 ===')
|
|
||||||
console.log('받은 자격증명:', credentials)
|
|
||||||
|
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 실제 API 호출 시뮬레이션
|
const result = await authService.login(credentials)
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
||||||
|
|
||||||
// 정확한 자격증명 확인 (대소문자 구분, 공백 제거)
|
if (result.success) {
|
||||||
const username = credentials.username?.trim()
|
token.value = result.data.token
|
||||||
const password = credentials.password?.trim()
|
user.value = result.data.user
|
||||||
|
isAuthenticated.value = true
|
||||||
|
|
||||||
console.log('정제된 자격증명:', { username, password })
|
|
||||||
console.log('예상 자격증명:', { username: 'user01', password: 'passw0rd' })
|
|
||||||
console.log('username 일치:', username === 'user01')
|
|
||||||
console.log('password 일치:', password === 'passw0rd')
|
|
||||||
|
|
||||||
if (username === 'user01' && password === 'passw0rd') {
|
|
||||||
const mockToken = 'mock_jwt_token_' + Date.now()
|
|
||||||
const mockUser = {
|
|
||||||
id: 1,
|
|
||||||
username: username,
|
|
||||||
nickname: '김사장',
|
|
||||||
businessName: '김사장님의 분식점',
|
|
||||||
email: 'kim@example.com',
|
|
||||||
avatar: '/images/avatar.png',
|
|
||||||
}
|
|
||||||
|
|
||||||
// 토큰과 사용자 정보 저장
|
|
||||||
token.value = mockToken
|
|
||||||
user.value = mockUser
|
|
||||||
|
|
||||||
// 로컬 스토리지에 저장
|
|
||||||
localStorage.setItem('auth_token', mockToken)
|
|
||||||
localStorage.setItem('user_info', JSON.stringify(mockUser))
|
|
||||||
|
|
||||||
console.log('로그인 성공! 사용자:', mockUser.nickname)
|
|
||||||
return { success: true }
|
return { success: true }
|
||||||
} else {
|
} else {
|
||||||
console.log('로그인 실패 - 자격증명 불일치')
|
return { success: false, error: result.message }
|
||||||
throw new Error('아이디 또는 비밀번호가 올바르지 않습니다.')
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('로그인 실패:', error)
|
return { success: false, error: '네트워크 오류가 발생했습니다.' }
|
||||||
return { success: false, error: error.message }
|
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logout 메서드를 실제 API 호출로 수정
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 로컬 상태 초기화
|
await authService.logout()
|
||||||
user.value = null
|
} catch (error) {
|
||||||
|
console.warn('로그아웃 API 호출 실패:', error)
|
||||||
|
} finally {
|
||||||
|
// 상태 초기화
|
||||||
token.value = null
|
token.value = null
|
||||||
refreshToken.value = null
|
|
||||||
|
|
||||||
// 로컬 스토리지 정리
|
|
||||||
localStorage.removeItem('auth_token')
|
|
||||||
localStorage.removeItem('refresh_token')
|
|
||||||
localStorage.removeItem('user_info')
|
|
||||||
|
|
||||||
console.log('로그아웃 완료')
|
|
||||||
return { success: true }
|
|
||||||
} catch (error) {
|
|
||||||
console.error('로그아웃 실패:', error)
|
|
||||||
return { success: false, error: error.message }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkAuthState = () => {
|
|
||||||
try {
|
|
||||||
const savedToken = localStorage.getItem('auth_token')
|
|
||||||
const savedUser = localStorage.getItem('user_info')
|
|
||||||
|
|
||||||
if (savedToken && savedUser) {
|
|
||||||
// JSON 파싱이 가능한지 확인
|
|
||||||
const parsedUser = JSON.parse(savedUser)
|
|
||||||
if (parsedUser && parsedUser.id) {
|
|
||||||
token.value = savedToken
|
|
||||||
user.value = parsedUser
|
|
||||||
console.log('저장된 인증 정보 복원됨:', parsedUser.nickname)
|
|
||||||
} else {
|
|
||||||
// 잘못된 사용자 정보인 경우 정리
|
|
||||||
clearAuthData()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('저장된 인증 정보가 없음')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('인증 상태 확인 실패:', error)
|
|
||||||
clearAuthData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearAuthData = () => {
|
|
||||||
user.value = null
|
user.value = null
|
||||||
token.value = null
|
isAuthenticated.value = false
|
||||||
refreshToken.value = null
|
isLoading.value = false
|
||||||
localStorage.removeItem('auth_token')
|
}
|
||||||
localStorage.removeItem('refresh_token')
|
|
||||||
localStorage.removeItem('user_info')
|
|
||||||
console.log('인증 데이터 초기화됨')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshUserInfo = async () => {
|
// 초기화 시 인증 상태 확인
|
||||||
if (!token.value) return
|
checkAuthState()
|
||||||
|
|
||||||
try {
|
|
||||||
// 실제 API 호출로 사용자 정보 갱신
|
|
||||||
// const response = await api.get('/auth/me')
|
|
||||||
// user.value = response.data
|
|
||||||
} catch (error) {
|
|
||||||
console.error('사용자 정보 갱신 실패:', error)
|
|
||||||
// 토큰이 유효하지 않은 경우 로그아웃
|
|
||||||
logout()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 상태
|
|
||||||
user,
|
user,
|
||||||
token,
|
token,
|
||||||
isLoading,
|
|
||||||
|
|
||||||
// 컴퓨티드
|
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
|
isLoading,
|
||||||
// 액션
|
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
refreshUserInfo,
|
checkAuthState
|
||||||
checkAuthState,
|
|
||||||
clearAuthData,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,601 +1,168 @@
|
|||||||
//* src/store/content.js
|
//* src/store/content.js 수정 - 기존 구조 유지하고 API 연동만 추가
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
import contentService from '@/services/content'
|
||||||
|
|
||||||
/**
|
|
||||||
* 콘텐츠 관리 스토어
|
|
||||||
* 마케팅 콘텐츠 CRUD 및 상태 관리
|
|
||||||
*/
|
|
||||||
export const useContentStore = defineStore('content', () => {
|
export const useContentStore = defineStore('content', () => {
|
||||||
// State
|
// 기존 상태들 유지
|
||||||
const contents = ref([])
|
const contentList = ref([])
|
||||||
const currentContent = ref(null)
|
const ongoingContents = ref([])
|
||||||
|
const selectedContent = ref(null)
|
||||||
|
const generatedContent = ref(null)
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
const isGenerating = ref(false)
|
|
||||||
const filters = ref({
|
|
||||||
type: 'all',
|
|
||||||
platform: 'all',
|
|
||||||
status: 'all',
|
|
||||||
dateRange: 'all',
|
|
||||||
})
|
|
||||||
|
|
||||||
// 풍부한 더미 데이터
|
// 기존 computed 속성들 유지
|
||||||
const sampleContents = [
|
const contentCount = computed(() => contentList.value.length)
|
||||||
{
|
const ongoingContentCount = computed(() => ongoingContents.value.length)
|
||||||
id: 1,
|
|
||||||
title: '🌶️ 떡볶이 신메뉴 출시 이벤트',
|
|
||||||
type: 'sns',
|
|
||||||
platform: 'INSTAGRAM',
|
|
||||||
content: '🌶️ 떡볶이 신메뉴 출시! 치즈가 듬뿍 들어간 치즈떡볶이가 새로 나왔어요! 매콤한 떡볶이와 고소한 치즈의 완벽한 조화✨\n\n#떡볶이 #신메뉴 #치즈떡볶이 #맛집 #분식 #매콤달콤',
|
|
||||||
hashtags: ['떡볶이', '신메뉴', '치즈떡볶이', '맛집', '분식', '매콤달콤'],
|
|
||||||
images: ['/images/menu-placeholder.png'],
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
views: 1240,
|
|
||||||
likes: 85,
|
|
||||||
comments: 12,
|
|
||||||
createdAt: '2024-01-15T10:30:00',
|
|
||||||
publishedAt: '2024-01-15T14:00:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: '📸 우리 매장 소개 포스터',
|
|
||||||
type: 'poster',
|
|
||||||
platform: 'POSTER',
|
|
||||||
content: '따뜻한 분위기의 우리 매장을 소개합니다. 정성스럽게 만든 음식으로 여러분을 맞이하겠습니다.',
|
|
||||||
hashtags: ['매장소개', '분식집', '맛집', '정성'],
|
|
||||||
images: ['/images/store-placeholder.png'],
|
|
||||||
status: 'DRAFT',
|
|
||||||
views: 0,
|
|
||||||
likes: 0,
|
|
||||||
comments: 0,
|
|
||||||
createdAt: '2024-01-14T16:20:00',
|
|
||||||
publishedAt: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: '🎉 특별 할인 이벤트 안내',
|
|
||||||
type: 'sns',
|
|
||||||
platform: 'INSTAGRAM',
|
|
||||||
content: '🎉 오늘 하루만 특별 할인! 모든 메뉴 20% 할인 이벤트 진행중🔥\n\n놓치면 후회할 기회! 지금 바로 방문하세요!\n\n⏰ 할인 시간: 오후 6시까지\n📍 위치: 서울 강남구 테헤란로 123',
|
|
||||||
hashtags: ['할인', '이벤트', '특가', '분식', '강남맛집'],
|
|
||||||
images: ['/images/event-poster.png'],
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
views: 892,
|
|
||||||
likes: 45,
|
|
||||||
comments: 8,
|
|
||||||
createdAt: '2024-01-13T09:15:00',
|
|
||||||
publishedAt: '2024-01-13T12:00:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: '🍜 김치찌개 맛집 블로그 포스팅',
|
|
||||||
type: 'blog',
|
|
||||||
platform: 'NAVER_BLOG',
|
|
||||||
content: '추운 겨울, 몸을 따뜻하게 해줄 김치찌개 한 그릇은 어떠세요? 우리 매장의 시그니처 메뉴인 김치찌개를 소개합니다.\n\n✅ 3년 묵은 김치 사용\n✅ 국내산 돼지고기\n✅ 진한 국물맛\n✅ 푸짐한 양\n\n한 번 맛보시면 계속 생각나는 그런 맛입니다!',
|
|
||||||
hashtags: ['김치찌개', '맛집', '추천메뉴', '겨울음식', '따뜻한국물'],
|
|
||||||
images: ['/images/kimchi-jjigae.jpg'],
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
views: 2156,
|
|
||||||
likes: 123,
|
|
||||||
comments: 15,
|
|
||||||
createdAt: '2024-01-12T14:45:00',
|
|
||||||
publishedAt: '2024-01-12T16:00:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
title: '🥢 순대국밥 홍보 포스터',
|
|
||||||
type: 'poster',
|
|
||||||
platform: 'POSTER',
|
|
||||||
content: '진짜 맛있는 순대국밥! 24시간 끓인 진한 국물과 신선한 순대로 만든 특별한 한 그릇',
|
|
||||||
hashtags: ['순대국밥', '24시간', '진한국물', '신선한순대'],
|
|
||||||
images: ['/images/sundae-soup.jpg'],
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
views: 567,
|
|
||||||
likes: 34,
|
|
||||||
comments: 3,
|
|
||||||
createdAt: '2024-01-11T11:20:00',
|
|
||||||
publishedAt: '2024-01-11T15:30:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
title: '🌈 컬러풀 떡볶이 인스타 스토리',
|
|
||||||
type: 'sns',
|
|
||||||
platform: 'INSTAGRAM',
|
|
||||||
content: '무지개 떡볶이 만들어봤어요! 🌈 천연 색소로 만든 건강한 컬러 떡볶이✨ 맛도 색깔만큼 다양해요!\n\n빨강: 매운맛 🔥\n노랑: 치즈맛 🧀\n초록: 깻잎맛 🌿\n보라: 자색고구마맛 🍠',
|
|
||||||
hashtags: ['컬러떡볶이', '무지개떡볶이', '천연색소', '건강한간식', '예쁜음식'],
|
|
||||||
images: ['/images/colorful-tteokbokki.jpg'],
|
|
||||||
status: 'DRAFT',
|
|
||||||
views: 0,
|
|
||||||
likes: 0,
|
|
||||||
comments: 0,
|
|
||||||
createdAt: '2024-01-10T13:30:00',
|
|
||||||
publishedAt: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 7,
|
|
||||||
title: '🍖 불고기 김밥 신메뉴 소개',
|
|
||||||
type: 'blog',
|
|
||||||
platform: 'NAVER_BLOG',
|
|
||||||
content: '달콤짭짤한 불고기와 신선한 채소가 들어간 불고기 김밥이 새로 출시되었습니다!\n\n🥩 국내산 소고기 불고기\n🥬 신선한 상추와 당근\n🍚 고슬고슬한 밥\n🥒 아삭한 단무지\n\n한 입 베어물면 입 안 가득 퍼지는 불고기의 달콤함과 채소의 아삭함이 조화롭게 어우러집니다.',
|
|
||||||
hashtags: ['불고기김밥', '신메뉴', '국내산소고기', '신선한채소', '달콤짭짤'],
|
|
||||||
images: ['/images/bulgogi-kimbap.jpg'],
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
views: 1834,
|
|
||||||
likes: 76,
|
|
||||||
comments: 9,
|
|
||||||
createdAt: '2024-01-09T16:15:00',
|
|
||||||
publishedAt: '2024-01-09T18:00:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 8,
|
|
||||||
title: '🎊 개업 1주년 기념 이벤트',
|
|
||||||
type: 'poster',
|
|
||||||
platform: 'POSTER',
|
|
||||||
content: '감사합니다! 개업 1주년을 맞아 특별 이벤트를 준비했습니다. 고객님들의 사랑에 보답하는 마음으로!',
|
|
||||||
hashtags: ['1주년', '감사이벤트', '특별할인', '기념일'],
|
|
||||||
images: ['/images/anniversary-event.jpg'],
|
|
||||||
status: 'DRAFT',
|
|
||||||
views: 0,
|
|
||||||
likes: 0,
|
|
||||||
comments: 0,
|
|
||||||
createdAt: '2024-01-08T10:00:00',
|
|
||||||
publishedAt: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 9,
|
|
||||||
title: '🍲 라면 끓이기 꿀팁 공유',
|
|
||||||
type: 'sns',
|
|
||||||
platform: 'NAVER_BLOG',
|
|
||||||
content: '집에서도 맛있는 라면 끓이는 법! 우리 매장 셰프가 알려주는 특별한 비법을 공개합니다.\n\n1️⃣ 물이 끓기 시작할 때 면 투입\n2️⃣ 스프는 면이 익은 후 넣기\n3️⃣ 계란은 마지막 1분에 추가\n4️⃣ 파는 불을 끄기 직전에!\n\n이 방법으로 끓이면 훨씬 더 맛있어요! 한번 시도해보세요 😊',
|
|
||||||
hashtags: ['라면끓이기', '꿀팁', '요리비법', '맛있는라면', '셰프추천'],
|
|
||||||
images: ['/images/ramen-cooking.jpg'],
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
views: 3245,
|
|
||||||
likes: 189,
|
|
||||||
comments: 27,
|
|
||||||
createdAt: '2024-01-07T12:30:00',
|
|
||||||
publishedAt: '2024-01-07T14:00:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 10,
|
|
||||||
title: '🥟 수제 만두 제작 과정',
|
|
||||||
type: 'sns',
|
|
||||||
platform: 'INSTAGRAM',
|
|
||||||
content: '손으로 직접 빚는 우리 매장의 수제 만두! 👐\n\n매일 아침 일찍 와서 정성스럽게 만듭니다 🥟\n신선한 재료만 사용해요!\n\n📹 만두 빚는 과정 영상도 준비했어요\n👀 스토리에서 확인하세요!',
|
|
||||||
hashtags: ['수제만두', '정성', '신선한재료', '매일제작', '장인정신'],
|
|
||||||
images: ['/images/handmade-mandu.jpg'],
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
views: 987,
|
|
||||||
likes: 67,
|
|
||||||
comments: 5,
|
|
||||||
createdAt: '2024-01-06T08:45:00',
|
|
||||||
publishedAt: '2024-01-06T10:00:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11,
|
|
||||||
title: '❄️ 겨울 한정 메뉴 출시',
|
|
||||||
type: 'poster',
|
|
||||||
platform: 'POSTER',
|
|
||||||
content: '추운 겨울을 따뜻하게! 겨울 한정 메뉴들이 출시되었습니다. 몸과 마음을 따뜻하게 해드릴게요.',
|
|
||||||
hashtags: ['겨울한정', '따뜻한음식', '한정메뉴', '계절메뉴'],
|
|
||||||
images: ['/images/winter-menu.jpg'],
|
|
||||||
status: 'DRAFT',
|
|
||||||
views: 0,
|
|
||||||
likes: 0,
|
|
||||||
comments: 0,
|
|
||||||
createdAt: '2024-01-05T15:20:00',
|
|
||||||
publishedAt: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 12,
|
|
||||||
title: '🍜 우동 맛집 후기 모음',
|
|
||||||
type: 'blog',
|
|
||||||
platform: 'NAVER_BLOG',
|
|
||||||
content: '고객님들이 남겨주신 우동 후기를 모아봤습니다! 정말 감사한 마음입니다 💕\n\n"국물이 정말 진해요!"\n"면발이 쫄깃쫄깃해서 좋아요"\n"야채가 신선하고 양도 많아요"\n"사장님이 친절하세요"\n\n앞으로도 더 맛있는 우동으로 보답하겠습니다!',
|
|
||||||
hashtags: ['우동', '고객후기', '진한국물', '쫄깃한면', '신선한야채'],
|
|
||||||
images: ['/images/udon-reviews.jpg'],
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
views: 1456,
|
|
||||||
likes: 89,
|
|
||||||
comments: 12,
|
|
||||||
createdAt: '2024-01-04T14:10:00',
|
|
||||||
publishedAt: '2024-01-04T16:30:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 13,
|
|
||||||
title: '🎯 주말 특가 이벤트',
|
|
||||||
type: 'sns',
|
|
||||||
platform: 'INSTAGRAM',
|
|
||||||
content: '주말 특가 이벤트! 🎯\n\n토요일, 일요일 이틀간만!\n모든 음료 50% 할인! 🥤\n\n✅ 콜라, 사이다\n✅ 주스류\n✅ 전통차\n✅ 커피\n\n주말에 가족, 친구들과 함께 오세요! 👨👩👧👦',
|
|
||||||
hashtags: ['주말특가', '음료할인', '50%할인', '가족외식', '친구모임'],
|
|
||||||
images: ['/images/weekend-drink-sale.jpg'],
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
views: 1123,
|
|
||||||
likes: 78,
|
|
||||||
comments: 14,
|
|
||||||
createdAt: '2024-01-03T11:00:00',
|
|
||||||
publishedAt: '2024-01-03T13:00:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 14,
|
|
||||||
title: '🏆 맛집 인증서 획득!',
|
|
||||||
type: 'poster',
|
|
||||||
platform: 'POSTER',
|
|
||||||
content: '드디어! 지역 맛집 인증서를 받았습니다! 고객님들의 사랑 덕분입니다. 감사합니다!',
|
|
||||||
hashtags: ['맛집인증', '지역맛집', '인증서', '고객감사'],
|
|
||||||
images: ['/images/restaurant-certificate.jpg'],
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
views: 756,
|
|
||||||
likes: 124,
|
|
||||||
comments: 18,
|
|
||||||
createdAt: '2024-01-02T09:30:00',
|
|
||||||
publishedAt: '2024-01-02T11:00:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 15,
|
|
||||||
title: '🌟 2024년 신년 인사',
|
|
||||||
type: 'sns',
|
|
||||||
platform: 'NAVER_BLOG',
|
|
||||||
content: '2024년 새해가 밝았습니다! ✨\n\n지난 한 해 동안 저희 매장을 사랑해주신 모든 고객님들께 진심으로 감사드립니다.\n\n새해에도 더욱 맛있는 음식과 따뜻한 서비스로 보답하겠습니다.\n\n🎊 새해 복 많이 받으세요! 🎊\n\n올해도 많은 관심과 사랑 부탁드립니다!',
|
|
||||||
hashtags: ['신년인사', '새해복', '고객감사', '2024년', '따뜻한서비스'],
|
|
||||||
images: ['/images/new-year-greeting.jpg'],
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
views: 2341,
|
|
||||||
likes: 156,
|
|
||||||
comments: 23,
|
|
||||||
createdAt: '2024-01-01T00:00:00',
|
|
||||||
publishedAt: '2024-01-01T09:00:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 16,
|
|
||||||
title: '🍕 피자떡볶이 실험중!',
|
|
||||||
type: 'sns',
|
|
||||||
platform: 'INSTAGRAM',
|
|
||||||
content: '새로운 메뉴 개발중! 🍕+🌶️\n\n피자떡볶이가 과연 맛있을까요?\nR&D 중인 신메뉴를 살짝 공개! 👀\n\n토마토 소스 베이스에\n모짜렐라 치즈 토핑\n바질과 오레가노 향신료까지!\n\n출시 전 맛보기 이벤트도 계획중이에요!',
|
|
||||||
hashtags: ['피자떡볶이', '신메뉴개발', 'R&D', '실험적메뉴', '맛보기이벤트'],
|
|
||||||
images: ['/images/pizza-tteokbokki.jpg'],
|
|
||||||
status: 'DRAFT',
|
|
||||||
views: 0,
|
|
||||||
likes: 0,
|
|
||||||
comments: 0,
|
|
||||||
createdAt: '2023-12-30T16:45:00',
|
|
||||||
publishedAt: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 17,
|
|
||||||
title: '🎄 크리스마스 특별 메뉴',
|
|
||||||
type: 'poster',
|
|
||||||
platform: 'POSTER',
|
|
||||||
content: '크리스마스를 맛있게! 특별한 날을 위한 특별한 메뉴들을 준비했습니다.',
|
|
||||||
hashtags: ['크리스마스', '특별메뉴', '연말', '특별한날'],
|
|
||||||
images: ['/images/christmas-menu.jpg'],
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
views: 1678,
|
|
||||||
likes: 93,
|
|
||||||
comments: 7,
|
|
||||||
createdAt: '2023-12-20T10:15:00',
|
|
||||||
publishedAt: '2023-12-20T12:00:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 18,
|
|
||||||
title: '🔥 매운맛 도전 이벤트',
|
|
||||||
type: 'sns',
|
|
||||||
platform: 'INSTAGRAM',
|
|
||||||
content: '매운맛 도전자 모집! 🔥🔥🔥\n\n👹 지옥 떡볶이 도전!\n🌶️ 매운맛 단계별 도전\n🏅 완주시 상품 증정\n📹 도전 영상 촬영 가능\n\n용기있는 분들의 도전을 기다립니다!\n(단, 매운 것 못 드시는 분은 조심하세요 😅)',
|
|
||||||
hashtags: ['매운맛도전', '지옥떡볶이', '도전이벤트', '상품증정', '용기있는자'],
|
|
||||||
images: ['/images/spicy-challenge.jpg'],
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
views: 2567,
|
|
||||||
likes: 234,
|
|
||||||
comments: 45,
|
|
||||||
createdAt: '2023-12-15T14:20:00',
|
|
||||||
publishedAt: '2023-12-15T15:00:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 19,
|
|
||||||
title: '☕ 겨울 음료 메뉴 소개',
|
|
||||||
type: 'blog',
|
|
||||||
platform: 'NAVER_BLOG',
|
|
||||||
content: '추운 겨울, 따뜻한 음료 한 잔은 어떠세요? ☕\n\n우리 매장의 겨울 음료 메뉴를 소개합니다:\n\n🍫 진한 핫초콜릿\n🍯 꿀유자차\n🌿 생강차\n☕ 아메리카노\n🥛 따뜻한 우유\n\n모든 음료는 직접 우린 차와 신선한 재료로 만듭니다!\n겨울 추위를 이겨내는 따뜻함을 선사해드릴게요.',
|
|
||||||
hashtags: ['겨울음료', '따뜻한차', '핫초콜릿', '꿀유자차', '생강차'],
|
|
||||||
images: ['/images/winter-drinks.jpg'],
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
views: 1234,
|
|
||||||
likes: 67,
|
|
||||||
comments: 8,
|
|
||||||
createdAt: '2023-12-10T13:30:00',
|
|
||||||
publishedAt: '2023-12-10T15:00:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 20,
|
|
||||||
title: '🍱 도시락 배달 서비스 시작',
|
|
||||||
type: 'poster',
|
|
||||||
platform: 'POSTER',
|
|
||||||
content: '이제 도시락 배달도 가능합니다! 회사나 집에서 편리하게 주문하세요.',
|
|
||||||
hashtags: ['도시락배달', '배달서비스', '회사도시락', '편리한주문'],
|
|
||||||
images: ['/images/lunchbox-delivery.jpg'],
|
|
||||||
status: 'DRAFT',
|
|
||||||
views: 0,
|
|
||||||
likes: 0,
|
|
||||||
comments: 0,
|
|
||||||
createdAt: '2023-12-05T11:45:00',
|
|
||||||
publishedAt: null,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// Getters
|
// generateContent를 실제 API 호출로 수정
|
||||||
const contentCount = computed(() => contents.value.length)
|
const generateContent = async (type, formData) => {
|
||||||
|
|
||||||
const filteredContents = computed(() => {
|
|
||||||
let filtered = contents.value
|
|
||||||
|
|
||||||
if (filters.value.type !== 'all') {
|
|
||||||
filtered = filtered.filter((content) => content.type === filters.value.type)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters.value.platform !== 'all') {
|
|
||||||
filtered = filtered.filter((content) => content.platform === filters.value.platform)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters.value.status !== 'all') {
|
|
||||||
filtered = filtered.filter((content) => content.status === filters.value.status)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filtered.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
|
||||||
})
|
|
||||||
|
|
||||||
const publishedContents = computed(() =>
|
|
||||||
contents.value.filter((content) => content.status === 'PUBLISHED')
|
|
||||||
)
|
|
||||||
|
|
||||||
const draftContents = computed(() =>
|
|
||||||
contents.value.filter((content) => content.status === 'DRAFT')
|
|
||||||
)
|
|
||||||
|
|
||||||
const scheduledContents = computed(() =>
|
|
||||||
contents.value.filter((content) => content.status === 'SCHEDULED')
|
|
||||||
)
|
|
||||||
|
|
||||||
const recentContents = computed(() => {
|
|
||||||
return contents.value.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)).slice(0, 5)
|
|
||||||
})
|
|
||||||
|
|
||||||
const totalViews = computed(() =>
|
|
||||||
contents.value.reduce((sum, content) => sum + (content.views || 0), 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
const totalLikes = computed(() =>
|
|
||||||
contents.value.reduce((sum, content) => sum + (content.likes || 0), 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
const fetchContents = async () => {
|
|
||||||
try {
|
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
|
||||||
// API 호출 시뮬레이션
|
try {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
let result
|
||||||
|
if (type === 'sns') {
|
||||||
|
result = await contentService.generateSnsContent(formData)
|
||||||
|
} else if (type === 'poster') {
|
||||||
|
result = await contentService.generatePoster(formData)
|
||||||
|
}
|
||||||
|
|
||||||
// 더미 데이터 로드
|
if (result.success) {
|
||||||
contents.value = [...sampleContents]
|
generatedContent.value = result.data
|
||||||
|
return { success: true, data: result.data }
|
||||||
console.log('콘텐츠 목록 로드 완료:', contents.value.length)
|
} else {
|
||||||
return contents.value
|
return { success: false, error: result.message }
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('콘텐츠 로드 실패:', error)
|
return { success: false, error: '네트워크 오류가 발생했습니다.' }
|
||||||
throw error
|
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadContents = async () => {
|
// saveContent를 실제 API 호출로 수정
|
||||||
return await fetchContents()
|
const saveContent = async (type, contentData) => {
|
||||||
}
|
isLoading.value = true
|
||||||
|
|
||||||
const getContentById = (id) => {
|
|
||||||
return contents.value.find((content) => content.id === parseInt(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
const addContent = (content) => {
|
|
||||||
const newContent = {
|
|
||||||
...content,
|
|
||||||
id: Date.now(), // 임시 ID
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
views: 0,
|
|
||||||
likes: 0,
|
|
||||||
comments: 0,
|
|
||||||
}
|
|
||||||
contents.value.unshift(newContent)
|
|
||||||
return newContent
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateContent = async (contentId, updatedData) => {
|
|
||||||
try {
|
try {
|
||||||
const index = contents.value.findIndex((content) => content.id === contentId)
|
let result
|
||||||
if (index !== -1) {
|
if (type === 'sns') {
|
||||||
contents.value[index] = {
|
result = await contentService.saveSnsContent(contentData)
|
||||||
...contents.value[index],
|
} else if (type === 'poster') {
|
||||||
...updatedData,
|
result = await contentService.savePoster(contentData)
|
||||||
updatedAt: new Date().toISOString(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// API 호출 시뮬레이션
|
if (result.success) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
// 콘텐츠 목록 새로고침
|
||||||
|
await fetchContentList()
|
||||||
return contents.value[index]
|
return { success: true, message: '콘텐츠가 저장되었습니다.' }
|
||||||
|
} else {
|
||||||
|
return { success: false, error: result.message }
|
||||||
}
|
}
|
||||||
throw new Error('콘텐츠를 찾을 수 없습니다.')
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('콘텐츠 수정 실패:', error)
|
return { success: false, error: '네트워크 오류가 발생했습니다.' }
|
||||||
throw error
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchContentList를 실제 API 호출로 수정
|
||||||
|
const fetchContentList = async (filters = {}) => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await contentService.getContents(filters)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
contentList.value = result.data
|
||||||
|
return { success: true }
|
||||||
|
} else {
|
||||||
|
return { success: false, error: result.message }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: '네트워크 오류가 발생했습니다.' }
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchOngoingContents를 실제 API 호출로 수정
|
||||||
|
const fetchOngoingContents = async (period = 'month') => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await contentService.getOngoingContents(period)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
ongoingContents.value = result.data
|
||||||
|
return { success: true }
|
||||||
|
} else {
|
||||||
|
return { success: false, error: result.message }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: '네트워크 오류가 발생했습니다.' }
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 콘텐츠 수정/삭제 메서드 추가
|
||||||
|
const updateContent = async (contentId, updateData) => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await contentService.updateContent(contentId, updateData)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
await fetchContentList()
|
||||||
|
return { success: true, message: '콘텐츠가 수정되었습니다.' }
|
||||||
|
} else {
|
||||||
|
return { success: false, error: result.message }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: '네트워크 오류가 발생했습니다.' }
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteContent = async (contentId) => {
|
const deleteContent = async (contentId) => {
|
||||||
try {
|
|
||||||
const index = contents.value.findIndex((content) => content.id === contentId)
|
|
||||||
if (index !== -1) {
|
|
||||||
const deletedContent = contents.value[index]
|
|
||||||
contents.value.splice(index, 1)
|
|
||||||
|
|
||||||
// API 호출 시뮬레이션
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 300))
|
|
||||||
|
|
||||||
return deletedContent
|
|
||||||
}
|
|
||||||
throw new Error('콘텐츠를 찾을 수 없습니다.')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('콘텐츠 삭제 실패:', error)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteMultipleContents = async (contentIds) => {
|
|
||||||
try {
|
|
||||||
// API 호출 시뮬레이션
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
||||||
|
|
||||||
const deletedContents = []
|
|
||||||
contentIds.forEach(id => {
|
|
||||||
const index = contents.value.findIndex((content) => content.id === id)
|
|
||||||
if (index !== -1) {
|
|
||||||
deletedContents.push(contents.value[index])
|
|
||||||
contents.value.splice(index, 1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return deletedContents
|
|
||||||
} catch (error) {
|
|
||||||
console.error('콘텐츠 일괄 삭제 실패:', error)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const publishContent = async (contentId) => {
|
|
||||||
try {
|
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
|
||||||
// API 호출 시뮬레이션
|
try {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
const result = await contentService.deleteContent(contentId)
|
||||||
|
|
||||||
const content = updateContent(contentId, {
|
if (result.success) {
|
||||||
status: 'PUBLISHED',
|
await fetchContentList()
|
||||||
publishedAt: new Date().toISOString(),
|
return { success: true, message: '콘텐츠가 삭제되었습니다.' }
|
||||||
})
|
} else {
|
||||||
|
return { success: false, error: result.message }
|
||||||
console.log('콘텐츠 발행 완료:', content?.title)
|
}
|
||||||
return content
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('콘텐츠 발행 실패:', error)
|
return { success: false, error: '네트워크 오류가 발생했습니다.' }
|
||||||
throw error
|
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const scheduleContent = async (contentId, scheduledTime) => {
|
|
||||||
try {
|
|
||||||
isLoading.value = true
|
|
||||||
|
|
||||||
// API 호출 시뮬레이션
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
||||||
|
|
||||||
const content = updateContent(contentId, {
|
|
||||||
status: 'SCHEDULED',
|
|
||||||
scheduledAt: new Date(scheduledTime).toISOString(),
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log('콘텐츠 예약 완료:', content?.title)
|
|
||||||
return content
|
|
||||||
} catch (error) {
|
|
||||||
console.error('콘텐츠 예약 실패:', error)
|
|
||||||
throw error
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateContent = async (options) => {
|
|
||||||
try {
|
|
||||||
isGenerating.value = true
|
|
||||||
|
|
||||||
// AI 콘텐츠 생성 시뮬레이션
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 3000))
|
|
||||||
|
|
||||||
const generatedContent = {
|
|
||||||
title: `AI 생성 콘텐츠 - ${options.type}`,
|
|
||||||
type: options.type,
|
|
||||||
platform: options.platform,
|
|
||||||
content: `AI가 생성한 ${options.type} 콘텐츠입니다. ${options.description || ''}`,
|
|
||||||
hashtags: options.hashtags || [],
|
|
||||||
images: options.images || [],
|
|
||||||
status: 'DRAFT',
|
|
||||||
}
|
|
||||||
|
|
||||||
const newContent = addContent(generatedContent)
|
|
||||||
console.log('AI 콘텐츠 생성 완료:', newContent.title)
|
|
||||||
|
|
||||||
return newContent
|
|
||||||
} catch (error) {
|
|
||||||
console.error('AI 콘텐츠 생성 실패:', error)
|
|
||||||
throw error
|
|
||||||
} finally {
|
|
||||||
isGenerating.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const setFilters = (newFilters) => {
|
|
||||||
filters.value = { ...filters.value, ...newFilters }
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetFilters = () => {
|
|
||||||
filters.value = {
|
|
||||||
type: 'all',
|
|
||||||
platform: 'all',
|
|
||||||
status: 'all',
|
|
||||||
dateRange: 'all',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const setCurrentContent = (content) => {
|
|
||||||
currentContent.value = content
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearCurrentContent = () => {
|
|
||||||
currentContent.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// State
|
// 상태
|
||||||
contents,
|
contentList,
|
||||||
currentContent,
|
ongoingContents,
|
||||||
|
selectedContent,
|
||||||
|
generatedContent,
|
||||||
isLoading,
|
isLoading,
|
||||||
isGenerating,
|
|
||||||
filters,
|
// 컴퓨티드
|
||||||
// Getters
|
|
||||||
contentCount,
|
contentCount,
|
||||||
filteredContents,
|
ongoingContentCount,
|
||||||
publishedContents,
|
|
||||||
draftContents,
|
// 메서드
|
||||||
scheduledContents,
|
|
||||||
recentContents,
|
|
||||||
totalViews,
|
|
||||||
totalLikes,
|
|
||||||
// Actions
|
|
||||||
loadContents,
|
|
||||||
fetchContents,
|
|
||||||
getContentById,
|
|
||||||
addContent,
|
|
||||||
updateContent,
|
|
||||||
deleteContent,
|
|
||||||
deleteMultipleContents,
|
|
||||||
publishContent,
|
|
||||||
scheduleContent,
|
|
||||||
generateContent,
|
generateContent,
|
||||||
setFilters,
|
saveContent,
|
||||||
resetFilters,
|
fetchContentList,
|
||||||
setCurrentContent,
|
fetchOngoingContents,
|
||||||
clearCurrentContent,
|
updateContent,
|
||||||
|
deleteContent
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
202
src/store/store.js
Normal file
202
src/store/store.js
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
//* src/store/store.js 수정 - 기존 구조 유지하고 API 연동만 추가
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import storeService from '@/services/store'
|
||||||
|
|
||||||
|
export const useStoreStore = defineStore('store', () => {
|
||||||
|
// 기존 상태들 유지
|
||||||
|
const storeInfo = ref(null)
|
||||||
|
const menus = ref([])
|
||||||
|
const salesData = ref(null)
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
// 기존 computed 속성들 유지
|
||||||
|
const hasStoreInfo = computed(() => !!storeInfo.value)
|
||||||
|
const menuCount = computed(() => menus.value?.length || 0)
|
||||||
|
|
||||||
|
// fetchStoreInfo를 실제 API 호출로 수정
|
||||||
|
const fetchStoreInfo = async () => {
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
console.log('개발 모드: 매장 정보 API 호출 건너뛰기')
|
||||||
|
return { success: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await storeService.getStore()
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
storeInfo.value = result.data
|
||||||
|
return { success: true }
|
||||||
|
} else {
|
||||||
|
console.warn('매장 정보 조회 실패:', result.message)
|
||||||
|
return { success: false, error: result.message }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('매장 정보 조회 실패:', error)
|
||||||
|
return { success: false, error: '네트워크 오류가 발생했습니다.' }
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveStoreInfo를 실제 API 호출로 수정
|
||||||
|
const saveStoreInfo = async (storeData) => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
let result
|
||||||
|
if (storeInfo.value) {
|
||||||
|
// 기존 매장 정보 수정
|
||||||
|
result = await storeService.updateStore(storeData)
|
||||||
|
} else {
|
||||||
|
// 새 매장 등록
|
||||||
|
result = await storeService.registerStore(storeData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
storeInfo.value = result.data
|
||||||
|
return { success: true, message: '매장 정보가 저장되었습니다.' }
|
||||||
|
} else {
|
||||||
|
return { success: false, error: result.message }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: '네트워크 오류가 발생했습니다.' }
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchMenus를 실제 API 호출로 수정
|
||||||
|
const fetchMenus = async () => {
|
||||||
|
if (!storeInfo.value?.storeId) {
|
||||||
|
console.warn('매장 ID가 없어 메뉴를 조회할 수 없습니다.')
|
||||||
|
return { success: false, error: '매장 정보가 필요합니다.' }
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await storeService.getMenus(storeInfo.value.storeId)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
menus.value = result.data
|
||||||
|
return { success: true }
|
||||||
|
} else {
|
||||||
|
return { success: false, error: result.message }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: '네트워크 오류가 발생했습니다.' }
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 메뉴 관련 메서드들 API 연동 추가
|
||||||
|
const saveMenu = async (menuData) => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await storeService.registerMenu(menuData)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// 메뉴 목록 새로고침
|
||||||
|
await fetchMenus()
|
||||||
|
return { success: true, message: '메뉴가 등록되었습니다.' }
|
||||||
|
} else {
|
||||||
|
return { success: false, error: result.message }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: '네트워크 오류가 발생했습니다.' }
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateMenu = async (menuId, menuData) => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await storeService.updateMenu(menuId, menuData)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// 메뉴 목록 새로고침
|
||||||
|
await fetchMenus()
|
||||||
|
return { success: true, message: '메뉴가 수정되었습니다.' }
|
||||||
|
} else {
|
||||||
|
return { success: false, error: result.message }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: '네트워크 오류가 발생했습니다.' }
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteMenu = async (menuId) => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await storeService.deleteMenu(menuId)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// 메뉴 목록 새로고침
|
||||||
|
await fetchMenus()
|
||||||
|
return { success: true, message: '메뉴가 삭제되었습니다.' }
|
||||||
|
} else {
|
||||||
|
return { success: false, error: result.message }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: '네트워크 오류가 발생했습니다.' }
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 매출 정보 조회 추가
|
||||||
|
const fetchSalesData = async () => {
|
||||||
|
if (!storeInfo.value?.storeId) {
|
||||||
|
return { success: false, error: '매장 정보가 필요합니다.' }
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await storeService.getSales(storeInfo.value.storeId)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
salesData.value = result.data
|
||||||
|
return { success: true }
|
||||||
|
} else {
|
||||||
|
return { success: false, error: result.message }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: '네트워크 오류가 발생했습니다.' }
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 상태
|
||||||
|
storeInfo,
|
||||||
|
menus,
|
||||||
|
salesData,
|
||||||
|
isLoading,
|
||||||
|
|
||||||
|
// 컴퓨티드
|
||||||
|
hasStoreInfo,
|
||||||
|
menuCount,
|
||||||
|
|
||||||
|
// 메서드
|
||||||
|
fetchStoreInfo,
|
||||||
|
saveStoreInfo,
|
||||||
|
fetchMenus,
|
||||||
|
saveMenu,
|
||||||
|
updateMenu,
|
||||||
|
deleteMenu,
|
||||||
|
fetchSalesData
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
@ -820,24 +820,22 @@ const confirmLogout = () => {
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
console.log('DashboardView 마운트됨')
|
console.log('DashboardView 마운트됨')
|
||||||
|
|
||||||
|
// 실제 API 호출 추가
|
||||||
try {
|
try {
|
||||||
// 지표 애니메이션 시작
|
// 매장 정보 로드
|
||||||
setTimeout(() => {
|
if (!storeStore.hasStoreInfo) {
|
||||||
startMetricsAnimation()
|
await storeStore.fetchStoreInfo()
|
||||||
}, 500)
|
}
|
||||||
|
|
||||||
// 차트 그리기
|
// 매출 데이터 로드
|
||||||
setTimeout(() => {
|
await storeStore.fetchSalesData()
|
||||||
drawChart()
|
|
||||||
}, 1000)
|
// 진행 중인 콘텐츠 로드
|
||||||
|
await contentStore.fetchOngoingContents()
|
||||||
|
|
||||||
// AI 추천 로드
|
|
||||||
setTimeout(() => {
|
|
||||||
// 자동으로 AI 추천을 로드하지 않고 사용자 액션 대기
|
|
||||||
// refreshAiRecommendation()
|
|
||||||
}, 1500)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('대시보드 초기화 실패:', error)
|
console.warn('대시보드 데이터 로드 실패 (개발 중이므로 무시):', error)
|
||||||
|
// 개발 중에는 에러를 무시하고 계속 진행
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -549,14 +549,19 @@ const handleSignup = async () => {
|
|||||||
|
|
||||||
console.log('회원가입 응답:', response.data)
|
console.log('회원가입 응답:', response.data)
|
||||||
|
|
||||||
if (response.data.success) {
|
// 백엔드 응답 구조에 맞게 수정
|
||||||
|
if (response.data.status === 200 || response.data.message?.includes('완료')) {
|
||||||
signupSuccess.value = '회원가입이 완료되었습니다! 로그인해주세요'
|
signupSuccess.value = '회원가입이 완료되었습니다! 로그인해주세요'
|
||||||
|
|
||||||
// 3초 후 다이얼로그 닫기
|
// 즉시 성공 메시지 표시
|
||||||
|
appStore.showSnackbar('회원가입이 완료되었습니다', 'success')
|
||||||
|
|
||||||
|
// 1초 후 다이얼로그 닫기 (더 빠르게)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
closeSignupDialog()
|
closeSignupDialog()
|
||||||
appStore.showSnackbar('회원가입이 완료되었습니다', 'success')
|
}, 1000)
|
||||||
}, 3000)
|
} else {
|
||||||
|
signupError.value = response.data.message || '회원가입에 실패했습니다'
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('회원가입 실패:', error)
|
console.error('회원가입 실패:', error)
|
||||||
|
|||||||
@ -1301,62 +1301,22 @@ const handleImageUpload = (event) => {
|
|||||||
* 매장 정보 저장
|
* 매장 정보 저장
|
||||||
*/
|
*/
|
||||||
const saveStoreInfo = async () => {
|
const saveStoreInfo = async () => {
|
||||||
console.log('매장 정보 저장 시작')
|
if (!storeForm.value?.validate()) return
|
||||||
|
|
||||||
if (!formValid.value) {
|
console.log('매장 정보 저장:', storeFormData.value)
|
||||||
showSnackbar('입력 정보를 확인해주세요', 'error')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
saving.value = true
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 개발 모드에서는 시뮬레이션으로 처리
|
const result = await storeStore.saveStoreInfo(storeFormData.value)
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
console.log('개발 모드: 매장 정보 저장 시뮬레이션')
|
|
||||||
|
|
||||||
// 1초 대기 (로딩 상태 표시)
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
||||||
|
|
||||||
// 폼 데이터를 스토어에 직접 저장
|
|
||||||
const storeData = { ...formData.value, id: Date.now() }
|
|
||||||
storeStore.storeInfo = storeData
|
|
||||||
|
|
||||||
showSnackbar(
|
|
||||||
editMode.value ? '매장 정보가 수정되었습니다' : '매장 정보가 등록되었습니다',
|
|
||||||
'success'
|
|
||||||
)
|
|
||||||
closeDialog()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 프로덕션에서는 실제 API 호출
|
|
||||||
let result
|
|
||||||
if (editMode.value) {
|
|
||||||
// 매장 정보 수정
|
|
||||||
result = await storeStore.updateStore(storeInfo.value.id, formData.value)
|
|
||||||
} else {
|
|
||||||
// 매장 정보 등록
|
|
||||||
result = await storeStore.registerStore(formData.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
showSnackbar(
|
appStore.showSnackbar(result.message || '매장 정보가 저장되었습니다', 'success')
|
||||||
editMode.value ? '매장 정보가 수정되었습니다' : '매장 정보가 등록되었습니다',
|
showStoreForm.value = false
|
||||||
'success'
|
|
||||||
)
|
|
||||||
closeDialog()
|
|
||||||
|
|
||||||
// 매장 정보 새로고침
|
|
||||||
await storeStore.fetchStoreInfo()
|
|
||||||
} else {
|
} else {
|
||||||
showSnackbar(result.message || '저장 중 오류가 발생했습니다', 'error')
|
appStore.showSnackbar(result.error || '저장에 실패했습니다', 'error')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('매장 정보 저장 중 오류:', error)
|
console.error('매장 정보 저장 실패:', error)
|
||||||
showSnackbar('저장 중 오류가 발생했습니다', 'error')
|
appStore.showSnackbar('네트워크 오류가 발생했습니다', 'error')
|
||||||
} finally {
|
|
||||||
saving.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1440,63 +1400,46 @@ const handleMenuImageUpload = (event) => {
|
|||||||
* 메뉴 저장
|
* 메뉴 저장
|
||||||
*/
|
*/
|
||||||
const saveMenu = async () => {
|
const saveMenu = async () => {
|
||||||
if (!menuFormValid.value) {
|
if (!menuForm.value?.validate()) return
|
||||||
showSnackbar('입력 정보를 확인해주세요', 'error')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
savingMenu.value = true
|
console.log('메뉴 저장:', menuFormData.value)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1초 대기 (시뮬레이션)
|
let result
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
if (isMenuEdit.value && editingMenuId.value) {
|
||||||
|
result = await storeStore.updateMenu(editingMenuId.value, menuFormData.value)
|
||||||
if (editMenuMode.value) {
|
|
||||||
// 메뉴 수정
|
|
||||||
const index = menus.value.findIndex(m => m.id === menuFormData.value.id)
|
|
||||||
if (index !== -1) {
|
|
||||||
menus.value[index] = { ...menuFormData.value }
|
|
||||||
}
|
|
||||||
showSnackbar('메뉴가 수정되었습니다', 'success')
|
|
||||||
} else {
|
} else {
|
||||||
// 메뉴 등록
|
result = await storeStore.saveMenu(menuFormData.value)
|
||||||
const newMenu = {
|
|
||||||
...menuFormData.value,
|
|
||||||
id: Date.now(),
|
|
||||||
rating: 0
|
|
||||||
}
|
|
||||||
menus.value.push(newMenu)
|
|
||||||
showSnackbar('메뉴가 등록되었습니다', 'success')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
closeMenuDialog()
|
if (result.success) {
|
||||||
|
appStore.showSnackbar(result.message || '메뉴가 저장되었습니다', 'success')
|
||||||
|
showMenuForm.value = false
|
||||||
|
resetMenuForm()
|
||||||
|
} else {
|
||||||
|
appStore.showSnackbar(result.error || '저장에 실패했습니다', 'error')
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('메뉴 저장 중 오류:', error)
|
console.error('메뉴 저장 실패:', error)
|
||||||
showSnackbar('저장 중 오류가 발생했습니다', 'error')
|
appStore.showSnackbar('네트워크 오류가 발생했습니다', 'error')
|
||||||
} finally {
|
|
||||||
savingMenu.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 메뉴 삭제
|
* 메뉴 삭제
|
||||||
*/
|
*/
|
||||||
const deleteMenu = async () => {
|
const deleteMenu = async (menuId) => {
|
||||||
deletingMenu.value = true
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1초 대기 (시뮬레이션)
|
const result = await storeStore.deleteMenu(menuId)
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
||||||
|
|
||||||
menus.value = menus.value.filter(m => m.id !== deleteMenuTarget.value.id)
|
if (result.success) {
|
||||||
showSnackbar('메뉴가 삭제되었습니다', 'success')
|
appStore.showSnackbar(result.message || '메뉴가 삭제되었습니다', 'success')
|
||||||
showDeleteMenuDialog.value = false
|
} else {
|
||||||
deleteMenuTarget.value = null
|
appStore.showSnackbar(result.error || '삭제에 실패했습니다', 'error')
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('메뉴 삭제 중 오류:', error)
|
console.error('메뉴 삭제 실패:', error)
|
||||||
showSnackbar('삭제 중 오류가 발생했습니다', 'error')
|
appStore.showSnackbar('네트워크 오류가 발생했습니다', 'error')
|
||||||
} finally {
|
|
||||||
deletingMenu.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1572,21 +1515,17 @@ const editFromDetail = () => {
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
console.log('StoreManagementView 마운트됨')
|
console.log('StoreManagementView 마운트됨')
|
||||||
|
|
||||||
// 개발 모드에서는 API 호출을 건너뛰고 바로 UI 표시
|
try {
|
||||||
if (import.meta.env.DEV) {
|
// 매장 정보 로드
|
||||||
console.log('개발 모드: API 호출 건너뛰기')
|
if (!storeStore.hasStoreInfo) {
|
||||||
// 개발 중에는 즉시 UI 표시
|
await storeStore.fetchStoreInfo()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 프로덕션에서만 실제 API 호출
|
// 메뉴 목록 로드
|
||||||
if (!storeStore.hasStoreInfo) {
|
await storeStore.fetchMenus()
|
||||||
try {
|
|
||||||
await storeStore.fetchStoreInfo()
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('매장 정보 로드 실패 (개발 중이므로 무시):', error)
|
console.warn('매장 관리 데이터 로드 실패 (개발 중이므로 무시):', error)
|
||||||
// 개발 중에는 에러를 무시하고 계속 진행
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user