|
@@ -1,8 +1,14 @@
|
|
|
<script setup>
|
|
|
-import { ref, reactive } from "vue";
|
|
|
+import { ref, reactive, watch } from "vue";
|
|
|
import axios from "axios";
|
|
|
import moment from "moment";
|
|
|
-import Navbar from "@/components/NavbarSub.vue";
|
|
|
+import Navbar from "@/components/Navbar.vue";
|
|
|
+import { useMainStore } from "@/stores/store";
|
|
|
+
|
|
|
+const store = useMainStore();
|
|
|
+let pageNum = ref(1); // 頁數(預設第一頁)
|
|
|
+let pageAmount = ref(9); // 每頁顯示筆數
|
|
|
+let totalPages = ref(1); // 總頁數
|
|
|
|
|
|
let searchInput = ref("");
|
|
|
let searchError = ref(false);
|
|
@@ -13,243 +19,352 @@ const courseData = reactive({
|
|
|
classes: [],
|
|
|
});
|
|
|
|
|
|
-// 取得資料
|
|
|
-(async function getData() {
|
|
|
+const listLocation = ref(null);
|
|
|
+
|
|
|
+// 切換分頁時回到列表上方
|
|
|
+watch(pageNum, () => {
|
|
|
+ getClass();
|
|
|
+ listLocation.value.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
|
+});
|
|
|
+
|
|
|
+async function getClass() {
|
|
|
+ let url = `https://cmm.ai:8088/api/get_class_name?page_num=${pageNum.value}&page_amount=${pageAmount.value}`;
|
|
|
+
|
|
|
try {
|
|
|
- const response = await axios.get("https://cmm.ai:8088/api/get_class_name");
|
|
|
- console.log("response.data.classes", response.data.classes);
|
|
|
+ const response = await axios.get(url);
|
|
|
+ totalPages.value = store.getTotalPages(response.data.total_num, 9);
|
|
|
courseAll.classes = response.data.classes;
|
|
|
courseData.classes = response.data.classes;
|
|
|
} catch (error) {
|
|
|
console.error(error);
|
|
|
}
|
|
|
-})();
|
|
|
-
|
|
|
-// 搜尋
|
|
|
-async function search() {
|
|
|
- searchError.value = false;
|
|
|
- let keyword = searchInput.value;
|
|
|
- if (keyword !== "") {
|
|
|
- try {
|
|
|
- const response = await axios.get(
|
|
|
- `https://cmm.ai:8088/api/search_class_like?keyword=${keyword}`
|
|
|
- );
|
|
|
- if (response.data.classes.length !== 0) {
|
|
|
- courseData.classes = response.data.classes;
|
|
|
- } else {
|
|
|
- searchError.value = true;
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error(error);
|
|
|
- }
|
|
|
- } else {
|
|
|
- courseData.classes = courseAll.classes;
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
-// async function setFavoriteClass(classId, userId = 1) {
|
|
|
-// // /api/add_favorite_class
|
|
|
-// console.log("classId", classId);
|
|
|
-// console.log("userId", userId);
|
|
|
+getClass();
|
|
|
+
|
|
|
+// 開啟登入視窗
|
|
|
+function openLoginDialog() {
|
|
|
+ store.loginDialog = true;
|
|
|
+}
|
|
|
+
|
|
|
+// 收藏課程清單
|
|
|
+let favorites = reactive({
|
|
|
+ list: [],
|
|
|
+});
|
|
|
+
|
|
|
+let progress = ref(false);
|
|
|
+let token = localStorage.getItem("token");
|
|
|
|
|
|
-// const formData = new FormData();
|
|
|
-// formData.append("class_event_id", classId);
|
|
|
-// formData.append("user_id", userId);
|
|
|
+// 加入收藏課程
|
|
|
+async function setFavoriteClass(classId) {
|
|
|
+ let isLogin = store.checkToken();
|
|
|
+ if (!isLogin) {
|
|
|
+ openLoginDialog();
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
-// try {
|
|
|
-// const response = await axios.post("https://cmm.ai:8088/api/add_favorite_class", formData);
|
|
|
-// console.log("add_favorite_class response", response);
|
|
|
-// } catch (error) {
|
|
|
-// console.error(error);
|
|
|
-// }
|
|
|
-// }
|
|
|
+ progress.value = true;
|
|
|
|
|
|
-async function setFavoriteClass(classId, userId = 2) {
|
|
|
- const url = `https://cmm.ai:8088/api/add_favorite_class?class_event_id=${classId}&user_id=${userId}`;
|
|
|
+ const url = `https://cmm.ai:8088/api/add_favorite_class?class_name_id=${classId}&access_token=${token}`;
|
|
|
+ try {
|
|
|
+ const response = await axios.post(url);
|
|
|
+ getFavoriteClass();
|
|
|
+ progress.value = false;
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error);
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
+// 取得收藏課程
|
|
|
+async function getFavoriteClass() {
|
|
|
try {
|
|
|
- const response = await axios.post(url, '', {
|
|
|
- headers: {
|
|
|
- 'Accept': 'application/json'
|
|
|
- }
|
|
|
- });
|
|
|
- console.log("add_favorite_class response", response);
|
|
|
- getFavoriteClass(userId);
|
|
|
+ const response = await axios.get(
|
|
|
+ `https://cmm.ai:8088/api/get_favorite_class?access_token=${token}`
|
|
|
+ );
|
|
|
+ favorites.list = response.data.favorite_courses;
|
|
|
} catch (error) {
|
|
|
console.error(error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-async function getFavoriteClass(userId) {
|
|
|
- console.log("getFavoriteClass userId", userId);
|
|
|
+getFavoriteClass();
|
|
|
+
|
|
|
+// 刪除收藏課程
|
|
|
+async function deleteFavoriteClass(classId) {
|
|
|
+ progress.value = true;
|
|
|
try {
|
|
|
- const response = await axios.get(`https://cmm.ai:8088/api/get_favorite_class?user_id=${userId}`);
|
|
|
- console.log("getFavoriteClass response", response);
|
|
|
+ const response = await axios.post(
|
|
|
+ `https://cmm.ai:8088/api/delete_favorite_class?class_name_id=${classId}&access_token=${token}`
|
|
|
+ );
|
|
|
+ progress.value = false;
|
|
|
+ getFavoriteClass();
|
|
|
} catch (error) {
|
|
|
console.error(error);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+// 檢查收藏狀態
|
|
|
+function isClassFavorite(classId) {
|
|
|
+ let list = favorites.list.map((e) => e.class_name_id);
|
|
|
+ return list.includes(classId);
|
|
|
+}
|
|
|
+
|
|
|
+const breadcrumbs = reactive([
|
|
|
+ {
|
|
|
+ title: "首頁",
|
|
|
+ disabled: false,
|
|
|
+ href: "/",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "探索課程",
|
|
|
+ disabled: true,
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+const testData = [
|
|
|
+ {
|
|
|
+ title: "種子教師研習",
|
|
|
+ start_time: "2023/06/15",
|
|
|
+ end_time: "2023/06/20",
|
|
|
+ address: "地方工藝館 工藝教室",
|
|
|
+ img: store.getImageUrl("college-group/img.jpg"),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "種子教師研習",
|
|
|
+ start_time: "2023/06/15",
|
|
|
+ end_time: "2023/06/20",
|
|
|
+ address: "地方工藝館 工藝教室",
|
|
|
+ img: store.getImageUrl("college-group/img.jpg"),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "種子教師研習",
|
|
|
+ start_time: "2023/06/15",
|
|
|
+ end_time: "2023/06/20",
|
|
|
+ address: "地方工藝館 工藝教室",
|
|
|
+ img: store.getImageUrl("college-group/img.jpg"),
|
|
|
+ },
|
|
|
+];
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
- <Navbar />
|
|
|
- <div class="banner">
|
|
|
- <img src="@/assets/img/img-01.jpg" alt="" />
|
|
|
- <h3>課程清單</h3>
|
|
|
- </div>
|
|
|
-
|
|
|
- <v-container class="my-16 pa-0">
|
|
|
- <div
|
|
|
- class="d-flex align-center ms-auto mb-16 search-btn"
|
|
|
- style="max-width: 300px"
|
|
|
- >
|
|
|
- <v-text-field
|
|
|
- label="課程搜尋"
|
|
|
- v-model="searchInput"
|
|
|
- variant="outlined"
|
|
|
- density="compact"
|
|
|
- hide-details
|
|
|
- @keyup.enter="search()"
|
|
|
- ></v-text-field>
|
|
|
- <div v-if="searchError" class="d-flex justify-center error">
|
|
|
- <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
|
|
|
- 沒有符合搜尋條件的項目
|
|
|
+ <div class="college-bg-img">
|
|
|
+ <Navbar />
|
|
|
+ <v-container fluid class="college-content pb-16 px-sm-0">
|
|
|
+ <div class="banner">
|
|
|
+ <img src="@/assets/img/course/banner.png" alt="" />
|
|
|
</div>
|
|
|
- <button @click="search()" class="btn">
|
|
|
- <img src="@/assets/img/icon/search.png" alt="" width="25" />
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- <v-row no-gutters>
|
|
|
- <v-col
|
|
|
- v-for="item in courseData.classes"
|
|
|
- :key="item"
|
|
|
- cols="12"
|
|
|
- xl="3"
|
|
|
- lg="4"
|
|
|
- sm="6"
|
|
|
- class="mb-10"
|
|
|
- >
|
|
|
- <v-card class="mx-5 h-100">
|
|
|
- <!-- 傳遞課程 id -->
|
|
|
- <router-link
|
|
|
- :to="`/course-detail/${item.class_name_id}`"
|
|
|
- class="cover-img"
|
|
|
+ <div class="main-block">
|
|
|
+ <v-breadcrumbs
|
|
|
+ :items="breadcrumbs"
|
|
|
+ divider="/"
|
|
|
+ class="mt-10 pa-0"
|
|
|
+ ></v-breadcrumbs>
|
|
|
+
|
|
|
+ <div
|
|
|
+ class="d-flex flex-column flex-sm-row align-center justify-space-between title"
|
|
|
+ >
|
|
|
+ <h2>最新開課</h2>
|
|
|
+ <div class="search">
|
|
|
+ <span>
|
|
|
+ <input
|
|
|
+ v-model="searchInput"
|
|
|
+ type="text"
|
|
|
+ @keyup.enter="search()"
|
|
|
+ />
|
|
|
+ <button @click="search()">
|
|
|
+ <img src="@/assets/img/news/news-search-icon.png" alt="" />
|
|
|
+ </button>
|
|
|
+ </span>
|
|
|
+ <div
|
|
|
+ v-if="searchError"
|
|
|
+ class="d-flex justify-center align-center error me-4"
|
|
|
+ >
|
|
|
+ <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
|
|
|
+ 沒有符合搜尋條件的項目
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <v-row>
|
|
|
+ <v-col
|
|
|
+ sm="6"
|
|
|
+ md="4"
|
|
|
+ cols="12"
|
|
|
+ v-for="(item, index) in testData"
|
|
|
+ :key="index"
|
|
|
+ class="pa-5"
|
|
|
>
|
|
|
- <v-img :src="item.cover_img" height="220px" cover></v-img>
|
|
|
- </router-link>
|
|
|
- <v-card-title class="font-weight-medium">
|
|
|
- {{ item.name }}
|
|
|
- </v-card-title>
|
|
|
-
|
|
|
- <v-card-text>
|
|
|
- <p class="text-gray font-weight-light">{{ item.introduction }}</p>
|
|
|
- <div class="d-flex align-center mt-2">
|
|
|
- <img src="@/assets/img/icon/location_icon.png" alt="" />
|
|
|
- <p class="mb-0 ms-3">
|
|
|
- {{ item.school }}
|
|
|
- </p>
|
|
|
+ <div class="main-card">
|
|
|
+ <section class="card-title">
|
|
|
+ <h3>{{ item.title }}</h3>
|
|
|
+ </section>
|
|
|
+ <div class="card-info">
|
|
|
+ <img :src="item.img" alt="" class="cover-img" />
|
|
|
+ <ul>
|
|
|
+ <li class="d-flex align-center">
|
|
|
+ <img src="@/assets/img/icon/date_icon.png" alt="" />
|
|
|
+ <p class="mb-0 ms-3">
|
|
|
+ {{ moment(`${item.start_time}`).format("YYYY/MM/DD") }} -
|
|
|
+ {{ moment(`${item.end_time}`).format("YYYY/MM/DD") }}
|
|
|
+ </p>
|
|
|
+ </li>
|
|
|
+ <li class="d-flex align-center mt-3">
|
|
|
+ <img src="@/assets/img/icon/location_icon.png" alt="" />
|
|
|
+ <p class="mb-0 ms-3">
|
|
|
+ {{ item.address }}
|
|
|
+ </p>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+ </v-col>
|
|
|
+ </v-row>
|
|
|
|
|
|
- <!-- <ul>
|
|
|
- <li class="d-flex align-center">
|
|
|
- <img src="@/assets/img/icon/date_icon.png" alt="" />
|
|
|
- <p class="mb-0 ms-3">
|
|
|
- {{ moment(`${item.start_time}`).format("YYYY/MM/DD") }} -
|
|
|
- {{ moment(`${item.end_time}`).format("YYYY/MM/DD") }}
|
|
|
- </p>
|
|
|
- </li>
|
|
|
- <li class="d-flex align-center mt-2">
|
|
|
- <img src="@/assets/img/icon/location_icon.png" alt="" />
|
|
|
- <p class="mb-0 ms-3">
|
|
|
- {{ item.school }}
|
|
|
- </p>
|
|
|
- </li>
|
|
|
- </ul> -->
|
|
|
- </v-card-text>
|
|
|
- <v-card-action class="d-block mt-5">
|
|
|
- <button
|
|
|
- class="favorites-btn"
|
|
|
- @click="setFavoriteClass(item.class_name_id)"
|
|
|
+ <div
|
|
|
+ class="d-flex flex-column flex-sm-row align-center justify-space-between title"
|
|
|
+ ref="listLocation"
|
|
|
+ >
|
|
|
+ <h2>課程清單</h2>
|
|
|
+ <div class="search">
|
|
|
+ <span>
|
|
|
+ <input
|
|
|
+ v-model="searchInput"
|
|
|
+ type="text"
|
|
|
+ @keyup.enter="search()"
|
|
|
+ />
|
|
|
+ <button @click="search()">
|
|
|
+ <img src="@/assets/img/news/news-search-icon.png" alt="" />
|
|
|
+ </button>
|
|
|
+ </span>
|
|
|
+ <div
|
|
|
+ v-if="searchError"
|
|
|
+ class="d-flex justify-center align-center error me-4"
|
|
|
>
|
|
|
- <v-icon
|
|
|
- color="primary"
|
|
|
- icon="mdi-bookmark-outline"
|
|
|
- size="large"
|
|
|
- ></v-icon>
|
|
|
- <v-icon color="primary" icon="mdi-bookmark" size="large"></v-icon>
|
|
|
- </button>
|
|
|
- </v-card-action>
|
|
|
- </v-card>
|
|
|
- </v-col>
|
|
|
- </v-row>
|
|
|
- </v-container>
|
|
|
+ <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
|
|
|
+ 沒有符合搜尋條件的項目
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <v-row>
|
|
|
+ <v-col
|
|
|
+ sm="6"
|
|
|
+ md="4"
|
|
|
+ cols="12"
|
|
|
+ v-for="(item, index) in courseData.classes"
|
|
|
+ :key="index"
|
|
|
+ class="pa-5"
|
|
|
+ >
|
|
|
+ <div class="main-card">
|
|
|
+ <section class="card-title">
|
|
|
+ <h3>{{ item.name }}</h3>
|
|
|
+ </section>
|
|
|
+ <div class="card-info">
|
|
|
+ <router-link
|
|
|
+ :to="`/course-detail/${item.class_name_id}`"
|
|
|
+ class="cover-img"
|
|
|
+ >
|
|
|
+ <v-img
|
|
|
+ class="mx-auto cover-img"
|
|
|
+ :lazy-src="item.cover_img"
|
|
|
+ height="220px"
|
|
|
+ cover
|
|
|
+ :src="item.cover_img"
|
|
|
+ >
|
|
|
+ <template v-slot:placeholder>
|
|
|
+ <div
|
|
|
+ class="d-flex align-center justify-center fill-height"
|
|
|
+ >
|
|
|
+ <v-progress-circular
|
|
|
+ color="grey-lighten-4"
|
|
|
+ indeterminate
|
|
|
+ ></v-progress-circular>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </v-img>
|
|
|
+ <!-- <v-img :src="item.cover_img" height="220px" cover></v-img> -->
|
|
|
+ </router-link>
|
|
|
+ <ul>
|
|
|
+ <li class="d-flex align-center">
|
|
|
+ <p class="text-gray font-weight-light">
|
|
|
+ {{ item.introduction }}
|
|
|
+ </p>
|
|
|
+ </li>
|
|
|
+ <li class="d-flex align-center mt-3">
|
|
|
+ <img src="@/assets/img/icon/location_icon.png" alt="" />
|
|
|
+ <p class="mb-0 ms-3">
|
|
|
+ {{ item.school }}
|
|
|
+ </p>
|
|
|
+ </li>
|
|
|
+ <li>
|
|
|
+ <div class="d-block mt-5">
|
|
|
+ <button class="favorites-btn">
|
|
|
+ <v-icon
|
|
|
+ v-if="isClassFavorite(item.class_name_id)"
|
|
|
+ @click="deleteFavoriteClass(item.class_name_id)"
|
|
|
+ color="primary"
|
|
|
+ icon="mdi-bookmark"
|
|
|
+ size="large"
|
|
|
+ ></v-icon>
|
|
|
+ <v-icon
|
|
|
+ v-else
|
|
|
+ @click="setFavoriteClass(item.class_name_id)"
|
|
|
+ color="primary"
|
|
|
+ icon="mdi-bookmark-outline"
|
|
|
+ size="large"
|
|
|
+ ></v-icon>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </v-col>
|
|
|
+ </v-row>
|
|
|
+ <div class="progress-item" v-if="progress">
|
|
|
+ <v-progress-circular
|
|
|
+ :size="50"
|
|
|
+ indeterminate
|
|
|
+ color="primary"
|
|
|
+ ></v-progress-circular>
|
|
|
+ </div>
|
|
|
+ <v-pagination
|
|
|
+ v-model="pageNum"
|
|
|
+ :length="totalPages"
|
|
|
+ rounded="circle"
|
|
|
+ class="mt-16"
|
|
|
+ ></v-pagination>
|
|
|
+ </div>
|
|
|
+ </v-container>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
-p {
|
|
|
- overflow: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
- display: -webkit-box;
|
|
|
- -webkit-line-clamp: 2;
|
|
|
- -webkit-box-orient: vertical;
|
|
|
- line-break: after-white-space;
|
|
|
- line-height: 22px;
|
|
|
-}
|
|
|
-
|
|
|
.banner {
|
|
|
- position: relative;
|
|
|
+ display: flex;
|
|
|
+ justify-content: end;
|
|
|
img {
|
|
|
- width: 100%;
|
|
|
- height: 400px;
|
|
|
- object-fit: cover;
|
|
|
- object-position: 0 80%;
|
|
|
- @media (max-width: 767px) {
|
|
|
- object-position: center;
|
|
|
- }
|
|
|
- }
|
|
|
- h3 {
|
|
|
- display: inline-block;
|
|
|
- padding: 30px 80px;
|
|
|
- font-size: 26px;
|
|
|
- font-weight: 500;
|
|
|
- position: absolute;
|
|
|
- z-index: 10;
|
|
|
- top: 50%;
|
|
|
- left: 50%;
|
|
|
- letter-spacing: 1px;
|
|
|
- transform: translate(-50%, -50%);
|
|
|
- color: #fff;
|
|
|
- background-color: var(--main-color);
|
|
|
- @media (max-width: 575px) {
|
|
|
- padding: 25px 50px;
|
|
|
- font-size: 20px;
|
|
|
- }
|
|
|
+ width: 75%;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-.search-btn {
|
|
|
- position: relative;
|
|
|
- .btn {
|
|
|
- margin: 2px 10px 0;
|
|
|
- transition: all 0.3s;
|
|
|
- &:hover {
|
|
|
- opacity: 0.8;
|
|
|
+.main-block {
|
|
|
+ margin-top: -20%;
|
|
|
+
|
|
|
+ .main-card {
|
|
|
+ height: 100%;
|
|
|
+ position: relative;
|
|
|
+ .card-info {
|
|
|
+ line-height: 24px;
|
|
|
+ ul {
|
|
|
+ padding: 20px 10px;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- .error {
|
|
|
- position: absolute;
|
|
|
- bottom: -30px;
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
-.cover-img {
|
|
|
- display: block;
|
|
|
- overflow: hidden;
|
|
|
- .v-img {
|
|
|
- transition: all 0.5s;
|
|
|
- &:hover {
|
|
|
- transform: scale(1.2);
|
|
|
- }
|
|
|
- }
|
|
|
+.v-pagination {
|
|
|
+ margin: auto;
|
|
|
+ max-width: 500px;
|
|
|
}
|
|
|
</style>
|