
--- client/resources/api/asset.js
+++ client/resources/api/asset.js
... | ... | @@ -18,4 +18,9 @@ |
18 | 18 |
// 자산(프로젝트) 즐겨찾기 설정 기능 |
19 | 19 |
export const updateProjectFavoriteProc = (projectGroupId, data) => { |
20 | 20 |
return apiClient.post(`/project/${projectGroupId}/updateProjectFavorite.json`, data); |
21 |
-}(파일 끝에 줄바꿈 문자 없음) |
|
21 |
+} |
|
22 |
+ |
|
23 |
+// 자산(프로젝트) 이름 변경 기능 |
|
24 |
+export const renameProjectProc = (projectId, data) => { |
|
25 |
+ return apiClient.post(`/project/${projectId}/renameProject.json`, data); |
|
26 |
+} |
--- client/views/pages/subPage/Asset.vue
+++ client/views/pages/subPage/Asset.vue
... | ... | @@ -54,19 +54,18 @@ |
54 | 54 |
<!-- 드롭다운 메뉴 표시 --> |
55 | 55 |
<div v-if="openMenuIndex === index" class="dropdown-menu" :class="dropdownDirection"> |
56 | 56 |
<button><img src="../../../resources/img/content/ico_subShare.svg" alt=""> 공유</button> |
57 |
- <button><img src="../../../resources/img/content/ico_subCopy.svg" alt="" @click="duplicateProject(project.projectId)"> 복사</button> |
|
58 |
- <button><img src="../../../resources/img/content/ico_trashcan.svg" alt="" @click="deleteProject(project.projectGroupId)"> 삭제</button> |
|
59 |
- <button><img src="../../../resources/img/content/ico_version.svg" alt=""> 버전기록</button> |
|
60 |
- <!-- @click="changeProjectName --> |
|
61 |
- <button><img src="" alt="" > 이름 변경</button> |
|
57 |
+ <button @click="duplicateProject(project.projectId)"><img src="../../../resources/img/content/ico_subCopy.svg" alt=""> 복사</button> |
|
58 |
+ <button @click="deleteProject(project.projectGroupId)"><img src="../../../resources/img/content/ico_trashcan.svg" alt=""> 삭제</button> |
|
59 |
+ <button @click="openPopup('versions', project)"><img src="../../../resources/img/content/ico_version.svg" alt=""> 버전기록</button> |
|
60 |
+ <button @click="openPopup('rename', project)"><img src="" alt=""> 이름 변경</button> |
|
62 | 61 |
</div> |
63 | 62 |
</div> |
64 | 63 |
</div> |
65 | 64 |
<div class="old-projects"> |
66 |
- <!-- 팝업 창 --> |
|
67 |
- <div v-if="showPopup" class="popup-overlay" @click.self="showPopup = false"> |
|
65 |
+ <!-- 이전 버전 팝업 창 --> |
|
66 |
+ <div v-if="activePopup === 'versions'" class="popup-overlay" @click.self="closePopup()"> |
|
68 | 67 |
<div class="popup"> |
69 |
- <button class="close-btn" @click="showPopup = false">X</button> |
|
68 |
+ <button class="close-btn" @click="closePopup()">X</button> |
|
70 | 69 |
<!-- 여기에 내용 넣기 --> |
71 | 70 |
<h3>작업내역</h3> |
72 | 71 |
<img src="../../../resources/img/content/ico_digital_asset.svg" alt="작업 이미지" /> |
... | ... | @@ -81,12 +80,28 @@ |
81 | 80 |
</div> |
82 | 81 |
</div> |
83 | 82 |
</div> |
83 |
+ |
|
84 |
+ <div class="change-project-name"> |
|
85 |
+ <!-- 이름 변경 팝업 창 --> |
|
86 |
+ <div v-if="activePopup === 'rename'" class="popup-overlay" @click.self="closePopup()"> |
|
87 |
+ <div class="popup"> |
|
88 |
+ <h3>이름 변경</h3> |
|
89 |
+ <div class="input-wrapper"> |
|
90 |
+ <input type="text" class="form-control sm" style="border: 0;" v-model="newProjectName" @keyup.enter="changeProjectName(selectedProject.id)"> |
|
91 |
+ </div> |
|
92 |
+ <div class="button-wrapper"> |
|
93 |
+ <button @click="closePopup()">취소</button> |
|
94 |
+ <button @click="changeProjectName(selectedProject.id)">저장</button> |
|
95 |
+ </div> |
|
96 |
+ </div> |
|
97 |
+ </div> |
|
98 |
+ </div> |
|
84 | 99 |
</div> |
85 | 100 |
</template> |
86 | 101 |
|
87 | 102 |
<script> |
88 | 103 |
import { mapGetters } from 'vuex'; |
89 |
-import { deleteProjectProc, findAllProjectsProc, duplicateProjectProc, updateProjectFavoriteProc } from '../../../resources/api/asset'; |
|
104 |
+import { deleteProjectProc, findAllProjectsProc, duplicateProjectProc, updateProjectFavoriteProc, renameProjectProc } from '../../../resources/api/asset'; |
|
90 | 105 |
|
91 | 106 |
export default { |
92 | 107 |
data() { |
... | ... | @@ -101,14 +116,16 @@ |
101 | 116 |
members: [], |
102 | 117 |
openMenuIndex: null, |
103 | 118 |
dropdownDirection: 'right', // 또는 'left' |
104 |
- // showPopup: false, |
|
119 |
+ activePopup: null, // 실행 중인 팝업창(이전 버전, 이름 변경) |
|
105 | 120 |
keyword: '', // 검색 키워드 |
106 | 121 |
selectedSort: 'LATEST', // 프로젝트 목록 정렬 기준 선택 (기본은 '최근 수정순') |
107 | 122 |
sortOptions: [ // 프로젝트 목록 정렬 기준 |
108 | 123 |
{ value: 'LATEST', text: '최근 수정순' }, |
109 | 124 |
{ value: 'OLDEST', text: '오래된 순' }, |
110 | 125 |
{ value: 'NAME_ASC', text: '이름순' }, |
111 |
- ] |
|
126 |
+ ], |
|
127 |
+ newProjectName: '', // 변경할 프로젝트 이름 |
|
128 |
+ selectedProject: null, // 선택된 프로젝트 |
|
112 | 129 |
|
113 | 130 |
}; |
114 | 131 |
}, |
... | ... | @@ -221,10 +238,17 @@ |
221 | 238 |
console.error('복제 실패: ', error); |
222 | 239 |
}) |
223 | 240 |
}, |
224 |
- // showPopupFunc(){ |
|
225 |
- // console.log("팝업창 열기"); |
|
226 |
- // this.showPopup = true; |
|
227 |
- // }, |
|
241 |
+ // 팝업창 열기 |
|
242 |
+ openPopup(popupType, project){ |
|
243 |
+ this.activePopup = popupType; |
|
244 |
+ this.newProjectName = project.name; |
|
245 |
+ this.selectedProject = project; |
|
246 |
+ }, |
|
247 |
+ // 팝업창 닫기 |
|
248 |
+ closePopup(){ |
|
249 |
+ console.log('팝업창 닫기: '); |
|
250 |
+ this.activePopup = null; |
|
251 |
+ }, |
|
228 | 252 |
// 프로젝트 삭제 |
229 | 253 |
deleteProject(projectGroupId) { |
230 | 254 |
const data = { |
... | ... | @@ -253,6 +277,23 @@ |
253 | 277 |
console.error('즐겨찾기 설정 실패: ', error); |
254 | 278 |
}) |
255 | 279 |
}, |
280 |
+ // 프로젝트 이름 변경 |
|
281 |
+ changeProjectName(projectId) { |
|
282 |
+ |
|
283 |
+ const data = { |
|
284 |
+ memberId : this.getMemId, |
|
285 |
+ projectName : this.newProjectName |
|
286 |
+ } |
|
287 |
+ renameProjectProc(projectId, data) |
|
288 |
+ .then((response) => { |
|
289 |
+ console.log('이름 변경'); |
|
290 |
+ this.closePopup(); |
|
291 |
+ this.loadMainProjects(); // 이름 변경 후 목록 새로고침 |
|
292 |
+ }) |
|
293 |
+ .catch((error) => { |
|
294 |
+ console.error('프로젝트 이름 변경 실패: '+ error); |
|
295 |
+ }) |
|
296 |
+ } |
|
256 | 297 |
|
257 | 298 |
}, |
258 | 299 |
watch: { |
... | ... | @@ -279,7 +320,7 @@ |
279 | 320 |
|
280 | 321 |
</script> |
281 | 322 |
|
282 |
-<!-- <style scoped> |
|
323 |
+<style scoped> |
|
283 | 324 |
.popup-overlay { |
284 | 325 |
position: fixed; |
285 | 326 |
top: 0; |
... | ... | @@ -311,4 +352,18 @@ |
311 | 352 |
font-size: 18px; |
312 | 353 |
cursor: pointer; |
313 | 354 |
} |
314 |
-</style> -->(파일 끝에 줄바꿈 문자 없음) |
|
355 |
+ |
|
356 |
+.button-wrapper { |
|
357 |
+ display: flex; |
|
358 |
+ justify-content: flex-end; |
|
359 |
+ gap: 8px; |
|
360 |
+} |
|
361 |
+ |
|
362 |
+.button-wrapper button { |
|
363 |
+ padding: 8px 16px; |
|
364 |
+ border: 1px solid #ccc; |
|
365 |
+ border-radius: 4px; |
|
366 |
+ cursor: pointer; |
|
367 |
+ background-color: #f0f0f0; |
|
368 |
+} |
|
369 |
+</style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/subPage/FeedBack.vue
+++ client/views/pages/subPage/FeedBack.vue
... | ... | @@ -202,7 +202,7 @@ |
202 | 202 |
// } catch (err) { |
203 | 203 |
// console.error('회신 실패:', err); |
204 | 204 |
// } |
205 |
- if (!this.replyText.trim() || !this.stompClient) return; |
|
205 |
+ if (!this.replyText.trim() || !this.stompClient) return; // 빈 메시지 또는 stomp 연결 안돼있으면 전송 종료 |
|
206 | 206 |
|
207 | 207 |
const msg = { |
208 | 208 |
chatRoomId: this.selectedFeedback.chatRoomId, |
... | ... | @@ -210,9 +210,9 @@ |
210 | 210 |
msgContent: this.replyText, |
211 | 211 |
}; |
212 | 212 |
|
213 |
- this.stompClient.publish({ |
|
213 |
+ this.stompClient.publish({ // 메시지 발행(클라이언트 -> 서버) |
|
214 | 214 |
destination: "/pub/chat.sendMessage", |
215 |
- body: JSON.stringify(msg) |
|
215 |
+ body: JSON.stringify(msg) // json 문자열로 메시지 전송 |
|
216 | 216 |
}); |
217 | 217 |
this.replyText = ''; |
218 | 218 |
}, |
... | ... | @@ -285,13 +285,16 @@ |
285 | 285 |
connectToChatRoom(roomId) { |
286 | 286 |
if (this.stompClient && this.connectedRoomId === roomId) return; |
287 | 287 |
|
288 |
- const socket = new SockJS('http://localhost:10911/ws'); |
|
288 |
+ const socket = new SockJS('http://localhost:10911/ws'); // 소켓 연결 |
|
289 | 289 |
|
290 |
+ // stomp 설정 |
|
290 | 291 |
const client = new Client({ |
291 | 292 |
webSocketFactory: () => socket, |
292 | 293 |
onConnect: () => { |
293 |
- client.subscribe(`/sub/chat.room.${roomId}`, (message) => { |
|
294 |
- const msg = JSON.parse(message.body); |
|
294 |
+ client.subscribe(`/sub/chat.room.${roomId}`, (message) => { // 메시지 구독(서버 -> 클라이언트) |
|
295 |
+ |
|
296 |
+ const msg = JSON.parse(message.body); // 받은 메시지 json 형태로 파싱 |
|
297 |
+ |
|
295 | 298 |
this.feedbackChatList.push({ |
296 | 299 |
id: msg.chatMsgId, |
297 | 300 |
user: msg.senderName, |
--- client/views/pages/subPage/Modeling.vue
+++ client/views/pages/subPage/Modeling.vue
... | ... | @@ -13,9 +13,10 @@ |
13 | 13 |
</button> |
14 | 14 |
</div> |
15 | 15 |
<div class="tab-content"> |
16 |
- <div class="layout center justify-center h_100" style="background-color: #ffffff; border-radius: 2rem;" v-if="activeTab === 'Image Prompt'"> |
|
17 |
- <div class="layout center center" style="flex-direction: column;"> |
|
18 |
- <input type="file" ref="fileInput" name="" id="file" class="sr-only"> |
|
16 |
+ <div class="layout center justify-center h_100" :style="imagePromptStyle" v-if="activeTab === 'Image Prompt'"> |
|
17 |
+ <!-- style="background-color: #ffffff; border-radius: 2rem;" --> |
|
18 |
+ <div v-if="!imageUrl" class="layout center center" style="flex-direction: column;"> |
|
19 |
+ <input type="file" ref="fileInput" @change="handleFileChange" accept="image/*" name="" id="file" class="sr-only"> |
|
19 | 20 |
<span class="file-icon" id="file"><img src="../../../resources/img/content/ico_file.svg" alt=""></span> |
20 | 21 |
<label for="file" style="text-align: center;">파일을 업로드하세요.</label> |
21 | 22 |
</div> |
... | ... | @@ -377,6 +378,7 @@ |
377 | 378 |
projectGroupId: null, // 프로젝트 피드백 작성 시 사용 |
378 | 379 |
isReset: null, // 이미지 초기화 시 사용 |
379 | 380 |
feedback: '', // 피드백 내용 |
381 |
+ imageUrl: '', // 이미지 썸네일 |
|
380 | 382 |
}; |
381 | 383 |
}, |
382 | 384 |
methods: { |
... | ... | @@ -440,7 +442,7 @@ |
440 | 442 |
createProject() { |
441 | 443 |
const formData = new FormData(); |
442 | 444 |
const fileInput = this.$refs.fileInput; // 업로드 파일 가져오기 |
443 |
- if (!fileInput && this.isReset == true) { |
|
445 |
+ if (this.isReset == true) { |
|
444 | 446 |
alert("파일을 선택해주세요."); |
445 | 447 |
return; |
446 | 448 |
} |
... | ... | @@ -459,7 +461,7 @@ |
459 | 461 |
updateProject(projectId) { |
460 | 462 |
const formData = new FormData(); |
461 | 463 |
const fileInput = this.$refs.fileInput; // 업로드 파일 가져오기(없으면 기존 이미지 유지: fileInput==null) |
462 |
- if (!fileInput && this.isReset == true) { |
|
464 |
+ if (this.isReset == true) { |
|
463 | 465 |
alert("파일을 선택해주세요."); |
464 | 466 |
return; |
465 | 467 |
} |
... | ... | @@ -491,9 +493,12 @@ |
491 | 493 |
const projectInfo = response.data.result; |
492 | 494 |
this.projectId = projectId; |
493 | 495 |
this.projectGroupId = projectInfo.project.projectGroupId; |
496 |
+ |
|
494 | 497 |
this.localCommentData.summary = projectInfo.projectComment.summary; |
495 | 498 |
this.localCommentData.comment = projectInfo.projectComment.comment; |
496 | 499 |
this.saveComment(); |
500 |
+ |
|
501 |
+ this.imageUrl = 'http://localhost:10911' + projectInfo.projectImage.fileUrl; |
|
497 | 502 |
|
498 | 503 |
const memberList = projectInfo.projectMember; |
499 | 504 |
this.members = memberList.map(member => ({ |
... | ... | @@ -507,6 +512,10 @@ |
507 | 512 |
// 이미지 초기화 |
508 | 513 |
resetImage(){ |
509 | 514 |
this.isReset = true; |
515 |
+ if (this.imageUrl) { |
|
516 |
+ URL.revokeObjectURL(this.imageUrl); // 메모리 해제 |
|
517 |
+ } |
|
518 |
+ this.imageUrl = ''; |
|
510 | 519 |
}, |
511 | 520 |
// 피드백 저장 |
512 | 521 |
saveFeedback() { |
... | ... | @@ -524,7 +533,19 @@ |
524 | 533 |
}) |
525 | 534 |
.catch(error => {console.error('피드백 저장 실패');}); |
526 | 535 |
this.closeModal(); |
527 |
- } |
|
536 |
+ }, |
|
537 |
+ // 이미지 썸네일 url 생성 |
|
538 |
+ handleFileChange(event){ |
|
539 |
+ |
|
540 |
+ const file = event.target.files[0]; |
|
541 |
+ if(file && file.type.startsWith('image/')){ |
|
542 |
+ // 기존 썸네일 URL이 있다면 메모리 해제 |
|
543 |
+ if (this.imageUrl) { |
|
544 |
+ URL.revokeObjectURL(this.imageUrl); |
|
545 |
+ } |
|
546 |
+ this.imageUrl = URL.createObjectURL(file); |
|
547 |
+ } |
|
548 |
+ }, |
|
528 | 549 |
}, |
529 | 550 |
watch: {}, |
530 | 551 |
computed: { |
... | ... | @@ -537,6 +558,22 @@ |
537 | 558 |
return tool.icon; |
538 | 559 |
}; |
539 | 560 |
}, |
561 |
+ imagePromptStyle() { |
|
562 |
+ const style = { |
|
563 |
+ 'background-color': '#ffffff', |
|
564 |
+ 'border-radius': '2rem', |
|
565 |
+ 'cursor': 'pointer' |
|
566 |
+ }; |
|
567 |
+ |
|
568 |
+ if (this.imageUrl) { |
|
569 |
+ style['background-image'] = `url(${this.imageUrl})`; |
|
570 |
+ style['background-size'] = 'contain'; |
|
571 |
+ style['background-position'] = 'center'; |
|
572 |
+ style['background-repeat'] = 'no-repeat'; |
|
573 |
+ style['cursor'] = 'default'; |
|
574 |
+ } |
|
575 |
+ return style; |
|
576 |
+ }, |
|
540 | 577 |
...mapGetters([ |
541 | 578 |
'getMemId', |
542 | 579 |
'getMemNm', |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?