하관우 하관우 03-26
2025-03-26 하관우 멀티파트 토큰 재발급 수정정
@e0df0e367ac05b6348a383b55129b76f1c5e6ec3
client/resources/api/category.js
--- client/resources/api/category.js
+++ client/resources/api/category.js
@@ -1,4 +1,4 @@
-import apiClient from "./index";
+import {apiClient} from "./index";
 
 // 카테고리 목록 조회 (검색조건 없음)
 export const findAllByNullProc = () => {
@@ -6,6 +6,16 @@
 }
 
 // 카테고리 목록 조회 (검색조건 있음)
-export const findAllCategoryProc = () => {
-  return apiClient.get(`/category/findAllCategory.json`);
-}
(파일 끝에 줄바꿈 문자 없음)
+export const findAllCategoryProc = searchReqDTO => {
+  return apiClient.get(`/category/findAllCategory.json`, { params: searchReqDTO });
+}
+
+// 카테고리 정보 변경
+export const updateCategory = (ctgryId, updateCtgryDTO) => {
+  return apiClient.put(`/category/${ctgryId}/updateCategory.json`, updateCtgryDTO);
+}
+
+// 카테고리 정보 등록
+export const saveCategory = insertCtgryDTO => {
+  return apiClient.post(`/category/saveCategory.json`, insertCtgryDTO);
+}
client/resources/api/dcry.js
--- client/resources/api/dcry.js
+++ client/resources/api/dcry.js
@@ -1,4 +1,4 @@
-import apiClient from "./index";
+import {apiClient} from "./index";
 
 // 기록물 목록 조회
 export const findAllDatas = (searchReqDTO) => {
client/resources/api/index.js
--- client/resources/api/index.js
+++ client/resources/api/index.js
@@ -1,85 +1,96 @@
 import axios from 'axios';
 import store from "../../views/pages/AppStore";
 
-const apiClient = axios.create({
-    headers: {
-        'Content-Type': 'application/json; charset=UTF-8',
-    }
-});
-
-// 요청 인터셉터
-apiClient.interceptors.request.use(
-    config => {
-        const token = store.state.authorization; // Access Token 가져오기
-        if (token) {
-            config.headers.Authorization = token; // 단순히 토큰만 추가
+// Axios 클라이언트 생성 함수
+const createClient = (contentType) => {
+    const client = axios.create({
+        headers: {
+            'Content-Type': contentType,
         }
-        return config;
-    },
-    error => {
-        return Promise.reject(error);
-    }
-);
+    });
 
-// 응답 인터셉터
-apiClient.interceptors.response.use(
-    response => {
-        return response;
-    },
-    async error => {
-        const originalReq = error.config;
-
-        // 403 에러 처리
-        if (error.response.status === 403) {
-            alert('접근 권한이 없습니다.');
-            window.history.back();
+    // 요청 인터셉터
+    client.interceptors.request.use(
+        config => {
+            const token = store.state.authorization; // Access Token 가져오기
+            if (token) {
+                config.headers.Authorization = token; // 토큰 추가
+            }
+            return config;
+        },
+        error => {
             return Promise.reject(error);
         }
+    );
 
-        // 401 에러 처리 (토큰 만료)
-        if (error.response.status === 401 && error.response.data.message === '로그인 시간이 만료되었습니다.' && !originalReq._retry) {
-            // 토큰 재발급 요청
-            try {
-                const res = await axios.post("/refresh/tknReissue.json", {}, {
-                    headers: {
-                        "Content-Type": "application/json; charset=UTF-8",
-                    },
-                });
+    // 응답 인터셉터
+    client.interceptors.response.use(
+        response => {
+            return response;
+        },
+        async error => {
+            const originalReq = error.config;
 
-                // 응답 상태가 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); // 사용자 역할 저장
-
-                    return apiClient(originalReq); // 원래 요청 재시도
-                } else {
-                    // 200이 아닌 경우
-                    throw new Error("토큰 재발급 요청 실패");
-                }
-            } catch (refreshError) {
-                const redirect = window.location.pathname + window.location.search;
-                sessionStorage.setItem("redirect", redirect);
-                alert('세션이 종료되었습니다.\n로그인을 새로 해주세요.');
-                store.commit("setStoreReset");
-                window.location = '/login.page'; // 로그인 페이지로 리다이렉트
-                return Promise.reject(refreshError);
+            // 403 에러 처리
+            if (error.response.status === 403) {
+                alert('접근 권한이 없습니다.');
+                window.history.back();
+                return Promise.reject(error);
             }
+
+            // 401 에러 처리 (토큰 만료)
+            if (error.response.status === 401 && error.response.data.message === '로그인 시간이 만료되었습니다.' && !originalReq._retry) {
+                // 토큰 재발급 요청
+                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); // 사용자 역할 저장
+
+                        return client(originalReq); // 원래 요청 재시도
+                    } else {
+                        // 200이 아닌 경우
+                        throw new Error("토큰 재발급 요청 실패");
+                    }
+                } catch (refreshError) {
+                    const redirect = window.location.pathname + window.location.search;
+                    sessionStorage.setItem("redirect", redirect);
+                    alert('세션이 종료되었습니다.\n로그인을 새로 해주세요.');
+                    store.commit("setStoreReset");
+                    window.location = '/login.page'; // 로그인 페이지로 리다이렉트
+                    return Promise.reject(refreshError);
+                }
+            }
+
+            return Promise.reject(error);
         }
+    );
 
-        return Promise.reject(error);
-    }
-);
+    return client;
+};
 
-export default apiClient;
+// JSON 요청을 위한 apiClient
+const apiClient = createClient('application/json; charset=UTF-8');
+
+// 멀티파트 파일 업로드를 위한 fileClient
+const fileClient = createClient('multipart/form-data');
+
+export { apiClient, fileClient }; // 두 클라이언트를 내보냄
client/resources/api/main.js
--- client/resources/api/main.js
+++ client/resources/api/main.js
@@ -1,4 +1,4 @@
-import apiClient from "./index";
+import {apiClient} from "./index";
 
 // 메인화면 정보 조회
 export const findAllSttusesProc = () => {
client/resources/api/user.js
--- client/resources/api/user.js
+++ client/resources/api/user.js
@@ -1,4 +1,4 @@
-import apiClient from "./index";
+import {apiClient} from "./index";
 import store from '../../views/pages/AppStore';
 
 // 로그인
client/views/pages/user/CategoryManagement.vue
--- client/views/pages/user/CategoryManagement.vue
+++ client/views/pages/user/CategoryManagement.vue
@@ -16,62 +16,68 @@
             <div class="left-con">
                 <div class="search-wrap mb-20">
                     <div class="search-area">
-                       <div class="wfull" style="height: 100%;"> <input type="text"  v-model="searchText"></div>
-                        <button class="search-btn"><img :src="searchicon" alt=""></button>
+                        <div class="wfull" style="height: 100%;">
+                            <input type="text" v-model="searchReqDTO.searchText" @keyup.enter="searchCategories">
+                        </div>
+                        <button class="search-btn" @click="searchCategories"><img :src="searchicon" alt=""></button>
                     </div>
-                   
-                    
                 </div>
                 <table class="mb-10">
-                        <thead>
-                            <tr>
-                                <th>카테고리명</th>
-                            </tr>
-                        </thead>
-                        <tbody>
-                            <tr v-for="item in items" :key="item.id" :class="{'delete-member': item.delete}">
-                                <!-- Category 칼럼 -->
-                                <td>
-                                    {{ item.category}}
-                                </td>
-                            </tr>
-                        </tbody>
-                    </table>
+                    <thead>
+                        <tr>
+                            <th>카테고리명</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        <tr v-for="item in selectedCategories" :key="item.ctgryId"
+                            :class="{ 'delete-member': item.useAt === 'N' }" @click="selectCategory(item)">
+                            <!-- Category 칼럼 -->
+                            <td>
+                                {{ item.ctgryNm }}
+                            </td>
+                        </tr>
+                    </tbody>
+                </table>
             </div>
             <div class="righi-con wfull">
                 <div class="btn-group-small flex-end mb-20">
-                    <div class="button green-line">복구</div>
-                    <div class="button red-line">삭제</div>
-                    <div class="button pink-line-bg flex align-center"><img :src="check_pink" alt="">
+                    <div v-if="copySelectedCategory.useAt === 'N' && isNewInsert && selectedCategory.ctgryId != null"
+                        class="button green-line" @click="updateByUseAtCategory">복구</div>
+                    <div v-if="copySelectedCategory.useAt === 'Y' && isNewInsert && selectedCategory.ctgryId != null"
+                        class="button red-line" @click="updateByUseAtCategory">삭제</div>
+                    <div class="button pink-line-bg flex align-center" @click="resetCategory"><img :src="check_pink"
+                            alt="">
                         <p>신규등록</p>
                     </div>
-                    <div class="button blue-line-bg flex align-center"><img :src="check_blue" alt="">
+                    <div class="button blue-line-bg flex align-center" @click="insertByUpdateCategory"><img
+                            :src="check_blue" alt="">
                         <p>등록</p>
                     </div>
-                    <div class="button gray-bg">취소</div>
+                    <div class="button gray-bg" @click="cancelCategory">취소</div>
                 </div>
                 <form action="" class="insert-form mb-50">
                     <dl>
                         <dd>
                             <label for="id" class="require">카테고리명</label>
-                            <div class="wfull"><input type="text" id="id" value="admin" readonly></div>
+                            <div class="wfull"><input type="text" id="id" v-model="selectedCategory.ctgryNm">
+                            </div>
                         </dd>
                         <div class="hr"></div>
                         <dd>
                             <label for="use" class="require">사용여부</label>
                             <div class="switch">
-                                <input type="checkbox" id="switch" checked/>
+                                <input type="checkbox" id="switch" :checked="selectedCategory.useAt === 'Y'"
+                                    @change="toggleUseAt" />
                                 <label for="switch">Toggle</label>
-
                             </div>
 
                         </dd>
                         <div class="hr"></div>
                         <dd>
-                            <label for="text" >설명</label>
-                            <textarea name="" id="text"></textarea>
+                            <label for="text">설명</label>
+                            <textarea name="" id="text" v-model="selectedCategory.dc"></textarea>
                         </dd>
-                      
+
                     </dl>
                 </form>
             </div>
@@ -82,6 +88,7 @@
 </template>
 <script>
 import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue';
+import { findAllCategoryProc, updateCategory, saveCategory } from "../../../resources/api/category"; // 카테고리 목록 검색
 
 export default {
     components: {
@@ -93,12 +100,6 @@
 
     data() {
         return {
-            items: [
-                { id: 1, category: '카테고리 1', delete: false },
-                { id: 2, category: '카테고리 2', delete: false },
-                { id: 3, category: '카테고리 3', delete: true },
-            ],
-            isModalOpen: false,
             // Define the image sources
             homeicon: 'client/resources/images/icon/home.png',
             erroricon: 'client/resources/images/icon/error.png',
@@ -106,19 +107,185 @@
             check_pink: 'client/resources/images/checkbox_pink.png',
             check_blue: 'client/resources/images/checkbox_blue.png',
             searchicon: 'client/resources/images/icon/search.png',
-            fileNames: [],
             selectedCategories: [],
+            // 선택된 카테고리 정보를 저장할 객체
+            selectedCategory: {
+                ctgryId: null,
+                ctgryNm: null,
+                useAt: null,
+                dc: null
+            },
+            // 선택된 카테고리 정보를 저장할 객체 copy
+            copySelectedCategory: {
+                oldCtgryNm: null,
+                ctgryId: null,
+                ctgryNm: null,
+                useAt: null,
+                dc: null
+            },
+            searchReqDTO: {
+                searchType: "nm",
+                searchText: null,
+                useAt: null
+            },
+            isNewInsert: true,
         };
     },
     computed: {
-        filteredItems() {
-            // This could be modified to support filtering based on searchQuery
-            return this.items.filter(item =>
-                item.category.includes(this.searchQuery)
-            );
-        }
     },
     methods: {
+        //카테고리 전체 조회
+        async searchCategories() {
+            try {
+                const response = await findAllCategoryProc(this.searchReqDTO);
+                if (response.status === 200) {
+                    this.selectedCategories = response.data.data.ctgry; // API 응답에서 카테고리 목록을 가져옴
+                    console.log("가져온 카테고리들 ", this.searchCategories);
+                    this.selectedCategory = {}
+                }
+            } catch (error) {
+                console.error("검색 중 오류 발생:", error);
+            }
+        },
+        // 클릭한 카테고리의 정보를 오른쪽에 표시
+        selectCategory(item) {
+            this.isNewInsert = true;
+            this.selectedCategory = {
+                ctgryId: item.ctgryId,
+                ctgryNm: item.ctgryNm,
+                useAt: item.useAt,
+                dc: item.dc
+            };
+            this.copySelectedCategory = {
+                ctgryId: item.ctgryId,
+                ctgryNm: item.ctgryNm,
+                useAt: item.useAt,
+                dc: item.dc
+            };
+
+            console.log("선택 된 카테고리", this.selectedCategory);
+        },
+        // 삭제 또는 복구 로직
+        async updateByUseAtCategory() {
+            if (this.selectedCategory.useAt === 'Y') {
+                this.selectedCategory.useAt = 'N'
+                if (confirm("선택한 카테고리를 삭제 하시겠습니까?")) {
+                    try {
+                        const response = await updateCategory(this.selectedCategory.ctgryId, this.selectedCategory);
+                        if (response.status === 200) {
+                            alert("삭제되었습니다..");
+                            this.searchCategories();
+                        }
+                    } catch (error) {
+                        alert("삭제가 실패하였습니다.");
+                        this.searchCategories();
+
+                    }
+                }
+            } else {
+                this.selectedCategory.useAt = 'Y'
+                if (confirm("선택한 카테고리를 복구 하시겠습니까?")) {
+                    try {
+                        const response = await updateCategory(this.selectedCategory.ctgryId, this.selectedCategory);
+                        if (response.status === 200) {
+                            alert("복구되었습니다..");
+                            this.searchCategories();
+                        }
+                    } catch (error) {
+                        alert("복구가 실패하였습니다.");
+                        this.searchCategories();
+
+                    }
+                }
+            }
+        },
+        // 신규 등록록
+        resetCategory() {
+            this.isNewInsert = false
+            this.selectedCategory = {
+                ctgryId: null,
+                ctgryNm: null,
+                useAt: null,
+                dc: null
+            };
+            this.copySelectedCategory = {
+                ctgryId: null,
+                ctgryNm: null,
+                useAt: null,
+                dc: null
+            };
+        },
+        // 등록 및 수정
+        async insertByUpdateCategory() {
+            if (!this.isValidationCategory()) {
+                if (this.selectedCategory.ctgryId == null || this.selectedCategory.ctgryId == '') {
+                    if (confirm("카테고리 등록을 하시겠습니까?")) {
+                        try {
+                            const response = await saveCategory(this.selectedCategory.ctgryId, this.selectedCategory);
+                            if (response.status === 200) {
+                                alert("등록되었습니다..");
+                                this.searchCategories();
+                            }
+                        } catch (error) {
+                            alert("등록이 실패하였습니다.");
+                            this.searchCategories();
+                        }
+                    }
+                } else {
+                    if (this.copySelectedCategory.ctgryNm == this.selectCategory.ctgryNm) {
+                        if (confirm("카테고리 수정을 하시겠습니까?")) {
+                            try {
+                                const response = await updateCategory(this.selectedCategory.ctgryId, this.selectedCategory);
+                                if (response.status === 200) {
+                                    alert("수정되었습니다..");
+                                    this.searchCategories();
+                                }
+                            } catch (error) {
+                                alert("수정이 실패하였습니다.");
+                                this.searchCategories();
+                            }
+                        }
+                    } else {
+                        if (confirm("카테고리 수정을 하시겠습니까?")) {
+                            try {
+                                this.selectCategory.oldCtgryNm = this.copySelectedCategory.ctgryNm;
+                                const response = await updateCategory(this.selectedCategory.ctgryId, this.selectedCategory);
+                                if (response.status === 200) {
+                                    alert("수정되었습니다..");
+                                    this.searchCategories();
+                                }
+                            } catch (error) {
+                                alert("수정이 실패하였습니다.");
+                                this.searchCategories();
+                            }
+                        }
+                    }
+                }
+            } else {
+                alert("필수 항목 값을 입력해주세요");
+            }
+        },
+        // 사용 여부를 토글하는 메서드
+        toggleUseAt() {
+            this.selectedCategory.useAt = this.selectedCategory.useAt === 'Y' ? 'N' : 'Y';
+        },
+        cancelCategory() {
+            this.selectedCategory = {
+                ctgryId: this.copySelectedCategory.ctgryId,
+                ctgryNm: this.copySelectedCategory.ctgryNm,
+                useAt: this.copySelectedCategory.useAt,
+                dc: this.copySelectedCategory.dc,
+            };
+        },
+        //카테고리 벨류데이션 체크
+        isValidationCategory() {
+            return this.selectedCategory.ctgryNm == null || this.selectedCategory.ctgryNm == '' || this.selectedCategory.useAt == null || this.selectedCategory.useAt == '';
+        },
+
+    },
+    mounted() {
+        // 초기 카테고리 조회 (optional)
+        this.searchCategories();
     }
 };
 </script>
Add a comment
List