This commit is contained in:
SeoJHeasdw
2025-06-11 16:35:16 +09:00
parent ea04212298
commit df016023be
12 changed files with 3242 additions and 3894 deletions
+154
View File
@@ -0,0 +1,154 @@
//* src/store/app.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
/**
* 앱 전역 상태 관리 스토어
* UI 상태, 로딩, 에러, 알림 등 관리
*/
export const useAppStore = defineStore('app', () => {
// State
const isLoading = ref(false)
const loadingMessage = ref('')
const error = ref(null)
const drawer = ref(false)
const rail = ref(false)
// 스낵바 상태
const snackbar = ref({
show: false,
message: '',
color: 'info',
timeout: 4000,
location: 'bottom',
})
// 확인 다이얼로그 상태
const confirmDialog = ref({
show: false,
title: '',
message: '',
icon: '',
confirmText: '확인',
cancelText: '취소',
loading: false,
callback: null,
})
// 앱 설정
const settings = ref({
darkMode: false,
language: 'ko',
notifications: true,
})
// Getters
const isDrawerOpen = computed(() => drawer.value)
const isRailMode = computed(() => rail.value)
const currentError = computed(() => error.value)
// Actions
const setLoading = (loading, message = '') => {
isLoading.value = loading
loadingMessage.value = message
}
const setError = (errorMessage) => {
error.value = errorMessage
}
const clearError = () => {
error.value = null
}
const toggleDrawer = () => {
drawer.value = !drawer.value
}
const setDrawer = (value) => {
drawer.value = value
}
const toggleRail = () => {
rail.value = !rail.value
}
const setRail = (value) => {
rail.value = value
}
const showSnackbar = (message, color = 'info', timeout = 4000) => {
snackbar.value = {
show: true,
message,
color,
timeout,
location: 'bottom',
}
}
const hideSnackbar = () => {
snackbar.value.show = false
}
const showConfirmDialog = (options) => {
confirmDialog.value = {
show: true,
title: options.title || '확인',
message: options.message || '',
icon: options.icon || '',
confirmText: options.confirmText || '확인',
cancelText: options.cancelText || '취소',
loading: false,
callback: options.callback || null,
}
}
const hideConfirmDialog = () => {
confirmDialog.value.show = false
confirmDialog.value.callback = null
}
const updateSettings = (newSettings) => {
settings.value = { ...settings.value, ...newSettings }
// 로컬 스토리지에 저장
localStorage.setItem('app_settings', JSON.stringify(settings.value))
}
const loadSettings = () => {
const savedSettings = localStorage.getItem('app_settings')
if (savedSettings) {
settings.value = { ...settings.value, ...JSON.parse(savedSettings) }
}
}
return {
// State
isLoading,
loadingMessage,
error,
drawer,
rail,
snackbar,
confirmDialog,
settings,
// Getters
isDrawerOpen,
isRailMode,
currentError,
// Actions
setLoading,
setError,
clearError,
toggleDrawer,
setDrawer,
toggleRail,
setRail,
showSnackbar,
hideSnackbar,
showConfirmDialog,
hideConfirmDialog,
updateSettings,
loadSettings,
}
})
+117
View File
@@ -0,0 +1,117 @@
//* src/store/auth.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
/**
* 인증 관리 스토어
* 로그인/로그아웃 상태 관리
*/
export const useAuthStore = defineStore('auth', () => {
// State
const user = ref(null)
const token = ref(localStorage.getItem('auth_token') || null)
const isLoading = ref(false)
// Getters
const isAuthenticated = computed(() => !!token.value && !!user.value)
const userInfo = computed(() => user.value)
// Actions
const login = async (credentials) => {
isLoading.value = true
try {
console.log('Auth Store - 로그인 시도:', credentials)
// 임시 로그인 로직 (실제 API 연동 전)
if (credentials.userId === 'user01' && credentials.password === 'passw0rd') {
const userData = {
id: 1,
userId: credentials.userId,
name: '테스트 사용자',
email: 'test@example.com',
}
const tokenData = 'temp_jwt_token_' + Date.now()
// 상태 업데이트
token.value = tokenData
user.value = userData
// 로컬 스토리지에 저장
localStorage.setItem('auth_token', tokenData)
localStorage.setItem('user_info', JSON.stringify(userData))
console.log('Auth Store - 로그인 성공:', { token: tokenData, user: userData })
console.log('Auth Store - isAuthenticated:', isAuthenticated.value)
return { success: true, user: userData, token: tokenData }
} else {
return { success: false, message: '아이디 또는 비밀번호가 틀렸습니다' }
}
} catch (error) {
console.error('Auth Store - 로그인 에러:', error)
return { success: false, message: '로그인 중 오류가 발생했습니다' }
} finally {
isLoading.value = false
}
}
const logout = () => {
console.log('Auth Store - 로그아웃')
token.value = null
user.value = null
localStorage.removeItem('auth_token')
localStorage.removeItem('user_info')
}
const clearAuth = () => {
console.log('Auth Store - 인증 정보 클리어')
logout()
}
const checkAuth = () => {
console.log('Auth Store - 인증 상태 체크')
const storedToken = localStorage.getItem('auth_token')
const storedUser = localStorage.getItem('user_info')
if (storedToken && storedUser) {
try {
token.value = storedToken
user.value = JSON.parse(storedUser)
console.log('Auth Store - 저장된 인증 정보 복원:', { token: storedToken, user: user.value })
} catch (error) {
console.error('Auth Store - 인증 정보 복원 실패:', error)
clearAuth()
}
}
}
const refreshUserInfo = async () => {
console.log('Auth Store - 사용자 정보 갱신')
// 임시로 현재 사용자 정보 반환
if (token.value) {
return Promise.resolve(user.value)
} else {
throw new Error('토큰이 없습니다')
}
}
// 초기 인증 상태 체크
checkAuth()
return {
// State
user,
token,
isLoading,
// Getters
isAuthenticated,
userInfo,
// Actions
login,
logout,
clearAuth,
checkAuth,
refreshUserInfo,
}
})
+300
View File
@@ -0,0 +1,300 @@
//* src/store/content.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
/**
* 콘텐츠 관리 스토어
* 마케팅 콘텐츠 CRUD 및 상태 관리
*/
export const useContentStore = defineStore('content', () => {
// State
const contents = ref([])
const currentContent = ref(null)
const isLoading = ref(false)
const isGenerating = ref(false)
const filters = ref({
type: 'all',
platform: 'all',
status: 'all',
dateRange: 'all',
})
// 임시 데이터 (실제로는 API에서 가져옴)
const sampleContents = [
{
id: 1,
title: '떡볶이 신메뉴 출시 이벤트',
type: 'sns',
platform: 'instagram',
content: '🌶️ 떡볶이 신메뉴 출시! 치즈가 듬뿍 들어간 치즈떡볶이가 새로 나왔어요!',
hashtags: ['떡볶이', '신메뉴', '치즈떡볶이', '맛집'],
images: ['/images/menu-placeholder.png'],
status: 'published',
views: 1240,
likes: 85,
comments: 12,
createdAt: new Date('2024-01-15T10:30:00'),
publishedAt: new Date('2024-01-15T14:00:00'),
},
{
id: 2,
title: '매장 소개 포스터',
type: 'poster',
platform: 'blog',
content: '우리 매장을 소개하는 포스터입니다.',
hashtags: ['매장소개', '분식집', '맛집'],
images: ['/images/store-placeholder.png'],
status: 'draft',
views: 0,
likes: 0,
comments: 0,
createdAt: new Date('2024-01-14T16:20:00'),
publishedAt: null,
},
{
id: 3,
title: '할인 이벤트 안내',
type: 'sns',
platform: 'instagram',
content: '🎉 특별 할인 이벤트! 오늘 하루만 모든 메뉴 20% 할인!',
hashtags: ['할인', '이벤트', '특가', '분식'],
images: ['/images/ai-character.png'],
status: 'scheduled',
views: 0,
likes: 0,
comments: 0,
createdAt: new Date('2024-01-13T09:15:00'),
scheduledAt: new Date('2024-01-16T12:00:00'),
},
]
// 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 loadContents = async () => {
try {
isLoading.value = true
// API 호출 시뮬레이션
await new Promise((resolve) => setTimeout(resolve, 1000))
// 임시로 샘플 데이터 사용
contents.value = [...sampleContents]
console.log('콘텐츠 목록 로드 완료:', contents.value.length)
} catch (error) {
console.error('콘텐츠 로드 실패:', error)
throw error
} finally {
isLoading.value = false
}
}
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(),
views: 0,
likes: 0,
comments: 0,
}
contents.value.unshift(newContent)
return newContent
}
const updateContent = (contentId, updatedData) => {
const index = contents.value.findIndex((content) => content.id === contentId)
if (index !== -1) {
contents.value[index] = {
...contents.value[index],
...updatedData,
updatedAt: new Date(),
}
return contents.value[index]
}
return null
}
const deleteContent = (contentId) => {
const index = contents.value.findIndex((content) => content.id === contentId)
if (index !== -1) {
const deletedContent = contents.value[index]
contents.value.splice(index, 1)
return deletedContent
}
return null
}
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(),
})
console.log('콘텐츠 발행 완료:', content?.title)
return content
} catch (error) {
console.error('콘텐츠 발행 실패:', error)
throw 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),
})
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,
isLoading,
isGenerating,
filters,
// Getters
contentCount,
filteredContents,
publishedContents,
draftContents,
scheduledContents,
recentContents,
totalViews,
totalLikes,
// Actions
loadContents,
getContentById,
addContent,
updateContent,
deleteContent,
publishContent,
scheduleContent,
generateContent,
setFilters,
resetFilters,
setCurrentContent,
clearCurrentContent,
}
})