박정하 박정하 03-28
250328 박정하 라우터, 보도자료 수정
@0fac8e856f5e7cee9e38858b020d840f2070f5a3
client/views/component/listLayout/CardViewList.vue (Renamed from client/views/component/CardViewList.vue)
--- client/views/component/CardViewList.vue
+++ client/views/component/listLayout/CardViewList.vue
No changes
client/views/component/listLayout/DefaultPagination.vue (Renamed from client/views/component/DefaultPagination.vue)
--- client/views/component/DefaultPagination.vue
+++ client/views/component/listLayout/DefaultPagination.vue
No changes
client/views/component/modal/CategorySelectModal.vue
--- client/views/component/modal/CategorySelectModal.vue
+++ client/views/component/modal/CategorySelectModal.vue
@@ -38,7 +38,7 @@
 </template>
 <script>
 // COMPONENT
-import DefaultPagination from '@/views/component/DefaultPagination.vue';
+import DefaultPagination from '@/views/component/listLayout/DefaultPagination.vue';
 // API
 import { findAllCategoryProc } from '@/resources/api/category';
 
client/views/pages/AppRouter.js
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
@@ -2,65 +2,56 @@
 import store from "./AppStore";
 
 // 공통
-import Login from "./login/Login.vue";
 import Main from "./main/Main.vue";
-import NotFound from "./etc/NotFound.vue";
+import TotalSearch from "./main/TotalSearch.vue";
 // 회원관리
+import Login from "./login/Login.vue";
 import MyInfo from "./user/MyInfo.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 VideoHistoryInsert from "./user/VideoHistoryInsert.vue";
-import VideoHistoryDetail from "./user/VideoHistoryDetail.vue";
-import VideoHistorySearch from "./user/VideoHistorySearch.vue";
+import MemberManagement from "./member/MemberManagement.vue";
+// 기록물 - 사진
+import PicHistorySearch from "./bbsDcry/photo/PicHistorySearch.vue";
+import PicHistoryInsert from "./bbsDcry/photo/PicHistoryInsert.vue";
+import PicHistoryDetail from "./bbsDcry/photo/PicHistoryDetail.vue";
+// 기록물 - 영상
+import VideoHistoryInsert from "./bbsDcry/video/VideoHistoryInsert.vue";
+import VideoHistoryDetail from "./bbsDcry/video/VideoHistoryDetail.vue";
+import VideoHistorySearch from "./bbsDcry/video/VideoHistorySearch.vue";
 // 미디어 영상
-import MediaVideoInsert from "./user/MediaVideoInsert.vue";
-import MediaVideoDetail from "./user/MediaVideoDetail.vue";
-import MediaVideoSearch from "./user/MediaVideoSearch.vue";
+import MediaVideoInsert from "./bbsMediaVido/MediaVideoInsert.vue";
+import MediaVideoDetail from "./bbsMediaVido/MediaVideoDetail.vue";
+import MediaVideoSearch from "./bbsMediaVido/MediaVideoSearch.vue";
 // 보도자료
-import NewsReleaseDetail from "./user/NewsReleaseDetail.vue";
-import NewsReleaseInsert from "./user/NewsReleaseInsert.vue";
-import NewsReleaseSearch from "./user/NewsReleaseSearch.vue";
-
-
-import MemberManagement from "./user/MemberManagement.vue";
-// 카테고리관리
-import CategoryManagement from "./user/CategoryManagement.vue";
-
+import NewsReleaseDetail from "./bbsNesDta/NewsReleaseDetail.vue";
+import NewsReleaseInsert from "./bbsNesDta/NewsReleaseInsert.vue";
+import NewsReleaseSearch from "./bbsNesDta/NewsReleaseSearch.vue";
+// 카테고리 관리
+import CategoryManagement from "./ctgry/CategoryManagement.vue";
 
 const routes = [
+  // 공통
   { path: "/", name: "MainPage", component: Main, meta: { authorization: ['ROLE_ADMIN', 'ROLE_USER'] } },
+  { path: "/TotalSearch.page", name: "TotalSearch", component: TotalSearch },
+  // 회원관리
   { path: "/Login.page", name: "Login", component: Login },
   { 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 },
-  // 사진기록물
+  { path: "/MemberManagement.page", name: "MemberManagement", component: MemberManagement },
+  // 기록물 - 사진
   { path: "/PicHistorySearch.page", name: "PicHistorySearch", component: PicHistorySearch },
   { path: "/PicHistoryInsert.page", name: "PicHistoryInsert", component: PicHistoryInsert },
   { path: "/PicHistoryDetail.page", name: "PicHistoryDetail", component: PicHistoryDetail },
-  // 영상기록물
+  // 기록물 - 영상
   { path: "/VideoHistorySearch.page", name: "VideoHistorySearch", component: VideoHistorySearch },
   { path: "/VideoHistoryInsert.page", name: "VideoHistoryInsert", component: VideoHistoryInsert },
   { path: "/VideoHistoryDetail.page", name: "VideoHistoryDetail", component: VideoHistoryDetail },
-
   // 미디어 영상
   { path: "/MediaVideoSearch.page", name: "MediaVideoSearch", component: MediaVideoSearch },
   { path: "/MediaVideoInsert.page", name: "MediaVideoInsert", component: MediaVideoInsert },
   { path: "/MediaVideoDetail.page", name: "MediaVideoDetail", component: MediaVideoDetail },
-
-// 보도자료
-{ path: "/NewsReleaseSearch.page", name: "NewsReleaseSearch", component: NewsReleaseSearch },
+  // 보도자료
+  { path: "/NewsReleaseSearch.page", name: "NewsReleaseSearch", component: NewsReleaseSearch },
   { path: "/NewsReleaseInsert.page", name: "NewsReleaseInsert", component: NewsReleaseInsert },
   { path: "/NewsReleaseDetail.page", name: "NewsReleaseDetail", component: NewsReleaseDetail },
-
-
-
-  { path: "/MemberManagement.page", name: "MemberManagement", component: MemberManagement },
+  // 카테고리 관리
   { path: "/CategoryManagement.page", name: "CategoryManagement", component: CategoryManagement },
 ];
 
@@ -95,7 +86,7 @@
     }
   }
   if (!routeExists) {
-    next({ name: 'NotFoundPage' });
+    next('/');
     return;
   }
   next();
client/views/pages/bbsDcry/photo/PicHistoryDetail.vue (Renamed from client/views/pages/user/PicHistoryDetail.vue)
--- client/views/pages/user/PicHistoryDetail.vue
+++ client/views/pages/bbsDcry/photo/PicHistoryDetail.vue
@@ -96,7 +96,7 @@
 // import required modules
 import { FreeMode, Navigation, Thumbs } from 'swiper/modules';
 // COMPONENT
-import ViewerComponent from '../../component/editor/ViewerComponent.vue';
+import ViewerComponent from '../../../component/editor/ViewerComponent.vue';
 // API
 import { findDcryProc, deleteDcryProc } from '@/resources/api/dcry';
 import { fileDownloadProc, multiFileDownloadProc } from '@/resources/api/file';
client/views/pages/bbsDcry/photo/PicHistoryInsert.vue (Renamed from client/views/pages/user/PicHistoryInsert.vue)
--- client/views/pages/user/PicHistoryInsert.vue
+++ client/views/pages/bbsDcry/photo/PicHistoryInsert.vue
@@ -99,8 +99,8 @@
 <script>
 import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue';
 // COMPONENT
-import EditorComponent from '../../component/editor/EditorComponent.vue';
-import CategorySelectModal from '../../component/modal/CategorySelectModal.vue';
+import EditorComponent from '../../../component/editor/EditorComponent.vue';
+import CategorySelectModal from '../../../component/modal/CategorySelectModal.vue';
 // API
 import { findDcryProc, saveDcry, updateDcry } from '@/resources/api/dcry';
 
client/views/pages/bbsDcry/photo/PicHistorySearch.vue (Renamed from client/views/pages/user/PicHistorySearch.vue)
--- client/views/pages/user/PicHistorySearch.vue
+++ client/views/pages/bbsDcry/photo/PicHistorySearch.vue
@@ -118,7 +118,7 @@
 // COMPONENT
 import CardStyleComponent from '@/views/component/listLayout/CardStyleComponent.vue';
 import ListStyleComponent from '@/views/component/listLayout/ListStyleComponent.vue';
