하관우 하관우 04-01
2025-04-01 하관우 게시물 사진 다운로드 수정, 멀티파트 리프레쉬 토큰 수정, 메뉴별 통계 수정정
@6d17d940bdb513313e0db546444b7ef70323524d
client/resources/api/index.js
--- client/resources/api/index.js
+++ client/resources/api/index.js
@@ -1,6 +1,35 @@
 import axios from 'axios';
 import store from "../../views/pages/AppStore";
 
+// 토큰 재발급 함수 (공통 함수로 분리)
+const refreshToken = async () => {
+    const res = await axios.post("/refresh/tknReissue.json", {}, {
+        headers: {
+            "Content-Type": "application/json; charset=UTF-8",
+        },
+    });
+
+    if (res.status === 200) {
+        console.log("토큰 재발급 성공! 굿~");
+        // 새로 발급 받은 AccessToken 저장
+        store.commit('setAuthorization', res.headers.authorization);
+        
+        // JWT 토큰 디코딩
+        const base64String = store.state.authorization.split('.')[1];
+        const base64 = base64String.replace(/-/g, '+').replace(/_/g, '/');
+        const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => {
+            return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
+        }).join(''));
+        const mbr = JSON.parse(jsonPayload);
+        store.commit("setUserNm", mbr.userNm); // 사용자 이름 저장
+        store.commit('setRoles', mbr.roles); // 사용자 역할 저장
+        
+        return true;
+    }
+    
+    throw new Error("토큰 재발급 요청 실패");
+};
+
 // Axios 클라이언트 생성 함수
 const createClient = (contentType) => {
     const client = axios.create({
@@ -32,45 +61,36 @@
             const originalReq = error.config;
 
             // 403 에러 처리
-            if (error.response.status === 403) {
+            if (error.response && error.response.status === 403) {
                 alert('접근 권한이 없습니다.');
                 window.history.back();
                 return Promise.reject(error);
             }
 
             // 401 에러 처리 (토큰 만료)
-            if (error.response.status === 401 && error.response.data.message === '로그인 시간이 만료되었습니다.' && !originalReq._retry) {
-                // 토큰 재발급 요청
+            if (error.response && 
+                error.response.status === 401 && 
+                error.response.data.message === '로그인 시간이 만료되었습니다.' && 
+                !originalReq._retry) {
+                
+                originalReq._retry = true; // 재시도 플래그 설정
+                
                 try {
-                    const res = await axios.post("/refresh/tknReissue.json", {}, {
-                        headers: {
-                            "Content-Type": "application/json; charset=UTF-8",
-                        },
-                    });
-
-                    // 응답 상태가 200일 경우에만 처리
-                    if (res.status === 200) {
-                        console.log("토큰 재발급 성공! 굿~");
-                        // 새로 발급 받은 AccessToken 저장
-                        store.commit('setAuthorization', res.headers.authorization);
-                        originalReq.headers.Authorization = store.state.authorization; // 새로 발급 받은 AccessToken을 기존 요청에 추가
-
-                        // JWT 토큰 디코딩
-                        const base64String = store.state.authorization.split('.')[1];
-                        const base64 = base64String.replace(/-/g, '+').replace(/_/g, '/');
-                        const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => {
-                            return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
-                        }).join(''));
-                        const mbr = JSON.parse(jsonPayload);
-                        store.commit("setUserNm", mbr.userNm); // 사용자 이름 저장
-                        store.commit('setRoles', mbr.roles); // 사용자 역할 저장
-
-                        originalReq._retry = true; // 재시도 플래그 설정
-                        return client(originalReq); // 원래 요청 재시도
-                    } else {
-                        // 200이 아닌 경우
-                        throw new Error("토큰 재발급 요청 실패");
+                    // 공통 토큰 재발급 함수 호출
+                    await refreshToken();
+                    
+                    // 중요: Content-Type 헤더 보존
+                    originalReq.headers.Authorization = store.state.authorization;
+                    
+                    // multipart/form-data 요청의 경우 data 처리 방식이 다름
+                    if (originalReq.headers['Content-Type'] && 
+                        originalReq.headers['Content-Type'].includes('multipart/form-data')) {
+                        // multipart의 경우 FormData가 이미 생성되어 있으므로 그대로 사용
+                        // 특별한 처리 없이 원래 요청 재시도
                     }
+                    
+                    // 원래 요청 재시도
+                    return client(originalReq);
                 } catch (refreshError) {
                     const redirect = window.location.pathname + window.location.search;
                     sessionStorage.setItem("redirect", redirect);
@@ -94,4 +114,4 @@
 // 멀티파트 파일 업로드를 위한 fileClient
 const fileClient = createClient('multipart/form-data');
 
-export { apiClient, fileClient }; // 두 클라이언트를 내보냄
+export { apiClient, fileClient }; // 두 클라이언트를 내보냄
(파일 끝에 줄바꿈 문자 없음)
client/views/layout/Header.vue
--- client/views/layout/Header.vue
+++ client/views/layout/Header.vue
@@ -11,17 +11,17 @@
                         <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'"
                             @click="updateMenuStats('MENU_00000001')">기록물
                             <div class="submenu">
-                                <p>• <router-link :to="{ path: '/PicHistorySearch.page' }">사진 기록물</router-link></p>
+                                <p>• <router-link :to="{ path: '/PicHistorySearch.page' }" @click="updateMenuStats('MENU_00000002')">사진 기록물</router-link></p>
                                 <div class="hr"></div>
-                                <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }">영상 기록물</router-link></p>
+                                <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }" @click="updateMenuStats('MENU_00000003')">영상 기록물</router-link></p>
                             </div>
                         </li>
                         <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'"
                             @click="updateMenuStats('MENU_00000004')">언론에서 바라본 구미시
                             <div class="submenu">
-                                <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }">미디어 영상</router-link></p>
+                                <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }" @click="updateMenuStats('MENU_00000005')">미디어 영상</router-link></p>
                                 <div class="hr"></div>
-                                <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }">보도자료</router-link></p>
+                                <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }" @click="updateMenuStats('MENU_00000006')">보도자료</router-link></p>
                             </div>
                         </li>
                         <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'"
