
2025-04-01 하관우 게시물 사진 다운로드 수정, 멀티파트 리프레쉬 토큰 수정, 메뉴별 통계 수정정
@6d17d940bdb513313e0db546444b7ef70323524d
--- client/resources/api/index.js
+++ client/resources/api/index.js
... | ... | @@ -1,6 +1,35 @@ |
1 | 1 |
import axios from 'axios'; |
2 | 2 |
import store from "../../views/pages/AppStore"; |
3 | 3 |
|
4 |
+// 토큰 재발급 함수 (공통 함수로 분리) |
|
5 |
+const refreshToken = async () => { |
|
6 |
+ const res = await axios.post("/refresh/tknReissue.json", {}, { |
|
7 |
+ headers: { |
|
8 |
+ "Content-Type": "application/json; charset=UTF-8", |
|
9 |
+ }, |
|
10 |
+ }); |
|
11 |
+ |
|
12 |
+ if (res.status === 200) { |
|
13 |
+ console.log("토큰 재발급 성공! 굿~"); |
|
14 |
+ // 새로 발급 받은 AccessToken 저장 |
|
15 |
+ store.commit('setAuthorization', res.headers.authorization); |
|
16 |
+ |
|
17 |
+ // JWT 토큰 디코딩 |
|
18 |
+ const base64String = store.state.authorization.split('.')[1]; |
|
19 |
+ const base64 = base64String.replace(/-/g, '+').replace(/_/g, '/'); |
|
20 |
+ const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => { |
|
21 |
+ return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); |
|
22 |
+ }).join('')); |
|
23 |
+ const mbr = JSON.parse(jsonPayload); |
|
24 |
+ store.commit("setUserNm", mbr.userNm); // 사용자 이름 저장 |
|
25 |
+ store.commit('setRoles', mbr.roles); // 사용자 역할 저장 |
|
26 |
+ |
|
27 |
+ return true; |
|
28 |
+ } |
|
29 |
+ |
|
30 |
+ throw new Error("토큰 재발급 요청 실패"); |
|
31 |
+}; |
|
32 |
+ |
|
4 | 33 |
// Axios 클라이언트 생성 함수 |
5 | 34 |
const createClient = (contentType) => { |
6 | 35 |
const client = axios.create({ |
... | ... | @@ -32,45 +61,36 @@ |
32 | 61 |
const originalReq = error.config; |
33 | 62 |
|
34 | 63 |
// 403 에러 처리 |
35 |
- if (error.response.status === 403) { |
|
64 |
+ if (error.response && error.response.status === 403) { |
|
36 | 65 |
alert('접근 권한이 없습니다.'); |
37 | 66 |
window.history.back(); |
38 | 67 |
return Promise.reject(error); |
39 | 68 |
} |
40 | 69 |
|
41 | 70 |
// 401 에러 처리 (토큰 만료) |
42 |
- if (error.response.status === 401 && error.response.data.message === '로그인 시간이 만료되었습니다.' && !originalReq._retry) { |
|
43 |
- // 토큰 재발급 요청 |
|
71 |
+ if (error.response && |
|
72 |
+ error.response.status === 401 && |
|
73 |
+ error.response.data.message === '로그인 시간이 만료되었습니다.' && |
|
74 |
+ !originalReq._retry) { |
|
75 |
+ |
|
76 |
+ originalReq._retry = true; // 재시도 플래그 설정 |
|
77 |
+ |
|
44 | 78 |
try { |
45 |
- const res = await axios.post("/refresh/tknReissue.json", {}, { |
|
46 |
- headers: { |
|
47 |
- "Content-Type": "application/json; charset=UTF-8", |
|
48 |
- }, |
|
49 |
- }); |
|
50 |
- |
|
51 |
- // 응답 상태가 200일 경우에만 처리 |
|
52 |
- if (res.status === 200) { |
|
53 |
- console.log("토큰 재발급 성공! 굿~"); |
|
54 |
- // 새로 발급 받은 AccessToken 저장 |
|
55 |
- store.commit('setAuthorization', res.headers.authorization); |
|
56 |
- originalReq.headers.Authorization = store.state.authorization; // 새로 발급 받은 AccessToken을 기존 요청에 추가 |
|
57 |
- |
|
58 |
- // JWT 토큰 디코딩 |
|
59 |
- const base64String = store.state.authorization.split('.')[1]; |
|
60 |
- const base64 = base64String.replace(/-/g, '+').replace(/_/g, '/'); |
|
61 |
- const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => { |
|
62 |
- return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); |
|
63 |
- }).join('')); |
|
64 |
- const mbr = JSON.parse(jsonPayload); |
|
65 |
- store.commit("setUserNm", mbr.userNm); // 사용자 이름 저장 |
|
66 |
- store.commit('setRoles', mbr.roles); // 사용자 역할 저장 |
|
67 |
- |
|
68 |
- originalReq._retry = true; // 재시도 플래그 설정 |
|
69 |
- return client(originalReq); // 원래 요청 재시도 |
|
70 |
- } else { |
|
71 |
- // 200이 아닌 경우 |
|
72 |
- throw new Error("토큰 재발급 요청 실패"); |
|
79 |
+ // 공통 토큰 재발급 함수 호출 |
|
80 |
+ await refreshToken(); |
|
81 |
+ |
|
82 |
+ // 중요: Content-Type 헤더 보존 |
|
83 |
+ originalReq.headers.Authorization = store.state.authorization; |
|
84 |
+ |
|
85 |
+ // multipart/form-data 요청의 경우 data 처리 방식이 다름 |
|
86 |
+ if (originalReq.headers['Content-Type'] && |
|
87 |
+ originalReq.headers['Content-Type'].includes('multipart/form-data')) { |
|
88 |
+ // multipart의 경우 FormData가 이미 생성되어 있으므로 그대로 사용 |
|
89 |
+ // 특별한 처리 없이 원래 요청 재시도 |
|
73 | 90 |
} |
91 |
+ |
|
92 |
+ // 원래 요청 재시도 |
|
93 |
+ return client(originalReq); |
|
74 | 94 |
} catch (refreshError) { |
75 | 95 |
const redirect = window.location.pathname + window.location.search; |
76 | 96 |
sessionStorage.setItem("redirect", redirect); |
... | ... | @@ -94,4 +114,4 @@ |
94 | 114 |
// 멀티파트 파일 업로드를 위한 fileClient |
95 | 115 |
const fileClient = createClient('multipart/form-data'); |
96 | 116 |
|
97 |
-export { apiClient, fileClient }; // 두 클라이언트를 내보냄 |
|
117 |
+export { apiClient, fileClient }; // 두 클라이언트를 내보냄(파일 끝에 줄바꿈 문자 없음) |
--- client/views/layout/Header.vue
+++ client/views/layout/Header.vue
... | ... | @@ -11,17 +11,17 @@ |
11 | 11 |
<li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" |
12 | 12 |
@click="updateMenuStats('MENU_00000001')">기록물 |
13 | 13 |
<div class="submenu"> |
14 |
- <p>• <router-link :to="{ path: '/PicHistorySearch.page' }">사진 기록물</router-link></p> |
|
14 |
+ <p>• <router-link :to="{ path: '/PicHistorySearch.page' }" @click="updateMenuStats('MENU_00000002')">사진 기록물</router-link></p> |
|
15 | 15 |
<div class="hr"></div> |
16 |
- <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }">영상 기록물</router-link></p> |
|
16 |
+ <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }" @click="updateMenuStats('MENU_00000003')">영상 기록물</router-link></p> |
|
17 | 17 |
</div> |
18 | 18 |
</li> |
19 | 19 |
<li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" |
20 | 20 |
@click="updateMenuStats('MENU_00000004')">언론에서 바라본 구미시 |
21 | 21 |
<div class="submenu"> |
22 |
- <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }">미디어 영상</router-link></p> |
|
22 |
+ <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }" @click="updateMenuStats('MENU_00000005')">미디어 영상</router-link></p> |
|
23 | 23 |
<div class="hr"></div> |
24 |
- <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }">보도자료</router-link></p> |
|
24 |
+ <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }" @click="updateMenuStats('MENU_00000006')">보도자료</router-link></p> |
|
25 | 25 |
</div> |
26 | 26 |
</li> |
27 | 27 |
<li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" |
... | ... | @@ -32,9 +32,22 @@ |
32 | 32 |
:to="{ path: '/CategoryManagement.page' }">카테고리 관리</router-link></li> |
33 | 33 |
|
34 | 34 |
<li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'" |
35 |
- @click="updateMenuStats('MENU_00000001')">기록물</li> |
|
35 |
+ @click="updateMenuStats('MENU_00000001')">기록물 |
|
36 |
+ <div class="submenu"> |
|
37 |
+ <p>• <router-link :to="{ path: '/PicHistorySearch.page' }" @click="updateMenuStats('MENU_00000002')">사진 기록물</router-link></p> |
|
38 |
+ <div class="hr"></div> |
|
39 |
+ <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }" @click="updateMenuStats('MENU_00000003')">영상 기록물</router-link></p> |
|
40 |
+ </div> |
|
41 |
+ </li> |
|
42 |
+ |
|
36 | 43 |
<li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'" |
37 |
- @click="updateMenuStats('MENU_00000004')">언론에서 바라본 구미시</li> |
|
44 |
+ @click="updateMenuStats('MENU_00000004')">언론에서 바라본 구미시 |
|
45 |
+ <div class="submenu"> |
|
46 |
+ <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }" @click="updateMenuStats('MENU_00000005')">미디어 영상</router-link></p> |
|
47 |
+ <div class="hr"></div> |
|
48 |
+ <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }" @click="updateMenuStats('MENU_00000006')">보도자료</router-link></p> |
|
49 |
+ </div> |
|
50 |
+ </li> |
|
38 | 51 |
</ul> |
39 | 52 |
</nav> |
40 | 53 |
</div> |
... | ... | @@ -64,17 +77,17 @@ |
64 | 77 |
<li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000001')"> |
65 | 78 |
<h6>기록물</h6> |
66 | 79 |
<div class="submenu"> |
67 |
- <p>• <router-link :to="{ path: '/PicHistorySearch.page' }" @click="closeMenu">사진 기록물</router-link></p> |
|
80 |
+ <p>• <router-link :to="{ path: '/PicHistorySearch.page' }" @click="updateMenuStats('MENU_00000002')">사진 기록물</router-link></p> |
|
68 | 81 |
<div class="hr pink"></div> |
69 |
- <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }" @click="closeMenu">영상 기록물</router-link></p> |
|
82 |
+ <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }" @click="updateMenuStats('MENU_00000003')">영상 기록물</router-link></p> |
|
70 | 83 |
</div> |
71 | 84 |
</li> |
72 | 85 |
<li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000004')"> |
73 | 86 |
<h6>언론에서 바라본 구미시</h6> |
74 | 87 |
<div class="submenu"> |
75 |
- <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }" @click="closeMenu">미디어 영상</router-link></p> |
|
88 |
+ <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }" @click="updateMenuStats('MENU_00000004')">미디어 영상</router-link></p> |
|
76 | 89 |
<div class="hr pink"></div> |
77 |
- <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }" @click="closeMenu">보도자료</router-link></p> |
|
90 |
+ <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }" @click="updateMenuStats('MENU_00000005')">보도자료</router-link></p> |
|
78 | 91 |
</div> |
79 | 92 |
</li> |
80 | 93 |
<li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000007')"> |
... | ... | @@ -91,10 +104,20 @@ |
91 | 104 |
</li> |
92 | 105 |
|
93 | 106 |
<li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'" @click="updateMenuStats('MENU_00000001')"> |
94 |
- <router-link :to="{ path: '/PicHistorySearch.page' }" @click="closeMenu">기록물</router-link> |
|
107 |
+ <h6>기록물</h6> |
|
108 |
+ <div class="submenu"> |
|
109 |
+ <p>• <router-link :to="{ path: '/PicHistorySearch.page' }" @click="updateMenuStats('MENU_00000002')">사진 기록물</router-link></p> |
|
110 |
+ <div class="hr pink"></div> |
|
111 |
+ <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }" @click="updateMenuStats('MENU_00000003')">영상 기록물</router-link></p> |
|
112 |
+ </div> |
|
95 | 113 |
</li> |
96 | 114 |
<li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'" @click="updateMenuStats('MENU_00000004')"> |
97 |
- <router-link :to="{ path: '/MediaVideoSearch.page' }" @click="closeMenu">언론에서 바라본 구미시</router-link> |
|
115 |
+ <h6>언론에서 바라본 구미시</h6> |
|
116 |
+ <div class="submenu"> |
|
117 |
+ <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }" @click="updateMenuStats('MENU_00000004')">미디어 영상</router-link></p> |
|
118 |
+ <div class="hr pink"></div> |
|
119 |
+ <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }" @click="updateMenuStats('MENU_00000005')">보도자료</router-link></p> |
|
120 |
+ </div> |
|
98 | 121 |
</li> |
99 | 122 |
</ul> |
100 | 123 |
</nav> |
--- 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" /> |
39 | 41 |
<img :src="item.filePath" :alt="item.fileNm" /> |
... | ... | @@ -189,12 +191,18 @@ |
189 | 191 |
|
190 | 192 |
try { |
191 | 193 |
// 파일 ID 수집 |
192 |
- let fileIds = this.selectedFiles[0]; |
|
193 |
- if (type === 'all' && this.findResult.files.length > 1) { |
|
194 |
+ let fileIds; |
|
195 |
+ if (type === 'selected') { |
|
196 |
+ if (this.selectedFiles.length == 1) { |
|
197 |
+ fileIds = this.selectedFiles[0]; |
|
198 |
+ } else { |
|
199 |
+ fileIds = this.selectedFiles.join(','); |
|
200 |
+ } |
|
201 |
+ } else if (type === 'all' && this.findResult.files.length > 1) { |
|
194 | 202 |
fileIds = this.findResult.files.map(file => file.fileId).join(','); |
195 | 203 |
} |
196 | 204 |
|
197 |
- let isMultiple = fileIds.length > 1; |
|
205 |
+ let isMultiple = this.selectedFiles.length > 1; |
|
198 | 206 |
const response = isMultiple ? await multiFileDownloadProc(fileIds) : await fileDownloadProc(fileIds); |
199 | 207 |
|
200 | 208 |
// 파일명 조회 |
--- client/views/pages/bbsDcry/photo/PicHistoryInsert.vue
+++ client/views/pages/bbsDcry/photo/PicHistoryInsert.vue
... | ... | @@ -17,23 +17,23 @@ |
17 | 17 |
<dl> |
18 | 18 |
<dd> |
19 | 19 |
<label for="sj" class="require">제목</label> |
20 |
- <div class="wfull"><input type="text" id="sj" placeholder="제목을 입력하세요." v-model="reqDTO.sj"></div> |
|
20 |
+ <div class="wfull"><input type="text" id="sj" placeholder="제목을 입력하세요." v-model="requestDTO.sj"></div> |
|
21 | 21 |
</dd> |
22 | 22 |
<div class="hr"></div> |
23 | 23 |
<dd> |
24 | 24 |
<label for="prdctnYear">생산연도</label> |
25 |
- <input type="text" id="prdctnYear" placeholder="생산연도를 입력하세요" v-model="reqDTO.prdctnYear"> |
|
25 |
+ <input type="text" id="prdctnYear" placeholder="생산연도를 입력하세요" v-model="requestDTO.prdctnYear"> |
|
26 | 26 |
</dd> |
27 | 27 |
<div class="hr"></div> |
28 | 28 |
<dd> |
29 | 29 |
<label for="adres">주소</label> |
30 |
- <div class="wfull"><input type="text" id="adres" placeholder="주소를 입력하세요" v-model="reqDTO.adres"></div> |
|
30 |
+ <div class="wfull"><input type="text" id="adres" placeholder="주소를 입력하세요" v-model="requestDTO.adres"></div> |
|
31 | 31 |
</dd> |
32 | 32 |
<div class="hr"></div> |
33 | 33 |
<dd> |
34 | 34 |
<label for="text">내용</label> |
35 | 35 |
<div class="wfull"> |
36 |
- <EditorComponent v-model:contents="reqDTO.cn" /> |
|
36 |
+ <EditorComponent v-model:contents="requestDTO.cn" /> |
|
37 | 37 |
</div> |
38 | 38 |
</dd> |
39 | 39 |
<div class="hr"></div> |
... | ... | @@ -42,7 +42,8 @@ |
42 | 42 |
<p>카테고리</p><button type="button" class="category-add" @click="fnToggleModal">추가하기</button> |
43 | 43 |
</label> |
44 | 44 |
<ul class="category"> |
45 |
- <li v-for="(item, idx) of selectedCtgries" :key="idx">{{ item.ctgryNm }} <button type="button" class="cancel" @click="fnDelCtgry(item.ctgryId)"><b>✕</b></button></li> |
|
45 |
+ <li v-for="(item, idx) of selectedCtgries" :key="idx">{{ item.ctgryNm }} <button type="button" |
|
46 |
+ class="cancel" @click="fnDelCtgry(item.ctgryId)"><b>✕</b></button></li> |
|
46 | 47 |
</ul> |
47 | 48 |
</dd> |
48 | 49 |
<div class="hr"></div> |
... | ... | @@ -51,11 +52,14 @@ |
51 | 52 |
<ul class="wfull"> |
52 | 53 |
<li class="flex align-center"> |
53 | 54 |
<p>파일첨부</p> |
54 |
- <div class="invalid-feedback"><img :src="erroricon" alt=""><span>첨부파일은 건당 최대 10GB를 초과할 수 없습니다.</span></div> |
|
55 |
+ <div class="invalid-feedback"><img :src="erroricon" alt=""><span>첨부파일은 건당 최대 10GB를 초과할 수 없습니다.</span> |
|
56 |
+ </div> |
|
55 | 57 |
</li> |
56 | 58 |
<li class="file-insert"> |
57 |
- <input type="file" id="fileInput" class="file-input" multiple accept="image/jpeg,image/png,image/gif,image/jpg" @change="handleFileSelect"> |
|
58 |
- <label for="fileInput" class="file-label mb-20" @dragover.prevent="handleDragOver" @dragleave.prevent="handleDragLeave" @drop.prevent="handleDrop" :class="{ 'drag-over': isDragging }"> |
|
59 |
+ <input type="file" id="fileInput" class="file-input" multiple |
|
60 |
+ accept="image/jpeg,image/png,image/gif,image/jpg" @change="handleFileSelect"> |
|
61 |
+ <label for="fileInput" class="file-label mb-20" @dragover.prevent="handleDragOver" |
|
62 |
+ @dragleave.prevent="handleDragLeave" @drop.prevent="handleDrop" :class="{ 'drag-over': isDragging }"> |
|
59 | 63 |
<div class="flex-center align-center"> |
60 | 64 |
<img :src="fileicon" alt=""> |
61 | 65 |
<p>파일첨부하기</p> |
... | ... | @@ -64,7 +68,7 @@ |
64 | 68 |
</label> |
65 | 69 |
<p class="mb-10">파일목록</p> |
66 | 70 |
<div id="fileNames" class="file-names"> |
67 |
- <div v-if="reqDTO.files.length === 0 && multipartFiles.length === 0">선택된 파일이 없습니다.</div> |
|
71 |
+ <div v-if="requestDTO.files.length === 0 && multipartFiles.length === 0">선택된 파일이 없습니다.</div> |
|
68 | 72 |
<!-- 새로 추가된 파일 목록 --> |
69 | 73 |
<div v-for="(file, idx) of multipartFiles" :key="idx" class="flex-sp-bw mb-5 file-wrap"> |
70 | 74 |
<div class="file-name"> |
... | ... | @@ -74,7 +78,7 @@ |
74 | 78 |
<button type="button" class="cancel" @click="fnDelFile('new', idx)"><b>✕</b></button> |
75 | 79 |
</div> |
76 | 80 |
<!-- 기존 등록된 파일 목록 --> |
77 |
- <div v-for="(file, idx) of reqDTO.files" :key="idx" class="flex-sp-bw mb-5 file-wrap"> |
|
81 |
+ <div v-for="(file, idx) of requestDTO.files" :key="idx" class="flex-sp-bw mb-5 file-wrap"> |
|
78 | 82 |
<div class="file-name"> |
79 | 83 |
<img src="/client/resources/images/icon/imgicon.png" alt="fileicon"> |
80 | 84 |
<p>{{ file.fileNm }}</p> |
... | ... | @@ -95,7 +99,8 @@ |
95 | 99 |
</button> |
96 | 100 |
</div> |
97 | 101 |
</div> |
98 |
- <CategorySelectModal v-if="isModalOpen" :selectedCtgries="selectedCtgries" @toggleModal="fnToggleModal" @addCtgries="fnAddCtgries" /> |
|
102 |
+ <CategorySelectModal v-if="isModalOpen" :selectedCtgries="selectedCtgries" @toggleModal="fnToggleModal" |
|
103 |
+ @addCtgries="fnAddCtgries" /> |
|
99 | 104 |
</template> |
100 | 105 |
<script> |
101 | 106 |
import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; |
... | ... | @@ -132,7 +137,7 @@ |
132 | 137 |
fileNames: [], |
133 | 138 |
|
134 | 139 |
// 등록/수정 요청 객체 |
135 |
- reqDTO: { |
|
140 |
+ requestDTO: { |
|
136 | 141 |
dcryId: null, |
137 | 142 |
sj: null, |
138 | 143 |
cn: null, |
... | ... | @@ -179,20 +184,20 @@ |
179 | 184 |
} |
180 | 185 |
}, |
181 | 186 |
|
182 |
- // dcry > reqDTO |
|
187 |
+ // dcry > requestDTO |
|
183 | 188 |
copyToDcryReqDTO(dcry) { |
184 |
- const copyFields = Object.keys(this.reqDTO).filter(key => key !== 'dcryId' && key !== 'ty' && key !== 'files'); |
|
189 |
+ const copyFields = Object.keys(this.requestDTO).filter(key => key !== 'dcryId' && key !== 'ty' && key !== 'files'); |
|
185 | 190 |
copyFields.forEach(field => { |
186 |
- this.reqDTO[field] = this.$isEmpty(dcry[field]) ? null : dcry[field]; |
|
191 |
+ this.requestDTO[field] = this.$isEmpty(dcry[field]) ? null : dcry[field]; |
|
187 | 192 |
}); |
188 | 193 |
|
189 |
- this.reqDTO.ty = 'P'; // 사진기록물 |
|
190 |
- this.reqDTO.files = dcry.files.length > 0 ? dcry.files : []; // 기존 첨부파일 |
|
194 |
+ this.requestDTO.ty = 'P'; // 사진기록물 |
|
195 |
+ this.requestDTO.files = dcry.files.length > 0 ? dcry.files : []; // 기존 첨부파일 |
|
191 | 196 |
|
192 | 197 |
this.multipartFiles = []; |
193 | 198 |
this.selectedCtgries = dcry.ctgrys.length > 0 ? dcry.ctgrys : []; |
194 | 199 |
|
195 |
- console.log(this.reqDTO); |
|
200 |
+ console.log(this.requestDTO); |
|
196 | 201 |
}, |
197 | 202 |
|
198 | 203 |
// 카테고리 모달 열기/닫기 |
... | ... | @@ -263,19 +268,23 @@ |
263 | 268 |
if (type === 'new') { |
264 | 269 |
this.multipartFiles.splice(separator, 1); |
265 | 270 |
} else if (type === 'old') { |
266 |
- this.reqDTO.files = this.reqDTO.files.filter(item => item.fileId !== separator); |
|
271 |
+ this.requestDTO.files = this.requestDTO.files.filter(item => item.fileId !== separator); |
|
267 | 272 |
} |
268 | 273 |
}, |
269 | 274 |
|
270 | 275 |
// 등록 |
271 | 276 |
async submitForm() { |
272 | 277 |
// 유효성 검사 |
273 |
- if (!this.reqDTO.sj) { |
|
278 |
+ if (!this.requestDTO.sj) { |
|
274 | 279 |
alert("제목을 입력해 주세요."); |
275 | 280 |
return; |
276 | 281 |
} |
277 | 282 |
|
278 |
- if ((this.multipartFiles.length + this.requestDTO.files.length) == 0) { |
|
283 |
+ let count = this.multipartFiles.length |
|
284 |
+ if (!this.$isEmpty(this.pageId)) { |
|
285 |
+ count += this.requestDTO.files.length |
|
286 |
+ } |
|
287 |
+ if (count == 0) { |
|
279 | 288 |
alert("파일을 1개 이상 첨부해 주세요."); |
280 | 289 |
return; |
281 | 290 |
} |
... | ... | @@ -284,11 +293,11 @@ |
284 | 293 |
const formData = new FormData(); |
285 | 294 |
|
286 | 295 |
// 텍스트 데이터 추가 |
287 |
- formData.append('sj', this.reqDTO.sj); |
|
288 |
- formData.append('cn', this.reqDTO.cn); |
|
289 |
- formData.append('adres', this.reqDTO.adres); |
|
290 |
- formData.append('prdctnYear', this.reqDTO.prdctnYear); |
|
291 |
- formData.append('ty', this.reqDTO.ty); |
|
296 |
+ formData.append('sj', this.requestDTO.sj); |
|
297 |
+ formData.append('cn', this.requestDTO.cn); |
|
298 |
+ formData.append('adres', this.requestDTO.adres); |
|
299 |
+ formData.append('prdctnYear', this.requestDTO.prdctnYear); |
|
300 |
+ formData.append('ty', this.requestDTO.ty); |
|
292 | 301 |
|
293 | 302 |
// 게시물 아이디 |
294 | 303 |
if (!this.$isEmpty(this.pageId)) { |
... | ... | @@ -296,8 +305,8 @@ |
296 | 305 |
} |
297 | 306 |
|
298 | 307 |
// 파일 아이디 |
299 |
- if (!this.$isEmpty(this.reqDTO.fileId)) { |
|
300 |
- formData.append('fileId', this.reqDTO.fileId); |
|
308 |
+ if (!this.$isEmpty(this.requestDTO.fileId)) { |
|
309 |
+ formData.append('fileId', this.requestDTO.fileId); |
|
301 | 310 |
} |
302 | 311 |
|
303 | 312 |
// 카테고리 Ids 추가 |
... | ... | @@ -313,8 +322,8 @@ |
313 | 322 |
} |
314 | 323 |
|
315 | 324 |
// 기존파일 수정 |
316 |
- if (!this.$isEmpty(this.pageId) && this.reqDTO.files.length > 0) { |
|
317 |
- for (let file of this.reqDTO.files) { |
|
325 |
+ if (!this.$isEmpty(this.pageId) && this.requestDTO.files.length > 0) { |
|
326 |
+ for (let file of this.requestDTO.files) { |
|
318 | 327 |
formData.append("files", file.fileId); |
319 | 328 |
} |
320 | 329 |
} |
--- client/views/pages/bbsDcry/video/VideoHistoryInsert.vue
+++ client/views/pages/bbsDcry/video/VideoHistoryInsert.vue
... | ... | @@ -280,7 +280,11 @@ |
280 | 280 |
return; |
281 | 281 |
} |
282 | 282 |
|
283 |
- if ((this.multipartFiles.length + this.requestDTO.files.length) == 0) { |
|
283 |
+ let count = this.multipartFiles.length |
|
284 |
+ if (!this.$isEmpty(this.pageId)) { |
|
285 |
+ count += this.requestDTO.files.length |
|
286 |
+ } |
|
287 |
+ if (count == 0) { |
|
284 | 288 |
alert("파일을 1개 이상 첨부해 주세요."); |
285 | 289 |
return; |
286 | 290 |
} |
--- client/views/pages/bbsNesDta/NewsReleaseInsert.vue
+++ client/views/pages/bbsNesDta/NewsReleaseInsert.vue
... | ... | @@ -232,7 +232,11 @@ |
232 | 232 |
const maxSize = 10 * 1024 * 1024 * 1024; // 10GB |
233 | 233 |
|
234 | 234 |
// 유효성 검사 |
235 |
- if ((files.length + this.multipartFiles.length + this.requestDTO.files.length) > 1) { |
|
235 |
+ let count = files.length + this.multipartFiles.length |
|
236 |
+ if (!this.$isEmpty(this.pageId)) { |
|
237 |
+ count += this.requestDTO.files.length |
|
238 |
+ } |
|
239 |
+ if (count > 1) { |
|
236 | 240 |
alert("썸네일은 한 개만 등록 가능합니다."); |
237 | 241 |
return; |
238 | 242 |
} |
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?