diff --git a/src/views/akopolio/Edit/edit.js b/src/views/akopolio/Edit/edit.js index dd08647..3a5437b 100644 --- a/src/views/akopolio/Edit/edit.js +++ b/src/views/akopolio/Edit/edit.js @@ -4,25 +4,54 @@ import { ref, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; import axios from 'axios'; +const nickname = ref(''); + +const fetchUserData = async () => { + try { + const response = await fetch( + `${process.env.VUE_APP_BE_API_URL}/api/users/profile`, + { + method: 'GET', + credentials: 'include', + } + ); + + if (response.ok) { + const data = await response.json(); + nickname.value = data.nickname; + } else { + console.error('사용자 정보 불러오기 실패:', response.status, response.statusText); + if (response.status === 401) { + alert('인증에 실패했습니다. 다시 로그인해주세요.'); + window.location.href = '/login'; + } else { + alert('사용자 정보 가져오기 실패. 다시 시도해주세요.'); + } + } + } catch (error) { + console.error('사용자 정보 가져오기 오류:', error); + alert('사용자 정보 가져오기 오류가 발생했습니다.'); + } +}; + export default { components: { MainHeader, - MainFooter + MainFooter, }, setup() { const route = useRoute(); + const portfolio = ref(null); const portfolioId = route.params.id; const activityName = ref(''); const activityDate = ref(''); - const selectedTags = ref([]); + const selectedTags = ref([]); // 초기값 배열로 설정 const tags = ref([ '전공', '교양', '교내동아리', '교외동아리', '학회', '봉사활동', '인턴', '아르바이트', '대외활동', '서포터즈', '기자단', '강연/행사', '스터디', '부트캠프', '프로젝트', '연구', '학생회', '기타', ]); - const images = ref([ - { previewUrl: "https://via.placeholder.com/150" } // 예시 이미지 - ]); + const images = ref([]); // 초기값 배열로 설정 const star = ref({ situation: '', task: '', @@ -36,12 +65,10 @@ export default { }); const isDropdownOpen = ref(false); - // 드롭다운 토글 const toggleDropdown = () => { isDropdownOpen.value = !isDropdownOpen.value; }; - // 태그 선택/해제 const toggleTag = (tag) => { const index = selectedTags.value.indexOf(tag); if (index > -1) { @@ -51,54 +78,61 @@ export default { } }; - // 텍스트 영역 자동 크기 조정 const autoResize = (event) => { const textarea = event.target; textarea.style.height = 'auto'; textarea.style.height = `${textarea.scrollHeight}px`; }; - // 포트폴리오 데이터 가져오기 - const fetchPortfolioById = async () => { + const fetchPortfolioById = async (id) => { try { - const response = await fetch( - `${process.env.VUE_APP_BE_API_URL}/api/portfolios/${portfolioId}` - ); + const apiUrl = `${process.env.VUE_APP_BE_API_URL}/api/portfolios/${id}`; + console.log('Fetching portfolio from URL:', apiUrl); + + const response = await fetch(apiUrl); + console.log('Response status:', response.status); if (!response.ok) { + console.error(`Response status: ${response.status}, ${response.statusText}`); throw new Error(`Failed to fetch portfolio: ${response.statusText}`); } const data = await response.json(); - activityName.value = data.activityName || ''; - activityDate.value = data.activityDate || ''; - selectedTags.value = data.selectedTags || []; - star.value = data.star || { situation: '', task: '', action: '', result: '' }; - pmi.value = data.pmi || { plus: '', minus: '', interesting: '' }; - images.value = data.images || []; + console.log('Received portfolio data:', data); + + portfolio.value = { + baseInfo: data.baseInfo || {}, + experience: data.experience || { situation: '', task: '', action: '', result: '' }, + pmi: data.pmi || { plus: '', minus: '', interesting: '' }, + photoUrls: data.photoUrls || [], + }; + + images.value = portfolio.value.photoUrls || []; } catch (error) { console.error('Error fetching portfolio:', error); - alert('포트폴리오를 불러오는 중 오류가 발생했습니다.'); + alert('포트폴리오를 가져오는 중 오류가 발생했습니다.'); + portfolio.value = null; + images.value = []; } }; const handleFileChange = (event) => { const selectedFiles = Array.from(event.target.files); - if (images.value.length + selectedFiles.length > 5) { + if (Array.isArray(images.value) && images.value.length + selectedFiles.length > 5) { alert('최대 5개의 이미지만 업로드할 수 있습니다.'); return; } - + const newImagesPromises = selectedFiles.map((file) => { const previewUrl = URL.createObjectURL(file); const imageElement = new Image(); imageElement.src = previewUrl; - + return new Promise((resolve) => { imageElement.onload = () => { const aspectRatio = imageElement.width / imageElement.height; let containerWidth, containerHeight; - + if (aspectRatio > 1) { containerWidth = '300px'; containerHeight = `${300 / aspectRatio}px`; @@ -106,7 +140,7 @@ export default { containerWidth = `${300 * aspectRatio}px`; containerHeight = '300px'; } - + resolve({ file, name: file.name, @@ -118,7 +152,7 @@ export default { }; }); }); - + Promise.all(newImagesPromises).then((newImages) => { const uniqueNewImages = newImages.filter( (newImage) => @@ -126,80 +160,98 @@ export default { (existingImage) => existingImage.previewUrl === newImage.previewUrl ) ); - + if (uniqueNewImages.length === 0) { alert('이미 선택된 이미지입니다.'); return; } - + images.value = [...images.value, ...uniqueNewImages]; }); }; - + const removeImage = (index) => { URL.revokeObjectURL(images.value[index].previewUrl); images.value.splice(index, 1); }; - - const prefix = 'images'; + const uploadImages = async () => { const uploadedUrls = []; - for (const image of images.value) { - try { - const { data } = await axios.post( - `${process.env.VUE_APP_BE_API_URL}/file/${prefix}/presigned-url`, - { - fileName: image.name, - fileType: image.file.type, - } - ); - - await axios.put(data.url, image.file, { - headers: { 'Content-Type': image.file.type }, + + try { + console.log("Starting image upload..."); + + for (const image of images.value) { + const apiUrl = `${process.env.VUE_APP_BE_API_URL}/file/IMAGE/presigned-url`; + console.log('Requesting presigned URL from:', apiUrl); + + const response = await axios.post(apiUrl, { + imageName: image.file.name, + }); + + const presignedUrl = response.data; + console.log("Received presigned URL:", presignedUrl); + + await axios.put(presignedUrl, image.file, { + headers: { + "Content-Type": image.file.type, + }, }); - - const uploadedUrl = data.url.split('?')[0]; + + console.log("Image uploaded successfully!"); + + const uploadedUrl = presignedUrl.split("?")[0]; uploadedUrls.push(uploadedUrl); - } catch (error) { - console.error('Error uploading image:', error); - alert('이미지 업로드 중 오류가 발생했습니다.'); + console.log("Uploaded image URL:", uploadedUrl); } + + console.log("Uploaded image URLs:", uploadedUrls); + + } catch (error) { + console.error("Error uploading image:", error); + alert("이미지 업로드 중 오류가 발생했습니다."); } }; - - // 저장 버튼 클릭 시 서버에 반영 - const saveData = async () => { + const saveData = async (id) => { if (!isFormComplete.value) { alert('모든 필드를 입력해주세요.'); return; } try { - // 수정된 포트폴리오 데이터와 이미지 URL을 서버에 전송 - const response = await fetch( - `${process.env.VUE_APP_BE_API_URL}/api/portfolios/${portfolioId}`, + const response = await axios.put( + `${process.env.VUE_APP_BE_API_URL}/api/portfolios/${id}`, + { + name: activityName.value, + startDate: activityDate.value, + tags: selectedTags.value, + experience: { + situation: star.value.situation, + task: star.value.task, + action: star.value.action, + result: star.value.result, + }, + pmi: { + plus: pmi.value.plus, + minus: pmi.value.minus, + interesting: pmi.value.interesting, + }, + photoUrls: images.value.map(image => image.name), + }, { - method: 'PUT', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ - activityName: activityName.value, - activityDate: activityDate.value, - selectedTags: selectedTags.value, - star: star.value, - pmi: pmi.value, - images: images.value.map(image => image.name), // 서버에 전달할 이미지 파일 이름만 전달 - }), } ); - if (!response.ok) { - throw new Error(`Failed to save data: ${response.statusText}`); + if (response.status === 200) { + alert('데이터가 성공적으로 저장되었습니다.'); + } else { + alert('저장에 실패했습니다. 다시 시도해주세요.'); } - alert('데이터가 성공적으로 저장되었습니다.'); } catch (error) { console.error('Error saving data:', error); alert('데이터 저장 중 오류가 발생했습니다.'); @@ -210,34 +262,37 @@ export default { return ( activityName.value && activityDate.value && - selectedTags.value.length > 0 && + Array.isArray(selectedTags.value) && selectedTags.value.length > 0 && Object.values(star.value).every((field) => field) && Object.values(pmi.value).every((field) => field) ); }); - // 컴포넌트 마운트 시 데이터 가져오기 - onMounted(() => { - fetchPortfolioById(); + onMounted(async () => { + images.value = []; + selectedTags.value = []; + await fetchUserData(); + await fetchPortfolioById(portfolioId); }); return { + portfolio, activityName, activityDate, - tags, selectedTags, - isDropdownOpen, + tags, + images, star, pmi, - images, + isDropdownOpen, toggleDropdown, toggleTag, autoResize, - handleFileChange, - removeImage, - uploadImages, + handleFileChange, + removeImage, + uploadImages, saveData, - isFormComplete + isFormComplete, }; - } + }, }; diff --git a/src/views/akopolio/create/create.js b/src/views/akopolio/create/create.js index 1897d31..a9e47ac 100644 --- a/src/views/akopolio/create/create.js +++ b/src/views/akopolio/create/create.js @@ -1,6 +1,7 @@ import axios from 'axios'; import MainHeader from '../../../components/layout/Header.vue'; import MainFooter from '../../../components/layout/Footer.vue'; +import { ref, computed, onMounted } from 'vue' export default { components: { @@ -31,7 +32,9 @@ export default { }, tooltipVisible: false, images: [], // 선택된 이미지 파일 배열 - uploadedImageUrls: [] // 업로드된 이미지의 URL 저장 + uploadedImageUrls: [], // 업로드된 이미지의 URL 저장 + portfolios: [], + uploadError: false, }; }, computed: { @@ -46,152 +49,228 @@ export default { } }, methods: { - toggleDropdown() { - this.isDropdownOpen = !this.isDropdownOpen; - }, - toggleTag(tag) { - const index = this.selectedTags.indexOf(tag); - if (index > -1) { - this.selectedTags.splice(index, 1); - } else { - this.selectedTags.push(tag); - } - }, - autoResize(event) { - const textarea = event.target; - textarea.style.height = 'auto'; - textarea.style.height = `${textarea.scrollHeight}px`; - }, - handleFileChange(event) { - const selectedFiles = Array.from(event.target.files); + async fetchUserData() { + try { + const response = await fetch( + `${process.env.VUE_APP_BE_API_URL}/api/users/profile`, + { + method: 'GET', + credentials: 'include', + } + ); + + console.log('서버 응답 상태:', response.status, response.statusText); + + if (response.ok) { + const data = await response.json(); + this.nickname = data.nickname; // nickname을 data 안으로 변경 + console.log('사용자 정보:', data); + } else { + console.error('사용자 정보 불러오기 실패:', response.status, response.statusText); + if (response.status === 401) { + alert('인증에 실패했습니다. 다시 로그인해주세요.'); + window.location.href = '/login'; + } else { + alert('사용자 정보 가져오기 실패. 다시 시도해주세요.'); + } + } + } catch (error) { + console.error('사용자 정보 가져오기 오류:', error); + alert('사용자 정보 가져오기 오류가 발생했습니다.'); + } + }, - if (this.images.length + selectedFiles.length > 5) { - alert('최대 5개의 이미지만 업로드할 수 있습니다.'); - return; - } + toggleDropdown() { + this.isDropdownOpen = !this.isDropdownOpen; + }, + toggleTag(tag) { + const index = this.selectedTags.indexOf(tag); + if (index > -1) { + this.selectedTags.splice(index, 1); + } else { + this.selectedTags.push(tag); + } + }, + autoResize(event) { + const textarea = event.target; + textarea.style.height = 'auto'; + textarea.style.height = `${textarea.scrollHeight}px`; + }, + handleFileChange(event) { + const selectedFiles = Array.from(event.target.files); - const newImagesPromises = selectedFiles.map(file => { - const previewUrl = URL.createObjectURL(file); - const imageElement = new Image(); - imageElement.src = previewUrl; + if (this.images.length + selectedFiles.length > 5) { + alert('최대 5개의 이미지만 업로드할 수 있습니다.'); + return; + } - return new Promise(resolve => { - imageElement.onload = () => { - const aspectRatio = imageElement.width / imageElement.height; - let containerWidth, containerHeight; + const newImagesPromises = selectedFiles.map(file => { + const previewUrl = URL.createObjectURL(file); + const imageElement = new Image(); + imageElement.src = previewUrl; - if (aspectRatio > 1) { - containerWidth = '300px'; - containerHeight = `${300 / aspectRatio}px`; - } else { - containerWidth = `${300 * aspectRatio}px`; - containerHeight = '300px'; - } + return new Promise(resolve => { + imageElement.onload = () => { + const aspectRatio = imageElement.width / imageElement.height; + let containerWidth, containerHeight; - resolve({ - file, - name: file.name, - size: file.size, - previewUrl, - containerWidth, - containerHeight + if (aspectRatio > 1) { + containerWidth = '300px'; + containerHeight = `${300 / aspectRatio}px`; + } else { + containerWidth = `${300 * aspectRatio}px`; + containerHeight = '300px'; + } + + resolve({ + file, + name: file.name, + size: file.size, + previewUrl, + containerWidth, + containerHeight + }); + }; }); - }; - }); - }); + }); - Promise.all(newImagesPromises).then(newImages => { - const uniqueNewImages = newImages.filter(newImage => - !this.images.some(existingImage => existingImage.previewUrl === newImage.previewUrl) - ); + Promise.all(newImagesPromises).then(newImages => { + const uniqueNewImages = newImages.filter(newImage => + !this.images.some(existingImage => existingImage.previewUrl === newImage.previewUrl) + ); - if (uniqueNewImages.length === 0) { - alert('이미 선택된 이미지입니다.'); - return; - } + if (uniqueNewImages.length === 0) { + alert('이미 선택된 이미지입니다.'); + return; + } + + this.images = [...this.images, ...uniqueNewImages]; + }); + }, + removeImage(index) { + // 이미지 삭제 시 미리보기 URL 해제 + URL.revokeObjectURL(this.images[index].previewUrl); + this.images.splice(index, 1); + }, - this.images = [...this.images, ...uniqueNewImages]; - }); -}, - removeImage(index) { - // 이미지 삭제 시 미리보기 URL 해제 - URL.revokeObjectURL(this.images[index].previewUrl); - this.images.splice(index, 1); - }, async uploadImages() { - const prefix = 'images'; const uploadedUrls = []; - + + this.uploadError = false; + + console.log("Starting image upload..."); + for (const image of this.images) { try { - // 1. 프리사인드 URL 요청 - - const { data } = await axios.post( - `${process.env.VUE_APP_BE_API_URL}/file/${prefix}/presigned-url`, - { - fileName: image.name, - fileType: image.type, - } - ); + const apiUrl = `${process.env.VUE_APP_BE_API_URL}/file/IMAGE/presigned-url`; + console.log('Requesting presigned URL from:', apiUrl); - // 2. 프리사인드 URL을 이용해 이미지 업로드 - - await axios.put(data.url, image.file, { - headers: { 'Content-Type': image.file.type }, + // 프리사인드 URL 요청 + const response = await axios.post(apiUrl, { + imageName: image.file.name, // 파일 이름 + }); + + // 백엔드가 반환한 프리사인드 URL + const presignedUrl = response.data; // 데이터가 문자열 URL이라고 가정 + + console.log("Received presigned URL:", presignedUrl); + + // S3에 파일 업로드 + await axios.put(presignedUrl, image.file, { + headers: { + "Content-Type": image.file.type, // 파일 MIME 타입 설정 + }, }); - - // 3. 업로드된 이미지의 URL을 저장 (프리사인드 URL에서 파일 URL을 추출) - - const uploadedUrl = data.url.split('?')[0]; + console.log("Image uploaded successfully!"); + + // 업로드된 이미지 URL 저장 (프리사인드 URL의 base URL만 추출) + const uploadedUrl = presignedUrl.split("?")[0]; // URL에서 쿼리 파라미터 제거 uploadedUrls.push(uploadedUrl); - + console.log("Uploaded image URL:", uploadedUrl); + } catch (error) { - console.error('Error uploading image:', error); - alert('이미지 업로드 중 오류가 발생했습니다.'); + console.error("Error uploading image:", error); + this.uploadError = true; + alert("이미지 업로드 중 오류가 발생했습니다."); + return; } } - + this.uploadedImageUrls = uploadedUrls; - }, - + console.log("Uploaded image URLs:", this.uploadedImageUrls); + }, + async saveData() { if (!this.isFormComplete) { alert('모든 필드를 입력해주세요.'); return; } + // 이미지가 선택된 경우 업로드 진행 if (this.images.length > 0) { await this.uploadImages(); } + // 이미지 업로드 중 오류가 발생한 경우 saveData 종료 + if (this.uploadError) { + return; // 오류 발생 시 저장을 진행하지 않음 + } + // 백엔드 연동을 위한 데이터 객체 const newPortfolio = { - title: this.activityName, - createdDate: this.activityDate, - tags: this.selectedTags, - star: { ...this.star }, - pmi: { ...this.pmi }, - images: this.uploadedImageUrls // 업로드된 이미지 URL을 포함 + name: this.activityName, // 활동명 + startDate: this.activityDate, // 활동 시작일 + tags: this.selectedTags, // 선택된 태그들 + experience: { // STAR 메서드 적용 + situation: this.star.situation, + task: this.star.task, + action: this.star.action, + result: this.star.result, + }, + pmi: { // PMI 메서드 적용 + plus: this.pmi.plus, + minus: this.pmi.minus, + interesting: this.pmi.interesting, + }, + photoUrls: this.uploadedImageUrls // 업로드된 이미지 URL 배열 }; try { - await axios.post( - `${process.env.VUE_APP_BE_API_URL}/api/portfolios`, + // 포트폴리오 저장 요청 + const response = await axios.post( + `${process.env.VUE_APP_BE_API_URL}/api/portfolios`, newPortfolio ); + // 서버 응답 내용 출력 + console.log('POST Response:', response.data); // 응답 확인 alert('활동이 성공적으로 저장되었습니다!'); + // 포트폴리오 목록을 다시 불러옴 + await this.fetchPortfolios(); // 포트폴리오 목록을 다시 가져와서 업데이트 + // 저장 후 페이지 이동 (예: 메인 페이지) this.$router.push('/akopolio/main'); } catch (error) { - console.error('Error saving portfolio:', error); + console.error('Error saving portfolio:', error); // 에러 로그 출력 alert('저장 중 오류가 발생했습니다.'); } }, - + // 새로운 fetchPortfolios 메서드 추가 + async fetchPortfolios() { + try { + const response = await axios.get( + `${process.env.VUE_APP_BE_API_URL}/api/portfolios`, + { credentials: 'include' } + ); + this.portfolios = response.data; + } catch (error) { + console.error('포트폴리오 목록 불러오기 오류:', error); + alert('포트폴리오 목록을 불러오는 중 오류가 발생했습니다.'); + } + }, resetForm() { this.activityName = ''; this.activityDate = ''; @@ -201,5 +280,9 @@ export default { this.images = []; this.uploadedImageUrls = []; } + }, + + mounted() { + this.fetchUserData(); } -}; \ No newline at end of file +}; diff --git a/src/views/akopolio/detail/akopolioDetail.vue b/src/views/akopolio/detail/akopolioDetail.vue index e7613fa..e53ee7d 100644 --- a/src/views/akopolio/detail/akopolioDetail.vue +++ b/src/views/akopolio/detail/akopolioDetail.vue @@ -72,7 +72,7 @@
활동일: {{ item.createdDate }}
+활동일: {{ item.startDate }}