CourseDetail.vue 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066
  1. <script setup>
  2. import { ref, reactive, computed, onMounted, watch } from "vue";
  3. import { useMainStore } from "@/stores/store";
  4. import { useRoute } from "vue-router";
  5. import { useI18n } from "vue-i18n";
  6. import VueDatePicker from "@vuepic/vue-datepicker";
  7. import "@vuepic/vue-datepicker/dist/main.css";
  8. import axios from "axios";
  9. import moment from "moment";
  10. import Navbar from "@/components/Navbar.vue";
  11. import CourseCard from "@/components/CourseCard.vue";
  12. const { t } = useI18n();
  13. const route = useRoute();
  14. const store = useMainStore();
  15. const courseId = route.params.id; // 網址參數
  16. onMounted(() => {
  17. store.getFavoriteClass();
  18. });
  19. // signUpDialog
  20. let step = ref(1);
  21. // let signUpDialog = ref(false);
  22. let signUpDialog = reactive([]);
  23. let checkConsent = ref(false);
  24. watch(signUpDialog, (val) => {
  25. console.log("signUpDialog", val);
  26. });
  27. let groupName = ref("");
  28. let course = reactive({
  29. data: [],
  30. });
  31. let isInner = ref("");
  32. let isLoading = ref(false);
  33. let isSignUpLoading = ref(false);
  34. const breadcrumbs = reactive([
  35. {
  36. title: "home.title",
  37. disabled: false,
  38. href: "/",
  39. },
  40. {
  41. title: "navbar.craft_course",
  42. disabled: false,
  43. href: "/course-list",
  44. },
  45. {
  46. title: "course_list",
  47. disabled: true,
  48. },
  49. ]);
  50. let courseNameId = ref(null);
  51. let courseLoading = ref(false);
  52. // 取得資料
  53. (async () => {
  54. courseLoading.value = true;
  55. try {
  56. const response = await axios.get(
  57. `${store.apiUrl}/api/get_class_name?class_name_id=${courseId}`
  58. );
  59. course.data = response.data.classes[0];
  60. isInner.value = course.data.is_inner;
  61. console.log("courseData", course.data);
  62. courseNameId.value = course.data.class_name_id;
  63. console.log(" courseNameId.value", courseNameId.value);
  64. getOtherClass();
  65. if (course.data.group_id === 1) {
  66. groupName.value = "未來工藝學群";
  67. } else if (course.data.group_id === 2) {
  68. groupName.value = "技藝工藝學群";
  69. } else if (course.data.group_id === 3) {
  70. groupName.value = "生活工藝學群";
  71. } else if (course.data.group_id === 4) {
  72. groupName.value = "青年工藝學群";
  73. } else if (course.data.group_id === 5) {
  74. groupName.value = "世代工藝學群";
  75. } else if (course.data.group_id === 6) {
  76. groupName.value = "修護工藝學群";
  77. } else if (course.data.group_id === 7) {
  78. groupName.value = "跨域工藝學群";
  79. } else if (course.data.group_id === 8) {
  80. groupName.value = "線上工藝學群";
  81. } else if (course.data.group_id === 9) {
  82. groupName.value = "綠工藝希望工程";
  83. }
  84. setTimeout(() => {
  85. courseLoading.value = false;
  86. }, 500);
  87. } catch (error) {
  88. console.error(error);
  89. }
  90. })();
  91. let session = reactive({
  92. data: [],
  93. });
  94. // 取得場次
  95. (async () => {
  96. try {
  97. const response = await axios.get(
  98. `${store.apiUrl}/api/get_event?class_name_id=${courseId}`
  99. );
  100. session.data = response.data.classes;
  101. console.log("session.data", session.data);
  102. console.log("response", response.data.classes[0]);
  103. } catch (error) {
  104. console.error(error);
  105. }
  106. })();
  107. let other = reactive({
  108. classes: [],
  109. });
  110. // 取得清單
  111. async function getOtherClass() {
  112. isLoading.value = true;
  113. try {
  114. const response = await axios.get(
  115. `${store.apiUrl}/api/get_class_name?is_check=1&category=${course.data.category}&page_num=1&page_amount=4`
  116. );
  117. console.log("其他 response", response);
  118. other.classes = response.data.classes.filter(
  119. (item) => item.class_name_id !== courseNameId.value
  120. ); // 移除當前課程
  121. other.classes = other.classes.slice(0, 3); // 只保留陣列前三筆
  122. isLoading.value = false;
  123. } catch (error) {
  124. console.error(error);
  125. }
  126. }
  127. const dynamicCols = computed(() => {
  128. return isInner.value === 0 ? "5" : "8";
  129. });
  130. let user = reactive({
  131. name: "",
  132. user_name: "",
  133. birthday: "",
  134. gender: "",
  135. phone: "",
  136. address: "",
  137. });
  138. function closeSignUpDialog() {
  139. for (let index = 0; index < signUpDialog.length; index++) {
  140. signUpDialog[index] = false;
  141. }
  142. checkConsent.value = false;
  143. step.value = 1;
  144. }
  145. // 取得報名資料
  146. async function signUp(index) {
  147. let isLogin = store.checkToken();
  148. let token = store.token;
  149. console.log("isLogin", isLogin);
  150. console.log("token", token);
  151. if (isLogin && token) {
  152. signUpDialog[index] = true;
  153. let response = await axios.get(
  154. `${store.apiUrl}/api/get_user_information?get_all=0&get_detail_information=1&access_token=${token}`
  155. );
  156. console.log("response.data user_inform", response.data);
  157. Object.keys(response.data.user_inform[0]).forEach((key) => {
  158. if (user[key] !== undefined) {
  159. user[key] = response.data.user_inform[0][key];
  160. }
  161. });
  162. // 判斷手機格式
  163. const phoneSplit = response.data.user_inform[0].phone.split(" ");
  164. if (phoneSplit.length === 1) {
  165. user.phone = phoneSplit[0];
  166. } else if (phoneSplit.length === 2) {
  167. user.phone = phoneSplit[1];
  168. }
  169. console.log("response", response);
  170. } else {
  171. signUpDialog[index] = false;
  172. alert("請先登入會員!");
  173. return;
  174. // store.openLoginDialog();
  175. }
  176. }
  177. let birthdayError = ref(false);
  178. let resultCard = reactive({
  179. text: "",
  180. icon: "",
  181. color: "",
  182. });
  183. // 確認報名
  184. async function signUpSubmit(id) {
  185. let token = store.token;
  186. isSignUpLoading.value = true;
  187. if (!user.birthday || user.birthday === "") {
  188. birthdayError.value = true;
  189. } else {
  190. birthdayError.value = false;
  191. }
  192. // 檢查欄位是否有空值
  193. for (const value of Object.values(user)) {
  194. if (!value) {
  195. isSignUpLoading.value = false;
  196. return;
  197. }
  198. }
  199. console.log("token", token);
  200. const formData = new FormData();
  201. formData.append("event_id", id);
  202. try {
  203. const response = await axios.post(
  204. `${store.apiUrl}/api/input_registration?access_token=${token}`,
  205. formData
  206. );
  207. console.log("確認報名", response);
  208. console.log("response.data.msg", response.data.msg);
  209. isSignUpLoading.value = false;
  210. if (response.data.msg === "success") {
  211. resultCard.text = "報名成功!";
  212. resultCard.icon = "mdi-check-circle";
  213. resultCard.color = "success";
  214. step.value++;
  215. } else if (response.data.msg === "已經有報名過了") {
  216. console.log("已經有報名過了");
  217. resultCard.text = "已報名過此課程";
  218. resultCard.icon = "mdi-information";
  219. resultCard.color = "info";
  220. step.value++;
  221. } else {
  222. resultCard.text = response.data.msg;
  223. resultCard.icon = "mdi-information";
  224. resultCard.color = "info";
  225. step.value++;
  226. }
  227. isSignUpLoading.value = false;
  228. console.log("isSignUpLoading", isSignUpLoading.value);
  229. } catch (error) {
  230. console.error(error);
  231. }
  232. }
  233. // 開啟登入視窗
  234. // function openLoginDialog() {
  235. // store.loginDialog = true;
  236. // }
  237. let currentTitle = computed(() => {
  238. switch (step.value) {
  239. case 1:
  240. return "form.registration_consent";
  241. case 2:
  242. return "form.course_registration";
  243. default:
  244. return "";
  245. }
  246. });
  247. // 判斷報名時間是否已截止
  248. function isDateExpired(dateString) {
  249. const registrationEndDate = new Date(dateString);
  250. // 獲取當前時間
  251. const currentDate = new Date();
  252. console.log("registrationEndDate", registrationEndDate);
  253. console.log("currentDate", currentDate);
  254. if (registrationEndDate > currentDate) {
  255. return true; // 日期已過期
  256. } else {
  257. return false; // 日期未過期
  258. }
  259. }
  260. </script>
  261. <template>
  262. <Navbar />
  263. <v-container class="mt-16 course-detail">
  264. <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-3 mb-16 pa-0">
  265. <template v-slot:title="{ item }">
  266. {{ t(item.title) }}
  267. </template>
  268. </v-breadcrumbs>
  269. <Transition v-if="courseLoading">
  270. <div class="d-flex justify-center h-screen">
  271. <v-progress-circular
  272. color="grey-lighten-4"
  273. indeterminate
  274. ></v-progress-circular>
  275. </div>
  276. </Transition>
  277. <Transition v-else>
  278. <v-row class="justify-center">
  279. <v-col cols="12" md="3" class="title pa-0">
  280. <img
  281. src="@/assets/img/course/detail-background.png"
  282. alt="臺灣工藝學習平台"
  283. class="bg-img"
  284. />
  285. <h2 class="mb-5 mb-md-0 me-md-5">{{ course.data.name }}</h2>
  286. </v-col>
  287. <v-col cols="12" :md="dynamicCols" class="pa-0 d-flex justify-center">
  288. <v-img
  289. class="cover-img"
  290. :class="{ small: isInner === 0 }"
  291. :lazy-src="store.getImageSrc(course.data)"
  292. :src="store.getImageSrc(course.data)"
  293. :alt="course.data.name"
  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. <!-- <img
  305. :src="store.getImageSrc(course.data)"
  306. alt="臺灣工藝學習平台"
  307. class="cover-img"
  308. :class="{ small: isInner === 0 }"
  309. /> -->
  310. </v-col>
  311. <v-col cols="12">
  312. <div class="info">
  313. <table class="data-table">
  314. <thead>
  315. <tr>
  316. <th colspan="2">{{ t("course_info") }}</th>
  317. </tr>
  318. </thead>
  319. <tbody>
  320. <tr>
  321. <td>{{ t("form.course_name") }}</td>
  322. <td>{{ course.data.name }}</td>
  323. </tr>
  324. <tr>
  325. <td>{{ t("form.course_intro") }}</td>
  326. <td
  327. v-html="store.formatString(course.data.introduction)"
  328. ></td>
  329. </tr>
  330. <tr v-show="course.data.category !== ''">
  331. <td>{{ t("form.craft_category") }}</td>
  332. <td>
  333. {{
  334. course.data.group_id === 9
  335. ? course.data.group_sort
  336. : course.data.category
  337. }}
  338. </td>
  339. </tr>
  340. <tr>
  341. <td>{{ t("form.course_location") }}</td>
  342. <td>{{ course.data.address }}</td>
  343. </tr>
  344. <tr v-if="course.data.organizer !== ''">
  345. <td>{{ t("form.organizer") }}</td>
  346. <td>
  347. {{
  348. course.data.group_id === 9
  349. ? course.data.location_name
  350. : course.data.organizer
  351. }}
  352. </td>
  353. </tr>
  354. <!-- <tr>
  355. <td>課程地點</td>
  356. <td>{{ course.data.address }}</td>
  357. </tr> -->
  358. <tr>
  359. <td>{{ t("form.all_study_groups") }}</td>
  360. <td>{{ groupName }}</td>
  361. </tr>
  362. <!-- <tr>
  363. <td>報名對象</td>
  364. <td></td>
  365. </tr> -->
  366. <!-- <tr>
  367. <td>收費方式</td>
  368. <td></td>
  369. </tr> -->
  370. <!-- <tr>
  371. <td>報名方式</td>
  372. <td></td>
  373. </tr> -->
  374. <tr
  375. v-if="
  376. isInner !== 0 &&
  377. session.data.length &&
  378. session.data[0]?.contact !== '' &&
  379. course.data.group_id !== 9
  380. "
  381. >
  382. <td>{{ t("form.contact_information") }}</td>
  383. <td>{{ session.data[0]?.contact }}</td>
  384. </tr>
  385. <tr
  386. v-if="
  387. isInner !== 0 &&
  388. session.data.length &&
  389. session.data[0]?.fee_payment &&
  390. session.data[0]?.fee_payment !== null
  391. "
  392. >
  393. <td>{{ t("form.payment_method") }}</td>
  394. <td>
  395. {{ session.data[0]?.fee_payment }}
  396. <span
  397. v-if="session.data[0]?.fee_payment === '匯款'"
  398. class="text-red"
  399. >
  400. (匯款後請主動來電告知姓名與帳號後五碼)
  401. </span>
  402. </td>
  403. </tr>
  404. <tr v-if="isInner === 0">
  405. <td>{{ t("form.remarks") }}</td>
  406. <td>此課程不會登錄至學習護照中</td>
  407. </tr>
  408. </tbody>
  409. </table>
  410. <div class="d-flex justify-end">
  411. <v-btn v-if="isInner === 0" rounded="xl" color="brown">
  412. <a :href="session.data[0]?.URL" target="_blank"
  413. >觀看課程資訊與報名</a
  414. >
  415. </v-btn>
  416. <!-- <v-btn v-else rounded="xl" color="brown">附件下載</v-btn> -->
  417. </div>
  418. </div>
  419. </v-col>
  420. <v-col
  421. cols="12"
  422. v-if="isInner !== 0 && session.data.length"
  423. :class="{
  424. hide: course.data.special_class_list_name === 'one_day_class',
  425. }"
  426. >
  427. <div class="sessions mb-16">
  428. <table>
  429. <thead>
  430. <tr>
  431. <th>{{ t("form.session_name") }}</th>
  432. <th>{{ t("form.start_time") }} / {{ t("form.end_time") }}</th>
  433. <th v-show="session.data[0].location">
  434. {{ t("form.classroom") }}
  435. </th>
  436. <th>{{ t("form.course_price") }}</th>
  437. <th>{{ t("form.course_quota") }}</th>
  438. <th>{{ t("form.instructor") }}</th>
  439. <th>{{ t("form.registration_deadline") }}</th>
  440. <th></th>
  441. </tr>
  442. </thead>
  443. <tbody>
  444. <tr v-for="(item, index) in session.data" :key="index">
  445. <td width="20%">{{ item.class_name }}</td>
  446. <td width="20%">
  447. {{
  448. moment(`${item.start_time}`)
  449. .utcOffset(0)
  450. .format("YYYY/MM/DD HH:mm")
  451. }}
  452. <br />
  453. <br />
  454. {{
  455. moment(`${item.end_time}`)
  456. .utcOffset(0)
  457. .format("YYYY/MM/DD HH:mm")
  458. }}
  459. </td>
  460. <td width="15%" v-show="item.location !== ''">
  461. {{ item.location }}
  462. </td>
  463. <td width="20%">{{ item.fee_method }}</td>
  464. <td width="10%">{{ item.number_limit }}</td>
  465. <td width="15%">{{ item.lecturer }}</td>
  466. <td>
  467. <span
  468. v-if="
  469. item.registration_end && item.registration_end !== ''
  470. "
  471. >
  472. {{
  473. moment(`${item.registration_end}`)
  474. .utcOffset(0)
  475. .format("YYYY/MM/DD HH:mm")
  476. }}
  477. </span>
  478. <span v-else></span>
  479. </td>
  480. <td>
  481. <v-btn v-if="isInner === 0" rounded="xl" color="brown">
  482. <a :href="item.URL" target="_blank">{{ t("sign_up") }}</a>
  483. </v-btn>
  484. <v-btn
  485. v-if="isDateExpired(item.registration_end)"
  486. @click="signUp(index)"
  487. :text="t('sign_up')"
  488. rounded="xl"
  489. color="brown"
  490. >
  491. </v-btn>
  492. <v-btn
  493. v-else
  494. :text="t('registration_closed')"
  495. rounded="xl"
  496. color="brown"
  497. disabled
  498. >
  499. </v-btn>
  500. <v-dialog
  501. v-model="signUpDialog[index]"
  502. width="auto"
  503. class="pa-0 signup-dialog"
  504. scrollable
  505. persistent
  506. >
  507. <template v-slot:default="{ isActive }">
  508. <v-card class="mx-auto pa-3" max-width="1000">
  509. <v-card-title
  510. class="text-h6 font-weight-regular justify-space-between"
  511. >
  512. <v-avatar
  513. v-if="step !== 3"
  514. color="primary"
  515. size="26"
  516. v-text="step"
  517. class="me-3"
  518. ></v-avatar>
  519. <span>{{ t(`${currentTitle}`) }}</span>
  520. </v-card-title>
  521. <v-window v-model="step" :touch="false">
  522. <v-window-item :value="1">
  523. <v-card-text>
  524. <section class="consent">
  525. <h4 class="text-center">
  526. 個人資料使用同意書
  527. </h4>
  528. <p>
  529. 臺灣工藝學習平台〈以下簡稱工藝中心〉因辦理
  530. <span class="font-weight-medium"
  531. >{{ item.class_name }}</span
  532. >
  533. 業務而獲取您的個人資料:姓名、連絡方式〈包括但不限於電話號碼、E-MAIL、居住或工作地址〉等得以直接或間接識別您個人之資料。若您所提供之個人資料,經檢舉或工藝中心發現不足以確認您的身分真實性或其他個人資料冒用、盜用、資料不實等情形,工藝中心有權暫時停止提供對您的服務。
  534. </p>
  535. <p>
  536. 工藝中心將基於個人資料保護法及相關法令之規定下,依隱私權保護政策,蒐集、處理及利用您的個人資料。您同意工藝中心以您所提供的個人資料確認您的身份、與您進行連絡、提供您相關服務及資訊,以及其他隱私權保護政策規範之使用方式。工藝中心針對您的個人資料利用之期間,自您簽署同意起至您請求刪除個資為止。
  537. </p>
  538. <p>
  539. 您可依個人資料保護法,就您的個人資料向工藝中心進行查詢或請求閱覽、請求給複製本、請求補充或更正、請求停止蒐集及處理與利用、請求刪除。但因您行使上述權利而導致工藝中心相關業務對您的權益產生減損時,工藝中心不負相關賠償責任。
  540. </p>
  541. </section>
  542. <v-checkbox
  543. v-model="checkConsent"
  544. label="我已完全閱讀並同意以上內容"
  545. color="brown"
  546. ></v-checkbox>
  547. </v-card-text>
  548. </v-window-item>
  549. <v-window-item :value="2">
  550. <v-card-text class="pa-0">
  551. <v-sheet class="mx-auto user-form">
  552. <v-form @submit.prevent>
  553. <h2
  554. class="text-center font-weight-medium mb-8"
  555. >
  556. {{ item.class_name }}
  557. </h2>
  558. <v-label class="d-block">
  559. <p class="d-flex mb-5">
  560. {{ t("profile.full_name") }}
  561. <span class="mark">*</span>
  562. </p>
  563. <v-text-field
  564. v-model="user.name"
  565. :rules="[
  566. (v) => !!v || '請輸入您的真實姓名',
  567. ]"
  568. density="compact"
  569. variant="outlined"
  570. ></v-text-field>
  571. </v-label>
  572. <v-label class="mt-1">
  573. <p class="d-flex mb-5">
  574. {{ t("profile.birthday") }}
  575. <span class="mark">*</span>
  576. </p>
  577. </v-label>
  578. <VueDatePicker
  579. v-model="user.birthday"
  580. :format="store.datePickerFormat"
  581. :enable-time-picker="false"
  582. :max-date="new Date()"
  583. locale="cn"
  584. ></VueDatePicker>
  585. <span
  586. v-if="birthdayError"
  587. class="birthday-error"
  588. >請選擇您的生日</span
  589. >
  590. <v-label class="d-block mt-5">
  591. <p class="d-flex mb-5">
  592. {{ t("profile.phoneNumber") }}
  593. <span class="mark">*</span>
  594. </p>
  595. <v-text-field
  596. v-model="user.phone"
  597. :rules="[
  598. (v) => !!v || '請輸入您的手機號碼',
  599. ]"
  600. density="compact"
  601. variant="outlined"
  602. ></v-text-field>
  603. </v-label>
  604. <v-label class="d-block mt-1">
  605. <p class="d-flex mb-3">
  606. {{ t("profile.gender") }}
  607. <span class="mark">*</span>
  608. </p>
  609. <v-radio-group
  610. v-model="user.gender"
  611. inline
  612. >
  613. <v-radio
  614. label="男"
  615. value="男"
  616. color="brown"
  617. ></v-radio>
  618. <v-radio
  619. label="女"
  620. value="女"
  621. class="mx-2"
  622. color="brown"
  623. ></v-radio>
  624. <v-radio
  625. label="多元"
  626. value="多元"
  627. color="brown"
  628. ></v-radio>
  629. </v-radio-group>
  630. </v-label>
  631. <v-label class="d-block">
  632. <p class="d-flex mb-5">
  633. {{ t("profile.address") }}
  634. <span class="mark">*</span>
  635. </p>
  636. <v-text-field
  637. v-model="user.address"
  638. :rules="[
  639. (v) => !!v || '請輸入您的地址',
  640. ]"
  641. density="compact"
  642. variant="outlined"
  643. ></v-text-field>
  644. </v-label>
  645. <v-label class="d-block">
  646. <p class="d-flex mb-5">
  647. {{ t("form.remarks") }}
  648. </p>
  649. <v-textarea
  650. placeholder="如有特殊需求或有需攜伴,請在備註欄告知老師"
  651. variant="outlined"
  652. rows="2"
  653. ></v-textarea>
  654. </v-label>
  655. <v-row class="mt-3">
  656. <v-col cols="6">
  657. <v-btn
  658. rounded
  659. class="w-100"
  660. variant="outlined"
  661. color="brown"
  662. @click="
  663. isActive.value = false;
  664. step = 1;
  665. "
  666. >{{ t("form.cancel") }}</v-btn
  667. >
  668. </v-col>
  669. <v-col cols="6">
  670. <v-btn
  671. rounded
  672. type="submit"
  673. class="w-100"
  674. variant="flat"
  675. color="brown"
  676. :loading="isSignUpLoading"
  677. @click="signUpSubmit(item.event_id)"
  678. >{{ t("sign_up") }}</v-btn
  679. >
  680. </v-col>
  681. </v-row>
  682. </v-form>
  683. </v-sheet>
  684. </v-card-text>
  685. </v-window-item>
  686. <v-window-item :value="3">
  687. <v-card-text class="px-0 px-sm-2">
  688. <v-sheet
  689. width="300"
  690. class="text-center mx-auto result-card"
  691. >
  692. <v-icon
  693. class="mb-5"
  694. :color="resultCard.color"
  695. :icon="resultCard.icon"
  696. size="112"
  697. ></v-icon>
  698. <h2 class="text-h5 mb-6">
  699. {{ resultCard.text }}
  700. </h2>
  701. <v-divider class="mb-7"></v-divider>
  702. <v-btn
  703. class="text-none me-3"
  704. rounded
  705. width="90"
  706. color="brown"
  707. variant="outlined"
  708. @click="
  709. isActive.value = false;
  710. step = 1;
  711. "
  712. >
  713. {{ t("form.close") }}
  714. </v-btn>
  715. <v-btn
  716. color="brown"
  717. class="text-none"
  718. rounded
  719. @click="isActive.value = false"
  720. >
  721. <router-link to="/user/passport"
  722. >前往會員專區</router-link
  723. >
  724. </v-btn>
  725. </v-sheet>
  726. </v-card-text>
  727. </v-window-item>
  728. </v-window>
  729. <v-divider v-if="step === 1"></v-divider>
  730. <v-card-actions v-if="step === 1" class="mt-2">
  731. <!-- <v-btn v-if="step > 1" variant="text" @click="step--">
  732. 上一步
  733. </v-btn> -->
  734. <v-spacer></v-spacer>
  735. <v-btn
  736. class="me-2 px-5"
  737. color="brown"
  738. variant="outlined"
  739. @click="isActive.value = false"
  740. >
  741. {{ t("disagree") }}
  742. </v-btn>
  743. <v-btn
  744. class="px-5"
  745. color="brown"
  746. variant="flat"
  747. :disabled="!checkConsent"
  748. @click="step++"
  749. >
  750. {{ t("agree") }}
  751. </v-btn>
  752. </v-card-actions>
  753. </v-card>
  754. </template>
  755. </v-dialog>
  756. </td>
  757. </tr>
  758. </tbody>
  759. </table>
  760. </div>
  761. </v-col>
  762. <v-col v-if="other.classes.length" cols="12" class="mb-16">
  763. <p class="other-title">{{ t("recommended_courses") }}</p>
  764. <div class="d-flex justify-center mb-10" v-if="isLoading">
  765. <v-progress-circular
  766. color="grey-lighten-4"
  767. indeterminate
  768. ></v-progress-circular>
  769. </div>
  770. <v-row>
  771. <v-col
  772. sm="6"
  773. md="4"
  774. cols="12"
  775. v-for="(item, index) in other.classes"
  776. :key="index"
  777. class="pa-5"
  778. >
  779. <CourseCard :data="item" />
  780. </v-col>
  781. </v-row>
  782. </v-col>
  783. </v-row>
  784. </Transition>
  785. </v-container>
  786. </template>
  787. <style lang="scss">
  788. .v-window {
  789. overflow-y: auto;
  790. }
  791. .hide {
  792. display: none !important;
  793. }
  794. .course-detail {
  795. .cover-img {
  796. .v-responsive__sizer {
  797. padding-bottom: 0 !important;
  798. }
  799. img {
  800. max-height: 25em;
  801. object-fit: contain;
  802. &.small {
  803. width: auto;
  804. height: auto;
  805. }
  806. }
  807. }
  808. .title {
  809. position: relative;
  810. display: flex;
  811. flex-direction: column;
  812. align-items: center;
  813. justify-content: center;
  814. h2 {
  815. font-size: 1.5em;
  816. font-weight: 500;
  817. line-height: 2em;
  818. letter-spacing: 0.0625em;
  819. color: #201715;
  820. }
  821. .bg-img {
  822. width: 15.625em;
  823. position: absolute;
  824. z-index: -1;
  825. right: -0.9375em;
  826. bottom: -6.25em;
  827. }
  828. }
  829. .info {
  830. margin: 3em auto;
  831. padding: 1.5em 3em;
  832. overflow-x: auto;
  833. @media (max-width: 600px) {
  834. & {
  835. padding: 1.5em;
  836. }
  837. }
  838. .data-table {
  839. @media (max-width: 600px) {
  840. width: 700px;
  841. }
  842. }
  843. table {
  844. td {
  845. padding: 25px 25px 0;
  846. line-height: 2em;
  847. letter-spacing: 0.0625em;
  848. vertical-align: top;
  849. white-space: pre-line; // 顯示換行標籤
  850. &:first-child {
  851. border-right: 0.0625em solid #333;
  852. }
  853. strong {
  854. font-weight: 500;
  855. }
  856. }
  857. th {
  858. padding-bottom: 1.25em;
  859. border-bottom: 0.0625em solid #333;
  860. }
  861. th,
  862. td:first-child {
  863. width: 7.3125em;
  864. font-size: 1.125em;
  865. font-weight: 500;
  866. }
  867. }
  868. }
  869. .info,
  870. .sessions {
  871. background-color: #f2f2f4;
  872. table {
  873. width: 100%;
  874. border-collapse: collapse;
  875. td {
  876. line-height: 1.875em;
  877. }
  878. }
  879. }
  880. .sessions {
  881. padding: 1.5em 1.5em 0.5em;
  882. letter-spacing: 0.1em;
  883. overflow-x: scroll;
  884. table {
  885. width: 100%;
  886. position: relative;
  887. @media (max-width: 1280px) {
  888. width: 1000px;
  889. }
  890. th {
  891. width: 7.3125em;
  892. font-size: 1.125em;
  893. font-weight: 500;
  894. padding-bottom: 1.25em;
  895. white-space: nowrap;
  896. }
  897. td {
  898. padding: 1em 2em;
  899. text-align: center;
  900. vertical-align: middle;
  901. white-space: nowrap;
  902. }
  903. tbody {
  904. background-color: #fff;
  905. }
  906. }
  907. .signup-btn {
  908. position: absolute;
  909. bottom: -3.3125em;
  910. right: 0;
  911. }
  912. }
  913. @media (max-width: 960px) {
  914. width: 100%;
  915. }
  916. .other-title {
  917. margin: 1.875em 0;
  918. padding-bottom: 1.875em;
  919. font-size: 1.625em;
  920. text-align: center;
  921. border-bottom: 0.0625em solid;
  922. }
  923. }
  924. .user-form {
  925. width: 37.5em;
  926. padding: 0 1.875em 1.875em;
  927. // box-shadow: 0.0625em 0.0625em 0.5em #ccc;
  928. // border-radius: 0.3125em;
  929. // margin: 0 0.9375em 0.9375em;
  930. @media (max-width: 600px) {
  931. width: 100%;
  932. padding: 0;
  933. }
  934. h2 {
  935. font-size: 1.125em;
  936. }
  937. .result-card {
  938. position: absolute;
  939. top: 50%;
  940. left: 50%;
  941. transform: translate(-50%, -50%);
  942. }
  943. .birthday-error {
  944. display: block;
  945. font-size: 0.75em;
  946. color: #b00020;
  947. margin-left: 0.9375em;
  948. padding-top: 0.3125em;
  949. }
  950. }
  951. .signup-dialog {
  952. .v-card-text {
  953. @media (max-width: 600px) {
  954. overflow-y: auto;
  955. max-height: 31.25em;
  956. }
  957. }
  958. }
  959. .consent {
  960. max-width: 50em;
  961. padding: 1.875em 1.25em;
  962. border: 0.0625em solid #909090;
  963. border-radius: 0.3125em;
  964. h4 {
  965. font-size: 1.125em;
  966. font-weight: 400;
  967. margin-bottom: 1.25em;
  968. }
  969. p {
  970. margin-top: 1.25em;
  971. font-size: 1em;
  972. line-height: 1.625em;
  973. }
  974. }
  975. </style>