@@ -32,9 +32,22 @@
                                 :to="{ path: '/CategoryManagement.page' }">카테고리 관리</router-link></li>
 
                         <li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'"
-                            @click="updateMenuStats('MENU_00000001')">기록물</li>
+                            @click="updateMenuStats('MENU_00000001')">기록물
+                            <div class="submenu">
+                                <p>• <router-link :to="{ path: '/PicHistorySearch.page' }" @click="updateMenuStats('MENU_00000002')">사진 기록물</router-link></p>
+                                <div class="hr"></div>
+                                <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }" @click="updateMenuStats('MENU_00000003')">영상 기록물</router-link></p>
+                            </div>
+                        </li>
+                            
                         <li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'"
-                            @click="updateMenuStats('MENU_00000004')">언론에서 바라본 구미시</li>
+                            @click="updateMenuStats('MENU_00000004')">언론에서 바라본 구미시
+                            <div class="submenu">
+                                <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }" @click="updateMenuStats('MENU_00000005')">미디어 영상</router-link></p>
+                                <div class="hr"></div>
+                                <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }" @click="updateMenuStats('MENU_00000006')">보도자료</router-link></p>
+                            </div>
+                        </li>
                     </ul>
                 </nav>
             </div>
@@ -64,17 +77,17 @@
                 <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000001')">
                     <h6>기록물</h6>
                     <div class="submenu">
-                        <p>• <router-link :to="{ path: '/PicHistorySearch.page' }" @click="closeMenu">사진 기록물</router-link></p>
+                        <p>• <router-link :to="{ path: '/PicHistorySearch.page' }" @click="updateMenuStats('MENU_00000002')">사진 기록물</router-link></p>
                         <div class="hr pink"></div>
