news.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. <template>
  2. <main>
  3. <TopVisual :className="className" :title="title" :navigation="navigation" />
  4. <section class="news--section">
  5. <div class="sub--container type2">
  6. <div class="title--wrap">
  7. <h2 class="title">
  8. 친환경 플라스틱과 관련한 <br />
  9. <strong>새로운 소식들을 전해드립니다.</strong>
  10. </h2>
  11. </div>
  12. <div class="news--wrap">
  13. <div class="news--list">
  14. <a
  15. v-for="news in paginatedNews"
  16. :key="news.id"
  17. :href="news.link"
  18. target="_blank"
  19. rel="noopener noreferrer"
  20. class="news"
  21. >
  22. <div class="news--title--wrap">
  23. <h4>{{ news.title }}</h4>
  24. <span>{{ news.date }}</span>
  25. </div>
  26. <div class="news--thumb--wrap">
  27. <img :src="news.image" alt="" />
  28. </div>
  29. </a>
  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 className = ref("media");
  62. const title = ref("Media");
  63. const navigation = ref([
  64. {
  65. name: "Media",
  66. link: "/media/news",
  67. gnbList: [
  68. { name: "Company", link: "/company/intro" },
  69. { name: "Product", link: "/products/materials" },
  70. { name: "Technology", link: "/technology/facilities" },
  71. { name: "Media", link: "/media/news" },
  72. { name: "Contact", link: "/contact/notice" },
  73. ],
  74. },
  75. {
  76. name: "환경뉴스",
  77. link: "/media/news",
  78. gnbList: [
  79. { name: "환경뉴스", link: "/media/news" },
  80. { name: "보도자료", link: "/media/press" },
  81. ],
  82. },
  83. ]);
  84. const loading = ref(true);
  85. const allNewsData = ref([]); // 전체 데이터를 누적하여 저장
  86. const newsData = ref([]);
  87. const totalCount = ref(0);
  88. const loadedPages = ref(new Set()); // 이미 로드한 백엔드 페이지 추적
  89. // API에서 뉴스 데이터 가져오기
  90. const fetchNewsList = async (page = 1, reset = false) => {
  91. try {
  92. // 이미 로드한 페이지는 스킵
  93. if (!reset && loadedPages.value.has(page)) {
  94. console.log(`페이지 ${page}는 이미 로드됨`);
  95. return;
  96. }
  97. loading.value = true;
  98. // 공지사항과 동일한 방식으로 호출
  99. const response = await $postForm(`/board_list/media`, {
  100. boardId: "news",
  101. page: page,
  102. searchKind: "",
  103. searchKeyword: "",
  104. });
  105. // 백엔드가 JSON으로 응답하는지 확인
  106. if (response && typeof response === "object") {
  107. // JSON 응답인 경우
  108. if (response.success && response.list) {
  109. // 전체 개수와 현재 페이지를 기준으로 번호 계산
  110. totalCount.value = response.totalCount || 0;
  111. const currentPageNum = page || 1;
  112. const pageSize = 20; // 백엔드의 페이지 사이즈와 동일
  113. console.log("뉴스 API 응답:", {
  114. totalCount: totalCount.value,
  115. listCount: response.list.length,
  116. currentPage: currentPageNum,
  117. pageSize,
  118. backendPage: page,
  119. });
  120. const newData = response.list.map((item, index) => {
  121. // 번호 = 전체개수 - ((현재페이지-1) * 페이지크기 + 인덱스)
  122. const displayNumber =
  123. totalCount.value - ((currentPageNum - 1) * pageSize + index);
  124. return {
  125. id: displayNumber, // 순차적인 번호로 표시
  126. title: item.title,
  127. date: item.regdate,
  128. image: item.main_file1
  129. ? `https://www.greenwhaleglobal.com/backend${item.main_file1}`
  130. : "/img/img--cycle--center.png", // 기본 이미지
  131. link: item.etc1 || "#", // etc1 필드에 외부 링크 저장
  132. };
  133. });
  134. if (reset) {
  135. // 리셋 모드: 전체 데이터 초기화
  136. allNewsData.value = newData;
  137. loadedPages.value = new Set([page]);
  138. } else {
  139. // 누적 모드: 새 데이터를 적절한 위치에 삽입
  140. const startIndex = (page - 1) * pageSize;
  141. allNewsData.value.splice(startIndex, pageSize, ...newData);
  142. loadedPages.value.add(page);
  143. }
  144. // newsData를 allNewsData로 업데이트
  145. newsData.value = [...allNewsData.value];
  146. } else {
  147. console.error("JSON 응답 형식이 올바르지 않습니다:", response);
  148. // 에러시 기본 더미 데이터 사용
  149. }
  150. } else if (typeof response === "string") {
  151. // HTML 응답인 경우 (백엔드에서 AJAX 감지 실패시)
  152. console.warn("HTML 응답을 받았습니다. AJAX 감지가 실패했을 수 있습니다.");
  153. console.log("HTML 내용:", response.substring(0, 200) + "...");
  154. // 기본 더미 데이터 사용
  155. } else {
  156. console.error("예상하지 못한 응답 형식:", typeof response, response);
  157. // 기본 더미 데이터 사용
  158. }
  159. } catch (error) {
  160. console.error("뉴스 데이터 로드 실패:", error);
  161. // 에러시 기본 더미 데이터 사용
  162. } finally {
  163. loading.value = false;
  164. }
  165. };
  166. // 페이지네이션 로직
  167. const currentPage = ref(1);
  168. const itemsPerPage = 10; // 프론트엔드에서 10개씩 표시
  169. const backendPageSize = 20; // 백엔드는 20개씩 가져옴
  170. const totalPages = computed(() => Math.ceil(totalCount.value / itemsPerPage));
  171. const paginatedNews = computed(() => {
  172. const start = (currentPage.value - 1) * itemsPerPage;
  173. const end = start + itemsPerPage;
  174. return newsData.value.slice(start, end);
  175. });
  176. // 백엔드에서 필요한 데이터가 있는지 확인하고 필요시 API 호출
  177. const needToFetchData = (targetPage) => {
  178. const startIndex = (targetPage - 1) * itemsPerPage;
  179. const endIndex = targetPage * itemsPerPage;
  180. // 필요한 데이터의 백엔드 페이지들 계산
  181. const startBackendPage = Math.floor(startIndex / backendPageSize) + 1;
  182. const endBackendPage = Math.ceil(endIndex / backendPageSize);
  183. // 아직 로드되지 않은 페이지가 있는지 확인
  184. for (let i = startBackendPage; i <= endBackendPage; i++) {
  185. if (!loadedPages.value.has(i)) {
  186. return i; // 로드가 필요한 백엔드 페이지 번호 반환
  187. }
  188. }
  189. return null; // 모든 필요한 데이터가 이미 로드됨
  190. };
  191. const goToPage = async (page) => {
  192. if (page >= 1 && page <= totalPages.value) {
  193. currentPage.value = page;
  194. const backendPageToLoad = needToFetchData(page);
  195. if (backendPageToLoad !== null) {
  196. console.log(
  197. `프론트엔드 페이지 ${page}를 위해 백엔드 페이지 ${backendPageToLoad} 로드`
  198. );
  199. await fetchNewsList(backendPageToLoad);
  200. }
  201. }
  202. };
  203. const nextPage = async () => {
  204. if (currentPage.value < totalPages.value) {
  205. await goToPage(currentPage.value + 1);
  206. }
  207. };
  208. const prevPage = async () => {
  209. if (currentPage.value > 1) {
  210. await goToPage(currentPage.value - 1);
  211. }
  212. };
  213. // 컴포넌트 마운트 시 데이터 로드
  214. onMounted(() => {
  215. fetchNewsList(1, true); // reset=true로 초기 로드
  216. });
  217. </script>