박정하 박정하 03-25
250325 박정하 통합검색 수정
@f4c83affe36f86796e8b5641b4acecb68a8206e1
 
client/views/component/List/CardViewList.vue (added)
+++ client/views/component/List/CardViewList.vue
@@ -0,0 +1,108 @@
+<template>
+  <div class="mb-30">
+    <div class="flex-sp-bw mb-20">
+      <div class="resultext">
+        <img :src="resulticon" alt="">
+        <p>총 <b>{{ count }}개</b>의 {{ helpText }}</p>
+      </div>
+      <button type="button" class="gopage" @click="fnMoveTo(routeName, null)">모두보기</button>
+    </div>
+    <ul>
+      <li v-for="(item, idx2) of list" :key="idx2" class="result-box mb-15">
+        <div class="main-img">
+          <img v-if="item.hasOwnProperty('files') && item.files.length > 0" :src="item.files[0].filePath" alt="">
+          <img v-else src="client/resources/images/img6.png" alt="">
+        </div>
+        <div class="text-box">
+          <h5>{{ item.sj }}</h5>
+          <p v-if="name === 'P' || name === 'V'" class="address">{{ item.adres }}</p>
+          <p class="text">{{ item.cn }}</p>
+          <div class="mb-20">
+            <ul class="category">
+              <li v-for="(ctgry, idx3) of item.ctgrys" :key="idx3" class="category1">{{ ctgry.ctgryNm }}</li>
+            </ul>
+          </div>
+          <div class="date">
+            <ul>
+              <li>생산연도 <b>{{ item.prdctnYear }}</b></li>
+              <li>|</li>
+              <li>등록일 <b>{{ item.rgsde }}</b></li>
+            </ul>
+          </div>
+        </div>
+      </li>
+    </ul>
+  </div>
+</template>
+<script>
+export default {
+  name: "CardViewList",
+
+  props: {
+    name: {
+      type: String,
+      default: 'P',
+    },
+    count: {
+      type: Number,
+      default: 0,
+    },
+    list: {
+      type: Array,
+      default: () => [],
+    },
+  },
+
+  data() {
+    return {
+    };
+  },
+
+  computed: {
+    helpText() {
+      switch (this.name) {
+        case 'P':
+          return '사진 기록물이 검색되었습니다.';
+        case 'V':
+          return '영상 기록물이 검색되었습니다.';
+        case 'M':
+          return '미디어 영상이 검색되었습니다.';
+        case 'N':
+          return '보도자료가 검색되었습니다.';
+      }
+    },
+    routeName() {
+      switch (this.name) {
+        case 'P':
+          return 'PicHistorySearch';
+        case 'V':
+          return '영상 기록물';
+        case 'M':
+          return '미디어 영상';
+        case 'N':
+          return '보도자료';
+      }
+    },
+  },
+
+  created() { },
+  mounted() { },
+  watch: {},
+  methods: {
+    // 페이지 이동
+    fnMoveTo(page, id) {
+      if (id !== null || id !== '') {
+        this.$router.push({ name: page, query: { id: id } });
+      } else {
+        this.$router.push({ name: page });
+      }
+    }
+  },
+};
+</script>
+<style scoped>
+/* 임시로 추가 */
+.main-img img {
+  max-width: 320px;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
client/views/component/modal/CategorySelectModal.vue
--- client/views/component/modal/CategorySelectModal.vue
+++ client/views/component/modal/CategorySelectModal.vue
@@ -50,17 +50,62 @@
   </div>
 </template>
 <script>
-// 모달 사용 용례
-// selectedCtgries: 부모 요소에 저장된 '선택한 카테고리' 목록
-// @toggleModal: 모달 열고 닫는 함수 (호출만 해도 됨/열려있는 경우 닫힘, 닫혀있는 경우 열림)
+import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue';
+
 export default {
   name: 'CategorySelectModal',
+  components: {
+    DoubleLeftOutlined,
+    LeftOutlined,
+    RightOutlined,
+    DoubleRightOutlined
+  },
   props: {
     selectedCtgries: {
       type: Array,
-      default: [],
+      default: () => [],
     }
   },
-}
-</script>
-<style></style>
(파일 끝에 줄바꿈 문자 없음)
+  data() {
+    return {
+      items: [
+        { id: 1, category: '카테고리 1', selected: false },
+        { id: 2, category: '카테고리 2', selected: false },
+        { id: 3, category: '카테고리 3', selected: false },
+      ],
+      searchicon: 'client/resources/images/icon/search.png',
+      currentPage: 1,
+      totalPages: 1
+    };
+  },
+  methods: {
+    closeModal() {
+      this.$emit('toggleModal');
+    },
+    registerCategories() {
+      // 선택된 카테고리 목록
+      const selectedCategories = this.items
+        .filter(item => item.selected)
+        .map(item => item.category);
+
+      // 선택된 카테고리 ID 목록
+      const selectedIds = this.items
+        .filter(item => item.selected)
+        .map(item => item.id);
+
+      // 부모 컴포넌트로 전달하고 모달 닫기
+      this.$emit('toggleModal', selectedIds);
+    },
+    previousPage() {
+      if (this.currentPage > 1) {
+        this.currentPage--;
+      }
+    },
+    nextPage() {
+      if (this.currentPage < this.totalPages) {
+        this.currentPage++;
+      }
+    }
+  }
+};
+</script>
(파일 끝에 줄바꿈 문자 없음)
client/views/layout/Header.vue
--- client/views/layout/Header.vue
+++ client/views/layout/Header.vue
@@ -1,86 +1,78 @@
 <template>