-                        <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }" @click="closeMenu">영상 기록물</router-link></p>
+                        <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }" @click="updateMenuStats('MENU_00000003')">영상 기록물</router-link></p>
                     </div>
                 </li>
                 <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000004')">
                     <h6>언론에서 바라본 구미시</h6>
                     <div class="submenu">
-                        <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }" @click="closeMenu">미디어 영상</router-link></p>
+                        <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }" @click="updateMenuStats('MENU_00000004')">미디어 영상</router-link></p>
                         <div class="hr pink"></div>
-                        <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }" @click="closeMenu">보도자료</router-link></p>
+                        <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }" @click="updateMenuStats('MENU_00000005')">보도자료</router-link></p>
                     </div>
                 </li>
                 <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000007')">
@@ -91,10 +104,20 @@
                 </li>
 
                 <li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'" @click="updateMenuStats('MENU_00000001')">
-                    <router-link :to="{ path: '/PicHistorySearch.page' }" @click="closeMenu">기록물</router-link>
+                    <h6>기록물</h6>
+                    <div class="submenu">
+                        <p>• <router-link :to="{ path: '/PicHistorySearch.page' }" @click="updateMenuStats('MENU_00000002')">사진 기록물</router-link></p>
+                        <div class="hr pink"></div>
+                        <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }" @click="updateMenuStats('MENU_00000003')">영상 기록물</router-link></p>
+                    </div>
                 </li>
                 <li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'" @click="updateMenuStats('MENU_00000004')">
-                    <router-link :to="{ path: '/MediaVideoSearch.page' }" @click="closeMenu">언론에서 바라본 구미시</router-link>
+                    <h6>언론에서 바라본 구미시</h6>
+                    <div class="submenu">
+                        <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }" @click="updateMenuStats('MENU_00000004')">미디어 영상</router-link></p>
+                        <div class="hr pink"></div>
+                        <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }" @click="updateMenuStats('MENU_00000005')">보도자료</router-link></p>
+                    </div>
                 </li>
             </ul>
         </nav>
client/views/pages/bbsDcry/photo/PicHistoryDetail.vue
--- client/views/pages/bbsDcry/photo/PicHistoryDetail.vue
+++ client/views/pages/bbsDcry/photo/PicHistoryDetail.vue
@@ -26,14 +26,16 @@
       <div>
         <div class="gallery">
           <div class="main-swiper">
-            <swiper :style="{ '--swiper-navigation-color': '#fff', '--swiper-pagination-color': '#fff' }" :loop="true" :spaceBetween="10" :thumbs="{ swiper: thumbsSwiper }" :modules="modules" class="mySwiper2">
+            <swiper :style="{ '--swiper-navigation-color': '#fff', '--swiper-pagination-color': '#fff' }" :loop="true"
+              :spaceBetween="10" :thumbs="{ swiper: thumbsSwiper }" :modules="modules" class="mySwiper2">
               <swiper-slide v-for="(item, idx) of findResult.files" :key="idx">
                 <img :src="item.filePath" :alt="item.fileNm" />
               </swiper-slide>
             </swiper>
           </div>
           <div class="thumbnail">
-            <swiper @swiper="setThumbsSwiper" :spaceBetween="20" :slidesPerView="4" :freeMode="true" :watchSlidesProgress="true" :modules="modules" :navigation="true" class="mySwiper">
+            <swiper @swiper="setThumbsSwiper" :spaceBetween="20" :slidesPerView="4" :freeMode="true"
+              :watchSlidesProgress="true" :modules="modules" :navigation="true" class="mySwiper">
               <swiper-slide v-for="(item, idx) of findResult.files" :key="idx">
                 <input type="checkbox" :id="'photo_' + idx" :value="item.fileId" v-model="selectedFiles" />
                 <img :src="item.filePath" :alt="item.fileNm" />
