source edit

This commit is contained in:
unknown 2025-06-16 11:26:01 +09:00
parent dc87099df7
commit de983127a3
10 changed files with 635 additions and 937 deletions

View File

@ -1,9 +1,9 @@
//* src/services/content.js
//* src/services/content.js - 기존 파일 수정 (API 설계서 기준)
import { contentApi, handleApiError, formatSuccessResponse } from './api.js'
/**
* 마케팅 콘텐츠 관련 API 서비스
* 유저스토리: CON-005, CON-015, CON-020, CON-025, CON-030
* API 설계서 기준으로 수정됨
*/
class ContentService {
/**
@ -14,16 +14,22 @@ class ContentService {
async generateSnsContent(contentData) {
try {
const response = await contentApi.post('/sns/generate', {
targetType: contentData.targetType, // 메뉴, 매장, 이벤트
platform: contentData.platform, // 인스타그램, 네이버블로그
images: contentData.images,
storeId: contentData.storeId,
platform: contentData.platform,
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,
startDate: contentData.startDate,
endDate: contentData.endDate,
toneAndManner: contentData.toneAndManner, // 친근함, 전문적, 유머러스, 고급스러운
promotionType: contentData.promotionType, // 할인정보, 이벤트정보, 신메뉴알림, 없음
emotionIntensity: contentData.emotionIntensity, // 차분함, 보통, 열정적, 과장된
requirements: contentData.requirements || '',
eventDate: contentData.eventDate,
hashtagStyle: contentData.hashtagStyle,
hashtagCount: contentData.hashtagCount || 10,
includeCallToAction: contentData.includeCallToAction || false,
includeEmoji: contentData.includeEmoji || true,
contentLength: contentData.contentLength || '보통'
})
return formatSuccessResponse(response.data.data, 'SNS 게시물이 생성되었습니다.')
@ -33,7 +39,7 @@ class ContentService {
}
/**
* SNS 게시물 저장
* SNS 게시물 저장 (CON-010: SNS 게시물 저장)
* @param {Object} saveData - 저장할 SNS 콘텐츠 정보
* @returns {Promise<Object>} 저장 결과
*/
@ -43,10 +49,14 @@ class ContentService {
title: saveData.title,
content: saveData.content,
hashtags: saveData.hashtags,
images: saveData.images,
platform: saveData.platform,
status: saveData.status || 'DRAFT',
publishSchedule: saveData.publishSchedule,
category: saveData.category,
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 게시물이 저장되었습니다.')
@ -63,15 +73,19 @@ class ContentService {
async generatePoster(posterData) {
try {
const response = await contentApi.post('/poster/generate', {
storeId: posterData.storeId,
title: posterData.title,
targetType: posterData.targetType,
images: posterData.images,
eventName: posterData.eventName,
startDate: posterData.startDate,
endDate: posterData.endDate,
photoStyle: posterData.photoStyle, // 모던, 클래식, 감성적
promotionType: posterData.promotionType,
emotionIntensity: posterData.emotionIntensity,
sizes: posterData.sizes || ['1:1', '9:16', '16:9'], // SNS용, 스토리용, 블로그용
eventDate: posterData.eventDate,
discountInfo: posterData.discountInfo,
designStyle: posterData.designStyle,
colorScheme: posterData.colorScheme,
includeQrCode: posterData.includeQrCode || false,
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, '홍보 포스터가 생성되었습니다.')
@ -81,7 +95,7 @@ class ContentService {
}
/**
* 홍보 포스터 저장
* 홍보 포스터 저장 (CON-016: 홍보 포스터 저장)
* @param {Object} saveData - 저장할 포스터 정보
* @returns {Promise<Object>} 저장 결과
*/
@ -93,7 +107,7 @@ class ContentService {
posterSizes: saveData.posterSizes,
targetType: saveData.targetType,
eventName: saveData.eventName,
status: saveData.status || 'DRAFT',
status: saveData.status || 'DRAFT'
})
return formatSuccessResponse(response.data.data, '홍보 포스터가 저장되었습니다.')
@ -171,7 +185,7 @@ class ContentService {
hashtags: updateData.hashtags,
startDate: updateData.startDate,
endDate: updateData.endDate,
status: updateData.status,
status: updateData.status
})
return formatSuccessResponse(response.data.data, '콘텐츠가 수정되었습니다.')
@ -194,55 +208,7 @@ class ContentService {
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 default contentService
export default contentService

View 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

View 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

View File

@ -1,9 +1,9 @@
//* src/services/store.js
import { storeApi, handleApiError, formatSuccessResponse } from './api.js'
//* src/services/store.js - 기존 파일 수정 (API 설계서 기준)
import { storeApi, menuApi, salesApi, handleApiError, formatSuccessResponse } from './api.js'
/**
* 매장 관련 API 서비스
* 유저스토리: STR-005, STR-010, STR-015, STR-020, STR-025, STR-030, STR-035, STR-040
* API 설계서 기준으로 수정됨
*/
class StoreService {
/**
@ -15,16 +15,14 @@ class StoreService {
try {
const response = await storeApi.post('/register', {
storeName: storeData.storeName,
storeImage: storeData.storeImage,
businessType: storeData.businessType,
address: storeData.address,
phoneNumber: storeData.phoneNumber,
businessNumber: storeData.businessNumber,
instaAccount: storeData.instaAccount,
naverBlogAccount: storeData.naverBlogAccount,
operatingHours: storeData.operatingHours,
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, '매장이 등록되었습니다.')
@ -34,7 +32,7 @@ class StoreService {
}
/**
* 매장 정보 조회 (STR-005: 매장 정보 관리)
* 매장 정보 조회 (STR-005: 매장 조회)
* @returns {Promise<Object>} 매장 정보
*/
async getStore() {
@ -49,13 +47,22 @@ class StoreService {
/**
* 매장 정보 수정 (STR-010: 매장 수정)
* @param {number} storeId - 매장 ID
* @param {Object} storeData - 수정할 매장 정보
* @returns {Promise<Object>} 매장 수정 결과
*/
async updateStore(storeId, storeData) {
async updateStore(storeData) {
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, '매장 정보가 수정되었습니다.')
} catch (error) {
@ -64,13 +71,13 @@ class StoreService {
}
/**
* 매출 정보 조회 (STR-020: 대시보드)
* @param {string} period - 조회 기간 (today, week, month, year)
* 매출 정보 조회 (SAL-005: 매출 조회)
* @param {number} storeId - 매장 ID
* @returns {Promise<Object>} 매출 정보
*/
async getSales(period = 'today') {
async getSales(storeId) {
try {
const response = await storeApi.get(`/sales?period=${period}`)
const response = await salesApi.get(`/${storeId}`)
return formatSuccessResponse(response.data.data, '매출 정보를 조회했습니다.')
} catch (error) {
@ -79,16 +86,16 @@ class StoreService {
}
/**
* 메뉴 등록 (STR-030: 메뉴 등록)
* 메뉴 등록 (MNU-010: 메뉴 등록)
* @param {Object} menuData - 메뉴 정보
* @returns {Promise<Object>} 메뉴 등록 결과
*/
async registerMenu(menuData) {
try {
const response = await storeApi.post('/menu/register', {
const response = await menuApi.post('/register', {
menuName: menuData.menuName,
menuCategory: menuData.menuCategory,
menuImage: menuData.menuImage,
menuCategory: menuData.menuCategory || menuData.category,
menuImage: menuData.menuImage || menuData.image,
price: menuData.price,
description: menuData.description,
isPopular: menuData.isPopular || false,
@ -102,19 +109,13 @@ class StoreService {
}
/**
* 메뉴 목록 조회 (STR-025: 메뉴 조회)
* @param {Object} filters - 필터링 옵션
* 메뉴 목록 조회 (MNU-005: 메뉴 조회)
* @param {number} storeId - 매장 ID
* @returns {Promise<Object>} 메뉴 목록
*/
async getMenus(filters = {}) {
async getMenus(storeId) {
try {
const queryParams = new URLSearchParams()
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()}`)
const response = await menuApi.get(`/${storeId}`)
return formatSuccessResponse(response.data.data, '메뉴 목록을 조회했습니다.')
} catch (error) {
@ -123,14 +124,22 @@ class StoreService {
}
/**
* 메뉴 수정 (STR-035: 메뉴 수정)
* 메뉴 수정 (MNU-015: 메뉴 수정)
* @param {number} menuId - 메뉴 ID
* @param {Object} menuData - 수정할 메뉴 정보
* @returns {Promise<Object>} 메뉴 수정 결과
*/
async updateMenu(menuId, menuData) {
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, '메뉴가 수정되었습니다.')
} catch (error) {
@ -139,13 +148,13 @@ class StoreService {
}
/**
* 메뉴 삭제 (STR-040: 메뉴 삭제)
* 메뉴 삭제 (MNU-020: 메뉴 삭제)
* @param {number} menuId - 메뉴 ID
* @returns {Promise<Object>} 메뉴 삭제 결과
*/
async deleteMenu(menuId) {
try {
await storeApi.delete(`/menu/${menuId}`)
await menuApi.delete(`/${menuId}`)
return formatSuccessResponse(null, '메뉴가 삭제되었습니다.')
} catch (error) {
@ -168,21 +177,7 @@ class StoreService {
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 default storeService
export default storeService

View File

@ -1,155 +1,82 @@
//* src/store/auth.js
//* src/store/auth.js 수정 - 기존 구조 유지하고 API 연동만 추가
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { ref } from 'vue'
import authService from '@/services/auth'
export const useAuthStore = defineStore('auth', () => {
// 상태
// 기존 상태들 유지
const user = ref(null)
const token = ref(null)
const refreshToken = ref(null)
const isAuthenticated = ref(false)
const isLoading = ref(false)
// 컴퓨티드 - 더 엄격한 인증 체크
const isAuthenticated = computed(() => {
return !!(token.value && user.value)
})
// 기존 checkAuthState 메서드 유지
const checkAuthState = () => {
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) => {
console.log('=== AUTH STORE 로그인 시작 ===')
console.log('받은 자격증명:', credentials)
isLoading.value = true
try {
// 실제 API 호출 시뮬레이션
await new Promise((resolve) => setTimeout(resolve, 1000))
// 정확한 자격증명 확인 (대소문자 구분, 공백 제거)
const username = credentials.username?.trim()
const password = credentials.password?.trim()
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)
const result = await authService.login(credentials)
if (result.success) {
token.value = result.data.token
user.value = result.data.user
isAuthenticated.value = true
return { success: true }
} else {
console.log('로그인 실패 - 자격증명 불일치')
throw new Error('아이디 또는 비밀번호가 올바르지 않습니다.')
return { success: false, error: result.message }
}
} catch (error) {
console.error('로그인 실패:', error)
return { success: false, error: error.message }
return { success: false, error: '네트워크 오류가 발생했습니다.' }
} finally {
isLoading.value = false
}
}
// logout 메서드를 실제 API 호출로 수정
const logout = async () => {
isLoading.value = true
try {
// 로컬 상태 초기화
user.value = null
await authService.logout()
} catch (error) {
console.warn('로그아웃 API 호출 실패:', error)
} finally {
// 상태 초기화
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 }
user.value = null
isAuthenticated.value = false
isLoading.value = false
}
}
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
token.value = null
refreshToken.value = null
localStorage.removeItem('auth_token')
localStorage.removeItem('refresh_token')
localStorage.removeItem('user_info')
console.log('인증 데이터 초기화됨')
}
const refreshUserInfo = async () => {
if (!token.value) return
try {
// 실제 API 호출로 사용자 정보 갱신
// const response = await api.get('/auth/me')
// user.value = response.data
} catch (error) {
console.error('사용자 정보 갱신 실패:', error)
// 토큰이 유효하지 않은 경우 로그아웃
logout()
}
}
// 초기화 시 인증 상태 확인
checkAuthState()
return {
// 상태
user,
token,
isLoading,
// 컴퓨티드
isAuthenticated,
// 액션
isLoading,
login,
logout,
refreshUserInfo,
checkAuthState,
clearAuthData,
checkAuthState
}
})

View File

@ -1,601 +1,168 @@
//* src/store/content.js
//* src/store/content.js 수정 - 기존 구조 유지하고 API 연동만 추가
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import contentService from '@/services/content'
/**
* 콘텐츠 관리 스토어
* 마케팅 콘텐츠 CRUD 상태 관리
*/
export const useContentStore = defineStore('content', () => {
// State
const contents = ref([])
const currentContent = ref(null)
// 기존 상태들 유지
const contentList = ref([])
const ongoingContents = ref([])
const selectedContent = ref(null)
const generatedContent = ref(null)
const isLoading = ref(false)
const isGenerating = ref(false)
const filters = ref({
type: 'all',
platform: 'all',
status: 'all',
dateRange: 'all',
})
// 풍부한 더미 데이터
const sampleContents = [
{
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,
}
]
// 기존 computed 속성들 유지
const contentCount = computed(() => contentList.value.length)
const ongoingContentCount = computed(() => ongoingContents.value.length)
// Getters
const contentCount = computed(() => contents.value.length)
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 () => {
// generateContent를 실제 API 호출로 수정
const generateContent = async (type, formData) => {
isLoading.value = true
try {
isLoading.value = true
// API 호출 시뮬레이션
await new Promise((resolve) => setTimeout(resolve, 1000))
// 더미 데이터 로드
contents.value = [...sampleContents]
console.log('콘텐츠 목록 로드 완료:', contents.value.length)
return contents.value
let result
if (type === 'sns') {
result = await contentService.generateSnsContent(formData)
} else if (type === 'poster') {
result = await contentService.generatePoster(formData)
}
if (result.success) {
generatedContent.value = result.data
return { success: true, data: result.data }
} else {
return { success: false, error: result.message }
}
} catch (error) {
console.error('콘텐츠 로드 실패:', error)
throw error
return { success: false, error: '네트워크 오류가 발생했습니다.' }
} finally {
isLoading.value = false
}
}
const loadContents = async () => {
return await fetchContents()
}
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) => {
// saveContent를 실제 API 호출로 수정
const saveContent = async (type, contentData) => {
isLoading.value = true
try {
const index = contents.value.findIndex((content) => content.id === contentId)
if (index !== -1) {
contents.value[index] = {
...contents.value[index],
...updatedData,
updatedAt: new Date().toISOString(),
}
// API 호출 시뮬레이션
await new Promise((resolve) => setTimeout(resolve, 500))
return contents.value[index]
let result
if (type === 'sns') {
result = await contentService.saveSnsContent(contentData)
} else if (type === 'poster') {
result = await contentService.savePoster(contentData)
}
if (result.success) {
// 콘텐츠 목록 새로고침
await fetchContentList()
return { success: true, message: '콘텐츠가 저장되었습니다.' }
} else {
return { success: false, error: result.message }
}
throw new Error('콘텐츠를 찾을 수 없습니다.')
} catch (error) {
console.error('콘텐츠 수정 실패:', error)
throw error
return { success: false, 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) => {
isLoading.value = true
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
const result = await contentService.deleteContent(contentId)
if (result.success) {
await fetchContentList()
return { success: true, message: '콘텐츠가 삭제되었습니다.' }
} else {
return { success: false, error: result.message }
}
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
// API 호출 시뮬레이션
await new Promise((resolve) => setTimeout(resolve, 1000))
const content = updateContent(contentId, {
status: 'PUBLISHED',
publishedAt: new Date().toISOString(),
})
console.log('콘텐츠 발행 완료:', content?.title)
return content
} catch (error) {
console.error('콘텐츠 발행 실패:', error)
throw error
return { success: false, error: '네트워크 오류가 발생했습니다.' }
} finally {
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 {
// State
contents,
currentContent,
// 상태
contentList,
ongoingContents,
selectedContent,
generatedContent,
isLoading,
isGenerating,
filters,
// Getters
// 컴퓨티드
contentCount,
filteredContents,
publishedContents,
draftContents,
scheduledContents,
recentContents,
totalViews,
totalLikes,
// Actions
loadContents,
fetchContents,
getContentById,
addContent,
updateContent,
deleteContent,
deleteMultipleContents,
publishContent,
scheduleContent,
ongoingContentCount,
// 메서드
generateContent,
setFilters,
resetFilters,
setCurrentContent,
clearCurrentContent,
saveContent,
fetchContentList,
fetchOngoingContents,
updateContent,
deleteContent
}
})

202
src/store/store.js Normal file
View 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
}
})

View File

@ -819,25 +819,23 @@ const confirmLogout = () => {
//
onMounted(async () => {
console.log('DashboardView 마운트됨')
// API
try {
//
setTimeout(() => {
startMetricsAnimation()
}, 500)
//
setTimeout(() => {
drawChart()
}, 1000)
// AI
setTimeout(() => {
// AI
// refreshAiRecommendation()
}, 1500)
//
if (!storeStore.hasStoreInfo) {
await storeStore.fetchStoreInfo()
}
//
await storeStore.fetchSalesData()
//
await contentStore.fetchOngoingContents()
} catch (error) {
console.error('대시보드 초기화 실패:', error)
console.warn('대시보드 데이터 로드 실패 (개발 중이므로 무시):', error)
//
}
})

View File

@ -549,14 +549,19 @@ const handleSignup = async () => {
console.log('회원가입 응답:', response.data)
if (response.data.success) {
//
if (response.data.status === 200 || response.data.message?.includes('완료')) {
signupSuccess.value = '회원가입이 완료되었습니다! 로그인해주세요'
// 3
//
appStore.showSnackbar('회원가입이 완료되었습니다', 'success')
// 1 ( )
setTimeout(() => {
closeSignupDialog()
appStore.showSnackbar('회원가입이 완료되었습니다', 'success')
}, 3000)
}, 1000)
} else {
signupError.value = response.data.message || '회원가입에 실패했습니다'
}
} catch (error) {
console.error('회원가입 실패:', error)

View File

@ -1301,62 +1301,22 @@ const handleImageUpload = (event) => {
* 매장 정보 저장
*/
const saveStoreInfo = async () => {
console.log('매장 정보 저장 시작')
if (!storeForm.value?.validate()) return
console.log('매장 정보 저장:', storeFormData.value)
if (!formValid.value) {
showSnackbar('입력 정보를 확인해주세요', 'error')
return
}
saving.value = true
try {
//
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)
}
const result = await storeStore.saveStoreInfo(storeFormData.value)
if (result.success) {
showSnackbar(
editMode.value ? '매장 정보가 수정되었습니다' : '매장 정보가 등록되었습니다',
'success'
)
closeDialog()
//
await storeStore.fetchStoreInfo()
appStore.showSnackbar(result.message || '매장 정보가 저장되었습니다', 'success')
showStoreForm.value = false
} else {
showSnackbar(result.message || '저장 중 오류가 발생했습니다', 'error')
appStore.showSnackbar(result.error || '저장에 실패했습니다', 'error')
}
} catch (error) {
console.error('매장 정보 저장 중 오류:', error)
showSnackbar('저장 중 오류가 발생했습니다', 'error')
} finally {
saving.value = false
console.error('매장 정보 저장 실패:', error)
appStore.showSnackbar('네트워크 오류가 발생했습니다', 'error')
}
}
@ -1440,63 +1400,46 @@ const handleMenuImageUpload = (event) => {
* 메뉴 저장
*/
const saveMenu = async () => {
if (!menuFormValid.value) {
showSnackbar('입력 정보를 확인해주세요', 'error')
return
}
savingMenu.value = true
if (!menuForm.value?.validate()) return
console.log('메뉴 저장:', menuFormData.value)
try {
// 1 ()
await new Promise(resolve => setTimeout(resolve, 1000))
if (editMenuMode.value) {
//
const index = menus.value.findIndex(m => m.id === menuFormData.value.id)
if (index !== -1) {
menus.value[index] = { ...menuFormData.value }
}
showSnackbar('메뉴가 수정되었습니다', 'success')
let result
if (isMenuEdit.value && editingMenuId.value) {
result = await storeStore.updateMenu(editingMenuId.value, menuFormData.value)
} else {
//
const newMenu = {
...menuFormData.value,
id: Date.now(),
rating: 0
}
menus.value.push(newMenu)
showSnackbar('메뉴가 등록되었습니다', 'success')
result = await storeStore.saveMenu(menuFormData.value)
}
if (result.success) {
appStore.showSnackbar(result.message || '메뉴가 저장되었습니다', 'success')
showMenuForm.value = false
resetMenuForm()
} else {
appStore.showSnackbar(result.error || '저장에 실패했습니다', 'error')
}
closeMenuDialog()
} catch (error) {
console.error('메뉴 저장 중 오류:', error)
showSnackbar('저장 중 오류가 발생했습니다', 'error')
} finally {
savingMenu.value = false
console.error('메뉴 저장 실패:', error)
appStore.showSnackbar('네트워크 오류가 발생했습니다', 'error')
}
}
/**
* 메뉴 삭제
*/
const deleteMenu = async () => {
deletingMenu.value = true
const deleteMenu = async (menuId) => {
try {
// 1 ()
await new Promise(resolve => setTimeout(resolve, 1000))
menus.value = menus.value.filter(m => m.id !== deleteMenuTarget.value.id)
showSnackbar('메뉴가 삭제되었습니다', 'success')
showDeleteMenuDialog.value = false
deleteMenuTarget.value = null
const result = await storeStore.deleteMenu(menuId)
if (result.success) {
appStore.showSnackbar(result.message || '메뉴가 삭제되었습니다', 'success')
} else {
appStore.showSnackbar(result.error || '삭제에 실패했습니다', 'error')
}
} catch (error) {
console.error('메뉴 삭제 중 오류:', error)
showSnackbar('삭제 중 오류가 발생했습니다', 'error')
} finally {
deletingMenu.value = false
console.error('메뉴 삭제 실패:', error)
appStore.showSnackbar('네트워크 오류가 발생했습니다', 'error')
}
}
@ -1572,21 +1515,17 @@ const editFromDetail = () => {
onMounted(async () => {
console.log('StoreManagementView 마운트됨')
// API UI
if (import.meta.env.DEV) {
console.log('개발 모드: API 호출 건너뛰기')
// UI
return
}
// API
if (!storeStore.hasStoreInfo) {
try {
try {
//
if (!storeStore.hasStoreInfo) {
await storeStore.fetchStoreInfo()
} catch (error) {
console.warn('매장 정보 로드 실패 (개발 중이므로 무시):', error)
//
}
//
await storeStore.fetchMenus()
} catch (error) {
console.warn('매장 관리 데이터 로드 실패 (개발 중이므로 무시):', error)
}
})
</script>