-
-    <header>
-        <div class="header-container w1500">
-            <div class="logo-wrap">
-                <router-link :to="{ path: '/' }" class="logo"><img :src="logo" alt=""></router-link>
-            </div>
-            <div class="nav-wrap">
-                <nav>
-                    <ul>
-                        <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'"
-                            @click="updateMenuStats('MENU_00000001')"><router-link :to="{ path: '/' }" >기록물</router-link></li>
-                        <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'"
-                            @click="updateMenuStats('MENU_00000004')"><router-link :to="{ path: '/' }" >언론에서 바라본 구미시</router-link></li>
-                        <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'"
-                            @click="updateMenuStats('MENU_00000007')"><router-link :to="{ path: '/MemberManagement.page' }" >회원관리</router-link></li>
-                        <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'"
-                            @click="updateMenuStats('MENU_00000008')"><router-link :to="{ path: '/CategoryManagement.page' }" >카테고리 관리</router-link></li>
-
-                        <li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'"
-                            @click="updateMenuStats('MENU_00000001')">기록물</li>
-                        <li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'"
-                            @click="updateMenuStats('MENU_00000004')">언론에서 바라본 구미시</li>
-                    </ul>
-                </nav>
-            </div>
-            <div class="auth-area">
-                <ul v-if="$store.state.userId != null">
-                    <li><img src="../../resources/images/icon/user-settings-line.png" alt="">
-                        <router-link :to="{ path: '/MyInfo.page' }">{{ $store.state.userNm }}</router-link>
-                    </li>
-                    <li>
-                        <div class="line"></div>
-                    </li>
-                    <li><img src="../../resources/images/icon/logout-box-line.png" alt="">
-                        <a href="#" @click.prevent="logout">로그아웃</a>
-                    </li>
-                </ul>
-                <a href="#" class="all-menu"><img src="../../resources/images/allmenu.png" alt=""></a>
-            </div>
-        </div>
-    </header>
+  <header>
+    <div class="header-container w1500">
+      <div class="logo-wrap">
+        <router-link :to="{ path: '/' }" class="logo"><img :src="logo" alt=""></router-link>
+      </div>
+      <div class="nav-wrap">
+        <nav>
+          <ul>
+            <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000001')"><router-link :to="{ path: '/PicHistorySearch.page' }">기록물</router-link></li>
+            <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000004')"><router-link :to="{ path: '/' }">언론에서 바라본 구미시</router-link></li>
+            <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000007')"><router-link :to="{ path: '/MemberManagement.page' }">회원관리</router-link></li>
+            <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000008')"><router-link :to="{ path: '/CategoryManagement.page' }">카테고리 관리</router-link></li>
+            <li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'" @click="updateMenuStats('MENU_00000001')">기록물</li>
+            <li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'" @click="updateMenuStats('MENU_00000004')">언론에서 바라본 구미시</li>
+          </ul>
+        </nav>
+      </div>
+      <div class="auth-area">
+        <ul v-if="$store.state.userId != null">
+          <li><img src="../../resources/images/icon/user-settings-line.png" alt="">
+            <router-link :to="{ path: '/MyInfo.page' }">{{ $store.state.userNm }}</router-link>
+          </li>
+          <li>
+            <div class="line"></div>
+          </li>
+          <li><img src="../../resources/images/icon/logout-box-line.png" alt="">
+            <a href="#" @click.prevent="logout">로그아웃</a>
+          </li>
+        </ul>
+        <a href="#" class="all-menu"><img src="../../resources/images/allmenu.png" alt=""></a>
+      </div>
+    </div>
+  </header>
 </template>
 <script>
 import { logOutProc } from "../../resources/api/user"
 import { updateStatsByMenuId } from "../../resources/api/main"
 export default {
-    data() {
-        return {
-            // Define the image sources
-            logo: 'client/resources/images/logo.png',
-        };
+  data() {
+    return {
+      // Define the image sources
+      logo: 'client/resources/images/logo.png',
+    };
+  },
+  methods: {
+    async updateMenuStats(menuId) {
+      try {
+        const response = await updateStatsByMenuId(menuId);
+        if (response.status === 200) {
+          console.log(`메뉴 ID ${menuId} 통계 업데이트 성공`);
+        }
+      } catch (error) {
+        console.error(`메뉴 ID ${menuId} 통계 업데이트 중 오류:`, error);
+      }
     },
-    methods: {
-        async updateMenuStats(menuId) {
-            try {
-                const response = await updateStatsByMenuId(menuId);
-                if (response.status === 200) {
-                    console.log(`메뉴 ID ${menuId} 통계 업데이트 성공`);
-                }
-            } catch (error) {
-                console.error(`메뉴 ID ${menuId} 통계 업데이트 중 오류:`, error);
-            }
-        },
-        logout() {
-            // 백엔드 로그아웃 API 호출
-            logOutProc()
-                .then(() => {
-                    console.log('로그아웃 성공 - 서버 측 쿠키 삭제 완료');
-                    this.$store.commit('setStoreReset'); // 로그아웃 성공 후 스토어 초기화
-                    this.$router.push({ path: '/Login.page' }); // 로그인 페이지로 리다이렉트
-                })
-                .catch(err => {
-                    console.error('로그아웃 처리 중 오류:', err);
-                    this.$store.commit('setStoreReset'); // 오류가 있어도 스토어는 초기화
-                    this.$router.push({ path: '/Login.page' }); // 로그인 페이지로 리다이렉트
-                });
-        },
-        
+    logout() {
+      // 백엔드 로그아웃 API 호출
+      logOutProc()
+        .then(() => {
+          console.log('로그아웃 성공 - 서버 측 쿠키 삭제 완료');
+          this.$store.commit('setStoreReset'); // 로그아웃 성공 후 스토어 초기화
+          this.$router.push({ path: '/Login.page' }); // 로그인 페이지로 리다이렉트
+        })
+        .catch(err => {
+          console.error('로그아웃 처리 중 오류:', err);
+          this.$store.commit('setStoreReset'); // 오류가 있어도 스토어는 초기화
+          this.$router.push({ path: '/Login.page' }); // 로그인 페이지로 리다이렉트
+        });
     },
-    watch: {},
-    computed: {},
-    components: {},
-    mounted() { },
+
+  },
+  watch: {},
+  computed: {},
+  components: {},
+  mounted() { },
 };
 </script>
(파일 끝에 줄바꿈 문자 없음)
client/views/pages/AppRouter.js
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
@@ -1,26 +1,29 @@
 import { createWebHistory, createRouter } from "vue-router";
 import store from "./AppStore";
 
-// 공통페이지
+// 공통
 import Login from "./login/Login.vue";
-import MyInfo from "./user/MyInfo.vue";
 import Main from "./main/Main.vue";
 import NotFound from "./etc/NotFound.vue";
-
+// 통합검색
 import TotalSearch from "./user/TotalSearch.vue";
+// 사진기록물
 import PicHistorySearch from "./user/PicHistorySearch.vue";
 import PicHistoryInsert from "./user/PicHistoryInsert.vue";
 import PicHistoryDetail from "./user/PicHistoryDetail.vue";
+// 보도자료
 import BodoDetail from "./user/BodoDetail.vue";
-
+// 회원관리
+import MyInfo from "./user/MyInfo.vue";
 import MemberManagement from "./user/MemberManagement.vue";
+// 카테고리관리
 import CategoryManagement from "./user/CategoryManagement.vue";
 
 
 const routes = [
-  { path: "/", name: "MainPage", component: Main, meta: { authorization: ['ROLE_ADMIN', 'ROLE_USER']} },
+  { path: "/", name: "MainPage", component: Main, meta: { authorization: ['ROLE_ADMIN', 'ROLE_USER'] } },
   { path: "/Login.page", name: "Login", component: Login },
-  { path: "/MyInfo.page", name: "MyInfo", component: MyInfo, meta: { authorization: ['ROLE_ADMIN', 'ROLE_USER']} },
+  { path: "/MyInfo.page", name: "MyInfo", component: MyInfo, meta: { authorization: ['ROLE_ADMIN', 'ROLE_USER'] } },
   { path: "/notFound.page", name: "NotFoundPage", component: NotFound },
 
   { path: "/TotalSearch.page", name: "TotalSearch", component: TotalSearch },
@@ -28,11 +31,8 @@
   { path: "/PicHistoryInsert.page", name: "PicHistoryInsert", component: PicHistoryInsert },
   { path: "/PicHistoryDetail.page", name: "PicHistoryDetail", component: PicHistoryDetail },
   { path: "/BodoDetail.page", name: "BodoDetail", component: BodoDetail },
-
   { path: "/MemberManagement.page", name: "MemberManagement", component: MemberManagement },
   { path: "/CategoryManagement.page", name: "CategoryManagement", component: CategoryManagement },
-
-
 ];
 
 const AppRouter = createRouter({
@@ -57,10 +57,10 @@
     alert('로그인이 필요합니다.');
     return next('/Login.page'); // 로그인 페이지로 리다이렉트
   }
-  
+
   // 권한이 없을 경우
-  if(authorization){
-    if(!authorization.includes(store.state.roles[0].authority)){
+  if (authorization) {
+    if (!authorization.includes(store.state.roles[0].authority)) {
       alert('접근 권한이 없습니다.');
       return next(from.path);
     }
client/views/pages/user/PicHistoryInsert.vue
--- client/views/pages/user/PicHistoryInsert.vue
+++ client/views/pages/user/PicHistoryInsert.vue
@@ -12,7 +12,7 @@
         </ul>
       </div>
     </div>
-    <form action="" class="insert-form mb-50">
+    <form class="insert-form mb-50">
       <dl>
         <dd>
           <label for="id" class="require">제목</label>
@@ -51,11 +51,11 @@
           <ul class="wfull">
             <li class="flex align-center">
               <p>파일첨부</p>
-              <div class="invalid-feedback"><img :src="erroricon" alt=""><span>첨부파일은 10건까지 등록 가능하며, 건당 최대 100MB를 초과할 수 없습니다.</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 @change="showFileNames">
-              <label for="fileInput" class="file-label mb-20">
+              <input type="file" id="fileInput" class="file-input" multiple @change="showFileNames" accept="image/jpeg,image/png,image/gif,image/jpg">
+              <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>
                 </div>
@@ -66,7 +66,6 @@
                 <span v-if="fileNames.length === 0">선택된 파일이 없습니다.</span>
                 <div v-for="(file, index) in fileNames" :key="index" class="flex-sp-bw mb-5 file-wrap">
                   <div class="file-name">
-                    <!-- Corrected here: Use file.icon instead of fileicons.img -->
                     <img :src="file.icon" alt="fileicon">
                     <p>{{ file.name }}</p>
                   </div>
@@ -79,13 +78,15 @@
       </dl>
     </form>
     <div class="btn-group flex-center">
-      <button class="cancel">취소</button>
-      <button class="register">등록</button>
+      <button type="button" class="cancel" @click="fnMoveTo('PicHistorySearch')">취소</button>
+      <button type="button" class="register" @click="submitForm">등록</button>
     </div>
   </div>
   <CategorySelectModal v-if="isModalOpen" :selectedCtgries="selectedCtgries" @toggleModal="fnToggleModal" />
 </template>
 <script>
+import axios from 'axios';
+import apiClient from '../../../resources/api';
 import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue';
 // COMPONENT
 import EditorComponent from '../../component/EditorComponent.vue';
@@ -97,12 +98,13 @@
     LeftOutlined,
     RightOutlined,
     DoubleRightOutlined,
-    EditorComponent, CategorySelectModal,
+    EditorComponent,
+    CategorySelectModal,
   },
 
   data() {
     return {
-      // Define the image sources
+      // 아이콘 경로
       homeicon: 'client/resources/images/icon/home.png',
       erroricon: 'client/resources/images/icon/error.png',
       righticon: 'client/resources/images/icon/right.png',
@@ -110,6 +112,7 @@
       searchicon: 'client/resources/images/icon/search.png',
 
       isModalOpen: false,
+      isDragging: false,
 
       items: [
         { id: 1, category: '카테고리 1', selected: false },
@@ -123,11 +126,10 @@
         adres: null, // 주소
         prdctnYear: null, // 생산연도
         ty: 'P', // 타입 ( P: 사진, V: 영상 )
-        multipartFiles: null, // 첨부파일 정보
         ctgryIds: null, // 카테고리 정보
       },
 
-      files: [],
+      selectedFiles: [],
       selectedCtgries: [], // 카테고리 목록
     };
   },
@@ -139,74 +141,209 @@
       );
     }
   },
-  created() {
-  },
   methods: {
-    registerCategories() {
-      // Add selected categories to the displayed list
-      this.selectedCtgries = this.items
-        .filter(item => item.selected)
-        .map(item => item.category);
-      this.closeModal(); // Close modal after registration
+    // 드래그 앤 드롭 이벤트 핸들러
+    handleDragOver(event) {
+      this.isDragging = true;
+    },
+    handleDragLeave(event) {
+      this.isDragging = false;
+    },
+    handleDrop(event) {
+      this.isDragging = false;
+      const files = event.dataTransfer.files;
+      if (files.length > 0) {
+        this.processFiles(files);
+      }
+    },
+
+    // 파일 업로드 처리 함수
+    processFiles(files) {
+      // 파일 타입 검증 (이미지 파일만 허용)
+      const allowedTypes = ['jpg', 'jpeg', 'png', 'gif'];
+      const maxSize = 10 * 1024 * 1024 * 1024; // 10GB
+
+      for (let i = 0; i < files.length; i++) {
+        const fileType = files[i].name.split('.').pop().toLowerCase();
+        if (!allowedTypes.includes(fileType)) {
+          alert(`${files[i].name} 파일은 허용되지 않는 형식입니다. 이미지 파일(jpg, jpeg, png, gif)만 업로드 가능합니다.`);
+          return;
+        }
+
+        // 파일 크기 제한 체크 (10GB)
+        if (files[i].size > maxSize) {
+          alert(`${files[i].name} 파일이 10GB를 초과합니다.`);
+          return;
+        }
+      }
+
+      // 실제 File 객체들을 저장
+      for (let i = 0; i < files.length; i++) {
+        this.selectedFiles.push(files[i]);
+      }
+
+      // UI에 표시할 파일 정보 저장
+      for (let i = 0; i < files.length; i++) {
+        const file = files[i];
+        const fileType = file.name.split('.').pop().toLowerCase();  // 파일 확장자 추출
+
+        // 파일 타입에 따른 아이콘 선택
+        let iconPath = this.fileicon; // 기본 아이콘
+
+        if (['jpg', 'jpeg', 'png', 'gif'].includes(fileType)) {
+          iconPath = 'client/resources/images/icon/imgicon.png';
+        } else if (['pdf'].includes(fileType)) {
+          iconPath = 'client/resources/images/icon/pdficon.png';
+        } else if (['xls', 'xlsx'].includes(fileType)) {
+          iconPath = 'client/resources/images/icon/excelicon.png';
+        } else if (['hwp'].includes(fileType)) {
+          iconPath = 'client/resources/images/icon/hwpicon.png';
+        }
+
+        // 파일 이름과 아이콘을 목록에 추가
+        this.fileNames.push({
+          name: file.name,
+          icon: iconPath,
+          size: this.formatFileSize(file.size)
+        });
+      }
+    },
+
+    fnToggleModal(selectedCtgryIds) {
+      this.isModalOpen = !this.isModalOpen;
+      if (selectedCtgryIds && selectedCtgryIds.length > 0) {
+        this.insertDTO.ctgryIds = selectedCtgryIds;
+      }
     },
     removeCategory(index) {
       // Remove category from the list
       this.selectedCtgries.splice(index, 1);
     },
-    searchCategories() {
-      // You can implement search logic if needed
-    },
-    nextPage() {
-      if (this.currentPage < this.totalPages) {
-        this.currentPage++;
-      }
-    },
-    previousPage() {
-      if (this.currentPage > 1) {
-        this.currentPage--;
-      }
-    },
-    showFileNames(event) {
-      const files = event.target.files;
-      this.fileNames = [];  // Clear previous file names
-
-      for (let i = 0; i < files.length; i++) {
-        const file = files[i];
-        const fileType = file.name.split('.').pop().toLowerCase();  // Get file extension
-
-        // Set default icon
-        let iconPath = this.fileicons;
-
-        // Determine the icon based on file type
-        if (['jpg', 'jpeg', 'png', 'gif'].includes(fileType)) {
-          iconPath = 'client/resources/images/icon/imgicon.png';  // Example for image files
-        } else if (['pdf'].includes(fileType)) {
-          iconPath = 'client/resources/images/icon/pdficon.png';  // Example for PDF files
-        } else if (['xls'].includes(fileType)) {
-          iconPath = 'client/resources/images/icon/excelicon.png';  // Example for audio files
-        } else if (['hwp'].includes(fileType)) {
-          iconPath = 'client/resources/images/icon/hwpicon.png';  // Example for video files
-        }
-
-        // Push the file name and corresponding icon to the fileNames array
-        this.fileNames.push({
-          name: file.name,
-          icon: iconPath
-        });
-      }
-    },
-    removeFile(index) {
-      // Remove file from the list
-      this.fileNames.splice(index, 1);
-      console.log(removeFile)
-    },
     openModal() {
       this.isModalOpen = true;
     },
-    // 모달 닫기
     closeModal() {
       this.isModalOpen = false;
     },
+    showFileNames(event) {
+      const files = event.target.files;
+      if (files.length > 0) {
+        this.processFiles(files);
+      }
+    },
+    removeFile(index) {
+      // UI 목록과 실제 파일 객체 목록에서 모두 제거
+      this.fileNames.splice(index, 1);
+      this.selectedFiles.splice(index, 1);
+    },
+    formatFileSize(bytes) {
+      if (bytes === 0) return '0 Bytes';
+      const k = 1024;
+      const sizes = ['Bytes', 'KB', 'MB', 'GB'];
+      const i = Math.floor(Math.log(bytes) / Math.log(k));
+      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+    },
+
+    // 등록
+    submitForm() {
+      const vm = this;
+
+      // 폼 요소들의 값을 가져오기
+      const titleInput = document.getElementById('id');
+      const yearInput = document.getElementById('year');
+      const addressInput = document.getElementById('address');
+
+      // insertDTO 업데이트
+      vm.insertDTO.sj = titleInput ? titleInput.value : null;
+      vm.insertDTO.prdctnYear = yearInput ? yearInput.value : null;
+      vm.insertDTO.adres = addressInput ? addressInput.value : null;
+
+      // 유효성 검사
+      if (!vm.insertDTO.sj) {
+        alert("제목을 입력해 주세요.");
+        return;
+      }
+      if (vm.selectedFiles.length < 1) {
+        alert("파일을 1개 이상 첨부해 주세요.");
+        return;
+      }
+
+      // 데이터 세팅
+      const formData = new FormData();
+
+      // 텍스트 데이터 추가
+      formData.append('sj', vm.insertDTO.sj);
+      formData.append('cn', vm.insertDTO.cn || '');
+      formData.append('adres', vm.insertDTO.adres || '');
+      formData.append('prdctnYear', vm.insertDTO.prdctnYear || '');
+      formData.append('ty', vm.insertDTO.ty);
+
+      // 카테고리 IDs 추가
+      if (vm.insertDTO.ctgryIds && vm.insertDTO.ctgryIds.length > 0) {
+        // 백엔드 요구사항에 따라 조정 필요
+        vm.insertDTO.ctgryIds.forEach((id, index) => {
+          formData.append(`ctgryIds[${index}]`, id);
+        });
+      }
+
+      // 파일 추가
+      for (let i = 0; i < vm.selectedFiles.length; i++) {
+        formData.append("multipartFiles", vm.selectedFiles[i]);
+      }
+
+      // API 통신
+      axios({
+        url: "/dcry/saveDcry.file",
+        method: "post",
+        headers: {
+          "Content-Type": "multipart/form-data",
+        },
+        data: formData,
+      }).then(response => {
+        let result = response.data;
+        let id = result.data.dcryId;
+        alert("등록 되었습니다.");
+
+        // 상세 페이지로 이동
+        vm.fnMoveTo('PicHistoryDetail', id);
+      }).catch(error => {
+        if (error.response) {
+          alert(error.response.data.message);
+        } else {
+          alert("에러가 발생했습니다.");
+        }
+        console.error(error.message);
+      });
+    },
+
+    // 페이지 이동
+    fnMoveTo(page, id) {
+      if (id !== null || id !== '') {
+        this.$router.push({ name: page, query: { id: id } });
+      } else {
+        this.$router.push({ name: page });
+      }
+    }
   }
 };
 </script>
+<style>
+.file-label {
+  border: 2px dashed #ddd;
+  border-radius: 8px;
+  padding: 20px;
+  text-align: center;
+  cursor: pointer;
+  transition: all 0.3s ease;
+}
+
+.file-label:hover {
+  border-color: #aaa;
+  background-color: #f9f9f9;
+}
+
+.file-label.drag-over {
+  border-color: #1890ff;
+  background-color: rgba(24, 144, 255, 0.1);
+}
+</style>
(파일 끝에 줄바꿈 문자 없음)
client/views/pages/user/TotalSearch.vue
--- client/views/pages/user/TotalSearch.vue
+++ client/views/pages/user/TotalSearch.vue
@@ -4,7 +4,6 @@
       <h2>통합검색</h2>
       <div class="breadcrumb-list">
         <ul>
-          <!-- Bind the image source dynamically for homeicon -->
           <li>
             <img :src="homeicon" alt="Home Icon">
             <p></p>
@@ -14,7 +13,7 @@
         </ul>
       </div>
     </div>
-    <form action="search" class="search-form mb-40">
+    <div class="search-form mb-40">
       <dl>
         <dd class="mb-15">
           <p>기록유형</p>
@@ -23,9 +22,21 @@
               <input type="checkbox" id="allRecord" v-model="isChkAllRecord" @change="fnChkAllOptions('record')" />
               <label for="allRecord">전체</label>
             </li>
-            <li v-for="(record, idx) in searchRecord" :key="idx">
-              <input type="checkbox" :id="idx" :name="searchRecord" :value="record.key" v-model="searchReqDTO.searchRecord" @change="fnChkOption('record')" />
-              <label :for="idx">{{ record.value }}</label>
+            <li>
+              <input type="checkbox" id="photoRecord" v-model="searchReqDTO.usePhoto" @change="fnChkOption('record')" />
+              <label for="photoRecord">사진</label>
+            </li>
+            <li>
+              <input type="checkbox" id="videoRecord" v-model="searchReqDTO.useVideo" @change="fnChkOption('record')" />
+              <label for="videoRecord">영상</label>
+            </li>
+            <li>
+              <input type="checkbox" id="mediaVideo" v-model="searchReqDTO.useMedia" @change="fnChkOption('record')" />
+              <label for="mediaVideo">미디어영상</label>
+            </li>
+            <li>
+              <input type="checkbox" id="newsData" v-model="searchReqDTO.useNews" @change="fnChkOption('record')" />
+              <label for="newsData">보도자료</label>
             </li>
           </ul>
         </dd>
@@ -36,15 +47,23 @@
               <input type="checkbox" id="allScope" v-model="isChkAllScope" @change="fnChkAllOptions('scope')" />
               <label for="allScope">전체</label>
             </li>
-            <li v-for="(scope, idx) in searchType" :key="idx">
-              <input type="checkbox" :id="idx" :name="searchType" :value="scope.key" v-model="searchReqDTO.searchType" @change="fnChkOption('scope')" />
-              <label :for="idx">{{ scope.value }}</label>
+            <li>
+              <input type="checkbox" id="searchSj" v-model="searchReqDTO.useSj" @change="fnChkOption('scope')" />
+              <label for="searchSj">제목</label>
+            </li>
+            <li>
+              <input type="checkbox" id="searchCn" v-model="searchReqDTO.useCn" @change="fnChkOption('scope')" />
+              <label for="searchCn">내용</label>
+            </li>
+            <li>
+              <input type="checkbox" id="searchAdres" v-model="searchReqDTO.useAdres" @change="fnChkOption('scope')" />
+              <label for="searchAdres">주소</label>
             </li>
           </ul>
         </dd>
         <dd class="mb-15">
           <p>검색어</p>
-          <div class="wfull"><input type="text" v-model="searchReqDTO.searchText"></div>
+          <div class="wfull"><input type="text" v-model="searchReqDTO.searchText" v-on:keyup.enter="fnSearch()"></div>
         </dd>
         <dd class="mb-15">
           <p>생산연도</p>
@@ -56,7 +75,7 @@
           <p>카테고리</p>
           <ul>
             <li v-for="(category, idx) of categorys" :key="idx">
-              <input type="checkbox" :id="category.ctgryId" name="categorys" :value="category.ctgryId" v-model="searchReqDTO.searchCtgry" />
+              <input type="checkbox" :id="category.ctgryId" name="categorys" :value="category.ctgryId" v-model="searchReqDTO.searchCtgries" />
               <label :for="category.ctgryId">{{ category.ctgryNm }}</label>
             </li>
           </ul>
@@ -75,48 +94,15 @@
             <img :src="reseticon" alt="">
             <p>초기화</p>
           </button>
-          <button type="button" class="search" @click="fnFindAllDatas">
+          <button type="button" class="search" @click="fnSearch">
             <img :src="searchicon" alt="">
             <p>검색</p>
           </button>
         </div>
       </dl>
-    </form>
+    </div>
     <div class="search-result">
-      <ul>
-        <li v-for="(resultItem, idx) of searchResult" :key="idx" class="mb-30">
-          <div class="flex-sp-bw mb-20">
-            <div class="resultext">
-              <img :src="resulticon" alt="">
-              <p>총 <b>{{ resultItem.count }}개</b>의 {{ searchRecord[idx].value }}{{ resultItem.key === 'N' ? '가' : '이' }} 검색되었습니다.</p>
-            </div>
-            <router-link :to="{ path: '/' + resultItem.id }" class="gopage">모두보기</router-link>
-          </div>
-          <div v-for="(item, idx2) of resultItem.list" :key="idx2" class="result-box">
-            <div class="main-img">
-              <img v-if="item.hasOwnProperty('files') && item.files.length > 0" :src="item.files[0].filePath" alt="">
-              <img v-else src="client/resources/images/img6.png" alt="">
-            </div>
-            <div class="text-box">
-              <h5>{{ item.sj }}</h5>
-              <p v-if="resultItem.key === 'P' || resultItem.key === 'V'" class="address">{{ item.adres }}</p>
-              <p class="text">{{ item.cn }}</p>
-              <div class="mb-20">
-                <ul class="category">
-                  <li v-for="(ctgry, idx3) of item.ctgrys" :key="idx3" class="category1">{{ ctgry.ctgryNm }}</li>
-                </ul>
-              </div>
-              <div class="date">
-                <ul>
-                  <li>생산연도 <b>{{ item.prdctnYear }}</b></li>
-                  <li>|</li>
-                  <li>등록 <b>{{ item.rgsde }}</b></li>
-                </ul>
-              </div>
-            </div>
-          </div>
-        </li>
-      </ul>
+      <CardViewList v-for="(item, idx) of searchResult" :key="idx" :name="item.key" :count="item.count" :list="item.list" />
     </div>
   </div>
 </template>
@@ -124,8 +110,12 @@
 // 통합 검색
 import { findAllDatas } from "../../../resources/api/main";
 import { findAllByNullProc } from "../../../resources/api/category"; // 카테고리 목록 검색
+import CardViewList from "../../component/List/CardViewList.vue";
 
 export default {
+  components: {
+    CardViewList
+  },
   data() {
     return {
       // icon
@@ -137,14 +127,20 @@
 
       // 검색용 객체 초기값
       searchDefault: {
-        searchRecord: ["P", "V", "M", "N"],
-        searchType: ["sj", "cn", "adres"],
+        usePhoto: true,
+        useVideo: true,
+        useMedia: true,
+        useNews: true,
+        useSj: true,
+        useCn: true,
+        useAdres: true,
         searchText: null,
         startYear: null,
         endYear: null,
-        searchCtgry: [],
+        searchCtgries: [],
         order: "rgsde",
       },
+
       searchReqDTO: {},
 
       isChkAllRecord: true, // 기록유형 전체 체크 여부
@@ -183,7 +179,7 @@
       this.searchReqDTO.searchText = searchText;
     }
 
-    this.fnFindAllDatas(); // 통합검색
+    this.fnSearch(); // 통합검색
   },
 
   methods: {
@@ -196,9 +192,14 @@
           return;
         }
       }
-      this.searchReqDTO = Object.assign({}, this.searchDefault); // 검색객체 초기화
+
+      this.searchReqDTO = JSON.parse(JSON.stringify(this.searchDefault));
+      this.isChkAllRecord = true;
+      this.isChkAllScope = true;
+
       this.searchResult = []; // 검색결과 초기화
     },
+
     // 카테고리 목록 조회
     async fnFindCategorys() {
       try {
@@ -208,34 +209,33 @@
         this.categorys = []; // 카테고리 목록 초기화
 
         if (error.response) {
-          console.log("에러 응답:", error.response.data);
+          alert(error.response.data.message);
         }
-        console.error("Error:", error);
+        console.error(error.message);
       }
     },
 
     // 통합검색
-    async fnFindAllDatas() {
-      if (this.searchReqDTO.searchRecord == null || this.searchReqDTO.searchRecord.length < 1) {
+    async fnSearch() {
+      // 유효성 검사
+      if (!this.searchReqDTO.usePhoto && !this.searchReqDTO.useVideo && !this.searchReqDTO.useMedia && !this.searchReqDTO.useNews) {
         alert('검색 유형은 최소 한 개 이상 선택해주세요.');
         return;
       }
 
       try {
-        let params = {};
-        if (this.searchReqDTO.searchRecord.length > 0) {
-          params.searchRecords = this.searchReqDTO.searchRecord.join(',');
+        // 깊은 복사로 파라미터 생성
+        const params = JSON.parse(JSON.stringify(this.searchReqDTO));
+
+        // 디버깅용 로그 - 실제 전송되는 값 확인
+        console.log("검색 파라미터:", params);
+
+        // 카테고리 목록 처리
+        if (this.searchReqDTO.searchCtgries && this.searchReqDTO.searchCtgries.length > 0) {
+          params.searchCtgries = this.searchReqDTO.searchCtgries.join(',');
+        } else {
+          delete params.searchCtgries;
         }
-        if (this.searchReqDTO.searchType.length > 0) {
-          params.searchTypes = this.searchReqDTO.searchType.join(',');
-        }
-        if (this.searchReqDTO.searchCtgry.length > 0) {
-          params.searchCtgries = this.searchReqDTO.searchCtgry.join(',');
-        }
-        params.searchText = this.searchReqDTO.searchText;
-        params.startYear = this.searchReqDTO.startYear;
-        params.endYear = this.searchReqDTO.endYear;
-        params.order = this.searchReqDTO.order;
 
         // API 호출
         const response = await findAllDatas(params);
@@ -254,18 +254,15 @@
     fnChkAllOptions(type) {
       switch (type) {
         case 'record':
-          if (this.isChkAllRecord) {
-            this.searchReqDTO.searchRecord = this.searchRecord.map(item => item.key);
-          } else {
-            this.searchReqDTO.searchRecord = [];
-          }
+          this.searchReqDTO.usePhoto = this.isChkAllRecord;
+          this.searchReqDTO.useVideo = this.isChkAllRecord;
+          this.searchReqDTO.useMedia = this.isChkAllRecord;
+          this.searchReqDTO.useNews = this.isChkAllRecord;
           break;
         case 'scope':
-          if (this.isChkAllScope) {
-            this.searchReqDTO.searchType = this.searchType.map(item => item.key);
-          } else {
-            this.searchReqDTO.searchType = [];
-          }
+          this.searchReqDTO.useSj = this.isChkAllScope;
+          this.searchReqDTO.useCn = this.isChkAllScope;
+          this.searchReqDTO.useAdres = this.isChkAllScope;
           break;
       }
     },
@@ -274,13 +271,13 @@
     fnChkOption(type) {
       switch (type) {
         case 'record':
-          this.isChkAllRecord = this.searchReqDTO.searchRecord.length === this.searchRecord.length;
+          this.isChkAllRecord = this.searchReqDTO.usePhoto && this.searchReqDTO.useVideo && this.searchReqDTO.useMedia && this.searchReqDTO.useNews;
           break;
         case 'scope':
-          this.isChkAllScope = this.searchReqDTO.searchType.length === this.searchType.length;
+          this.isChkAllScope = this.searchReqDTO.useSj && this.searchReqDTO.useCn && this.searchReqDTO.useAdres;
           break;
       }
-    },
+    }
   },
 };
-</script>
+</script>
(파일 끝에 줄바꿈 문자 없음)
Add a comment
List