SyuanYu 1 jaar geleden
bovenliggende
commit
7a0946846f

+ 24 - 0
package-lock.json

@@ -16,6 +16,7 @@
         "leaflet.markercluster": "^1.5.3",
         "moment": "^2.29.4",
         "pinia": "^2.1.4",
+        "swiper": "^10.1.0",
         "vue": "^3.2.47",
         "vue-router": "^4.2.2",
         "vuetify": "^3.3.2"
@@ -1199,6 +1200,24 @@
         "source-map": "^0.6.0"
       }
     },
+    "node_modules/swiper": {
+      "version": "10.1.0",
+      "resolved": "https://registry.npmjs.org/swiper/-/swiper-10.1.0.tgz",
+      "integrity": "sha512-E+wh+hcSbwlRfXuwBTclcOOikOjNdSF0a2Sdg3J4cIWtHO64A7SaLRfezfrJ67CW3GEc15AduYU2YKlElsjqsQ==",
+      "funding": [
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/swiperjs"
+        },
+        {
+          "type": "open_collective",
+          "url": "http://opencollective.com/swiper"
+        }
+      ],
+      "engines": {
+        "node": ">= 4.7.0"
+      }
+    },
     "node_modules/terser": {
       "version": "5.19.1",
       "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.1.tgz",
@@ -2105,6 +2124,11 @@
         "source-map": "^0.6.0"
       }
     },
