release
This commit is contained in:
parent
4c1381203f
commit
4a252efa72
@ -216,8 +216,8 @@ class ContentService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ multipart/form-data 형식으로 수정된 SNS 콘텐츠 생성
|
||||
/**
|
||||
* ✅ 완전한 SnsContentCreateRequest DTO에 맞춘 SNS 콘텐츠 생성
|
||||
* @param {Object} contentData - 콘텐츠 생성 데이터
|
||||
* @returns {Promise<Object>} 생성된 콘텐츠
|
||||
*/
|
||||
@ -225,7 +225,7 @@ class ContentService {
|
||||
try {
|
||||
console.log('🤖 SNS 콘텐츠 생성 요청:', contentData)
|
||||
|
||||
// ✅ Java 백엔드 필수 필드 검증 (SnsContentCreateRequest 기준)
|
||||
// ✅ 필수 필드 검증
|
||||
if (!contentData.storeId) {
|
||||
throw new Error('매장 ID는 필수입니다.')
|
||||
}
|
||||
@ -238,46 +238,80 @@ class ContentService {
|
||||
throw new Error('콘텐츠 제목은 필수입니다.')
|
||||
}
|
||||
|
||||
// ✅ FormData 생성 (multipart/form-data)
|
||||
// ✅ FormData 생성
|
||||
const formData = new FormData()
|
||||
|
||||
// ✅ request JSON 부분 구성 (Java SnsContentCreateRequest DTO에 맞춤)
|
||||
// ✅ 완전한 SnsContentCreateRequest DTO에 맞춘 데이터 구성
|
||||
const requestData = {
|
||||
storeId: contentData.storeId || 1,
|
||||
// ========== 기본 정보 ==========
|
||||
storeId: parseInt(contentData.storeId),
|
||||
storeName: contentData.storeName || '샘플 매장',
|
||||
storeType: contentData.storeType || '음식점',
|
||||
platform: this.normalizePlatform(contentData.platform),
|
||||
title: contentData.title,
|
||||
title: String(contentData.title).trim(),
|
||||
|
||||
// ========== 콘텐츠 생성 조건 ==========
|
||||
category: contentData.category || '메뉴소개',
|
||||
requirement: contentData.requirement || contentData.requirements || `${contentData.title}에 대한 SNS 게시물을 만들어주세요`,
|
||||
target: contentData.target || contentData.targetAudience || '일반고객',
|
||||
contentType: contentData.contentType || 'sns',
|
||||
eventName: contentData.eventName || null,
|
||||
toneAndManner: contentData.toneAndManner || '친근함',
|
||||
emotionIntensity: contentData.emotionIntensity || contentData.emotionalIntensity || '보통',
|
||||
|
||||
// ========== 이벤트 정보 ==========
|
||||
eventName: contentData.eventName || '',
|
||||
startDate: this.convertToJavaDate(contentData.startDate),
|
||||
endDate: this.convertToJavaDate(contentData.endDate)
|
||||
endDate: this.convertToJavaDate(contentData.endDate),
|
||||
|
||||
// ========== 미디어 정보 ==========
|
||||
images: [], // 파일로 별도 전송
|
||||
photoStyle: contentData.photoStyle || '밝고 화사한',
|
||||
|
||||
// ========== 추가 옵션 ==========
|
||||
includeHashtags: contentData.includeHashtags !== false,
|
||||
includeEmojis: contentData.includeEmojis !== false,
|
||||
includeCallToAction: contentData.includeCallToAction !== false,
|
||||
includeLocation: contentData.includeLocation || false,
|
||||
|
||||
// ========== 플랫폼별 옵션 ==========
|
||||
forInstagramStory: contentData.forInstagramStory || false,
|
||||
forNaverBlogPost: contentData.forNaverBlogPost || false,
|
||||
|
||||
// ========== AI 생성 옵션 ==========
|
||||
alternativeTitleCount: contentData.alternativeTitleCount || 3,
|
||||
alternativeHashtagSetCount: contentData.alternativeHashtagSetCount || 2,
|
||||
preferredAiModel: contentData.preferredAiModel || 'gpt-4-turbo',
|
||||
|
||||
// ========== 검증 플래그 ==========
|
||||
validForPlatform: true,
|
||||
validEventDates: true
|
||||
}
|
||||
|
||||
// null 값 제거
|
||||
// ✅ null/undefined 값 정리
|
||||
Object.keys(requestData).forEach(key => {
|
||||
if (requestData[key] === null || requestData[key] === undefined) {
|
||||
delete requestData[key]
|
||||
}
|
||||
// 빈 문자열도 제거 (Boolean과 Number 제외)
|
||||
if (typeof requestData[key] === 'string' && requestData[key].trim() === '') {
|
||||
delete requestData[key]
|
||||
}
|
||||
})
|
||||
|
||||
console.log('📝 [API] Java 백엔드용 SNS 요청 데이터:', requestData)
|
||||
console.log('📝 [API] 완전한 SNS 요청 데이터:', requestData)
|
||||
|
||||
// ✅ request를 JSON 문자열로 FormData에 추가
|
||||
// ✅ FormData에 JSON 추가
|
||||
formData.append('request', JSON.stringify(requestData))
|
||||
|
||||
// ✅ 이미지 파일들을 FormData에 추가 (SNS는 선택적)
|
||||
if (contentData.images && contentData.images.length > 0) {
|
||||
// Base64 이미지를 Blob으로 변환하여 추가
|
||||
// ✅ 이미지 파일 처리
|
||||
let imageCount = 0
|
||||
if (contentData.images && Array.isArray(contentData.images) && contentData.images.length > 0) {
|
||||
for (let i = 0; i < contentData.images.length; i++) {
|
||||
const imageData = contentData.images[i]
|
||||
if (typeof imageData === 'string' && imageData.startsWith('data:image/')) {
|
||||
try {
|
||||
const blob = this.base64ToBlob(imageData)
|
||||
formData.append('files', blob, `image_${i}.jpg`)
|
||||
imageCount++
|
||||
} catch (error) {
|
||||
console.warn(`⚠️ 이미지 ${i} 변환 실패:`, error)
|
||||
}
|
||||
@ -285,9 +319,19 @@ class ContentService {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('📁 [API] FormData 구성 완료')
|
||||
console.log(`📁 [API] FormData 구성 완료 (이미지 ${imageCount}개)`)
|
||||
|
||||
// ✅ multipart/form-data로 Java 백엔드 API 호출
|
||||
// ✅ 디버깅을 위한 FormData 내용 출력
|
||||
console.log('📋 [DEBUG] FormData 항목들:')
|
||||
for (let [key, value] of formData.entries()) {
|
||||
if (value instanceof Blob) {
|
||||
console.log(` ${key}: Blob (${value.size} bytes, ${value.type})`)
|
||||
} else {
|
||||
console.log(` ${key}:`, value)
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ API 호출
|
||||
const response = await contentApi.post('/sns/generate', formData, {
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
@ -297,13 +341,19 @@ class ContentService {
|
||||
|
||||
console.log('✅ [API] SNS 콘텐츠 생성 응답:', response.data)
|
||||
|
||||
// ✅ Java 백엔드 ApiResponse 구조에 맞춰 처리
|
||||
if (response.data && response.data.success && response.data.data) {
|
||||
// ✅ 응답 처리
|
||||
if (response.data?.success && response.data?.data) {
|
||||
return formatSuccessResponse({
|
||||
content: response.data.data.content,
|
||||
hashtags: response.data.data.hashtags || []
|
||||
content: response.data.data.content || '',
|
||||
hashtags: response.data.data.hashtags || [],
|
||||
contentId: response.data.data.contentId,
|
||||
platform: response.data.data.platform,
|
||||
title: response.data.data.title,
|
||||
alternativeTitles: response.data.data.alternativeTitles || [],
|
||||
alternativeHashtagSets: response.data.data.alternativeHashtagSets || []
|
||||
}, 'SNS 게시물이 생성되었습니다.')
|
||||
} else if (response.data && response.data.status === 200 && response.data.data) {
|
||||
} else if (response.data?.data?.content) {
|
||||
// success 필드가 없는 경우도 처리
|
||||
return formatSuccessResponse({
|
||||
content: response.data.data.content,
|
||||
hashtags: response.data.data.hashtags || []
|
||||
@ -315,18 +365,32 @@ class ContentService {
|
||||
} catch (error) {
|
||||
console.error('❌ [API] SNS 콘텐츠 생성 실패:', error)
|
||||
|
||||
if (error.response?.status === 400) {
|
||||
const backendMessage = error.response.data?.message || '요청 데이터가 잘못되었습니다.'
|
||||
return {
|
||||
success: false,
|
||||
message: backendMessage,
|
||||
error: error.response.data
|
||||
// ✅ 상세한 에러 로깅
|
||||
if (error.response) {
|
||||
console.error('❌ [DEBUG] HTTP Status:', error.response.status)
|
||||
console.error('❌ [DEBUG] Response Headers:', error.response.headers)
|
||||
console.error('❌ [DEBUG] Response Data:', error.response.data)
|
||||
|
||||
if (error.response.status === 400) {
|
||||
const backendMessage = error.response.data?.message || '요청 데이터가 잘못되었습니다.'
|
||||
return {
|
||||
success: false,
|
||||
message: `요청 검증 실패: ${backendMessage}`,
|
||||
error: error.response.data
|
||||
}
|
||||
} else if (error.response.status === 500) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'AI 서비스에서 콘텐츠 생성 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.',
|
||||
error: error.response.data
|
||||
}
|
||||
}
|
||||
} else if (error.response?.status === 500) {
|
||||
} else if (error.request) {
|
||||
console.error('❌ [DEBUG] Request timeout or network error')
|
||||
return {
|
||||
success: false,
|
||||
message: 'AI 서비스에서 콘텐츠 생성 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.',
|
||||
error: error.response.data
|
||||
message: '서버에 연결할 수 없습니다. 네트워크 연결을 확인해 주세요.',
|
||||
error: 'NETWORK_ERROR'
|
||||
}
|
||||
}
|
||||
|
||||
@ -412,7 +476,7 @@ class ContentService {
|
||||
|
||||
// ✅ multipart/form-data로 Java 백엔드 API 호출
|
||||
const response = await contentApi.post('/poster/generate', formData, {
|
||||
timeout: 60000,
|
||||
timeout: 0,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
@ -545,67 +609,95 @@ class ContentService {
|
||||
* @returns {Promise<Object>} 저장 결과
|
||||
*/
|
||||
async saveSnsContent(saveData) {
|
||||
try {
|
||||
const requestData = {}
|
||||
|
||||
if (saveData.contentId) requestData.contentId = saveData.contentId
|
||||
if (saveData.storeId !== undefined) requestData.storeId = saveData.storeId
|
||||
if (saveData.platform) requestData.platform = saveData.platform
|
||||
if (saveData.title) requestData.title = saveData.title
|
||||
if (saveData.content) requestData.content = saveData.content
|
||||
if (saveData.hashtags) requestData.hashtags = saveData.hashtags
|
||||
if (saveData.images) requestData.images = saveData.images
|
||||
if (saveData.finalTitle) requestData.finalTitle = saveData.finalTitle
|
||||
if (saveData.finalContent) requestData.finalContent = saveData.finalContent
|
||||
if (saveData.status) requestData.status = saveData.status
|
||||
if (saveData.category) requestData.category = saveData.category
|
||||
if (saveData.requirement) requestData.requirement = saveData.requirement
|
||||
if (saveData.eventName) requestData.eventName = saveData.eventName
|
||||
if (saveData.startDate) requestData.startDate = saveData.startDate
|
||||
if (saveData.endDate) requestData.endDate = saveData.endDate
|
||||
if (saveData.promotionalType) requestData.promotionalType = saveData.promotionalType
|
||||
if (saveData.eventDate) requestData.eventDate = saveData.eventDate
|
||||
|
||||
const response = await contentApi.post('/sns/save', requestData)
|
||||
return formatSuccessResponse(response.data.data, 'SNS 게시물이 저장되었습니다.')
|
||||
} catch (error) {
|
||||
return handleApiError(error)
|
||||
try {
|
||||
const requestData = {}
|
||||
|
||||
// ❌ contentId 제거 (백엔드 DTO에 없음)
|
||||
// if (saveData.contentId) requestData.contentId = saveData.contentId
|
||||
|
||||
// ✅ 필수 필드들
|
||||
if (saveData.storeId !== undefined) requestData.storeId = saveData.storeId
|
||||
|
||||
// ✅ contentType 필수 필드 추가 - enum 값에 맞게
|
||||
requestData.contentType = 'SNS' // 첫 번째 enum 버전에 맞춤
|
||||
|
||||
// ✅ platform 필수 필드 보장
|
||||
if (saveData.platform) {
|
||||
requestData.platform = saveData.platform
|
||||
} else {
|
||||
requestData.platform = 'INSTAGRAM' // 기본값
|
||||
}
|
||||
|
||||
// 선택적 필드들
|
||||
if (saveData.title) requestData.title = saveData.title
|
||||
if (saveData.content) requestData.content = saveData.content
|
||||
if (saveData.hashtags) requestData.hashtags = saveData.hashtags
|
||||
if (saveData.images) requestData.images = saveData.images
|
||||
if (saveData.finalTitle) requestData.finalTitle = saveData.finalTitle
|
||||
if (saveData.finalContent) requestData.finalContent = saveData.finalContent
|
||||
if (saveData.status) requestData.status = saveData.status
|
||||
if (saveData.category) requestData.category = saveData.category
|
||||
if (saveData.requirement) requestData.requirement = saveData.requirement
|
||||
if (saveData.toneAndManner) requestData.toneAndManner = saveData.toneAndManner
|
||||
if (saveData.emotionIntensity || saveData.emotionalIntensity) {
|
||||
requestData.emotionIntensity = saveData.emotionIntensity || saveData.emotionalIntensity
|
||||
}
|
||||
if (saveData.eventName) requestData.eventName = saveData.eventName
|
||||
if (saveData.startDate) requestData.startDate = saveData.startDate
|
||||
if (saveData.endDate) requestData.endDate = saveData.endDate
|
||||
if (saveData.promotionalType) requestData.promotionalType = saveData.promotionalType
|
||||
if (saveData.eventDate) requestData.eventDate = saveData.eventDate
|
||||
|
||||
console.log('📤 [API] SNS 저장 요청 데이터:', requestData)
|
||||
|
||||
const response = await contentApi.post('/sns/save', requestData)
|
||||
return formatSuccessResponse(response.data.data, 'SNS 게시물이 저장되었습니다.')
|
||||
} catch (error) {
|
||||
console.error('❌ [API] SNS 저장 실패:', error)
|
||||
return handleApiError(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 포스터 저장 (CON-015: 포스터 저장)
|
||||
* 포스터 저장 (CON-015: 포스터 저장) - 수정된 버전
|
||||
* @param {Object} saveData - 저장할 포스터 데이터
|
||||
* @returns {Promise<Object>} 저장 결과
|
||||
*/
|
||||
async savePoster(saveData) {
|
||||
try {
|
||||
const requestData = {}
|
||||
|
||||
if (saveData.contentId) requestData.contentId = saveData.contentId
|
||||
if (saveData.storeId !== undefined) requestData.storeId = saveData.storeId
|
||||
if (saveData.title) requestData.title = saveData.title
|
||||
if (saveData.content) requestData.content = saveData.content
|
||||
if (saveData.images) requestData.images = saveData.images
|
||||
if (saveData.status) requestData.status = saveData.status
|
||||
if (saveData.category) requestData.category = saveData.category
|
||||
if (saveData.requirement) requestData.requirement = saveData.requirement
|
||||
if (saveData.eventName) requestData.eventName = saveData.eventName
|
||||
if (saveData.startDate) requestData.startDate = saveData.startDate
|
||||
if (saveData.endDate) requestData.endDate = saveData.endDate
|
||||
if (saveData.photoStyle) requestData.photoStyle = saveData.photoStyle
|
||||
if (saveData.targetAudience) requestData.targetAudience = saveData.targetAudience
|
||||
if (saveData.promotionType) requestData.promotionType = saveData.promotionType
|
||||
if (saveData.imageStyle) requestData.imageStyle = saveData.imageStyle
|
||||
if (saveData.promotionStartDate) requestData.promotionStartDate = saveData.promotionStartDate
|
||||
if (saveData.promotionEndDate) requestData.promotionEndDate = saveData.promotionEndDate
|
||||
|
||||
const response = await contentApi.post('/poster/save', requestData)
|
||||
return formatSuccessResponse(response.data.data, '포스터가 저장되었습니다.')
|
||||
} catch (error) {
|
||||
return handleApiError(error)
|
||||
}
|
||||
try {
|
||||
const requestData = {}
|
||||
|
||||
// ❌ contentId 제거 (백엔드 DTO에 없음)
|
||||
// if (saveData.contentId) requestData.contentId = saveData.contentId
|
||||
|
||||
if (saveData.storeId !== undefined) requestData.storeId = saveData.storeId
|
||||
if (saveData.title) requestData.title = saveData.title
|
||||
if (saveData.content) requestData.content = saveData.content
|
||||
if (saveData.images) requestData.images = saveData.images
|
||||
if (saveData.status) requestData.status = saveData.status
|
||||
if (saveData.category) requestData.category = saveData.category
|
||||
if (saveData.requirement) requestData.requirement = saveData.requirement
|
||||
if (saveData.toneAndManner) requestData.toneAndManner = saveData.toneAndManner
|
||||
if (saveData.emotionIntensity) requestData.emotionIntensity = saveData.emotionIntensity
|
||||
if (saveData.eventName) requestData.eventName = saveData.eventName
|
||||
if (saveData.startDate) requestData.startDate = saveData.startDate
|
||||
if (saveData.endDate) requestData.endDate = saveData.endDate
|
||||
if (saveData.photoStyle) requestData.photoStyle = saveData.photoStyle
|
||||
if (saveData.targetAudience) requestData.targetAudience = saveData.targetAudience
|
||||
if (saveData.promotionType) requestData.promotionType = saveData.promotionType
|
||||
if (saveData.imageStyle) requestData.imageStyle = saveData.imageStyle
|
||||
if (saveData.promotionStartDate) requestData.promotionStartDate = saveData.promotionStartDate
|
||||
if (saveData.promotionEndDate) requestData.promotionEndDate = saveData.promotionEndDate
|
||||
|
||||
console.log('📤 [API] 포스터 저장 요청 데이터:', requestData)
|
||||
|
||||
const response = await contentApi.post('/poster/save', requestData)
|
||||
return formatSuccessResponse(response.data.data, '포스터가 저장되었습니다.')
|
||||
} catch (error) {
|
||||
console.error('❌ [API] 포스터 저장 실패:', error)
|
||||
return handleApiError(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ 콘텐츠 저장 (통합)
|
||||
@ -613,12 +705,24 @@ class ContentService {
|
||||
* @returns {Promise<Object>} 저장 결과
|
||||
*/
|
||||
async saveContent(saveData) {
|
||||
if (saveData.contentType === 'poster' || saveData.type === 'poster') {
|
||||
return await this.savePoster(saveData)
|
||||
} else {
|
||||
try {
|
||||
console.log('💾 [API] 콘텐츠 저장 요청:', saveData)
|
||||
|
||||
// ✅ 콘텐츠 타입에 따라 다른 API 호출
|
||||
if (saveData.contentType === 'SNS' || saveData.platform) {
|
||||
// SNS 콘텐츠 저장
|
||||
console.log('📱 [API] SNS 콘텐츠 저장 API 호출')
|
||||
return await this.saveSnsContent(saveData)
|
||||
} else {
|
||||
// 포스터 콘텐츠 저장
|
||||
console.log('🖼️ [API] 포스터 콘텐츠 저장 API 호출')
|
||||
return await this.savePoster(saveData)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ [API] 콘텐츠 저장 실패:', error)
|
||||
return handleApiError(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ 진행 중인 콘텐츠 조회 (첫 번째 코드에서 추가)
|
||||
|
||||
@ -277,6 +277,146 @@ export const useContentStore = defineStore('content', () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 포스터 저장 - 수정된 버전
|
||||
*/
|
||||
const savePoster = async (saveData) => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
console.log('💾 [STORE] 포스터 저장 요청:', saveData)
|
||||
|
||||
// 매장 ID 조회 (필요한 경우)
|
||||
let storeId = saveData.storeId
|
||||
if (!storeId) {
|
||||
try {
|
||||
storeId = await getStoreId()
|
||||
} catch (error) {
|
||||
console.warn('⚠️ 매장 ID 조회 실패, 기본값 사용:', error)
|
||||
storeId = 1
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ PosterContentSaveRequest 구조에 맞게 데이터 변환 (contentId 처리 개선)
|
||||
const requestData = {
|
||||
// ✅ contentId 처리: 값이 있으면 사용, 없으면 임시 ID 생성
|
||||
contentId: saveData.contentId || Date.now(),
|
||||
storeId: storeId,
|
||||
title: saveData.title || '',
|
||||
|
||||
// ✅ content 필드에 실제 값 보장 (null이면 안됨)
|
||||
content: saveData.content || saveData.title || '포스터 콘텐츠',
|
||||
|
||||
// ✅ images 배열이 비어있지 않도록 보장
|
||||
images: Array.isArray(saveData.images) ? saveData.images.filter(img => img) : [],
|
||||
|
||||
status: saveData.status || 'PUBLISHED',
|
||||
category: saveData.category || '이벤트',
|
||||
requirement: saveData.requirement || '',
|
||||
toneAndManner: saveData.toneAndManner || '친근함',
|
||||
emotionIntensity: saveData.emotionIntensity || '보통',
|
||||
eventName: saveData.eventName || '',
|
||||
startDate: saveData.startDate,
|
||||
endDate: saveData.endDate,
|
||||
photoStyle: saveData.photoStyle || '밝고 화사한'
|
||||
}
|
||||
|
||||
// ✅ 필수 필드 검증
|
||||
if (!requestData.title) {
|
||||
throw new Error('제목은 필수입니다.')
|
||||
}
|
||||
if (!requestData.images || requestData.images.length === 0) {
|
||||
throw new Error('이미지는 필수입니다.')
|
||||
}
|
||||
|
||||
console.log('📝 [STORE] 최종 저장 요청 데이터:', {
|
||||
...requestData,
|
||||
images: `${requestData.images.length}개 이미지`
|
||||
})
|
||||
|
||||
const result = await contentService.savePoster(requestData)
|
||||
|
||||
if (result.success) {
|
||||
console.log('✅ [STORE] 포스터 저장 성공')
|
||||
|
||||
// 목록 새로고침
|
||||
await loadContents()
|
||||
|
||||
return { success: true, message: '포스터가 저장되었습니다.' }
|
||||
} else {
|
||||
console.error('❌ [STORE] 포스터 저장 실패:', result.message)
|
||||
return { success: false, error: result.message }
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ [STORE] 포스터 저장 예외:', error)
|
||||
return { success: false, error: '네트워크 오류가 발생했습니다.' }
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SNS 콘텐츠 저장
|
||||
*/
|
||||
const saveSnsContent = async (saveData) => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
console.log('💾 [STORE] SNS 콘텐츠 저장 요청:', saveData)
|
||||
|
||||
// 매장 ID 조회 (필요한 경우)
|
||||
let storeId = saveData.storeId
|
||||
if (!storeId) {
|
||||
try {
|
||||
storeId = await getStoreId()
|
||||
} catch (error) {
|
||||
console.warn('⚠️ 매장 ID 조회 실패, 기본값 사용:', error)
|
||||
storeId = 1
|
||||
}
|
||||
}
|
||||
|
||||
// SnsContentSaveRequest 구조에 맞게 데이터 변환
|
||||
const requestData = {
|
||||
contentId: saveData.contentId || Date.now(), // 임시 ID 생성
|
||||
storeId: storeId,
|
||||
platform: saveData.platform || 'INSTAGRAM',
|
||||
title: saveData.title || '',
|
||||
content: saveData.content || '',
|
||||
hashtags: saveData.hashtags || [],
|
||||
images: saveData.images || [],
|
||||
finalTitle: saveData.finalTitle || saveData.title,
|
||||
finalContent: saveData.finalContent || saveData.content,
|
||||
status: saveData.status || 'DRAFT',
|
||||
category: saveData.category || '메뉴소개',
|
||||
requirement: saveData.requirement || '',
|
||||
toneAndManner: saveData.toneAndManner || '친근함',
|
||||
emotionIntensity: saveData.emotionIntensity || '보통',
|
||||
eventName: saveData.eventName || '',
|
||||
startDate: saveData.startDate,
|
||||
endDate: saveData.endDate
|
||||
}
|
||||
|
||||
const result = await contentService.saveSnsContent(requestData)
|
||||
|
||||
if (result.success) {
|
||||
console.log('✅ [STORE] SNS 콘텐츠 저장 성공')
|
||||
|
||||
// 목록 새로고침
|
||||
await loadContents()
|
||||
|
||||
return { success: true, message: 'SNS 콘텐츠가 저장되었습니다.' }
|
||||
} else {
|
||||
console.error('❌ [STORE] SNS 콘텐츠 저장 실패:', result.message)
|
||||
return { success: false, error: result.message }
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ [STORE] SNS 콘텐츠 저장 예외:', error)
|
||||
return { success: false, error: '네트워크 오류가 발생했습니다.' }
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* fetchContentList를 실제 API 호출로 수정 (기존 호환성 유지)
|
||||
*/
|
||||
@ -343,7 +483,7 @@ export const useContentStore = defineStore('content', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 콘텐츠 관리 기능들 (첫 번째 코드에서 추가) =====
|
||||
// ===== 콘텐츠 관리 기능들 =====
|
||||
|
||||
/**
|
||||
* 콘텐츠 수정
|
||||
@ -674,6 +814,8 @@ export const useContentStore = defineStore('content', () => {
|
||||
loadContents, // 새로 추가된 메서드 (매장 정보 조회 포함)
|
||||
generateContent,
|
||||
saveContent,
|
||||
savePoster, // 포스터 전용 저장
|
||||
saveSnsContent, // SNS 콘텐츠 전용 저장
|
||||
fetchContentList, // 기존 호환성 유지
|
||||
fetchOngoingContents,
|
||||
fetchContentDetail,
|
||||
|
||||
@ -207,7 +207,7 @@
|
||||
<!-- 요구사항 -->
|
||||
<v-textarea
|
||||
v-model="formData.requirements"
|
||||
label="구체적인 요구사항 (선택사항)"
|
||||
label="구체적인 요구사항"
|
||||
variant="outlined"
|
||||
rows="3"
|
||||
density="compact"
|
||||
@ -664,19 +664,19 @@ const router = useRouter()
|
||||
const contentStore = useContentStore()
|
||||
const appStore = useAppStore()
|
||||
|
||||
// 반응형 데이터
|
||||
// ✅ 반응형 데이터 - isGenerating 추가
|
||||
const selectedType = ref('sns')
|
||||
const formValid = ref(false)
|
||||
const uploadedFiles = ref([])
|
||||
const previewImages = ref([])
|
||||
const isPublishing = ref(false)
|
||||
const isGenerating = ref(false) // ✅ 추가
|
||||
const publishingIndex = ref(-1)
|
||||
const showDetailDialog = ref(false)
|
||||
const selectedVersion = ref(0)
|
||||
const generatedVersions = ref([])
|
||||
const remainingGenerations = ref(3)
|
||||
|
||||
// 폼 데이터 - 누락된 필드들 추가
|
||||
// 폼 데이터
|
||||
const formData = ref({
|
||||
title: '',
|
||||
platform: '',
|
||||
@ -691,9 +691,14 @@ const formData = ref({
|
||||
promotionStartDate: '',
|
||||
promotionEndDate: '',
|
||||
requirements: '',
|
||||
toneAndManner: '친근함',
|
||||
emotionIntensity: '보통',
|
||||
imageStyle: '모던',
|
||||
promotionType: '할인 정보',
|
||||
photoStyle: '밝고 화사한'
|
||||
})
|
||||
|
||||
// AI 옵션 - 누락된 필드들 추가
|
||||
// AI 옵션
|
||||
const aiOptions = ref({
|
||||
toneAndManner: 'friendly',
|
||||
promotion: 'general',
|
||||
@ -734,14 +739,6 @@ const targetTypes = [
|
||||
{ title: '이벤트', value: 'event' },
|
||||
]
|
||||
|
||||
// 추가 옵션들 정의
|
||||
const categoryOptions = [
|
||||
{ title: '음식', value: '음식' },
|
||||
{ title: '매장', value: '매장' },
|
||||
{ title: '이벤트', value: '이벤트' },
|
||||
{ title: '기타', value: '기타' }
|
||||
]
|
||||
|
||||
// 타겟 연령층 옵션
|
||||
const targetAgeOptions = [
|
||||
{ title: '10대', value: '10대' },
|
||||
@ -752,16 +749,6 @@ const targetAgeOptions = [
|
||||
{ title: '60대 이상', value: '60대 이상' }
|
||||
]
|
||||
|
||||
const photoStyleOptions = [
|
||||
{ title: '밝고 화사한', value: '밝고 화사한' },
|
||||
{ title: '모던한', value: '모던' },
|
||||
{ title: '미니멀한', value: '미니멀' },
|
||||
{ title: '빈티지', value: '빈티지' },
|
||||
{ title: '컬러풀', value: '컬러풀' },
|
||||
{ title: '우아한', value: '우아한' },
|
||||
{ title: '캐주얼', value: '캐주얼' }
|
||||
]
|
||||
|
||||
// 타입별 타겟 옵션 함수
|
||||
const getTargetTypes = (type) => {
|
||||
if (type === 'poster') {
|
||||
@ -820,51 +807,39 @@ const promotionEndDateRules = [
|
||||
}
|
||||
]
|
||||
|
||||
// ✅ 수정: 이미지 URL 유효성 검사 함수 - 더 관대하게 수정
|
||||
const getValidImageUrl = (imageUrl) => {
|
||||
console.log('🖼️ 이미지 URL 검증:', imageUrl, typeof imageUrl)
|
||||
|
||||
if (!imageUrl || typeof imageUrl !== 'string') {
|
||||
console.log('❌ 이미지 URL이 문자열이 아님')
|
||||
return null
|
||||
// ✅ Computed 속성들
|
||||
const formValid = computed(() => {
|
||||
// 기본 필수 필드 검증
|
||||
if (!formData.value.title || !formData.value.targetType) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 조건을 더 관대하게 수정 - 최소 길이만 체크
|
||||
if (imageUrl.length < 10) {
|
||||
console.log('❌ 이미지 URL이 너무 짧음:', imageUrl.length)
|
||||
return null
|
||||
// SNS 타입인 경우 플랫폼 필수
|
||||
if (selectedType.value === 'sns' && !formData.value.platform) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 유효한 이미지 URL 형식 체크 (조건 완화)
|
||||
if (imageUrl.startsWith('http') ||
|
||||
imageUrl.startsWith('data:image/') ||
|
||||
imageUrl.startsWith('blob:') ||
|
||||
imageUrl.startsWith('//') ||
|
||||
imageUrl.includes('blob.core.windows.net')) { // Azure Blob Storage 추가
|
||||
|
||||
console.log('✅ 유효한 이미지 URL:', imageUrl.substring(0, 50) + '...')
|
||||
return imageUrl
|
||||
// 이벤트 타입인 경우 추가 검증
|
||||
if (formData.value.targetType === 'event') {
|
||||
if (!formData.value.eventName || !formData.value.startDate || !formData.value.endDate) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
console.log('❌ 유효하지 않은 이미지 URL 형식')
|
||||
return null
|
||||
}
|
||||
|
||||
// ✅ 이미지 미리보기 함수
|
||||
const previewImage = (imageUrl, title) => {
|
||||
if (!imageUrl) return
|
||||
// 포스터 타입인 경우 추가 검증
|
||||
if (selectedType.value === 'poster') {
|
||||
if (!formData.value.promotionStartDate || !formData.value.promotionEndDate) {
|
||||
return false
|
||||
}
|
||||
// 포스터는 이미지 필수
|
||||
if (!previewImages.value || previewImages.value.length === 0) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 간단히 새 탭에서 이미지 열기
|
||||
window.open(imageUrl, '_blank')
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// ✅ 추가: 이미지 에러 핸들링 함수
|
||||
const handleImageError = (event) => {
|
||||
console.error('❌ 이미지 로딩 실패:', event.target?.src)
|
||||
// 에러 시 플레이스홀더 표시를 위해 특별한 처리 필요 없음 (v-img의 error slot이 처리)
|
||||
}
|
||||
|
||||
// 수정: canGenerate computed 추가
|
||||
const canGenerate = computed(() => {
|
||||
try {
|
||||
// 기본 조건들 확인
|
||||
@ -894,7 +869,6 @@ const canGenerate = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
// Computed
|
||||
const currentVersion = computed(() => {
|
||||
return generatedVersions.value[selectedVersion.value] || null
|
||||
})
|
||||
@ -905,7 +879,6 @@ const selectContentType = (type) => {
|
||||
console.log(`${type} 타입 선택됨`)
|
||||
}
|
||||
|
||||
// 수정: handleFileUpload 함수 - 중복 등록 방지
|
||||
const handleFileUpload = (files) => {
|
||||
console.log('📁 파일 업로드 이벤트:', files)
|
||||
|
||||
@ -979,135 +952,159 @@ const removeImage = (index) => {
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 수정: generateContent 함수 - Java 백엔드에 맞게 데이터 구성
|
||||
const generateContent = async () => {
|
||||
if (!canGenerate.value || remainingGenerations.value <= 0) {
|
||||
console.log('⚠️ 생성 조건을 만족하지 않음')
|
||||
if (!formValid.value) {
|
||||
appStore.showSnackbar('모든 필수 항목을 입력해주세요.', 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
// 최대 3개 버전 체크
|
||||
if (generatedVersions.value.length >= 3) {
|
||||
appStore.showSnackbar('최대 3개의 버전까지만 생성할 수 있습니다.', 'warning')
|
||||
if (remainingGenerations.value <= 0) {
|
||||
appStore.showSnackbar('생성 가능 횟수를 모두 사용했습니다.', 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
isGenerating.value = true
|
||||
|
||||
try {
|
||||
console.log('🎯 콘텐츠 생성 시작')
|
||||
console.log('🚀 [UI] 콘텐츠 생성 시작')
|
||||
console.log('📋 [UI] 폼 데이터:', formData.value)
|
||||
console.log('📁 [UI] 이미지 데이터:', previewImages.value)
|
||||
|
||||
// ✅ 콘텐츠 타입에 따른 데이터 구성 분기
|
||||
let contentData
|
||||
// ✅ 매장 ID 가져오기
|
||||
let storeId = 1 // 기본값
|
||||
|
||||
try {
|
||||
// localStorage에서 매장 정보 조회 시도
|
||||
const storeInfo = JSON.parse(localStorage.getItem('storeInfo') || '{}')
|
||||
const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}')
|
||||
|
||||
if (storeInfo.storeId) {
|
||||
storeId = storeInfo.storeId
|
||||
} else if (userInfo.storeId) {
|
||||
storeId = userInfo.storeId
|
||||
} else {
|
||||
console.warn('⚠️ localStorage에서 매장 ID를 찾을 수 없음, 기본값 사용:', storeId)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ 매장 정보 파싱 실패, 기본값 사용:', storeId)
|
||||
}
|
||||
|
||||
console.log('🏪 [UI] 사용할 매장 ID:', storeId)
|
||||
|
||||
// ✅ Base64 이미지 URL 추출
|
||||
const imageUrls = previewImages.value?.map(img => img.url).filter(url => url) || []
|
||||
console.log('📁 [UI] 추출된 이미지 URL들:', imageUrls)
|
||||
|
||||
// ✅ 포스터 타입의 경우 이미지 필수 검증
|
||||
if (selectedType.value === 'poster' && imageUrls.length === 0) {
|
||||
throw new Error('포스터 생성을 위해 최소 1개의 이미지가 필요합니다.')
|
||||
}
|
||||
|
||||
// ✅ 콘텐츠 생성 데이터 구성
|
||||
const contentData = {
|
||||
title: formData.value.title,
|
||||
platform: formData.value.platform || (selectedType.value === 'poster' ? 'POSTER' : 'INSTAGRAM'),
|
||||
contentType: selectedType.value,
|
||||
type: selectedType.value,
|
||||
category: getCategory(formData.value.targetType),
|
||||
requirement: formData.value.requirements || `${formData.value.title}에 대한 ${selectedType.value === 'poster' ? '포스터' : 'SNS 게시물'}를 만들어주세요`,
|
||||
targetType: formData.value.targetType,
|
||||
targetAudience: formData.value.targetType,
|
||||
eventName: formData.value.eventName,
|
||||
eventDate: formData.value.eventDate,
|
||||
startDate: formData.value.startDate,
|
||||
endDate: formData.value.endDate,
|
||||
toneAndManner: formData.value.toneAndManner || '친근함',
|
||||
emotionIntensity: formData.value.emotionIntensity || '보통',
|
||||
images: imageUrls, // ✅ Base64 이미지 URL 배열
|
||||
storeId: storeId // ✅ 매장 ID 추가
|
||||
}
|
||||
|
||||
// ✅ 포스터 전용 필드 추가
|
||||
if (selectedType.value === 'poster') {
|
||||
// ✅ Java 백엔드 PosterContentCreateRequest에 맞게 데이터 구성
|
||||
contentData = {
|
||||
type: selectedType.value,
|
||||
contentType: selectedType.value,
|
||||
|
||||
// ✅ Java 백엔드 필수 필드들 (PosterContentCreateRequest 기준)
|
||||
storeId: 1,
|
||||
title: formData.value.title,
|
||||
targetAudience: convertTargetAudienceToKorean(formData.value.targetType),
|
||||
promotionStartDate: formData.value.promotionStartDate,
|
||||
promotionEndDate: formData.value.promotionEndDate,
|
||||
images: previewImages.value.map(img => img.url),
|
||||
|
||||
// ✅ 선택적 필드들 (Java DTO에 맞춤)
|
||||
menuName: formData.value.targetType === 'menu' ? formData.value.title : null,
|
||||
eventName: formData.value.targetType === 'event' ? formData.value.eventName : null,
|
||||
imageStyle: aiOptions.value.imageStyle || '모던',
|
||||
category: getJavaCategory(formData.value.targetType),
|
||||
requirement: formData.value.requirements || `${formData.value.title}에 대한 포스터를 만들어주세요`,
|
||||
startDate: convertDateTimeToDateStrict(formData.value.startDate),
|
||||
endDate: convertDateTimeToDateStrict(formData.value.endDate),
|
||||
photoStyle: aiOptions.value.photoStyle || '밝고 화사한'
|
||||
}
|
||||
} else {
|
||||
// ✅ Java 백엔드 SnsContentCreateRequest에 맞게 데이터 구성
|
||||
contentData = {
|
||||
type: selectedType.value,
|
||||
contentType: selectedType.value,
|
||||
|
||||
// ✅ Java 백엔드 필수 필드들 (SnsContentCreateRequest 기준)
|
||||
storeId: 1,
|
||||
storeName: '샘플 매장',
|
||||
storeType: '음식점',
|
||||
platform: formData.value.platform,
|
||||
title: formData.value.title,
|
||||
category: getJavaCategory(formData.value.targetType),
|
||||
requirement: formData.value.requirements || `${formData.value.title}에 대한 SNS 게시물을 만들어주세요`,
|
||||
target: convertTargetAudienceToKorean(formData.value.targetType),
|
||||
images: previewImages.value.map(img => img.url),
|
||||
|
||||
// ✅ 선택적 필드들
|
||||
eventName: formData.value.targetType === 'event' ? formData.value.eventName : null,
|
||||
startDate: convertDateTimeToDateStrict(formData.value.startDate),
|
||||
endDate: convertDateTimeToDateStrict(formData.value.endDate)
|
||||
}
|
||||
contentData.promotionStartDate = formData.value.promotionStartDate
|
||||
contentData.promotionEndDate = formData.value.promotionEndDate
|
||||
contentData.imageStyle = formData.value.imageStyle || '모던'
|
||||
contentData.promotionType = formData.value.promotionType
|
||||
contentData.photoStyle = formData.value.photoStyle || '밝고 화사한'
|
||||
}
|
||||
|
||||
// ✅ undefined 값들 제거 (Java에서 오류 방지)
|
||||
Object.keys(contentData).forEach(key => {
|
||||
if (contentData[key] === undefined) {
|
||||
delete contentData[key]
|
||||
}
|
||||
})
|
||||
console.log('📤 [UI] 생성 요청 데이터:', contentData)
|
||||
|
||||
console.log('🎯 [GENERATE] Java 백엔드용 데이터:', contentData)
|
||||
|
||||
// ✅ 필수 필드 재검증
|
||||
if (!contentData.title) {
|
||||
throw new Error('제목은 필수입니다.')
|
||||
// ✅ contentData 무결성 체크
|
||||
if (!contentData || typeof contentData !== 'object') {
|
||||
throw new Error('콘텐츠 데이터 구성에 실패했습니다.')
|
||||
}
|
||||
|
||||
if (selectedType.value === 'poster') {
|
||||
if (!contentData.targetAudience) {
|
||||
throw new Error('홍보 대상은 필수입니다.')
|
||||
}
|
||||
if (!contentData.promotionStartDate || !contentData.promotionEndDate) {
|
||||
throw new Error('홍보 기간은 필수입니다.')
|
||||
}
|
||||
if (!contentData.images || contentData.images.length === 0) {
|
||||
throw new Error('포스터 생성을 위해서는 이미지가 필요합니다.')
|
||||
}
|
||||
} else {
|
||||
if (!contentData.platform) {
|
||||
throw new Error('플랫폼은 필수입니다.')
|
||||
}
|
||||
if (!Array.isArray(contentData.images)) {
|
||||
console.error('❌ [UI] contentData.images가 배열이 아님!')
|
||||
contentData.images = []
|
||||
}
|
||||
|
||||
// AI 콘텐츠 생성 - store.generateContent에 단일 파라미터로 전달
|
||||
// ✅ Store 호출
|
||||
console.log('🚀 [UI] contentStore.generateContent 호출')
|
||||
const generated = await contentStore.generateContent(contentData)
|
||||
|
||||
console.log('🎯 [GENERATE] AI 생성 응답:', generated)
|
||||
|
||||
if (generated && generated.success) {
|
||||
const newContent = {
|
||||
id: Date.now() + Math.random(),
|
||||
...contentData,
|
||||
// 프론트엔드 표시용 원본 데이터도 보존
|
||||
targetType: formData.value.targetType,
|
||||
platform: selectedType.value === 'sns' ? formData.value.platform : 'poster',
|
||||
content: generated.content || generated.data?.content || '생성된 콘텐츠 내용',
|
||||
hashtags: generated.hashtags || generated.data?.hashtags || [],
|
||||
createdAt: new Date(),
|
||||
status: 'draft',
|
||||
// ✅ 포스터인 경우 posterImage 필드 추가
|
||||
posterImage: selectedType.value === 'poster' ? (generated.posterImage || generated.data?.posterImage || generated.content) : null
|
||||
}
|
||||
|
||||
generatedVersions.value.push(newContent)
|
||||
selectedVersion.value = generatedVersions.value.length - 1
|
||||
remainingGenerations.value--
|
||||
|
||||
console.log('✅ [GENERATE] AI 콘텐츠 생성 성공:', newContent)
|
||||
appStore.showSnackbar(`콘텐츠 버전 ${generatedVersions.value.length}이 생성되었습니다!`, 'success')
|
||||
} else {
|
||||
throw new Error(generated?.error || '콘텐츠 생성에 실패했습니다.')
|
||||
|
||||
if (!generated || !generated.success) {
|
||||
throw new Error(generated?.message || '콘텐츠 생성에 실패했습니다.')
|
||||
}
|
||||
|
||||
// ✅ 포스터 생성 결과 처리 개선
|
||||
let finalContent = ''
|
||||
let posterImageUrl = ''
|
||||
|
||||
if (selectedType.value === 'poster') {
|
||||
// 포스터의 경우 generated.data에서 이미지 URL 추출
|
||||
posterImageUrl = generated.data?.posterImage || generated.data?.content || generated.content || ''
|
||||
finalContent = posterImageUrl // content 필드에 이미지 URL 저장
|
||||
|
||||
console.log('🖼️ [UI] 포스터 이미지 URL:', posterImageUrl)
|
||||
} else {
|
||||
// SNS의 경우 기존 로직 유지
|
||||
finalContent = generated.content || generated.data?.content || ''
|
||||
|
||||
// SNS용 이미지 추가
|
||||
if (contentData.images && contentData.images.length > 0) {
|
||||
const imageHtml = contentData.images.map(imageUrl =>
|
||||
`<div style="margin-bottom: 15px; text-align: center;">
|
||||
<img src="${imageUrl}" style="width: 100%; max-width: 400px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1);" />
|
||||
</div>`
|
||||
).join('')
|
||||
|
||||
if (isHtmlContent(finalContent)) {
|
||||
finalContent = imageHtml + finalContent
|
||||
} else {
|
||||
finalContent = imageHtml + `<div style="padding: 15px; font-family: 'Noto Sans KR', Arial, sans-serif; line-height: 1.6;">${finalContent.replace(/\n/g, '<br>')}</div>`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 생성된 콘텐츠 객체에 이미지 정보 포함
|
||||
const newContent = {
|
||||
id: Date.now() + Math.random(),
|
||||
...contentData,
|
||||
content: finalContent,
|
||||
posterImage: posterImageUrl, // 포스터 이미지 URL 별도 저장
|
||||
hashtags: generated.hashtags || generated.data?.hashtags || [],
|
||||
createdAt: new Date(),
|
||||
status: 'draft',
|
||||
uploadedImages: previewImages.value || [], // ✅ 업로드된 이미지 정보 보존
|
||||
images: imageUrls, // ✅ Base64 URL 보존
|
||||
platform: contentData.platform || 'POSTER'
|
||||
}
|
||||
|
||||
generatedVersions.value.push(newContent)
|
||||
selectedVersion.value = generatedVersions.value.length - 1
|
||||
remainingGenerations.value--
|
||||
|
||||
appStore.showSnackbar(`콘텐츠 버전 ${generatedVersions.value.length}이 생성되었습니다!`, 'success')
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ [GENERATE] 콘텐츠 생성 실패:', error)
|
||||
appStore.showSnackbar(`콘텐츠 생성 중 오류가 발생했습니다: ${error.message}`, 'error')
|
||||
console.error('❌ [UI] 콘텐츠 생성 실패:', error)
|
||||
console.error('❌ [UI] 에러 스택:', error.stack)
|
||||
appStore.showSnackbar(error.message || '콘텐츠 생성 중 오류가 발생했습니다.', 'error')
|
||||
} finally {
|
||||
isGenerating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -1116,7 +1113,9 @@ const getCategory = (targetType) => {
|
||||
'new_menu': '메뉴소개',
|
||||
'discount': '이벤트',
|
||||
'store': '인테리어',
|
||||
'event': '이벤트'
|
||||
'event': '이벤트',
|
||||
'menu': '메뉴소개',
|
||||
'service': '서비스'
|
||||
}
|
||||
return mapping[targetType] || '기타'
|
||||
}
|
||||
@ -1132,37 +1131,151 @@ const saveVersion = async (index) => {
|
||||
try {
|
||||
const version = generatedVersions.value[index]
|
||||
|
||||
// contentStore.saveContent에 단일 파라미터로 전달
|
||||
const saveData = {
|
||||
type: version.type || version.contentType,
|
||||
contentType: version.contentType || version.type,
|
||||
title: version.title,
|
||||
content: version.content,
|
||||
hashtags: version.hashtags,
|
||||
platform: version.platform,
|
||||
category: getCategory(version.targetType),
|
||||
eventName: version.eventName,
|
||||
eventDate: version.eventDate,
|
||||
status: 'PUBLISHED',
|
||||
storeId: version.storeId
|
||||
console.log('💾 [UI] 저장할 버전 데이터:', version)
|
||||
|
||||
// ✅ 매장 ID 가져오기
|
||||
let storeId = 1 // 기본값
|
||||
|
||||
try {
|
||||
const storeInfo = JSON.parse(localStorage.getItem('storeInfo') || '{}')
|
||||
const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}')
|
||||
|
||||
if (storeInfo.storeId) {
|
||||
storeId = storeInfo.storeId
|
||||
} else if (userInfo.storeId) {
|
||||
storeId = userInfo.storeId
|
||||
} else {
|
||||
console.warn('⚠️ localStorage에서 매장 ID를 찾을 수 없음, 기본값 사용:', storeId)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ 매장 정보 파싱 실패, 기본값 사용:', storeId)
|
||||
}
|
||||
|
||||
const result = await contentStore.saveContent(saveData)
|
||||
console.log('🏪 [UI] 사용할 매장 ID:', storeId)
|
||||
|
||||
if (result.success) {
|
||||
version.status = 'published'
|
||||
version.publishedAt = new Date()
|
||||
// ✅ 이미지 데이터 준비
|
||||
let imageUrls = []
|
||||
|
||||
// 포스터의 경우 생성된 포스터 이미지 URL과 업로드된 이미지들을 포함
|
||||
if (selectedType.value === 'poster') {
|
||||
// 1. 생성된 포스터 이미지 URL 추가
|
||||
if (version.posterImage) {
|
||||
imageUrls.push(version.posterImage)
|
||||
console.log('💾 [UI] 생성된 포스터 이미지:', version.posterImage)
|
||||
}
|
||||
|
||||
appStore.showSnackbar(`버전 ${index + 1}이 성공적으로 저장되었습니다!`, 'success')
|
||||
// 2. previewImages에서 원본 이미지 URL 추가
|
||||
if (previewImages.value && previewImages.value.length > 0) {
|
||||
const originalImages = previewImages.value.map(img => img.url).filter(url => url)
|
||||
imageUrls = [...imageUrls, ...originalImages]
|
||||
console.log('💾 [UI] 원본 이미지들:', originalImages)
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (confirm('저장된 콘텐츠를 확인하시겠습니까?')) {
|
||||
router.push('/content')
|
||||
}
|
||||
}, 1000)
|
||||
// 3. version에 저장된 이미지도 확인
|
||||
if (version.uploadedImages && version.uploadedImages.length > 0) {
|
||||
const versionImages = version.uploadedImages.map(img => img.url).filter(url => url)
|
||||
imageUrls = [...imageUrls, ...versionImages]
|
||||
}
|
||||
|
||||
// 4. version.images도 확인
|
||||
if (version.images && Array.isArray(version.images) && version.images.length > 0) {
|
||||
imageUrls = [...imageUrls, ...version.images]
|
||||
}
|
||||
|
||||
// 중복 제거
|
||||
imageUrls = [...new Set(imageUrls)]
|
||||
|
||||
console.log('💾 [UI] 포스터 최종 이미지 URL들:', imageUrls)
|
||||
|
||||
// 이미지가 없으면 에러
|
||||
if (!imageUrls || imageUrls.length === 0) {
|
||||
throw new Error('포스터 저장을 위해 최소 1개의 이미지가 필요합니다.')
|
||||
}
|
||||
} else {
|
||||
throw new Error(result.error || '저장에 실패했습니다.')
|
||||
// SNS의 경우 선택적으로 이미지 포함
|
||||
if (previewImages.value && previewImages.value.length > 0) {
|
||||
imageUrls = previewImages.value.map(img => img.url).filter(url => url)
|
||||
}
|
||||
if (version.images && Array.isArray(version.images)) {
|
||||
imageUrls = [...new Set([...imageUrls, ...version.images])]
|
||||
}
|
||||
}
|
||||
|
||||
console.log('💾 [UI] 최종 이미지 URL들:', imageUrls)
|
||||
|
||||
// ✅ 저장 데이터 구성 - 타입에 따라 다르게 처리
|
||||
let saveData
|
||||
|
||||
if (selectedType.value === 'poster') {
|
||||
// 포스터용 데이터 구성 (PosterContentSaveRequest에 맞춤)
|
||||
saveData = {
|
||||
// 매장 ID
|
||||
storeId: storeId,
|
||||
|
||||
// 기본 콘텐츠 정보 - 포스터는 content에 이미지 URL 저장
|
||||
title: version.title,
|
||||
content: version.posterImage || version.content, // 포스터 이미지 URL을 content에 저장
|
||||
images: imageUrls, // 모든 관련 이미지들
|
||||
|
||||
// 분류 정보
|
||||
category: getCategory(version.targetType || formData.value.targetType),
|
||||
requirement: formData.value.requirements || `${version.title}에 대한 포스터를 만들어주세요`,
|
||||
|
||||
// 이벤트 정보
|
||||
eventName: version.eventName || formData.value.eventName,
|
||||
startDate: formData.value.startDate,
|
||||
endDate: formData.value.endDate,
|
||||
|
||||
// 스타일 정보
|
||||
photoStyle: formData.value.photoStyle || '밝고 화사한'
|
||||
}
|
||||
} else {
|
||||
// SNS용 데이터 구성 (SnsContentSaveRequest에 맞춤)
|
||||
saveData = {
|
||||
// 매장 ID
|
||||
storeId: storeId,
|
||||
|
||||
// 필수 필드들
|
||||
contentType: 'SNS',
|
||||
platform: version.platform || formData.value.platform || 'INSTAGRAM',
|
||||
|
||||
// 기본 콘텐츠 정보
|
||||
title: version.title,
|
||||
content: version.content,
|
||||
hashtags: version.hashtags || [],
|
||||
images: imageUrls,
|
||||
|
||||
// 분류 정보
|
||||
category: getCategory(version.targetType || formData.value.targetType),
|
||||
requirement: formData.value.requirements || `${version.title}에 대한 SNS 게시물을 만들어주세요`,
|
||||
toneAndManner: formData.value.toneAndManner || '친근함',
|
||||
emotionIntensity: formData.value.emotionIntensity || '보통',
|
||||
|
||||
// 이벤트 정보
|
||||
eventName: version.eventName || formData.value.eventName,
|
||||
startDate: formData.value.startDate,
|
||||
endDate: formData.value.endDate,
|
||||
|
||||
// 상태 정보
|
||||
status: 'PUBLISHED'
|
||||
}
|
||||
}
|
||||
|
||||
console.log('💾 [UI] 최종 저장 데이터:', saveData)
|
||||
|
||||
// ✅ 저장 실행
|
||||
await contentStore.saveContent(saveData)
|
||||
|
||||
version.status = 'published'
|
||||
version.publishedAt = new Date()
|
||||
|
||||
appStore.showSnackbar(`버전 ${index + 1}이 성공적으로 저장되었습니다!`, 'success')
|
||||
|
||||
setTimeout(() => {
|
||||
if (confirm('저장된 콘텐츠를 확인하시겠습니까?')) {
|
||||
router.push('/content')
|
||||
}
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
console.error('❌ 콘텐츠 저장 실패:', error)
|
||||
appStore.showSnackbar(error.message || '콘텐츠 저장 중 오류가 발생했습니다.', 'error')
|
||||
@ -1216,7 +1329,8 @@ const getPlatformIcon = (platform) => {
|
||||
'INSTAGRAM': 'mdi-instagram',
|
||||
'NAVER_BLOG': 'mdi-web',
|
||||
'FACEBOOK': 'mdi-facebook',
|
||||
'KAKAO_STORY': 'mdi-chat'
|
||||
'KAKAO_STORY': 'mdi-chat',
|
||||
'POSTER': 'mdi-image'
|
||||
}
|
||||
return icons[platform] || 'mdi-web'
|
||||
}
|
||||
@ -1230,7 +1344,8 @@ const getPlatformColor = (platform) => {
|
||||
'INSTAGRAM': 'pink',
|
||||
'NAVER_BLOG': 'green',
|
||||
'FACEBOOK': 'blue',
|
||||
'KAKAO_STORY': 'amber'
|
||||
'KAKAO_STORY': 'amber',
|
||||
'POSTER': 'orange'
|
||||
}
|
||||
return colors[platform] || 'grey'
|
||||
}
|
||||
@ -1244,90 +1359,12 @@ const getPlatformLabel = (platform) => {
|
||||
'INSTAGRAM': '인스타그램',
|
||||
'NAVER_BLOG': '네이버 블로그',
|
||||
'FACEBOOK': '페이스북',
|
||||
'KAKAO_STORY': '카카오스토리'
|
||||
'KAKAO_STORY': '카카오스토리',
|
||||
'POSTER': '포스터'
|
||||
}
|
||||
return labels[platform] || platform
|
||||
}
|
||||
|
||||
// ✅ Java 백엔드 형식 변환 함수들
|
||||
const convertTargetAudienceToKorean = (targetType) => {
|
||||
const mapping = {
|
||||
'menu': '메뉴',
|
||||
'store': '매장',
|
||||
'event': '이벤트',
|
||||
'service': '서비스',
|
||||
'discount': '할인혜택'
|
||||
}
|
||||
return mapping[targetType] || '기타'
|
||||
}
|
||||
|
||||
// ✅ Java 백엔드용 카테고리 변환 (정확한 값 사용)
|
||||
const getJavaCategory = (targetType) => {
|
||||
const mapping = {
|
||||
'menu': '메뉴소개',
|
||||
'store': '매장홍보',
|
||||
'event': '이벤트',
|
||||
'service': '서비스',
|
||||
'discount': '이벤트'
|
||||
}
|
||||
return mapping[targetType] || '이벤트'
|
||||
}
|
||||
|
||||
const convertCategoryToKorean = (category) => {
|
||||
const mapping = {
|
||||
'음식': '이벤트',
|
||||
'매장': '이벤트',
|
||||
'이벤트': '이벤트',
|
||||
'기타': '이벤트'
|
||||
}
|
||||
return mapping[category] || '이벤트'
|
||||
}
|
||||
|
||||
// ✅ 날짜를 YYYY-MM-DD 형식으로 엄격하게 변환
|
||||
const convertDateTimeToDateStrict = (dateTimeString) => {
|
||||
if (!dateTimeString) return undefined // null 대신 undefined 반환
|
||||
|
||||
try {
|
||||
let dateStr = dateTimeString
|
||||
|
||||
// "2025-06-19T09:58" -> "2025-06-19" 형식으로 변환
|
||||
if (dateTimeString.includes('T')) {
|
||||
dateStr = dateTimeString.split('T')[0]
|
||||
}
|
||||
|
||||
// YYYY-MM-DD 형식 검증
|
||||
const dateRegex = /^\d{4}-\d{2}-\d{2}$/
|
||||
if (!dateRegex.test(dateStr)) {
|
||||
console.warn('⚠️ 잘못된 날짜 형식:', dateTimeString)
|
||||
return undefined
|
||||
}
|
||||
|
||||
// 유효한 날짜인지 확인
|
||||
const date = new Date(dateStr)
|
||||
if (isNaN(date.getTime())) {
|
||||
console.warn('⚠️ 유효하지 않은 날짜:', dateStr)
|
||||
return undefined
|
||||
}
|
||||
|
||||
return dateStr
|
||||
} catch (error) {
|
||||
console.error('❌ 날짜 변환 오류:', error, dateTimeString)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
const convertDateTimeToDate = (dateTimeString) => {
|
||||
if (!dateTimeString) return null
|
||||
|
||||
// "2025-06-19T09:58" -> "2025-06-19" 형식으로 변환
|
||||
if (dateTimeString.includes('T')) {
|
||||
return dateTimeString.split('T')[0]
|
||||
}
|
||||
|
||||
// 이미 YYYY-MM-DD 형식인 경우 그대로 반환
|
||||
return dateTimeString
|
||||
}
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
'draft': 'grey',
|
||||
@ -1396,6 +1433,15 @@ const truncateHtmlContent = (html, maxLength) => {
|
||||
return `<div style="padding: 10px; font-family: 'Noto Sans KR', Arial, sans-serif;">${truncateText(textContent, maxLength)}</div>`
|
||||
}
|
||||
|
||||
const previewImage = (imageUrl, title) => {
|
||||
if (!imageUrl) return
|
||||
window.open(imageUrl, '_blank')
|
||||
}
|
||||
|
||||
const handleImageError = (event) => {
|
||||
console.error('❌ 이미지 로딩 실패:', event.target?.src)
|
||||
}
|
||||
|
||||
// 라이프사이클
|
||||
onMounted(() => {
|
||||
console.log('📱 콘텐츠 생성 페이지 로드됨')
|
||||
@ -1481,4 +1527,5 @@ onMounted(() => {
|
||||
background: linear-gradient(transparent, white);
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -152,8 +152,22 @@
|
||||
{{ getStatusText(content.status) }}
|
||||
</v-chip>
|
||||
</div>
|
||||
|
||||
<!-- ✅ 포스터인 경우와 SNS인 경우 구분하여 미리보기 표시 -->
|
||||
<div class="text-caption text-truncate grey--text" style="max-width: 400px;">
|
||||
{{ content.content ? content.content.substring(0, 100) + '...' : '' }}
|
||||
<div v-if="content.platform === 'POSTER' || content.platform === 'poster'">
|
||||
<div v-if="content.content && isImageUrl(content.content)" class="d-flex align-center">
|
||||
<v-icon size="16" color="primary" class="mr-1">mdi-image</v-icon>
|
||||
<span>포스터 이미지 생성됨</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ content.content ? content.content.substring(0, 100) + '...' : '포스터 콘텐츠' }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- ✅ SNS인 경우 HTML 태그 제거하고 텍스트만 표시 -->
|
||||
<div v-else>
|
||||
{{ content.content ? getPlainTextPreview(content.content) : '' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@ -272,9 +286,61 @@
|
||||
</v-chip>
|
||||
</div>
|
||||
|
||||
<!-- ✅ 포스터인 경우 이미지로 표시, SNS인 경우 HTML 렌더링 -->
|
||||
<div class="mb-4">
|
||||
<div class="text-subtitle-2 text-grey-600 mb-1">내용</div>
|
||||
<div class="text-body-1 content-preview">{{ selectedContent.content }}</div>
|
||||
|
||||
<!-- ✅ 포스터인 경우 이미지로 표시 -->
|
||||
<div v-if="selectedContent.platform === 'POSTER' || selectedContent.platform === 'poster'">
|
||||
<v-img
|
||||
v-if="selectedContent.content && isImageUrl(selectedContent.content)"
|
||||
:src="selectedContent.content"
|
||||
:alt="selectedContent.title"
|
||||
cover
|
||||
class="rounded-lg elevation-2"
|
||||
style="max-width: 400px; aspect-ratio: 3/4; cursor: pointer;"
|
||||
@click="previewImage(selectedContent.content, selectedContent.title)"
|
||||
@error="handleImageError"
|
||||
>
|
||||
<template v-slot:placeholder>
|
||||
<div class="d-flex align-center justify-center fill-height bg-grey-lighten-4">
|
||||
<v-progress-circular indeterminate color="primary" size="32" />
|
||||
<span class="ml-2 text-grey">이미지 로딩 중...</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:error>
|
||||
<div class="d-flex flex-column align-center justify-center fill-height bg-grey-lighten-3">
|
||||
<v-icon size="32" color="grey" class="mb-2">mdi-image-broken</v-icon>
|
||||
<span class="text-caption text-grey">이미지를 불러올 수 없습니다</span>
|
||||
<span class="text-caption text-grey mt-1" style="word-break: break-all; max-width: 300px;">
|
||||
{{ selectedContent.content?.substring(0, 50) }}...
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</v-img>
|
||||
|
||||
<div v-else class="d-flex flex-column align-center justify-center bg-grey-lighten-4 rounded-lg pa-8">
|
||||
<v-icon size="48" color="grey" class="mb-2">mdi-image-off</v-icon>
|
||||
<span class="text-body-2 text-grey">포스터 이미지가 없습니다</span>
|
||||
<span class="text-caption text-grey mt-1" v-if="selectedContent.content">
|
||||
URL: {{ selectedContent.content }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ SNS인 경우 HTML 렌더링으로 표시 -->
|
||||
<div v-else>
|
||||
<!-- HTML 콘텐츠가 있는 경우 렌더링하여 표시 -->
|
||||
<div v-if="isHtmlContent(selectedContent.content)"
|
||||
class="content-preview html-content"
|
||||
v-html="selectedContent.content">
|
||||
</div>
|
||||
<!-- 일반 텍스트인 경우 그대로 표시 -->
|
||||
<div v-else class="text-body-1 content-preview">
|
||||
{{ selectedContent.content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4" v-if="selectedContent.hashtags && selectedContent.hashtags.length > 0">
|
||||
@ -664,7 +730,78 @@ const deleteSelectedItems = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 유틸리티 함수들
|
||||
// ✅ 유틸리티 함수들 추가
|
||||
/**
|
||||
* URL이 이미지 URL인지 확인
|
||||
*/
|
||||
const isImageUrl = (url) => {
|
||||
if (!url || typeof url !== 'string') return false
|
||||
|
||||
// 이미지 확장자 체크
|
||||
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg']
|
||||
const lowerUrl = url.toLowerCase()
|
||||
|
||||
// 확장자로 체크
|
||||
if (imageExtensions.some(ext => lowerUrl.includes(ext))) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Blob URL 체크
|
||||
if (url.startsWith('blob:') || url.startsWith('data:image/')) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Azure Blob Storage나 기타 클라우드 스토리지 URL 체크
|
||||
if (url.includes('blob.core.windows.net') ||
|
||||
url.includes('amazonaws.com') ||
|
||||
url.includes('googleusercontent.com') ||
|
||||
url.includes('cloudinary.com')) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ HTML 콘텐츠인지 확인
|
||||
*/
|
||||
const isHtmlContent = (content) => {
|
||||
if (!content || typeof content !== 'string') return false
|
||||
|
||||
// HTML 태그가 포함되어 있는지 확인
|
||||
const htmlTagRegex = /<[^>]+>/g
|
||||
return htmlTagRegex.test(content)
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ HTML 태그를 제거하고 텍스트만 추출하여 미리보기용으로 반환
|
||||
*/
|
||||
const getPlainTextPreview = (content) => {
|
||||
if (!content) return ''
|
||||
|
||||
// HTML 태그 제거
|
||||
const textContent = content.replace(/<[^>]*>/g, '').trim()
|
||||
|
||||
// 100자로 제한하고 ... 추가
|
||||
return textContent.length > 100 ? textContent.substring(0, 100) + '...' : textContent
|
||||
}
|
||||
|
||||
/**
|
||||
* 이미지 미리보기
|
||||
*/
|
||||
const previewImage = (imageUrl, title) => {
|
||||
if (!imageUrl) return
|
||||
window.open(imageUrl, '_blank')
|
||||
}
|
||||
|
||||
/**
|
||||
* 이미지 로딩 에러 처리
|
||||
*/
|
||||
const handleImageError = (event) => {
|
||||
console.error('❌ 이미지 로딩 실패:', event.target?.src)
|
||||
}
|
||||
|
||||
// 기존 유틸리티 함수들
|
||||
const getStatusColor = (status) => {
|
||||
const statusColors = {
|
||||
'DRAFT': 'orange',
|
||||
@ -783,4 +920,61 @@ onMounted(() => {
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ✅ HTML 콘텐츠 스타일링 */
|
||||
:deep(.html-content) {
|
||||
font-family: 'Noto Sans KR', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
padding: 20px;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
:deep(.html-content h1),
|
||||
:deep(.html-content h2),
|
||||
:deep(.html-content h3) {
|
||||
margin: 0 0 10px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
:deep(.html-content h3) {
|
||||
font-size: 18px;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
:deep(.html-content p) {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
:deep(.html-content div[style*="background"]) {
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
:deep(.html-content div[style*="border"]) {
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #e1e8ed;
|
||||
}
|
||||
|
||||
:deep(.html-content img) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
:deep(.html-content span[style*="#1DA1F2"]) {
|
||||
color: #1976d2 !important;
|
||||
}
|
||||
|
||||
:deep(.html-content span[style*="#1EC800"]) {
|
||||
color: #4caf50 !important;
|
||||
}
|
||||
|
||||
:deep(.html-content span[style*="#00B33C"]) {
|
||||
color: #2e7d32 !important;
|
||||
}
|
||||
</style>
|
||||
@ -252,12 +252,7 @@
|
||||
<div v-else-if="aiRecommendation" class="ai-recommendation-content">
|
||||
<!-- 추천 제목 -->
|
||||
<div class="recommendation-header mb-4">
|
||||
<div class="d-flex align-center mb-2">
|
||||
<span class="recommendation-emoji mr-2">{{ aiRecommendation.emoji }}</span>
|
||||
<h4 class="text-h6 font-weight-bold text-primary">
|
||||
{{ aiRecommendation.title }}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 스크롤 가능한 콘텐츠 영역 -->
|
||||
|
||||
@ -167,7 +167,7 @@ const generatePoster = async (formData) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 포스터 저장
|
||||
* 포스터 저장 - 수정된 버전
|
||||
*/
|
||||
const savePoster = async () => {
|
||||
if (!generatedPoster.value) {
|
||||
@ -181,12 +181,22 @@ const savePoster = async () => {
|
||||
loadingMessage.value = '포스터 저장 중...'
|
||||
loadingSubMessage.value = '잠시만 기다려주세요'
|
||||
|
||||
// ✅ 생성된 포스터의 실제 데이터를 사용하고 contentId 처리 개선
|
||||
const result = await posterStore.savePoster({
|
||||
contentId: generatedPoster.value.contentId,
|
||||
// ✅ contentId: 임시 ID 사용 (백엔드에서 @NotNull이므로)
|
||||
contentId: generatedPoster.value.contentId || Date.now(), // 임시 ID 생성
|
||||
storeId: authStore.currentStore?.id || 1,
|
||||
title: posterForm.value.title,
|
||||
content: generatedPoster.value.content,
|
||||
images: [generatedPoster.value.posterImage],
|
||||
|
||||
// ✅ 생성된 포스터의 실제 콘텐츠 정보 사용
|
||||
content: generatedPoster.value.content || generatedPoster.value.description || posterForm.value.title,
|
||||
|
||||
// ✅ 원본 이미지와 생성된 포스터 이미지 모두 포함
|
||||
images: [
|
||||
...posterForm.value.images, // 원본 업로드 이미지들
|
||||
generatedPoster.value.posterImage || generatedPoster.value.imageUrl // 생성된 포스터 이미지
|
||||
].filter(img => img), // null/undefined 제거
|
||||
|
||||
status: 'PUBLISHED',
|
||||
category: posterForm.value.category,
|
||||
requirement: posterForm.value.requirement,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user