
--- client/views/component/CardViewList.vue
+++ client/views/component/listLayout/CardViewList.vue
No changes |
--- 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
... | ... | @@ -38,7 +38,7 @@ |
38 | 38 |
</template> |
39 | 39 |
<script> |
40 | 40 |
// COMPONENT |
41 |
-import DefaultPagination from '@/views/component/DefaultPagination.vue'; |
|
41 |
+import DefaultPagination from '@/views/component/listLayout/DefaultPagination.vue'; |
|
42 | 42 |
// API |
43 | 43 |
import { findAllCategoryProc } from '@/resources/api/category'; |
44 | 44 |
|
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
... | ... | @@ -2,65 +2,56 @@ |
2 | 2 |
import store from "./AppStore"; |
3 | 3 |
|
4 | 4 |
// 공통 |
5 |
-import Login from "./login/Login.vue"; |
|
6 | 5 |
import Main from "./main/Main.vue"; |
7 |
-import NotFound from "./etc/NotFound.vue"; |
|
6 |
+import TotalSearch from "./main/TotalSearch.vue"; |
|
8 | 7 |
// 회원관리 |
8 |
+import Login from "./login/Login.vue"; |
|
9 | 9 |
import MyInfo from "./user/MyInfo.vue"; |
10 |
-// 통합검색 |
|
11 |
-import TotalSearch from "./user/TotalSearch.vue"; |
|
12 |
-// 사진기록물 |
|
13 |
-import PicHistorySearch from "./user/PicHistorySearch.vue"; |
|
14 |
-import PicHistoryInsert from "./user/PicHistoryInsert.vue"; |
|
15 |
-import PicHistoryDetail from "./user/PicHistoryDetail.vue"; |
|
16 |
-// 영상기록물 |
|
17 |
-import VideoHistoryInsert from "./user/VideoHistoryInsert.vue"; |
|
18 |
-import VideoHistoryDetail from "./user/VideoHistoryDetail.vue"; |
|
19 |
-import VideoHistorySearch from "./user/VideoHistorySearch.vue"; |
|
10 |
+import MemberManagement from "./member/MemberManagement.vue"; |
|
11 |
+// 기록물 - 사진 |
|
12 |
+import PicHistorySearch from "./bbsDcry/photo/PicHistorySearch.vue"; |
|
13 |
+import PicHistoryInsert from "./bbsDcry/photo/PicHistoryInsert.vue"; |
|
14 |
+import PicHistoryDetail from "./bbsDcry/photo/PicHistoryDetail.vue"; |
|
15 |
+// 기록물 - 영상 |
|
16 |
+import VideoHistoryInsert from "./bbsDcry/video/VideoHistoryInsert.vue"; |
|
17 |
+import VideoHistoryDetail from "./bbsDcry/video/VideoHistoryDetail.vue"; |
|
18 |
+import VideoHistorySearch from "./bbsDcry/video/VideoHistorySearch.vue"; |
|
20 | 19 |
// 미디어 영상 |
21 |
-import MediaVideoInsert from "./user/MediaVideoInsert.vue"; |
|
22 |
-import MediaVideoDetail from "./user/MediaVideoDetail.vue"; |
|
23 |
-import MediaVideoSearch from "./user/MediaVideoSearch.vue"; |
|
20 |
+import MediaVideoInsert from "./bbsMediaVido/MediaVideoInsert.vue"; |
|
21 |
+import MediaVideoDetail from "./bbsMediaVido/MediaVideoDetail.vue"; |
|
22 |
+import MediaVideoSearch from "./bbsMediaVido/MediaVideoSearch.vue"; |
|
24 | 23 |
// 보도자료 |
25 |
-import NewsReleaseDetail from "./user/NewsReleaseDetail.vue"; |
|
26 |
-import NewsReleaseInsert from "./user/NewsReleaseInsert.vue"; |
|
27 |
-import NewsReleaseSearch from "./user/NewsReleaseSearch.vue"; |
|
28 |
- |
|
29 |
- |
|
30 |
-import MemberManagement from "./user/MemberManagement.vue"; |
|
31 |
-// 카테고리관리 |
|
32 |
-import CategoryManagement from "./user/CategoryManagement.vue"; |
|
33 |
- |
|
24 |
+import NewsReleaseDetail from "./bbsNesDta/NewsReleaseDetail.vue"; |
|
25 |
+import NewsReleaseInsert from "./bbsNesDta/NewsReleaseInsert.vue"; |
|
26 |
+import NewsReleaseSearch from "./bbsNesDta/NewsReleaseSearch.vue"; |
|
27 |
+// 카테고리 관리 |
|
28 |
+import CategoryManagement from "./ctgry/CategoryManagement.vue"; |
|
34 | 29 |
|
35 | 30 |
const routes = [ |
31 |
+ // 공통 |
|
36 | 32 |
{ path: "/", name: "MainPage", component: Main, meta: { authorization: ['ROLE_ADMIN', 'ROLE_USER'] } }, |
33 |
+ { path: "/TotalSearch.page", name: "TotalSearch", component: TotalSearch }, |
|
34 |
+ // 회원관리 |
|
37 | 35 |
{ path: "/Login.page", name: "Login", component: Login }, |
38 | 36 |
{ path: "/MyInfo.page", name: "MyInfo", component: MyInfo, meta: { authorization: ['ROLE_ADMIN', 'ROLE_USER'] } }, |
39 |
- { path: "/notFound.page", name: "NotFoundPage", component: NotFound }, |
|
40 |
- |
|
41 |
- { path: "/TotalSearch.page", name: "TotalSearch", component: TotalSearch }, |
|
42 |
- // 사진기록물 |
|
37 |
+ { path: "/MemberManagement.page", name: "MemberManagement", component: MemberManagement }, |
|
38 |
+ // 기록물 - 사진 |
|
43 | 39 |
{ path: "/PicHistorySearch.page", name: "PicHistorySearch", component: PicHistorySearch }, |
44 | 40 |
{ path: "/PicHistoryInsert.page", name: "PicHistoryInsert", component: PicHistoryInsert }, |
45 | 41 |
{ path: "/PicHistoryDetail.page", name: "PicHistoryDetail", component: PicHistoryDetail }, |
46 |
- // 영상기록물 |
|
42 |
+ // 기록물 - 영상 |
|
47 | 43 |
{ path: "/VideoHistorySearch.page", name: "VideoHistorySearch", component: VideoHistorySearch }, |
48 | 44 |
{ path: "/VideoHistoryInsert.page", name: "VideoHistoryInsert", component: VideoHistoryInsert }, |
49 | 45 |
{ path: "/VideoHistoryDetail.page", name: "VideoHistoryDetail", component: VideoHistoryDetail }, |
50 |
- |
|
51 | 46 |
// 미디어 영상 |
52 | 47 |
{ path: "/MediaVideoSearch.page", name: "MediaVideoSearch", component: MediaVideoSearch }, |
53 | 48 |
{ path: "/MediaVideoInsert.page", name: "MediaVideoInsert", component: MediaVideoInsert }, |
54 | 49 |
{ path: "/MediaVideoDetail.page", name: "MediaVideoDetail", component: MediaVideoDetail }, |
55 |
- |
|
56 |
-// 보도자료 |
|
57 |
-{ path: "/NewsReleaseSearch.page", name: "NewsReleaseSearch", component: NewsReleaseSearch }, |
|
50 |
+ // 보도자료 |
|
51 |
+ { path: "/NewsReleaseSearch.page", name: "NewsReleaseSearch", component: NewsReleaseSearch }, |
|
58 | 52 |
{ path: "/NewsReleaseInsert.page", name: "NewsReleaseInsert", component: NewsReleaseInsert }, |
59 | 53 |
{ path: "/NewsReleaseDetail.page", name: "NewsReleaseDetail", component: NewsReleaseDetail }, |
60 |
- |
|
61 |
- |
|
62 |
- |
|
63 |
- { path: "/MemberManagement.page", name: "MemberManagement", component: MemberManagement }, |
|
54 |
+ // 카테고리 관리 |
|
64 | 55 |
{ path: "/CategoryManagement.page", name: "CategoryManagement", component: CategoryManagement }, |
65 | 56 |
]; |
66 | 57 |
|
... | ... | @@ -95,7 +86,7 @@ |
95 | 86 |
} |
96 | 87 |
} |
97 | 88 |
if (!routeExists) { |
98 |
- next({ name: 'NotFoundPage' }); |
|
89 |
+ next('/'); |
|
99 | 90 |
return; |
100 | 91 |
} |
101 | 92 |
next(); |
--- client/views/pages/user/PicHistoryDetail.vue
+++ client/views/pages/bbsDcry/photo/PicHistoryDetail.vue
... | ... | @@ -96,7 +96,7 @@ |
96 | 96 |
// import required modules |
97 | 97 |
import { FreeMode, Navigation, Thumbs } from 'swiper/modules'; |
98 | 98 |
// COMPONENT |
99 |
-import ViewerComponent from '../../component/editor/ViewerComponent.vue'; |
|
99 |
+import ViewerComponent from '../../../component/editor/ViewerComponent.vue'; |
|
100 | 100 |
// API |
101 | 101 |
import { findDcryProc, deleteDcryProc } from '@/resources/api/dcry'; |
102 | 102 |
import { fileDownloadProc, multiFileDownloadProc } from '@/resources/api/file'; |
--- client/views/pages/user/PicHistoryInsert.vue
+++ client/views/pages/bbsDcry/photo/PicHistoryInsert.vue
... | ... | @@ -99,8 +99,8 @@ |
99 | 99 |
<script> |
100 | 100 |
import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; |
101 | 101 |
// COMPONENT |
102 |
-import EditorComponent from '../../component/editor/EditorComponent.vue'; |
|
103 |
-import CategorySelectModal from '../../component/modal/CategorySelectModal.vue'; |
|
102 |
+import EditorComponent from '../../../component/editor/EditorComponent.vue'; |
|
103 |
+import CategorySelectModal from '../../../component/modal/CategorySelectModal.vue'; |
|
104 | 104 |
// API |
105 | 105 |
import { findDcryProc, saveDcry, updateDcry } from '@/resources/api/dcry'; |
106 | 106 |
|
--- client/views/pages/user/PicHistorySearch.vue
+++ client/views/pages/bbsDcry/photo/PicHistorySearch.vue
... | ... | @@ -118,7 +118,7 @@ |
118 | 118 |
// COMPONENT |
119 | 119 |
import CardStyleComponent from '@/views/component/listLayout/CardStyleComponent.vue'; |
120 | 120 |
import ListStyleComponent from '@/views/component/listLayout/ListStyleComponent.vue'; |
121 |
-import DefaultPagination from '@/views/component/DefaultPagination.vue'; |
|
121 |
+import DefaultPagination from '@/views/component/listLayout/DefaultPagination.vue'; |
|
122 | 122 |
// API |
123 | 123 |
import { findAllByNullProc } from "@/resources/api/category"; |
124 | 124 |
import { findDcrysProc } from "@/resources/api/dcry"; |
--- client/views/pages/user/VideoHistoryDetail.vue
+++ client/views/pages/bbsDcry/video/VideoHistoryDetail.vue
... | ... | @@ -68,7 +68,7 @@ |
68 | 68 |
<script> |
69 | 69 |
import axios from "axios"; |
70 | 70 |
import { ref } from 'vue'; |
71 |
-import { updateUsers, logOutProc, updatePassword } from "../../../resources/api/user" |
|
71 |
+import { updateUsers, logOutProc, updatePassword } from "../../../../resources/api/user" |
|
72 | 72 |
// Import Swiper Vue components |
73 | 73 |
import { CaretRightOutlined, PauseOutlined } from '@ant-design/icons-vue'; |
74 | 74 |
import { Swiper, SwiperSlide } from 'swiper/vue'; |
--- client/views/pages/user/VideoHistoryInsert.vue
+++ client/views/pages/bbsDcry/video/VideoHistoryInsert.vue
... | ... | @@ -99,8 +99,8 @@ |
99 | 99 |
<script> |
100 | 100 |
import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; |
101 | 101 |
// COMPONENT |
102 |
-import EditorComponent from '../../component/editor/EditorComponent.vue'; |
|
103 |
-import CategorySelectModal from '../../component/modal/CategorySelectModal.vue'; |
|
102 |
+import EditorComponent from '../../../component/editor/EditorComponent.vue'; |
|
103 |
+import CategorySelectModal from '../../../component/modal/CategorySelectModal.vue'; |
|
104 | 104 |
// API |
105 | 105 |
import { findDcryProc, saveDcry, updateDcry } from '@/resources/api/dcry'; |
106 | 106 |
|
--- client/views/pages/user/VideoHistorySearch.vue
+++ client/views/pages/bbsDcry/video/VideoHistorySearch.vue
... | ... | @@ -118,7 +118,7 @@ |
118 | 118 |
// COMPONENT |
119 | 119 |
import CardStyleComponent from '@/views/component/listLayout/CardStyleComponent.vue'; |
120 | 120 |
import ListStyleComponent from '@/views/component/listLayout/ListStyleComponent.vue'; |
121 |
-import DefaultPagination from '@/views/component/DefaultPagination.vue'; |
|
121 |
+import DefaultPagination from '@/views/component/listLayout/DefaultPagination.vue'; |
|
122 | 122 |
// API |
123 | 123 |
import { findAllByNullProc } from "@/resources/api/category"; |
124 | 124 |
import { findDcrysProc } from "@/resources/api/dcry"; |
--- client/views/pages/user/MediaVideoDetail.vue
+++ client/views/pages/bbsMediaVido/MediaVideoDetail.vue
No changes |
--- client/views/pages/user/MediaVideoInsert.vue
+++ client/views/pages/bbsMediaVido/MediaVideoInsert.vue
No changes |
--- client/views/pages/user/MediaVideoSearch.vue
+++ client/views/pages/bbsMediaVido/MediaVideoSearch.vue
... | ... | @@ -113,7 +113,7 @@ |
113 | 113 |
// COMPONENT |
114 | 114 |
import CardStyleComponent from '@/views/component/listLayout/CardStyleComponent.vue'; |
115 | 115 |
import ListStyleComponent from '@/views/component/listLayout/ListStyleComponent.vue'; |
116 |
-import DefaultPagination from '@/views/component/DefaultPagination.vue'; |
|
116 |
+import DefaultPagination from '@/views/component/listLayout/DefaultPagination.vue'; |
|
117 | 117 |
// API |
118 | 118 |
import { findAllByNullProc } from "@/resources/api/category"; |
119 | 119 |
import { findAllMediaVidosProc } from "@/resources/api/mediaVido"; |
... | ... | @@ -265,13 +265,13 @@ |
265 | 265 |
} |
266 | 266 |
}, |
267 | 267 |
|
268 |
- // 기록유형 전체 선택 여부 변경 |
|
268 |
+ // 검색범위 전체 선택 여부 변경 |
|
269 | 269 |
fnChkAllOptions() { |
270 | 270 |
this.searchReqDTO.useSj = this.isChkAllScope; |
271 | 271 |
this.searchReqDTO.useCn = this.isChkAllScope; |
272 | 272 |
}, |
273 | 273 |
|
274 |
- // 기록유형 선택 여부 변경 |
|
274 |
+ // 검색범위 선택 여부 변경 |
|
275 | 275 |
fnChkOption() { |
276 | 276 |
this.isChkAllScope = this.searchReqDTO.useSj && this.searchReqDTO.useCn; |
277 | 277 |
}, |
+++ client/views/pages/bbsNesDta/NewsReleaseDetail.vue
... | ... | @@ -0,0 +1,167 @@ |
1 | +<template> | |
2 | + <div class="content"> | |
3 | + <div class="sub-title-area mb-30"> | |
4 | + <h2>보도자료</h2> | |
5 | + <div class="breadcrumb-list"> | |
6 | + <ul> | |
7 | + <li> | |
8 | + <img :src="homeicon" alt="Home Icon"> | |
9 | + <p>언론에서 바라본 구미시</p> | |
10 | + </li> | |
11 | + <li><img :src="righticon" alt=""></li> | |
12 | + <li>보도자료</li> | |
13 | + </ul> | |
14 | + </div> | |
15 | + </div> | |
16 | + <form class="gallery-form mb-40" @submit.prevent> | |
17 | + <dl class="mb-60"> | |
18 | + <dd> | |
19 | + <p>{{ findResult.sj }}</p> | |
20 | + <div class="date flex align-center"> | |
21 | + <img :src="calendaricon" alt=""> | |
22 | + <span>{{ $dotFormatDate(findResult.rgsde) }}</span> | |
23 | + </div> | |
24 | + </dd> | |
25 | + </dl> | |
26 | + <div class="flex-sp-bw mb-50"> | |
27 | + <div class="img-box"> | |
28 | + <img v-if="findResult.hasOwnProperty('files') && findResult.files.length > 0" :src="findResult.files[0].filePath" :alt="썸네일" style="max-width: 100%" /> | |
29 | + <img v-else :src="noimg" :alt="썸네일" /> | |
30 | + </div> | |
31 | + <form class="info-form wfull" @submit.prevent> | |
32 | + <div class="info-box"> | |
33 | + <h3>기본정보</h3> | |
34 | + <dl> | |
35 | + <dd class="mb-20"> | |
36 | + <img :src="addressicon" alt=""> | |
37 | + <span>링크</span> | |
38 | + <p>{{ findResult.link }}</p> | |
39 | + </dd> | |
40 | + <dd class="mb-20"> | |
41 | + <img :src="yearicon" alt=""> | |
42 | + <span>생산연도</span> | |
43 | + <p>{{ $dotFormatDate(findResult.prdctnYear) }}</p> | |
44 | + </dd> | |
45 | + <dd> | |
46 | + <img :src="categoryicon" alt=""> | |
47 | + <span>카테고리</span> | |
48 | + <ul class="category"> | |
49 | + <li v-for="(item, idx) of findResult.ctgrys" :key="idx" class="category">{{ item.ctgryNm }}</li> | |
50 | + </ul> | |
51 | + </dd> | |
52 | + </dl> | |
53 | + </div> | |
54 | + </form> | |
55 | + </div> | |
56 | + </form> | |
57 | + <h3>내용</h3> | |
58 | + <form class="info-form mb-50" @submit.prevent> | |
59 | + <dl> | |
60 | + <dd> | |
61 | + <ViewerComponent :content="findResult.cn" /> | |
62 | + </dd> | |
63 | + </dl> | |
64 | + </form> | |
65 | + <div class="btn-group flex-center"> | |
66 | + <button class="red-line " type="button" @click="fnDelete">삭제</button> | |
67 | + <button class="blue-line " type="button" @click="fnMoveTo('edit', pageId)">수정</button> | |
68 | + <button class="gray-line-bg " type="button" @click="fnMoveTo('list')">목록</button> | |
69 | + </div> | |
70 | + </div> | |
71 | +</template> | |
72 | +<script> | |
73 | +// COMPONENT | |
74 | +import ViewerComponent from '@/views/component/editor/ViewerComponent.vue'; | |
75 | +// API | |
76 | +import { findNesDtaProc, deleteNesDtaProc } from '@/resources/api/nesDta'; | |
77 | + | |
78 | +export default { | |
79 | + components: { | |
80 | + ViewerComponent, | |
81 | + }, | |
82 | + | |
83 | + data() { | |
84 | + return { | |
85 | + // ICON | |
86 | + noimg: "client/resources/images/no_img.png", | |
87 | + calendaricon: 'client/resources/images/icon/calendaricon.png', | |
88 | + homeicon: 'client/resources/images/icon/home.png', | |
89 | + erroricon: 'client/resources/images/icon/error.png', | |
90 | + righticon: 'client/resources/images/icon/right.png', | |
91 | + addressicon: 'client/resources/images/icon/addressicon.png', | |
92 | + yearicon: 'client/resources/images/icon/yearicon.png', | |
93 | + categoryicon: 'client/resources/images/icon/categoryicon.png', | |
94 | + | |
95 | + pageId: null, | |
96 | + findResult: {}, | |
97 | + selectedFiles: [], | |
98 | + }; | |
99 | + }, | |
100 | + | |
101 | + created() { | |
102 | + this.pageId = this.$route.query.id; | |
103 | + if (this.pageId === null) { | |
104 | + alert("게시물 존재하지 않습니다."); | |
105 | + this.fnMoveTo('list'); | |
106 | + } | |
107 | + }, | |
108 | + | |
109 | + mounted() { | |
110 | + this.fnFindNesDta(); // 상세 조회 | |
111 | + }, | |
112 | + | |
113 | + methods: { | |
114 | + // 상세 조회 | |
115 | + async fnFindNesDta() { | |
116 | + try { | |
117 | + const response = await findNesDtaProc(this.pageId); | |
118 | + this.findResult = response.data.data.nesDta; | |
119 | + } catch (error) { | |
120 | + alert('조회중 오류가 발생했습니다.'); | |
121 | + | |
122 | + if (error.response) { | |
123 | + alert(error.response.data.message); | |
124 | + } | |
125 | + console.error(error.message); | |
126 | + | |
127 | + this.fnMoveTo('list'); | |
128 | + } | |
129 | + }, | |
130 | + | |
131 | + // 삭제 | |
132 | + async fnDelete() { | |
133 | + let isCheck = confirm("해당 페이지를 삭제하시겠습니까?"); | |
134 | + if (!isCheck) { | |
135 | + return; | |
136 | + } | |
137 | + | |
138 | + try { | |
139 | + const response = await deleteNesDtaProc(this.pageId); | |
140 | + alert('해당 페이지를 삭제했습니다.'); | |
141 | + this.fnMoveTo('list'); // 목록으로 이동 | |
142 | + } catch (error) { | |
143 | + if (error.response) { | |
144 | + alert(error.response.data.message); | |
145 | + } | |
146 | + console.error(error.message); | |
147 | + } | |
148 | + }, | |
149 | + | |
150 | + // 페이지 이동 | |
151 | + fnMoveTo(type, id) { | |
152 | + const routes = { | |
153 | + 'list': { name: 'NewsReleaseSearch' }, | |
154 | + 'view': { name: 'NewsReleaseDetail', query: { id } }, | |
155 | + 'edit': { name: 'NewsReleaseInsert', query: this.$isEmpty(id) ? {} : { id } }, | |
156 | + }; | |
157 | + | |
158 | + if (routes[type]) { | |
159 | + this.$router.push(routes[type]); | |
160 | + } else { | |
161 | + alert("올바르지 않은 경로를 요청하여 목록으로 이동합니다."); | |
162 | + this.$router.push(routes['list']); | |
163 | + } | |
164 | + }, | |
165 | + }, | |
166 | +}; | |
167 | +</script>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/user/PicHistoryInsert.vue
+++ client/views/pages/bbsNesDta/NewsReleaseInsert.vue
... | ... | @@ -0,0 +1,362 @@ |
1 | +<template> | |
2 | + <div class="content"> | |
3 | + <div class="sub-title-area mb-30"> | |
4 | + <h2>보도자료</h2> | |
5 | + <div class="breadcrumb-list"> | |
6 | + <ul> | |
7 | + <li> | |
8 | + <img :src="homeicon" alt="Home Icon"> | |
9 | + <p>언론에서 바라본 구미시</p> | |
10 | + </li> | |
11 | + <li><img :src="righticon" alt=""></li> | |
12 | + <li>보도자료</li> | |
13 | + </ul> | |
14 | + </div> | |
15 | + </div> | |
16 | + <form class="insert-form mb-50" @submit.prevent> | |
17 | + <dl> | |
18 | + <dd> | |
19 | + <label for="sj" class="require">제목</label> | |
20 | + <div class="wfull"><input type="text" id="sj" placeholder="제목을 입력하세요." v-model="requestDTO.sj"></div> | |
21 | + </dd> | |
22 | + <div class="hr"></div> | |
23 | + <dd> | |
24 | + <label for="prdctnYear">생산연도</label> | |
25 | + <input type="text" id="prdctnYear" placeholder="생산연도를 입력하세요" v-model="requestDTO.prdctnYear"> | |
26 | + </dd> | |
27 | + <div class="hr"></div> | |
28 | + <dd> | |
29 | + <label for="link">주소</label> | |
30 | + <div class="wfull"><input type="text" id="link" placeholder="URL 주소를 입력하세요" v-model="requestDTO.link"></div> | |
31 | + </dd> | |
32 | + <div class="hr"></div> | |
33 | + <dd> | |
34 | + <label for="text">내용</label> | |
35 | + <div class="wfull"> | |
36 | + <EditorComponent v-model:contents="requestDTO.cn" /> | |
37 | + </div> | |
38 | + </dd> | |
39 | + <div class="hr"></div> | |
40 | + <dd> | |
41 | + <label for="category" class="flex align-center"> | |
42 | + <p>카테고리</p><button type="button" class="category-add" @click="fnToggleModal">추가하기</button> | |
43 | + </label> | |
44 | + <ul class="category"> | |
45 | + <li v-for="(item, idx) of selectedCtgries" :key="idx">{{ item.ctgryNm }} <button type="button" class="cancel" @click="fnDelCtgry(item.ctgryId)"><b>✕</b></button></li> | |
46 | + </ul> | |
47 | + </dd> | |
48 | + <div class="hr"></div> | |
49 | + <dd> | |
50 | + <label for="file">썸네일</label> | |
51 | + <ul class="wfull"> | |
52 | + <li class="flex align-center"> | |
53 | + <p>파일첨부</p> | |
54 | + <div class="invalid-feedback"><img :src="erroricon" alt=""><span>첨부파일은 건당 최대 10GB를 초과할 수 없습니다.</span></div> | |
55 | + </li> | |
56 | + <li class="file-insert"> | |
57 | + <input type="file" id="fileInput" class="file-input" accept="image/jpeg,image/png,image/gif,image/jpg" @change="handleFileSelect"> | |
58 | + <label for="fileInput" class="file-label mb-20" @dragover.prevent="handleDragOver" @dragleave.prevent="handleDragLeave" @drop.prevent="handleDrop" :class="{ 'drag-over': isDragging }"> | |
59 | + <div class="flex-center align-center"> | |
60 | + <img :src="fileicon" alt=""> | |
61 | + <p>파일첨부하기</p> | |
62 | + </div> | |
63 | + <p>파일을 첨부하시려면 이 영역으로 파일을 끌고 오거나 클릭해주세요</p> | |
64 | + </label> | |
65 | + <p class="mb-10">파일목록</p> | |
66 | + <div id="fileNames" class="file-names"> | |
67 | + <div v-if="requestDTO.files.length === 0 && multipartFiles.length === 0">선택된 파일이 없습니다.</div> | |
68 | + <!-- 새로 추가된 파일 목록 --> | |
69 | + <div v-for="(file, idx) of multipartFiles" :key="idx" class="flex-sp-bw mb-5 file-wrap"> | |
70 | + <div class="file-name"> | |
71 | + <img src="/client/resources/images/icon/imgicon.png" alt="fileicon"> | |
72 | + <p>{{ file.name }}</p> | |
73 | + </div> | |
74 | + <button type="button" class="cancel" @click="fnDelFile('new', idx)"><b>✕</b></button> | |
75 | + </div> | |
76 | + <!-- 기존 등록된 파일 목록 --> | |
77 | + <div v-for="(file, idx) of requestDTO.files" :key="idx" class="flex-sp-bw mb-5 file-wrap"> | |
78 | + <div class="file-name"> | |
79 | + <img src="/client/resources/images/icon/imgicon.png" alt="fileicon"> | |
80 | + <p>{{ file.fileNm }}</p> | |
81 | + </div> | |
82 | + <button type="button" class="cancel" @click="fnDelFile('old', file.fileId)"><b>✕</b></button> | |
83 | + </div> | |
84 | + </div> | |
85 | + </li> | |
86 | + </ul> | |
87 | + </dd> | |
88 | + </dl> | |
89 | + </form> | |
90 | + <div class="btn-group flex-center"> | |
91 | + <button type="button" class="cancel" @click="fnMoveTo('list')">취소</button> | |
92 | + <button type="button" class="register" @click="submitForm"> | |
93 | + <span v-if="$isEmpty(pageId)">등록</span> | |
94 | + <span v-else>수정</span> | |
95 | + </button> | |
96 | + </div> | |
97 | + </div> | |
98 | + <CategorySelectModal v-if="isModalOpen" :selectedCtgries="selectedCtgries" @toggleModal="fnToggleModal" @addCtgries="fnAddCtgries" /> | |
99 | +</template> | |
100 | +<script> | |
101 | +import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; | |
102 | +// COMPONENT | |
103 | +import EditorComponent from '@/views/component/editor/EditorComponent.vue'; | |
104 | +import CategorySelectModal from '@/views/component/modal/CategorySelectModal.vue'; | |
105 | +// API | |
106 | +import { findNesDtaProc, saveNesDtaProc, updateNesDtaProc } from '@/resources/api/nesDta'; | |
107 | + | |
108 | +export default { | |
109 | + components: { | |
110 | + DoubleLeftOutlined, | |
111 | + LeftOutlined, | |
112 | + RightOutlined, | |
113 | + DoubleRightOutlined, | |
114 | + EditorComponent, | |
115 | + CategorySelectModal, | |
116 | + }, | |
117 | + | |
118 | + data() { | |
119 | + return { | |
120 | + // 아이콘 경로 | |
121 | + homeicon: 'client/resources/images/icon/home.png', | |
122 | + erroricon: 'client/resources/images/icon/error.png', | |
123 | + righticon: 'client/resources/images/icon/right.png', | |
124 | + fileicon: 'client/resources/images/icon/file.png', | |
125 | + searchicon: 'client/resources/images/icon/search.png', | |
126 | + | |
127 | + pageId: null, | |
128 | + | |
129 | + isModalOpen: false, | |
130 | + isDragging: false, | |
131 | + | |
132 | + fileNames: [], | |
133 | + | |
134 | + // 등록/수정 요청 객체 | |
135 | + requestDTO: { | |
136 | + nesDtaId: null, | |
137 | + sj: null, | |
138 | + cn: null, | |
139 | + link: null, | |
140 | + prdctnYear: null, | |
141 | + fileId: null, | |
142 | + files: [], | |
143 | + ctgryIds: [], | |
144 | + }, | |
145 | + | |
146 | + multipartFiles: [], | |
147 | + selectedCtgries: [], // 카테고리 목록 | |
148 | + }; | |
149 | + }, | |
150 | + | |
151 | + created() { | |
152 | + this.pageId = this.$route.query.id; | |
153 | + if (!this.$isEmpty(this.pageId)) { | |
154 | + this.fnFindDcry(); // 상세 조회 | |
155 | + } | |
156 | + }, | |
157 | + | |
158 | + methods: { | |
159 | + // 상세 조회 | |
160 | + async fnFindDcry() { | |
161 | + try { | |
162 | + const response = await findNesDtaProc(this.pageId); | |
163 | + this.copyToDcryReqDTO(response.data.data.nesDta); | |
164 | + } catch (error) { | |
165 | + alert('조회중 오류가 발생했습니다.'); | |
166 | + this.fnMoveTo('list'); // 목록으로 이동 | |
167 | + | |
168 | + if (error.response) { | |
169 | + alert(error.response.data.message); | |
170 | + } | |
171 | + console.error(error.message); | |
172 | + } | |
173 | + }, | |
174 | + | |
175 | + // nesDta > requestDTO | |
176 | + copyToDcryReqDTO(nesDta) { | |
177 | + const copyFields = Object.keys(this.requestDTO).filter(key => key !== 'nesDtaId' && key !== 'ty' && key !== 'files'); | |
178 | + copyFields.forEach(field => { | |
179 | + this.requestDTO[field] = this.$isEmpty(nesDta[field]) ? null : nesDta[field]; | |
180 | + }); | |
181 | + | |
182 | + this.requestDTO.files = nesDta.files.length > 0 ? nesDta.files : []; // 기존 첨부파일 | |
183 | + | |
184 | + this.multipartFiles = []; | |
185 | + this.selectedCtgries = nesDta.ctgrys.length > 0 ? nesDta.ctgrys : []; | |
186 | + | |
187 | + console.log(this.requestDTO); | |
188 | + }, | |
189 | + | |
190 | + // 카테고리 모달 열기/닫기 | |
191 | + fnToggleModal() { | |
192 | + this.isModalOpen = !this.isModalOpen; | |
193 | + }, | |
194 | + | |
195 | + // 카테고리 등록 | |
196 | + fnAddCtgries(selectedCtgries) { | |
197 | + this.selectedCtgries = [...this.selectedCtgries, ...selectedCtgries]; | |
198 | + | |
199 | + this.fnToggleModal(); // 카테고리 모달 닫기 | |
200 | + }, | |
201 | + | |
202 | + // 카테고리 삭제 | |
203 | + fnDelCtgry(id) { | |
204 | + this.selectedCtgries = this.selectedCtgries.filter(item => item.ctgryId !== id); | |
205 | + }, | |
206 | + | |
207 | + // 드래그 앤 드롭 이벤트 핸들러 | |
208 | + handleDragOver(event) { | |
209 | + this.isDragging = true; | |
210 | + }, | |
211 | + handleDragLeave(event) { | |
212 | + this.isDragging = false; | |
213 | + }, | |
214 | + handleDrop(event) { | |
215 | + this.isDragging = false; | |
216 | + | |
217 | + const files = event.dataTransfer.files; | |
218 | + if (files.length > 0) { | |
219 | + this.processFiles(files); | |
220 | + } | |
221 | + }, | |
222 | + handleFileSelect(event) { | |
223 | + const files = event.target.files; | |
224 | + if (files.length > 0) { | |
225 | + this.processFiles(files); | |
226 | + } | |
227 | + }, | |
228 | + | |
229 | + // 파일 업로드 처리 함수 | |
230 | + processFiles(files) { | |
231 | + const allowedTypes = ['jpg', 'jpeg', 'png', 'gif']; // 이미지 파일만 허용 | |
232 | + const maxSize = 10 * 1024 * 1024 * 1024; // 10GB | |
233 | + | |
234 | + for (let file of files) { | |
235 | + const fileType = file.name.split('.').pop().toLowerCase(); | |
236 | + | |
237 | + // 파일 타입 검증 | |
238 | + if (!allowedTypes.includes(fileType)) { | |
239 | + alert(`${file.name} 파일은 허용되지 않는 형식입니다. 이미지 파일(jpg, jpeg, png, gif)만 업로드 가능합니다.`); | |
240 | + return; | |
241 | + } | |
242 | + | |
243 | + // 파일 크기 제한 검증 | |
244 | + if (file.size > maxSize) { | |
245 | + alert(`${file.name} 파일이 10GB를 초과합니다.`); | |
246 | + return; | |
247 | + } | |
248 | + | |
249 | + this.multipartFiles.push(file); | |
250 | + } | |
251 | + }, | |
252 | + | |
253 | + // 파일 삭제 | |
254 | + fnDelFile(type, separator) { | |
255 | + if (type === 'new') { | |
256 | + this.multipartFiles.splice(separator, 1); | |
257 | + } else if (type === 'old') { | |
258 | + this.requestDTO.files = this.requestDTO.files.filter(item => item.fileId !== separator); | |
259 | + } | |
260 | + }, | |
261 | + | |
262 | + // 등록 | |
263 | + async submitForm() { | |
264 | + // 유효성 검사 | |
265 | + if (!this.requestDTO.sj) { | |
266 | + alert("제목을 입력해 주세요."); | |
267 | + return; | |
268 | + } | |
269 | + | |
270 | + try { | |
271 | + const formData = new FormData(); | |
272 | + | |
273 | + // 텍스트 데이터 추가 | |
274 | + formData.append('sj', this.requestDTO.sj); | |
275 | + formData.append('cn', this.requestDTO.cn); | |
276 | + formData.append('link', this.requestDTO.link); | |
277 | + formData.append('prdctnYear', this.requestDTO.prdctnYear); | |
278 | + formData.append('ty', this.requestDTO.ty); | |
279 | + | |
280 | + // 게시물 아이디 | |
281 | + if (!this.$isEmpty(this.pageId)) { | |
282 | + formData.append('nesDtaId', this.pageId); | |
283 | + } | |
284 | + | |
285 | + // 파일 아이디 | |
286 | + if (!this.$isEmpty(this.requestDTO.fileId)) { | |
287 | + formData.append('fileId', this.requestDTO.fileId); | |
288 | + } | |
289 | + | |
290 | + // 카테고리 Ids 추가 | |
291 | + if (this.selectedCtgries && this.selectedCtgries.length > 0) { | |
292 | + for (let ctgry of this.selectedCtgries) { | |
293 | + formData.append("ctgryIds", ctgry.ctgryId); | |
294 | + } | |
295 | + } | |
296 | + | |
297 | + // 파일 추가 | |
298 | + if (this.multipartFiles.length > 0) { | |
299 | + formData.append("multipartFiles", this.multipartFiles[0]); | |
300 | + } | |
301 | + | |
302 | + // 기존파일 수정 | |
303 | + if (!this.$isEmpty(this.pageId) && this.requestDTO.files.length > 0) { | |
304 | + for (let file of this.requestDTO.files) { | |
305 | + formData.append("files", file.fileId); | |
306 | + } | |
307 | + } | |
308 | + | |
309 | + // API 통신 | |
310 | + const response = this.$isEmpty(this.pageId) ? await saveNesDtaProc(formData) : await updateNesDtaProc(formData); | |
311 | + alert(this.$isEmpty(this.pageId) ? "등록되었습니다." : "수정되었습니다."); | |
312 | + | |
313 | + // 상세 페이지로 이동 | |
314 | + this.fnMoveTo('view', response.data.data.nesDtaId); | |
315 | + } catch (error) { | |
316 | + if (error.response) { | |
317 | + alert(error.response.data.message); | |
318 | + } else { | |
319 | + alert("에러가 발생했습니다."); | |
320 | + } | |
321 | + console.error(error.message); | |
322 | + }; | |
323 | + }, | |
324 | + | |
325 | + // 페이지 이동 | |
326 | + fnMoveTo(type, id) { | |
327 | + const routes = { | |
328 | + 'list': { name: 'NewsReleaseSearch' }, | |
329 | + 'view': { name: 'NewsReleaseDetail', query: { id } }, | |
330 | + 'edit': { name: 'NewsReleaseInsert', query: this.$isEmpty(id) ? {} : { id } }, | |
331 | + }; | |
332 | + | |
333 | + if (routes[type]) { | |
334 | + this.$router.push(routes[type]); | |
335 | + } else { | |
336 | + alert("올바르지 않은 경로를 요청하여 목록으로 이동합니다."); | |
337 | + this.$router.push(routes['list']); | |
338 | + } | |
339 | + } | |
340 | + } | |
341 | +}; | |
342 | +</script> | |
343 | +<style> | |
344 | +.file-label { | |
345 | + border: 2px dashed #ddd; | |
346 | + border-radius: 8px; | |
347 | + padding: 20px; | |
348 | + text-align: center; | |
349 | + cursor: pointer; | |
350 | + transition: all 0.3s ease; | |
351 | +} | |
352 | + | |
353 | +.file-label:hover { | |
354 | + border-color: #aaa; | |
355 | + background-color: #f9f9f9; | |
356 | +} | |
357 | + | |
358 | +.file-label.drag-over { | |
359 | + border-color: #1890ff; | |
360 | + background-color: rgba(24, 144, 255, 0.1); | |
361 | +} | |
362 | +</style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/user/MediaVideoSearch.vue
+++ client/views/pages/bbsNesDta/NewsReleaseSearch.vue
... | ... | @@ -0,0 +1,285 @@ |
1 | +<template> | |
2 | + <div class="content"> | |
3 | + <div class="sub-title-area mb-30"> | |
4 | + <h2>보도자료</h2> | |
5 | + <div class="breadcrumb-list"> | |
6 | + <ul> | |
7 | + <li> | |
8 | + <img :src="homeicon" alt="Home Icon"> | |
9 | + <p>언론에서 바라본 구미시</p> | |
10 | + </li> | |
11 | + <li><img :src="righticon" alt=""></li> | |
12 | + <li>보도자료</li> | |
13 | + </ul> | |
14 | + </div> | |
15 | + </div> | |
16 | + <div class="search-form form"> | |
17 | + <dl> | |
18 | + <dd class="mb-15"> | |
19 | + <p>검색범위</p> | |
20 | + <ul> | |
21 | + <li> | |
22 | + <input type="checkbox" id="allScope" v-model="isChkAllScope" @change="fnChkAllOptions" /> | |
23 | + <label for="allScope">전체</label> | |
24 | + </li> | |
25 | + <li> | |
26 | + <input type="checkbox" id="searchSj" v-model="searchReqDTO.useSj" @change="fnChkOption" /> | |
27 | + <label for="searchSj">제목</label> | |
28 | + </li> | |
29 | + <li> | |
30 | + <input type="checkbox" id="searchCn" v-model="searchReqDTO.useCn" @change="fnChkOption" /> | |
31 | + <label for="searchCn">내용</label> | |
32 | + </li> | |
33 | + </ul> | |
34 | + </dd> | |
35 | + <dd class="mb-15"> | |
36 | + <p>검색어</p> | |
37 | + <div class="wfull"><input type="text" v-model="searchReqDTO.searchText" v-on:keyup.enter="fnSearch()"></div> | |
38 | + </dd> | |
39 | + <dd class="mb-15"> | |
40 | + <p>생산연도</p> | |
41 | + <input type="date" v-model="searchReqDTO.startYear"> | |
42 | + <p class="mark">~</p> | |
43 | + <input type="date" v-model="searchReqDTO.endYear"> | |
44 | + </dd> | |
45 | + <dd class="mb-20 category-dd"> | |
46 | + <p>카테고리</p> | |
47 | + <ul> | |
48 | + <li v-for="(category, idx) of categorys" :key="idx"> | |
49 | + <input type="checkbox" :id="'ctgry_' + idx" name="categorys" :value="category.ctgryId" v-model="searchReqDTO.searchCtgries" /> | |
50 | + <label :for="'ctgry_' + idx">{{ category.ctgryNm }}</label> | |
51 | + </li> | |
52 | + </ul> | |
53 | + </dd> | |
54 | + <dd class="mb-15"> | |
55 | + <p>정렬</p> | |
56 | + <ul> | |
57 | + <li v-for="(order, idx) of orders" :key="idx"> | |
58 | + <input type="radio" :id="order.key" name="orders" :value="order.key" v-model="searchReqDTO.order" /> | |
59 | + <label :for="order.key">{{ order.value }}</label> | |
60 | + </li> | |
61 | + </ul> | |
62 | + </dd> | |
63 | + <div class="btn-group"> | |
64 | + <button type="button" class="reset" @click="init"> | |
65 | + <img :src="reseticon" alt=""> | |
66 | + <p>초기화</p> | |
67 | + </button> | |
68 | + <button type="button" class="search" @click="fnSearch"> | |
69 | + <img :src="searchicon" alt=""> | |
70 | + <p>검색</p> | |
71 | + </button> | |
72 | + </div> | |
73 | + </dl> | |
74 | + </div> | |
75 | + <div class="search-result"> | |
76 | + <div class="tabs"> | |
77 | + <div class="flex-sp-bw mb-20 align-center"> | |
78 | + <div class="resultext "> | |
79 | + <img :src="resulticon" alt=""> | |
80 | + <p>총 <b>{{ searchReqDTO.totalRecordCount }}개</b>의 보도자료가 검색되었습니다. </p> | |
81 | + </div> | |
82 | + <div class="flex"> | |
83 | + <ul class="tab-box mb-20"> | |
84 | + <li v-for="(tab, idx) in tabs" :key="idx" class="tab-title" :class="{ active: selectedTabId === tab.id }" @click="selectTab(tab.id)"> | |
85 | + <img :src="selectedTabId === tab.id ? tab.activeImage : tab.inactiveImage" :alt="tab.title" class="tab-icon" /> | |
86 | + <p><b>{{ tab.title }}</b></p> | |
87 | + </li> | |
88 | + </ul> | |
89 | + <div class="select-box"> | |
90 | + <select v-model="searchReqDTO.recordSize" @change="fnSearch"> | |
91 | + <option :value="24">24개</option> | |
92 | + <option :value="36">36개</option> | |
93 | + <option :value="100">100개</option> | |
94 | + </select> | |
95 | + </div> | |
96 | + </div> | |
97 | + </div> | |
98 | + <div class="tab-content"> | |
99 | + <div v-if="searchResult.length > 0"> | |
100 | + <CardStyleComponent v-if="selectedTabId === 'CARD'" :name="'N'" :list="searchResult" /> | |
101 | + <ListStyleComponent v-if="selectedTabId === 'LIST'" :name="'N'" :list="searchResult" /> | |
102 | + </div> | |
103 | + <div v-else class="no-results"> | |
104 | + <p>등록된 게시물이 없습니다.</p> | |
105 | + </div> | |
106 | + </div> | |
107 | + </div> | |
108 | + <div class="btn-group flex-end mt-40"><button class="register"> <router-link :to="{ path: '/NewsReleaseInsert.page' }">등록</router-link></button></div> | |
109 | + <DefaultPagination class="mt-40" :search="searchReqDTO" @onChange="fnChangeCurrentPage" /> | |
110 | + </div> | |
111 | + </div> | |
112 | +</template> | |
113 | +<script> | |
114 | +// COMPONENT | |
115 | +import CardStyleComponent from '@/views/component/listLayout/CardStyleComponent.vue'; | |
116 | +import ListStyleComponent from '@/views/component/listLayout/ListStyleComponent.vue'; | |
117 | +import DefaultPagination from '@/views/component/listLayout/DefaultPagination.vue'; | |
118 | +// API | |
119 | +import { findAllByNullProc } from "@/resources/api/category"; | |
120 | +import { findAllNesDtasProc } from "@/resources/api/nesDta"; | |
121 | + | |
122 | +export default { | |
123 | + components: { | |
124 | + DefaultPagination, | |
125 | + CardStyleComponent, | |
126 | + ListStyleComponent, | |
127 | + }, | |
128 | + | |
129 | + data() { | |
130 | + return { | |
131 | + // ICON | |
132 | + resulticon: "client/resources/images/icon/r-check.png", | |
133 | + homeicon: 'client/resources/images/icon/home.png', | |
134 | + searchicon: 'client/resources/images/icon/search.png', | |
135 | + reseticon: 'client/resources/images/icon/reset.png', | |
136 | + righticon: 'client/resources/images/icon/right.png', | |
137 | + | |
138 | + // 검색용 객체 | |
139 | + isChkAllScope: true, // 검색범위 전체 체크 여부 | |
140 | + searchType: [ | |
141 | + { key: "sj", value: "제목" }, | |
142 | + { key: "cn", value: "내용" }, | |
143 | + { key: "adres", value: "주소" }, | |
144 | + ], // 검색범위 목록 | |
145 | + categorys: [], // 카테고리 목록 | |
146 | + orders: [ | |
147 | + { key: "rgsde", value: "최신" }, | |
148 | + { key: "rdcnt", value: "인기" }, | |
149 | + ], // 정렬 목록 | |
150 | + | |
151 | + // 검색용 객체 초기값 | |
152 | + searchDefault: { | |
153 | + useSj: true, | |
154 | + useCn: true, | |
155 | + searchText: null, | |
156 | + startYear: null, | |
157 | + endYear: null, | |
158 | + searchCtgries: [], | |
159 | + order: "rgsde", | |
160 | + // 페이지네이션 | |
161 | + currentPage: 1, // 현재 페이지 | |
162 | + recordSize: 24, // 한 페이지에 표시할 데이터 개수 | |
163 | + }, | |
164 | + searchReqDTO: {}, // 실제 검색에 사용되는 객체 | |
165 | + | |
166 | + searchResult: [], // 검색결과 | |
167 | + | |
168 | + // 목록 레이아웃 | |
169 | + selectedTabId: null, | |
170 | + tabs: [ | |
171 | + { | |
172 | + id: "CARD", | |
173 | + title: "카드형", | |
174 | + activeImage: "client/resources/images/list_icon01_on.png", // Active tab image | |
175 | + inactiveImage: "client/resources/images/list_icon01_off.png", | |
176 | + }, | |
177 | + { | |
178 | + id: "LIST", | |
179 | + title: "리스트형", | |
180 | + activeImage: "client/resources/images/list_icon02_on.png", // Active tab image | |
181 | + inactiveImage: "client/resources/images/list_icon02_off.png", | |
182 | + }, | |
183 | + ], | |
184 | + | |
185 | + isInitialLoad: true // 초기 로드 여부 | |
186 | + }; | |
187 | + }, | |
188 | + | |
189 | + created() { | |
190 | + this.init(); // 초기화 | |
191 | + this.fnFindCategorys(); // 카테고리 목록 조회 (검색조건 없음) | |
192 | + }, | |
193 | + | |
194 | + mounted() { | |
195 | + let searchText = this.$route.query.searchText; | |
196 | + if (searchText !== null) { | |
197 | + this.searchReqDTO.searchText = searchText; | |
198 | + } | |
199 | + | |
200 | + this.fnSearch(); // 통합검색 | |
201 | + }, | |
202 | + | |
203 | + methods: { | |
204 | + // 초기화 | |
205 | + init() { | |
206 | + if (this.isInitialLoad) { | |
207 | + this.isInitialLoad = false; | |
208 | + } else { | |
209 | + if (!confirm('검색 조건을 초기화하시겠습니까?')) { | |
210 | + return; | |
211 | + } | |
212 | + } | |
213 | + | |
214 | + this.searchReqDTO = JSON.parse(JSON.stringify(this.searchDefault)); | |
215 | + this.searchResult = []; // 검색결과 초기화 | |
216 | + | |
217 | + this.selectedTabId = this.tabs[0].id; | |
218 | + | |
219 | + this.fnSearch(); // 통합검색 | |
220 | + }, | |
221 | + | |
222 | + // 카테고리 목록 조회 | |
223 | + async fnFindCategorys() { | |
224 | + try { | |
225 | + const response = await findAllByNullProc(); | |
226 | + this.categorys = response.data.data.ctgry; | |
227 | + } catch (error) { | |
228 | + this.categorys = []; // 카테고리 목록 초기화 | |
229 | + | |
230 | + if (error.response) { | |
231 | + alert(error.response.data.message); | |
232 | + } | |
233 | + console.error(error.message); | |
234 | + } | |
235 | + }, | |
236 | + | |
237 | + // 페이지 이동 | |
238 | + fnChangeCurrentPage(currentPage) { | |
239 | + this.searchReqDTO.currentPage = Number(currentPage); | |
240 | + this.fnFindCategorys(); | |
241 | + }, | |
242 | + | |
243 | + // 통합검색 | |
244 | + async fnSearch() { | |
245 | + try { | |
246 | + const params = JSON.parse(JSON.stringify(this.searchReqDTO)); | |
247 | + | |
248 | + // 카테고리 목록 처리 | |
249 | + if (this.searchReqDTO.searchCtgries && this.searchReqDTO.searchCtgries.length > 0) { | |
250 | + params.searchCtgries = this.searchReqDTO.searchCtgries.join(','); | |
251 | + } else { | |
252 | + delete params.searchCtgries; | |
253 | + } | |
254 | + | |
255 | + // API 호출 | |
256 | + const response = await findAllNesDtasProc(params); | |
257 | + this.searchResult = response.data.data.nesDtas; | |
258 | + this.searchReqDTO = response.data.data.search; | |
259 | + } catch (error) { | |
260 | + this.searchResult = []; // 검색결과 초기화 | |
261 | + | |
262 | + if (error.response) { | |
263 | + alert(error.response.data.message); | |
264 | + } | |
265 | + console.error(error.message); | |
266 | + } | |
267 | + }, | |
268 | + | |
269 | + // 검색범위 전체 선택 여부 변경 | |
270 | + fnChkAllOptions() { | |
271 | + this.searchReqDTO.useSj = this.isChkAllScope; | |
272 | + this.searchReqDTO.useCn = this.isChkAllScope; | |
273 | + }, | |
274 | + | |
275 | + // 검색범위 선택 여부 변경 | |
276 | + fnChkOption() { | |
277 | + this.isChkAllScope = this.searchReqDTO.useSj && this.searchReqDTO.useCn; | |
278 | + }, | |
279 | + | |
280 | + selectTab(tabId) { | |
281 | + this.selectedTabId = tabId; | |
282 | + }, | |
283 | + }, | |
284 | +}; | |
285 | +</script>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/user/CategoryManagement.vue
+++ client/views/pages/ctgry/CategoryManagement.vue
No changes |
--- client/views/pages/etc/NotFound.vue
... | ... | @@ -1,9 +0,0 @@ |
1 | -<template> | |
2 | - <h1>에러페이지</h1> | |
3 | -</template> | |
4 | -<script> | |
5 | -export default { | |
6 | - | |
7 | -} | |
8 | -</script> | |
9 | -<style></style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/user/TotalSearch.vue
+++ client/views/pages/main/TotalSearch.vue
... | ... | @@ -108,7 +108,7 @@ |
108 | 108 |
</template> |
109 | 109 |
<script> |
110 | 110 |
// COMPONENT |
111 |
-import CardViewList from "../../component/CardViewList.vue"; |
|
111 |
+import CardViewList from "../../component/listLayout/CardViewList.vue"; |
|
112 | 112 |
// API |
113 | 113 |
import { findAllDatas } from "../../../resources/api/main"; // 통합 검색 |
114 | 114 |
import { findAllByNullProc } from "../../../resources/api/category"; // 카테고리 목록 검색 |
--- client/views/pages/user/MemberManagement.vue
+++ client/views/pages/member/MemberManagement.vue
No changes |
--- client/views/pages/user/NewsReleaseDetail.vue
... | ... | @@ -1,154 +0,0 @@ |
1 | -<template> | |
2 | - <div class="content"> | |
3 | - <div class="sub-title-area mb-30"> | |
4 | - <h2>보도자료</h2> | |
5 | - <div class="breadcrumb-list"> | |
6 | - <ul> | |
7 | - <li><img :src="homeicon" alt="Home Icon"> | |
8 | - <p>언론에서 바라본 구미시</p> | |
9 | - </li> | |
10 | - <li><img :src="righticon" alt=""></li> | |
11 | - <li>보도자료</li> | |
12 | - </ul> | |
13 | - </div> | |
14 | - </div> | |
15 | - <form action="" class="gallery-form mb-40"> | |
16 | - <dl class="mb-60"> | |
17 | - <dd> | |
18 | - <p>보도자료 제목1 | |
19 | - </p> | |
20 | - <div class="date flex align-center"> | |
21 | - <img :src="calendaricon" alt=""> | |
22 | - <span>2025.02.28</span> | |
23 | - </div> | |
24 | - </dd> | |
25 | - | |
26 | - </dl> | |
27 | - <div class="flex-sp-bw mb-50"> | |
28 | - <div class="img-box"> | |
29 | - <img :src="noimg" alt=""> | |
30 | - </div> | |
31 | - <div class="info-form wfull"> | |
32 | - <div class="info-box"> | |
33 | - <h3>기본정보</h3> | |
34 | - <dl> | |
35 | - <dd class="mb-20"> | |
36 | - <img :src="addressicon" alt=""> | |
37 | - <span>링크</span> | |
38 | - <p>https://news.sbs.co.kr/news/endPage.do?news_id=N100800 | |
39 | - 9901&plink=STAND&cooper=NAVER</p> | |
40 | - </dd> | |
41 | - <dd class="mb-20"> | |
42 | - <img :src="yearicon" alt=""> | |
43 | - <span>생산연도</span> | |
44 | - <p>2017</p> | |
45 | - | |
46 | - </dd> | |
47 | - <dd> | |
48 | - <img :src="categoryicon" alt=""> | |
49 | - <span>카테고리</span> | |
50 | - <ul class="category"> | |
51 | - <li v-if="resultitem.category1" class="category1">카테고리1</li> | |
52 | - <li v-if="resultitem.category2" class="category2">카테고리2</li> | |
53 | - </ul> | |
54 | - | |
55 | - </dd> | |
56 | - | |
57 | - </dl> | |
58 | - </div> | |
59 | - </div> | |
60 | - </div> | |
61 | - </form> | |
62 | - | |
63 | - <h3>내용</h3> | |
64 | - <form action="" class=" info-form mb-50"> | |
65 | - <dl> | |
66 | - <dd> | |
67 | - <p> 대한민국 최대의 내륙 산업단지를 보유하고, 서울로부터 277km, 부산으로부터 167km 거리에 있으며, 면적은 615㎢로 경상북도 전체 면적의 3.2%에 달합니다. 인구는 | |
68 | - 41만 명이고, 선산읍, 고아읍, 산동읍을 비롯한 3읍, 5면, 17개 동으로 구성되어 있습니다.</p> | |
69 | - | |
70 | - </dd> | |
71 | - </dl> | |
72 | - </form> | |
73 | - | |
74 | - | |
75 | - <div class="btn-group flex-center"> | |
76 | - <button class="red-line w130" type="button" @click="fnDeleteUser">삭제</button> | |
77 | - <button class="blue-line w130" type="button" @click="fnUpdateUser">수정</button> | |
78 | - <button class="gray-line-bg w130" type="button" @click="fnUpdateUser">목록</button> | |
79 | - </div> | |
80 | - </div> | |
81 | -</template> | |
82 | - | |
83 | -<script> | |
84 | -import axios from "axios"; | |
85 | -import { ref } from 'vue'; | |
86 | -import { updateUsers, logOutProc, updatePassword } from "../../../resources/api/user" | |
87 | -// Import Swiper Vue components | |
88 | -import { CaretRightOutlined, PauseOutlined } from '@ant-design/icons-vue'; | |
89 | -import { Swiper, SwiperSlide } from 'swiper/vue'; | |
90 | - | |
91 | -// Import Swiper styles | |
92 | -import 'swiper/css'; | |
93 | - | |
94 | -import 'swiper/css/free-mode'; | |
95 | -import 'swiper/css/navigation'; | |
96 | -import 'swiper/css/thumbs'; | |
97 | - | |
98 | -// import required modules | |
99 | -import { FreeMode, Navigation, Thumbs } from 'swiper/modules'; | |
100 | - | |
101 | -export default { | |
102 | - components: { | |
103 | - PauseOutlined, | |
104 | - CaretRightOutlined, | |
105 | - Swiper, | |
106 | - SwiperSlide, | |
107 | - }, | |
108 | - setup() { | |
109 | - const thumbsSwiper = ref(null); | |
110 | - | |
111 | - const setThumbsSwiper = (swiper) => { | |
112 | - thumbsSwiper.value = swiper; | |
113 | - }; | |
114 | - | |
115 | - return { | |
116 | - thumbsSwiper, | |
117 | - setThumbsSwiper, | |
118 | - modules: [FreeMode, Navigation, Thumbs], | |
119 | - }; | |
120 | - }, | |
121 | - data() { | |
122 | - return { | |
123 | - resultitem: { | |
124 | - category1: true, | |
125 | - category2: true, | |
126 | - }, | |
127 | - noimg: 'client/resources/images/img7.png', | |
128 | - calendaricon: 'client/resources/images/icon/calendaricon.png', | |
129 | - homeicon: 'client/resources/images/icon/home.png', | |
130 | - erroricon: 'client/resources/images/icon/error.png', | |
131 | - righticon: 'client/resources/images/icon/right.png', | |
132 | - addressicon: 'client/resources/images/icon/addressicon.png', | |
133 | - yearicon: 'client/resources/images/icon/yearicon.png', | |
134 | - categoryicon: 'client/resources/images/icon/categoryicon.png', | |
135 | - slides: [ | |
136 | - { img: 'client/resources/images/visual.png', alt: 'Slide 1' }, | |
137 | - { img: 'client/resources/images/visual.png', alt: 'Slide 2' }, | |
138 | - { img: 'client/resources/images/visual.png', alt: 'Slide 3' }, | |
139 | - { img: 'client/resources/images/visual.png', alt: 'Slide 3' }, | |
140 | - { img: 'client/resources/images/visual.png', alt: 'Slide 3' }, | |
141 | - { img: 'client/resources/images/visual.png', alt: 'Slide 3' }, | |
142 | - // Add more slides as needed | |
143 | - ], | |
144 | - | |
145 | - }; | |
146 | - }, | |
147 | - methods: { | |
148 | - }, | |
149 | - watch: {}, | |
150 | - computed: { | |
151 | - }, | |
152 | - mounted() { }, | |
153 | -}; | |
154 | -</script>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/user/NewsReleaseInsert.vue
... | ... | @@ -1,212 +0,0 @@ |
1 | -<template> | |
2 | - <div class="content"> | |
3 | - <div class="sub-title-area mb-30"> | |
4 | - <h2>영상 기록물</h2> | |
5 | - <div class="breadcrumb-list"> | |
6 | - <ul> | |
7 | - <li><img :src="homeicon" alt="Home Icon"> | |
8 | - <p>기록물</p> | |
9 | - </li> | |
10 | - <li><img :src="righticon" alt=""></li> | |
11 | - <li>영상 기록물</li> | |
12 | - </ul> | |
13 | - </div> | |
14 | - </div> | |
15 | - <form action="" class="insert-form mb-50"> | |
16 | - <dl> | |
17 | - <dd> | |
18 | - <label for="id" class="require">제목</label> | |
19 | - <div class="wfull"><input type="text" id="id" placeholder="제목을 입력하세요."></div> | |
20 | - </dd> | |
21 | - <div class="hr"></div> | |
22 | - <dd> | |
23 | - <label for="year">생산연도</label> | |
24 | - <input type="text" id="year" placeholder="생산연도를 입력하세요"> | |
25 | - </dd> | |
26 | - <div class="hr"></div> | |
27 | - <dd> | |
28 | - <label for="address">주소</label> | |
29 | - <div class="wfull"><input type="text" id="address" placeholder="주소를 입력하세요"></div> | |
30 | - </dd> | |
31 | - <div class="hr"></div> | |
32 | - <dd> | |
33 | - <label for="text">내용</label> | |
34 | - <div class="wfull"> | |
35 | - <EditorComponent :contents="insertDTO.cn" /> | |
36 | - </div> | |
37 | - </dd> | |
38 | - <div class="hr"></div> | |
39 | - <dd> | |
40 | - <label for="category" class="flex align-center"> | |
41 | - <p>카테고리</p><button type="button" class="category-add" @click="openModal">추가하기</button> | |
42 | - </label> | |
43 | - <ul class="category"> | |
44 | - <li v-for="(category, index) in selectedCtgries" :key="index"> {{ category }} <button type="button" class="cancel" @click="removeCategory(index)"><b>✕</b></button> | |
45 | - </li> | |
46 | - </ul> | |
47 | - </dd> | |
48 | - <div class="hr"></div> | |
49 | - <dd> | |
50 | - <label for="file" class="require">파일</label> | |
51 | - <ul class="wfull"> | |
52 | - <li class="flex align-center"> | |
53 | - <p>파일첨부</p> | |
54 | - <div class="invalid-feedback"><img :src="erroricon" alt=""><span>첨부파일은 10건까지 등록 가능하며, 건당 최대 100MB를 초과할 수 없습니다.</span></div> | |
55 | - </li> | |
56 | - <li class="file-insert"> | |
57 | - <input type="file" id="fileInput" class="file-input" multiple @change="showFileNames"> | |
58 | - <label for="fileInput" class="file-label mb-20"> | |
59 | - <div class="flex-center align-center"><img :src="fileicon" alt=""> | |
60 | - <p>파일첨부하기</p> | |
61 | - </div> | |
62 | - <p>파일을 첨부하시려면 이 영역으로 파일을 끌고 오거나 클릭해주세요</p> | |
63 | - </label> | |
64 | - <p class="mb-10">파일목록</p> | |
65 | - <div id="fileNames" class="file-names"> | |
66 | - <span v-if="fileNames.length === 0">선택된 파일이 없습니다.</span> | |
67 | - <div v-for="(file, index) in fileNames" :key="index" class="flex-sp-bw mb-5 file-wrap"> | |
68 | - <div class="file-name"> | |
69 | - <!-- Corrected here: Use file.icon instead of fileicons.img --> | |
70 | - <img :src="file.icon" alt="fileicon"> | |
71 | - <p>{{ file.name }}</p> | |
72 | - </div> | |
73 | - <button type="button" class="cancel" @click="removeFile(index)"><b>✕</b></button> | |
74 | - </div> | |
75 | - </div> | |
76 | - </li> | |
77 | - </ul> | |
78 | - </dd> | |
79 | - </dl> | |
80 | - </form> | |
81 | - <div class="btn-group flex-center"> | |
82 | - <button class="cancel">취소</button> | |
83 | - <button class="register">등록</button> | |
84 | - </div> | |
85 | - </div> | |
86 | - <CategorySelectModal v-if="isModalOpen" :selectedCtgries="selectedCtgries" @toggleModal="fnToggleModal" /> | |
87 | -</template> | |
88 | -<script> | |
89 | -import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; | |
90 | -// COMPONENT | |
91 | -import EditorComponent from '../../component/editor/EditorComponent.vue'; | |
92 | -import CategorySelectModal from '../../component/modal/CategorySelectModal.vue'; | |
93 | - | |
94 | -export default { | |
95 | - components: { | |
96 | - DoubleLeftOutlined, | |
97 | - LeftOutlined, | |
98 | - RightOutlined, | |
99 | - DoubleRightOutlined, | |
100 | - EditorComponent, CategorySelectModal, | |
101 | - }, | |
102 | - | |
103 | - data() { | |
104 | - return { | |
105 | - // Define the image sources | |
106 | - homeicon: 'client/resources/images/icon/home.png', | |
107 | - erroricon: 'client/resources/images/icon/error.png', | |
108 | - righticon: 'client/resources/images/icon/right.png', | |
109 | - fileicon: 'client/resources/images/icon/file.png', | |
110 | - searchicon: 'client/resources/images/icon/search.png', | |
111 | - | |
112 | - isModalOpen: false, | |
113 | - | |
114 | - items: [ | |
115 | - { id: 1, category: '카테고리 1', selected: false }, | |
116 | - { id: 2, category: '카테고리 2', selected: false }, | |
117 | - { id: 3, category: '카테고리 3', selected: false }, | |
118 | - ], | |
119 | - fileNames: [], | |
120 | - insertDTO: { | |
121 | - sj: null, //제목 | |
122 | - cn: null, //내용 | |
123 | - adres: null, // 주소 | |
124 | - prdctnYear: null, // 생산연도 | |
125 | - ty: 'P', // 타입 ( P: 사진, V: 영상 ) | |
126 | - multipartFiles: null, // 첨부파일 정보 | |
127 | - ctgryIds: null, // 카테고리 정보 | |
128 | - }, | |
129 | - | |
130 | - files: [], | |
131 | - selectedCtgries: [], // 카테고리 목록 | |
132 | - }; | |
133 | - }, | |
134 | - computed: { | |
135 | - filteredItems() { | |
136 | - // This could be modified to support filtering based on searchQuery | |
137 | - return this.items.filter(item => | |
138 | - item.category.includes(this.searchQuery) | |
139 | - ); | |
140 | - } | |
141 | - }, | |
142 | - created() { | |
143 | - }, | |
144 | - methods: { | |
145 | - registerCategories() { | |
146 | - // Add selected categories to the displayed list | |
147 | - this.selectedCtgries = this.items | |
148 | - .filter(item => item.selected) | |
149 | - .map(item => item.category); | |
150 | - this.closeModal(); // Close modal after registration | |
151 | - }, | |
152 | - removeCategory(index) { | |
153 | - // Remove category from the list | |
154 | - this.selectedCtgries.splice(index, 1); | |
155 | - }, | |
156 | - searchCategories() { | |
157 | - // You can implement search logic if needed | |
158 | - }, | |
159 | - nextPage() { | |
160 | - if (this.currentPage < this.totalPages) { | |
161 | - this.currentPage++; | |
162 | - } | |
163 | - }, | |
164 | - previousPage() { | |
165 | - if (this.currentPage > 1) { | |
166 | - this.currentPage--; | |
167 | - } | |
168 | - }, | |
169 | - showFileNames(event) { | |
170 | - const files = event.target.files; | |
171 | - this.fileNames = []; // Clear previous file names | |
172 | - | |
173 | - for (let i = 0; i < files.length; i++) { | |
174 | - const file = files[i]; | |
175 | - const fileType = file.name.split('.').pop().toLowerCase(); // Get file extension | |
176 | - | |
177 | - // Set default icon | |
178 | - let iconPath = this.fileicons; | |
179 | - | |
180 | - // Determine the icon based on file type | |
181 | - if (['jpg', 'jpeg', 'png', 'gif'].includes(fileType)) { | |
182 | - iconPath = 'client/resources/images/icon/imgicon.png'; // Example for image files | |
183 | - } else if (['pdf'].includes(fileType)) { | |
184 | - iconPath = 'client/resources/images/icon/pdficon.png'; // Example for PDF files | |
185 | - } else if (['xls'].includes(fileType)) { | |
186 | - iconPath = 'client/resources/images/icon/excelicon.png'; // Example for audio files | |
187 | - } else if (['hwp'].includes(fileType)) { | |
188 | - iconPath = 'client/resources/images/icon/hwpicon.png'; // Example for video files | |
189 | - } | |
190 | - | |
191 | - // Push the file name and corresponding icon to the fileNames array | |
192 | - this.fileNames.push({ | |
193 | - name: file.name, | |
194 | - icon: iconPath | |
195 | - }); | |
196 | - } | |
197 | - }, | |
198 | - removeFile(index) { | |
199 | - // Remove file from the list | |
200 | - this.fileNames.splice(index, 1); | |
201 | - console.log(removeFile) | |
202 | - }, | |
203 | - openModal() { | |
204 | - this.isModalOpen = true; | |
205 | - }, | |
206 | - // 모달 닫기 | |
207 | - closeModal() { | |
208 | - this.isModalOpen = false; | |
209 | - }, | |
210 | - } | |
211 | -}; | |
212 | -</script> |
--- client/views/pages/user/NewsReleaseSearch.vue
... | ... | @@ -1,414 +0,0 @@ |
1 | -<template> | |
2 | - <div class="content"> | |
3 | - <div class="sub-title-area mb-30"> | |
4 | - <h2>보도자료</h2> | |
5 | - <div class="breadcrumb-list"> | |
6 | - <ul> | |
7 | - <!-- Bind the image source dynamically for homeicon --> | |
8 | - <li><img :src="homeicon" alt="Home Icon"> | |
9 | - <p>언론에서 바라본 구미시</p> | |
10 | - </li> | |
11 | - <li><img :src="righticon" alt=""></li> | |
12 | - <li>보도자료</li> | |
13 | - </ul> | |
14 | - </div> | |
15 | - </div> | |
16 | - <div action="search" class="search-form form "> | |
17 | - <dl> | |
18 | - <dd class="mb-15"> | |
19 | - <p>검색범위</p> | |
20 | - <ul> | |
21 | - <li> | |
22 | - <input type="checkbox" id="allScope" v-model="isChkAllScope" | |
23 | - @change="fnChkAllOptions('scope')" /> | |
24 | - <label for="allScope">전체</label> | |
25 | - </li> | |
26 | - <li v-for="(scope, idx) in searchType" :key="idx"> | |
27 | - <input type="checkbox" :id="idx" :name="searchType" :value="scope.key" | |
28 | - v-model="searchReqDTO.searchType" @change="fnChkOption('scope')" /> | |
29 | - <label :for="idx">{{ scope.value }}</label> | |
30 | - </li> | |
31 | - </ul> | |
32 | - </dd> | |
33 | - <dd class="mb-15"> | |
34 | - <p>검색어</p> | |
35 | - <div class="wfull"><input type="text" v-model="searchReqDTO.searchText"></div> | |
36 | - </dd> | |
37 | - <dd class="mb-15"> | |
38 | - <p>생산연도</p> | |
39 | - <input type="date" v-model="searchReqDTO.startYear"> | |
40 | - <p class="mark">~</p> | |
41 | - <input type="date" v-model="searchReqDTO.endYear"> | |
42 | - </dd> | |
43 | - <dd class="mb-20 category-dd"> | |
44 | - <p>카테고리</p> | |
45 | - <ul> | |
46 | - <li v-for="(category, idx) of categorys" :key="idx"> | |
47 | - <input type="checkbox" :id="category.ctgryId" name="categorys" :value="category.ctgryId" | |
48 | - v-model="searchReqDTO.searchCtgry" /> | |
49 | - <label :for="category.ctgryId">{{ category.ctgryNm }}</label> | |
50 | - </li> | |
51 | - </ul> | |
52 | - </dd> | |
53 | - <dd class="mb-15"> | |
54 | - <p>정렬</p> | |
55 | - <ul> | |
56 | - <li v-for="(order, idx) of orders" :key="idx"> | |
57 | - <input type="radio" :id="order.key" name="orders" :value="order.key" | |
58 | - v-model="searchReqDTO.order" /> | |
59 | - <label :for="order.key">{{ order.value }}</label> | |
60 | - </li> | |
61 | - </ul> | |
62 | - </dd> | |
63 | - <div class="btn-group"> | |
64 | - <button class="reset"><img :src="reseticon" alt=""> | |
65 | - <p>초기화</p> | |
66 | - </button> | |
67 | - <button class="search"><img :src="searchicon" alt=""> | |
68 | - <p>검색</p> | |
69 | - </button> | |
70 | - </div> | |
71 | - | |
72 | - </dl> | |
73 | - | |
74 | - </div> | |
75 | - <div class="search-result"> | |
76 | - <div class="tabs"> | |
77 | - <div class="flex-sp-bw mb-20 align-center"> | |
78 | - <div class="resultext "> | |
79 | - <img :src="resulticon" alt=""> | |
80 | - <p>총 <b>{{ count }}개</b>의 보도자료가 검색되었습니다. </p> | |
81 | - </div> | |
82 | - <div class="flex "> | |
83 | - <ul class="tab-box mb-20"> | |
84 | - <li v-for="(tab, index) in tabs" :key="index" class="tab-title" | |
85 | - :class="{ active: selectedTab === tab.id }" @click="selectTab(tab.id)"> | |
86 | - <img :src="selectedTab === tab.id ? tab.activeImage : tab.inactiveImage" | |
87 | - :alt="tab.title" class="tab-icon" /> | |
88 | - <p><b>{{ tab.title }}</b></p> | |
89 | - </li> | |
90 | - </ul> | |
91 | - <div class="select-box"> | |
92 | - <select v-model="itemsPerPage" @change="changeItemsPerPage"> | |
93 | - <option :value="5" selected>5개</option> | |
94 | - <option :value="10">10개</option> | |
95 | - <option :value="15">15개</option> | |
96 | - </select> | |
97 | - </div> | |
98 | - </div> | |
99 | - | |
100 | - </div> | |
101 | - | |
102 | - <div class="tab-content"> | |
103 | - <!-- Loop through tabContents, and only display content that matches selectedTab --> | |
104 | - <div v-for="(tabContent, idx) in tabContents" :key="idx"> | |
105 | - <!-- Display content only if the tab's ID matches the selectedTab --> | |
106 | - <div v-show="tabContent.id === selectedTab"> | |
107 | - <!-- 카드형 Section (Card Layout) --> | |
108 | - <div v-if="tabContent.viewType === 'card'"> | |
109 | - <ul class="card-wrap"> | |
110 | - <li v-for="(resultitem, index) in paginatedItems" :key="index" class="mb-30"> | |
111 | - <div class="result-box"> | |
112 | - <!-- Main Image Section --> | |
113 | - <div class="main-img"> | |
114 | - <img :src="resultitem.img" alt="" class="tab-image" /> | |
115 | - </div> | |
116 | - <!-- Text Section --> | |
117 | - <div class="text-box"> | |
118 | - <router-link :to="{ path: '/NewsReleaseDetail.page' }"> | |
119 | - <h5>{{ resultitem.title }}</h5> | |
120 | - </router-link> | |
121 | - | |
122 | - <p class="address">{{ resultitem.address }}</p> | |
123 | - <p class="text">{{ resultitem.content }}</p> | |
124 | - | |
125 | - <div class="mb-20"> | |
126 | - <ul class="category"> | |
127 | - <li v-if="resultitem.category1" class="category1">카테고리1</li> | |
128 | - <li v-if="resultitem.category2" class="category2">카테고리2</li> | |
129 | - </ul> | |
130 | - </div> | |
131 | - | |
132 | - <div class="date"> | |
133 | - <ul> | |
134 | - <li>생산연도 <b>{{ resultitem.year }}</b></li> | |
135 | - <li>|</li> | |
136 | - <li>등록 <b>{{ resultitem.date }}</b></li> | |
137 | - </ul> | |
138 | - </div> | |
139 | - </div> | |
140 | - </div> | |
141 | - </li> | |
142 | - </ul> | |
143 | - | |
144 | - <!-- Empty State if no results in paginatedItems --> | |
145 | - <div v-if="paginatedItems.length === 0" class="no-results"> | |
146 | - <p>등록된 게시물이 없습니다.</p> | |
147 | - </div> | |
148 | - </div> | |
149 | - | |
150 | - <!-- 리스트형 Section (List Layout) --> | |
151 | - <div v-if="tabContent.viewType === 'list'"> | |
152 | - <ul class="list-wrap"> | |
153 | - <li v-for="(resultitem, index) in paginatedItems" :key="index" class="mb-30"> | |
154 | - <div class="text-box"> | |
155 | - <router-link :to="{ path: '/NewsReleaseDetail.page' }"> | |
156 | - <h5>{{ resultitem.title }}</h5> | |
157 | - </router-link> | |
158 | - <p class="address">{{ resultitem.address }}</p> | |
159 | - | |
160 | - <div class="flex-sp-bw"> | |
161 | - <div class="mb-20"> | |
162 | - <ul class="category"> | |
163 | - <li v-if="resultitem.category1" class="category1">카테고리1</li> | |
164 | - <li v-if="resultitem.category2" class="category2">카테고리2</li> | |
165 | - </ul> | |
166 | - </div> | |
167 | - | |
168 | - <div class="date "> | |
169 | - <ul> | |
170 | - <li>생산연도 <b>{{ resultitem.year }}</b></li> | |
171 | - <li>|</li> | |
172 | - <li>등록 <b>{{ resultitem.date }}</b></li> | |
173 | - </ul> | |
174 | - </div> | |
175 | - </div> | |
176 | - </div> | |
177 | - </li> | |
178 | - </ul> | |
179 | - | |
180 | - <!-- Empty State if no results in paginatedItems --> | |
181 | - <div v-if="paginatedItems.length === 0" class="no-results"> | |
182 | - <p>등록된 게시물이 없습니다.</p> | |
183 | - </div> | |
184 | - </div> | |
185 | - </div> | |
186 | - </div> | |
187 | - </div> | |
188 | - </div> | |
189 | - | |
190 | - <div class="btn-group flex-end mt-40"><button class="register"> <router-link | |
191 | - :to="{ path: '/PicHistoryInsert.page' }">등록</router-link></button></div> | |
192 | - <div class="pagination flex-center mt-40"> | |
193 | - | |
194 | - <!-- Previous and Next Page Buttons --> | |
195 | - <button> | |
196 | - <DoubleLeftOutlined /> | |
197 | - </button> | |
198 | - <button @click="previousPage" :disabled="currentPage === 1"> | |
199 | - <LeftOutlined /> | |
200 | - </button> | |
201 | - <button class="page-number clicked">1</button> | |
202 | - <button @click="nextPage" :disabled="currentPage === totalPages"> | |
203 | - <RightOutlined /> | |
204 | - </button> | |
205 | - <button> | |
206 | - <DoubleRightOutlined /> | |
207 | - </button> | |
208 | - </div> | |
209 | - </div> | |
210 | - </div> | |
211 | - | |
212 | -</template> | |
213 | -<script> | |
214 | -import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; | |
215 | -import { findAllCategoryProc } from "../../../resources/api/category"; // 카테고리 목록 검색 | |
216 | - | |
217 | -export default { | |
218 | - components: { | |
219 | - DoubleLeftOutlined, | |
220 | - LeftOutlined, | |
221 | - RightOutlined, | |
222 | - DoubleRightOutlined, | |
223 | - }, | |
224 | - data() { | |
225 | - return { | |
226 | - selectedTab: 1, | |
227 | - // 검색용 객체 | |
228 | - searchReqDTO: { | |
229 | - searchType: [], | |
230 | - searchText: null, | |
231 | - startYear: null, | |
232 | - endYear: null, | |
233 | - searchTy: null, | |
234 | - searchCtgry: [], | |
235 | - order: "rgsde", | |
236 | - }, | |
237 | - tabs: [ | |
238 | - | |
239 | - { | |
240 | - id: 1, | |
241 | - title: "카드형", | |
242 | - activeImage: "client/resources/images/list_icon01_on.png", // Active tab image | |
243 | - inactiveImage: "client/resources/images/list_icon01_off.png", | |
244 | - }, | |
245 | - { | |
246 | - id: 2, | |
247 | - title: "리스트형", | |
248 | - activeImage: "client/resources/images/list_icon02_on.png", // Active tab image | |
249 | - inactiveImage: "client/resources/images/list_icon02_off.png", | |
250 | - }, | |
251 | - ], | |
252 | - tabContents: [ | |
253 | - { id: 1, viewType: 'card', list: [{ sj: 'Item 1', rgsde: '2025-03-01', files: [{ filePath: 'image1.png' }] }] }, | |
254 | - { id: 2, viewType: 'list', list: [{ sj: 'Item 2', rgsde: '2025-03-02', files: [{ filePath: 'image2.png' }] }] }, | |
255 | - ], | |
256 | - paginatedItems: [], | |
257 | - resultitems: [ | |
258 | - { | |
259 | - img: 'client/resources/images/img6.png', | |
260 | - title: '보도자료 제목', | |
261 | - address: '경상북도 구미시 송정대로 55', | |
262 | - content: '대한민국 최대의 내륙 산업단지를 보유하고, 서울로부터 277km, 부산으로부터 167km 거리에 있으며, 면적은 615㎢로 경상북도 전체 면적의 3.2%에 달합니다. 인구는 41만 명이고, 선산읍, 고아읍, 산동읍을 비롯한 3읍, 5면, 17개 동으로 구성되어…', | |
263 | - category1: true, | |
264 | - category2: true, | |
265 | - year: 2020, | |
266 | - date: '2021-01-01' | |
267 | - }, | |
268 | - | |
269 | - ], | |
270 | - currentPage: 1, // Current page number | |
271 | - itemsPerPage: 5, | |
272 | - resulticon: "client/resources/images/icon/r-check.png", | |
273 | - homeicon: 'client/resources/images/icon/home.png', | |
274 | - searchicon: 'client/resources/images/icon/search.png', | |
275 | - reseticon: 'client/resources/images/icon/reset.png', | |
276 | - righticon: 'client/resources/images/icon/right.png', | |
277 | - count: 23, | |
278 | - checkOptions: [ | |
279 | - '전체', | |
280 | - '사진', | |
281 | - '영상', | |
282 | - '미디어 영상', | |
283 | - '보도자료', | |
284 | - ], | |
285 | - checkOptions2: [ | |
286 | - '전체', | |
287 | - '제목', | |
288 | - '내용', | |
289 | - '주소', | |
290 | - ], | |
291 | - checkOptions3: [ | |
292 | - '카테고리1', | |
293 | - '카테고리2', | |
294 | - '카테고리3', | |
295 | - '카테고리4', | |
296 | - '카테고리5', | |
297 | - ], | |
298 | - checkOptions4: [ | |
299 | - '최신', | |
300 | - '인기', | |
301 | - ], | |
302 | - isChkAllScope: false, // 검색범위 전체 체크 여부 | |
303 | - searchType: [ | |
304 | - { key: "sj", value: "제목" }, | |
305 | - { key: "cn", value: "내용" }, | |
306 | - { key: "adres", value: "주소" }, | |
307 | - ], // 검색범위 목록 | |
308 | - categorys: [], // 카테고리 목록 | |
309 | - orders: [ | |
310 | - { key: "rgsde", value: "최신" }, | |
311 | - { key: "rdcnt", value: "인기" }, | |
312 | - ], // 정렬 목록 | |
313 | - }; | |
314 | - }, | |
315 | - computed: { | |
316 | - // Total number of pages | |
317 | - totalPages() { | |
318 | - return Math.ceil(this.resultitems.length / this.itemsPerPage); | |
319 | - }, | |
320 | - | |
321 | - // Paginated items based on current page and items per page | |
322 | - paginatedItems() { | |
323 | - const start = (this.currentPage - 1) * this.itemsPerPage; | |
324 | - const end = start + this.itemsPerPage; | |
325 | - return this.resultitems.slice(start, end); | |
326 | - }, | |
327 | - }, | |
328 | - created() { | |
329 | - // 초기 데이터 세팅 | |
330 | - this.isChkAllScope = true; | |
331 | - this.searchReqDTO.searchType = this.searchType.map(item => item.key); | |
332 | - this.searchReqDTO.order = this.orders[0].key | |
333 | - | |
334 | - this.fnFindCategorys(); // 카테고리 목록 조회 (검색조건 없음) | |
335 | - }, | |
336 | - methods: { | |
337 | - selectTab(tabId) { | |
338 | - this.selectedTab = tabId; // Update the selected tab index | |
339 | - }, | |
340 | - // Change the number of items displayed per page | |
341 | - changeItemsPerPage() { | |
342 | - this.currentPage = 1; // Reset to first page when changing items per page | |
343 | - }, | |
344 | - previousPage() { | |
345 | - if (this.currentPage > 1) { | |
346 | - this.currentPage--; | |
347 | - } | |
348 | - }, | |
349 | - | |
350 | - // Go to the next page | |
351 | - nextPage() { | |
352 | - if (this.currentPage < this.totalPages) { | |
353 | - this.currentPage++; | |
354 | - } | |
355 | - }, | |
356 | - isChkAllScope: false, // 검색범위 전체 체크 여부 | |
357 | - searchType: [ | |
358 | - { key: "sj", value: "제목" }, | |
359 | - { key: "cn", value: "내용" }, | |
360 | - { key: "adres", value: "주소" }, | |
361 | - ], // 검색범위 목록 | |
362 | - categorys: [], // 카테고리 목록 | |
363 | - orders: [ | |
364 | - { key: "rgsde", value: "최신" }, | |
365 | - { key: "rdcnt", value: "인기" }, | |
366 | - ], // 정렬 목록 | |
367 | - | |
368 | - async fnFindCategorys() { | |
369 | - try { | |
370 | - const response = await findAllCategoryProc(); | |
371 | - this.categorys = response.data.data.ctgry; | |
372 | - } catch (error) { | |
373 | - if (error.response) { | |
374 | - console.log("에러 응답:", error.response.data); | |
375 | - } | |
376 | - console.error("Error:", error); | |
377 | - } | |
378 | - }, | |
379 | - | |
380 | - // 통합검색 | |
381 | - async fnFindAllDatas() { | |
382 | - try { | |
383 | - let params = {}; | |
384 | - if (this.searchReqDTO.searchRecord.length > 0) { | |
385 | - params.searchRecords = this.searchReqDTO.searchRecord.join(','); | |
386 | - } | |
387 | - if (this.searchReqDTO.searchType.length > 0) { | |
388 | - params.searchTypes = this.searchReqDTO.searchType.join(','); | |
389 | - } | |
390 | - if (this.searchReqDTO.searchCtgry.length > 0) { | |
391 | - params.searchCtgries = this.searchReqDTO.searchCtgry.join(','); | |
392 | - } | |
393 | - params.searchText = this.searchReqDTO.searchText; | |
394 | - params.startYear = this.searchReqDTO.startYear; | |
395 | - params.endYear = this.searchReqDTO.endYear; | |
396 | - params.order = this.searchReqDTO.order; | |
397 | - | |
398 | - // API 호출 | |
399 | - const response = await findAllDatas(params); | |
400 | - this.searchResult = response.data.data.searchResult; | |
401 | - } catch (error) { | |
402 | - if (error.response) { | |
403 | - console.log("에러 응답:", error.response.data); | |
404 | - } | |
405 | - console.error("Error:", error); | |
406 | - } | |
407 | - }, | |
408 | - }, | |
409 | - | |
410 | - | |
411 | - | |
412 | -}; | |
413 | -</script> | |
414 | -<style scoped></style>(파일 끝에 줄바꿈 문자 없음) |
--- server/modules/web/server.js
+++ server/modules/web/server.js
... | ... | @@ -142,7 +142,7 @@ |
142 | 142 |
*/ |
143 | 143 |
webServer.use(function (error, request, response, next) { |
144 | 144 |
const errorCode = !error.statusCode ? 500 : error.statusCode; |
145 |
- response.redirect('/notFound.page'); // 에러 페이지로 유도 |
|
145 |
+ response.redirect('/'); // 메인 페이지로 유도 |
|
146 | 146 |
let message = `[Error: ${errorCode}]${request.url} / n ${error.stack} / n`; |
147 | 147 |
Logger.logging(message); |
148 | 148 |
});(파일 끝에 줄바꿈 문자 없음) |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?