@@ -189,12 +191,18 @@
 
       try {
         // 파일 ID 수집
-        let fileIds = this.selectedFiles[0];
-        if (type === 'all' && this.findResult.files.length > 1) {
+        let fileIds;
+        if (type === 'selected') {
+          if (this.selectedFiles.length == 1) {
+            fileIds = this.selectedFiles[0];
+          } else {
+            fileIds = this.selectedFiles.join(',');
+          }
+        } else if (type === 'all' && this.findResult.files.length > 1) {
           fileIds = this.findResult.files.map(file => file.fileId).join(',');
         }
 
-        let isMultiple = fileIds.length > 1;
+        let isMultiple = this.selectedFiles.length > 1;
         const response = isMultiple ? await multiFileDownloadProc(fileIds) : await fileDownloadProc(fileIds);
 
         // 파일명 조회
client/views/pages/bbsDcry/photo/PicHistoryInsert.vue
--- client/views/pages/bbsDcry/photo/PicHistoryInsert.vue
+++ client/views/pages/bbsDcry/photo/PicHistoryInsert.vue
@@ -17,23 +17,23 @@
       <dl>
         <dd>
           <label for="sj" class="require">제목</label>
-          <div class="wfull"><input type="text" id="sj" placeholder="제목을 입력하세요." v-model="reqDTO.sj"></div>
+          <div class="wfull"><input type="text" id="sj" placeholder="제목을 입력하세요." v-model="requestDTO.sj"></div>
         </dd>
         <div class="hr"></div>
         <dd>
           <label for="prdctnYear">생산연도</label>
-          <input type="text" id="prdctnYear" placeholder="생산연도를 입력하세요" v-model="reqDTO.prdctnYear">
+          <input type="text" id="prdctnYear" placeholder="생산연도를 입력하세요" v-model="requestDTO.prdctnYear">
         </dd>
         <div class="hr"></div>
         <dd>
           <label for="adres">주소</label>
-          <div class="wfull"><input type="text" id="adres" placeholder="주소를 입력하세요" v-model="reqDTO.adres"></div>
+          <div class="wfull"><input type="text" id="adres" placeholder="주소를 입력하세요" v-model="requestDTO.adres"></div>
         </dd>
         <div class="hr"></div>
         <dd>
           <label for="text">내용</label>
           <div class="wfull">
-            <EditorComponent v-model:contents="reqDTO.cn" />
+            <EditorComponent v-model:contents="requestDTO.cn" />
           </div>
         </dd>
         <div class="hr"></div>
@@ -42,7 +42,8 @@
             <p>카테고리</p><button type="button" class="category-add" @click="fnToggleModal">추가하기</button>
           </label>
           <ul class="category">
-            <li v-for="(item, idx) of selectedCtgries" :key="idx">{{ item.ctgryNm }} <button type="button" class="cancel" @click="fnDelCtgry(item.ctgryId)"><b>✕</b></button></li>
+            <li v-for="(item, idx) of selectedCtgries" :key="idx">{{ item.ctgryNm }} <button type="button"
+                class="cancel" @click="fnDelCtgry(item.ctgryId)"><b>✕</b></button></li>
           </ul>
         </dd>
         <div class="hr"></div>
@@ -51,11 +52,14 @@
           <ul class="wfull">
             <li class="flex align-center">
               <p>파일첨부</p>
-              <div class="invalid-feedback"><img :src="erroricon" alt=""><span>첨부파일은 건당 최대 10GB를 초과할 수 없습니다.</span></div>
+              <div class="invalid-feedback"><img :src="erroricon" alt=""><span>첨부파일은 건당 최대 10GB를 초과할 수 없습니다.</span>
+              </div>
             </li>
             <li class="file-insert">
-              <input type="file" id="fileInput" class="file-input" multiple accept="image/jpeg,image/png,image/gif,image/jpg" @change="handleFileSelect">
-              <label for="fileInput" class="file-label mb-20" @dragover.prevent="handleDragOver" @dragleave.prevent="handleDragLeave" @drop.prevent="handleDrop" :class="{ 'drag-over': isDragging }">
+              <input type="file" id="fileInput" class="file-input" multiple
+                accept="image/jpeg,image/png,image/gif,image/jpg" @change="handleFileSelect">
+              <label for="fileInput" class="file-label mb-20" @dragover.prevent="handleDragOver"
+                @dragleave.prevent="handleDragLeave" @drop.prevent="handleDrop" :class="{ 'drag-over': isDragging }">
                 <div class="flex-center align-center">
                   <img :src="fileicon" alt="">
                   <p>파일첨부하기</p>
@@ -64,7 +68,7 @@
               </label>
               <p class="mb-10">파일목록</p>
               <div id="fileNames" class="file-names">
-                <div v-if="reqDTO.files.length === 0 && multipartFiles.length === 0">선택된 파일이 없습니다.</div>
+                <div v-if="requestDTO.files.length === 0 && multipartFiles.length === 0">선택된 파일이 없습니다.</div>
                 <!-- 새로 추가된 파일 목록 -->
                 <div v-for="(file, idx) of multipartFiles" :key="idx" class="flex-sp-bw mb-5 file-wrap">
                   <div class="file-name">
@@ -74,7 +78,7 @@
                   <button type="button" class="cancel" @click="fnDelFile('new', idx)"><b>✕</b></button>
                 </div>
                 <!-- 기존 등록된 파일 목록 -->
-                <div v-for="(file, idx) of reqDTO.files" :key="idx" class="flex-sp-bw mb-5 file-wrap">
+                <div v-for="(file, idx) of requestDTO.files" :key="idx" class="flex-sp-bw mb-5 file-wrap">
                   <div class="file-name">
                     <img src="/client/resources/images/icon/imgicon.png" alt="fileicon">
                     <p>{{ file.fileNm }}</p>
@@ -95,7 +99,8 @@
       </button>
     </div>
   </div>
-  <CategorySelectModal v-if="isModalOpen" :selectedCtgries="selectedCtgries" @toggleModal="fnToggleModal" @addCtgries="fnAddCtgries" />
+  <CategorySelectModal v-if="isModalOpen" :selectedCtgries="selectedCtgries" @toggleModal="fnToggleModal"
+    @addCtgries="fnAddCtgries" />
 </template>
 <script>
 import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue';
@@ -132,7 +137,7 @@
       fileNames: [],
 
       // 등록/수정 요청 객체
-      reqDTO: {
+      requestDTO: {
         dcryId: null,
         sj: null,
         cn: null,
@@ -179,20 +184,20 @@
       }
     },
 
-    // dcry > reqDTO
+    // dcry > requestDTO
     copyToDcryReqDTO(dcry) {
-      const copyFields = Object.keys(this.reqDTO).filter(key => key !== 'dcryId' && key !== 'ty' && key !== 'files');
+      const copyFields = Object.keys(this.requestDTO).filter(key => key !== 'dcryId' && key !== 'ty' && key !== 'files');
       copyFields.forEach(field => {
-        this.reqDTO[field] = this.$isEmpty(dcry[field]) ? null : dcry[field];
+        this.requestDTO[field] = this.$isEmpty(dcry[field]) ? null : dcry[field];
       });
 
-      this.reqDTO.ty = 'P'; // 사진기록물
-      this.reqDTO.files = dcry.files.length > 0 ? dcry.files : []; // 기존 첨부파일
+      this.requestDTO.ty = 'P'; // 사진기록물
+      this.requestDTO.files = dcry.files.length > 0 ? dcry.files : []; // 기존 첨부파일
 
       this.multipartFiles = [];
       this.selectedCtgries = dcry.ctgrys.length > 0 ? dcry.ctgrys : [];
 
-      console.log(this.reqDTO);
+      console.log(this.requestDTO);
     },
 
     // 카테고리 모달 열기/닫기
@@ -263,19 +268,23 @@
       if (type === 'new') {
         this.multipartFiles.splice(separator, 1);
       } else if (type === 'old') {
-        this.reqDTO.files = this.reqDTO.files.filter(item => item.fileId !== separator);
+        this.requestDTO.files = this.requestDTO.files.filter(item => item.fileId !== separator);
       }
     },
 
     // 등록
     async submitForm() {
       // 유효성 검사
-      if (!this.reqDTO.sj) {
+      if (!this.requestDTO.sj) {
         alert("제목을 입력해 주세요.");
         return;
       }
 
-      if ((this.multipartFiles.length + this.requestDTO.files.length) == 0) {
+      let count = this.multipartFiles.length
+      if (!this.$isEmpty(this.pageId)) {
+        count += this.requestDTO.files.length
+      }
+      if (count == 0) {
         alert("파일을 1개 이상 첨부해 주세요.");
         return;
       }
@@ -284,11 +293,11 @@
         const formData = new FormData();
 
         // 텍스트 데이터 추가
-        formData.append('sj', this.reqDTO.sj);
-        formData.append('cn', this.reqDTO.cn);
-        formData.append('adres', this.reqDTO.adres);
-        formData.append('prdctnYear', this.reqDTO.prdctnYear);
-        formData.append('ty', this.reqDTO.ty);
+        formData.append('sj', this.requestDTO.sj);
+        formData.append('cn', this.requestDTO.cn);
+        formData.append('adres', this.requestDTO.adres);
+        formData.append('prdctnYear', this.requestDTO.prdctnYear);
+        formData.append('ty', this.requestDTO.ty);
 
         // 게시물 아이디
         if (!this.$isEmpty(this.pageId)) {
@@ -296,8 +305,8 @@
         }
 
         // 파일 아이디
-        if (!this.$isEmpty(this.reqDTO.fileId)) {
-          formData.append('fileId', this.reqDTO.fileId);
+        if (!this.$isEmpty(this.requestDTO.fileId)) {
+          formData.append('fileId', this.requestDTO.fileId);
         }
 
         // 카테고리 Ids 추가
@@ -313,8 +322,8 @@
         }
 
         // 기존파일 수정
-        if (!this.$isEmpty(this.pageId) && this.reqDTO.files.length > 0) {
-          for (let file of this.reqDTO.files) {
+        if (!this.$isEmpty(this.pageId) && this.requestDTO.files.length > 0) {
+          for (let file of this.requestDTO.files) {
             formData.append("files", file.fileId);
           }
         }
client/views/pages/bbsDcry/video/VideoHistoryInsert.vue
--- client/views/pages/bbsDcry/video/VideoHistoryInsert.vue
+++ client/views/pages/bbsDcry/video/VideoHistoryInsert.vue
@@ -280,7 +280,11 @@
         return;
       }
 
-      if ((this.multipartFiles.length + this.requestDTO.files.length) == 0) {
+      let count = this.multipartFiles.length
+      if (!this.$isEmpty(this.pageId)) {
+        count += this.requestDTO.files.length
+      }
+      if (count == 0) {
         alert("파일을 1개 이상 첨부해 주세요.");
         return;
       }
client/views/pages/bbsNesDta/NewsReleaseInsert.vue
--- client/views/pages/bbsNesDta/NewsReleaseInsert.vue
+++ client/views/pages/bbsNesDta/NewsReleaseInsert.vue
@@ -232,7 +232,11 @@
       const maxSize = 10 * 1024 * 1024 * 1024; // 10GB
 
       // 유효성 검사
-      if ((files.length + this.multipartFiles.length + this.requestDTO.files.length) > 1) {
+      let count = files.length + this.multipartFiles.length
+      if (!this.$isEmpty(this.pageId)) {
+        count += this.requestDTO.files.length
+      }
+      if (count > 1) {
         alert("썸네일은 한 개만 등록 가능합니다.");
         return;
       }
Add a comment
List