-import DefaultPagination from '@/views/component/DefaultPagination.vue';
+import DefaultPagination from '@/views/component/listLayout/DefaultPagination.vue';
 // API
 import { findAllByNullProc } from "@/resources/api/category";
 import { findDcrysProc } from "@/resources/api/dcry";
client/views/pages/bbsDcry/video/VideoHistoryDetail.vue (Renamed from client/views/pages/user/VideoHistoryDetail.vue)
--- client/views/pages/user/VideoHistoryDetail.vue
+++ client/views/pages/bbsDcry/video/VideoHistoryDetail.vue
@@ -68,7 +68,7 @@
 <script>
 import axios from "axios";
 import { ref } from 'vue';
-import { updateUsers, logOutProc, updatePassword } from "../../../resources/api/user"
+import { updateUsers, logOutProc, updatePassword } from "../../../../resources/api/user"
 // Import Swiper Vue components
 import { CaretRightOutlined, PauseOutlined } from '@ant-design/icons-vue';
 import { Swiper, SwiperSlide } from 'swiper/vue';
client/views/pages/bbsDcry/video/VideoHistoryInsert.vue (Renamed from client/views/pages/user/VideoHistoryInsert.vue)
--- client/views/pages/user/VideoHistoryInsert.vue
+++ client/views/pages/bbsDcry/video/VideoHistoryInsert.vue
@@ -99,8 +99,8 @@
 <script>
 import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue';
 // COMPONENT
-import EditorComponent from '../../component/editor/EditorComponent.vue';
-import CategorySelectModal from '../../component/modal/CategorySelectModal.vue';
+import EditorComponent from '../../../component/editor/EditorComponent.vue';
+import CategorySelectModal from '../../../component/modal/CategorySelectModal.vue';
 // API
 import { findDcryProc, saveDcry, updateDcry } from '@/resources/api/dcry';
 
client/views/pages/bbsDcry/video/VideoHistorySearch.vue (Renamed from client/views/pages/user/VideoHistorySearch.vue)
--- client/views/pages/user/VideoHistorySearch.vue
+++ client/views/pages/bbsDcry/video/VideoHistorySearch.vue
@@ -118,7 +118,7 @@
 // COMPONENT
 import CardStyleComponent from '@/views/component/listLayout/CardStyleComponent.vue';
 import ListStyleComponent from '@/views/component/listLayout/ListStyleComponent.vue';
-import DefaultPagination from '@/views/component/DefaultPagination.vue';
+import DefaultPagination from '@/views/component/listLayout/DefaultPagination.vue';
 // API
 import { findAllByNullProc } from "@/resources/api/category";
 import { findDcrysProc } from "@/resources/api/dcry";
client/views/pages/bbsMediaVido/MediaVideoDetail.vue (Renamed from client/views/pages/user/MediaVideoDetail.vue)
--- client/views/pages/user/MediaVideoDetail.vue
+++ client/views/pages/bbsMediaVido/MediaVideoDetail.vue
No changes
client/views/pages/bbsMediaVido/MediaVideoInsert.vue (Renamed from client/views/pages/user/MediaVideoInsert.vue)
--- client/views/pages/user/MediaVideoInsert.vue
+++ client/views/pages/bbsMediaVido/MediaVideoInsert.vue
No changes
client/views/pages/bbsMediaVido/MediaVideoSearch.vue (Renamed from client/views/pages/user/MediaVideoSearch.vue)
--- client/views/pages/user/MediaVideoSearch.vue
+++ client/views/pages/bbsMediaVido/MediaVideoSearch.vue
@@ -113,7 +113,7 @@
 // COMPONENT
 import CardStyleComponent from '@/views/component/listLayout/CardStyleComponent.vue';
 import ListStyleComponent from '@/views/component/listLayout/ListStyleComponent.vue';
-import DefaultPagination from '@/views/component/DefaultPagination.vue';
+import DefaultPagination from '@/views/component/listLayout/DefaultPagination.vue';
 // API
 import { findAllByNullProc } from "@/resources/api/category";
 import { findAllMediaVidosProc } from "@/resources/api/mediaVido";
@@ -265,13 +265,13 @@
       }
     },
 
-    // 기록유형 전체 선택 여부 변경
+    // 검색범위 전체 선택 여부 변경
     fnChkAllOptions() {
       this.searchReqDTO.useSj = this.isChkAllScope;
       this.searchReqDTO.useCn = this.isChkAllScope;
     },
 
-    // 기록유형 선택 여부 변경
+    // 검색범위 선택 여부 변경
     fnChkOption() {
       this.isChkAllScope = this.searchReqDTO.useSj && this.searchReqDTO.useCn;
     },
 
