press.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. <template>
  2. <main>
  3. <TopVisual :className="className" :title="title" :navigation="navigation" />
  4. <section class="press--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="sns--wrap">
  13. <h3>SNS</h3>
  14. <div class="sns--list">
  15. <swiper
  16. :slides-per-view="4"
  17. :space-between="20"
  18. :loop="true"
  19. :autoplay="{
  20. delay: 3000,
  21. disableOnInteraction: false,
  22. }"
  23. :navigation="false"
  24. :modules="[Autoplay]"
  25. >
  26. <swiper-slide v-for="sns in snsData" :key="sns.id">
  27. <a class="sns" :href="sns.link" target="_blank" rel="noopener noreferrer">
  28. <div
  29. class="img--wrap"
  30. :style="`background-image: url('${sns.image}')`"
  31. ></div>
  32. <div class="txt--wrap">
  33. <h4>{{ sns.title }}</h4>
  34. <span>바로가기<i class="ico"></i></span>
  35. </div>
  36. </a>
  37. </swiper-slide>
  38. </swiper>
  39. </div>
  40. </div>
  41. <div class="news--wrap">
  42. <h3>기사</h3>
  43. <div class="news--list">
  44. <a
  45. v-for="news in paginatedNews"
  46. :key="news.id"
  47. :href="news.link"
  48. target="_blank"
  49. rel="noopener noreferrer"
  50. class="news"
  51. >
  52. <div class="news--title--wrap">
  53. <h4>{{ news.title }}</h4>
  54. <span>{{ news.date }}</span>
  55. </div>
  56. <div class="news--thumb--wrap">
  57. <img :src="news.image" alt="" />
  58. </div>
  59. </a>
  60. </div>
  61. <div class="pagination--wrap">
  62. <UButton
  63. @click="prevPage"
  64. class="prev--btn"
  65. :disabled="currentPage === 1"
  66. ></UButton>
  67. <div class="numbs">
  68. <UButton
  69. v-for="page in totalPages"
  70. :key="page"
  71. @click="goToPage(page)"
  72. :class="{ active: currentPage === page }"
  73. >
  74. {{ page }}
  75. </UButton>
  76. </div>
  77. <UButton
  78. @click="nextPage"
  79. class="next--btn"
  80. :disabled="currentPage === totalPages"
  81. ></UButton>
  82. </div>
  83. </div>
  84. </div>
  85. </section>
  86. </main>
  87. </template>
  88. <script setup>
  89. import { ref, computed, onMounted } from "vue";
  90. import { Swiper, SwiperSlide } from "swiper/vue";
  91. import { Autoplay } from "swiper/modules";
  92. import "swiper/css";
  93. import TopVisual from "~/components/topVisual.vue";
  94. const className = ref("media");
  95. const title = ref("Media");
  96. const navigation = ref([
  97. {
  98. name: "Media",
  99. link: "/media/press",
  100. gnbList: [
  101. { name: "Company", link: "/company/intro" },
  102. { name: "Product", link: "/products/materials" },
  103. { name: "Technology", link: "/technology/facilities" },
  104. { name: "Media", link: "/media/news" },
  105. { name: "Contact", link: "/contact/notice" },
  106. ],
  107. },
  108. {
  109. name: "보도자료",
  110. link: "/media/press",
  111. gnbList: [
  112. { name: "환경뉴스", link: "/media/news" },
  113. { name: "보도자료", link: "/media/press" },
  114. ],
  115. },
  116. ]);
  117. const loading = ref(true);
  118. const allNewsData = ref([]); // 전체 데이터를 누적하여 저장
  119. const newsData = ref([]);
  120. const snsData = ref([]);
  121. const totalCount = ref(0);
  122. const loadedPages = ref(new Set()); // 이미 로드한 백엔드 페이지 추적
  123. // API에서 보도자료 데이터 가져오기
  124. const fetchPressList = async (page = 1, reset = false) => {
  125. try {
  126. // 이미 로드한 페이지는 스킵
  127. if (!reset && loadedPages.value.has(page)) {
  128. console.log(`페이지 ${page}는 이미 로드됨`);
  129. return;
  130. }
  131. loading.value = true;
  132. // 공지사항과 동일한 방식으로 호출
  133. const response = await $postForm(`/board_list/media`, {
  134. boardId: "media",
  135. page: page,
  136. searchKind: "",
  137. searchKeyword: "",
  138. });
  139. // 백엔드가 JSON으로 응답하는지 확인
  140. if (response && typeof response === "object") {
  141. // JSON 응답인 경우
  142. if (response.success && response.list) {
  143. // 전체 개수와 현재 페이지를 기준으로 번호 계산
  144. totalCount.value = response.totalCount || 0;
  145. const currentPageNum = page || 1;
  146. const pageSize = 20; // 백엔드의 페이지 사이즈와 동일
  147. console.log("보도자료(media) API 응답:", {
  148. totalCount: totalCount.value,
  149. listCount: response.list.length,
  150. currentPage: currentPageNum,
  151. pageSize,
  152. backendPage: page,
  153. });
  154. const newData = response.list.map((item, index) => {
  155. // 번호 = 전체개수 - ((현재페이지-1) * 페이지크기 + 인덱스)
  156. const displayNumber =
  157. totalCount.value - ((currentPageNum - 1) * pageSize + index);
  158. return {
  159. id: displayNumber, // 순차적인 번호로 표시
  160. title: item.title,
  161. date: item.regdate,
  162. image: item.main_file1
  163. ? `/backend${item.main_file1}`
  164. : "/img/img--cycle--center.png", // 기본 이미지
  165. link: item.etc1 || "#", // etc1 필드에 외부 링크 저장
  166. };
  167. });
  168. if (reset) {
  169. // 리셋 모드: 전체 데이터 초기화
  170. allNewsData.value = newData;
  171. loadedPages.value = new Set([page]);
  172. } else {
  173. // 누적 모드: 새 데이터를 적절한 위치에 삽입
  174. const startIndex = (page - 1) * pageSize;
  175. allNewsData.value.splice(startIndex, pageSize, ...newData);
  176. loadedPages.value.add(page);
  177. }
  178. // newsData를 allNewsData로 업데이트
  179. newsData.value = [...allNewsData.value];
  180. } else {
  181. console.error("JSON 응답 형식이 올바르지 않습니다:", response);
  182. // 에러시 기본 더미 데이터 사용
  183. }
  184. } else if (typeof response === "string") {
  185. // HTML 응답인 경우 (백엔드에서 AJAX 감지 실패시)
  186. console.warn("HTML 응답을 받았습니다. AJAX 감지가 실패했을 수 있습니다.");
  187. console.log("HTML 내용:", response.substring(0, 200) + "...");
  188. // 기본 더미 데이터 사용
  189. } else {
  190. console.error("예상하지 못한 응답 형식:", typeof response, response);
  191. // 기본 더미 데이터 사용
  192. }
  193. } catch (error) {
  194. console.error("보도자료 데이터 로드 실패:", error);
  195. // 에러시 기본 더미 데이터 사용
  196. } finally {
  197. loading.value = false;
  198. }
  199. };
  200. // API에서 SNS 데이터 가져오기
  201. const fetchSnsList = async () => {
  202. try {
  203. const response = await $postForm(`/board_list/media_sns`, {
  204. boardId: "media_sns",
  205. page: 1,
  206. searchKind: "",
  207. searchKeyword: "",
  208. });
  209. if (response && typeof response === "object") {
  210. if (response.success && response.list) {
  211. //console.log("SNS API 응답:", response.list);
  212. snsData.value = response.list.map((item) => ({
  213. id: item.board_idx,
  214. title: item.title,
  215. image: item.main_file1
  216. ? `http://green.interscope.co.kr/backend${item.main_file1}`
  217. : "/img/img--sns1.png",
  218. link: item.etc1 || "#", // etc1 필드에 외부 링크 저장
  219. }));
  220. } else {
  221. console.error("SNS JSON 응답 형식이 올바르지 않습니다:", response);
  222. }
  223. } else {
  224. console.error("SNS 예상하지 못한 응답 형식:", typeof response, response);
  225. }
  226. } catch (error) {
  227. console.error("SNS 데이터 로드 실패:", error);
  228. }
  229. };
  230. // 페이지네이션 로직
  231. const currentPage = ref(1);
  232. const itemsPerPage = 10; // 프론트엔드에서 10개씩 표시
  233. const backendPageSize = 20; // 백엔드는 20개씩 가져옴
  234. const totalPages = computed(() => Math.ceil(totalCount.value / itemsPerPage));
  235. const paginatedNews = computed(() => {
  236. const start = (currentPage.value - 1) * itemsPerPage;
  237. const end = start + itemsPerPage;
  238. return newsData.value.slice(start, end);
  239. });
  240. // 백엔드에서 필요한 데이터가 있는지 확인하고 필요시 API 호출
  241. const needToFetchData = (targetPage) => {
  242. const startIndex = (targetPage - 1) * itemsPerPage;
  243. const endIndex = targetPage * itemsPerPage;
  244. // 필요한 데이터의 백엔드 페이지들 계산
  245. const startBackendPage = Math.floor(startIndex / backendPageSize) + 1;
  246. const endBackendPage = Math.ceil(endIndex / backendPageSize);
  247. // 아직 로드되지 않은 페이지가 있는지 확인
  248. for (let i = startBackendPage; i <= endBackendPage; i++) {
  249. if (!loadedPages.value.has(i)) {
  250. return i; // 로드가 필요한 백엔드 페이지 번호 반환
  251. }
  252. }
  253. return null; // 모든 필요한 데이터가 이미 로드됨
  254. };
  255. const goToPage = async (page) => {
  256. if (page >= 1 && page <= totalPages.value) {
  257. currentPage.value = page;
  258. const backendPageToLoad = needToFetchData(page);
  259. if (backendPageToLoad !== null) {
  260. console.log(`프론트엔드 페이지 ${page}를 위해 백엔드 페이지 ${backendPageToLoad} 로드`);
  261. await fetchPressList(backendPageToLoad);
  262. }
  263. }
  264. };
  265. const nextPage = async () => {
  266. if (currentPage.value < totalPages.value) {
  267. await goToPage(currentPage.value + 1);
  268. }
  269. };
  270. const prevPage = async () => {
  271. if (currentPage.value > 1) {
  272. await goToPage(currentPage.value - 1);
  273. }
  274. };
  275. // 컴포넌트 마운트 시 데이터 로드
  276. onMounted(() => {
  277. fetchPressList(1, true); // reset=true로 초기 로드
  278. fetchSnsList();
  279. });
  280. </script>