
--- client/views/component/SearchFormComponent.vue
+++ client/views/component/SearchFormComponent.vue
... | ... | @@ -111,7 +111,7 @@ |
111 | 111 |
startYear: null, |
112 | 112 |
endYear: null, |
113 | 113 |
searchCtgries: [], |
114 |
- order: "rgsde", |
|
114 |
+ order: "prdctn_year", |
|
115 | 115 |
// 페이지네이션 관련 속성 |
116 | 116 |
currentPage: 1, |
117 | 117 |
recordSize: 24, |
... | ... | @@ -140,7 +140,8 @@ |
140 | 140 |
|
141 | 141 |
// 정렬 목록 |
142 | 142 |
orders: [ |
143 |
- { key: "rgsde", value: "최신" }, |
|
143 |
+ { key: "prdctn_year", value: "생산연도" }, |
|
144 |
+ { key: "rgsde", value: "등록일" }, |
|
144 | 145 |
{ key: "rdcnt", value: "인기" }, |
145 | 146 |
], |
146 | 147 |
}; |
--- client/views/pages/bbsDcryPhoto/PicHistoryInsert.vue
+++ client/views/pages/bbsDcryPhoto/PicHistoryInsert.vue
... | ... | @@ -71,9 +71,14 @@ |
71 | 71 |
</label> |
72 | 72 |
</div> |
73 | 73 |
<div class="flex-sp-bw file-wrap"> |
74 |
- <div class="file-name"> |
|
75 |
- <img src="/client/resources/images/icon/imgicon.png" alt="fileicon"> |
|
76 |
- <p>{{ file.fileNm }}</p> |
|
74 |
+ <div class="file-item-with-thumbnail"> |
|
75 |
+ <div class="thumbnail-container"> |
|
76 |
+ <img :src="getFileUrl(file)" alt="thumbnail" class="file-thumbnail" @error="handleImageError"> |
|
77 |
+ </div> |
|
78 |
+ <div class="file-name"> |
|
79 |
+ <img src="/client/resources/images/icon/imgicon.png" alt="fileicon"> |
|
80 |
+ <p>{{ file.fileNm }}</p> |
|
81 |
+ </div> |
|
77 | 82 |
</div> |
78 | 83 |
<button type="button" class="cancel" @click="fnDelFile('old', file.fileOrdr)"><b>✕</b></button> |
79 | 84 |
</div> |
... | ... | @@ -87,9 +92,15 @@ |
87 | 92 |
</label> |
88 | 93 |
</div> |
89 | 94 |
<div class="flex-sp-bw file-wrap"> |
90 |
- <div class="file-name"> |
|
91 |
- <img src="/client/resources/images/icon/imgicon.png" alt="fileicon"> |
|
92 |
- <p>{{ file.name }}</p> |
|
95 |
+ <div class="file-item-with-thumbnail"> |
|
96 |
+ <div class="thumbnail-container"> |
|
97 |
+ <img :src="filePreviewUrls[idx]" alt="thumbnail" class="file-thumbnail" v-if="filePreviewUrls[idx]"> |
|
98 |
+ <div class="thumbnail-loading" v-else>Loading...</div> |
|
99 |
+ </div> |
|
100 |
+ <div class="file-name"> |
|
101 |
+ <img src="/client/resources/images/icon/imgicon.png" alt="fileicon"> |
|
102 |
+ <p>{{ file.name }}</p> |
|
103 |
+ </div> |
|
93 | 104 |
</div> |
94 | 105 |
<button type="button" class="cancel" @click="fnDelFile('new', idx)"><b>✕</b></button> |
95 | 106 |
</div> |
... | ... | @@ -149,6 +160,7 @@ |
149 | 160 |
isDragging: false, |
150 | 161 |
|
151 | 162 |
fileNames: [], |
163 |
+ filePreviewUrls: [], |
|
152 | 164 |
|
153 | 165 |
// 등록/수정 요청 객체 |
154 | 166 |
requestDTO: { |
... | ... | @@ -188,6 +200,26 @@ |
188 | 200 |
}, |
189 | 201 |
|
190 | 202 |
methods: { |
203 |
+ // 기존 파일의 URL 생성 (서버에서 제공하는 파일 URL) |
|
204 |
+ getFileUrl(file) { |
|
205 |
+ return file.filePath || '/client/resources/images/icon/imgicon.png'; |
|
206 |
+ }, |
|
207 |
+ |
|
208 |
+ // 이미지 로드 에러 처리 |
|
209 |
+ handleImageError(event) { |
|
210 |
+ event.target.src = '/client/resources/images/icon/imgicon.png'; |
|
211 |
+ }, |
|
212 |
+ |
|
213 |
+ // 파일 미리보기 URL 생성 |
|
214 |
+ createFilePreview(file) { |
|
215 |
+ return new Promise((resolve) => { |
|
216 |
+ const reader = new FileReader(); |
|
217 |
+ reader.onload = (e) => resolve(e.target.result); |
|
218 |
+ reader.onerror = () => resolve(null); |
|
219 |
+ reader.readAsDataURL(file); |
|
220 |
+ }); |
|
221 |
+ }, |
|
222 |
+ |
|
191 | 223 |
// 상세 조회 |
192 | 224 |
async fnFindDcry() { |
193 | 225 |
try { |
... | ... | @@ -220,6 +252,7 @@ |
220 | 252 |
this.requestDTO.files = dcry.files.length > 0 ? dcry.files : []; // 기존 첨부파일 |
221 | 253 |
|
222 | 254 |
this.multipartFiles = []; |
255 |
+ this.filePreviewUrls = []; |
|
223 | 256 |
this.selectedCtgries = dcry.ctgrys.length > 0 ? dcry.ctgrys : []; |
224 | 257 |
|
225 | 258 |
// 썸네일 |
... | ... | @@ -252,7 +285,7 @@ |
252 | 285 |
}, |
253 | 286 |
|
254 | 287 |
// 파일 업로드 처리 함수 |
255 |
- processFiles(files) { |
|
288 |
+ async processFiles(files) { |
|
256 | 289 |
const allowedTypes = ['jpg', 'jpeg', 'png', 'gif']; // 이미지 파일만 허용 |
257 | 290 |
const maxSize = 10 * 1024 * 1024 * 1024; // 10GB |
258 | 291 |
|
... | ... | @@ -295,9 +328,13 @@ |
295 | 328 |
this.multipartFiles.push(file); |
296 | 329 |
existingFilesMap.set(fileNameLower, file.name); |
297 | 330 |
|
331 |
+ // 미리보기 URL 생성 |
|
332 |
+ const previewUrl = await this.createFilePreview(file); |
|
333 |
+ this.filePreviewUrls.push(previewUrl); |
|
334 |
+ |
|
298 | 335 |
// 최초 등록 시 썸네일로 설정 |
299 |
- if (this.requestDTO.files.length < 1 && this.multipartFiles.length > 0) { |
|
300 |
- this.selectedThumb = this.multipartFiles[0].name; |
|
336 |
+ if (this.requestDTO.files.length < 1 && this.multipartFiles.length === 1) { |
|
337 |
+ this.selectedThumb = file.name; |
|
301 | 338 |
} |
302 | 339 |
} |
303 | 340 |
}, |
... | ... | @@ -306,6 +343,7 @@ |
306 | 343 |
fnDelFile(type, separator) { |
307 | 344 |
if (type === 'new') { |
308 | 345 |
this.multipartFiles.splice(separator, 1); |
346 |
+ this.filePreviewUrls.splice(separator, 1); // 미리보기 URL도 함께 삭제 |
|
309 | 347 |
|
310 | 348 |
// 모든 새 파일이 삭제된 경우 input 요소 초기화 |
311 | 349 |
if (this.multipartFiles.length === 0) { |
... | ... | @@ -496,4 +534,35 @@ |
496 | 534 |
border-color: #1890ff; |
497 | 535 |
background-color: rgba(24, 144, 255, 0.1); |
498 | 536 |
} |
537 |
+ |
|
538 |
+/* 썸네일 관련 스타일 */ |
|
539 |
+.file-item-with-thumbnail { |
|
540 |
+ display: flex; |
|
541 |
+ align-items: center; |
|
542 |
+ gap: 12px; |
|
543 |
+} |
|
544 |
+ |
|
545 |
+.thumbnail-container { |
|
546 |
+ flex-shrink: 0; |
|
547 |
+} |
|
548 |
+ |
|
549 |
+.file-thumbnail { |
|
550 |
+ width: 120px; |
|
551 |
+ height: 80px; |
|
552 |
+ object-fit: cover; |
|
553 |
+ border-radius: 4px; |
|
554 |
+} |
|
555 |
+ |
|
556 |
+.thumbnail-loading { |
|
557 |
+ width: 120px; |
|
558 |
+ height: 80px; |
|
559 |
+ background-color: #f5f5f5; |
|
560 |
+ border-radius: 4px; |
|
561 |
+ border: 1px solid #ddd; |
|
562 |
+ display: flex; |
|
563 |
+ align-items: center; |
|
564 |
+ justify-content: center; |
|
565 |
+ font-size: 12px; |
|
566 |
+ color: #666; |
|
567 |
+} |
|
499 | 568 |
</style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/bbsDcryPhoto/PicHistorySearch.vue
+++ client/views/pages/bbsDcryPhoto/PicHistorySearch.vue
... | ... | @@ -108,7 +108,7 @@ |
108 | 108 |
endYear: null, |
109 | 109 |
searchTy: "P", // 사진 기록물 고정 |
110 | 110 |
searchCtgries: [], |
111 |
- order: "rgsde", |
|
111 |
+ order: "prdctn_year", |
|
112 | 112 |
// 페이지네이션 |
113 | 113 |
currentPage: 1, // 현재 페이지 |
114 | 114 |
recordSize: 24, // 한 페이지에 표시할 데이터 개수 |
--- client/views/pages/bbsDcryVideo/VideoHistorySearch.vue
+++ client/views/pages/bbsDcryVideo/VideoHistorySearch.vue
... | ... | @@ -107,7 +107,7 @@ |
107 | 107 |
endYear: null, |
108 | 108 |
searchTy: "V", // 영상 기록물 고정 |
109 | 109 |
searchCtgries: [], |
110 |
- order: "rgsde", |
|
110 |
+ order: "prdctn_year", |
|
111 | 111 |
// 페이지네이션 |
112 | 112 |
currentPage: 1, // 현재 페이지 |
113 | 113 |
recordSize: 24, // 한 페이지에 표시할 데이터 개수 |
--- client/views/pages/bbsMediaVido/MediaVideoSearch.vue
+++ client/views/pages/bbsMediaVido/MediaVideoSearch.vue
... | ... | @@ -102,7 +102,7 @@ |
102 | 102 |
endYear: null, |
103 | 103 |
searchTy: "M", // 미디어 영상 고정 |
104 | 104 |
searchCtgries: [], |
105 |
- order: "rgsde", |
|
105 |
+ order: "prdctn_year", |
|
106 | 106 |
// 페이지네이션 |
107 | 107 |
currentPage: 1, // 현재 페이지 |
108 | 108 |
recordSize: 24, // 한 페이지에 표시할 데이터 개수 |
--- client/views/pages/bbsNesDta/NewsReleaseInsert.vue
+++ client/views/pages/bbsNesDta/NewsReleaseInsert.vue
... | ... | @@ -62,20 +62,35 @@ |
62 | 62 |
<div id="fileNames" class="file-names"> |
63 | 63 |
<div v-if="requestDTO.files.length === 0 && multipartFiles.length === 0">선택된 파일이 없습니다.</div> |
64 | 64 |
<!-- 기존 등록된 파일 목록 --> |
65 |
- <div v-for="(file, idx) of requestDTO.files" :key="idx" class="flex-sp-bw mb-5 file-wrap"> |
|
66 |
- <div class="file-name"> |
|
67 |
- <img src="/client/resources/images/icon/imgicon.png" alt="fileicon"> |
|
68 |
- <p>{{ file.fileNm }}</p> |
|
65 |
+ <div v-for="(file, idx) of requestDTO.files" :key="idx" class="mb-5"> |
|
66 |
+ <div class="flex-sp-bw file-wrap"> |
|
67 |
+ <div class="file-item-with-thumbnail"> |
|
68 |
+ <div class="thumbnail-container"> |
|
69 |
+ <img :src="getFileUrl(file)" alt="thumbnail" class="file-thumbnail" @error="handleImageError"> |
|
70 |
+ </div> |
|
71 |
+ <div class="file-name"> |
|
72 |
+ <img src="/client/resources/images/icon/imgicon.png" alt="fileicon"> |
|
73 |
+ <p>{{ file.fileNm }}</p> |
|
74 |
+ </div> |
|
75 |
+ </div> |
|
76 |
+ <button type="button" class="cancel" @click="fnDelFile('old', file.fileId)"><b>✕</b></button> |
|
69 | 77 |
</div> |
70 |
- <button type="button" class="cancel" @click="fnDelFile('old', file.fileId)"><b>✕</b></button> |
|
71 | 78 |
</div> |
72 | 79 |
<!-- 새로 추가된 파일 목록 --> |
73 |
- <div v-for="(file, idx) of multipartFiles" :key="idx" class="flex-sp-bw mb-5 file-wrap"> |
|
74 |
- <div class="file-name"> |
|
75 |
- <img src="/client/resources/images/icon/imgicon.png" alt="fileicon"> |
|
76 |
- <p>{{ file.name }}</p> |
|
80 |
+ <div v-for="(file, idx) of multipartFiles" :key="idx" class="mb-5"> |
|
81 |
+ <div class="flex-sp-bw file-wrap"> |
|
82 |
+ <div class="file-item-with-thumbnail"> |
|
83 |
+ <div class="thumbnail-container"> |
|
84 |
+ <img :src="filePreviewUrls[idx]" alt="thumbnail" class="file-thumbnail" v-if="filePreviewUrls[idx]"> |
|
85 |
+ <div class="thumbnail-loading" v-else>Loading...</div> |
|
86 |
+ </div> |
|
87 |
+ <div class="file-name"> |
|
88 |
+ <img src="/client/resources/images/icon/imgicon.png" alt="fileicon"> |
|
89 |
+ <p>{{ file.name }}</p> |
|
90 |
+ </div> |
|
91 |
+ </div> |
|
92 |
+ <button type="button" class="cancel" @click="fnDelFile('new', idx)"><b>✕</b></button> |
|
77 | 93 |
</div> |
78 |
- <button type="button" class="cancel" @click="fnDelFile('new', idx)"><b>✕</b></button> |
|
79 | 94 |
</div> |
80 | 95 |
</div> |
81 | 96 |
</li> |
... | ... | @@ -124,6 +139,7 @@ |
124 | 139 |
isDragging: false, |
125 | 140 |
|
126 | 141 |
fileNames: [], |
142 |
+ filePreviewUrls: [], // 새로 추가된 파일들의 미리보기 URL 저장 |
|
127 | 143 |
|
128 | 144 |
// 등록/수정 요청 객체 |
129 | 145 |
requestDTO: { |
... | ... | @@ -158,6 +174,26 @@ |
158 | 174 |
}, |
159 | 175 |
|
160 | 176 |
methods: { |
177 |
+ // 기존 파일의 URL 생성 (서버에서 제공하는 파일 URL) |
|
178 |
+ getFileUrl(file) { |
|
179 |
+ return file.filePath || '/client/resources/images/icon/imgicon.png'; |
|
180 |
+ }, |
|
181 |
+ |
|
182 |
+ // 이미지 로드 에러 처리 |
|
183 |
+ handleImageError(event) { |
|
184 |
+ event.target.src = '/client/resources/images/icon/imgicon.png'; |
|
185 |
+ }, |
|
186 |
+ |
|
187 |
+ // 파일 미리보기 URL 생성 |
|
188 |
+ createFilePreview(file) { |
|
189 |
+ return new Promise((resolve) => { |
|
190 |
+ const reader = new FileReader(); |
|
191 |
+ reader.onload = (e) => resolve(e.target.result); |
|
192 |
+ reader.onerror = () => resolve(null); |
|
193 |
+ reader.readAsDataURL(file); |
|
194 |
+ }); |
|
195 |
+ }, |
|
196 |
+ |
|
161 | 197 |
// 상세 조회 |
162 | 198 |
async fnFindDcry() { |
163 | 199 |
try { |
... | ... | @@ -184,6 +220,7 @@ |
184 | 220 |
this.requestDTO.files = nesDta.files.length > 0 ? nesDta.files : []; // 기존 첨부파일 |
185 | 221 |
|
186 | 222 |
this.multipartFiles = []; |
223 |
+ this.filePreviewUrls = []; |
|
187 | 224 |
this.selectedCtgries = nesDta.ctgrys.length > 0 ? nesDta.ctgrys : []; |
188 | 225 |
}, |
189 | 226 |
|
... | ... | @@ -210,7 +247,7 @@ |
210 | 247 |
}, |
211 | 248 |
|
212 | 249 |
// 파일 업로드 처리 함수 |
213 |
- processFiles(files) { |
|
250 |
+ async processFiles(files) { |
|
214 | 251 |
const allowedTypes = ['jpg', 'jpeg', 'png', 'gif']; // 이미지 파일만 허용 |
215 | 252 |
const maxSize = 10 * 1024 * 1024 * 1024; // 10GB |
216 | 253 |
|
... | ... | @@ -239,6 +276,10 @@ |
239 | 276 |
} |
240 | 277 |
|
241 | 278 |
this.multipartFiles.push(file); |
279 |
+ |
|
280 |
+ // 미리보기 URL 생성 |
|
281 |
+ const previewUrl = await this.createFilePreview(file); |
|
282 |
+ this.filePreviewUrls.push(previewUrl); |
|
242 | 283 |
} |
243 | 284 |
}, |
244 | 285 |
|
... | ... | @@ -253,6 +294,7 @@ |
253 | 294 |
fnDelFile(type, separator) { |
254 | 295 |
if (type === 'new') { |
255 | 296 |
this.multipartFiles.splice(separator, 1); |
297 |
+ this.filePreviewUrls.splice(separator, 1); // 미리보기 URL도 함께 삭제 |
|
256 | 298 |
|
257 | 299 |
// 모든 새 파일이 삭제된 경우 input 요소 초기화 |
258 | 300 |
if (this.multipartFiles.length === 0) { |
... | ... | @@ -323,6 +365,20 @@ |
323 | 365 |
} |
324 | 366 |
} |
325 | 367 |
|
368 |
+ // 파일 추가 |
|
369 |
+ if (this.multipartFiles.length > 0) { |
|
370 |
+ for (let file of this.multipartFiles) { |
|
371 |
+ formData.append("multipartFiles", file); |
|
372 |
+ } |
|
373 |
+ } |
|
374 |
+ |
|
375 |
+ // 기존파일 수정 |
|
376 |
+ if (!this.$isEmpty(this.pageId) && this.requestDTO.files.length > 0) { |
|
377 |
+ for (let file of this.requestDTO.files) { |
|
378 |
+ formData.append("files", file.fileId); |
|
379 |
+ } |
|
380 |
+ } |
|
381 |
+ |
|
326 | 382 |
// API 통신 |
327 | 383 |
const response = this.$isEmpty(this.pageId) ? await saveNesDtaProc(formData) : await updateNesDtaProc(formData); |
328 | 384 |
alert(this.$isEmpty(this.pageId) ? "등록되었습니다." : "수정되었습니다."); |
... | ... | @@ -379,4 +435,35 @@ |
379 | 435 |
border-color: #1890ff; |
380 | 436 |
background-color: rgba(24, 144, 255, 0.1); |
381 | 437 |
} |
438 |
+ |
|
439 |
+/* 썸네일 관련 스타일 */ |
|
440 |
+.file-item-with-thumbnail { |
|
441 |
+ display: flex; |
|
442 |
+ align-items: center; |
|
443 |
+ gap: 12px; |
|
444 |
+} |
|
445 |
+ |
|
446 |
+.thumbnail-container { |
|
447 |
+ flex-shrink: 0; |
|
448 |
+} |
|
449 |
+ |
|
450 |
+.file-thumbnail { |
|
451 |
+ width: 120px; |
|
452 |
+ height: 80px; |
|
453 |
+ object-fit: cover; |
|
454 |
+ border-radius: 4px; |
|
455 |
+} |
|
456 |
+ |
|
457 |
+.thumbnail-loading { |
|
458 |
+ width: 120px; |
|
459 |
+ height: 80px; |
|
460 |
+ background-color: #f5f5f5; |
|
461 |
+ border-radius: 4px; |
|
462 |
+ border: 1px solid #ddd; |
|
463 |
+ display: flex; |
|
464 |
+ align-items: center; |
|
465 |
+ justify-content: center; |
|
466 |
+ font-size: 12px; |
|
467 |
+ color: #666; |
|
468 |
+} |
|
382 | 469 |
</style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/bbsNesDta/NewsReleaseSearch.vue
+++ client/views/pages/bbsNesDta/NewsReleaseSearch.vue
... | ... | @@ -103,7 +103,7 @@ |
103 | 103 |
endYear: null, |
104 | 104 |
searchTy: "N", // 스크랩 자료 고정 |
105 | 105 |
searchCtgries: [], |
106 |
- order: "rgsde", |
|
106 |
+ order: "prdctn_year", |
|
107 | 107 |
// 페이지네이션 |
108 | 108 |
currentPage: 1, // 현재 페이지 |
109 | 109 |
recordSize: 24, // 한 페이지에 표시할 데이터 개수 |
--- client/views/pages/main/TotalSearch.vue
+++ client/views/pages/main/TotalSearch.vue
... | ... | @@ -58,7 +58,7 @@ |
58 | 58 |
startYear: null, |
59 | 59 |
endYear: null, |
60 | 60 |
searchCtgries: [], |
61 |
- order: "rgsde", |
|
61 |
+ order: "prdctn_year", |
|
62 | 62 |
}, |
63 | 63 |
|
64 | 64 |
// URL 파라미터로부터 가져온 초기 검색 조건 |
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?