client/views/pages/bbsNesDta/NewsReleaseDetail.vue (added)
+++ client/views/pages/bbsNesDta/NewsReleaseDetail.vue
@@ -0,0 +1,167 @@
+<template>
+  <div class="content">
+    <div class="sub-title-area mb-30">
+      <h2>보도자료</h2>
+      <div class="breadcrumb-list">
+        <ul>
+          <li>
+            <img :src="homeicon" alt="Home Icon">
+            <p>언론에서 바라본 구미시</p>
+          </li>
+          <li><img :src="righticon" alt=""></li>
+          <li>보도자료</li>
+        </ul>
+      </div>
+    </div>
+    <form class="gallery-form mb-40" @submit.prevent>
+      <dl class="mb-60">
+        <dd>
+          <p>{{ findResult.sj }}</p>
+          <div class="date flex align-center">
+            <img :src="calendaricon" alt="">
+            <span>{{ $dotFormatDate(findResult.rgsde) }}</span>
+          </div>
+        </dd>
+      </dl>
+      <div class="flex-sp-bw mb-50">
+        <div class="img-box">
+          <img v-if="findResult.hasOwnProperty('files') && findResult.files.length > 0" :src="findResult.files[0].filePath" :alt="썸네일" style="max-width: 100%" />
+          <img v-else :src="noimg" :alt="썸네일" />
+        </div>
+        <form class="info-form wfull" @submit.prevent>
+          <div class="info-box">
+            <h3>기본정보</h3>
+            <dl>
+              <dd class="mb-20">
+                <img :src="addressicon" alt="">
+                <span>링크</span>
+                <p>{{ findResult.link }}</p>
+              </dd>
+              <dd class="mb-20">
+                <img :src="yearicon" alt="">
+                <span>생산연도</span>
+                <p>{{ $dotFormatDate(findResult.prdctnYear) }}</p>
+              </dd>
+              <dd>
+                <img :src="categoryicon" alt="">
+                <span>카테고리</span>
+                <ul class="category">
+                  <li v-for="(item, idx) of findResult.ctgrys" :key="idx" class="category">{{ item.ctgryNm }}</li>
+                </ul>
+              </dd>
+            </dl>
+          </div>
+        </form>
+      </div>
+    </form>
+    <h3>내용</h3>
+    <form class="info-form mb-50" @submit.prevent>
+      <dl>
+        <dd>
+          <ViewerComponent :content="findResult.cn" />
+        </dd>
+      </dl>
+    </form>
+    <div class="btn-group flex-center">
+      <button class="red-line " type="button" @click="fnDelete">삭제</button>
+      <button class="blue-line " type="button" @click="fnMoveTo('edit', pageId)">수정</button>
+      <button class="gray-line-bg " type="button" @click="fnMoveTo('list')">목록</button>
+    </div>
+  </div>
+</template>
+<script>
+// COMPONENT
+import ViewerComponent from '@/views/component/editor/ViewerComponent.vue';
+// API
+import { findNesDtaProc, deleteNesDtaProc } from '@/resources/api/nesDta';
+
+export default {
+  components: {
+    ViewerComponent,
+  },
+
+  data() {
+    return {
+      // ICON
+      noimg: "client/resources/images/no_img.png",
+      calendaricon: 'client/resources/images/icon/calendaricon.png',
+      homeicon: 'client/resources/images/icon/home.png',
+      erroricon: 'client/resources/images/icon/error.png',
+      righticon: 'client/resources/images/icon/right.png',
+      addressicon: 'client/resources/images/icon/addressicon.png',
+      yearicon: 'client/resources/images/icon/yearicon.png',
+      categoryicon: 'client/resources/images/icon/categoryicon.png',
+
+      pageId: null,
+      findResult: {},
+      selectedFiles: [],
+    };
+  },
+
+  created() {
+    this.pageId = this.$route.query.id;
+    if (this.pageId === null) {
+      alert("게시물 존재하지 않습니다.");
+      this.fnMoveTo('list');
+    }
+  },
+
+  mounted() {
+    this.fnFindNesDta(); // 상세 조회
+  },
+
+  methods: {
+    // 상세 조회
+    async fnFindNesDta() {
+      try {
+        const response = await findNesDtaProc(this.pageId);
+        this.findResult = response.data.data.nesDta;
+      } catch (error) {
+        alert('조회중 오류가 발생했습니다.');
+
+        if (error.response) {
+          alert(error.response.data.message);
+        }
+        console.error(error.message);
+
+        this.fnMoveTo('list');
+      }
+    },
+
+    // 삭제
+    async fnDelete() {
+      let isCheck = confirm("해당 페이지를 삭제하시겠습니까?");
+      if (!isCheck) {
+        return;
+      }
+
+      try {
+        const response = await deleteNesDtaProc(this.pageId);
+        alert('해당 페이지를 삭제했습니다.');
+        this.fnMoveTo('list'); // 목록으로 이동
+      } catch (error) {
+        if (error.response) {
+          alert(error.response.data.message);
+        }
+        console.error(error.message);
+      }
+    },
+
+    // 페이지 이동
+    fnMoveTo(type, id) {
+      const routes = {
+        'list': { name: 'NewsReleaseSearch' },
+        'view': { name: 'NewsReleaseDetail', query: { id } },
+        'edit': { name: 'NewsReleaseInsert', query: this.$isEmpty(id) ? {} : { id } },
+      };
+
+      if (routes[type]) {
+        this.$router.push(routes[type]);
+      } else {
+        alert("올바르지 않은 경로를 요청하여 목록으로 이동합니다.");
+        this.$router.push(routes['list']);
+      }
+    },
+  },
+};
+</script>(파일 끝에 줄바꿈 문자 없음)
client/views/pages/bbsNesDta/NewsReleaseInsert.vue (copied from client/views/pages/user/PicHistoryInsert.vue)
--- client/views/pages/user/PicHistoryInsert.vue
+++ client/views/pages/bbsNesDta/NewsReleaseInsert.vue
@@ -0,0 +1,362 @@
+<template>
+  <div class="content">
+    <div class="sub-title-area mb-30">
+      <h2>보도자료</h2>
+      <div class="breadcrumb-list">
+        <ul>
+          <li>
+            <img :src="homeicon" alt="Home Icon">
+            <p>언론에서 바라본 구미시</p>
+          </li>
+          <li><img :src="righticon" alt=""></li>
+          <li>보도자료</li>
+        </ul>
+      </div>
+    </div>
+    <form class="insert-form mb-50" @submit.prevent>
+      <dl>
+        <dd>
+          <label for="sj" class="require">제목</label>
+          <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="requestDTO.prdctnYear">
+        </dd>
+        <div class="hr"></div>
+        <dd>
+          <label for="link">주소</label>
+          <div class="wfull"><input type="text" id="link" placeholder="URL 주소를 입력하세요" v-model="requestDTO.link"></div>
+        </dd>
+        <div class="hr"></div>
+        <dd>
+          <label for="text">내용</label>
+          <div class="wfull">
+            <EditorComponent v-model:contents="requestDTO.cn" />
+          </div>
+        </dd>
+        <div class="hr"></div>
+        <dd>
+          <label for="category" class="flex align-center">
+            <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>
+          </ul>
+        </dd>
+        <div class="hr"></div>
+        <dd>
+          <label for="file">썸네일</label>
+          <ul class="wfull">
+            <li class="flex align-center">
+              <p>파일첨부</p>
+              <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" 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>
+                </div>
+                <p>파일을 첨부하시려면 이 영역으로 파일을 끌고 오거나 클릭해주세요</p>
+              </label>
+              <p class="mb-10">파일목록</p>
+              <div id="fileNames" class="file-names">
+                <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">
+                    <img src="/client/resources/images/icon/imgicon.png" alt="fileicon">
+                    <p>{{ file.name }}</p>
+                  </div>
+                  <button type="button" class="cancel" @click="fnDelFile('new', idx)"><b>✕</b></button>
+                </div>
+                <!-- 기존 등록된 파일 목록 -->
+                <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>
+                  </div>
+                  <button type="button" class="cancel" @click="fnDelFile('old', file.fileId)"><b>✕</b></button>
+                </div>
+              </div>
+            </li>
+          </ul>
+        </dd>
+      </dl>
+    </form>
+    <div class="btn-group flex-center">
+      <button type="button" class="cancel" @click="fnMoveTo('list')">취소</button>
+      <button type="button" class="register" @click="submitForm">
+        <span v-if="$isEmpty(pageId)">등록</span>
+        <span v-else>수정</span>
+      </button>
+    </div>
+  </div>
+  <CategorySelectModal v-if="isModalOpen" :selectedCtgries="selectedCtgries" @toggleModal="fnToggleModal" @addCtgries="fnAddCtgries" />
+</template>
+<script>
+import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue';
+// COMPONENT
+import EditorComponent from '@/views/component/editor/EditorComponent.vue';
+import CategorySelectModal from '@/views/component/modal/CategorySelectModal.vue';
+// API
+import { findNesDtaProc, saveNesDtaProc, updateNesDtaProc } from '@/resources/api/nesDta';
+
+export default {
+  components: {
+    DoubleLeftOutlined,
+    LeftOutlined,
+    RightOutlined,
+    DoubleRightOutlined,
+    EditorComponent,
+    CategorySelectModal,
+  },
+
+  data() {
+    return {
+      // 아이콘 경로
+      homeicon: 'client/resources/images/icon/home.png',
+      erroricon: 'client/resources/images/icon/error.png',
+      righticon: 'client/resources/images/icon/right.png',
+      fileicon: 'client/resources/images/icon/file.png',
+      searchicon: 'client/resources/images/icon/search.png',
+
+      pageId: null,
+
+      isModalOpen: false,
+      isDragging: false,
+
+      fileNames: [],
+
+      // 등록/수정 요청 객체
+      requestDTO: {
+        nesDtaId: null,
+        sj: null,
+        cn: null,
+        link: null,
+        prdctnYear: null,
+        fileId: null,
+        files: [],
+        ctgryIds: [],
+      },
+
+      multipartFiles: [],
+      selectedCtgries: [], // 카테고리 목록
+    };
+  },
+
+  created() {
+    this.pageId = this.$route.query.id;
+    if (!this.$isEmpty(this.pageId)) {
+      this.fnFindDcry(); // 상세 조회
+    }
+  },
+
+  methods: {
+    // 상세 조회
+    async fnFindDcry() {
+      try {
+        const response = await findNesDtaProc(this.pageId);
+        this.copyToDcryReqDTO(response.data.data.nesDta);
+      } catch (error) {
+        alert('조회중 오류가 발생했습니다.');
+        this.fnMoveTo('list'); // 목록으로 이동
+
+        if (error.response) {
+          alert(error.response.data.message);
+        }
+        console.error(error.message);
+      }
+    },
+
+    // nesDta > requestDTO
+    copyToDcryReqDTO(nesDta) {
+      const copyFields = Object.keys(this.requestDTO).filter(key => key !== 'nesDtaId' && key !== 'ty' && key !== 'files');
+      copyFields.forEach(field => {
+        this.requestDTO[field] = this.$isEmpty(nesDta[field]) ? null : nesDta[field];
+      });
+
+      this.requestDTO.files = nesDta.files.length > 0 ? nesDta.files : []; // 기존 첨부파일
+
+      this.multipartFiles = [];
+      this.selectedCtgries = nesDta.ctgrys.length > 0 ? nesDta.ctgrys : [];
+
+      console.log(this.requestDTO);
+    },
+
+    // 카테고리 모달 열기/닫기
+    fnToggleModal() {
+      this.isModalOpen = !this.isModalOpen;
+    },
+
+    // 카테고리 등록
+    fnAddCtgries(selectedCtgries) {
+      this.selectedCtgries = [...this.selectedCtgries, ...selectedCtgries];
+
+      this.fnToggleModal(); // 카테고리 모달 닫기
+    },
+
+    // 카테고리 삭제
+    fnDelCtgry(id) {
+      this.selectedCtgries = this.selectedCtgries.filter(item => item.ctgryId !== id);
+    },
+
+    // 드래그 앤 드롭 이벤트 핸들러
+    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);
+      }
+    },
+    handleFileSelect(event) {
+      const files = event.target.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 file of files) {
+        const fileType = file.name.split('.').pop().toLowerCase();
+
+        // 파일 타입 검증
+        if (!allowedTypes.includes(fileType)) {
+          alert(`${file.name} 파일은 허용되지 않는 형식입니다. 이미지 파일(jpg, jpeg, png, gif)만 업로드 가능합니다.`);
+          return;
+        }
+
+        // 파일 크기 제한 검증
+        if (file.size > maxSize) {
+          alert(`${file.name} 파일이 10GB를 초과합니다.`);
+          return;
+        }
+
+        this.multipartFiles.push(file);
+      }
+    },
+
+    // 파일 삭제
+    fnDelFile(type, separator) {
+      if (type === 'new') {
+        this.multipartFiles.splice(separator, 1);
+      } else if (type === 'old') {
+        this.requestDTO.files = this.requestDTO.files.filter(item => item.fileId !== separator);
+      }
+    },
+
+    // 등록
+    async submitForm() {
+      // 유효성 검사
+      if (!this.requestDTO.sj) {
+        alert("제목을 입력해 주세요.");
+        return;
+      }
+
+      try {
+        const formData = new FormData();
+
+        // 텍스트 데이터 추가
+        formData.append('sj', this.requestDTO.sj);
+        formData.append('cn', this.requestDTO.cn);
+        formData.append('link', this.requestDTO.link);
+        formData.append('prdctnYear', this.requestDTO.prdctnYear);
+        formData.append('ty', this.requestDTO.ty);
+
+        // 게시물 아이디
+        if (!this.$isEmpty(this.pageId)) {
+          formData.append('nesDtaId', this.pageId);
+        }
+
+        // 파일 아이디
+        if (!this.$isEmpty(this.requestDTO.fileId)) {
+          formData.append('fileId', this.requestDTO.fileId);
+        }
+
+        // 카테고리 Ids 추가
+        if (this.selectedCtgries && this.selectedCtgries.length > 0) {
+          for (let ctgry of this.selectedCtgries) {
+            formData.append("ctgryIds", ctgry.ctgryId);
+          }
+        }
+
+        // 파일 추가
+        if (this.multipartFiles.length > 0) {
+          formData.append("multipartFiles", this.multipartFiles[0]);
+        }
+
+        // 기존파일 수정
+        if (!this.$isEmpty(this.pageId) && this.requestDTO.files.length > 0) {
+          for (let file of this.requestDTO.files) {
+            formData.append("files", file.fileId);
+          }
+        }
+
+        // API 통신
+        const response = this.$isEmpty(this.pageId) ? await saveNesDtaProc(formData) : await updateNesDtaProc(formData);
+        alert(this.$isEmpty(this.pageId) ? "등록되었습니다." : "수정되었습니다.");
+
+        // 상세 페이지로 이동
+        this.fnMoveTo('view', response.data.data.nesDtaId);
+      } catch (error) {
+        if (error.response) {
+          alert(error.response.data.message);
+        } else {
+          alert("에러가 발생했습니다.");
+        }
+        console.error(error.message);
+      };
+    },
+
+    // 페이지 이동
+    fnMoveTo(type, id) {
+      const routes = {
+        'list': { name: 'NewsReleaseSearch' },
+        'view': { name: 'NewsReleaseDetail', query: { id } },
+        'edit': { name: 'NewsReleaseInsert', query: this.$isEmpty(id) ? {} : { id } },
+      };
+
+      if (routes[type]) {
+        this.$router.push(routes[type]);
+      } else {
+        alert("올바르지 않은 경로를 요청하여 목록으로 이동합니다.");
+        this.$router.push(routes['list']);
+      }
+    }
+  }
+};
+</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/bbsNesDta/NewsReleaseSearch.vue (copied from client/views/pages/user/MediaVideoSearch.vue)
--- client/views/pages/user/MediaVideoSearch.vue
+++ client/views/pages/bbsNesDta/NewsReleaseSearch.vue
@@ -0,0 +1,285 @@
+<template>
+  <div class="content">
+    <div class="sub-title-area mb-30">
+      <h2>보도자료</h2>
+      <div class="breadcrumb-list">
+        <ul>
+          <li>
+            <img :src="homeicon" alt="Home Icon">
+            <p>언론에서 바라본 구미시</p>
+          </li>
+          <li><img :src="righticon" alt=""></li>
+          <li>보도자료</li>
+        </ul>
+      </div>
+    </div>
+    <div class="search-form form">
+      <dl>
+        <dd class="mb-15">
+          <p>검색범위</p>
+          <ul>
+            <li>
+              <input type="checkbox" id="allScope" v-model="isChkAllScope" @change="fnChkAllOptions" />
+              <label for="allScope">전체</label>
+            </li>
+            <li>
+              <input type="checkbox" id="searchSj" v-model="searchReqDTO.useSj" @change="fnChkOption" />
+              <label for="searchSj">제목</label>
+            </li>
+            <li>
+              <input type="checkbox" id="searchCn" v-model="searchReqDTO.useCn" @change="fnChkOption" />
+              <label for="searchCn">내용</label>
+            </li>
+          </ul>
+        </dd>
+        <dd class="mb-15">
+          <p>검색어</p>
+          <div class="wfull"><input type="text" v-model="searchReqDTO.searchText" v-on:keyup.enter="fnSearch()"></div>
+        </dd>
+        <dd class="mb-15">
+          <p>생산연도</p>
+          <input type="date" v-model="searchReqDTO.startYear">
+          <p class="mark">~</p>
+          <input type="date" v-model="searchReqDTO.endYear">
+        </dd>
+        <dd class="mb-20 category-dd">
+          <p>카테고리</p>
+          <ul>
+            <li v-for="(category, idx) of categorys" :key="idx">
+              <input type="checkbox" :id="'ctgry_' + idx" name="categorys" :value="category.ctgryId" v-model="searchReqDTO.searchCtgries" />
+              <label :for="'ctgry_' + idx">{{ category.ctgryNm }}</label>
+            </li>
+          </ul>
+        </dd>
+        <dd class="mb-15">
+          <p>정렬</p>
+          <ul>
+            <li v-for="(order, idx) of orders" :key="idx">
+              <input type="radio" :id="order.key" name="orders" :value="order.key" v-model="searchReqDTO.order" />
+              <label :for="order.key">{{ order.value }}</label>
+            </li>
+          </ul>
+        </dd>
+        <div class="btn-group">
+          <button type="button" class="reset" @click="init">
+            <img :src="reseticon" alt="">
+            <p>초기화</p>
+          </button>
+          <button type="button" class="search" @click="fnSearch">
+            <img :src="searchicon" alt="">
+            <p>검색</p>
+          </button>
+        </div>
+      </dl>
+    </div>
+    <div class="search-result">
+      <div class="tabs">
+        <div class="flex-sp-bw mb-20 align-center">
+          <div class="resultext ">
+            <img :src="resulticon" alt="">
+            <p>총 <b>{{ searchReqDTO.totalRecordCount }}개</b>의 보도자료가 검색되었습니다. </p>
+          </div>
+          <div class="flex">
+            <ul class="tab-box mb-20">
+              <li v-for="(tab, idx) in tabs" :key="idx" class="tab-title" :class="{ active: selectedTabId === tab.id }" @click="selectTab(tab.id)">
+                <img :src="selectedTabId === tab.id ? tab.activeImage : tab.inactiveImage" :alt="tab.title" class="tab-icon" />
+                <p><b>{{ tab.title }}</b></p>
+              </li>
+            </ul>
+            <div class="select-box">
+              <select v-model="searchReqDTO.recordSize" @change="fnSearch">
+                <option :value="24">24개</option>
+                <option :value="36">36개</option>
+                <option :value="100">100개</option>
+              </select>
+            </div>
+          </div>
+        </div>
+        <div class="tab-content">
+          <div v-if="searchResult.length > 0">
+            <CardStyleComponent v-if="selectedTabId === 'CARD'" :name="'N'" :list="searchResult" />
+            <ListStyleComponent v-if="selectedTabId === 'LIST'" :name="'N'" :list="searchResult" />
+          </div>
+          <div v-else class="no-results">
+            <p>등록된 게시물이 없습니다.</p>
+          </div>
+        </div>
+      </div>
+      <div class="btn-group flex-end mt-40"><button class="register"> <router-link :to="{ path: '/NewsReleaseInsert.page' }">등록</router-link></button></div>
+      <DefaultPagination class="mt-40" :search="searchReqDTO" @onChange="fnChangeCurrentPage" />
+    </div>
+  </div>
+</template>
+<script>
+// COMPONENT
+import CardStyleComponent from '@/views/component/listLayout/CardStyleComponent.vue';
+import ListStyleComponent from '@/views/component/listLayout/ListStyleComponent.vue';
+import DefaultPagination from '@/views/component/listLayout/DefaultPagination.vue';
+// API
+import { findAllByNullProc } from "@/resources/api/category";
+import { findAllNesDtasProc } from "@/resources/api/nesDta";
+
+export default {
+  components: {
+    DefaultPagination,
+    CardStyleComponent,
+    ListStyleComponent,
+  },
+
+  data() {
+    return {
+      // ICON
+      resulticon: "client/resources/images/icon/r-check.png",
+      homeicon: 'client/resources/images/icon/home.png',
+      searchicon: 'client/resources/images/icon/search.png',
+      reseticon: 'client/resources/images/icon/reset.png',
+      righticon: 'client/resources/images/icon/right.png',
+
+      // 검색용 객체
+      isChkAllScope: true, // 검색범위 전체 체크 여부
+      searchType: [
+        { key: "sj", value: "제목" },
+        { key: "cn", value: "내용" },
+        { key: "adres", value: "주소" },
+      ], // 검색범위 목록
+      categorys: [], // 카테고리 목록
+      orders: [
+        { key: "rgsde", value: "최신" },
+        { key: "rdcnt", value: "인기" },
+      ], // 정렬 목록
+
+      // 검색용 객체 초기값
+      searchDefault: {
+        useSj: true,
+        useCn: true,
+        searchText: null,
+        startYear: null,
+        endYear: null,
+        searchCtgries: [],
+        order: "rgsde",
+        // 페이지네이션
+        currentPage: 1, // 현재 페이지
+        recordSize: 24, // 한 페이지에 표시할 데이터 개수
+      },
+      searchReqDTO: {}, // 실제 검색에 사용되는 객체
+
+      searchResult: [], // 검색결과
+
+      // 목록 레이아웃
+      selectedTabId: null,
+      tabs: [
+        {
+          id: "CARD",
+          title: "카드형",
+          activeImage: "client/resources/images/list_icon01_on.png", // Active tab image
+          inactiveImage: "client/resources/images/list_icon01_off.png",
+        },
+        {
+          id: "LIST",
+          title: "리스트형",
+          activeImage: "client/resources/images/list_icon02_on.png", // Active tab image
+          inactiveImage: "client/resources/images/list_icon02_off.png",
+        },
+      ],
+
+      isInitialLoad: true // 초기 로드 여부
+    };
+  },
+
+  created() {
+    this.init(); // 초기화
+    this.fnFindCategorys(); // 카테고리 목록 조회 (검색조건 없음)
+  },
+
+  mounted() {
+    let searchText = this.$route.query.searchText;
+    if (searchText !== null) {
+      this.searchReqDTO.searchText = searchText;
+    }
+
+    this.fnSearch(); // 통합검색
+  },
+
+  methods: {
+    // 초기화
+    init() {
+      if (this.isInitialLoad) {
+        this.isInitialLoad = false;
+      } else {
+        if (!confirm('검색 조건을 초기화하시겠습니까?')) {
+          return;
+        }
+      }
+
+      this.searchReqDTO = JSON.parse(JSON.stringify(this.searchDefault));
+      this.searchResult = []; // 검색결과 초기화
+
+      this.selectedTabId = this.tabs[0].id;
+
+      this.fnSearch(); // 통합검색
+    },
+
+    // 카테고리 목록 조회
+    async fnFindCategorys() {
+      try {
+        const response = await findAllByNullProc();
+        this.categorys = response.data.data.ctgry;
+      } catch (error) {
+        this.categorys = []; // 카테고리 목록 초기화
+
+        if (error.response) {
+          alert(error.response.data.message);
+        }
+        console.error(error.message);
+      }
+    },
+
+    // 페이지 이동
+    fnChangeCurrentPage(currentPage) {
+      this.searchReqDTO.currentPage = Number(currentPage);
+      this.fnFindCategorys();
+    },
+
+    // 통합검색
+    async fnSearch() {
+      try {
+        const params = JSON.parse(JSON.stringify(this.searchReqDTO));
+
+        // 카테고리 목록 처리
+        if (this.searchReqDTO.searchCtgries && this.searchReqDTO.searchCtgries.length > 0) {
+          params.searchCtgries = this.searchReqDTO.searchCtgries.join(',');
+        } else {
+          delete params.searchCtgries;
+        }
+
+        // API 호출
+        const response = await findAllNesDtasProc(params);
+        this.searchResult = response.data.data.nesDtas;
+        this.searchReqDTO = response.data.data.search;
+      } catch (error) {
+        this.searchResult = []; // 검색결과 초기화
+
+        if (error.response) {
+          alert(error.response.data.message);
+        }
+        console.error(error.message);
+      }
+    },
+
+    // 검색범위 전체 선택 여부 변경
+    fnChkAllOptions() {
+      this.searchReqDTO.useSj = this.isChkAllScope;
+      this.searchReqDTO.useCn = this.isChkAllScope;
+    },
+
+    // 검색범위 선택 여부 변경
+    fnChkOption() {
+      this.isChkAllScope = this.searchReqDTO.useSj && this.searchReqDTO.useCn;
+    },
+
+    selectTab(tabId) {
+      this.selectedTabId = tabId;
+    },
+  },
+};
+</script>(파일 끝에 줄바꿈 문자 없음)
client/views/pages/ctgry/CategoryManagement.vue (Renamed from client/views/pages/user/CategoryManagement.vue)
--- client/views/pages/user/CategoryManagement.vue
+++ client/views/pages/ctgry/CategoryManagement.vue
No changes
 
