press.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  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 newsData = ref([]);
  119. const snsData = ref([]);
  120. const totalCount = ref(0);
  121. // API에서 보도자료 데이터 가져오기
  122. const fetchPressList = async (page = 1) => {
  123. try {
  124. loading.value = true;
  125. // 공지사항과 동일한 방식으로 호출
  126. const response = await $postForm(`/board_list/media`, {
  127. boardId: "media",
  128. page: page,
  129. searchKind: "",
  130. searchKeyword: "",
  131. });
  132. // 백엔드가 JSON으로 응답하는지 확인
  133. if (response && typeof response === "object") {
  134. // JSON 응답인 경우
  135. if (response.success && response.list) {
  136. // 전체 개수와 현재 페이지를 기준으로 번호 계산
  137. totalCount.value = response.totalCount || 0;
  138. const currentPageNum = page || 1;
  139. const pageSize = 20; // 백엔드의 페이지 사이즈와 동일
  140. // console.log("보도자료(media) API 응답:", {
  141. // totalCount: totalCount.value,
  142. // listCount: response.list.length,
  143. // currentPage: currentPageNum,
  144. // pageSize,
  145. // });
  146. newsData.value = response.list.map((item, index) => {
  147. // 번호 = 전체개수 - ((현재페이지-1) * 페이지크기 + 인덱스)
  148. const displayNumber =
  149. totalCount.value - ((currentPageNum - 1) * pageSize + index);
  150. return {
  151. id: displayNumber, // 순차적인 번호로 표시
  152. title: item.title,
  153. date: item.regdate,
  154. image: item.main_file1
  155. ? `/backend${item.main_file1}`
  156. : "/img/img--cycle--center.png", // 기본 이미지
  157. link: item.etc1 || "#", // etc1 필드에 외부 링크 저장
  158. };
  159. });
  160. } else {
  161. console.error("JSON 응답 형식이 올바르지 않습니다:", response);
  162. // 에러시 기본 더미 데이터 사용
  163. setDefaultPressData();
  164. }
  165. } else if (typeof response === "string") {
  166. // HTML 응답인 경우 (백엔드에서 AJAX 감지 실패시)
  167. console.warn("HTML 응답을 받았습니다. AJAX 감지가 실패했을 수 있습니다.");
  168. console.log("HTML 내용:", response.substring(0, 200) + "...");
  169. // 기본 더미 데이터 사용
  170. setDefaultPressData();
  171. } else {
  172. console.error("예상하지 못한 응답 형식:", typeof response, response);
  173. // 기본 더미 데이터 사용
  174. setDefaultPressData();
  175. }
  176. } catch (error) {
  177. console.error("보도자료 데이터 로드 실패:", error);
  178. // 에러시 기본 더미 데이터 사용
  179. setDefaultPressData();
  180. } finally {
  181. loading.value = false;
  182. }
  183. };
  184. // API에서 SNS 데이터 가져오기
  185. const fetchSnsList = async () => {
  186. try {
  187. const response = await $postForm(`/board_list/media_sns`, {
  188. boardId: "media_sns",
  189. page: 1,
  190. searchKind: "",
  191. searchKeyword: "",
  192. });
  193. if (response && typeof response === "object") {
  194. if (response.success && response.list) {
  195. //console.log("SNS API 응답:", response.list);
  196. snsData.value = response.list.map((item) => ({
  197. id: item.board_idx,
  198. title: item.title,
  199. image: item.main_file1
  200. ? `http://green.interscope.co.kr/backend${item.main_file1}`
  201. : "/img/img--sns1.png",
  202. link: item.etc1 || "#", // etc1 필드에 외부 링크 저장
  203. }));
  204. } else {
  205. console.error("SNS JSON 응답 형식이 올바르지 않습니다:", response);
  206. setDefaultSnsData();
  207. }
  208. } else {
  209. console.error("SNS 예상하지 못한 응답 형식:", typeof response, response);
  210. setDefaultSnsData();
  211. }
  212. } catch (error) {
  213. console.error("SNS 데이터 로드 실패:", error);
  214. setDefaultSnsData();
  215. }
  216. };
  217. // 기본 SNS 더미 데이터 설정
  218. const setDefaultSnsData = () => {
  219. snsData.value = [
  220. {
  221. id: 1,
  222. title: "그린웨일글로벌, 녹색인프라 수출펀드 첫 투자기업 선정. 그린웨일글로벌",
  223. image: "/img/img--sns1.png",
  224. link: "#",
  225. },
  226. {
  227. id: 2,
  228. title: "그린웨일글로벌, 녹색인프라 수출펀드 첫 투자기업 선정. 그린웨일글로벌",
  229. image: "/img/img--sns2.png",
  230. link: "#",
  231. },
  232. {
  233. id: 3,
  234. title: "그린웨일글로벌, 녹색인프라 수출펀드 첫 투자기업 선정. 그린웨일글로벌",
  235. image: "/img/img--sns3.png",
  236. link: "#",
  237. },
  238. {
  239. id: 4,
  240. title: "그린웨일글로벌, 녹색인프라 수출펀드 첫 투자기업 선정. 그린웨일글로벌",
  241. image: "/img/img--sns4.png",
  242. link: "#",
  243. },
  244. ];
  245. };
  246. // 기본 더미 데이터 설정
  247. const setDefaultPressData = () => {
  248. newsData.value = [
  249. {
  250. id: 1,
  251. title: "그린플라스틱연합, ESG친환경대전서 '자원순환 탄소중립 GPA 컨퍼런스' 개최",
  252. date: "2025.07.11",
  253. image: "/img/img--news.png",
  254. link: "/",
  255. },
  256. {
  257. id: 2,
  258. title: "친환경 플라스틱 기술 개발 동향",
  259. date: "2025.07.10",
  260. image: "/img/img--cycle--center.png",
  261. link: "/",
  262. },
  263. {
  264. id: 3,
  265. title: "탄소중립 실현을 위한 플라스틱 재활용 기술",
  266. date: "2025.07.09",
  267. image: "/img/img--cycle--center.png",
  268. link: "/",
  269. },
  270. {
  271. id: 4,
  272. title: "바이오플라스틱 시장 전망과 기술 동향",
  273. date: "2025.07.08",
  274. image: "/img/img--cycle--center.png",
  275. link: "/",
  276. },
  277. {
  278. id: 5,
  279. title: "플라스틱 감축을 위한 정부 정책 변화",
  280. date: "2025.07.07",
  281. image: "/img/img--cycle--center.png",
  282. link: "/",
  283. },
  284. {
  285. id: 6,
  286. title: "순환경제와 플라스틱 재활용 산업",
  287. date: "2025.07.06",
  288. image: "/img/img--cycle--center.png",
  289. link: "/",
  290. },
  291. {
  292. id: 7,
  293. title: "해양 플라스틱 오염 해결을 위한 혁신 기술",
  294. date: "2025.07.05",
  295. image: "/img/img--cycle--center.png",
  296. link: "/",
  297. },
  298. {
  299. id: 8,
  300. title: "친환경 포장재 개발 현황",
  301. date: "2025.07.04",
  302. image: "/img/img--cycle--center.png",
  303. link: "/",
  304. },
  305. {
  306. id: 9,
  307. title: "플라스틱 대체재 소재 연구 동향",
  308. date: "2025.07.03",
  309. image: "/img/img--cycle--center.png",
  310. link: "/",
  311. },
  312. {
  313. id: 10,
  314. title: "ESG 경영과 플라스틱 감축 전략",
  315. date: "2025.07.02",
  316. image: "/img/img--cycle--center.png",
  317. link: "/",
  318. },
  319. {
  320. id: 11,
  321. title: "글로벌 플라스틱 규제 동향",
  322. date: "2025.07.01",
  323. image: "/img/img--cycle--center.png",
  324. link: "/",
  325. },
  326. {
  327. id: 12,
  328. title: "생분해성 플라스틱 상용화 전망",
  329. date: "2025.06.30",
  330. image: "/img/img--cycle--center.png",
  331. link: "/",
  332. },
  333. {
  334. id: 13,
  335. title: "플라스틱 없는 일주일 캠페인",
  336. date: "2025.06.29",
  337. image: "/img/img--cycle--center.png",
  338. link: "/",
  339. },
  340. {
  341. id: 14,
  342. title: "재활용 플라스틱 품질 향상 기술",
  343. date: "2025.06.28",
  344. image: "/img/img--cycle--center.png",
  345. link: "/",
  346. },
  347. {
  348. id: 15,
  349. title: "플라스틱 순환경제 구축 방안",
  350. date: "2025.06.27",
  351. image: "/img/img--cycle--center.png",
  352. link: "/",
  353. },
  354. {
  355. id: 16,
  356. title: "친환경 소재 개발 투자 확대",
  357. date: "2025.06.26",
  358. image: "/img/img--cycle--center.png",
  359. link: "/",
  360. },
  361. {
  362. id: 17,
  363. title: "마이크로플라스틱 저감 기술",
  364. date: "2025.06.25",
  365. image: "/img/img--cycle--center.png",
  366. link: "/",
  367. },
  368. {
  369. id: 18,
  370. title: "플라스틱 재활용률 제고 방안",
  371. date: "2025.06.24",
  372. image: "/img/img--cycle--center.png",
  373. link: "/",
  374. },
  375. {
  376. id: 19,
  377. title: "바이오매스 기반 플라스틱 개발",
  378. date: "2025.06.23",
  379. image: "/img/img--cycle--center.png",
  380. link: "/",
  381. },
  382. {
  383. id: 20,
  384. title: "환경친화적 플라스틱 산업 전망",
  385. date: "2025.06.22",
  386. image: "/img/img--cycle--center.png",
  387. link: "/",
  388. },
  389. {
  390. id: 21,
  391. title: "플라스틱 폐기물 감축 정책",
  392. date: "2025.06.21",
  393. image: "/img/img--cycle--center.png",
  394. link: "/",
  395. },
  396. {
  397. id: 22,
  398. title: "지속가능한 포장재 솔루션",
  399. date: "2025.06.20",
  400. image: "/img/img--cycle--center.png",
  401. link: "/",
  402. },
  403. {
  404. id: 23,
  405. title: "플라스틱 리사이클링 혁신 기술",
  406. date: "2025.06.19",
  407. image: "/img/img--cycle--center.png",
  408. link: "/",
  409. },
  410. {
  411. id: 24,
  412. title: "친환경 플라스틱 인증 시스템",
  413. date: "2025.06.18",
  414. image: "/img/img--cycle--center.png",
  415. link: "/",
  416. },
  417. {
  418. id: 25,
  419. title: "탄소 발자국 감축을 위한 플라스틱 대안",
  420. date: "2025.06.17",
  421. image: "/img/img--cycle--center.png",
  422. link: "/",
  423. },
  424. ];
  425. };
  426. // 페이지네이션 로직
  427. const currentPage = ref(1);
  428. const itemsPerPage = 10; // 프론트엔드에서 10개씩 표시
  429. const backendPageSize = 20; // 백엔드는 20개씩 가져옴
  430. const totalPages = computed(() => Math.ceil(totalCount.value / itemsPerPage));
  431. const paginatedNews = computed(() => {
  432. const start = (currentPage.value - 1) * itemsPerPage;
  433. const end = start + itemsPerPage;
  434. return newsData.value.slice(start, end);
  435. });
  436. // 백엔드에서 필요한 데이터가 있는지 확인하고 필요시 API 호출
  437. const needToFetchData = (targetPage) => {
  438. const startIndex = (targetPage - 1) * itemsPerPage;
  439. const endIndex = startIndex + itemsPerPage - 1;
  440. return endIndex >= newsData.value.length && newsData.value.length < totalCount.value;
  441. };
  442. const goToPage = async (page) => {
  443. if (page >= 1 && page <= totalPages.value) {
  444. currentPage.value = page;
  445. if (needToFetchData(page)) {
  446. // 백엔드 페이지 계산: 프론트 페이지를 백엔드 페이지로 변환
  447. const backendPage = Math.ceil((page * itemsPerPage) / backendPageSize);
  448. await fetchPressList(backendPage);
  449. }
  450. }
  451. };
  452. const nextPage = async () => {
  453. if (currentPage.value < totalPages.value) {
  454. await goToPage(currentPage.value + 1);
  455. }
  456. };
  457. const prevPage = async () => {
  458. if (currentPage.value > 1) {
  459. await goToPage(currentPage.value - 1);
  460. }
  461. };
  462. // 컴포넌트 마운트 시 데이터 로드
  463. onMounted(() => {
  464. fetchPressList(1);
  465. fetchSnsList();
  466. });
  467. </script>