
--- client/resources/css/user/layout.css
+++ client/resources/css/user/layout.css
... | ... | @@ -162,4 +162,5 @@ |
162 | 162 |
position: fixed; |
163 | 163 |
right: 210px; |
164 | 164 |
bottom: 101px; |
165 |
+ z-index: 555; |
|
165 | 166 |
}(파일 끝에 줄바꿈 문자 없음) |
--- client/resources/css/user/sub.css
+++ client/resources/css/user/sub.css
... | ... | @@ -975,6 +975,7 @@ |
975 | 975 |
.swiper-button-next { |
976 | 976 |
right: 20px; |
977 | 977 |
} |
978 |
+ |
|
978 | 979 |
|
979 | 980 |
} |
980 | 981 |
|
... | ... | @@ -1044,6 +1045,43 @@ |
1044 | 1045 |
|
1045 | 1046 |
|
1046 | 1047 |
} |
1048 |
+.loading-overlay { |
|
1049 |
+ position: fixed; |
|
1050 |
+ top: 0; |
|
1051 |
+ left: 0; |
|
1052 |
+ right: 0; |
|
1053 |
+ bottom: 0; |
|
1054 |
+ background-color: rgba(0, 0, 0, 0.5); |
|
1055 |
+ display: flex; |
|
1056 |
+ flex-direction: column; |
|
1057 |
+ justify-content: center; |
|
1058 |
+ align-items: center; |
|
1059 |
+ z-index: 9999; |
|
1060 |
+ p{color: #fff;} |
|
1061 |
+ p:first-of-type { |
|
1062 |
+ font-size: 30px; |
|
1063 |
+ font-family: "Pretendard-B"; |
|
1064 |
+ margin-top: 28px; |
|
1065 |
+ } |
|
1066 |
+ p:last-of-type { |
|
1067 |
+ font-size: 22px; |
|
1068 |
+ font-family: "Pretendard-M"; |
|
1069 |
+ margin-top: 18px; |
|
1070 |
+ } |
|
1071 |
+ } |
|
1072 |
+ .loading-spinner { |
|
1073 |
+ background: url(../../images/loading.png); |
|
1074 |
+ width: 178px; |
|
1075 |
+ height: 178px; |
|
1076 |
+ } |
|
1077 |
+ @keyframes spin { |
|
1078 |
+ 0% { |
|
1079 |
+ transform: rotate(0deg); |
|
1080 |
+ } |
|
1081 |
+ 100% { |
|
1082 |
+ transform: rotate(360deg); |
|
1083 |
+ } |
|
1084 |
+ } |
|
1047 | 1085 |
|
1048 | 1086 |
/* 회원관리, 카테고리 관리 */ |
1049 | 1087 |
.management { |
+++ client/resources/images/loading.png
Binary file is not shown |
--- client/views/pages/bbsDcry/photo/PicHistoryDetail.vue
+++ client/views/pages/bbsDcry/photo/PicHistoryDetail.vue
... | ... | @@ -26,14 +26,16 @@ |
26 | 26 |
<div> |
27 | 27 |
<div class="gallery"> |
28 | 28 |
<div class="main-swiper"> |
29 |
- <swiper :style="{ '--swiper-navigation-color': '#fff', '--swiper-pagination-color': '#fff' }" :loop="true" :spaceBetween="10" :thumbs="{ swiper: thumbsSwiper }" :modules="modules" class="mySwiper2"> |
|
29 |
+ <swiper :style="{ '--swiper-navigation-color': '#fff', '--swiper-pagination-color': '#fff' }" :loop="true" |
|
30 |
+ :spaceBetween="10" :thumbs="{ swiper: thumbsSwiper }" :modules="modules" class="mySwiper2"> |
|
30 | 31 |
<swiper-slide v-for="(item, idx) of findResult.files" :key="idx"> |
31 | 32 |
<img :src="item.filePath" :alt="item.fileNm" /> |
32 | 33 |
</swiper-slide> |
33 | 34 |
</swiper> |
34 | 35 |
</div> |
35 | 36 |
<div class="thumbnail"> |
36 |
- <swiper @swiper="setThumbsSwiper" :spaceBetween="20" :slidesPerView="4" :freeMode="true" :watchSlidesProgress="true" :modules="modules" :navigation="true" class="mySwiper"> |
|
37 |
+ <swiper @swiper="setThumbsSwiper" :spaceBetween="20" :slidesPerView="4" :freeMode="true" |
|
38 |
+ :watchSlidesProgress="true" :modules="modules" :navigation="true" class="mySwiper"> |
|
37 | 39 |
<swiper-slide v-for="(item, idx) of findResult.files" :key="idx"> |
38 | 40 |
<input type="checkbox" :id="'photo_' + idx" :value="item.fileId" v-model="selectedFiles" @click.stop /> |
39 | 41 |
<img :src="item.filePath" :alt="item.fileNm" /> |
... | ... | @@ -44,6 +46,13 @@ |
44 | 46 |
<div class="btn-group"> |
45 | 47 |
<button class="select-down" @click="fnDownload('selected')">선택 다운로드</button> |
46 | 48 |
<button class="all-down" @click="fnDownload('all')">전체 다운로드</button> |
49 |
+ <div v-if="loading" class="loading-overlay"> |
|
50 |
+ <div class="loading-spinner"></div> |
|
51 |
+ <div> |
|
52 |
+ <p>다운로드 중입니다</p> |
|
53 |
+ <p>잠시만 기다려주세요</p> |
|
54 |
+ </div> |
|
55 |
+ </div> |
|
47 | 56 |
</div> |
48 | 57 |
</div> |
49 | 58 |
</form> |
... | ... | @@ -139,6 +148,7 @@ |
139 | 148 |
pageId: null, |
140 | 149 |
findResult: {}, |
141 | 150 |
selectedFiles: [], |
151 |
+ loading: false, |
|
142 | 152 |
}; |
143 | 153 |
}, |
144 | 154 |
|
... | ... | @@ -178,10 +188,14 @@ |
178 | 188 |
|
179 | 189 |
// 파일 다운로드 |
180 | 190 |
async fnDownload(type) { |
191 |
+ // Set loading to true when download starts |
|
192 |
+ this.loading = true; |
|
193 |
+ |
|
181 | 194 |
// 유효성 검사 |
182 | 195 |
if (type === 'selected') { |
183 | 196 |
if (this.selectedFiles.length === 0) { |
184 | 197 |
alert("파일을 1개 이상 선택하거나 전체 다운로드를 클릭해주세요."); |
198 |
+ this.loading = false; // Hide loading if validation fails |
|
185 | 199 |
return; |
186 | 200 |
} |
187 | 201 |
} |
... | ... | @@ -201,6 +215,7 @@ |
201 | 215 |
let isMultiple = fileList.length > 1; |
202 | 216 |
let fileIds = isMultiple ? fileList : fileList[0]; |
203 | 217 |
|
218 |
+ // Call the API to get the file data |
|
204 | 219 |
const response = isMultiple ? await multiFileDownloadProc(fileIds) : await fileDownloadProc(fileIds); |
205 | 220 |
|
206 | 221 |
// 파일명 추출 부분 수정 |
... | ... | @@ -216,17 +231,19 @@ |
216 | 231 |
|
217 | 232 |
// 파일 다운로드 처리 |
218 | 233 |
const blob = new Blob([response.data]); |
219 |
- const url = window.URL.createObjectURL(blob); |
|
220 |
- const link = document.createElement('a'); |
|
221 |
- link.href = url; |
|
222 |
- link.setAttribute('download', filename); |
|
223 |
- document.body.appendChild(link); |
|
224 |
- link.click(); |
|
234 |
+ const downloadUrl = window.URL.createObjectURL(blob); |
|
235 |
+ const downloadLink = document.createElement('a'); |
|
236 |
+ downloadLink.href = downloadUrl; |
|
237 |
+ downloadLink.setAttribute('download', filename); |
|
238 |
+ document.body.appendChild(downloadLink); |
|
239 |
+ downloadLink.click(); |
|
225 | 240 |
} catch (error) { |
226 | 241 |
alert('파일 다운로드 중 오류가 발생했습니다.'); |
227 | 242 |
} finally { |
228 |
- // 리소스 정리 |
|
243 |
+ // Hide loading spinner and clean up |
|
229 | 244 |
setTimeout(() => { |
245 |
+ this.loading = false; // Hide loading spinner |
|
246 |
+ |
|
230 | 247 |
if (url) { |
231 | 248 |
window.URL.revokeObjectURL(url); |
232 | 249 |
} |
--- client/views/pages/bbsDcry/video/VideoHistoryDetail.vue
+++ client/views/pages/bbsDcry/video/VideoHistoryDetail.vue
... | ... | @@ -62,6 +62,10 @@ |
62 | 62 |
<button type="button" class="blue-line" @click="fnMoveTo('edit', pageId)">수정</button> |
63 | 63 |
<button type="button" class="gray-line-bg" @click="fnMoveTo('list')">목록</button> |
64 | 64 |
<button type="button" class="gradient" @click="fnDownload">다운로드</button> |
65 |
+ <div v-if="loading" class="loading-overlay"> |
|
66 |
+ <div class="loading-spinner"></div> |
|
67 |
+ <p>파일을 다운로드 중입니다...</p> |
|
68 |
+ </div> |
|
65 | 69 |
</div> |
66 | 70 |
</div> |
67 | 71 |
</template> |
... | ... | @@ -93,6 +97,7 @@ |
93 | 97 |
pageId: null, |
94 | 98 |
findResult: {}, |
95 | 99 |
selectedFiles: [], |
100 |
+ loading: false, |
|
96 | 101 |
}; |
97 | 102 |
}, |
98 | 103 |
|
... | ... | @@ -134,6 +139,7 @@ |
134 | 139 |
async fnDownload() { |
135 | 140 |
let url = null; |
136 | 141 |
let link = null; |
142 |
+ this.loading = true; // Show loading spinner |
|
137 | 143 |
|
138 | 144 |
try { |
139 | 145 |
// 파일 ID 수집 |
... | ... | @@ -166,6 +172,7 @@ |
166 | 172 |
if (link && link.parentNode) { |
167 | 173 |
document.body.removeChild(link); |
168 | 174 |
} |
175 |
+ this.loading = false; // Hide loading spinner |
|
169 | 176 |
}, 100); |
170 | 177 |
} |
171 | 178 |
}, |
--- client/views/pages/main/Main.vue
+++ client/views/pages/main/Main.vue
... | ... | @@ -4,22 +4,12 @@ |
4 | 4 |
delay: 2500, |
5 | 5 |
disableOnInteraction: false, |
6 | 6 |
}" :pagination="{ |
7 |
- type: 'progressbar', |
|
8 |
- }" :navigation="true" :modules="modules" @slideChange="onSlideChange" class="mySwiper"> |
|
7 |
+ type: ['fraction', 'progressbar'], progressbarOpposite: true, |
|
8 |
+ }" :navigation="true" :modules="modules" class="mySwiper" :allowTouchMove="false"> |
|
9 | 9 |
<swiper-slide v-for="(item, index) in slides" :key="index"> |
10 | 10 |
<img :src="item.img" :alt="item.alt" /> |
11 | 11 |
</swiper-slide> |
12 |
- <div class="pagination"> |
|
13 |
- <div class="page-count">{{ currentSlide }} / {{ totalSlides }}</div> |
|
14 |
- <div class="btn-control"> |
|
15 |
- <button @click="play"> |
|
16 |
- <CaretRightOutlined /> |
|
17 |
- </button> |
|
18 |
- <button @click="stop"> |
|
19 |
- <PauseOutlined /> |
|
20 |
- </button> |
|
21 |
- </div> |
|
22 |
- </div> |
|
12 |
+ |
|
23 | 13 |
</swiper> |
24 | 14 |
<div class="search-wrap"> |
25 | 15 |
<div class="search-area"> |
... | ... | @@ -31,7 +21,7 @@ |
31 | 21 |
<option value="bodo">보도자료</option> |
32 | 22 |
</select> |
33 | 23 |
<div class="line"></div> |
34 |
- <input type="text" placeholder="검색어를 입력하세요" v-model="searchText" @keyup.enter="fnMoveTo('TotalSearch')"> |
|
24 |
+ <div style="width: 60%;"><input type="text" placeholder="검색어를 입력하세요" v-model="searchText" @keyup.enter="fnMoveTo('TotalSearch')"></div> |
|
35 | 25 |
<button type="button" class="search-btn" @click="fnMoveTo('TotalSearch')"><img :src="search" alt=""></button> |
36 | 26 |
</div> |
37 | 27 |
<div class="total-search"> |
... | ... | @@ -51,7 +41,7 @@ |
51 | 41 |
</template> |
52 | 42 |
</ul> |
53 | 43 |
<div class="current-banner"> |
54 |
- <div><span>기록물 현황</span><img :src="direct" alt=""></div> |
|
44 |
+ <div><span>기록물 현황</span></div> |
|
55 | 45 |
</div> |
56 | 46 |
</div> |
57 | 47 |
</div> |
... | ... | @@ -72,7 +62,7 @@ |
72 | 62 |
<div class="new-pic"> |
73 | 63 |
<div v-for="(item, idx2) in tabContent.list" :key="idx2" class="box-wrap"> |
74 | 64 |
<div class="box" @click="fnMoveTo(tabContent.view, item.dcryId)"> |
75 |
- <img :src="item.files[0].filePath" :alt="item.sj" class="tab-image" /> |
|
65 |
+ <div class="img-area"><img :src="item.files[0].filePath" :alt="item.sj" class="tab-image" /></div> |
|
76 | 66 |
<div class="info"> |
77 | 67 |
<p>{{ item.sj }}</p> |
78 | 68 |
<span>{{ $dotFormatDate(item.rgsde) }}</span> |
... | ... | @@ -82,7 +72,7 @@ |
82 | 72 |
<!-- 게시물이 없는 경우 --> |
83 | 73 |
<div v-for="i in Math.max(0, 3 - tabContent.list.length)" :key="`empty-${i}`" class="box-wrap"> |
84 | 74 |
<div class="box"> |
85 |
- <img :src="noimg" class="tab-image" /> |
|
75 |
+ <div class="img-area"><img :src="noimg" class="tab-image" /></div> |
|
86 | 76 |
<div class="info"> |
87 | 77 |
<p>등록된 게시물이 없습니다.</p> |
88 | 78 |
</div> |
... | ... | @@ -103,7 +93,7 @@ |
103 | 93 |
<div class="media-wrap"> |
104 | 94 |
<template v-for="(item, idx) of mediaContents" :key="idx"> |
105 | 95 |
<div class="media-box" @click="fnMoveTo('MediaVideoDetail', item.mediaVidoId)"> |
106 |
- <img :src="item.url" :alt="item.sj" class="media-image" /> |
|
96 |
+ <div class="img-area"><img :src="item.url" :alt="item.sj" class="media-image" /></div> |
|
107 | 97 |
<div class="info"> |
108 | 98 |
<p>{{ item.sj }}</p> |
109 | 99 |
<span>{{ $dotFormatDate(item.rgsde) }}</span> |
... | ... | @@ -112,7 +102,7 @@ |
112 | 102 |
</template> |
113 | 103 |
<!-- 게시글이 없는 경우 --> |
114 | 104 |
<div v-for="i in Math.max(0, 2 - mediaContents.length)" :key="`empty-${i}`" class="media-box"> |
115 |
- <img :src="nomedia" alt="" class="media-image" /> |
|
105 |
+ <div class="img-area"><img :src="nomedia" alt="" class="media-image" /></div> |
|
116 | 106 |
<div class="info"> |
117 | 107 |
<p>등록된 게시글이 없습니다.</p> |
118 | 108 |
</div> |
... | ... | @@ -253,30 +243,7 @@ |
253 | 243 |
selectTab(index) { |
254 | 244 |
this.selectedTab = index; // Update the selected tab index |
255 | 245 |
}, |
256 |
- play() { |
|
257 |
- const swiper = this.$refs.swiper.swiper; |
|
258 |
- if (swiper && swiper.autoplay) { |
|
259 |
- swiper.autoplay.start(); // Start autoplay |
|
260 |
- } else { |
|
261 |
- console.warn('Swiper instance or autoplay is not available'); |
|
262 |
- } |
|
263 |
- }, |
|
264 |
- // Method to stop autoplay |
|
265 |
- stop() { |
|
266 |
- const swiper = this.$refs.swiper.swiper; |
|
267 |
- if (swiper && swiper.autoplay) { |
|
268 |
- swiper.autoplay.stop(); // Stop autoplay |
|
269 |
- } else { |
|
270 |
- console.warn('Swiper instance or autoplay is not available'); |
|
271 |
- } |
|
272 |
- }, |
|
273 |
- // Method to update the current slide and total slides |
|
274 |
- onSlideChange() { |
|
275 |
- const swiper = this.$refs.swiper.swiper; |
|
276 |
- if (swiper) { |
|
277 |
- this.currentSlide = swiper.realIndex + 1; // Get current slide (1-based index) |
|
278 |
- } |
|
279 |
- }, |
|
246 |
+ |
|
280 | 247 |
// 메인화면 정보 조회 |
281 | 248 |
async fnFindAllSttuses() { |
282 | 249 |
try { |
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?