notice.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. <template>
  2. <main>
  3. <TopVisual :className="className" :title="title" :navigation="navigation" />
  4. <section class="notice--section">
  5. <div class="sub--container type2">
  6. <div class="title--wrap">
  7. <h2 class="title">공지사항</h2>
  8. </div>
  9. <div class="search--wrap">
  10. <USelect v-model="searchValue" :items="searchItems" />
  11. <UInput
  12. v-model="searchKeyword"
  13. placeholder="검색어를 입력해주세요."
  14. @keyup.enter="performSearch"
  15. />
  16. <UButton @click="performSearch" class="search--btn"></UButton>
  17. </div>
  18. <div class="notice--wrap">
  19. <div class="notice--list">
  20. <NuxtLink
  21. v-for="news in paginatedNews"
  22. :key="news.id"
  23. :to="news.link"
  24. class="notice"
  25. >
  26. <span class="news--index">{{ news.id }}</span>
  27. <h4>{{ news.title }}</h4>
  28. <span class="news--date">{{ news.date }}</span>
  29. </NuxtLink>
  30. </div>
  31. <div class="pagination--wrap">
  32. <UButton
  33. @click="prevPage"
  34. class="prev--btn"
  35. :disabled="currentPage === 1"
  36. ></UButton>
  37. <div class="numbs">
  38. <UButton
  39. v-for="page in totalPages"
  40. :key="page"
  41. @click="goToPage(page)"
  42. :class="{ active: currentPage === page }"
  43. >
  44. {{ page }}
  45. </UButton>
  46. </div>
  47. <UButton
  48. @click="nextPage"
  49. class="next--btn"
  50. :disabled="currentPage === totalPages"
  51. ></UButton>
  52. </div>
  53. </div>
  54. </div>
  55. </section>
  56. </main>
  57. </template>
  58. <script setup>
  59. import { ref, computed, onMounted } from "vue";
  60. import TopVisual from "~/components/topVisual.vue";
  61. const searchItems = ref(["제목", "내용", "제목+내용"]);
  62. const searchValue = ref("제목");
  63. const searchKeyword = ref("");
  64. const totalCount = ref(0);
  65. const loading = ref(true);
  66. const allNewsData = ref([]); // 전체 데이터를 누적하여 저장
  67. const loadedPages = ref(new Set()); // 이미 로드한 백엔드 페이지 추적
  68. const className = ref("contact");
  69. const title = ref("Contact");
  70. const navigation = ref([
  71. {
  72. name: "Contact",
  73. link: "/contact/notice",
  74. gnbList: [
  75. { name: "Company", link: "/company/intro" },
  76. { name: "Product", link: "/products/materials" },
  77. { name: "Technology", link: "/technology/facilities" },
  78. { name: "Media", link: "/media/news" },
  79. { name: "Contact", link: "/contact/notice" },
  80. ],
  81. },
  82. {
  83. name: "공지사항",
  84. link: "/contact/notice",
  85. gnbList: [
  86. { name: "공지사항", link: "/contact/notice" },
  87. { name: "FAQ", link: "/contact/faq" },
  88. { name: "고객센터", link: "/contact/support" },
  89. { name: "오시는길", link: "/contact/location" },
  90. ],
  91. },
  92. ]);
  93. // 뉴스 데이터 배열 - API에서 받아올 것
  94. const newsData = ref([]);
  95. // 검색 실행 함수
  96. const performSearch = async () => {
  97. currentPage.value = 1; // 검색시 첫 페이지로 이동
  98. await fetchNoticeList(1, true); // reset=true로 검색
  99. };
  100. // 검색 초기화 함수
  101. const resetSearch = async () => {
  102. searchKeyword.value = "";
  103. searchValue.value = "제목";
  104. currentPage.value = 1;
  105. await fetchNoticeList(1, true); // reset=true로 초기화
  106. };
  107. // API에서 공지사항 데이터 가져오기
  108. const fetchNoticeList = async (page = 1, reset = false) => {
  109. try {
  110. // 검색이 변경되면 항상 리셋
  111. if (searchKeyword.value || searchValue.value !== "제목") {
  112. reset = true;
  113. }
  114. // 이미 로드한 페이지는 스킵
  115. if (!reset && loadedPages.value.has(page)) {
  116. console.log(`페이지 ${page}는 이미 로드됨`);
  117. return;
  118. }
  119. loading.value = true;
  120. // 검색 종류 매핑
  121. const getSearchKind = (searchType) => {
  122. switch (searchType) {
  123. case "제목": return "title";
  124. case "내용": return "contents";
  125. case "제목+내용": return "title_contents";
  126. default: return "title";
  127. }
  128. };
  129. // CodeIgniter 방식으로 호출
  130. const response = await $postForm(`/board_list/notice`, {
  131. page: page,
  132. searchKind: getSearchKind(searchValue.value),
  133. searchKeyword: searchKeyword.value || "",
  134. });
  135. // 백엔드가 JSON으로 응답하는지 확인
  136. if (response && typeof response === "object") {
  137. // JSON 응답인 경우
  138. if (response.success && response.list) {
  139. // 전체 개수와 현재 페이지를 기준으로 번호 계산
  140. totalCount.value = response.totalCount || 0;
  141. const currentPageNum = page || 1;
  142. const pageSize = 20; // 백엔드의 페이지 사이즈와 동일
  143. console.log("공지사항 API 응답:", {
  144. totalCount: totalCount.value,
  145. listCount: response.list.length,
  146. currentPage: currentPageNum,
  147. pageSize,
  148. backendPage: page,
  149. });
  150. const newData = response.list.map((item, index) => {
  151. // 번호 = 전체개수 - ((현재페이지-1) * 페이지크기 + 인덱스)
  152. const displayNumber = totalCount.value - ((currentPageNum - 1) * pageSize + index);
  153. return {
  154. id: displayNumber, // 순차적인 번호로 표시
  155. title: item.title,
  156. date: item.regdate,
  157. link: `/contact/noticeView?idx=${item.board_idx}`, // 실제 링크는 board_idx 사용
  158. };
  159. });
  160. if (reset) {
  161. // 리셋 모드: 전체 데이터 초기화
  162. allNewsData.value = newData;
  163. loadedPages.value = new Set([page]);
  164. } else {
  165. // 누적 모드: 새 데이터를 적절한 위치에 삽입
  166. const startIndex = (page - 1) * pageSize;
  167. allNewsData.value.splice(startIndex, pageSize, ...newData);
  168. loadedPages.value.add(page);
  169. }
  170. // newsData를 allNewsData로 업데이트
  171. newsData.value = [...allNewsData.value];
  172. } else {
  173. console.error("JSON 응답 형식이 올바르지 않습니다:", response);
  174. newsData.value = [];
  175. }
  176. } else if (typeof response === "string") {
  177. // HTML 응답인 경우 (백엔드에서 AJAX 감지 실패시)
  178. console.warn("HTML 응답을 받았습니다. AJAX 감지가 실패했을 수 있습니다.");
  179. console.log("HTML 내용:", response.substring(0, 200) + "...");
  180. newsData.value = [];
  181. } else {
  182. console.error("예상하지 못한 응답 형식:", typeof response, response);
  183. newsData.value = [];
  184. }
  185. } catch (error) {
  186. console.error("공지사항 데이터 로드 실패:", error);
  187. // 에러시 기본 더미 데이터 사용
  188. } finally {
  189. loading.value = false;
  190. }
  191. };
  192. // 페이지네이션 로직
  193. const currentPage = ref(1);
  194. const itemsPerPage = 10;
  195. const backendPageSize = 20;
  196. const totalPages = computed(() => Math.ceil(totalCount.value / itemsPerPage));
  197. const paginatedNews = computed(() => {
  198. const start = (currentPage.value - 1) * itemsPerPage;
  199. const end = start + itemsPerPage;
  200. return newsData.value.slice(start, end);
  201. });
  202. // 백엔드에서 필요한 데이터가 있는지 확인하고 필요시 API 호출
  203. const needToFetchData = (targetPage) => {
  204. const startIndex = (targetPage - 1) * itemsPerPage;
  205. const endIndex = targetPage * itemsPerPage;
  206. // 필요한 데이터의 백엔드 페이지들 계산
  207. const startBackendPage = Math.floor(startIndex / backendPageSize) + 1;
  208. const endBackendPage = Math.ceil(endIndex / backendPageSize);
  209. // 아직 로드되지 않은 페이지가 있는지 확인
  210. for (let i = startBackendPage; i <= endBackendPage; i++) {
  211. if (!loadedPages.value.has(i)) {
  212. return i; // 로드가 필요한 백엔드 페이지 번호 반환
  213. }
  214. }
  215. return null; // 모든 필요한 데이터가 이미 로드됨
  216. };
  217. const goToPage = async (page) => {
  218. if (page >= 1 && page <= totalPages.value) {
  219. currentPage.value = page;
  220. const backendPageToLoad = needToFetchData(page);
  221. if (backendPageToLoad !== null) {
  222. console.log(`프론트엔드 페이지 ${page}를 위해 백엔드 페이지 ${backendPageToLoad} 로드`);
  223. await fetchNoticeList(backendPageToLoad);
  224. }
  225. }
  226. };
  227. const nextPage = async () => {
  228. if (currentPage.value < totalPages.value) {
  229. await goToPage(currentPage.value + 1);
  230. }
  231. };
  232. const prevPage = async () => {
  233. if (currentPage.value > 1) {
  234. await goToPage(currentPage.value - 1);
  235. }
  236. };
  237. // 컴포넌트 마운트 시 데이터 로드
  238. onMounted(() => {
  239. fetchNoticeList(1, true); // reset=true로 초기 로드
  240. });
  241. </script>