release
This commit is contained in:
@@ -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,
|
||||
}
|
||||
})
|
||||
@@ -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,
|
||||
}
|
||||
})
|
||||
@@ -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,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user