Home.vue 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280
  1. <script setup>
  2. import { ref, reactive, watch, onMounted } from "vue";
  3. import { useMainStore } from "@/stores/store";
  4. import { useI18n } from "vue-i18n";
  5. import axios from "axios";
  6. import Map from "@/components/Map.vue";
  7. import Navbar from "@/components/Navbar.vue";
  8. import TwCities from "@/assets/TwCities.json";
  9. import HomeList from "@/components/HomeList.vue";
  10. import TermsList from "@/components/TermsList.vue";
  11. import CourseCard from "@/components/CourseCard.vue";
  12. import CraftsArticle from "@/components/CraftsArticle.vue";
  13. const store = useMainStore();
  14. const { t } = useI18n();
  15. let loading = ref(true);
  16. onMounted(() => {
  17. // 無障礙網頁需於 iframe 加上 title
  18. // let hubspot = document.querySelector("#hubspot-conversations-iframe");
  19. // if (hubspot) {
  20. // hubspot.title = "HubSpot iframe";
  21. // }
  22. // console.log("hubspot", hubspot);
  23. store.getFavoriteClass();
  24. setTimeout(() => {
  25. loading.value = false;
  26. console.log("VITE_API_URL", import.meta.env.VITE_API_URL);
  27. console.log("apiUrl", store.apiUrl);
  28. }, 300);
  29. });
  30. // 地圖搜尋
  31. let selectedCounty = ref(null); // 縣市
  32. let selectedDistrict = ref(null); // 區域
  33. // 縣市
  34. let counties = reactive({
  35. list: [],
  36. });
  37. TwCities.map((e) => counties.list.push(e.name));
  38. // 區域
  39. let district = reactive({
  40. list: [],
  41. });
  42. // 選擇縣市後顯示對應區域
  43. watch(selectedCounty, (newValue) => {
  44. district.list = [];
  45. selectedDistrict.value = null;
  46. TwCities.map((e) => {
  47. if (e.name === newValue) {
  48. e.districts.map((item) => district.list.push(item.name));
  49. }
  50. });
  51. });
  52. let schoolKeyword = ref("");
  53. let locationSearchId = reactive({
  54. list: [],
  55. });
  56. // 搜尋據點
  57. async function searchSchool() {
  58. pageNum.value = 1;
  59. schoolState.value = true;
  60. locationSearchId.list.length = 0;
  61. let url = `${store.apiUrl}/api/get_school?is_check=1`;
  62. let location = "";
  63. if (selectedCounty.value && selectedDistrict.value) {
  64. location = `${selectedCounty.value}${selectedDistrict.value}`;
  65. } else if (selectedCounty.value) {
  66. location = `${selectedCounty.value}`;
  67. }
  68. if (schoolKeyword.value !== "" && location !== "") {
  69. url += `&keyword=${schoolKeyword.value}&location_keyword=${location}`;
  70. } else if (schoolKeyword.value === "" && location !== "") {
  71. url += `&location_keyword=${location}`;
  72. } else if (schoolKeyword.value !== "" && location === "") {
  73. url += `&keyword=${schoolKeyword.value}`;
  74. }
  75. console.log("url", url);
  76. try {
  77. const response = await axios.get(url);
  78. // totalPages.value = store.getTotalPages(response.data.total_num, 4);
  79. // classes.data = response.data.classes;
  80. console.log("搜尋地圖", response.data.schools);
  81. // 存據點 ID
  82. response.data.schools.map((item) =>
  83. locationSearchId.list.push(item.location_id)
  84. );
  85. console.log("locationSearchId.list", locationSearchId.list);
  86. // // 儲存地圖經緯度
  87. store.mapLocation.lat = parseFloat(response.data.schools[0].Lat);
  88. store.mapLocation.lng = parseFloat(response.data.schools[0].Lng);
  89. } catch (error) {
  90. console.error(error);
  91. }
  92. getClass();
  93. }
  94. let pageNum = ref(1); // 頁數(預設第一頁)
  95. let pageAmount = ref(4); // 每頁顯示筆數
  96. let totalPages = ref(1); // 總頁數
  97. // 切換分頁
  98. watch(pageNum, () => {
  99. getClass();
  100. });
  101. let classes = reactive({
  102. data: [],
  103. });
  104. let assignState = ref(false);
  105. let assignLocationId = ref(null);
  106. let schoolState = ref(false); // 搜尋狀態
  107. async function getClass() {
  108. console.log("get_class_name");
  109. let url = `${store.apiUrl}/api/get_class_name?is_check=1&page_num=${pageNum.value}&page_amount=${pageAmount.value}`;
  110. let assignUrl = `${store.apiUrl}/api/get_class_name?is_check=1&page_num=${pageNum.value}&page_amount=${pageAmount.value}&location_id=${assignLocationId.value}`;
  111. if (schoolState.value) {
  112. const list = `[${locationSearchId.list.join(",")}]`;
  113. url += `&location_id=${list}`;
  114. }
  115. console.log("schoolState url", url);
  116. // if (schoolState.value) {
  117. // let location = "";
  118. // if (selectedCounty.value && selectedDistrict.value) {
  119. // location = `${selectedCounty.value}${selectedDistrict.value}`;
  120. // console.log("location &&", location);
  121. // } else if (selectedCounty.value) {
  122. // location = `${selectedCounty.value}`;
  123. // console.log("location >>", location);
  124. // }
  125. // if (schoolKeyword.value !== "" && location !== "") {
  126. // url += `&keyword=${schoolKeyword.value}&location_keyword=${location}`;
  127. // } else if (schoolKeyword.value === "" && location !== "") {
  128. // url += `&location_keyword=${location}`;
  129. // } else if (schoolKeyword.value !== "" && location === "") {
  130. // url += `&keyword=${schoolKeyword.value}`;
  131. // }
  132. // console.log("url", url);
  133. // }
  134. try {
  135. const response = await axios.get(assignState.value ? assignUrl : url);
  136. totalPages.value = store.getTotalPages(response.data.total_num, 4);
  137. classes.data = response.data.classes;
  138. console.log("response", response.data.classes);
  139. // 儲存地圖經緯度
  140. store.mapLocation.lat = parseFloat(response.data.classes[0].Lat);
  141. store.mapLocation.lng = parseFloat(response.data.classes[0].Lng);
  142. } catch (error) {
  143. console.error(error);
  144. }
  145. }
  146. getClass();
  147. // 取得特定據點課程總筆數
  148. const getClassList = async (locationId) => {
  149. assignState.value = true;
  150. assignLocationId.value = locationId;
  151. try {
  152. const response = await axios.get(
  153. `${store.apiUrl}/api/get_class_name?location_id=${locationId}`
  154. );
  155. getClass();
  156. pageNum.value = 1;
  157. totalPages.value = store.getTotalPages(response.data.classes.length, 4);
  158. } catch (error) {
  159. console.log("error", error);
  160. }
  161. };
  162. let recommend = reactive({
  163. list: [],
  164. });
  165. // 推薦課程
  166. (async () => {
  167. try {
  168. const response = await axios.get(
  169. `${store.apiUrl}/api/get_class_name?recommend=1`
  170. );
  171. console.log("推薦課程", response);
  172. recommend.list = response.data.classes;
  173. console.log(" recommend.list", recommend.list);
  174. } catch (error) {
  175. console.error(error);
  176. }
  177. })();
  178. let panel = ref([]);
  179. const tagList = reactive([
  180. {
  181. title: "crafts.title_1",
  182. url: "/crafts#articleList",
  183. },
  184. {
  185. title: "crafts.title_2",
  186. url: "/crafts#readList",
  187. },
  188. {
  189. title: "crafts.title_3",
  190. url: "/crafts#bookList",
  191. },
  192. {
  193. title: "crafts.title_4",
  194. url: "/crafts#journal",
  195. },
  196. {
  197. title: "crafts.title_5",
  198. url: "/crafts#thesisGrant",
  199. },
  200. ]);
  201. let news = reactive({
  202. list: [],
  203. });
  204. // 重要訊息
  205. async function getNews() {
  206. // 取得最新兩篇
  207. try {
  208. const response = await axios.get(
  209. `${store.apiUrl}/api/get_news?page_num=1&page_amount=2`
  210. );
  211. news.list = response.data.news;
  212. console.log("重要訊息(新聞)", news.list);
  213. } catch (error) {
  214. console.error(error);
  215. }
  216. }
  217. getNews();
  218. let exhibit = reactive({
  219. list: [],
  220. });
  221. // 展覽(課程)
  222. async function getExhibit() {
  223. // 取得最新兩篇
  224. try {
  225. const response = await axios.get(
  226. `${store.apiUrl}/api/get_class_name?category=展覽&page_num=1&page_amount=2`
  227. );
  228. exhibit.list = response.data.classes;
  229. console.log("重要訊息(展覽)", exhibit.list);
  230. } catch (error) {
  231. console.error(error);
  232. }
  233. }
  234. let assignTag = ref("news");
  235. function selectTag(btn) {
  236. if (btn === "news") {
  237. assignTag.value = "news";
  238. } else {
  239. assignTag.value = "exhibit";
  240. getExhibit();
  241. }
  242. }
  243. // 工藝地圖手機版列表(底部彈出視窗)
  244. const isBottomSheetOpen = ref(false);
  245. // 開啟底部彈出視窗
  246. function handleTouch() {
  247. console.log("handleTouch");
  248. isBottomSheetOpen.value = !isBottomSheetOpen.value;
  249. }
  250. onMounted(() => {
  251. if (store.isMobile) {
  252. window.addEventListener("scroll", () => {
  253. let scrollHeight = document.documentElement.scrollTop;
  254. let bottomSheet = document.querySelector(".bottom-sheet");
  255. let mapBlock = document.querySelector(".map-block"); // 全台工藝地圖
  256. let recommendBlock = document.querySelector(".recommend-block"); // 工藝課程推薦
  257. // 取得位置
  258. let mapPosition = mapBlock.offsetTop;
  259. let recommendPosition = recommendBlock.offsetTop;
  260. // 判斷是否滾動到工藝地圖區塊
  261. if (
  262. scrollHeight >= mapPosition - 300 &&
  263. scrollHeight <= recommendPosition - 300
  264. ) {
  265. bottomSheet.style.display = "flex";
  266. } else {
  267. bottomSheet.style.display = "none";
  268. }
  269. });
  270. }
  271. });
  272. let closeBanner = ref(false);
  273. </script>
  274. <template>
  275. <Transition>
  276. <div class="loading-item" v-if="loading">
  277. <v-progress-circular
  278. color="grey-lighten-4"
  279. indeterminate
  280. ></v-progress-circular>
  281. </div>
  282. </Transition>
  283. <div class="banner" :class="{ close: closeBanner }">
  284. <img
  285. src="@/assets/img/home/banner.webp"
  286. alt="臺灣工藝學習平台"
  287. class="cover"
  288. />
  289. <img
  290. src="@/assets/img/home/logo-center.png"
  291. alt="臺灣工藝學習平台"
  292. class="logo"
  293. />
  294. </div>
  295. <button
  296. @click="closeBanner = true"
  297. class="close-btn"
  298. :class="{ close: closeBanner }"
  299. >
  300. <v-icon icon="mdi-close"></v-icon>
  301. </button>
  302. <Navbar />
  303. <v-container class="px-md-0 pb-16 mb-16">
  304. <section class="text-center intro">
  305. <h2 class="title">{{ t("home.title_1") }}</h2>
  306. <p class="my-10">
  307. {{ t("home.content") }}
  308. </p>
  309. <!-- <p class="my-10">
  310. 以佈局具國際視野之工藝學習共享平台為目標,藉由「工藝學校」的主體概念,推動臺灣工藝學習平台,以共享、友善、全人、全民的終身工藝手作平台進行人才、課程、知識、教材之工藝資源嫁接媒合與內容設計,以在地、就近、線上、線下等多元方式提供不同型態之學習體驗內容及選擇。
  311. </p>
  312. <p>
  313. With the goal of laying out a craft learning and sharing platform with
  314. an international perspective, through the main concept of "craft
  315. school", we promote the global learning platform of International Craft
  316. Learning Platform co-ops, and use a lifelong craft platform run on the
  317. values of sharing, friendliness, and holisticness to design and
  318. integrate craft resources such as talents, courses, knowledge and
  319. teaching materials, and provide different types of learning experiences
  320. in local ways, both online and offline.
  321. </p> -->
  322. </section>
  323. <v-carousel
  324. cycle
  325. height="auto"
  326. hide-delimiters
  327. hide-delimiter-background
  328. show-arrows="hover"
  329. >
  330. <v-carousel-item>
  331. <div class="d-flex h-100 align-center">
  332. <img
  333. src="@/assets/img/home/carousel-01.jpg"
  334. alt="臺灣工藝學習平台"
  335. class="w-100"
  336. />
  337. </div>
  338. </v-carousel-item>
  339. </v-carousel>
  340. <div class="news-content">
  341. <div class="d-flex tab-btn mb-16">
  342. <v-btn
  343. variant="text"
  344. @click="selectTag('news')"
  345. :class="{ active: assignTag === 'news' }"
  346. >
  347. {{ t("news") }}
  348. </v-btn>
  349. <v-btn
  350. variant="text"
  351. @click="selectTag('exhibit')"
  352. :class="{ active: assignTag === 'exhibit' }"
  353. >
  354. {{ t("exhibitions") }}
  355. </v-btn>
  356. </div>
  357. <div class="d-flex justify-end mb-5 mb-sm-0">
  358. <router-link to="/news">{{ t("more_news") }} >></router-link>
  359. </div>
  360. <ul v-if="assignTag === 'news'">
  361. <li v-for="(item, index) in news.list" :key="index" class="mb-16 list">
  362. <HomeList :data="item" />
  363. <!-- <section class="d-flex">
  364. <p class="category mb-5">
  365. <span></span>
  366. {{ item.category }}
  367. </p>
  368. <p class="ms-5">
  369. {{ moment(`${item.create_time}`).format("YYYY-MM-DD") }}
  370. </p>
  371. </section>
  372. <v-card
  373. variant="outlined"
  374. class="d-flex flex-md-row flex-column align-center pa-5"
  375. >
  376. <v-row class="align-center">
  377. <v-col cols="12">
  378. <router-link :to="`/news/${item.news_id}`" class="cover-img">
  379. <section class="d-flex flex-column pa-3">
  380. <h3>{{ item.title }}</h3>
  381. <p v-html="item.content"></p>
  382. </section>
  383. </router-link>
  384. </v-col>
  385. </v-row>
  386. </v-card> -->
  387. </li>
  388. </ul>
  389. <ul v-else>
  390. <li
  391. v-for="(item, index) in exhibit.list"
  392. :key="index"
  393. class="mb-16 list"
  394. >
  395. <HomeList :data="item" />
  396. </li>
  397. </ul>
  398. </div>
  399. <h2 class="my-10 title">{{ t("home.title_2") }}</h2>
  400. <v-row>
  401. <v-col cols="6" md="4">
  402. <router-link to="/college-group/future" class="img-info">
  403. <img
  404. src="@/assets/img/home/首頁元素-12.webp"
  405. alt="臺灣工藝學習平台"
  406. />
  407. <section>
  408. <p>{{ t("college_group_1") }}</p>
  409. </section>
  410. </router-link>
  411. </v-col>
  412. <v-col cols="6" md="4">
  413. <router-link to="/college-group/craft" class="img-info">
  414. <img
  415. src="@/assets/img/home/首頁元素-11.webp"
  416. alt="臺灣工藝學習平台"
  417. />
  418. <section>
  419. <p>{{ t("college_group_2") }}</p>
  420. </section>
  421. </router-link>
  422. </v-col>
  423. <v-col cols="6" md="4">
  424. <router-link to="/college-group/cross" class="img-info">
  425. <img
  426. src="@/assets/img/home/首頁元素-06.webp"
  427. alt="臺灣工藝學習平台"
  428. />
  429. <section>
  430. <p>{{ t("college_group_3") }}</p>
  431. </section>
  432. </router-link>
  433. </v-col>
  434. <v-col cols="6" md="4">
  435. <router-link to="/college-group/online" class="img-info">
  436. <img
  437. src="@/assets/img/home/首頁元素-09.webp"
  438. alt="臺灣工藝學習平台"
  439. />
  440. <section>
  441. <p>{{ t("college_group_4") }}</p>
  442. </section>
  443. </router-link>
  444. </v-col>
  445. <v-col cols="6" md="4">
  446. <router-link to="/college-group/craft-for-all" class="img-info">
  447. <img
  448. src="@/assets/img/home/臺灣綠工藝希望工程.png"
  449. alt="臺灣工藝學習平台"
  450. />
  451. <section>
  452. <p>{{ t("college_group_5") }}</p>
  453. </section>
  454. </router-link>
  455. </v-col>
  456. <v-col cols="6" md="4">
  457. <router-link to="/college-group/life" class="img-info">
  458. <img
  459. src="@/assets/img/home/首頁元素-07.webp"
  460. alt="臺灣工藝學習平台"
  461. />
  462. <section>
  463. <p>{{ t("college_group_6") }}</p>
  464. </section>
  465. </router-link>
  466. </v-col>
  467. </v-row>
  468. <div class="map-block">
  469. <h3 class="mb-10 title">{{ t("home.title_3") }}</h3>
  470. <v-row class="justify-center align-center mb-3">
  471. <v-col cols="6" sm="3" md="2">
  472. <v-select
  473. v-model="selectedCounty"
  474. :label="t('cities')"
  475. :items="counties.list"
  476. density="compact"
  477. variant="outlined"
  478. hide-details
  479. clearable
  480. ></v-select>
  481. </v-col>
  482. <v-col cols="6" sm="3" md="2">
  483. <v-select
  484. v-model="selectedDistrict"
  485. :label="t('districts')"
  486. :items="district.list"
  487. density="compact"
  488. variant="outlined"
  489. hide-details
  490. clearable
  491. ></v-select>
  492. </v-col>
  493. <v-col cols="8" sm="4" md="5" lg="3">
  494. <v-text-field
  495. v-model="schoolKeyword"
  496. variant="outlined"
  497. density="compact"
  498. :placeholder="t('location_keywords')"
  499. hide-details
  500. ></v-text-field>
  501. </v-col>
  502. <v-col cols="4" sm="2" lg="2">
  503. <v-btn
  504. @click="searchSchool()"
  505. variant="flat"
  506. append-icon="mdi-magnify"
  507. color="purple"
  508. >
  509. {{ t("search") }}
  510. </v-btn>
  511. </v-col>
  512. </v-row>
  513. <div class="v-row">
  514. <v-col md="8" cols="12">
  515. <div class="map">
  516. <Map @locationId="getClassList" />
  517. </div>
  518. </v-col>
  519. <v-col md="4" cols="12" :class="{ 'd-none': store.isMobile }">
  520. <p v-if="!classes.data.length" class="text-center text-gray">
  521. 未找到符合條件的結果
  522. </p>
  523. <v-list v-else lines="three" class="list pa-0">
  524. <v-list-item v-for="item in classes.data" :key="item.id">
  525. <div class="d-flex align-center">
  526. <router-link
  527. :to="`/course-detail/${item.class_name_id}`"
  528. class="link"
  529. >
  530. <v-img
  531. :lazy-src="store.getImageSrc(item)"
  532. cover
  533. :src="store.getImageSrc(item)"
  534. alt="臺灣工藝學習平台"
  535. >
  536. <template v-slot:placeholder>
  537. <div
  538. class="d-flex align-center justify-center fill-height"
  539. >
  540. <v-progress-circular
  541. color="grey-lighten-4"
  542. indeterminate
  543. ></v-progress-circular>
  544. </div>
  545. </template>
  546. </v-img>
  547. </router-link>
  548. <section>
  549. <h2>{{ item.name }}</h2>
  550. <div class="d-flex align-start">
  551. <v-icon
  552. color="primary"
  553. icon="mdi-domain"
  554. class="me-2"
  555. ></v-icon>
  556. <p>
  557. {{
  558. item.group_id === 9
  559. ? item.location_name
  560. : item.organizer
  561. }}
  562. </p>
  563. </div>
  564. <div class="d-flex align-start">
  565. <v-icon
  566. color="primary"
  567. icon="mdi-map-marker"
  568. class="me-2 pt-1"
  569. ></v-icon>
  570. <p>{{ item.address }}</p>
  571. </div>
  572. </section>
  573. </div>
  574. </v-list-item>
  575. </v-list>
  576. <v-pagination
  577. v-if="classes.data.length"
  578. v-model="pageNum"
  579. :length="totalPages"
  580. class="my-4"
  581. rounded="circle"
  582. ></v-pagination>
  583. </v-col>
  584. </div>
  585. <!-- 手機版底部視窗 -->
  586. <div class="bottom-sheet" :class="{ show: isBottomSheetOpen }">
  587. <div class="content">
  588. <div @touchstart.prevent="handleTouch" class="btn">
  589. <span></span>
  590. </div>
  591. <div class="list">
  592. <v-list lines="three" class="list pa-0">
  593. <v-list-item v-for="item in classes.data" :key="item.id">
  594. <div class="d-flex align-center">
  595. <router-link
  596. :to="`/course-detail/${item.class_name_id}`"
  597. class="link"
  598. >
  599. <v-img
  600. :lazy-src="
  601. item.is_inner === 0
  602. ? item.cover_img
  603. : item.special_class_list_name === 'one_day_class'
  604. ? store.getImageUrl('default.webp')
  605. : `${store.imgUrl}/${item.cover_img}`
  606. "
  607. cover
  608. :src="
  609. item.is_inner === 0
  610. ? item.cover_img
  611. : item.special_class_list_name === 'one_day_class'
  612. ? store.getImageUrl('default.webp')
  613. : `${store.imgUrl}/${item.cover_img}`
  614. "
  615. alt="臺灣工藝學習平台"
  616. >
  617. <template v-slot:placeholder>
  618. <div
  619. class="d-flex align-center justify-center fill-height"
  620. >
  621. <v-progress-circular
  622. color="grey-lighten-4"
  623. indeterminate
  624. ></v-progress-circular>
  625. </div>
  626. </template>
  627. </v-img>
  628. </router-link>
  629. <section>
  630. <h2>{{ item.name }}</h2>
  631. <div class="d-flex align-start">
  632. <v-icon
  633. color="primary"
  634. icon="mdi-domain"
  635. class="me-2"
  636. ></v-icon>
  637. <p>{{ item.organizer }}</p>
  638. </div>
  639. <div class="d-flex align-start">
  640. <v-icon
  641. color="primary"
  642. icon="mdi-map-marker"
  643. class="me-2 pt-1"
  644. ></v-icon>
  645. <p>{{ item.location_name }}</p>
  646. </div>
  647. </section>
  648. </div>
  649. </v-list-item>
  650. </v-list>
  651. <v-pagination
  652. v-model="pageNum"
  653. :length="totalPages"
  654. class="my-4"
  655. rounded="circle"
  656. ></v-pagination>
  657. </div>
  658. </div>
  659. </div>
  660. <!-- <div class="map">
  661. <Map @locationId="getClassList" />
  662. </div> -->
  663. </div>
  664. <h2 class="mb-10 title">{{ t("home.title_4") }}</h2>
  665. <v-row class="recommend-block">
  666. <v-col
  667. cols="12"
  668. sm="6"
  669. lg="4"
  670. v-for="(item, index) in recommend.list"
  671. :key="index"
  672. class="pa-5"
  673. >
  674. <CourseCard :data="item" />
  675. </v-col>
  676. <v-col cols="12">
  677. <router-link to="/course-list" class="course-link">
  678. <img
  679. src="@/assets/img/course/探索課程素材-15.png"
  680. alt="臺灣工藝學習平台"
  681. />
  682. <p>{{ t("see_more") }}</p>
  683. </router-link>
  684. </v-col>
  685. </v-row>
  686. </v-container>
  687. <!-- <v-container fluid class="pa-0 pt-sm-16 tutorial-block">
  688. <h2 class="mb-10 title">{{ t("tutorial.title") }}</h2>
  689. <CoursesTutorial />
  690. </v-container> -->
  691. <v-container class="px-md-0 my-16">
  692. <h2 class="mb-10 title">{{ t("home.title_6") }}</h2>
  693. <v-expansion-panels v-model="panel" multiple>
  694. <v-expansion-panel elevation="0">
  695. <v-expansion-panel-title>{{
  696. t("home.faq.q_1")
  697. }}</v-expansion-panel-title>
  698. <v-expansion-panel-text>
  699. <ul>
  700. <li>
  701. <h4><span></span> {{ t("home.faq.a_1_1") }}</h4>
  702. <p>
  703. {{ t("home.faq.a_1_2") }}
  704. </p>
  705. </li>
  706. <li>
  707. <h4><span></span> {{ t("home.faq.a_2_1") }}</h4>
  708. <p>
  709. {{ t("home.faq.a_2_2") }}
  710. </p>
  711. </li>
  712. <li>
  713. <h4><span></span> {{ t("home.faq.a_3_1") }}</h4>
  714. <p>
  715. {{ t("home.faq.a_3_2") }}
  716. </p>
  717. </li>
  718. <li>
  719. <h4><span></span> {{ t("home.faq.a_4_1") }}</h4>
  720. <p>
  721. {{ t("home.faq.a_4_2") }}
  722. </p>
  723. </li>
  724. </ul>
  725. </v-expansion-panel-text>
  726. </v-expansion-panel>
  727. <v-expansion-panel elevation="0">
  728. <v-expansion-panel-title>
  729. {{ t("terms.terms_of_service") }}</v-expansion-panel-title
  730. >
  731. <v-expansion-panel-text>
  732. <TermsList />
  733. </v-expansion-panel-text>
  734. </v-expansion-panel>
  735. <v-expansion-panel elevation="0">
  736. <v-expansion-panel-title>{{
  737. t("home.faq.q_2")
  738. }}</v-expansion-panel-title>
  739. <v-expansion-panel-text>
  740. <p v-html="$t('home.faq.a_2')"></p>
  741. </v-expansion-panel-text>
  742. </v-expansion-panel>
  743. <v-expansion-panel elevation="0">
  744. <v-expansion-panel-title>{{
  745. t("home.faq.q_3")
  746. }}</v-expansion-panel-title>
  747. <v-expansion-panel-text>
  748. {{ t("home.faq.a_3") }}
  749. </v-expansion-panel-text>
  750. </v-expansion-panel>
  751. <v-expansion-panel elevation="0">
  752. <v-expansion-panel-title>{{
  753. t("home.faq.q_4")
  754. }}</v-expansion-panel-title>
  755. <v-expansion-panel-text>
  756. {{ t("home.faq.a_4") }}
  757. </v-expansion-panel-text>
  758. </v-expansion-panel>
  759. <v-expansion-panel elevation="0">
  760. <v-expansion-panel-title>{{
  761. t("home.faq.q_5")
  762. }}</v-expansion-panel-title>
  763. <v-expansion-panel-text>
  764. {{ t("home.faq.a_5") }}
  765. </v-expansion-panel-text>
  766. </v-expansion-panel>
  767. </v-expansion-panels>
  768. <h2 class="mb-10 mt-16 title">{{ t("crafts.title") }}</h2>
  769. <v-row class="px-5 px-sm-10 mt-16 mb-10 tag-btn">
  770. <v-col
  771. v-for="(item, index) in tagList"
  772. :key="index"
  773. :cols="index + 1 === tagList.length ? '12' : '6'"
  774. md=""
  775. >
  776. <a :href="item.url" target="_blank" class="py-3 py-lg-6 item">
  777. <!-- 網址 -->
  778. {{ t(`${item.title}`) }}
  779. </a>
  780. </v-col>
  781. </v-row>
  782. <CraftsArticle />
  783. <router-link to="/crafts" class="crafts-link">
  784. <img
  785. src="@/assets/img/course/探索課程素材-15.png"
  786. alt="臺灣工藝學習平台"
  787. />
  788. <p>{{ t("crafts.see_more") }}</p>
  789. </router-link>
  790. <div class="mt-16">
  791. <img
  792. class="d-none d-md-block"
  793. src="@/assets/img/course/banner.webp"
  794. alt="臺灣工藝學習平台"
  795. />
  796. <img
  797. class="d-block d-md-none"
  798. src="@/assets/img/course/banner-mb.webp"
  799. alt="臺灣工藝學習平台"
  800. />
  801. </div>
  802. </v-container>
  803. </template>
  804. <style lang="scss" scoped>
  805. .banner {
  806. display: flex;
  807. align-items: center;
  808. justify-content: center;
  809. position: relative;
  810. opacity: 1;
  811. transition: all 0.3s;
  812. .cover {
  813. width: 100vw;
  814. height: 100vh;
  815. object-fit: cover;
  816. }
  817. .logo {
  818. max-width: 600px;
  819. position: absolute;
  820. z-index: 10;
  821. }
  822. &.close {
  823. opacity: 0;
  824. position: absolute;
  825. }
  826. }
  827. .intro {
  828. margin: 120px 0 6.25em;
  829. p {
  830. line-height: 32px;
  831. }
  832. }
  833. .title {
  834. font-size: 1.625em;
  835. font-weight: 400;
  836. letter-spacing: 0.125em;
  837. text-align: center;
  838. }
  839. .navbar {
  840. margin-top: 40px;
  841. margin-bottom: 0;
  842. position: sticky;
  843. top: 0;
  844. z-index: 2000;
  845. background: #fff;
  846. &::before {
  847. top: unset;
  848. left: unset;
  849. width: auto;
  850. height: 100%;
  851. }
  852. }
  853. .img-info {
  854. display: block;
  855. position: relative;
  856. section {
  857. width: 100%;
  858. height: 100%;
  859. position: absolute;
  860. top: 0;
  861. display: flex;
  862. align-items: center;
  863. justify-content: center;
  864. background: rgba(108, 149, 163, 0.5);
  865. // background: rgba(167, 193, 204, 0.7);
  866. cursor: pointer;
  867. opacity: 1;
  868. transition: all 0.5s;
  869. &:hover {
  870. // opacity: 1;
  871. p {
  872. font-size: 1.4em;
  873. }
  874. }
  875. p {
  876. color: #fff;
  877. font-size: 1.25em;
  878. letter-spacing: 0.0625em;
  879. transition: all 0.3s;
  880. }
  881. }
  882. }
  883. .news-content {
  884. margin: 6.25em auto;
  885. }
  886. .map-block {
  887. margin: 6.25em auto;
  888. .map {
  889. height: 100%;
  890. min-height: 100vh;
  891. }
  892. .list {
  893. .link {
  894. width: 40%;
  895. margin-right: 1.25em;
  896. border-radius: 0.3125em;
  897. overflow: hidden;
  898. @media (max-width: 960px) {
  899. width: auto;
  900. }
  901. .v-img {
  902. width: 200px;
  903. height: 9.375em;
  904. margin-right: 1.25em;
  905. border-radius: 0.3125em;
  906. box-shadow: 0.125em 0.125em 4px #aaaaaa;
  907. transition: all 0.3s;
  908. object-fit: cover;
  909. @media (max-width: 960px) {
  910. width: 250px;
  911. height: 170px;
  912. margin-right: 0;
  913. }
  914. @media (max-width: 575px) {
  915. width: 9.375em;
  916. height: 70px;
  917. }
  918. &:hover {
  919. transform: scale(1.1);
  920. }
  921. }
  922. }
  923. section {
  924. width: 60%;
  925. h2 {
  926. margin-bottom: 0.625em;
  927. font-size: 1em;
  928. font-weight: 500;
  929. }
  930. p {
  931. font-size: 0.875em;
  932. font-weight: 400;
  933. }
  934. h2,
  935. p {
  936. line-height: 1.625em;
  937. letter-spacing: 0.0625em;
  938. // 超過兩行則省略
  939. overflow: hidden;
  940. text-overflow: ellipsis;
  941. display: -webkit-box;
  942. -webkit-line-clamp: 2;
  943. -webkit-box-orient: vertical;
  944. line-break: after-white-space;
  945. }
  946. }
  947. .v-list-item {
  948. height: 215px;
  949. padding: 1.25em 0;
  950. border-bottom: 0.0625em solid #cccccc;
  951. @media (max-width: 575px) {
  952. height: auto;
  953. }
  954. &:last-child {
  955. border-bottom: none;
  956. }
  957. .v-list-item__content {
  958. overflow: initial !important;
  959. }
  960. }
  961. }
  962. }
  963. .course-link {
  964. margin-top: 0.9375em;
  965. color: #000;
  966. background-color: var(--sub-color);
  967. }
  968. .crafts-link {
  969. margin: 6.25em 0;
  970. color: #fff;
  971. background-color: var(--purple);
  972. img {
  973. filter: invert(88%) sepia(100%) saturate(0%) hue-rotate(151deg)
  974. brightness(103%) contrast(103%);
  975. }
  976. }
  977. .crafts-link,
  978. .course-link {
  979. padding: 0.9375em;
  980. display: flex;
  981. align-items: center;
  982. justify-content: center;
  983. border-radius: 0.625em;
  984. box-shadow: 0.125em 0.125em 0.625em #aaaaaa;
  985. &:hover {
  986. img {
  987. transform: rotateZ(10deg);
  988. }
  989. }
  990. img {
  991. max-width: 85px;
  992. padding-right: 1.25em;
  993. transition: all 0.3s;
  994. }
  995. p {
  996. font-size: 1.25em;
  997. letter-spacing: 0.125em;
  998. }
  999. }
  1000. .tutorial-block {
  1001. max-width: 100% !important;
  1002. }
  1003. .loading-item {
  1004. position: sticky;
  1005. top: 0;
  1006. left: 0;
  1007. right: 0;
  1008. height: 100vh;
  1009. z-index: 3000;
  1010. display: flex;
  1011. align-items: center;
  1012. justify-content: center;
  1013. background: #fff;
  1014. }
  1015. /* 底部彈出視窗的樣式 */
  1016. .bottom-sheet {
  1017. display: none;
  1018. // display: flex;
  1019. justify-content: center;
  1020. align-items: center;
  1021. position: fixed;
  1022. bottom: 2.1875em;
  1023. left: 0;
  1024. width: 100%;
  1025. height: 300px; /* 設置底部彈出視窗的高度 */
  1026. z-index: 1000;
  1027. background-color: #fff;
  1028. box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.1);
  1029. transition: transform 0.3s ease-in-out;
  1030. transform: translateY(100%); /* 預設隱藏底部視窗 */
  1031. &.show {
  1032. bottom: 0;
  1033. transform: translateY(0);
  1034. }
  1035. .content {
  1036. height: 100%;
  1037. display: flex;
  1038. position: relative;
  1039. flex-direction: column;
  1040. align-items: center;
  1041. text-align: center;
  1042. .btn {
  1043. width: 100%;
  1044. padding: 1.25em;
  1045. display: flex;
  1046. justify-content: center;
  1047. span {
  1048. display: block;
  1049. height: 0.3125em;
  1050. width: 3.75em;
  1051. position: absolute;
  1052. top: 0.9375em;
  1053. z-index: 1000;
  1054. background: #bbbbbb;
  1055. border-radius: 1.25em;
  1056. }
  1057. }
  1058. .v-pagination {
  1059. width: 200px;
  1060. }
  1061. .list {
  1062. margin: 0 0.625em 1.25em;
  1063. overflow-y: auto;
  1064. section {
  1065. text-align: start;
  1066. }
  1067. }
  1068. }
  1069. }
  1070. .close-btn {
  1071. position: absolute;
  1072. right: 15px;
  1073. top: 5px;
  1074. .v-icon {
  1075. color: #fff;
  1076. font-size: 40px;
  1077. text-shadow: 1px 1px 3px #333;
  1078. }
  1079. &.close {
  1080. display: none;
  1081. }
  1082. }
  1083. // Vuetify Expansion 樣式
  1084. .v-expansion-panel {
  1085. margin-bottom: 1.875em;
  1086. line-height: 1.875em;
  1087. border-radius: 0.625em !important;
  1088. li:first-child {
  1089. h4 {
  1090. margin-top: 0;
  1091. }
  1092. }
  1093. h4 {
  1094. display: flex;
  1095. align-items: center;
  1096. margin-top: 1.25em;
  1097. font-size: 1.125em;
  1098. font-weight: 500;
  1099. span {
  1100. display: block;
  1101. width: 8px;
  1102. height: 8px;
  1103. margin-top: 0.3125em;
  1104. margin-right: 0.625em;
  1105. border-radius: 0.625em;
  1106. background-color: var(--blue);
  1107. }
  1108. }
  1109. }
  1110. .v-expansion-panel-title {
  1111. padding: 1.25em 1.875em;
  1112. font-size: 1em;
  1113. line-height: 1.625em;
  1114. letter-spacing: 0.0625em;
  1115. border: 0.0625em solid var(--blue);
  1116. }
  1117. .v-expansion-panel-text {
  1118. padding-top: 8px;
  1119. }
  1120. .v-expansion-panel-title__overlay {
  1121. opacity: 0 !important;
  1122. }
  1123. .v-expansion-panels:not(.v-expansion-panels--variant-accordion)
  1124. > :first-child:not(:last-child):not(.v-expansion-panel--active):not(
  1125. .v-expansion-panel--before-active
  1126. ),
  1127. .v-expansion-panels:not(.v-expansion-panels--variant-accordion)
  1128. > :last-child:not(:first-child):not(.v-expansion-panel--active):not(
  1129. .v-expansion-panel--after-active
  1130. ),
  1131. .v-expansion-panels:not(.v-expansion-panels--variant-accordion)
  1132. > :not(:first-child):not(:last-child):not(.v-expansion-panel--active):not(
  1133. .v-expansion-panel--after-active
  1134. ),
  1135. .v-expansion-panels:not(.v-expansion-panels--variant-accordion)
  1136. > :not(:first-child):not(:last-child):not(.v-expansion-panel--active):not(
  1137. .v-expansion-panel--before-active
  1138. ),
  1139. .v-expansion-panel--active > .v-expansion-panel-title {
  1140. border-top-left-radius: 0.625em !important;
  1141. border-top-right-radius: 0.625em !important;
  1142. border-bottom-left-radius: 0.625em !important;
  1143. border-bottom-right-radius: 0.625em !important;
  1144. }
  1145. .v-expansion-panel:not(:first-child)::after {
  1146. border-top-style: none;
  1147. }
  1148. .v-expansion-panel-title--active:hover > .v-expansion-panel-title__overlay,
  1149. .v-expansion-panel-title[aria-haspopup="menu"][aria-expanded="true"]:hover
  1150. > .v-expansion-panel-title__overlay {
  1151. opacity: 0;
  1152. }
  1153. </style>