Crafts.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761
  1. <script setup>
  2. import { ref, reactive, watch, computed, onMounted } from "vue";
  3. import { useRoute, useRouter } from "vue-router";
  4. import { useMainStore } from "@/stores/store";
  5. import { useI18n } from "vue-i18n";
  6. import axios from "axios";
  7. import Navbar from "@/components/Navbar.vue";
  8. import PDFViewer from "@/components/PDFViewer.vue";
  9. import craftsPdfList from "@/utils/useCraftsPdf";
  10. import ArticleCard from "@/components/ArticleCard.vue";
  11. import CraftsArticle from "@/components/CraftsArticle.vue";
  12. const { t } = useI18n();
  13. const route = useRoute();
  14. const router = useRouter();
  15. const store = useMainStore();
  16. const routeHash = route.hash; // 網址參數(錨點)
  17. const bookName = route.params.id; // 網址參數(pdf書名)
  18. if (routeHash) {
  19. setTimeout(() => {
  20. const targetElement = document.querySelector(routeHash);
  21. if (targetElement) {
  22. targetElement.scrollIntoView({ behavior: "smooth" });
  23. }
  24. }, 500);
  25. }
  26. onMounted(() => {
  27. if (bookName) {
  28. setTimeout(() => {
  29. let book = read.list.filter((item) => item.article_id == bookName);
  30. updatePDF(book[0].files);
  31. }, 1000);
  32. }
  33. });
  34. let searchInput = ref("");
  35. let searchError = ref(false);
  36. let currentPage = ref(1); // 當前頁數(預設第一頁)
  37. let itemsPerPage = ref(5); // 每頁顯示筆數
  38. let totalPages = ref(1); // 總頁數
  39. const newsAll = reactive({
  40. list: [],
  41. });
  42. const newsData = reactive({
  43. list: [],
  44. });
  45. (async function getData() {
  46. try {
  47. const response = await axios.get(`${store.apiUrl}/api/get_news`);
  48. console.log("response", response.data.news);
  49. newsAll.list = response.data.news;
  50. newsData.list = response.data.news;
  51. totalPages.value = store.getTotalPages(response.data.news.length, 5); // 計算頁數
  52. } catch (error) {
  53. console.error(error);
  54. }
  55. })();
  56. let thesis = reactive({
  57. list: [],
  58. });
  59. (async function getArticle() {
  60. try {
  61. const response = await axios.get(
  62. `${store.apiUrl}/api/get_article?group_sort=論文補助`
  63. );
  64. thesis.list = response.data.articles;
  65. console.log("response", response.data.articles);
  66. } catch (error) {
  67. console.error(error);
  68. }
  69. })();
  70. // 搜尋
  71. async function search() {
  72. searchError.value = false;
  73. let keyword = searchInput.value;
  74. if (keyword !== "") {
  75. try {
  76. const response = await axios.get(
  77. `${store.apiUrl}/api/search_news_like?keyword=${keyword}`
  78. );
  79. if (response.data.news.length !== 0) {
  80. newsData.list = response.data.news;
  81. } else {
  82. searchError.value = true;
  83. }
  84. } catch (error) {
  85. console.error(error);
  86. }
  87. } else {
  88. newsData.list = newsAll.list;
  89. }
  90. }
  91. const searchLocation = ref(null);
  92. // 切換分頁時回到列表上方
  93. watch(currentPage, () => {
  94. searchLocation.value.scrollIntoView({ behavior: "smooth", block: "start" });
  95. });
  96. // 分頁顯示
  97. const paginatedList = computed(() => {
  98. const startIndex = (currentPage.value - 1) * itemsPerPage.value;
  99. const endIndex = startIndex + itemsPerPage.value;
  100. return newsData.list.slice(startIndex, endIndex);
  101. });
  102. const breadcrumbs = reactive([
  103. {
  104. title: "home.title",
  105. disabled: false,
  106. href: "/",
  107. },
  108. {
  109. title: "navbar.crafts",
  110. disabled: true,
  111. href: "/crafts",
  112. },
  113. ]);
  114. const tagList = reactive([
  115. {
  116. title: "crafts.title_1",
  117. url: "#articleList",
  118. },
  119. {
  120. title: "crafts.title_2",
  121. url: "#readList",
  122. },
  123. {
  124. title: "crafts.title_3",
  125. url: "#bookList",
  126. },
  127. {
  128. title: "crafts.title_4",
  129. url: "#journal",
  130. },
  131. {
  132. title: "crafts.title_5",
  133. url: "#thesisGrant",
  134. },
  135. ]);
  136. let fileName = ref(""); // PDFViewer Props
  137. const readRef = ref(null);
  138. const viewerRef = ref(null);
  139. function updatePDF(name) {
  140. // 更新 pdf 後移除網址參數
  141. router.replace({ path: "/crafts" });
  142. fileName.value = name;
  143. if (!store.isMobile) {
  144. setTimeout(() => {
  145. viewerRef.value.scrollIntoView({ behavior: "smooth", block: "start" });
  146. }, 300);
  147. }
  148. }
  149. function isAnchorLink(url) {
  150. // 檢查 URL 是否為錨點
  151. return url.startsWith("#");
  152. }
  153. let read = reactive({
  154. list: [],
  155. });
  156. // 線上閱讀
  157. (async () => {
  158. try {
  159. const response = await axios.get(
  160. `${store.apiUrl}/api/get_article?group_sort=線上閱讀`
  161. );
  162. read.list = response.data.articles;
  163. console.log("線上閱讀", read.list);
  164. } catch (error) {
  165. console.error(error);
  166. }
  167. })();
  168. let book = reactive({
  169. list: [],
  170. });
  171. // 工藝書單
  172. (async () => {
  173. try {
  174. const response = await axios.get(
  175. `${store.apiUrl}/api/get_article?group_sort=工藝書單`
  176. );
  177. book.list = response.data.articles;
  178. console.log("工藝書單", response.data.articles);
  179. } catch (error) {
  180. console.error(error);
  181. }
  182. })();
  183. // 正則表達式移除 style 屬性
  184. const filteredContent = (content) => {
  185. const regex = /style="text-align: center;"/g;
  186. return content.replace(regex, "");
  187. };
  188. function handlePdfUrl(pdf) {
  189. let json = pdf.replace(/'/g, '"');
  190. let file = JSON.parse(json);
  191. let url = file.file1;
  192. return url;
  193. }
  194. </script>
  195. <template>
  196. <Navbar />
  197. <div class="bg-img">
  198. <v-container class="position-relative">
  199. <div class="article-content">
  200. <v-breadcrumbs :items="breadcrumbs" divider="/" class="my-5">
  201. <template v-slot:title="{ item }">
  202. {{ t(item.title) }}
  203. </template>
  204. </v-breadcrumbs>
  205. <v-row class="px-sm-10 pb-3 tag-btn">
  206. <v-col
  207. v-for="(item, index) in tagList"
  208. :key="index"
  209. :cols="index + 1 === tagList.length ? '12' : '6'"
  210. md=""
  211. >
  212. <a
  213. v-if="isAnchorLink(item.url)"
  214. :href="item.url"
  215. @click.stop
  216. class="py-3 py-sm-6 item"
  217. >
  218. <!-- 錨點 -->
  219. <h3>{{ t(`${item.title}`) }}</h3>
  220. </a>
  221. <a v-else :href="item.url" target="_blank" class="py-3 py-lg-6">
  222. <!-- 網址 -->
  223. <h3>{{ t(`${item.title}`) }}</h3>
  224. </a>
  225. </v-col>
  226. </v-row>
  227. <h2 id="articleList" class="mb-16 pb-5">{{ t("crafts.title_1") }}</h2>
  228. <!-- <div class="search pt-5 my-10 me-sm-16" ref="searchLocation">
  229. <span>
  230. <input
  231. v-model="searchInput"
  232. type="text"
  233. @keyup.enter="search()"
  234. placeholder="關鍵字搜尋"
  235. />
  236. <button @click="search()">
  237. <img src="@/assets/img/news/news-search-icon.png" alt="臺灣工藝學校全球學習共享平台" />
  238. </button>
  239. </span>
  240. <div
  241. v-if="searchError"
  242. class="d-flex justify-center align-center error me-4"
  243. >
  244. <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
  245. {{ t("no_found") }}
  246. </div>
  247. </div> -->
  248. <CraftsArticle />
  249. <h2 ref="readRef" id="readList">{{ t("crafts.title_2") }}</h2>
  250. <v-row class="justify-center align-start mt-16 read-list">
  251. <v-col
  252. cols="12"
  253. sm="6"
  254. md="4"
  255. v-for="(item, index) in read.list"
  256. :key="index"
  257. class="d-flex flex-row flex-sm-column align-center px-sm-10"
  258. >
  259. <a
  260. v-if="store.isMobile"
  261. :href="handlePdfUrl(item.files)"
  262. target="_blank"
  263. >
  264. <v-img
  265. class="mx-auto cover-img"
  266. :lazy-src="`${store.imgUrl}/${item.cover_img}`"
  267. :src="`${store.imgUrl}/${item.cover_img}`"
  268. alt="臺灣工藝學校全球學習共享平台"
  269. >
  270. <template v-slot:placeholder>
  271. <div class="d-flex align-center justify-center fill-height">
  272. <v-progress-circular
  273. color="grey-lighten-4"
  274. indeterminate
  275. ></v-progress-circular>
  276. </div>
  277. </template>
  278. </v-img>
  279. <!-- <img :src="item.img" alt="臺灣工藝學校全球學習共享平台" /> -->
  280. </a>
  281. <!-- <img
  282. v-else
  283. :src="item.img"
  284. alt="臺灣工藝學校全球學習共享平台"
  285. @click="updatePDF(item.fileName)"
  286. /> -->
  287. <v-img
  288. v-else
  289. class="mx-auto cover-img"
  290. :lazy-src="`${store.imgUrl}/${item.cover_img}`"
  291. :src="`${store.imgUrl}/${item.cover_img}`"
  292. @click="updatePDF(item.files)"
  293. alt="臺灣工藝學校全球學習共享平台"
  294. >
  295. <template v-slot:placeholder>
  296. <div class="d-flex align-center justify-center fill-height">
  297. <v-progress-circular
  298. color="grey-lighten-4"
  299. indeterminate
  300. ></v-progress-circular>
  301. </div>
  302. </template>
  303. </v-img>
  304. <!-- <a :href="item.url" target="_blank">
  305. <img :src="item.img" alt="臺灣工藝學校全球學習共享平台" />
  306. </a> -->
  307. <section>
  308. <h3 v-html="item.title"></h3>
  309. <!-- <p>{{ item.depiction }}</p> -->
  310. </section>
  311. </v-col>
  312. </v-row>
  313. <div ref="viewerRef">
  314. <PDFViewer :file="fileName" />
  315. </div>
  316. <h2 id="bookList" class="mb-16">{{ t("crafts.title_3") }}</h2>
  317. <!-- <div class="search pt-5 my-10 me-sm-16" ref="searchLocation ">
  318. <span>
  319. <input
  320. v-model="searchInput"
  321. type="text"
  322. @keyup.enter="search()"
  323. placeholder="關鍵字搜尋"
  324. />
  325. <button @click="search()">
  326. <img src="@/assets/img/news/news-search-icon.png" alt="臺灣工藝學校全球學習共享平台" />
  327. </button>
  328. </span>
  329. <div
  330. v-if="searchError"
  331. class="d-flex justify-center align-center error me-4"
  332. >
  333. <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
  334. {{ t("no_found") }}
  335. </div>
  336. </div> -->
  337. <v-row class="justify-center book-list">
  338. <v-col
  339. cols="12"
  340. sm="10"
  341. md="6"
  342. v-for="(item, index) in book.list"
  343. :key="index"
  344. class="position-relative mt-10 mb-16 pb-16"
  345. >
  346. <a
  347. :href="$router.resolve(`/article-detail/${item.article_id}`).href"
  348. >
  349. <div class="overflow-hidden">
  350. <v-img
  351. class="mx-auto cover-img"
  352. :lazy-src="`${store.imgUrl}/${item.cover_img}`"
  353. cover
  354. :src="`${store.imgUrl}/${item.cover_img}`"
  355. alt="臺灣工藝學校全球學習共享平台"
  356. >
  357. <template v-slot:placeholder>
  358. <div class="d-flex align-center justify-center fill-height">
  359. <v-progress-circular
  360. color="grey-lighten-4"
  361. indeterminate
  362. ></v-progress-circular>
  363. </div>
  364. </template>
  365. </v-img>
  366. </div>
  367. </a>
  368. <!-- <a :href="item.url" target="_blank">
  369. <img :src="item.img" alt="臺灣工藝學校全球學習共享平台" />
  370. </a> -->
  371. <section class="info">
  372. <p v-html="filteredContent(item.content)"></p>
  373. <span>{{ JSON.parse(item.tags)[0] }}</span>
  374. </section>
  375. </v-col>
  376. </v-row>
  377. <!-- <v-pagination
  378. v-model="currentPage"
  379. class="my-4"
  380. :length="totalPages"
  381. ></v-pagination> -->
  382. <h2 id="journal">{{ t("crafts.title_4") }}</h2>
  383. <div class="journal-content">
  384. <p>
  385. 臺灣工藝學刊(Journal of
  386. Craftology|Taiwan)是由中華民國文化部所屬臺灣工藝學校全球學習共享平台所出版之學術性半年刊,創立於2021年。
  387. <br />
  388. <br />
  389. 本刊企圖以工藝為問題意識核心,致力於工藝學跨領域思維整合之研究論文,內容涵蓋當代工藝多元價值論述,藝術與人文、社會及科學之跨領域發展整合、產生對話與交集,提供工藝研究交流平台,經由公開徵求工藝相關領域之研究論文稿件,旨在促進臺灣工藝文化主體與工藝產業發展的學術研究質量,建構國內工藝學知識體系,以提升國內工藝學術研究水準。
  390. </p>
  391. <div class="list">
  392. <span class="title">
  393. <p>學刊主題</p>
  394. <p>發刊</p>
  395. </span>
  396. <ul>
  397. <li>
  398. <p>臺灣工藝學刊-第 1 期</p>
  399. <p>2022.11</p>
  400. </li>
  401. <li>
  402. <p>臺灣工藝學刊-創刊特別號</p>
  403. <p>2021.12</p>
  404. </li>
  405. </ul>
  406. </div>
  407. <div class="pdf-list">
  408. <div class="side-title">
  409. <h5>研究論文</h5>
  410. <h5>實務報告</h5>
  411. </div>
  412. <ul>
  413. <li v-for="(item, index) in craftsPdfList" :key="index">
  414. <h3>{{ item.title }}</h3>
  415. <h4>{{ item.en_title }}</h4>
  416. <p>{{ item.author }} {{ item.en_author }}</p>
  417. <p>
  418. 關鍵字:{{ item.keywords }} <br />
  419. keywords:{{ item.en_keywords }}
  420. </p>
  421. <a
  422. :href="`${store.imgUrl}/pdf/crafts/${item.fileName}.pdf`"
  423. download
  424. >
  425. <span>
  426. <v-icon
  427. icon="mdi-download"
  428. color="gray"
  429. class="me-2 pt-1"
  430. ></v-icon>
  431. 全文下載
  432. </span>
  433. </a>
  434. </li>
  435. </ul>
  436. </div>
  437. </div>
  438. <h2 id="thesisGrant">{{ t("crafts.title_5") }}</h2>
  439. <ul class="mt-10">
  440. <li
  441. v-for="(item, index) in thesis.list"
  442. :key="index"
  443. class="post-item pa-5"
  444. rounded="xl"
  445. >
  446. <ArticleCard :data="item" type="article" />
  447. </li>
  448. </ul>
  449. </div>
  450. </v-container>
  451. </div>
  452. </template>
  453. <style lang="scss" scoped>
  454. .article-content {
  455. margin-bottom: 9.375em;
  456. }
  457. h2 {
  458. padding-top: 6.25em;
  459. font-size: 1.875em;
  460. font-weight: 500;
  461. text-align: center;
  462. }
  463. .bg-img {
  464. background-image: url("@/assets/img/crafts/background.png");
  465. background-size: cover;
  466. background-position: top;
  467. @media (max-width: 960px) {
  468. background-size: contain;
  469. background-repeat: repeat;
  470. }
  471. }
  472. .read-list,
  473. .book-list {
  474. p {
  475. margin-bottom: 0.625em;
  476. }
  477. h3,
  478. p {
  479. font-weight: 500;
  480. text-align: center;
  481. line-height: 1.875em;
  482. }
  483. }
  484. .read-list {
  485. .v-img {
  486. width: 100%;
  487. // height: 100%;
  488. max-height: 25em;
  489. object-fit: cover;
  490. cursor: pointer;
  491. @media (max-width: 600px) {
  492. width: 50%;
  493. }
  494. }
  495. @media (max-width: 600px) {
  496. section {
  497. width: 50%;
  498. padding: 0 0.625em;
  499. font-size: 0.875em;
  500. }
  501. }
  502. p {
  503. width: 18.125em;
  504. @media (max-width: 600px) {
  505. width: auto;
  506. }
  507. }
  508. }
  509. .book-list {
  510. margin-bottom: 6.25em;
  511. .info {
  512. width: 26.875em;
  513. padding: 0.625em 1.25em;
  514. position: absolute;
  515. right: -2.5em;
  516. bottom: -3.75em;
  517. z-index: 1;
  518. background: rgba(255, 255, 255, 0.8);
  519. border: 0.125em solid #c8cbcc;
  520. @media (max-width: 1280px) {
  521. width: 25em;
  522. }
  523. @media (max-width: 600px) {
  524. width: 20.625em;
  525. right: -0.1875em;
  526. bottom: -5em;
  527. }
  528. p {
  529. width: 18.75em;
  530. margin-bottom: 0;
  531. text-align: start;
  532. }
  533. span {
  534. display: block;
  535. margin-top: auto;
  536. position: absolute;
  537. bottom: 0.9375em;
  538. right: 0.9375em;
  539. }
  540. }
  541. }
  542. .pdf-list {
  543. display: flex;
  544. margin: 6.25em 0;
  545. .side-title {
  546. padding: 4.375em 0 4.375em 0.625em;
  547. display: flex;
  548. flex-direction: column;
  549. justify-content: space-between;
  550. border-radius: 0.3125em;
  551. background-color: var(--purple);
  552. h5 {
  553. padding: 1.25em 0.625em;
  554. position: relative;
  555. font-size: 1.625em;
  556. font-weight: 400;
  557. writing-mode: vertical-rl; // 垂直
  558. color: var(--purple);
  559. letter-spacing: 0.125em;
  560. background-color: #fff;
  561. @media (max-width: 600px) {
  562. font-size: 1.25em;
  563. }
  564. &::after,
  565. &::before {
  566. content: "";
  567. display: block;
  568. height: 0.9375em;
  569. width: 105%;
  570. background: #fff;
  571. position: absolute;
  572. }
  573. &::after {
  574. top: -0.4em;
  575. right: -0.2em;
  576. transform: rotate(-15deg);
  577. @media (max-width: 1200px) {
  578. top: -0.5em;
  579. right: -0.1875em;
  580. }
  581. }
  582. &::before {
  583. bottom: -0.4em;
  584. right: -0.2em;
  585. transform: rotate(15deg);
  586. @media (max-width: 1200px) {
  587. bottom: -0.5em;
  588. right: -0.1875em;
  589. }
  590. }
  591. }
  592. }
  593. ul {
  594. background-color: #fff;
  595. padding: 3.125em 3.125em 0 3.125em;
  596. @media (max-width: 600px) {
  597. padding: 1.5625em 1.5625em 0 1.5625em;
  598. }
  599. li {
  600. margin-bottom: 1.5625em;
  601. padding-bottom: 1.5625em;
  602. border-bottom: 0.0625em dashed #ccc;
  603. &:last-child {
  604. margin-bottom: 0;
  605. // padding-bottom: 0;
  606. }
  607. h3 {
  608. font-size: 1.375em;
  609. line-height: 1.875em;
  610. @media (max-width: 600px) {
  611. font-size: 1.125em;
  612. }
  613. }
  614. h4 {
  615. margin: 0.9375em 0;
  616. font-size: 1.125em;
  617. line-height: 1.625em;
  618. color: var(--gray);
  619. @media (max-width: 600px) {
  620. font-size: 1em;
  621. }
  622. }
  623. h3,
  624. h4 {
  625. font-weight: 500;
  626. }
  627. h3,
  628. h4,
  629. p {
  630. letter-spacing: 0.0625em;
  631. }
  632. p {
  633. font-size: 0.875em;
  634. color: var(--gray);
  635. }
  636. a {
  637. display: inline-block;
  638. padding: 0.3125em 0.625em;
  639. margin-top: 1.25em;
  640. font-size: 0.875em;
  641. border-radius: 0.3125em;
  642. border: 0.125em solid var(--gray);
  643. transition: all 0.3s;
  644. &:hover {
  645. opacity: 0.7;
  646. }
  647. span {
  648. display: flex;
  649. align-items: center;
  650. }
  651. }
  652. }
  653. }
  654. }
  655. .journal-content {
  656. margin-top: 5em;
  657. p {
  658. line-height: 1.75em;
  659. }
  660. .list {
  661. letter-spacing: 0.0625em;
  662. .title {
  663. margin-top: 6.25em;
  664. padding-bottom: 1.25em;
  665. font-size: 1.25em;
  666. border-bottom: 0.125em solid var(--purple);
  667. }
  668. .title,
  669. ul li {
  670. display: flex;
  671. justify-content: space-between;
  672. }
  673. ul {
  674. margin-top: 1.25em;
  675. li {
  676. margin-top: 0.625em;
  677. padding-bottom: 0.625em;
  678. border-bottom: 0.125em dashed #dddddd;
  679. // &:first-child {
  680. // border-bottom: 0.125em dashed #ccc;
  681. // }
  682. p {
  683. font-weight: 400;
  684. }
  685. }
  686. }
  687. }
  688. }
  689. </style>