client/views/pages/etc/NotFound.vue (deleted)
--- client/views/pages/etc/NotFound.vue
@@ -1,9 +0,0 @@
-<template>
-  <h1>에러페이지</h1>
-</template>
-<script>
-export default {
-
-}
-</script>
-<style></style>(파일 끝에 줄바꿈 문자 없음)
client/views/pages/main/TotalSearch.vue (Renamed from client/views/pages/user/TotalSearch.vue)
--- client/views/pages/user/TotalSearch.vue
+++ client/views/pages/main/TotalSearch.vue
@@ -108,7 +108,7 @@
 </template>
 <script>
 // COMPONENT
-import CardViewList from "../../component/CardViewList.vue";
+import CardViewList from "../../component/listLayout/CardViewList.vue";
 // API
 import { findAllDatas } from "../../../resources/api/main"; // 통합 검색
 import { findAllByNullProc } from "../../../resources/api/category"; // 카테고리 목록 검색
client/views/pages/member/MemberManagement.vue (Renamed from client/views/pages/user/MemberManagement.vue)
--- client/views/pages/user/MemberManagement.vue
+++ client/views/pages/member/MemberManagement.vue
No changes
 
client/views/pages/user/NewsReleaseDetail.vue (deleted)
--- client/views/pages/user/NewsReleaseDetail.vue
@@ -1,154 +0,0 @@
-<template>
-    <div class="content">
-        <div class="sub-title-area mb-30">
-            <h2>보도자료</h2>
-            <div class="breadcrumb-list">
-                <ul>
-                    <li><img :src="homeicon" alt="Home Icon">
-                        <p>언론에서 바라본 구미시</p>
-                    </li>
-                    <li><img :src="righticon" alt=""></li>
-                    <li>보도자료</li>
-                </ul>
-            </div>
-        </div>
-        <form action="" class="gallery-form mb-40">
-            <dl class="mb-60">
-                <dd>
-                    <p>보도자료 제목1
-                    </p>
-                    <div class="date flex align-center">
-                        <img :src="calendaricon" alt="">
-                        <span>2025.02.28</span>
-                    </div>
-                </dd>
-
-            </dl>
-            <div class="flex-sp-bw mb-50">
-                <div class="img-box">
-                    <img :src="noimg" alt="">
-                </div>
-                <div class="info-form wfull">
-                   <div class="info-box">
-                        <h3>기본정보</h3>
-                        <dl>
-                            <dd class="mb-20">
-                                <img :src="addressicon" alt="">
-                                <span>링크</span>
-                                <p>https://news.sbs.co.kr/news/endPage.do?news_id=N100800
-                                    9901&plink=STAND&cooper=NAVER</p>
-                            </dd>
-                            <dd class="mb-20">
-                                <img :src="yearicon" alt="">
-                                <span>생산연도</span>
-                                <p>2017</p>
-    
-                            </dd>
-                            <dd>
-                                <img :src="categoryicon" alt="">
-                                <span>카테고리</span>
-                                <ul class="category">
-                                    <li v-if="resultitem.category1" class="category1">카테고리1</li>
-                                    <li v-if="resultitem.category2" class="category2">카테고리2</li>
-                                </ul>
-    
-                            </dd>
-    
-                        </dl>
-                   </div>
-                </div>
-            </div>
-        </form>
-
-        <h3>내용</h3>
-        <form action="" class=" info-form mb-50">
-            <dl>
-                <dd>
-                    <p> 대한민국 최대의 내륙 산업단지를 보유하고, 서울로부터 277km, 부산으로부터 167km 거리에 있으며, 면적은 615㎢로 경상북도 전체 면적의 3.2%에 달합니다. 인구는
-                        41만 명이고, 선산읍, 고아읍, 산동읍을 비롯한 3읍, 5면, 17개 동으로 구성되어 있습니다.</p>
-
-                </dd>
-            </dl>
-        </form>
-
-
-        <div class="btn-group flex-center">
-            <button class="red-line w130" type="button" @click="fnDeleteUser">삭제</button>
-            <button class="blue-line w130" type="button" @click="fnUpdateUser">수정</button>
-            <button class="gray-line-bg w130" type="button" @click="fnUpdateUser">목록</button>
-        </div>
-    </div>
-</template>
-
-<script>
-import axios from "axios";
-import { ref } from 'vue';
-import { updateUsers, logOutProc, updatePassword } from "../../../resources/api/user"
-// Import Swiper Vue components
-import { CaretRightOutlined, PauseOutlined } from '@ant-design/icons-vue';
-import { Swiper, SwiperSlide } from 'swiper/vue';
-
-// Import Swiper styles
-import 'swiper/css';
-
-import 'swiper/css/free-mode';
-import 'swiper/css/navigation';
-import 'swiper/css/thumbs';
-
-// import required modules
-import { FreeMode, Navigation, Thumbs } from 'swiper/modules';
-
-export default {
-    components: {
-        PauseOutlined,
-        CaretRightOutlined,
-        Swiper,
-        SwiperSlide,
-    },
-    setup() {
-        const thumbsSwiper = ref(null);
-
-        const setThumbsSwiper = (swiper) => {
-            thumbsSwiper.value = swiper;
-        };
-
-        return {
-            thumbsSwiper,
-            setThumbsSwiper,
-            modules: [FreeMode, Navigation, Thumbs],
-        };
-    },
-    data() {
-        return {
-            resultitem: {
-                category1: true,
-                category2: true,
-            },
-            noimg: 'client/resources/images/img7.png',
-            calendaricon: 'client/resources/images/icon/calendaricon.png',
-            homeicon: 'client/resources/images/icon/home.png',
-            erroricon: 'client/resources/images/icon/error.png',
-            righticon: 'client/resources/images/icon/right.png',
-            addressicon: 'client/resources/images/icon/addressicon.png',
-            yearicon: 'client/resources/images/icon/yearicon.png',
-            categoryicon: 'client/resources/images/icon/categoryicon.png',
-            slides: [
-                { img: 'client/resources/images/visual.png', alt: 'Slide 1' },
-                { img: 'client/resources/images/visual.png', alt: 'Slide 2' },
-                { img: 'client/resources/images/visual.png', alt: 'Slide 3' },
-                { img: 'client/resources/images/visual.png', alt: 'Slide 3' },
-                { img: 'client/resources/images/visual.png', alt: 'Slide 3' },
-                { img: 'client/resources/images/visual.png', alt: 'Slide 3' },
-                // Add more slides as needed
-            ],
-
-        };
-    },
-    methods: {
-    },
-    watch: {},
-    computed: {
-    },
-    mounted() { },
-};
-</script>(파일 끝에 줄바꿈 문자 없음)
 
