SyuanYu 1 год назад
Родитель
Сommit
4fc7089c9c

+ 6 - 0
src/assets/css/style.css

@@ -247,4 +247,10 @@ input:focus-visible {
 }
 .main-card .card-info span p {
   line-height: 20px;
+}
+
+.favorites-btn {
+  position: absolute;
+  bottom: 10px;
+  right: 10px;
 }/*# sourceMappingURL=style.css.map */

+ 1 - 1
src/assets/css/style.css.map

@@ -1 +1 @@
-{"version":3,"sources":["style.scss","style.css"],"names":[],"mappings":"AAAA,cAAA;AAEA;;;;;;;;;;;;;;;;;;;;EAoBE,SAAA;EACA,UAAA;EACA,SAAA;EACA,eAAA;EACA,wBAAA;EACA,sBAAA;ACAF;;ADGA,sBAAA;AAEA;EACE,cAAA;ACDF;;ADIA;;EAEE,gBAAA;ACDF;;ADIA;EACE,eAAA;EACA,YAAA;ACDF;;ADIA;EACE,qBAAA;EACA,cAAA;ACDF;;ADIA,kBAAA;AAEA;EACE,qBAAA;EACA,oBAAA;ACFF;;ADKA;EACE,uCAAA;ACFF;;ADKA;EACE,mCAAA;ACFF;;ADKA;EACE,aAAA;EACA,sBAAA;EACA,qBAAA;EACA,oBAAA;EACA,kBAAA;ACFF;ADIE;EACE,kBAAA;ACFJ;ADKE;EACE,iBAAA;EACA,oBAAA;EACA,sBAAA;EACA,sBAAA;ACHJ;ADOE;EACE,kBAAA;EACA,WAAA;EACA,OAAA;EACA,QAAA;ACLJ;ADOI;EACE,WAAA;EACA,kBAAA;EACA,QAAA;EACA,QAAA;ACLN;ADSE;EACE,kBAAA;EACA,aAAA;ACPJ;;ADWA;EACE,YAAA;EACA,kEAAA;EACA,2BAAA;EACA,sBAAA;EACA,4BAAA;ACRF;;ADWA;EACE,aAAA;EACA,uBAAA;EACA,kBAAA;EACA,SAAA;EACA,QAAA;EACA,OAAA;EACA,UAAA;ACRF;ADUE;EACE,WAAA;ACRJ;ADWE;EACE,kBAAA;EACA,QAAA;EACA,SAAA;EACA,eAAA;EACA,gBAAA;EACA,qBAAA;ACTJ;ADWI;EARF;IASI,eAAA;ECRJ;AACF;ADUI;EAZF;IAaI,eAAA;ECPJ;AACF;ADSI;EAhBF;IAiBI,eAAA;IACA,QAAA;IACA,QAAA;ECNJ;AACF;;ADUA;EACE,UAAA;EACA,wBAAA;ACPF;ADSE;EAJF;IAKI,UAAA;ECNF;AACF;ADQE;EACE,mBAAA;EACA,gBAAA;EACA,sBAAA;ACNJ;ADQI;EALF;IAMI,mBAAA;ECLJ;AACF;ADOI;EATF;IAUI,kBAAA;ECJJ;AACF;ADMI;EACE,eAAA;EACA,gBAAA;EACA,iBAAA;EACA,iBAAA;ACJN;ADMM;EANF;IAOI,eAAA;ECHN;AACF;ADKM;EAVF;IAWI,cAAA;IACA,mBAAA;ECFN;AACF;ADKI;EACE,cAAA;ACHN;ADKM;EAHF;IAII,cAAA;ECFN;AACF;ADKI;EACE,kBAAA;EACA,YAAA;EACA,2BAAA;ACHN;ADKM;EALF;IAMI,uBAAA;ECFN;AACF;;ADOA;EACE,mBAAA;EACA,mBAAA;EACA,gCAAA;EACA,kCAAA;ACJF;ADME;EACE,YAAA;EACA,aAAA;EACA,mBAAA;EACA,aAAA;EACA,uBAAA;EACA,mBAAA;EACA,6BAAA;ACJJ;ADMI;EACE,eAAA;EACA,gBAAA;EACA,kBAAA;EACA,iBAAA;ACJN;ADQE;EACE,aAAA;ACNJ;ADSE;;EAGE,gBAAA;EACA,uBAAA;EACA,oBAAA;EACA,qBAAA;EACA,4BAAA;EACA,6BAAA;ACRJ;ADWE;EACE,eAAA;ACTJ;ADWI;EACE,aAAA;EACA,WAAA;EACA,oBAAA;KAAA,iBAAA;ACTN;ADYI;EACE,YAAA;ACVN;ADYM;EACE,iBAAA;ACVR","file":"style.css"}
+{"version":3,"sources":["style.scss","style.css"],"names":[],"mappings":"AAAA,cAAA;AAEA;;;;;;;;;;;;;;;;;;;;EAoBE,SAAA;EACA,UAAA;EACA,SAAA;EACA,eAAA;EACA,wBAAA;EACA,sBAAA;ACAF;;ADGA,sBAAA;AAEA;EACE,cAAA;ACDF;;ADIA;;EAEE,gBAAA;ACDF;;ADIA;EACE,eAAA;EACA,YAAA;ACDF;;ADIA;EACE,qBAAA;EACA,cAAA;ACDF;;ADIA,kBAAA;AAEA;EACE,qBAAA;EACA,oBAAA;ACFF;;ADKA;EACE,uCAAA;ACFF;;ADKA;EACE,mCAAA;ACFF;;ADKA;EACE,aAAA;EACA,sBAAA;EACA,qBAAA;EACA,oBAAA;EACA,kBAAA;ACFF;ADIE;EACE,kBAAA;ACFJ;ADKE;EACE,iBAAA;EACA,oBAAA;EACA,sBAAA;EACA,sBAAA;ACHJ;ADOE;EACE,kBAAA;EACA,WAAA;EACA,OAAA;EACA,QAAA;ACLJ;ADOI;EACE,WAAA;EACA,kBAAA;EACA,QAAA;EACA,QAAA;ACLN;ADSE;EACE,kBAAA;EACA,aAAA;ACPJ;;ADWA;EACE,YAAA;EACA,kEAAA;EACA,2BAAA;EACA,sBAAA;EACA,4BAAA;ACRF;;ADWA;EACE,aAAA;EACA,uBAAA;EACA,kBAAA;EACA,SAAA;EACA,QAAA;EACA,OAAA;EACA,UAAA;ACRF;ADUE;EACE,WAAA;ACRJ;ADWE;EACE,kBAAA;EACA,QAAA;EACA,SAAA;EACA,eAAA;EACA,gBAAA;EACA,qBAAA;ACTJ;ADWI;EARF;IASI,eAAA;ECRJ;AACF;ADUI;EAZF;IAaI,eAAA;ECPJ;AACF;ADSI;EAhBF;IAiBI,eAAA;IACA,QAAA;IACA,QAAA;ECNJ;AACF;;ADUA;EACE,UAAA;EACA,wBAAA;ACPF;ADSE;EAJF;IAKI,UAAA;ECNF;AACF;ADQE;EACE,mBAAA;EACA,gBAAA;EACA,sBAAA;ACNJ;ADQI;EALF;IAMI,mBAAA;ECLJ;AACF;ADOI;EATF;IAUI,kBAAA;ECJJ;AACF;ADMI;EACE,eAAA;EACA,gBAAA;EACA,iBAAA;EACA,iBAAA;ACJN;ADMM;EANF;IAOI,eAAA;ECHN;AACF;ADKM;EAVF;IAWI,cAAA;IACA,mBAAA;ECFN;AACF;ADKI;EACE,cAAA;ACHN;ADKM;EAHF;IAII,cAAA;ECFN;AACF;ADKI;EACE,kBAAA;EACA,YAAA;EACA,2BAAA;ACHN;ADKM;EALF;IAMI,uBAAA;ECFN;AACF;;ADOA;EACE,mBAAA;EACA,mBAAA;EACA,gCAAA;EACA,kCAAA;ACJF;ADME;EACE,YAAA;EACA,aAAA;EACA,mBAAA;EACA,aAAA;EACA,uBAAA;EACA,mBAAA;EACA,6BAAA;ACJJ;ADMI;EACE,eAAA;EACA,gBAAA;EACA,kBAAA;EACA,iBAAA;ACJN;ADQE;EACE,aAAA;ACNJ;ADSE;;EAGE,gBAAA;EACA,uBAAA;EACA,oBAAA;EACA,qBAAA;EACA,4BAAA;EACA,6BAAA;ACRJ;ADWE;EACE,eAAA;ACTJ;ADWI;EACE,aAAA;EACA,WAAA;EACA,oBAAA;KAAA,iBAAA;ACTN;ADYI;EACE,YAAA;ACVN;ADYM;EACE,iBAAA;ACVR;;ADgBA;EACE,kBAAA;EACA,YAAA;EACA,WAAA;ACbF","file":"style.css"}