+    "swiper": {
+      "version": "10.1.0",
+      "resolved": "https://registry.npmjs.org/swiper/-/swiper-10.1.0.tgz",
+      "integrity": "sha512-E+wh+hcSbwlRfXuwBTclcOOikOjNdSF0a2Sdg3J4cIWtHO64A7SaLRfezfrJ67CW3GEc15AduYU2YKlElsjqsQ=="
+    },
     "terser": {
       "version": "5.19.1",
       "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.1.tgz",

+ 1 - 0
package.json

@@ -17,6 +17,7 @@
     "leaflet.markercluster": "^1.5.3",
     "moment": "^2.29.4",
     "pinia": "^2.1.4",
+    "swiper": "^10.1.0",
     "vue": "^3.2.47",
     "vue-router": "^4.2.2",
     "vuetify": "^3.3.2"

BIN
src/assets/img/setup-courses/素材-01.png


BIN
src/assets/img/setup-courses/素材-02.png


BIN
src/assets/img/setup-courses/素材-03.png


+ 4 - 4
src/components/CourseCard.vue

@@ -87,8 +87,8 @@ function isClassFavorite(classId) {
             :lazy-src="
               data.is_inner === 0
                 ? data.cover_img
-                : data.special_class_list_name=== 'one_day_class'
-                ? 'src/assets/img/一日學徒.png'
+                : data.special_class_list_name === 'one_day_class'
+                ? `../src/assets/img/一日學徒.png`
                 : `https://ntcri.org/${data.cover_img}`
             "
             height="200px"
@@ -96,8 +96,8 @@ function isClassFavorite(classId) {
             :src="
               data.is_inner === 0
                 ? data.cover_img
-                : data.special_class_list_name=== 'one_day_class'
-                ? 'src/assets/img/一日學徒.png'
+                : data.special_class_list_name === 'one_day_class'
+                ? '../src/assets/img/一日學徒.png'
                 : `https://ntcri.org/${data.cover_img}`
             "
           >

+ 1 - 1
src/components/Navbar.vue

@@ -81,7 +81,7 @@ const collegeList = reactive([
 const otherList = reactive([
   {
     title: "我要開課",
-    url: "/",
+    url: "/setup-courses",
   },
   {
     title: "網站架構",

+ 7 - 0
src/router/index.js

@@ -11,6 +11,8 @@ const Article = defineAsyncComponent(() => import('@/views/Article.vue'));
 const ArticleDetail = defineAsyncComponent(() => import('@/views/ArticleDetail.vue'));
 const CourseList = defineAsyncComponent(() => import('@/views/CourseList.vue'));
 const CourseDetail = defineAsyncComponent(() => import('@/views/CourseDetail.vue'));
+const SetUpCourses = defineAsyncComponent(() => import('@/views/SetUpCourses.vue'));
+
 // 八大學群
 const CollegeGroup = defineAsyncComponent(() => import('@/views/CollegeGroup/Main.vue'));
 const Craft = defineAsyncComponent(() => import('@/views/CollegeGroup/Craft.vue'));
@@ -240,6 +242,11 @@ const routes = [
     name: 'CourseDetail',
     component: CourseDetail,
   },
+  {
+    path: '/setup-courses',
+    name: 'SetUpCourses',
+    component: SetUpCourses,
+  },
   // {
   //   path: '/college-group/craft',
   //   name: 'Craft',

+ 4 - 0
src/stores/store.js

@@ -72,6 +72,10 @@ export const useMainStore = defineStore('mainStore', {
     // 過濾圖片標籤
     removeHtmlImages(value) {
       return value.replace(/<img\b[^>]*>/gi, "");
+    },
+    // 開啟登入視窗
+    openLoginDialog() {
+      this.loginDialog = true;
     }
   },
 })

+ 18 - 20
src/views/CourseDetail.vue

@@ -28,7 +28,7 @@ let groupName = ref("");
 let course = reactive({
   data: [],
 });
-let groupSort = ref("");
+let isInner = ref("");
 let isLoading = ref(false);
 let isSignUpLoading = ref(false);
 
@@ -56,7 +56,7 @@ const breadcrumbs = reactive([
       `https://cmm.ai:8088/api/get_class_name?class_name_id=${courseId}`
     );
     course.data = response.data.classes[0];
-    groupSort.value = course.data.group_sort;
+    isInner.value = course.data.is_inner;
     console.log("courseData", course.data);
     getOtherClass();
 
@@ -135,7 +135,7 @@ async function getOtherClass() {
 // }
 
 const dynamicCols = computed(() => {
-  return groupSort.value === "pinkoi" ? "5" : "8";
+  return isInner.value === 0 ? "5" : "8";
 });
 
 let user = reactive({
@@ -185,7 +185,7 @@ async function signUp(index) {
     console.log("response", response);
   } else {
     signUpDialog[index] = false;
-    openLoginDialog();
+    store.openLoginDialog();
   }
 }
 
@@ -246,9 +246,9 @@ async function signUpSubmit(id) {
 }
 
 // 開啟登入視窗
-function openLoginDialog() {
-  store.loginDialog = true;
-}
+// function openLoginDialog() {
+//   store.loginDialog = true;
+// }
 
 let currentTitle = computed(() => {
   switch (step.value) {
@@ -282,13 +282,15 @@ let currentTitle = computed(() => {
       <v-col :cols="dynamicCols" class="pa-0 d-flex justify-center">
         <img
           :src="
-            groupSort === 'pinkoi'
+            isInner === 0
               ? course.data.cover_img
+              : course.data.special_class_list_name === 'one_day_class'
+              ? `../src/assets/img/一日學徒.png`
               : `https://ntcri.org/${course.data.cover_img}`
           "
           alt=""
           class="cover-img"
-          :class="{ small: groupSort === 'pinkoi' }"
+          :class="{ small: isInner === 0 }"
         />
       </v-col>
       <v-col cols="12">
@@ -338,7 +340,7 @@ let currentTitle = computed(() => {
               </tr> -->
               <tr
                 v-if="
-                  groupSort !== 'pinkoi' &&
+                  isInner !== 0 &&
                   session.data.length &&
                   session.data[0]?.contact !== ''
                 "
@@ -346,14 +348,14 @@ let currentTitle = computed(() => {
                 <td>聯絡方式</td>
                 <td>{{ session.data[0]?.contact }}</td>
               </tr>
-              <tr v-if="groupSort === 'pinkoi'">
+              <tr v-if="isInner === 0">
                 <td>備註</td>
                 <td>此課程不會登錄至學習護照中</td>
               </tr>
             </tbody>
           </table>
           <div class="d-flex justify-end">
-            <v-btn v-if="groupSort === 'pinkoi'" rounded="xl" color="brown">
+            <v-btn v-if="isInner === 0" rounded="xl" color="brown">
               <a :href="session.data[0]?.URL" target="_blank"
                 >觀看課程資訊與報名</a
               >
@@ -362,7 +364,7 @@ let currentTitle = computed(() => {
           </div>
         </div>
       </v-col>
-      <v-col cols="12" v-if="groupSort !== 'pinkoi' && session.data.length">
+      <v-col cols="12" v-if="isInner !== 0 && session.data.length">
         <div class="sessions">
           <table>
             <thead>
@@ -385,18 +387,13 @@ let currentTitle = computed(() => {
                   {{ moment(`${item.end_time}`).format("YYYY/MM/DD H:mm") }}
                 </td>
                 <td>
-                  {{
-                    groupSort === "pinkoi" ? course.data.school : item.location
-                  }}
+                  {{ isInner === 0 ? course.data.school : item.location }}
                 </td>
                 <td></td>
                 <td></td>
                 <td>
                   <v-btn rounded="xl" color="brown">
-                    <a
-                      v-if="groupSort === 'pinkoi'"
-                      :href="item.URL"
-                      target="_blank"
+                    <a v-if="isInner === 0" :href="item.URL" target="_blank"
                       >報名</a
                     >
 
@@ -757,6 +754,7 @@ let currentTitle = computed(() => {
         line-height: 32px;
         letter-spacing: 1px;
         vertical-align: top;
+        white-space: pre-line; // 顯示換行標籤
         &:first-child {
           border-right: 1px solid #333;
         }

+ 0 - 262
src/views/CourseList copy.vue

@@ -1,262 +0,0 @@
-<script setup>
-import { ref, reactive, watch } from "vue";
-import { useMainStore } from "@/stores/store";
-import axios from "axios";
-import Navbar from "@/components/Navbar.vue";
-import CourseCard from "@/components/CourseCard.vue";
-
-const store = useMainStore();
-let pageNum = ref(1); // 頁數(預設第一頁)
-let pageAmount = ref(18); // 每頁顯示筆數
-let totalPages = ref(1); // 總頁數
-
-let loading = ref(false);
-let searchInput = ref("");
-let searchError = ref(false);
-const courseAll = reactive({
-  classes: [],
-});
-const courseData = reactive({
-  classes: [],
-});
-
-const listLocation = ref(null);
-
-// 切換分頁時回到列表上方
-watch(pageNum, () => {
-  getClass();
-  listLocation.value.scrollIntoView({ behavior: "smooth", block: "start" });
-});
-
-async function getClass() {
-  loading.value = true;
-  let url = `https://cmm.ai:8088/api/get_class_name?page_num=${pageNum.value}&page_amount=${pageAmount.value}`;
-
-  try {
-    const response = await axios.get(url);
-    totalPages.value = store.getTotalPages(response.data.total_num, 18);
-    courseAll.classes = response.data.classes;
-    courseData.classes = response.data.classes;
-    loading.value = false;
-    console.log("response", response);
-  } catch (error) {
-    loading.value = false;
-    console.error(error);
-  }
-}
-
-getClass();
-
-// 開啟登入視窗
-function openLoginDialog() {
-  store.loginDialog = true;
-}
-
-// 收藏課程清單
-let favorites = reactive({
-  list: [],
-});
-
-let progress = ref(false);
-// let token = localStorage.getItem("token");
-
-// 加入收藏課程
-// async function setFavoriteClass(classId) {
-//   let isLogin = store.checkToken();
-//   if (!isLogin) {
-//     openLoginDialog();
-//     return;
-//   }
-
-//   progress.value = true;
-
-//   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.get(
-//       `https://cmm.ai:8088/api/get_favorite_class?access_token=${token}`
-//     );
-//     favorites.list = response.data.favorite_courses;
-//   } catch (error) {
-//     console.error(error);
-//   }
-// }
-
-// getFavoriteClass();
-
-// 刪除收藏課程
-// async function deleteFavoriteClass(classId) {
-//   progress.value = true;
-//   try {
-//     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,
-  },
-]);
-</script>
-
-<template>
-  <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>
-      <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()" placeholder="關鍵字搜尋"
-              />
-              <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"
-          >
-            <CourseCard :data="item" />
-          </v-col>
-        </v-row> -->
-
-        <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()"
-                placeholder="關鍵字搜尋"
-              />
-              <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>
-
-        <div class="d-flex justify-center mb-10" v-if="loading">
-          <v-progress-circular
-            color="grey-lighten-4"
-            indeterminate
-          ></v-progress-circular>
-        </div>
-
-        <v-row>
-          <v-col
-            sm="6"
-            md="4"
-            cols="12"
-            v-for="(item, index) in courseData.classes"
-            :key="index"
-            class="pa-5"
-          >
-            <CourseCard :data="item" />
-          </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>
-.banner {
-  display: flex;
-  justify-content: end;
-  img {
-    width: 75%;
-  }
-}
-
-.main-block {
-  margin-top: -20%;
-}
-
-.v-pagination {
-  margin: auto;
-  max-width: 500px;
-}
-</style>

+ 0 - 72
src/views/Home-back.vue

@@ -1,72 +0,0 @@
-<script setup>
-import { reactive } from "vue";
-import Navbar from "@/components/Navbar.vue";
-import Carousels from "@/components/Carousels.vue";
-import Map from "@/components/Map.vue";
-
-function handleScroll(event) {
-  const container = event.currentTarget;
-  console.log('event.deltaY',event.deltaY);
-  if (event.deltaY > 0) {
-    container.scrollLeft += 150;
-  } else {
-    container.scrollLeft -= 150;
-  }
-}
-
-let initialTouchPosition = null;
-
-function handleTouchStart(event) {
-  initialTouchPosition = event.touches[0].clientX;
-}
-
-function handleTouchMove(event) {
-  if (!initialTouchPosition) return;
-
-  const currentTouchPosition = event.touches[0].clientX;
-  const diff = currentTouchPosition - initialTouchPosition;
-
-  const container = document.querySelector('.main-container');
-  if (diff > 0) {
-    container.scrollLeft -= 150;
-  } else {
-    container.scrollLeft += 150;
-  }
-
-  initialTouchPosition = null;
-}
-</script>
-
-<template>
-  <!-- <Navbar /> -->
-  <div class="main-container" @wheel="handleScroll" @touchstart="handleTouchStart" @touchmove="handleTouchMove">
-    <Navbar />
-    <div class="d-flex content">
-      <img src="@/assets/img/banner-0615.webp" alt="" />
-      <div class="map">
-        <Map />
-      </div>
-    </div>
-    <!-- <Carousels :imgList="images" /> -->
-    <!-- <Map /> -->
-  </div>
-</template>
-
-<style lang="scss">
-.main-container {
-  width: 100%;
-  height: 100vh;
-  overflow-x: hidden;
-  overflow-y: hidden;
-  .content {
-    // width: 200%;
-    width: 3000px; // 圖片+地圖的寬度
-    height: 100%;
-    padding-top: 170px;
-    white-space: nowrap;
-    .map {
-      width: 1500px;
-    }
-  }
-}
-</style>

+ 126 - 0
src/views/SetUpCourses.vue

@@ -0,0 +1,126 @@
+<script setup>
+import { ref, reactive, watch } from "vue";
+import { useMainStore } from "@/stores/store";
+import axios from "axios";
+import Navbar from "@/components/Navbar.vue";
+import { Swiper, SwiperSlide } from "swiper/vue";
+import { Pagination } from "swiper/modules";
+import "swiper/css";
+import "swiper/css/pagination";
+import "swiper/css/navigation";
+
+const store = useMainStore();
+let isLogin = store.checkToken();
+console.log("isLogin", isLogin);
+
+const modules = [Pagination];
+</script>
+
+<template>
+  <Navbar />
+
+  <v-container class="my-16 py-16">
+    <v-row>
+      <v-col cols="12" md="6">
+        <div class="options">
+          <img src="@/assets/img/setup-courses/素材-01.png" alt="" />
+          <router-link to="/">觀看開課教學</router-link>
+        </div>
+      </v-col>
+      <v-col cols="12" md="6" class="mt-16 mt-md-0">
+        <div v-if="!isLogin" class="options">
+          <img src="@/assets/img/setup-courses/素材-02.png" alt="" />
+          <button @click="store.openLoginDialog()">登入會員開課</button>
+        </div>
+        <div v-else class="options">
+          <img src="@/assets/img/setup-courses/素材-03.png" alt="" />
+          <router-link to="/">開始創建課程</router-link>
+        </div>
+      </v-col>
+    </v-row>
+
+    <!-- 垂直輪播測試 -->
+    <!-- <div style="height: 500px; position: relative">
+      <div class="background-image"></div>
+      <swiper
+        :direction="'vertical'"
+        :pagination="{
+          clickable: true,
+        }"
+        :modules="modules"
+      >
+        <swiper-slide>Slide 1</swiper-slide>
+        <swiper-slide>Slide 2</swiper-slide>
+        <swiper-slide>Slide 3</swiper-slide>
+        左右箭頭
+        <div class="swiper-button-prev" @click.stop="prevEl(item, index)" />
+        <div class="swiper-button-next" @click.stop="nextEl" />
+      </swiper>
+    </div> -->
+  </v-container>
+</template>
+
+<style lang="scss" scoped>
+.swiper-slide {
+  height: 500px !important;
+}
+.options {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  a,
+  button {
+    display: block;
+    padding: 20px 80px;
+    font-size: 20px;
+    letter-spacing: 2px;
+    border: 2px solid var(--purple);
+    border-radius: 15px;
+    transition: all 0.3s;
+
+    &:hover {
+      box-shadow: 0 0 10px var(--purple);
+    }
+
+    @media (max-width: 600px) {
+      font-size: 18px;
+    }
+  }
+  img {
+    width: 100%;
+    max-width: 500px;
+  }
+}
+
+// Swiper
+.background-image {
+  background-image: url("../src/assets/img/一日學徒.png");
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  background-size: cover;
+  z-index: -1;
+}
+
+.swiper {
+  width: 100%;
+  height: 100%;
+}
+
+.swiper-slide {
+  text-align: center;
+  font-size: 18px;
+
+  /* Center slide text vertically */
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.swiper-slide img {
+  display: block;
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+</style>