client/views/pages/user/NewsReleaseInsert.vue (deleted)
--- client/views/pages/user/NewsReleaseInsert.vue
@@ -1,212 +0,0 @@
-<template>
-  <div class="content">
-    <div class="sub-title-area mb-30">
-      <h2>영상 기록물</h2>
-      <div class="breadcrumb-list">
-        <ul>
-          <li><img :src="homeicon" alt="Home Icon">
-            <p>기록물</p>
-          </li>
-          <li><img :src="righticon" alt=""></li>
-          <li>영상 기록물</li>
-        </ul>
-      </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" placeholder="제목을 입력하세요."></div>
-        </dd>
-        <div class="hr"></div>
-        <dd>
-          <label for="year">생산연도</label>
-          <input type="text" id="year" placeholder="생산연도를 입력하세요">
-        </dd>
-        <div class="hr"></div>
-        <dd>
-          <label for="address">주소</label>
-          <div class="wfull"><input type="text" id="address" placeholder="주소를 입력하세요"></div>
-        </dd>
-        <div class="hr"></div>
-        <dd>
-          <label for="text">내용</label>
-          <div class="wfull">
-            <EditorComponent :contents="insertDTO.cn" />
-          </div>
-        </dd>
-        <div class="hr"></div>
-        <dd>
-          <label for="category" class="flex align-center">
-            <p>카테고리</p><button type="button" class="category-add" @click="openModal">추가하기</button>
-          </label>
-          <ul class="category">
-            <li v-for="(category, index) in selectedCtgries" :key="index"> {{ category }} <button type="button" class="cancel" @click="removeCategory(index)"><b>✕</b></button>
-            </li>
-          </ul>
-        </dd>
-        <div class="hr"></div>
-        <dd>
-          <label for="file" class="require">파일</label>
-          <ul class="wfull">
-            <li class="flex align-center">
-              <p>파일첨부</p>
-              <div class="invalid-feedback"><img :src="erroricon" alt=""><span>첨부파일은 10건까지 등록 가능하며, 건당 최대 100MB를 초과할 수 없습니다.</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">
-                <div class="flex-center align-center"><img :src="fileicon" alt="">
-                  <p>파일첨부하기</p>
-                </div>
-                <p>파일을 첨부하시려면 이 영역으로 파일을 끌고 오거나 클릭해주세요</p>
-              </label>
-              <p class="mb-10">파일목록</p>
-              <div id="fileNames" class="file-names">
-                <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>
-                  <button type="button" class="cancel" @click="removeFile(index)"><b>✕</b></button>
-                </div>
-              </div>
-            </li>
-          </ul>
-        </dd>
-      </dl>
-    </form>
-    <div class="btn-group flex-center">
-      <button class="cancel">취소</button>
-      <button class="register">등록</button>
-    </div>
-  </div>
-  <CategorySelectModal v-if="isModalOpen" :selectedCtgries="selectedCtgries" @toggleModal="fnToggleModal" />
-</template>
-<script>
-import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue';
-// COMPONENT
-import EditorComponent from '../../component/editor/EditorComponent.vue';
-import CategorySelectModal from '../../component/modal/CategorySelectModal.vue';
-
-export default {
-  components: {
-    DoubleLeftOutlined,
-    LeftOutlined,
-    RightOutlined,
-    DoubleRightOutlined,
-    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',
-      fileicon: 'client/resources/images/icon/file.png',
-      searchicon: 'client/resources/images/icon/search.png',
-
-      isModalOpen: false,
-
-      items: [
-        { id: 1, category: '카테고리 1', selected: false },
-        { id: 2, category: '카테고리 2', selected: false },
-        { id: 3, category: '카테고리 3', selected: false },
-      ],
-      fileNames: [],
-      insertDTO: {
-        sj: null, //제목
-        cn: null, //내용
-        adres: null, // 주소
-        prdctnYear: null, // 생산연도
-        ty: 'P', // 타입 ( P: 사진, V: 영상 )
-        multipartFiles: null, // 첨부파일 정보
-        ctgryIds: null, // 카테고리 정보
-      },
-
-      files: [],
-      selectedCtgries: [], // 카테고리 목록
-    };
-  },
-  computed: {
-    filteredItems() {
-      // This could be modified to support filtering based on searchQuery
-      return this.items.filter(item =>
-        item.category.includes(this.searchQuery)
-      );
-    }
-  },
-  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
-    },
-    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;
-    },
-  }
-};
-</script>
 
