storeManagement contentmanagement edit

This commit is contained in:
unknown 2025-06-12 15:48:27 +09:00
parent 7315bac5a3
commit e0a23ed6d2
3 changed files with 1361 additions and 611 deletions

View File

@ -19,53 +19,308 @@ export const useContentStore = defineStore('content', () => {
dateRange: 'all', dateRange: 'all',
}) })
// 임시 데이터 (실제로는 API에서 가져옴) // 풍부한 더미 데이터
const sampleContents = [ const sampleContents = [
{ {
id: 1, id: 1,
title: '떡볶이 신메뉴 출시 이벤트', title: '🌶️ 떡볶이 신메뉴 출시 이벤트',
type: 'sns', type: 'sns',
platform: 'instagram', platform: 'INSTAGRAM',
content: '🌶️ 떡볶이 신메뉴 출시! 치즈가 듬뿍 들어간 치즈떡볶이가 새로 나왔어요!', content: '🌶️ 떡볶이 신메뉴 출시! 치즈가 듬뿍 들어간 치즈떡볶이가 새로 나왔어요! 매콤한 떡볶이와 고소한 치즈의 완벽한 조화✨\n\n#떡볶이 #신메뉴 #치즈떡볶이 #맛집 #분식 #매콤달콤',
hashtags: ['떡볶이', '신메뉴', '치즈떡볶이', '맛집'], hashtags: ['떡볶이', '신메뉴', '치즈떡볶이', '맛집', '분식', '매콤달콤'],
images: ['/images/menu-placeholder.png'], images: ['/images/menu-placeholder.png'],
status: 'published', status: 'PUBLISHED',
views: 1240, views: 1240,
likes: 85, likes: 85,
comments: 12, comments: 12,
createdAt: new Date('2024-01-15T10:30:00'), createdAt: '2024-01-15T10:30:00',
publishedAt: new Date('2024-01-15T14:00:00'), publishedAt: '2024-01-15T14:00:00',
}, },
{ {
id: 2, id: 2,
title: '매장 소개 포스터', title: '📸 우리 매장 소개 포스터',
type: 'poster', type: 'poster',
platform: 'blog', platform: 'POSTER',
content: '우리 매장을 소개하는 포스터입니다.', content: '따뜻한 분위기의 우리 매장을 소개합니다. 정성스럽게 만든 음식으로 여러분을 맞이하겠습니다.',
hashtags: ['매장소개', '분식집', '맛집'], hashtags: ['매장소개', '분식집', '맛집', '정성'],
images: ['/images/store-placeholder.png'], images: ['/images/store-placeholder.png'],
status: 'draft', status: 'DRAFT',
views: 0, views: 0,
likes: 0, likes: 0,
comments: 0, comments: 0,
createdAt: new Date('2024-01-14T16:20:00'), createdAt: '2024-01-14T16:20:00',
publishedAt: null, publishedAt: null,
}, },
{ {
id: 3, id: 3,
title: '할인 이벤트 안내', title: '🎉 특별 할인 이벤트 안내',
type: 'sns', type: 'sns',
platform: 'instagram', platform: 'INSTAGRAM',
content: '🎉 특별 할인 이벤트! 오늘 하루만 모든 메뉴 20% 할인!', content: '🎉 오늘 하루만 특별 할인! 모든 메뉴 20% 할인 이벤트 진행중🔥\n\n놓치면 후회할 기회! 지금 바로 방문하세요!\n\n⏰ 할인 시간: 오후 6시까지\n📍 위치: 서울 강남구 테헤란로 123',
hashtags: ['할인', '이벤트', '특가', '분식'], hashtags: ['할인', '이벤트', '특가', '분식', '강남맛집'],
images: ['/images/ai-character.png'], images: ['/images/event-poster.png'],
status: 'scheduled', 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, views: 0,
likes: 0, likes: 0,
comments: 0, comments: 0,
createdAt: new Date('2024-01-13T09:15:00'), createdAt: '2024-01-10T13:30:00',
scheduledAt: new Date('2024-01-16T12:00: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 // Getters
@ -90,15 +345,15 @@ export const useContentStore = defineStore('content', () => {
}) })
const publishedContents = computed(() => const publishedContents = computed(() =>
contents.value.filter((content) => content.status === 'published') contents.value.filter((content) => content.status === 'PUBLISHED')
) )
const draftContents = computed(() => const draftContents = computed(() =>
contents.value.filter((content) => content.status === 'draft') contents.value.filter((content) => content.status === 'DRAFT')
) )
const scheduledContents = computed(() => const scheduledContents = computed(() =>
contents.value.filter((content) => content.status === 'scheduled') contents.value.filter((content) => content.status === 'SCHEDULED')
) )
const recentContents = computed(() => { const recentContents = computed(() => {
@ -114,17 +369,18 @@ export const useContentStore = defineStore('content', () => {
) )
// Actions // Actions
const loadContents = async () => { const fetchContents = async () => {
try { try {
isLoading.value = true isLoading.value = true
// API 호출 시뮬레이션 // API 호출 시뮬레이션
await new Promise((resolve) => setTimeout(resolve, 1000)) await new Promise((resolve) => setTimeout(resolve, 1000))
// 임시로 샘플 데이터 사용 // 더미 데이터 로드
contents.value = [...sampleContents] contents.value = [...sampleContents]
console.log('콘텐츠 목록 로드 완료:', contents.value.length) console.log('콘텐츠 목록 로드 완료:', contents.value.length)
return contents.value
} catch (error) { } catch (error) {
console.error('콘텐츠 로드 실패:', error) console.error('콘텐츠 로드 실패:', error)
throw error throw error
@ -133,6 +389,10 @@ export const useContentStore = defineStore('content', () => {
} }
} }
const loadContents = async () => {
return await fetchContents()
}
const getContentById = (id) => { const getContentById = (id) => {
return contents.value.find((content) => content.id === parseInt(id)) return contents.value.find((content) => content.id === parseInt(id))
} }
@ -141,7 +401,7 @@ export const useContentStore = defineStore('content', () => {
const newContent = { const newContent = {
...content, ...content,
id: Date.now(), // 임시 ID id: Date.now(), // 임시 ID
createdAt: new Date(), createdAt: new Date().toISOString(),
views: 0, views: 0,
likes: 0, likes: 0,
comments: 0, comments: 0,
@ -150,27 +410,66 @@ export const useContentStore = defineStore('content', () => {
return newContent return newContent
} }
const updateContent = (contentId, updatedData) => { const updateContent = async (contentId, updatedData) => {
const index = contents.value.findIndex((content) => content.id === contentId) try {
if (index !== -1) { const index = contents.value.findIndex((content) => content.id === contentId)
contents.value[index] = { if (index !== -1) {
...contents.value[index], contents.value[index] = {
...updatedData, ...contents.value[index],
updatedAt: new Date(), ...updatedData,
updatedAt: new Date().toISOString(),
}
// API 호출 시뮬레이션
await new Promise((resolve) => setTimeout(resolve, 500))
return contents.value[index]
} }
return contents.value[index] throw new Error('콘텐츠를 찾을 수 없습니다.')
} catch (error) {
console.error('콘텐츠 수정 실패:', error)
throw error
} }
return null
} }
const deleteContent = (contentId) => { const deleteContent = async (contentId) => {
const index = contents.value.findIndex((content) => content.id === contentId) try {
if (index !== -1) { const index = contents.value.findIndex((content) => content.id === contentId)
const deletedContent = contents.value[index] if (index !== -1) {
contents.value.splice(index, 1) const deletedContent = contents.value[index]
return deletedContent 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
} }
return null
} }
const publishContent = async (contentId) => { const publishContent = async (contentId) => {
@ -181,8 +480,8 @@ export const useContentStore = defineStore('content', () => {
await new Promise((resolve) => setTimeout(resolve, 1000)) await new Promise((resolve) => setTimeout(resolve, 1000))
const content = updateContent(contentId, { const content = updateContent(contentId, {
status: 'published', status: 'PUBLISHED',
publishedAt: new Date(), publishedAt: new Date().toISOString(),
}) })
console.log('콘텐츠 발행 완료:', content?.title) console.log('콘텐츠 발행 완료:', content?.title)
@ -203,8 +502,8 @@ export const useContentStore = defineStore('content', () => {
await new Promise((resolve) => setTimeout(resolve, 500)) await new Promise((resolve) => setTimeout(resolve, 500))
const content = updateContent(contentId, { const content = updateContent(contentId, {
status: 'scheduled', status: 'SCHEDULED',
scheduledAt: new Date(scheduledTime), scheduledAt: new Date(scheduledTime).toISOString(),
}) })
console.log('콘텐츠 예약 완료:', content?.title) console.log('콘텐츠 예약 완료:', content?.title)
@ -231,7 +530,7 @@ export const useContentStore = defineStore('content', () => {
content: `AI가 생성한 ${options.type} 콘텐츠입니다. ${options.description || ''}`, content: `AI가 생성한 ${options.type} 콘텐츠입니다. ${options.description || ''}`,
hashtags: options.hashtags || [], hashtags: options.hashtags || [],
images: options.images || [], images: options.images || [],
status: 'draft', status: 'DRAFT',
} }
const newContent = addContent(generatedContent) const newContent = addContent(generatedContent)
@ -285,10 +584,12 @@ export const useContentStore = defineStore('content', () => {
totalLikes, totalLikes,
// Actions // Actions
loadContents, loadContents,
fetchContents,
getContentById, getContentById,
addContent, addContent,
updateContent, updateContent,
deleteContent, deleteContent,
deleteMultipleContents,
publishContent, publishContent,
scheduleContent, scheduleContent,
generateContent, generateContent,

File diff suppressed because it is too large Load Diff

View File

@ -467,9 +467,531 @@
{{ snackbar.message }} {{ snackbar.message }}
</v-snackbar> </v-snackbar>
<!-- 매장 등록/수정 다이얼로그 -->
<v-dialog
v-model="showCreateDialog"
max-width="800"
persistent
scrollable
>
<v-card>
<v-card-title class="pa-4">
<span class="text-h6">{{ editMode ? '매장 정보 수정' : '매장 정보 등록' }}</span>
<v-spacer />
</v-card-title>
<v-divider />
<v-card-text class="pa-6" style="max-height: 500px;">
<v-form ref="storeForm" v-model="formValid">
<v-row>
<!-- 매장 이미지 -->
<v-col cols="12">
<h4 class="text-subtitle-1 font-weight-bold mb-3">매장 이미지</h4>
<div class="text-center mb-4">
<v-avatar size="120" class="mb-3">
<v-img
:src="formData.imageUrl || '/images/store-placeholder.png'"
alt="매장 이미지"
/>
</v-avatar>
<br>
<v-btn
color="primary"
variant="outlined"
prepend-icon="mdi-camera"
@click="selectImage"
>
이미지 선택
</v-btn>
<input
ref="imageInput"
type="file"
accept="image/*"
style="display: none;"
@change="handleImageUpload"
>
</div>
</v-col>
<!-- 기본 정보 -->
<v-col cols="12" sm="6">
<v-text-field
v-model="formData.storeName"
label="매장명 *"
variant="outlined"
:rules="[v => !!v || '매장명을 입력해주세요']"
required
/>
</v-col>
<v-col cols="12" sm="6">
<v-select
v-model="formData.businessType"
label="업종 *"
variant="outlined"
:items="businessTypes"
:rules="[v => !!v || '업종을 선택해주세요']"
required
/>
</v-col>
<v-col cols="12" sm="6">
<v-text-field
v-model="formData.ownerName"
label="사업자명 *"
variant="outlined"
:rules="[v => !!v || '사업자명을 입력해주세요']"
required
/>
</v-col>
<v-col cols="12" sm="6">
<v-text-field
v-model="formData.businessNumber"
label="사업자등록번호 *"
variant="outlined"
:rules="businessNumberRules"
required
/>
</v-col>
<v-col cols="12">
<v-text-field
v-model="formData.address"
label="주소 *"
variant="outlined"
:rules="[v => !!v || '주소를 입력해주세요']"
required
/>
</v-col>
<v-col cols="12" sm="6">
<v-text-field
v-model="formData.phoneNumber"
label="연락처 *"
variant="outlined"
:rules="phoneRules"
required
/>
</v-col>
<v-col cols="12" sm="6">
<v-text-field
v-model.number="formData.seatCount"
label="좌석 수"
variant="outlined"
type="number"
min="0"
/>
</v-col>
<!-- SNS 정보 -->
<div class="form-section">
<h3 class="text-h6 font-weight-bold mb-4">SNS 계정 정보</h3>
<v-row>
<!-- 인스타그램 -->
<v-col cols="12" md="6">
<div class="d-flex align-center mb-2">
<v-icon color="purple" class="mr-2">mdi-instagram</v-icon>
<span class="text-subtitle-2 font-weight-medium">인스타그램</span>
</div>
<div class="d-flex gap-2 align-center">
<v-text-field
v-model="formData.instagramUrl"
placeholder="@계정명 또는 URL 입력"
variant="outlined"
density="comfortable"
prepend-inner-icon="mdi-at"
hide-details="auto"
class="flex-grow-1"
/>
<v-btn
color="purple"
size="small"
variant="tonal"
:loading="snsCheckLoading.instagram"
@click="checkSnsConnection('instagram')"
>
연동 확인
</v-btn>
</div>
</v-col>
<!-- 네이버 블로그 -->
<v-col cols="12" md="6">
<div class="d-flex align-center mb-2">
<v-icon color="green" class="mr-2">mdi-blogger</v-icon>
<span class="text-subtitle-2 font-weight-medium">네이버 블로그</span>
</div>
<div class="d-flex gap-2 align-center">
<v-text-field
v-model="formData.blogUrl"
placeholder="blog.naver.com/계정명"
variant="outlined"
density="comfortable"
prepend-inner-icon="mdi-web"
hide-details="auto"
class="flex-grow-1"
/>
<v-btn
color="green"
size="small"
variant="tonal"
:loading="snsCheckLoading.naver_blog"
@click="checkSnsConnection('naver_blog')"
>
연동 확인
</v-btn>
</div>
</v-col>
</v-row>
</div>
<!-- 운영 정보 -->
<v-col cols="12">
<h4 class="text-subtitle-1 font-weight-bold mb-3">운영 정보</h4>
</v-col>
<v-col cols="12" sm="6">
<v-text-field
v-model="formData.openTime"
label="오픈 시간"
variant="outlined"
type="time"
/>
</v-col>
<v-col cols="12" sm="6">
<v-text-field
v-model="formData.closeTime"
label="마감 시간"
variant="outlined"
type="time"
/>
</v-col>
<v-col cols="12">
<v-select
v-model="formData.holidays"
label="휴무일"
variant="outlined"
:items="daysOfWeek"
multiple
chips
/>
</v-col>
<v-col cols="12" sm="6">
<v-switch
v-model="formData.deliveryAvailable"
label="배달 서비스"
color="primary"
/>
</v-col>
<v-col cols="12" sm="6">
<v-switch
v-model="formData.takeoutAvailable"
label="포장 서비스"
color="primary"
/>
</v-col>
</v-row>
</v-form>
</v-card-text>
<v-divider />
<v-card-actions class="pa-4">
<v-spacer />
<v-btn
variant="text"
@click="closeDialog"
>
취소
</v-btn>
<v-btn
color="primary"
:loading="saving"
:disabled="!formValid"
@click="saveStoreInfo"
>
{{ editMode ? '수정하기' : '등록하기' }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- 메뉴 등록/수정 다이얼로그 -->
<v-dialog
v-model="showMenuDialog"
max-width="600"
persistent
scrollable
>
<v-card>
<v-card-title class="pa-4">
<span class="text-h6">{{ editMenuMode ? '메뉴 수정' : '메뉴 등록' }}</span>
<v-spacer />
<v-btn
icon
@click="closeMenuDialog"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-divider />
<v-card-text class="pa-6" style="max-height: 500px;">
<v-form ref="menuForm" v-model="menuFormValid">
<!-- 메뉴 이미지 -->
<div class="text-center mb-6">
<v-img
:src="menuFormData.imageUrl || '/images/menu-placeholder.png'"
:alt="menuFormData.menuName"
max-width="200"
max-height="150"
class="mx-auto mb-3 rounded"
/>
<v-btn
color="primary"
variant="outlined"
prepend-icon="mdi-camera"
@click="selectMenuImage"
>
이미지 선택
</v-btn>
<input
ref="menuImageInput"
type="file"
accept="image/*"
style="display: none;"
@change="handleMenuImageUpload"
>
</div>
<v-row>
<v-col cols="12" sm="8">
<v-text-field
v-model="menuFormData.menuName"
label="메뉴명 *"
variant="outlined"
:rules="[v => !!v || '메뉴명을 입력해주세요']"
required
/>
</v-col>
<v-col cols="12" sm="4">
<v-text-field
v-model.number="menuFormData.price"
label="가격 *"
variant="outlined"
type="number"
prefix="₩"
:rules="priceRules"
required
/>
</v-col>
<v-col cols="12">
<v-combobox
v-model="menuFormData.category"
label="카테고리 *"
variant="outlined"
:items="menuCategories"
:rules="[v => !!v || '카테고리를 선택해주세요']"
required
/>
</v-col>
<v-col cols="12">
<v-textarea
v-model="menuFormData.description"
label="메뉴 설명"
variant="outlined"
rows="3"
placeholder="메뉴에 대한 자세한 설명을 입력해주세요"
/>
</v-col>
<v-col cols="12" sm="6">
<div class="d-flex flex-column gap-2 mt-4">
<v-switch
v-model="menuFormData.available"
label="판매 중"
color="success"
/>
<v-switch
v-model="menuFormData.recommended"
label="추천 메뉴"
color="warning"
/>
</div>
</v-col>
</v-row>
</v-form>
</v-card-text>
<v-divider />
<v-card-actions class="pa-4">
<v-spacer />
<v-btn
variant="text"
@click="closeMenuDialog"
>
취소
</v-btn>
<v-btn
color="primary"
:loading="savingMenu"
:disabled="!menuFormValid"
@click="saveMenu"
>
{{ editMenuMode ? '수정하기' : '등록하기' }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- 메뉴 삭제 확인 다이얼로그 -->
<v-dialog v-model="showDeleteMenuDialog" max-width="400">
<v-card>
<v-card-title class="text-h6">메뉴 삭제</v-card-title>
<v-card-text>
<p>정말로 <strong>{{ deleteMenuTarget?.menuName }}</strong> 메뉴를 삭제하시겠습니까?</p>
<v-alert type="warning" variant="tonal" class="mt-3">
삭제된 메뉴는 복구할 없습니다.
</v-alert>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
color="grey"
variant="text"
@click="showDeleteMenuDialog = false"
>
취소
</v-btn>
<v-btn
color="error"
:loading="deletingMenu"
@click="deleteMenu"
>
삭제
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- 메뉴 상세 다이얼로그 -->
<v-dialog v-model="showMenuDetailDialog" max-width="500">
<v-card class="menu-detail-card">
<v-card-title class="pa-4">
<span class="text-h6">메뉴 상세 정보</span>
<v-spacer />
<v-btn
icon
@click="showMenuDetailDialog = false"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-divider />
<v-card-text class="pa-4" v-if="selectedMenuDetail">
<!-- 메뉴 이미지 -->
<div class="text-center mb-4">
<v-img
:src="selectedMenuDetail.imageUrl || '/images/menu-placeholder.png'"
:alt="selectedMenuDetail.menuName"
max-width="300"
max-height="200"
class="mx-auto rounded"
/>
</div>
<!-- 메뉴 기본 정보 -->
<div class="menu-detail-info mb-4">
<h3 class="text-h6 font-weight-bold mb-2">{{ selectedMenuDetail.menuName }}</h3>
<p class="text-body-1 mb-2">
{{ selectedMenuDetail.description || '설명이 없습니다.' }}
</p>
</div>
<!-- 메뉴 정보 카드들 -->
<v-row>
<v-col cols="6">
<v-card variant="tonal" color="primary" class="text-center pa-4">
<v-icon size="32" class="mb-2">mdi-food</v-icon>
<h4 class="text-subtitle-1 font-weight-bold">카테고리</h4>
<p class="text-body-2">{{ selectedMenuDetail.category }}</p>
</v-card>
</v-col>
<v-col cols="6">
<v-card variant="tonal" color="success" class="text-center pa-4">
<v-icon size="32" class="mb-2">mdi-currency-krw</v-icon>
<h4 class="text-subtitle-1 font-weight-bold">가격</h4>
<p class="text-body-2">{{ formatCurrency(selectedMenuDetail.price) }}</p>
</v-card>
</v-col>
</v-row>
</v-card-text>
<v-divider />
<v-card-actions class="pa-4 justify-end">
<v-btn
variant="text"
@click="showMenuDetailDialog = false"
>
닫기
</v-btn>
<v-btn
color="primary"
@click="editFromDetail"
>
수정하기
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- SNS 연동 확인 결과 다이얼로그 -->
<v-dialog v-model="showSnsResultDialog" max-width="400">
<v-card>
<v-card-title class="pa-4">
<v-icon :color="snsConnectionResult.success ? 'success' : 'error'" class="mr-2">
{{ snsConnectionResult.success ? 'mdi-check-circle' : 'mdi-alert-circle' }}
</v-icon>
SNS 연동 확인
</v-card-title>
<v-card-text class="pa-4">
<p>{{ snsConnectionResult.message }}</p>
</v-card-text>
<v-card-actions class="pa-4">
<v-spacer />
<v-btn
color="primary"
@click="showSnsResultDialog = false"
>
확인
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-container>
</template>
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useStoreStore } from '@/stores/store' import { useStoreStore } from '@/store/index'
/** /**
* AI 마케팅 서비스 - 매장 관리 페이지 * AI 마케팅 서비스 - 매장 관리 페이지