캐릭터 챗봇 #74
							
								
								
									
										514
									
								
								src/views/Chat/CharacterList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										514
									
								
								src/views/Chat/CharacterList.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,514 @@ | ||||
| <template> | ||||
|   <div> | ||||
|     <v-toolbar dark> | ||||
|       <v-spacer /> | ||||
|       <v-toolbar-title>캐릭터 리스트</v-toolbar-title> | ||||
|       <v-spacer /> | ||||
|     </v-toolbar> | ||||
|  | ||||
|     <br> | ||||
|  | ||||
|     <v-container> | ||||
|       <v-row> | ||||
|         <v-col cols="4"> | ||||
|           <v-btn | ||||
|             color="primary" | ||||
|             dark | ||||
|             @click="showAddDialog" | ||||
|           > | ||||
|             캐릭터 추가 | ||||
|           </v-btn> | ||||
|         </v-col> | ||||
|         <v-spacer /> | ||||
|         <v-col cols="6"> | ||||
|           <v-text-field | ||||
|             v-model="search_word" | ||||
|             label="캐릭터 이름 검색" | ||||
|             @keyup.enter="search" | ||||
|           > | ||||
|             <v-btn | ||||
|               slot="append" | ||||
|               color="#9970ff" | ||||
|               dark | ||||
|               @click="search" | ||||
|             > | ||||
|               검색 | ||||
|             </v-btn> | ||||
|           </v-text-field> | ||||
|         </v-col> | ||||
|       </v-row> | ||||
|       <v-row> | ||||
|         <v-col> | ||||
|           <v-simple-table class="elevation-10"> | ||||
|             <template> | ||||
|               <thead> | ||||
|                 <tr> | ||||
|                   <th class="text-center"> | ||||
|                     ID | ||||
|                   </th> | ||||
|                   <th class="text-center"> | ||||
|                     이미지 | ||||
|                   </th> | ||||
|                   <th class="text-center"> | ||||
|                     이름 | ||||
|                   </th> | ||||
|                   <th class="text-center"> | ||||
|                     설명 | ||||
|                   </th> | ||||
|                   <th class="text-center"> | ||||
|                     상태 | ||||
|                   </th> | ||||
|                   <th class="text-center"> | ||||
|                     생성일 | ||||
|                   </th> | ||||
|                   <th class="text-center"> | ||||
|                     관리 | ||||
|                   </th> | ||||
|                 </tr> | ||||
|               </thead> | ||||
|               <tbody> | ||||
|                 <tr | ||||
|                   v-for="item in characters" | ||||
|                   :key="item.id" | ||||
|                 > | ||||
|                   <td>{{ item.id }}</td> | ||||
|                   <td align="center"> | ||||
|                     <v-img | ||||
|                       max-width="70" | ||||
|                       max-height="70" | ||||
|                       :src="item.imageUrl" | ||||
|                       class="rounded-circle" | ||||
|                     /> | ||||
|                   </td> | ||||
|                   <td>{{ item.name }}</td> | ||||
|                   <td style="max-width: 200px !important; word-break:break-all; height: auto;"> | ||||
|                     <vue-show-more-text | ||||
|                       :text="item.description" | ||||
|                       :lines="3" | ||||
|                     /> | ||||
|                   </td> | ||||
|                   <td> | ||||
|                     <v-chip | ||||
|                       :color="item.isActive ? 'green' : 'red'" | ||||
|                       text-color="white" | ||||
|                       small | ||||
|                     > | ||||
|                       {{ item.isActive ? '활성' : '비활성' }} | ||||
|                     </v-chip> | ||||
|                   </td> | ||||
|                   <td>{{ item.createdAt }}</td> | ||||
|                   <td> | ||||
|                     <v-row> | ||||
|                       <v-col> | ||||
|                         <v-btn | ||||
|                           small | ||||
|                           color="primary" | ||||
|                           :disabled="is_loading" | ||||
|                           @click="showEditDialog(item)" | ||||
|                         > | ||||
|                           수정 | ||||
|                         </v-btn> | ||||
|                       </v-col> | ||||
|                       <v-col> | ||||
|                         <v-btn | ||||
|                           small | ||||
|                           color="error" | ||||
|                           :disabled="is_loading" | ||||
|                           @click="deleteConfirm(item)" | ||||
|                         > | ||||
|                           삭제 | ||||
|                         </v-btn> | ||||
|                       </v-col> | ||||
|                     </v-row> | ||||
|                   </td> | ||||
|                 </tr> | ||||
|               </tbody> | ||||
|             </template> | ||||
|           </v-simple-table> | ||||
|         </v-col> | ||||
|       </v-row> | ||||
|       <v-row class="text-center"> | ||||
|         <v-col> | ||||
|           <v-pagination | ||||
|             v-model="page" | ||||
|             :length="total_page" | ||||
|             circle | ||||
|             @input="next" | ||||
|           /> | ||||
|         </v-col> | ||||
|       </v-row> | ||||
|     </v-container> | ||||
|  | ||||
|     <!-- 캐릭터 추가/수정 다이얼로그 --> | ||||
|     <v-dialog | ||||
|       v-model="show_dialog" | ||||
|       max-width="600px" | ||||
|       persistent | ||||
|     > | ||||
|       <v-card> | ||||
|         <v-card-title> | ||||
|           {{ is_edit ? '캐릭터 수정' : '캐릭터 추가' }} | ||||
|         </v-card-title> | ||||
|         <v-card-text> | ||||
|           <v-container> | ||||
|             <v-row> | ||||
|               <v-col cols="12"> | ||||
|                 <v-text-field | ||||
|                   v-model="character.name" | ||||
|                   label="이름" | ||||
|                   required | ||||
|                 /> | ||||
|               </v-col> | ||||
|             </v-row> | ||||
|             <v-row> | ||||
|               <v-col cols="12"> | ||||
|                 <v-textarea | ||||
|                   v-model="character.description" | ||||
|                   label="설명" | ||||
|                   required | ||||
|                 /> | ||||
|               </v-col> | ||||
|             </v-row> | ||||
|             <v-row> | ||||
|               <v-col cols="12"> | ||||
|                 <v-file-input | ||||
|                   v-model="character.image" | ||||
|                   label="캐릭터 이미지" | ||||
|                   accept="image/*" | ||||
|                   prepend-icon="mdi-camera" | ||||
|                   show-size | ||||
|                   truncate-length="15" | ||||
|                 /> | ||||
|               </v-col> | ||||
|             </v-row> | ||||
|             <v-row> | ||||
|               <v-col cols="12"> | ||||
|                 <v-switch | ||||
|                   v-model="character.isActive" | ||||
|                   label="활성화" | ||||
|                 /> | ||||
|               </v-col> | ||||
|             </v-row> | ||||
|           </v-container> | ||||
|         </v-card-text> | ||||
|         <v-card-actions> | ||||
|           <v-spacer /> | ||||
|           <v-btn | ||||
|             color="blue darken-1" | ||||
|             text | ||||
|             @click="closeDialog" | ||||
|           > | ||||
|             취소 | ||||
|           </v-btn> | ||||
|           <v-btn | ||||
|             color="blue darken-1" | ||||
|             text | ||||
|             @click="saveCharacter" | ||||
|           > | ||||
|             저장 | ||||
|           </v-btn> | ||||
|         </v-card-actions> | ||||
|       </v-card> | ||||
|     </v-dialog> | ||||
|  | ||||
|     <!-- 삭제 확인 다이얼로그 --> | ||||
|     <v-dialog | ||||
|       v-model="show_delete_confirm_dialog" | ||||
|       max-width="400px" | ||||
|       persistent | ||||
|     > | ||||
|       <v-card> | ||||
|         <v-card-text /> | ||||
|         <v-card-text> | ||||
|           "{{ selected_character.name }}"을(를) 삭제하시겠습니까? | ||||
|         </v-card-text> | ||||
|         <v-card-actions v-show="!is_loading"> | ||||
|           <v-spacer /> | ||||
|           <v-btn | ||||
|             color="blue darken-1" | ||||
|             text | ||||
|             @click="closeDeleteDialog" | ||||
|           > | ||||
|             취소 | ||||
|           </v-btn> | ||||
|           <v-btn | ||||
|             color="red darken-1" | ||||
|             text | ||||
|             @click="deleteCharacter" | ||||
|           > | ||||
|             확인 | ||||
|           </v-btn> | ||||
|         </v-card-actions> | ||||
|       </v-card> | ||||
|     </v-dialog> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import VueShowMoreText from 'vue-show-more-text' | ||||
|  | ||||
| export default { | ||||
|   name: "CharacterList", | ||||
|  | ||||
|   components: { VueShowMoreText }, | ||||
|  | ||||
|   data() { | ||||
|     return { | ||||
|       is_loading: false, | ||||
|       show_dialog: false, | ||||
|       show_delete_confirm_dialog: false, | ||||
|       is_edit: false, | ||||
|       page: 1, | ||||
|       total_page: 0, | ||||
|       search_word: '', | ||||
|       character: { | ||||
|         id: null, | ||||
|         name: '', | ||||
|         description: '', | ||||
|         image: null, | ||||
|         imageUrl: '', | ||||
|         isActive: true, | ||||
|         createdAt: '' | ||||
|       }, | ||||
|       characters: [], | ||||
|       selected_character: {} | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   async created() { | ||||
|     await this.getCharacters() | ||||
|   }, | ||||
|  | ||||
|   methods: { | ||||
|     notifyError(message) { | ||||
|       this.$dialog.notify.error(message) | ||||
|     }, | ||||
|  | ||||
|     notifySuccess(message) { | ||||
|       this.$dialog.notify.success(message) | ||||
|     }, | ||||
|  | ||||
|     showAddDialog() { | ||||
|       this.is_edit = false | ||||
|       this.character = { | ||||
|         id: null, | ||||
|         name: '', | ||||
|         description: '', | ||||
|         image: null, | ||||
|         imageUrl: '', | ||||
|         isActive: true, | ||||
|         createdAt: '' | ||||
|       } | ||||
|       this.show_dialog = true | ||||
|     }, | ||||
|  | ||||
|     showEditDialog(item) { | ||||
|       this.is_edit = true | ||||
|       this.selected_character = item | ||||
|       this.character = { | ||||
|         id: item.id, | ||||
|         name: item.name, | ||||
|         description: item.description, | ||||
|         image: null, | ||||
|         imageUrl: item.imageUrl, | ||||
|         isActive: item.isActive, | ||||
|         createdAt: item.createdAt | ||||
|       } | ||||
|       this.show_dialog = true | ||||
|     }, | ||||
|  | ||||
|     closeDialog() { | ||||
|       this.show_dialog = false | ||||
|       this.character = { | ||||
|         id: null, | ||||
|         name: '', | ||||
|         description: '', | ||||
|         image: null, | ||||
|         imageUrl: '', | ||||
|         isActive: true, | ||||
|         createdAt: '' | ||||
|       } | ||||
|       this.selected_character = {} | ||||
|     }, | ||||
|  | ||||
|     deleteConfirm(item) { | ||||
|       this.selected_character = item | ||||
|       this.show_delete_confirm_dialog = true | ||||
|     }, | ||||
|  | ||||
|     closeDeleteDialog() { | ||||
|       this.show_delete_confirm_dialog = false | ||||
|       this.selected_character = {} | ||||
|     }, | ||||
|  | ||||
|     async saveCharacter() { | ||||
|       if ( | ||||
|         this.character.name === null || | ||||
|         this.character.name === undefined || | ||||
|         this.character.name.trim().length <= 0 | ||||
|       ) { | ||||
|         this.notifyError("이름을 입력하세요") | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       if ( | ||||
|         this.character.description === null || | ||||
|         this.character.description === undefined || | ||||
|         this.character.description.trim().length <= 0 | ||||
|       ) { | ||||
|         this.notifyError("설명을 입력하세요") | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       if (this.is_loading) return; | ||||
|  | ||||
|       this.is_loading = true | ||||
|  | ||||
|       try { | ||||
|         // 여기에 API 호출 코드를 추가합니다. | ||||
|         // 예시: | ||||
|         // const res = this.is_edit | ||||
|         //   ? await api.updateCharacter(this.character) | ||||
|         //   : await api.createCharacter(this.character); | ||||
|  | ||||
|         // API 호출이 없으므로 임시로 성공 처리 | ||||
|         setTimeout(() => { | ||||
|           this.closeDialog() | ||||
|           this.notifySuccess(this.is_edit ? '수정되었습니다.' : '추가되었습니다.') | ||||
|           this.getCharacters() | ||||
|           this.is_loading = false | ||||
|         }, 1000) | ||||
|       } catch (e) { | ||||
|         this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') | ||||
|         this.is_loading = false | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     async deleteCharacter() { | ||||
|       if (this.is_loading) return; | ||||
|       this.is_loading = true | ||||
|  | ||||
|       try { | ||||
|         // 여기에 API 호출 코드를 추가합니다. | ||||
|         // 예시: | ||||
|         // const res = await api.deleteCharacter(this.selected_character.id); | ||||
|  | ||||
|         // API 호출이 없으므로 임시로 성공 처리 | ||||
|         setTimeout(() => { | ||||
|           this.closeDeleteDialog() | ||||
|           this.notifySuccess('삭제되었습니다.') | ||||
|           this.getCharacters() | ||||
|           this.is_loading = false | ||||
|         }, 1000) | ||||
|       } catch (e) { | ||||
|         this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') | ||||
|         this.is_loading = false | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     async next() { | ||||
|       if (this.search_word.length < 2) { | ||||
|         this.search_word = '' | ||||
|         await this.getCharacters() | ||||
|       } else { | ||||
|         await this.searchCharacters() | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     async getCharacters() { | ||||
|       this.is_loading = true | ||||
|       try { | ||||
|         // 여기에 API 호출 코드를 추가합니다. | ||||
|         // 예시: | ||||
|         // const res = await api.getCharacters(this.page); | ||||
|  | ||||
|         // API 호출이 없으므로 임시 데이터 생성 | ||||
|         setTimeout(() => { | ||||
|           // 임시 데이터 | ||||
|           const mockData = { | ||||
|             totalCount: 15, | ||||
|             items: Array.from({ length: 10 }, (_, i) => ({ | ||||
|               id: i + 1 + (this.page - 1) * 10, | ||||
|               name: `캐릭터 ${i + 1 + (this.page - 1) * 10}`, | ||||
|               description: `이것은 캐릭터 ${i + 1 + (this.page - 1) * 10}에 대한 설명입니다. 이 캐릭터는 다양한 특성을 가지고 있습니다.`, | ||||
|               imageUrl: 'https://via.placeholder.com/150', | ||||
|               isActive: Math.random() > 0.3, | ||||
|               createdAt: new Date().toISOString().split('T')[0] | ||||
|             })) | ||||
|           } | ||||
|  | ||||
|           const total_page = Math.ceil(mockData.totalCount / 10) | ||||
|           this.characters = mockData.items | ||||
|  | ||||
|           if (total_page <= 0) | ||||
|             this.total_page = 1 | ||||
|           else | ||||
|             this.total_page = total_page | ||||
|  | ||||
|           this.is_loading = false | ||||
|         }, 500) | ||||
|       } catch (e) { | ||||
|         this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') | ||||
|         this.is_loading = false | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     async search() { | ||||
|       this.page = 1 | ||||
|       await this.searchCharacters() | ||||
|     }, | ||||
|  | ||||
|     async searchCharacters() { | ||||
|       if (this.search_word.length === 0) { | ||||
|         await this.getCharacters() | ||||
|       } else if (this.search_word.length < 2) { | ||||
|         this.notifyError('검색어를 2글자 이상 입력하세요.') | ||||
|       } else { | ||||
|         this.is_loading = true | ||||
|         try { | ||||
|           // 여기에 API 호출 코드를 추가합니다. | ||||
|           // 예시: | ||||
|           // const res = await api.searchCharacters(this.search_word, this.page); | ||||
|  | ||||
|           // API 호출이 없으므로 임시 데이터 생성 | ||||
|           setTimeout(() => { | ||||
|             // 검색 결과 임시 데이터 | ||||
|             const filteredItems = Array.from({ length: 3 }, (_, i) => ({ | ||||
|               id: i + 1, | ||||
|               name: `${this.search_word} 캐릭터 ${i + 1}`, | ||||
|               description: `이것은 ${this.search_word} 캐릭터 ${i + 1}에 대한 설명입니다.`, | ||||
|               imageUrl: 'https://via.placeholder.com/150', | ||||
|               isActive: true, | ||||
|               createdAt: new Date().toISOString().split('T')[0] | ||||
|             })) | ||||
|  | ||||
|             const mockData = { | ||||
|               totalCount: 3, | ||||
|               items: filteredItems | ||||
|             } | ||||
|  | ||||
|             const total_page = Math.ceil(mockData.totalCount / 10) | ||||
|             this.characters = mockData.items | ||||
|  | ||||
|             if (total_page <= 0) | ||||
|               this.total_page = 1 | ||||
|             else | ||||
|               this.total_page = total_page | ||||
|  | ||||
|             this.is_loading = false | ||||
|           }, 500) | ||||
|         } catch (e) { | ||||
|           this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') | ||||
|           this.is_loading = false | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .v-data-table { | ||||
|   width: 100%; | ||||
| } | ||||
| </style> | ||||
		Reference in New Issue
	
	Block a user