client/views/pages/user/NewsReleaseSearch.vue (deleted)
--- client/views/pages/user/NewsReleaseSearch.vue
@@ -1,414 +0,0 @@
-<template>
-    <div class="content">
-        <div class="sub-title-area mb-30">
-            <h2>보도자료</h2>
-            <div class="breadcrumb-list">
-                <ul>
-                    <!-- Bind the image source dynamically for homeicon -->
-                    <li><img :src="homeicon" alt="Home Icon">
-                        <p>언론에서 바라본 구미시</p>
-                    </li>
-                    <li><img :src="righticon" alt=""></li>
-                    <li>보도자료</li>
-                </ul>
-            </div>
-        </div>
-        <div action="search" class="search-form form ">
-            <dl>
-                <dd class="mb-15">
-                    <p>검색범위</p>
-                    <ul>
-                        <li>
-                            <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>
-                    </ul>
-                </dd>
-                <dd class="mb-15">
-                    <p>검색어</p>
-                    <div class="wfull"><input type="text" v-model="searchReqDTO.searchText"></div>
-                </dd>
-                <dd class="mb-15">
-                    <p>생산연도</p>
-                    <input type="date" v-model="searchReqDTO.startYear">
-                    <p class="mark">~</p>
-                    <input type="date" v-model="searchReqDTO.endYear">
-                </dd>
-                <dd class="mb-20 category-dd">
-                    <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" />
-                            <label :for="category.ctgryId">{{ category.ctgryNm }}</label>
-                        </li>
-                    </ul>
-                </dd>
-                <dd class="mb-15">
-                    <p>정렬</p>
-                    <ul>
-                        <li v-for="(order, idx) of orders" :key="idx">
-                            <input type="radio" :id="order.key" name="orders" :value="order.key"
-                                v-model="searchReqDTO.order" />
-                            <label :for="order.key">{{ order.value }}</label>
-                        </li>
-                    </ul>
-                </dd>
-                <div class="btn-group">
-                    <button class="reset"><img :src="reseticon" alt="">
-                        <p>초기화</p>
-                    </button>
-                    <button class="search"><img :src="searchicon" alt="">
-                        <p>검색</p>
-                    </button>
-                </div>
-
-            </dl>
-
-        </div>
-        <div class="search-result">
-            <div class="tabs">
-                <div class="flex-sp-bw mb-20 align-center">
-                    <div class="resultext ">
-                        <img :src="resulticon" alt="">
-                        <p>총 <b>{{ count }}개</b>의 보도자료가 검색되었습니다. </p>
-                    </div>
-                    <div class="flex ">
-                        <ul class="tab-box mb-20">
-                            <li v-for="(tab, index) in tabs" :key="index" class="tab-title"
-                                :class="{ active: selectedTab === tab.id }" @click="selectTab(tab.id)">
-                                <img :src="selectedTab === tab.id ? tab.activeImage : tab.inactiveImage"
-                                    :alt="tab.title" class="tab-icon" />
-                                <p><b>{{ tab.title }}</b></p>
-                            </li>
-                        </ul>
-                        <div class="select-box">
-                            <select v-model="itemsPerPage" @change="changeItemsPerPage">
-                                <option :value="5" selected>5개</option>
-                                <option :value="10">10개</option>
-                                <option :value="15">15개</option>
-                            </select>
-                        </div>
-                    </div>
-
-                </div>
-
-                <div class="tab-content">
-                    <!-- Loop through tabContents, and only display content that matches selectedTab -->
-                    <div v-for="(tabContent, idx) in tabContents" :key="idx">
-                        <!-- Display content only if the tab's ID matches the selectedTab -->
-                        <div v-show="tabContent.id === selectedTab">
-                            <!-- 카드형 Section (Card Layout) -->
-                            <div v-if="tabContent.viewType === 'card'">
-                                <ul class="card-wrap">
-                                    <li v-for="(resultitem, index) in paginatedItems" :key="index" class="mb-30">
-                                        <div class="result-box">
-                                            <!-- Main Image Section -->
-                                            <div class="main-img">
-                                                <img :src="resultitem.img" alt="" class="tab-image" />
-                                            </div>
-                                            <!-- Text Section -->
-                                            <div class="text-box">
-                                                <router-link :to="{ path: '/NewsReleaseDetail.page' }">
-                                                    <h5>{{ resultitem.title }}</h5>
-                                                </router-link>
-
-                                                <p class="address">{{ resultitem.address }}</p>
-                                                <p class="text">{{ resultitem.content }}</p>
-
-                                                <div class="mb-20">
-                                                    <ul class="category">
-                                                        <li v-if="resultitem.category1" class="category1">카테고리1</li>
-                                                        <li v-if="resultitem.category2" class="category2">카테고리2</li>
-                                                    </ul>
-                                                </div>
-
-                                                <div class="date">
-                                                    <ul>
-                                                        <li>생산연도 <b>{{ resultitem.year }}</b></li>
-                                                        <li>|</li>
-                                                        <li>등록 <b>{{ resultitem.date }}</b></li>
-                                                    </ul>
-                                                </div>
-                                            </div>
-                                        </div>
-                                    </li>
-                                </ul>
-
-                                <!-- Empty State if no results in paginatedItems -->
-                                <div v-if="paginatedItems.length === 0" class="no-results">
-                                    <p>등록된 게시물이 없습니다.</p>
-                                </div>
-                            </div>
-
-                            <!-- 리스트형 Section (List Layout) -->
-                            <div v-if="tabContent.viewType === 'list'">
-                                <ul class="list-wrap">
-                                    <li v-for="(resultitem, index) in paginatedItems" :key="index" class="mb-30">
-                                        <div class="text-box">
-                                            <router-link :to="{ path: '/NewsReleaseDetail.page' }">
-                                                    <h5>{{ resultitem.title }}</h5>
-                                                </router-link>
-                                            <p class="address">{{ resultitem.address }}</p>
-
-                                            <div class="flex-sp-bw">
-                                                <div class="mb-20">
-                                                    <ul class="category">
-                                                        <li v-if="resultitem.category1" class="category1">카테고리1</li>
-                                                        <li v-if="resultitem.category2" class="category2">카테고리2</li>
-                                                    </ul>
-                                                </div>
-
-                                                <div class="date ">
-                                                    <ul>
-                                                        <li>생산연도 <b>{{ resultitem.year }}</b></li>
-                                                        <li>|</li>
-                                                        <li>등록 <b>{{ resultitem.date }}</b></li>
-                                                    </ul>
-                                                </div>
-                                            </div>
-                                        </div>
-                                    </li>
-                                </ul>
-
-                                <!-- Empty State if no results in paginatedItems -->
-                                <div v-if="paginatedItems.length === 0" class="no-results">
-                                    <p>등록된 게시물이 없습니다.</p>
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-                </div>
-            </div>
-
-            <div class="btn-group flex-end mt-40"><button class="register"> <router-link
-                        :to="{ path: '/PicHistoryInsert.page' }">등록</router-link></button></div>
-            <div class="pagination flex-center mt-40">
-
-                <!-- Previous and Next Page Buttons -->
-                <button>
-                    <DoubleLeftOutlined />
-                </button>
-                <button @click="previousPage" :disabled="currentPage === 1">
-                    <LeftOutlined />
-                </button>
-                <button class="page-number clicked">1</button>
-                <button @click="nextPage" :disabled="currentPage === totalPages">
-                    <RightOutlined />
-                </button>
-                <button>
-                    <DoubleRightOutlined />
-                </button>
-            </div>
-        </div>
-    </div>
-
-</template>
-<script>
-import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue';
-import { findAllCategoryProc } from "../../../resources/api/category"; // 카테고리 목록 검색
-
-export default {
-    components: {
-        DoubleLeftOutlined,
-        LeftOutlined,
-        RightOutlined,
-        DoubleRightOutlined,
-    },
-    data() {
-        return {
-            selectedTab: 1,
-            // 검색용 객체
-            searchReqDTO: {
-                searchType: [],
-                searchText: null,
-                startYear: null,
-                endYear: null,
-                searchTy: null,
-                searchCtgry: [],
-                order: "rgsde",
-            },
-            tabs: [
-
-                {
-                    id: 1,
-                    title: "카드형",
-                    activeImage: "client/resources/images/list_icon01_on.png", // Active tab image
-                    inactiveImage: "client/resources/images/list_icon01_off.png",
-                },
-                {
-                    id: 2,
-                    title: "리스트형",
-                    activeImage: "client/resources/images/list_icon02_on.png", // Active tab image
-                    inactiveImage: "client/resources/images/list_icon02_off.png",
-                },
-            ],
-            tabContents: [
-                { id: 1, viewType: 'card', list: [{ sj: 'Item 1', rgsde: '2025-03-01', files: [{ filePath: 'image1.png' }] }] },
-                { id: 2, viewType: 'list', list: [{ sj: 'Item 2', rgsde: '2025-03-02', files: [{ filePath: 'image2.png' }] }] },
-            ],
-            paginatedItems: [],
-            resultitems: [
-                {
-                    img: 'client/resources/images/img6.png',
-                    title: '보도자료 제목',
-                    address: '경상북도 구미시 송정대로 55',
-                    content: '대한민국 최대의 내륙 산업단지를 보유하고, 서울로부터 277km, 부산으로부터 167km 거리에 있으며, 면적은 615㎢로 경상북도 전체 면적의 3.2%에 달합니다. 인구는 41만 명이고, 선산읍, 고아읍, 산동읍을 비롯한 3읍, 5면, 17개 동으로 구성되어…',
-                    category1: true,
-                    category2: true,
-                    year: 2020,
-                    date: '2021-01-01'
-                },
-
-            ],
-            currentPage: 1, // Current page number
-            itemsPerPage: 5,
-            resulticon: "client/resources/images/icon/r-check.png",
-            homeicon: 'client/resources/images/icon/home.png',
-            searchicon: 'client/resources/images/icon/search.png',
-            reseticon: 'client/resources/images/icon/reset.png',
-            righticon: 'client/resources/images/icon/right.png',
-            count: 23,
-            checkOptions: [
-                '전체',
-                '사진',
-                '영상',
-                '미디어 영상',
-                '보도자료',
-            ],
-            checkOptions2: [
-                '전체',
-                '제목',
-                '내용',
-                '주소',
-            ],
-            checkOptions3: [
-                '카테고리1',
-                '카테고리2',
-                '카테고리3',
-                '카테고리4',
-                '카테고리5',
-            ],
-            checkOptions4: [
-                '최신',
-                '인기',
-            ],
-            isChkAllScope: false, // 검색범위 전체 체크 여부
-            searchType: [
-                { key: "sj", value: "제목" },
-                { key: "cn", value: "내용" },
-                { key: "adres", value: "주소" },
-            ], // 검색범위 목록
-            categorys: [], // 카테고리 목록
-            orders: [
-                { key: "rgsde", value: "최신" },
-                { key: "rdcnt", value: "인기" },
-            ], // 정렬 목록
-        };
-    },
-    computed: {
-        // Total number of pages
-        totalPages() {
-            return Math.ceil(this.resultitems.length / this.itemsPerPage);
-        },
-
-        // Paginated items based on current page and items per page
-        paginatedItems() {
-            const start = (this.currentPage - 1) * this.itemsPerPage;
-            const end = start + this.itemsPerPage;
-            return this.resultitems.slice(start, end);
-        },
-    },
-    created() {
-        // 초기 데이터 세팅
-        this.isChkAllScope = true;
-        this.searchReqDTO.searchType = this.searchType.map(item => item.key);
-        this.searchReqDTO.order = this.orders[0].key
-
-        this.fnFindCategorys(); // 카테고리 목록 조회 (검색조건 없음)
-    },
-    methods: {
-        selectTab(tabId) {
-            this.selectedTab = tabId; // Update the selected tab index
-        },
-        // Change the number of items displayed per page
-        changeItemsPerPage() {
-            this.currentPage = 1; // Reset to first page when changing items per page
-        },
-        previousPage() {
-            if (this.currentPage > 1) {
-                this.currentPage--;
-            }
-        },
-
-        // Go to the next page
-        nextPage() {
-            if (this.currentPage < this.totalPages) {
-                this.currentPage++;
-            }
-        },
-        isChkAllScope: false, // 검색범위 전체 체크 여부
-        searchType: [
-            { key: "sj", value: "제목" },
-            { key: "cn", value: "내용" },
-            { key: "adres", value: "주소" },
-        ], // 검색범위 목록
-        categorys: [], // 카테고리 목록
-        orders: [
-            { key: "rgsde", value: "최신" },
-            { key: "rdcnt", value: "인기" },
-        ], // 정렬 목록
-
-        async fnFindCategorys() {
-            try {
-                const response = await findAllCategoryProc();
-                this.categorys = response.data.data.ctgry;
-            } catch (error) {
-                if (error.response) {
-                    console.log("에러 응답:", error.response.data);
-                }
-                console.error("Error:", error);
-            }
-        },
-
-        // 통합검색
-        async fnFindAllDatas() {
-            try {
-                let params = {};
-                if (this.searchReqDTO.searchRecord.length > 0) {
-                    params.searchRecords = this.searchReqDTO.searchRecord.join(',');
-                }
-                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);
-                this.searchResult = response.data.data.searchResult;
-            } catch (error) {
-                if (error.response) {
-                    console.log("에러 응답:", error.response.data);
-                }
-                console.error("Error:", error);
-            }
-        },
-    },
-
-
-
-};
-</script>
-<style scoped></style>(파일 끝에 줄바꿈 문자 없음)
server/modules/web/server.js
--- server/modules/web/server.js
+++ server/modules/web/server.js
@@ -142,7 +142,7 @@
  */
 webServer.use(function (error, request, response, next) {
   const errorCode = !error.statusCode ? 500 : error.statusCode;
-  response.redirect('/notFound.page'); // 에러 페이지로 유도
+  response.redirect('/'); // 메인 페이지로 유도
   let message = `[Error: ${errorCode}]${request.url} / n ${error.stack} / n`;
   Logger.logging(message);
 });
(파일 끝에 줄바꿈 문자 없음)
Add a comment
List