+ 6 - 0
src/assets/css/style.scss

@@ -260,4 +260,10 @@ input:focus-visible {
       }
     }
   }
+}
+
+.favorites-btn {
+  position: absolute;
+  bottom: 10px;
+  right: 10px;
 }

+ 124 - 44
src/components/Navbar.vue

@@ -1,5 +1,5 @@
 <script setup>
-import { ref } from "vue";
+import { ref, reactive } from "vue";
 import { useMainStore } from "@/stores/store";
 import Login from "@/views/Login.vue";
 
@@ -16,6 +16,50 @@ function toggleMenu() {
 function handleClose(value) {
   dialog.value = value;
 }
+
+function handleMouseEvents(event) {
+  const screenWidth = window.innerWidth;
+  if (screenWidth >= 1280) {
+    collegeMenuShow.value = event;
+  } else {
+    return;
+  }
+}
+
+const collegeList = reactive([
+  {
+    title: "世代工藝",
+    url: "/college-group/generation",
+  },
+  {
+    title: "未來工藝",
+    url: "/college-group/future",
+  },
+  {
+    title: "生活工藝",
+    url: "/college-group/craft",
+  },
+  {
+    title: "技藝工藝",
+    url: "/college-group/craft",
+  },
+  {
+    title: "青年工藝",
+    url: "/college-group/teenager",
+  },
+  {
+    title: "修護工藝",
+    url: "/college-group/repair",
+  },
+  {
+    title: "跨域工藝",
+    url: "/college-group/cross",
+  },
+  {
+    title: "線上工藝",
+    url: "/college-group/craft",
+  },
+]);
 </script>
 
 <template>
@@ -31,33 +75,33 @@ function handleClose(value) {
         <router-link :to="'/course-list'">探索課程</router-link>
       </li>
       <li class="position-relative">
-        <a href="javascript:;" @mouseover="collegeMenuShow = true" @mouseleave="collegeMenuShow = false">工藝學群</a>
-        <div class="college-slider" :class="{ slider: collegeMenuShow }" @mouseover="collegeMenuShow = true"
-          @mouseleave="collegeMenuShow = false">
+        <a
+          href="javascript:;"
+          @mouseover="collegeMenuShow = true"
+          @mouseleave="collegeMenuShow = false"
+          class="d-none d-lg-block"
+          >工藝學群</a
+        >
+        <a
+          href="javascript:;"
+          @click="collegeMenuShow = !collegeMenuShow"
+          class="d-block d-lg-none"
+          >工藝學群
+          <v-icon
+            icon="mdi-chevron-down"
+            class="toggle-icon"
+            :class="{ slider: collegeMenuShow }"
+          ></v-icon
+        ></a>
+        <div
+          class="college-slider"
+          :class="{ slider: collegeMenuShow }"
+          @mouseover="handleMouseEvents(true)"
+          @mouseleave="handleMouseEvents(false)"
+        >
           <ul>
-            <li>
-              <router-link :to="'/college-group/generation'">世代工藝</router-link>
-            </li>
-            <li>
-              <router-link :to="'/college-group/future'">未來工藝</router-link>
-            </li>
-            <li>
-              <router-link :to="'/college-group/craft'">生活工藝</router-link>
-            </li>
-            <li>
-              <router-link :to="'/college-group/craft'">技藝工藝</router-link>
-            </li>
-            <li>
-              <router-link :to="'/college-group/teenager'">青年工藝</router-link>
-            </li>
-            <li>
-              <router-link :to="'/college-group/repair'">修護工藝</router-link>
-            </li>
-            <li>
-              <router-link :to="'/college-group/cross'">跨域工藝</router-link>
-            </li>
-            <li>
-              <router-link :to="'/college-group/craft'">線上工藝</router-link>
+            <li v-for="(item, index) in collegeList" :key="index">
+              <router-link :to="item.url">{{ item.title }}</router-link>
             </li>
           </ul>
         </div>
@@ -143,7 +187,7 @@ function handleClose(value) {
       overflow: initial;
     }
 
-    &>li {
+    & > li {
       margin-left: 28px;
       font-weight: 400;
 
@@ -186,37 +230,73 @@ function handleClose(value) {
       transition: all 0.3s;
 
       @media (max-width: 1280px) {
-        top: 30px;
-        left: 50vw;
+        position: initial;
+        width: auto;
+        max-height: 0;
+        box-shadow: none;
+        transition: none;
+        overflow: hidden;
       }
 
-      @media (max-width: 600px) {
-        left: 58vw;
-        width: 100px;
-      }
+      // @media (max-width: 1280px) {
+      //   top: 30px;
+      //   left: 50vw;
+      // }
+
+      // @media (max-width: 600px) {
+      //   left: 58vw;
+      //   width: 100px;
+      // }
 
       &.slider {
         opacity: 1;
+        @media (max-width: 1280px) {
+          max-height: 100%;
+        }
       }
 
-      li {
-        &:last-child {
-          border-bottom: none;
+      ul {
+        @media (max-width: 1280px) {
+          padding-bottom: 15px;
         }
+        li {
+          transition: all 0.3s;
+          
+          &:last-child {
+            border-bottom: none;
+          }
 
-        &:hover {
-          background-color: #eeeeee;
-        }
+          &:hover {
+            background-color: #eee;
+            @media (max-width: 1280px) {
+              opacity: 0.8;
+              background-color: #fff;
+            }
+          }
 
-        a {
-          display: block;
-          width: 100%;
-          padding: 15px 10px;
+          a {
+            display: block;
+            width: 100%;
+            padding: 15px 10px;
+            @media (max-width: 1280px) {
+              padding: 15px 10px 15px 40px;
+            }
+          }
         }
       }
     }
   }
 
+  .toggle-icon {
+    margin-left: 5px;
+    position: absolute;
+    top: 17px;
+    transition: all 0.3s;
+    &.slider {
+      transform: rotate(180deg);
+    }
+  }
+
   .icon {
     display: none;
     transition: all 0.3s;

+ 57 - 5
src/views/CourseList.vue

@@ -46,6 +46,49 @@ async function search() {
     courseData.classes = courseAll.classes;
   }
 }
+
+// async function setFavoriteClass(classId, userId = 1) {
+//   // /api/add_favorite_class
+//   console.log("classId", classId);
+//   console.log("userId", userId);
+
+//   const formData = new FormData();
+//   formData.append("class_event_id", classId);
+//   formData.append("user_id", userId);
+
+//   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);
+//   }
+// }
+
+async function setFavoriteClass(classId, userId = 2) {
+  const url = `https://cmm.ai:8088/api/add_favorite_class?class_event_id=${classId}&user_id=${userId}`;
+
+  try {
+    const response = await axios.post(url, '', {
+      headers: {
+        'Accept': 'application/json'
+      }
+    });
+    console.log("add_favorite_class response", response);
+    getFavoriteClass(userId);
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+async function getFavoriteClass(userId) {
+  console.log("getFavoriteClass userId", userId);
+  try {
+    const response = await axios.get(`https://cmm.ai:8088/api/get_favorite_class?user_id=${userId}`);
+    console.log("getFavoriteClass response", response);
+  } catch (error) {
+    console.error(error);
+  }
+}
 </script>
 
 <template>
@@ -92,11 +135,7 @@ async function search() {
             :to="`/course-detail/${item.class_name_id}`"
             class="cover-img"
           >
-            <v-img
-              :src="item.cover_img"
-              height="220px"
-              cover
-            ></v-img>
+            <v-img :src="item.cover_img" height="220px" cover></v-img>
           </router-link>
           <v-card-title class="font-weight-medium">
             {{ item.name }}
@@ -127,6 +166,19 @@ async function search() {
               </li>
             </ul> -->
           </v-card-text>
+          <v-card-action class="d-block mt-5">
+            <button
+              class="favorites-btn"
+              @click="setFavoriteClass(item.class_name_id)"
+            >
+              <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>

+ 1 - 0
src/views/Login.vue

@@ -255,6 +255,7 @@ console.log("JWT 過期時間:", formattedExpiration);
               prepend-inner-icon="mdi-lock"
               variant="solo"
               density="compact"
+              type="password"
             ></v-text-field>
             <v-btn
               type="submit"