SyuanYu 1 gadu atpakaļ
vecāks
revīzija
5a9f2b2c4e
50 mainītis faili ar 2157 papildinājumiem un 2130 dzēšanām
  1. 1 1
      .env.development
  2. 3 0
      index.html
  3. 10 9
      src/App.vue
  4. 10 1
      src/assets/css/style.css
  5. 0 0
      src/assets/css/style.css.map
  6. 12 1
      src/assets/css/style.scss
  7. 33 22
      src/components/CourseCard.vue
  8. 5 3
      src/components/HomeList.vue
  9. 28 11
      src/components/Map.vue
  10. 63 36
      src/components/Navbar.vue
  11. 1 1
      src/components/PDFViewer.vue
  12. 255 6
      src/language/en.json
  13. 255 5
      src/language/zh.json
  14. 5 25
      src/router/index.js
  15. 64 42
      src/stores/store.js
  16. 13 14
      src/views/Article.vue
  17. 5 0
      src/views/ArticleDetail.vue
  18. 21 14
      src/views/CollegeGroup/Cfa.vue
  19. 24 129
      src/views/CollegeGroup/Craft.vue
  20. 25 24
      src/views/CollegeGroup/Cross.vue
  21. 14 12
      src/views/CollegeGroup/Future.vue
  22. 9 7
      src/views/CollegeGroup/Generation.vue
  23. 27 13
      src/views/CollegeGroup/Life.vue
  24. 13 4
      src/views/CollegeGroup/Life/Apprentice/Course.vue
  25. 14 12
      src/views/CollegeGroup/Life/Apprentice/Main.vue
  26. 14 13
      src/views/CollegeGroup/Life/Campus.vue
  27. 10 12
      src/views/CollegeGroup/Life/CraftJourney.vue
  28. 9 7
      src/views/CollegeGroup/Life/Shop.vue
  29. 2 0
      src/views/CollegeGroup/Main.vue
  30. 79 19
      src/views/CollegeGroup/Online.vue
  31. 14 9
      src/views/CollegeGroup/Proposal.vue
  32. 31 17
      src/views/CollegeGroup/Repair.vue
  33. 16 11
      src/views/CollegeGroup/Teenager.vue
  34. 89 351
      src/views/CourseDetail.vue
  35. 33 68
      src/views/CourseList.vue
  36. 209 297
      src/views/Courses/Create.vue
  37. 121 188
      src/views/Courses/Proposal.vue
  38. 26 37
      src/views/Courses/SetUp.vue
  39. 11 4
      src/views/Courses/Tutorial.vue
  40. 27 25
      src/views/Crafts.vue
  41. 23 18
      src/views/Home.vue
  42. 12 9
      src/views/News.vue
  43. 7 0
      src/views/NewsDetail.vue
  44. 194 218
      src/views/User/Courses.vue
  45. 29 36
      src/views/User/Dashboard.vue
  46. 22 9
      src/views/User/FavoriteClass.vue
  47. 136 86
      src/views/User/Passport.vue
  48. 113 276
      src/views/User/Profile.vue
  49. 18 26
      src/views/User/Proposal.vue
  50. 2 2
      src/views/User/Setting.vue

+ 1 - 1
.env.development

@@ -1,2 +1,2 @@
 VITE_API_URL=https://craftsplatform.ntcri.gov.tw
-VITE_API_IMG_URL=https://ntcri.org
+VITE_API_IMG_URL=https://craftsplatform.ntcri.gov.tw

+ 3 - 0
index.html

@@ -5,6 +5,9 @@
   <meta charset="UTF-8" />
   <link rel="icon" type="image/svg+xml" href="/src/assets/img/icon.png" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <meta property="og:title" content="臺灣工藝學校全球學習共享平台" />
+  <meta property="og:image" content="https://craftsplatform.ntcri.gov.tw/assets/logo.png" />
+  <meta property="og:description" content="藉由「工藝學校」的主體概念,推動臺灣工藝學校全球學習,以共享、友善、全人、全民的終身工藝手作平台,回饋於全國性工藝分校據點。" />
 
   <!-- Google Font -->
   <link rel="preconnect" href="https://fonts.googleapis.com">

+ 10 - 9
src/App.vue

@@ -22,14 +22,15 @@ onMounted(() => {
 
   window.addEventListener("scroll", checkScrollPosition);
 
+  // 聊天機器人 HubSpot
   // Start of HubSpot Embed Code
-  const script = document.createElement("script");
-  script.type = "text/javascript";
-  script.id = "hs-script-loader";
-  script.async = true;
-  script.defer = true;
-  script.src = "//js-na1.hs-scripts.com/43621630.js";
-  document.head.appendChild(script);
+  // const script = document.createElement("script");
+  // script.type = "text/javascript";
+  // script.id = "hs-script-loader";
+  // script.async = true;
+  // script.defer = true;
+  // script.src = "//js-na1.hs-scripts.com/43621630.js";
+  // document.head.appendChild(script);
   // End of HubSpot Embed Code
 });
 
@@ -57,8 +58,8 @@ onBeforeUnmount(() => {
   width: 3.75em !important;
   height: 3.75em !important;
   position: fixed !important;
-  right: 1em;
-  bottom: 5.625em;
+  right: 1.5em;
+  bottom: 1.5em;
   z-index: 1000;
   box-shadow: rgba(0, 0, 0, 0.1) 0 0.0625em 0.25em,
     rgba(0, 0, 0, 0.2) 0 0.125em 0.75em !important;

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

@@ -93,6 +93,12 @@ h2 {
   font-weight: 500;
 }
 
+hr {
+  margin: 1.25em 0;
+  border: none;
+  border-bottom: 0.06em solid #ccc;
+}
+
 *:focus-visible {
   outline: none;
 }
@@ -101,6 +107,10 @@ input:focus-visible {
   outline: 0.125em solid var(--sub-color);
 }
 
+.text-nowrap {
+  white-space: nowrap;
+}
+
 .v-enter-active,
 .v-leave-active {
   transition: opacity 0.5s ease;
@@ -974,7 +984,6 @@ input:focus-visible {
   display: flex;
   align-items: center;
   justify-content: end;
-  white-space: nowrap;
   line-height: 1.375em;
 }
 @media (max-width: 600px) {

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
src/assets/css/style.css.map


+ 12 - 1
src/assets/css/style.scss

@@ -98,14 +98,25 @@ h2 {
   font-weight: 500;
 }
 
+hr {
+  margin: 1.25em 0;
+  border: none;
+  border-bottom: 0.06em solid #ccc;
+}
+
 *:focus-visible {
   outline: none;
 }
 
+
 input:focus-visible {
   outline: 0.125em solid (var(--sub-color));
 }
 
+.text-nowrap {
+  white-space: nowrap;
+}
+
 // Vue Transition
 .v-enter-active,
 .v-leave-active {
@@ -1118,8 +1129,8 @@ input:focus-visible {
     display: flex;
     align-items: center;
     justify-content: end;
-    white-space: nowrap;
     line-height: 1.375em;
+    // white-space: nowrap;
 
     @media (max-width: 600px) {
       width: 7.8125em;

+ 33 - 22
src/components/CourseCard.vue

@@ -1,5 +1,5 @@
 <script setup>
-import { ref, reactive, watch } from "vue";
+import { ref, reactive, onMounted } from "vue";
 import { useMainStore } from "@/stores/store";
 import axios from "axios";
 
@@ -20,9 +20,10 @@ let favorites = reactive({
 
 // 加入收藏課程
 async function setFavoriteClass(classId) {
+  console.log("加入收藏課程");
   let isLogin = store.checkToken();
   if (!isLogin) {
-    store.openLoginDialog();
+    alert("請先登入會員!");
     return;
   }
 
@@ -31,26 +32,45 @@ async function setFavoriteClass(classId) {
   const url = `${store.apiUrl}/api/add_favorite_class?class_name_id=${classId}&access_token=${token}`;
   try {
     const response = await axios.post(url);
-    getFavoriteClass();
+    store.getFavoriteClass();
     progress.value = false;
+    console.log("加入收藏課程", response);
+    if (response.data.code === 200) {
+      alert("收藏成功!");
+    }
   } catch (error) {
     console.error(error);
   }
 }
 
 // 取得收藏課程
-async function getFavoriteClass() {
-  try {
-    const response = await axios.get(
-      `${store.apiUrl}/api/get_favorite_class?access_token=${token}`
-    );
-    favorites.list = response.data.favorite_courses;
-  } catch (error) {
-    console.error(error);
+// async function getFavoriteClass() {
+//   console.log("取得收藏課程", token);
+//   if (token) {
+//     try {
+//       const response = await axios.get(
+//         `${store.apiUrl}/api/get_favorite_class?access_token=${token}`
+//       );
+//       favorites.list = response.data.favorite_courses;
+//     } catch (error) {
+//       console.error(error);
+//     }
+//   }
+// }
+
+// 檢查收藏狀態
+function isClassFavorite(classId) {
+  if (store.favoritesList.length) {
+    let list = store.favoritesList.map((e) => e.class_name_id);
+    return list.includes(classId);
   }
 }
 
-getFavoriteClass();
+// onMounted(() => {
+//   setTimeout(() => {
+//     getFavoriteClass();
+//   });
+// });
 
 // 刪除收藏課程
 async function deleteFavoriteClass(classId) {
@@ -60,20 +80,12 @@ async function deleteFavoriteClass(classId) {
       `${store.apiUrl}/api/delete_favorite_class?class_name_id=${classId}&access_token=${token}`
     );
     progress.value = false;
-    getFavoriteClass();
+    store.getFavoriteClass();
   } catch (error) {
     console.error(error);
   }
 }
 
-// 檢查收藏狀態
-function isClassFavorite(classId) {
-  if (favorites.list) {
-    let list = favorites.list.map((e) => e.class_name_id);
-    return list.includes(classId);
-  }
-}
-
 // // 返回課程圖片網址
 // function getImageSrc(data) {
 //   console.log("data", data);
@@ -104,7 +116,6 @@ function isClassFavorite(classId) {
             ? $router.resolve(`/course-detail/${data.class_name_id}`).href
             : data.video_url
         "
-        target="_blank"
       >
         <div class="overflow-hidden">
           <v-img

+ 5 - 3
src/components/HomeList.vue

@@ -39,6 +39,7 @@ const props = defineProps({
         <router-link :to="`/news/${data.news_id}`" class="cover-img">
           <section class="d-flex flex-column pa-3">
             <h3>{{ data.title }}</h3>
+            <hr>
             <p v-html="data.content"></p>
           </section>
         </router-link>
@@ -52,6 +53,7 @@ const props = defineProps({
         >
           <section class="d-flex flex-column pa-3">
             <h3>{{ data.name }}</h3>
+            <hr>
             <p v-html="data.introduction"></p>
           </section>
         </router-link>
@@ -66,13 +68,13 @@ const props = defineProps({
   border-radius: 1.25em;
   background-image: url("@/assets/img/home/news-bg.png");
   h3 {
-    margin-bottom: 1.25em;
-    padding-bottom: 1.25em;
+    // margin-bottom: 1.25em;
+    // padding-bottom: 1.25em;
     font-size: 1.375em !important;
     font-weight: 500;
     line-height: 2em;
     letter-spacing: 0.0625em;
-    border-bottom: 0.0625em solid #ccc;
+    // border-bottom: 0.0625em solid #ccc;
   }
 
   h3,

+ 28 - 11
src/components/Map.vue

@@ -4,7 +4,7 @@ import "leaflet.markercluster";
 import "leaflet/dist/leaflet.css";
 import "leaflet.markercluster/dist/MarkerCluster.css";
 import "leaflet.markercluster/dist/MarkerCluster.Default.css";
-import { ref, reactive, onMounted, getCurrentInstance } from "vue";
+import { ref, reactive, onMounted, getCurrentInstance, watch } from "vue";
 import { useMainStore } from "@/stores/store";
 import axios from "axios";
 
@@ -18,23 +18,40 @@ onMounted(() => {
     data: [],
   });
 
-  // 監聽 Pinia 存儲中的值變化
-  store.$subscribe((item) => {
-    if (item.events.key === "lng") {
-      console.log("經緯度", item.events.target);
+  store.$subscribe((mutation, state) => {
+    console.log("state >>>", state);
+    console.log("state mapLocation >>>", state.mapLocation.lat);
 
+    if (state.mapLocation.lat) {
       const newCenter = {
-        lat: item.events.target.lat,
-        lng: item.events.target.lng,
+        lat: state.mapLocation.lat,
+        lng: state.mapLocation.lng,
       };
 
-      console.log("newCenter", newCenter);
-
       // 將地圖中心設為指定的經緯度
       map.setView([newCenter.lat, newCenter.lng], map.getZoom());
     }
   });
 
+  // 監聽 Pinia 存儲中的值變化
+  store.$subscribe((item) => {
+    console.log("item", item);
+    console.log("item.events", item.events);
+    // if (item.events?.key === "lng") {
+    //   console.log("經緯度", item.events.target);
+
+    //   const newCenter = {
+    //     lat: item.events.target.lat,
+    //     lng: item.events.target.lng,
+    //   };
+
+    //   console.log("newCenter", newCenter);
+
+    //   // 將地圖中心設為指定的經緯度
+    //   map.setView([newCenter.lat, newCenter.lng], map.getZoom());
+    // }
+  });
+
   (async function getData() {
     try {
       const response = await axios.get(`${store.apiUrl}/api/get_school`);
@@ -120,8 +137,8 @@ onMounted(() => {
 
   // 限制在台灣範圍
   const taiwanBounds = L.latLngBounds(
-    L.latLng(20.68, 119.16),
-    L.latLng(25.56, 122.48)
+    L.latLng(20, 118),
+  L.latLng(26.5, 124)
   );
   map.setMaxBounds(taiwanBounds);
   map.setMaxZoom(16); // 最大縮放級別

+ 63 - 36
src/components/Navbar.vue

@@ -3,7 +3,7 @@ import { ref, reactive, onMounted } from "vue";
 import { useRouter } from "vue-router";
 import { useMainStore } from "@/stores/store";
 import { useI18n } from "vue-i18n";
-import Login from "@/views/Login.vue";
+// import Login from "@/views/Login.vue";
 
 const router = useRouter();
 const store = useMainStore();
@@ -49,27 +49,27 @@ function handleClick(url) {
 
 const collegeList = reactive([
   {
-    title: "未來工藝",
+    title: "college_group_1",
     url: "/college-group/future",
   },
   {
-    title: "技藝工藝",
+    title: "college_group_2",
     url: "/college-group/craft",
   },
   {
-    title: "跨域增能",
+    title: "college_group_3",
     url: "/college-group/cross",
   },
   {
-    title: "線上工藝",
+    title: "college_group_4",
     url: "/college-group/online",
   },
   {
-    title: "希望工程",
+    title: "college_group_5",
     url: "/college-group/craft-for-all",
   },
   {
-    title: "生活工藝",
+    title: "college_group_6",
     url: "/college-group/life",
   },
   // {
@@ -117,7 +117,9 @@ function changeLang() {
 function handleLogout() {
   localStorage.removeItem("token");
   localStorage.removeItem("AuthToken");
-  window.location.reload();
+  window.location.href =
+    "https://member.moc.gov.tw/MOCMC/M0003/ottLogout?SYS_ID=CRAFT_NTCRI&urlRedirectTo=https://craftsplatform.ntcri.gov.tw/";
+  // window.location.reload();
 }
 </script>
 
@@ -128,7 +130,7 @@ function handleLogout() {
     </router-link>
     <ul class="menu align-center" :class="{ slider: menuShow }">
       <li>
-        <router-link :to="'/crafts'">工藝學</router-link>
+        <router-link :to="'/crafts'">{{ t("navbar.crafts") }}</router-link>
       </li>
 
       <li class="position-relative">
@@ -140,7 +142,8 @@ function handleLogout() {
           title="前往工藝學群頁面"
         >
           <span class="d-flex align-center">
-            工藝學群 <v-icon icon="mdi-chevron-down"></v-icon>
+            {{ t("navbar.craft_groups") }}
+            <v-icon icon="mdi-chevron-down"></v-icon>
           </span>
         </a>
         <a
@@ -148,7 +151,7 @@ function handleLogout() {
           @click="collegeMenuShow = !collegeMenuShow"
           class="d-block d-lg-none"
           title="前往工藝學群頁面"
-          >工藝學群
+          >{{ t("navbar.craft_groups") }}
           <v-icon
             icon="mdi-chevron-down"
             class="toggle-icon"
@@ -167,7 +170,7 @@ function handleLogout() {
                 href="javascript:;"
                 @click="handleClick(item.url)"
                 :title="`前往${item.title}頁面`"
-                >{{ item.title }}</a
+                >{{ t(`${item.title}`) }}</a
               >
 
               <!-- <router-link :to="item.url">{{ item.title }}</router-link> -->
@@ -177,11 +180,13 @@ function handleLogout() {
       </li>
 
       <li>
-        <router-link :to="'/news'">重要訊息</router-link>
+        <router-link :to="'/news'">{{ t("news") }}</router-link>
       </li>
 
       <li>
-        <router-link :to="'/course-list'">探索課程</router-link>
+        <router-link :to="'/course-list'">{{
+          t("navbar.craft_course")
+        }}</router-link>
       </li>
 
       <!-- <li>
@@ -199,7 +204,8 @@ function handleLogout() {
           @mouseleave="coursesMenuShow = false"
           class="d-none d-lg-flex align-center"
           title="前往我要開課頁面"
-          >我要開課<v-icon icon="mdi-chevron-down"></v-icon
+          >{{ t("navbar.create_course")
+          }}<v-icon icon="mdi-chevron-down"></v-icon
         ></router-link>
 
         <a
@@ -207,7 +213,7 @@ function handleLogout() {
           @click="coursesMenuShow = !coursesMenuShow"
           class="d-block d-lg-none"
           title="前往我要開課頁面"
-          >我要開課
+          >{{ t("navbar.create_course") }}
           <v-icon
             icon="mdi-chevron-down"
             class="toggle-icon"
@@ -222,12 +228,14 @@ function handleLogout() {
         >
           <ul>
             <li>
-              <router-link :to="'/setup-courses/tutorial'"
-                >開課教學</router-link
-              >
+              <router-link :to="'/setup-courses/tutorial'">{{
+                t("tutorial.title")
+              }}</router-link>
             </li>
             <li>
-              <router-link :to="'/setup-courses'">創建課程</router-link>
+              <router-link :to="'/setup-courses'">{{
+                t("navbar.create_course")
+              }}</router-link>
             </li>
           </ul>
         </div>
@@ -250,7 +258,7 @@ function handleLogout() {
         <a
           v-if="!store.loginState"
           href="https://member.moc.gov.tw/MOCMC/A0001/list?SYS_ID=CRAFT_NTCRI"
-          >會員登入</a
+          >{{ t("navbar.login") }}</a
         >
 
         <div v-else>
@@ -261,13 +269,13 @@ function handleLogout() {
             class="d-none d-lg-block"
           >
             <span class="d-flex align-center">
-              會員專區 <v-icon icon="mdi-chevron-down"></v-icon>
+              {{ t("navbar.member") }} <v-icon icon="mdi-chevron-down"></v-icon>
             </span>
           </router-link>
 
-          <router-link :to="'/user/profile'" class="d-block d-lg-none"
-            >會員專區</router-link
-          >
+          <router-link :to="'/user/profile'" class="d-block d-lg-none">{{
+            t("navbar.member")
+          }}</router-link>
         </div>
 
         <div
@@ -279,20 +287,35 @@ function handleLogout() {
         >
           <ul>
             <li>
-              <router-link :to="'/user/profile'">個人檔案</router-link>
+              <router-link :to="'/user/profile'">{{
+                t("navbar.profile")
+              }}</router-link>
             </li>
             <li>
-              <router-link :to="'/user/passport'">學習護照</router-link>
+              <router-link :to="'/user/passport'">{{
+                t("navbar.passport")
+              }}</router-link>
             </li>
             <li>
-              <router-link :to="'/user/courses'">我的開課</router-link>
+              <router-link :to="'/user/courses'">{{
+                t("navbar.courses")
+              }}</router-link>
             </li>
             <li>
-              <router-link :to="'/user/favorite-class'">我的收藏</router-link>
+              <router-link :to="'/user/favorite-class'">{{
+                t("navbar.collections")
+              }}</router-link>
             </li>
             <li>
-              <a href="javascript:;" @click="handleLogout()" title="點擊以登出">
-                登出
+              <a
+                href="javascript:;"
+                @click="
+                  store.logout();
+                  store.mocLogout();
+                "
+                title="點擊以登出"
+              >
+                {{ t("navbar.logout") }}
               </a>
             </li>
           </ul>
@@ -306,10 +329,12 @@ function handleLogout() {
           >中/EN</a
         >
       </li>
-      <li class="d-none d-lg-block">
+
+      <!-- <li class="d-none d-lg-block">
         <v-icon icon="mdi-magnify"></v-icon>
-      </li>
-      <li class="d-none d-lg-block position-relative">
+      </li> -->
+
+      <!-- <li class="d-none d-lg-block position-relative">
         <a
           href="javascript:;"
           @mouseover="otherMenuShow = true"
@@ -337,7 +362,8 @@ function handleLogout() {
             </li>
           </ul>
         </div>
-      </li>
+      </li> -->
+
       <li
         v-for="(item, index) in otherList"
         :key="index"
@@ -380,7 +406,7 @@ function handleLogout() {
 
   img {
     width: 100%;
-    max-width: 400px;
+    max-width: 370px;
     margin-top: 0.3125em;
     @media (max-width: 1400px) {
       max-width: 300px;
@@ -441,6 +467,7 @@ function handleLogout() {
         padding: 2.5em 0;
         color: #000;
         text-decoration: none;
+        line-height: 1.3;
         transition: all 0.3s;
 
         &:hover {

+ 1 - 1
src/components/PDFViewer.vue

@@ -39,7 +39,7 @@ function setPDFUrl(pdf) {
   <div class="pt-16" v-if="!store.isMobile">
     <iframe
       ref="pdfIframe"
-      src="https://ntcri.org/pdf/Re_Ceramic.pdf"
+      src="https://craftsplatform.ntcri.gov.tw/pdf/Re_Ceramic.pdf"
       width="100%"
       height="700px"
       frameborder="0"

+ 255 - 6
src/language/en.json

@@ -1,11 +1,12 @@
 {
-  "college_group_1": "Online Craft",
-  "college_group_2": "Craftsmanship",
-  "college_group_3": "Future Craft",
-  "college_group_4": "Cross-domain Skills",
-  "college_group_5": "Hope Project",
+  "college_group_1": "Future Craft",
+  "college_group_2": "Technical Craft",
+  "college_group_3": "Cross-Domain Skills",
+  "college_group_4": "Online Craft",
+  "college_group_5": "Dream Factory",
   "college_group_6": "Life Craft",
   "home": {
+    "title": "Home",
     "title_1": "NTCRI",
     "title_2": "Craft Groups",
     "title_3": "Taiwan Craft Map",
@@ -145,5 +146,253 @@
     "step_4_1": "Wait until our staff approves your course registration <br> and we will send you an email.",
     "step_4_2": "The seed of craft has grown. <br> Isn't that easy? <br> Visit “My Courses” to view course information and registration status <br> or edit text description."
   },
-  "see_more": "Click to see more"
+  "see_more": "Click to see more",
+  "navbar": {
+    "crafts": "Crafts",
+    "craft_groups": "Craft Groups",
+    "news": "What's News",
+    "craft_course": "Craft Course",
+    "create_course": "Create a Course",
+    "login": "Login",
+    "member": "Member Area",
+    "profile": "Profile",
+    "passport": "Learning Passport",
+    "courses": "My Courses",
+    "collections": "My Collections",
+    "logout": "Logout"
+  },
+  "profile": {
+    "identity": "Identity",
+    "displayName": "Display Identity",
+    "account": "Account",
+    "name": "Name",
+    "full_name": "Full Name",
+    "gender": "Gender",
+    "birthday": "Birthday",
+    "postalCode": "Postal Code",
+    "email": "Email",
+    "phoneNumber": "Phone Number",
+    "address": "Address",
+    "edit_profile": "Edit Profile",
+    "save": "Save",
+    "creator": "Creator",
+    "creator_name": "Creator Name",
+    "job_position": "Job Position",
+    "company": "Company",
+    "about_me": "About Me",
+    "skill": "Skill",
+    "material_tags": "Material Tags",
+    "skill_tags": "Skill Tags",
+    "partners": "Partners",
+    "create_partners": "Create Partners",
+    "processing_manufacturers": "Processing Manufacturers",
+    "processing_techniques_info": "Processing Techniques Information",
+    "applied_products": "Applied Products",
+    "applied_materials": "Applied Materials",
+    "applied_technique": "Applied Technique",
+    "products_materials": "Products Materials",
+    "manufactured_tools": "Manufactured Tools",
+    "collaboration_case_info": "Collaboration Case Information",
+    "case_name": "Case Name",
+    "production_quantity": "Production Quantity",
+    "collaboration_tags": "Collaboration Tags",
+    "technique_name": "Technique Name",
+    "technique_type": "Technique Type",
+    "technique_tags": "Technique Tags (Help people discover your works)"
+  },
+  "form": {
+    "create": "Create",
+    "cancel": "Cancel",
+    "check": "Check",
+    "close": "Close",
+    "save": "Save",
+    "name": "Name",
+    "instructor_name": "Instructor Name",
+    "job_nature": "Job Nature",
+    "teaching_experience": "Teaching Experience",
+    "craft_skills": "Craft Skills",
+    "related_certifications": "Related Certifications",
+    "social_media": "Social Media",
+    "instructor_intro": "Instructor Intro",
+    "full_time": "Full Time",
+    "part_time": "Part Time",
+    "instructor": "Instructor",
+    "all_study_groups": "All Study Groups",
+    "contact_information": "Contact Information",
+    "location_name": "Location Name",
+    "location_address": "Location Address",
+    "location_email": "Location Email",
+    "location_phone": "Location Phone",
+    "location_introduction": "Location Introduction",
+    "organizer": "Organizer",
+    "course_name": "Course Name",
+    "craft_category": "Craft Category",
+    "course_location": "Course Location",
+    "course_number": "Course Number",
+    "course_intro": "Course Introduction",
+    "course_price": "Course Price",
+    "course_quota": "Course Quota",
+    "course_duration": "Course Duration",
+    "course_content": "Course Content (Curriculum)",
+    "course_difficulty": "Course Difficulty",
+    "course_state": "Course State",
+    "management": "Management",
+    "course_management": "Course",
+    "attendance_management": "Attendance",
+    "edit_location": "Edit Location",
+    "edit_course": "Edit Course",
+    "edit_session": "Edit Session",
+    "close_course": "Close Course",
+    "student_information": "Student Information",
+    "course_create": "Create Date",
+    "session_name": "Session Name",
+    "session_date": "Session Date",
+    "classroom": "Classroom",
+    "payment": "Payment",
+    "payment_method": "Payment Method",
+    "remarks": "Remarks",
+    "course_type": "Course Type",
+    "start_date": "Start Date",
+    "end_date": "End Date",
+    "start_time": "Start Time",
+    "end_time": "End Time",
+    "registration_time": "Registration Time",
+    "registration_start_date": "Registration Start Date",
+    "registration_deadline": "Registration Deadline",
+    "registration_start_time": "Registration Start Time",
+    "registration_end_time": "Registration End Time",
+    "on_site_fee": "On-site",
+    "target_audience": "Target",
+    "target_placeholder": "All audiences, 18-65 years old, college students majoring in crafts, arts, and design, businesses or workshops specializing in woodwork",
+    "create_new_session": "Create New Session",
+    "minimum_enrollment": "Minimum Enrollment",
+    "enter_minimum_enrollment": "Please enter the minimum enrollment number",
+    "fields_blank": "Some fields are left blank",
+    "course_time_not_filled": "Course time has not been filled in yet",
+    "auto_fill_location_address": "Auto-fill Location Address",
+    "auto_fill_location_name": "Auto-fill Location Name",
+    "cover_image": "Cover Image",
+    "upload_course_cover_image": "Upload Course Cover Image",
+    "choose_image": "Choose Image",
+    "provide_name": "Provide your name or your workshop, brand, or institute name",
+    "safe_accessible_location": "Courses must be held at a safe and accessible location",
+    "enter_telephone_number": "Enter telephone number (including area code, e.g., 02-23887066)",
+    "classes_monday": "Classes on Monday",
+    "classes_schedule": "Classes on Monday and Wednesday",
+    "one_day_courses": "One-day courses",
+    "periodic_courses": "Periodic courses",
+    "periodic_courses_note": "When selecting periodic courses, the start and end date may not be on the same day",
+    "delete_session": "Delete the session",
+    "delete_session_confirmation": "Are you sure you want to delete the session?",
+    "content_requirements": "Content must be at least 100 characters, and can only contain information related to craft.",
+    "display_on_course_intro": "Will be displayed on course introduction for contact purposes",
+    "registration_consent": "Registration Consent",
+    "course_registration": "Course Registration"
+  },
+  "chapter": {
+    "example": "See example",
+    "title": "Chapter",
+    "name_1": "Technique demonstration, grinding and painting practice",
+    "name_2": "Sanding practice",
+    "name_3": "Polishing and lacquering",
+    "name_4": "Finishing up"
+  },
+  "all": "All",
+  "awaiting_approval": "Awaiting approval",
+  "approved": "Approved",
+  "rejected": "Rejected",
+  "closed": "Closed",
+  "proposal_awaiting_approval": "Awaiting approval",
+  "send": "Send",
+  "name": "Name",
+  "next": "Next",
+  "back": "Back",
+  "agree": "Agree",
+  "disagree": "Disagree",
+  "sign_up": "Sign Up",
+  "registration_closed": "Closed",
+  "search": "Search",
+  "description": "Description",
+  "news": "News",
+  "exhibitions": "Exhibitions",
+  "more_news": "More",
+  "cities": "Cities",
+  "districts": "Districts",
+  "reset": "Reset",
+  "category": "Category",
+  "article": "Article",
+  "course_list": "Course List",
+  "keyword_search": "Keyword Search",
+  "search_courses": "Search Courses",
+  "craft_plus": "CraftPlus",
+  "craft_for_all": "Craft for All",
+  "about_craft_for_all": "About Craft for All",
+  "event": "Event",
+  "all_courses": "All Courses",
+  "learning_hours_courses": "Learning hours",
+  "recommended_courses": "Other recommended courses",
+  "research_and_development_subsidy": "Research and development subsidy",
+  "research_plan": "Research plan",
+  "professional_craft_courses": "Professional craft courses",
+  "restoration_courses": "Restoration courses",
+  "craft_restoration": "Craft restoration",
+  "craft_restorer": "Craft restorer / Craftsperson (Choose one)",
+  "restoration_story": "Restoration story",
+  "variety_of_courses": "Providing a variety of professional cross-domain courses",
+  "teaching_method": "Teaching method",
+  "online_experience_courses": "Online courses",
+  "faq": "FAQ",
+  "media_coverage": "Media Coverage",
+  "no_found": "No items found match the search criteria",
+  "total_count": "Total Count",
+  "location_keywords": "Location Keywords",
+  "location_info": "Location Information",
+  "course_info": "Course Information",
+  "session_info": "Session Information",
+  "session": "課程場次",
+  "select_location": "Select location",
+  "before_opening_course": "Before opening a course, please select a course location",
+  "before_create_course": "Before opening a course, please create a course location",
+  "add_new_location": "Want to add a new location? Click here to send a request.",
+  "proposal": "Proposal",
+  "proposal_date": "Proposal Date",
+  "proposal_content": "Proposal Content",
+  "go_to_proposal": "Click here to send a request",
+  "course_instructor_proposal": "Course Instructor Proposal",
+  "create_course_instructor": "Create course instructor",
+  "select_course_instructor": "Select course instructor",
+  "instructor_resume": "Course Instructor Resume",
+  "assign_instructor": "Assign Course Instructor",
+  "plan_course_content": "Plan Course Content",
+  "enter_basic_info": "Enter Basic Information",
+  "craft_resume_intro": "Start creating your craft resume so that students get to understand you!",
+  "course_creation_bridge": "Providing a complete and clear explanation on the course content is the bridge to connect course instructors and students!",
+  "congratulations_course_creation": "Congratulations! You have successfully created a course!",
+  "congratulations_course_proposal": "Congratulations! You have successfully created a proposal!",
+  "create_course": "Create a Course",
+  "return_to_home": "Return to Homepage",
+  "go_to_course_area": "Go to Course Area",
+  "course_review_criteria": "Course Review Criteria",
+  "course_criteria_1": "Course content must be true and appropriate, and must not contain unrelated information (including advertising), plagiarism, and prohibited content such as sexually suggestive content, violence, personal attacks, and racism (including but not limited to gender, race, and sexual orientation)",
+  "course_criteria_2": "Provided images must match the course content",
+  "course_criteria_3": "Course content must be logical",
+  "confirmation_messages": {
+    "waiting_for_staff": "Please wait for confirmation from our staff",
+    "waiting_for_email": "Please wait for a confirmation email",
+    "go_to_my_courses": "Go to My Courses to view and update"
+  },
+  "learning_hours": {
+    "description_1": "When you register a course on the Platform with learning hours",
+    "description_2": "you can collect points and exchange them with gifts (craft materials)!",
+    "total": "Total",
+    "hour": "Hours",
+    "hours": "Hours",
+    "point": "Points",
+    "points": "Points",
+    "total_learning": "Total learning",
+    "completed_hours": "Completed course hours",
+    "completed_from": "from 2023",
+    "redeem_criteria": "Collect 20 points to redeem 1 pack of",
+    "craft_materials": "craft materials"
+  }
 }

+ 255 - 5
src/language/zh.json

@@ -1,11 +1,12 @@
 {
-  "college_group_1": "線上工藝",
+  "college_group_1": "未來工藝",
   "college_group_2": "技藝工藝",
-  "college_group_3": "未來工藝",
-  "college_group_4": "跨域增能",
+  "college_group_3": "跨域增能",
+  "college_group_4": "線上工藝",
   "college_group_5": "希望工程",
   "college_group_6": "生活工藝",
   "home": {
+    "title": "首頁",
     "title_1": "臺灣工藝學校",
     "title_2": "工藝學群",
     "title_3": "全台工藝地圖",
@@ -57,7 +58,7 @@
     "note": "注意:以下服務條款為中文翻譯成英文的版本,若中文版與英文版有不相符之處,最終解釋以中文版為準。",
     "list": {
       "title_1": "一、認知與接受條款",
-      "content_1": "「一日學徒平台(簡稱本網站)」係由國立台灣工藝研究發展中心(以下稱本中心)建置之工藝相關課程、體驗活動等資訊媒合網站,並依據本服務條款提供服務(以下稱「本服務」)。當您使用本服務時,即表示您已閱讀、瞭解並同意接受本服務條款之所有內容。本中心有權依據本網站提供服務之需求,於任何時間修改或變更本服務條款之內容,建議您隨時注意該等修改或變更。您於任何修改或變更後繼續使用本服務,視為您已閱讀、瞭解並同意接受該等修改或變更。如果您不同意本服務條款的內容,或者您所屬的國家或地域排除本服務條款內容之全部或一部時,您應立即停止使用本服務。",
+      "content_1": "「臺灣工藝學校全球學習共享平台(簡稱本網站)」係由國立台灣工藝研究發展中心(以下稱本中心)建置之工藝相關課程、體驗活動等資訊媒合網站,並依據本服務條款提供服務(以下稱「本服務」)。當您使用本服務時,即表示您已閱讀、瞭解並同意接受本服務條款之所有內容。本中心有權依據本網站提供服務之需求,於任何時間修改或變更本服務條款之內容,建議您隨時注意該等修改或變更。您於任何修改或變更後繼續使用本服務,視為您已閱讀、瞭解並同意接受該等修改或變更。如果您不同意本服務條款的內容,或者您所屬的國家或地域排除本服務條款內容之全部或一部時,您應立即停止使用本服務。",
       "title_2": "二、帳號登入與本服務使用",
       "content_2_1": "(一) 使用者得透過第三方平台帳戶(如:FB、Google等,以本網站當時支援之第三方平台為準)登入使用本網站。本中心將儲存各該第三方平台所允許儲存作為識別使用者帳戶之資訊。",
       "content_2_2": "(二) 若您為工藝教育者,擬透過本網站刊登工藝相關課程或體驗活動等資訊,您須另行同意「工藝教育者上傳課程同意規範」,始得上傳課程;若您擬透過本網站報名相關課程,您須另行同意「民眾報名須知」,始得報名課程。",
@@ -145,5 +146,254 @@
     "step_4_1": "等待後台人員審核資料後 <br> 就會寄送開課完成通知 Email 給您",
     "step_4_2": "工藝種子已經成長茁壯 <br> 是不是很簡單 <br> 隨時前往【我的開課】就可以看到課程資訊、報名狀況 <br> 也可以編輯文字內容"
   },
-  "see_more": "點我看更多課程"
+  "see_more": "點我看更多課程",
+  "navbar": {
+    "crafts": "工藝學",
+    "craft_groups": "工藝學群",
+    "news": "重要訊息",
+    "craft_course": "探索課程",
+    "create_course": "我要開課",
+    "login": "會員登入",
+    "member": "會員專區",
+    "profile": "個人檔案",
+    "passport": "學習護照",
+    "courses": "我的開課",
+    "collections": "我的收藏",
+    "logout": "登出"
+  },
+  "profile": {
+    "identity": "身份",
+    "displayName": "顯示名稱",
+    "account": "帳號",
+    "name": "姓名",
+    "full_name": "真實姓名",
+    "gender": "性別",
+    "birthday": "出生日期",
+    "postalCode": "郵遞區號",
+    "email": "電子郵件",
+    "phoneNumber": "手機號碼",
+    "address": "現居地",
+    "edit_profile": "修改個人資料",
+    "save": "儲存變更",
+    "creator": "創作者",
+    "creator_name": "創作者名稱",
+    "job_position": "職稱",
+    "company": "公司、工作室",
+    "about_me": "關於我",
+    "skill": "技能",
+    "material_tags": "擅長材質標籤",
+    "skill_tags": "擅長技能標籤",
+    "partners": "合作過的夥伴",
+    "create_partners": "新增合作夥伴",
+    "processing_manufacturers": "加工製造者",
+    "processing_techniques_info": "加工技法資訊",
+    "applied_products": "應用產品",
+    "applied_materials": "應用材質",
+    "applied_technique": "應用技法",
+    "products_materials": "產品材質",
+    "manufactured_tools": "製成機具",
+    "collaboration_case_info": "合作案例資訊",
+    "case_name": "案例名稱",
+    "production_quantity": "生產數量",
+    "collaboration_tags": "合作標籤",
+    "technique_name": "技法名稱",
+    "technique_type": "技法類型",
+    "technique_tags": "技法標籤(協助人們發現您的作品)"
+  },
+  "form": {
+    "create": "創建",
+    "cancel": "取消",
+    "check": "查看",
+    "close": "關閉",
+    "save": "儲存",
+    "name": "姓名",
+    "instructor_name": "工藝教育者姓名",
+    "job_nature": "工作性質",
+    "teaching_experience": "教學經驗",
+    "craft_skills": "專長工藝技能",
+    "related_certifications": "工藝相關證照",
+    "social_media": "社群媒體",
+    "instructor_intro": "老師介紹",
+    "full_time": "全職",
+    "part_time": "兼職",
+    "instructor": "講師",
+    "all_study_groups": "所屬學群",
+    "contact_information": "聯絡方式",
+    "location_name": "工坊名稱",
+    "location_address": "工坊地址",
+    "location_email": "工坊 Email",
+    "location_phone": "公開電話",
+    "location_introduction": "工坊簡介",
+    "organizer": "主辦單位",
+    "course_name": "課程名稱",
+    "craft_category": "工藝類別",
+    "course_location": "課程地點",
+    "course_number": "課程編號",
+    "course_intro": "課程簡介",
+    "course_price": "課程費用",
+    "course_quota": "課程名額",
+    "course_duration": "課程時間",
+    "course_content": "課程內容 (課綱)",
+    "course_difficulty": "課程難度",
+    "course_state": "課程狀態",
+    "management": "管理",
+    "course_management": "課程管理",
+    "attendance_management": "出席管理",
+    "edit_location": "編輯據點",
+    "edit_course": "編輯課程",
+    "edit_session": "編輯場次",
+    "close_course": "關閉課程",
+    "student_information": "學員資訊",
+    "course_create": "開課日期",
+    "session_name": "場次名稱",
+    "session_date": "場次日期",
+    "classroom": "課程教室",
+    "payment": "付款",
+    "payment_method": "收費方式",
+    "remarks": "備註",
+    "course_type": "課程類型",
+    "start_date": "起始日期",
+    "end_date": "結束日期",
+    "start_time": "起始時間",
+    "end_time": "結束時間",
+    "registration_time": "報名時間",
+    "registration_start_date": "報名日期",
+    "registration_deadline": "報名截止",
+    "registration_end_time": "截止時間",
+    "on_site_fee": "現場收費",
+    "target_audience": "適合對象",
+    "target_placeholder": "ex. 不拘、18-65 歲、大專院校工藝、美術、設計科系學生、從事木作相關之業者或個人工作室",
+    "create_new_session": "建立新場次",
+    "minimum_enrollment": "最低開課人數",
+    "enter_minimum_enrollment": "請輸入開課門檻人數",
+    "fields_blank": "尚有欄位未填寫",
+    "course_time_not_filled": "尚未填寫課程時間",
+    "auto_fill_location_address": "自動帶入據點地址",
+    "auto_fill_location_name": "自動帶入據點名稱",
+    "cover_image": "課程封面",
+    "upload_course_cover_image": "上傳課程封面",
+    "choose_image": "選擇相片上傳",
+    "provide_name": "可以是您的工作室或品牌名稱/教學單位/您的姓名",
+    "safe_accessible_location": "需在安全且便於學徒到達之地點開課",
+    "enter_telephone_number": "請輸入電話號碼包含區碼 (e.g., 02-23887066)",
+    "classes_monday": "每週一上課",
+    "classes_schedule": "每週一三上課",
+    "one_day_courses": "一日課程",
+    "periodic_courses": "週期課程",
+    "periodic_courses_note": "選擇週期課程時,起始日期與結束日期不可為同一天",
+    "delete_session": "刪除場次",
+    "delete_session_confirmation": "確定要刪除該場次嗎?",
+    "content_requirements": "內容不得少於 100 個字元,且不得含有工藝無關之資訊",
+    "display_on_course_intro": "會顯示於課程介紹中,想了解課程資訊者聯繫用途",
+    "registration_consent": "報名同意書",
+    "course_registration": "課程報名表"
+  },
+  "chapter": {
+    "example": "查看範例",
+    "title": "章節",
+    "name_1": "技法示範與研磨實作,上漆",
+    "name_2": "研磨實作",
+    "name_3": "推光、擦漆",
+    "name_4": "作品總整理"
+  },
+  "all": "全部",
+  "awaiting_approval": "未審核",
+  "approved": "已審核",
+  "rejected": "已駁回",
+  "closed": "已關閉",
+  "proposal_awaiting_approval": "提案審核中",
+  "send": "送出",
+  "name": "名稱",
+  "next": "下一步",
+  "back": "上一步",
+  "agree": "同意",
+  "disagree": "不同意",
+  "sign_up": "報名",
+  "registration_closed": "已截止",
+  "search": "查詢",
+  "description": "描述",
+  "news": "重要訊息",
+  "exhibitions": "當期展覽",
+  "more_news": "觀看更多訊息",
+  "cities": "縣市",
+  "districts": "區域",
+  "reset": "重設",
+  "category": "類別",
+  "article": "知識文章",
+  "course_list": "課程清單",
+  "keyword_search": "關鍵字搜尋",
+  "search_courses": "搜尋課程",
+  "craft_plus": "臺灣工藝數位鏈",
+  "craft_for_all": "臺灣綠工藝希望工程",
+  "about_craft_for_all": "關於臺灣綠工藝希望工程",
+  "event": "活動現場",
+  "all_courses": "所有課程",
+  "learning_hours_courses": "學習時數課程",
+  "recommended_courses": "其他推薦課程",
+  "research_and_development_subsidy": "研發補助",
+  "research_plan": "研究計畫",
+  "professional_craft_courses": "專業工藝課程",
+  "restoration_courses": "修護課程",
+  "craft_restoration": "修護工藝",
+  "craft_restorer": "工藝修護師",
+  "restoration_story": "修護故事",
+  "variety_of_courses": "提供多元領域專業課程",
+  "teaching_method": "教學型態",
+  "online_experience_courses": "線上體驗課程",
+  "faq": "常見問題",
+  "media_coverage": "媒體報導",
+  "no_found": "沒有符合搜尋條件的項目",
+  "total_count": "總筆數",
+  "location_keywords": "請輸入據點關鍵字",
+  "location_info": "據點資訊",
+  "course_info": "課程資訊",
+  "session_info": "場次資訊",
+  "session": "課程場次",
+  "select_location": "選擇據點",
+  "before_opening_course": "創建課程之前,請先選擇您的據點(上課地址)",
+  "before_create_course": "創建課程之前,請先新增您的據點(上課地址)",
+  "add_new_location": "想建立新據點?請點此前往提案",
+  "proposal": "提案",
+  "my_proposal": "我的提案",
+  "proposal_date": "提案日期",
+  "proposal_content": "提案內容",
+  "go_to_proposal": "點此前往提案",
+  "instructor": "工藝教育者",
+  "course_instructor_proposal": "工藝教育者提案",
+  "create_course_instructor": "新增工藝教育者",
+  "select_course_instructor": "選擇工藝教育者",
+  "instructor_resume": "工藝教育者履歷",
+  "assign_instructor": "指派工藝教育者",
+  "plan_course_content": "規劃開課內容",
+  "enter_basic_info": "填寫基本資料",
+  "craft_resume_intro": "開始打造屬於您的工藝履歷,讓學徒對你印象深刻吧!",
+  "course_creation_bridge": "完整且清楚的課程建立,是連接工藝老師與學徒的重要橋梁!",
+  "congratulations_course_creation": "恭喜您完成創建課程!",
+  "congratulations_course_proposal": "恭喜您完成課程提案!",
+  "create_course": "創建課程",
+  "return_to_home": "回到首頁",
+  "go_to_course_area": "前往開課專區",
+  "course_review_criteria": "課程審核標準",
+  "course_criteria_1": "課程內容正確合宜,無錯誤與誇大不實、不含與課程無關的推銷廣告、內容無抄襲侵權、無涉及任何色情、血腥暴力、人身攻擊、歧視(包含性別、種族、性傾向等)。",
+  "course_criteria_2": "提供圖片需與課程內容相符。",
+  "course_criteria_3": "教學內容符合邏輯。",
+  "confirmation_messages": {
+    "waiting_for_staff": "請等待後台人員確認您的課程資訊",
+    "waiting_for_email": "待您收到開課完成的 Email",
+    "go_to_my_courses": "就可以前往【我的開課】觀看及修改您的"
+  },
+  "learning_hours": {
+    "description_1": "凡在平台選擇「學習時數課程」並報名",
+    "description_2": "即可累積點數,兌換精美好禮-工藝材料包",
+    "total": "總共",
+    "hour": "小時",
+    "hours": "時數",
+    "point": "點",
+    "points": "點數",
+    "total_learning": "累積學習",
+    "completed_hours": "自 2023 年起至今",
+    "completed_from": "完成課程時數",
+    "redeem_criteria": "累積 20 點即可兌換 1 份",
+    "craft_materials": "工藝材料包"
+  }
 }

+ 5 - 25
src/router/index.js

@@ -361,7 +361,11 @@ const router = createRouter({
 
 // 檢查登入狀態
 router.beforeEach((to, from, next) => {
-  window.scrollTo({ top: 0, behavior: "smooth" });
+  // 切換頁面時回到畫面最上方
+  setTimeout(() => {
+    window.scrollTo({ top: 0 });
+  }, 300);
+
   const store = useMainStore();
   store.checkToken();
 
@@ -371,38 +375,14 @@ router.beforeEach((to, from, next) => {
     const haveToken = store.loginState;
     console.log('router store token', store.token);
     // let isTokenExpired = store.checkTokenExpiration(); // 檢查 Token 是否過期
-    // console.log('token是否過期', isTokenExpired);
     if (haveToken) {
       next(); // 允許使用者訪問目標頁面
-      // if (!isTokenExpired) {
-      //   next(); // 允許使用者訪問目標頁面
-      // } else {
-      //   store.loginState = false;
-      //   localStorage.removeItem("token");
-      //   next('/'); // 若 token 過期將重定向至首頁
-      // }
-      // next();
     } else {
       console.log('>> 沒有token');
       store.loginDialog = true; // 開啟登入視窗
       next(); // 若無 token 直接輸入網址前往時,將重定向至首頁
     }
   }
-  //  else if (to.meta.checkToken) {
-  //   const haveToken = store.loginState;
-  //   console.log('store token', store.token);
-  //   let isTokenExpired = store.checkTokenExpiration(); // 檢查 Token 是否過期
-  //   console.log('token是否過期', isTokenExpired);
-  //   // 已有 Token && 尚未過期
-  //   if (haveToken && !isTokenExpired) {
-  //     next();
-  //   } else {
-  //     console.log('無Token');
-  //     store.loginState = false;
-  //     localStorage.removeItem("token");
-  //     next();
-  //   }
-  // } 
   else {
     // 如果目標路由不需要驗證權限,直接允許使用者訪問
     next();

+ 64 - 42
src/stores/store.js

@@ -15,7 +15,9 @@ export const useMainStore = defineStore('mainStore', {
     mapLocation: { // 地圖經緯度
       lat: "",
       lng: ""
-    }
+    },
+    favoritesList: [],
+    userInfo: {}
   }),
   getters: {
     isMobile: () => {
@@ -43,8 +45,8 @@ export const useMainStore = defineStore('mainStore', {
     // 檢查登入狀態
     checkToken() {
       console.log('檢查登入狀態');
-      const token = localStorage.getItem("token");
-      const authToken = localStorage.getItem("AuthToken");
+      const token = sessionStorage.getItem("token");
+      const authToken = sessionStorage.getItem("AuthToken");
 
       if (token && authToken) {
         this.token = token;
@@ -63,20 +65,22 @@ export const useMainStore = defineStore('mainStore', {
     async handleAuthToken() {
       console.log('handleAuthToken');
       const queryParams = this.getURLParams();
-      const authToken = localStorage.getItem("AuthToken");
-
+      const authToken = sessionStorage.getItem("AuthToken");
+      console.log('判斷網址是否有 AuthCode 參數', queryParams.AuthCode);
       if (queryParams.AuthCode) {
         console.log('authToken', authToken);
         // 帶 AuthCode 取 AuthToken
         if (authToken === null || authToken === "undefined" || !authToken) {
           await this.getAuthToken(queryParams.AuthCode);
         }
+      } else {
+        console.log('沒有 AuthCode');
+        return;
       }
 
-      console.log('authToken >>', authToken);
-
       if (authToken !== null || authToken !== "undefined") {
         // this.loginState = true;
+        console.log('取藝文中心會員資料');
         this.getMocProfile(); // 取藝文中心會員資料
       }
     },
@@ -92,7 +96,7 @@ export const useMainStore = defineStore('mainStore', {
           console.log('有 AuthToken');
           this.loginState = true;
           this.authToken = response.data.authToken;
-          localStorage.setItem("AuthToken", response.data.authToken);
+          sessionStorage.setItem("AuthToken", response.data.authToken);
           console.log('this.authToken', this.authToken);
         }
       } catch (error) {
@@ -103,7 +107,7 @@ export const useMainStore = defineStore('mainStore', {
     async getMocProfile() {
       console.log('getMocProfile');
       console.log('this.authToken', this.authToken);
-      const authToken = localStorage.getItem("AuthToken");
+      const authToken = sessionStorage.getItem("AuthToken");
       console.log('authToken', authToken);
 
       let url = `${this.apiUrl}/api/get_user_data_from_MOC?AuthToken=${authToken}`;
@@ -128,7 +132,7 @@ export const useMainStore = defineStore('mainStore', {
       }
     },
     async getToken() {
-      const authToken = localStorage.getItem("AuthToken");
+      const authToken = sessionStorage.getItem("AuthToken");
 
       let url = `${this.apiUrl}/api/get_token?authToken=${authToken}&email=${this.mocProfile.EMAIL}`;
 
@@ -138,7 +142,8 @@ export const useMainStore = defineStore('mainStore', {
         console.log('Token:', response.data.token);
         if (response.data.token) {
           this.token = response.data.token;
-          localStorage.setItem("token", response.data.token);
+          sessionStorage.setItem("token", response.data.token);
+          // this.getUserInfo();
           // return true;
         } else {
           // return false;
@@ -161,56 +166,58 @@ export const useMainStore = defineStore('mainStore', {
     },
     logout() {
       this.loginState = false;
-      localStorage.removeItem("token");
-      localStorage.removeItem("AuthToken");
-      console.log('logout(不導向首頁)');
+      sessionStorage.removeItem("token");
+      sessionStorage.removeItem("AuthToken");
       // return this.router.push("/");
     },
+    mocLogout() {
+      window.location.href =
+        "https://member.moc.gov.tw/MOCMC/M0003/ottLogout?SYS_ID=CRAFT_NTCRI&urlRedirectTo=https://craftsplatform.ntcri.gov.tw/";
+    },
     getImageUrl(name) {
       return new URL(`/src/assets/img/${name}`, import.meta.url).href;
     },
     getTotalPages(totalRecords, recordsPerPage) { // 計算頁數
       return Math.ceil(totalRecords / recordsPerPage);
     },
-    setProfile(data) {
-      this.profile = data;
-    },
-    async getProfile() {
-      try {
-        const response = await axios.get(
-          `${this.apiUrl}/api/information?token=${this.token}`
-        );
-        console.log('getProfile', response);
-        if (response.data.msg === "no access" || !response.data.msg) {
-          console.log('getProfile logout');
-          this.logout();
-          return;
-        } else {
-          this.setProfile(response.data.msg);
-        }
-      } catch (error) {
-        console.error(error);
-      }
-    },
+    // setProfile(data) {
+    //   this.profile = data;
+    // },
+    // async getProfile() {
+    //   try {
+    //     const response = await axios.get(
+    //       `${this.apiUrl}/api/information?token=${this.token}`
+    //     );
+    //     console.log('getProfile', response);
+    //     if (response.data.msg === "no access" || !response.data.msg) {
+    //       console.log('getProfile logout');
+    //       this.logout();
+    //       return;
+    //     } else {
+    //       this.setProfile(response.data.msg);
+    //     }
+    //   } catch (error) {
+    //     console.error(error);
+    //   }
+    // },
     async getUserInfo() {
-      let token = localStorage.getItem("token");
-      console.log('getUserInfo token', token);
-      if (token) {
+      let token = sessionStorage.getItem("token");
+      console.log('是否已有資料', Object.keys(this.userInfo).length);
+      // 判斷 token & userInfo 是否已存在
+      if (token && !Object.keys(this.userInfo).length) {
         try {
           const response = await axios.get(
             `${this.apiUrl}/api/get_user_information?access_token=${token}`
           );
 
-          console.log('getUserInfo response', response);
+          this.userInfo = response.data.user_inform[0];
+          console.log('getUserInfo userInfo', this.userInfo);
 
-          return response;
+          // return response;
         } catch (error) {
           console.error(error);
         }
       }
-      // else {
-      //   this.logout();
-      // }
     },
     // 將日期格式轉成純字串
     formatDate(date) {
@@ -312,6 +319,21 @@ export const useMainStore = defineStore('mainStore', {
       alert(`${title}!點擊確認後將重新整理頁面。`);
       window.location.reload();
     },
+    // 取得收藏課程
+    async getFavoriteClass() {
+      console.log("store 取得收藏課程", this.token);
+      if (this.token) {
+        try {
+          const response = await axios.get(
+            `${this.apiUrl}/api/get_favorite_class?access_token=${this.token}`
+          );
+          this.favoritesList = response.data.favorite_courses;
+          console.log('store.favoritesList', this.favoritesList);
+        } catch (error) {
+          console.error(error);
+        }
+      }
+    },
     checkTokenExpiration() {
       if (this.token) {
         // 解碼 JWT

+ 13 - 14
src/views/Article.vue

@@ -2,13 +2,12 @@
 import { ref, reactive, watch, computed, onMounted } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 import axios from "axios";
-import moment from "moment";
 import Navbar from "@/components/Navbar.vue";
 import PDFViewer from "@/components/PDFViewer.vue";
-// import bookList from "@/utils/useBookList";
-// import readList from "@/utils/useReadList";
 
+const { t } = useI18n();
 const route = useRoute();
 const router = useRouter();
 const store = useMainStore();
@@ -83,12 +82,12 @@ const paginatedList = computed(() => {
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "知識文章",
+    title: "article",
     disabled: true,
     href: "/article",
   },
@@ -199,11 +198,11 @@ function isAnchorLink(url) {
   <div class="bg-img">
     <v-container class="position-relative">
       <div class="article-content">
-        <v-breadcrumbs
-          :items="breadcrumbs"
-          divider="/"
-          class="my-5"
-        ></v-breadcrumbs>
+        <v-breadcrumbs :items="breadcrumbs" divider="/" class="my-5">
+          <template v-slot:title="{ item }">
+            {{ t(item.title) }}
+          </template>
+        </v-breadcrumbs>
 
         <v-row class="px-10 pb-3 tag-btn">
           <v-col
@@ -234,7 +233,7 @@ function isAnchorLink(url) {
               v-model="searchInput"
               type="text"
               @keyup.enter="search()"
-              placeholder="關鍵字搜尋"
+              :placeholder="t('keyword_search')"
             />
             <button @click="search()">
               <img
@@ -248,7 +247,7 @@ function isAnchorLink(url) {
             class="d-flex justify-center align-center error me-4"
           >
             <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
-            沒有符合搜尋條件的項目
+            {{ t("no_found") }}
           </div>
         </div>
 
@@ -317,7 +316,7 @@ function isAnchorLink(url) {
               v-model="searchInput"
               type="text"
               @keyup.enter="search()"
-              placeholder="關鍵字搜尋"
+              :placeholder="t('keyword_search')"
             />
             <button @click="search()">
               <img
@@ -331,7 +330,7 @@ function isAnchorLink(url) {
             class="d-flex justify-center align-center error me-4"
           >
             <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
-            沒有符合搜尋條件的項目
+            {{ t("no_found") }}
           </div>
         </div>
 

+ 5 - 0
src/views/ArticleDetail.vue

@@ -177,6 +177,11 @@ ul {
   }
 }
 
+section {
+  line-height: 2;
+  letter-spacing: 1px;
+}
+
 .content {
   padding: 1.25em;
   background-image: url("@/assets/img/news/news-bg.webp");

+ 21 - 14
src/views/CollegeGroup/Cfa.vue

@@ -1,23 +1,30 @@
 <script setup>
-import { ref, reactive, watch } from "vue";
+import { ref, reactive, onMounted, watch } from "vue";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 import axios from "axios";
 import moment from "moment";
 import CourseCard from "@/components/CourseCard.vue";
 
+const { t } = useI18n();
 const store = useMainStore();
+
+onMounted(() => {
+  store.getFavoriteClass();
+});
+
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "工藝學群",
+    title: "navbar.craft_groups",
     disabled: true,
   },
   {
-    title: "希望工程",
+    title: "college_group_5",
     disabled: true,
   },
 ]);
@@ -157,14 +164,14 @@ getClass();
 </script>
 
 <template>
-  <v-breadcrumbs
-    :items="breadcrumbs"
-    divider="/"
-    class="mt-10 pa-0"
-  ></v-breadcrumbs>
+  <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-10 pa-0">
+    <template v-slot:title="{ item }">
+      {{ t(item.title) }}
+    </template>
+  </v-breadcrumbs>
 
   <div class="title">
-    <h2>關於臺灣綠工藝希望工程</h2>
+    <h2>{{ t("about_craft_for_all") }}</h2>
   </div>
 
   <v-row>
@@ -189,7 +196,7 @@ getClass();
   </v-row>
 
   <div class="title" ref="activityList">
-    <h2>活動現場</h2>
+    <h2>{{ t("event") }}</h2>
   </div>
 
   <v-row>
@@ -248,7 +255,7 @@ getClass();
   ></v-pagination>
 
   <div class="title" ref="mediaList">
-    <h2>媒體報導</h2>
+    <h2>{{ t("media_coverage") }}</h2>
   </div>
 
   <ul class="tgc-report">
@@ -277,7 +284,7 @@ getClass();
   ></v-pagination>
 
   <div>
-    <h2 class="title">常見問題</h2>
+    <h2 class="title">{{ t("faq") }}</h2>
   </div>
 
   <v-expansion-panels>
@@ -288,7 +295,7 @@ getClass();
   </v-expansion-panels>
 
   <div ref="courseList">
-    <h2 class="title">臺灣綠工藝希望工程-課程</h2>
+    <h2 class="title">{{ t("craft_for_all") }}</h2>
   </div>
 
   <div class="d-flex justify-center mb-10" v-if="courseLoading">

+ 24 - 129
src/views/CollegeGroup/Craft.vue

@@ -1,13 +1,18 @@
 <script setup>
-import { ref, reactive, watch } from "vue";
+import { ref, reactive, onMounted, watch } from "vue";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 import axios from "axios";
-import moment from "moment";
 import CourseCard from "@/components/CourseCard.vue";
 import ArticleCard from "@/components/ArticleCard.vue";
 
+const { t } = useI18n();
 const store = useMainStore();
 
+onMounted(() => {
+  store.getFavoriteClass();
+});
+
 let pageAmount = ref(6); // 每頁顯示筆數
 
 // 課程清單
@@ -99,16 +104,16 @@ getClasses("pinkoi", pinkoiPageNum.value);
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "工藝學群",
+    title: "navbar.craft_groups",
     disabled: true,
   },
   {
-    title: "技藝工藝",
+    title: "college_group_2",
     disabled: true,
   },
 ]);
@@ -163,24 +168,24 @@ const articles = reactive({
 </script>
 
 <template>
-  <v-breadcrumbs
-    :items="breadcrumbs"
-    divider="/"
-    class="mt-10 pa-0"
-  ></v-breadcrumbs>
+  <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-10 pa-0">
+    <template v-slot:title="{ item }">
+      {{ t(item.title) }}
+    </template>
+  </v-breadcrumbs>
 
   <div
     class="d-flex flex-column flex-sm-row align-center justify-space-between"
     id="lightCraft"
   >
-    <h2 class="title">專業工藝課程</h2>
+    <h2 class="title">{{ t("professional_craft_courses") }}</h2>
     <div class="search">
       <span>
         <input
           v-model="searchInput"
           type="text"
           @keyup.enter="search()"
-          placeholder="關鍵字搜尋"
+          :placeholder="t('keyword_search')"
         />
         <button @click="search()">
           <img
@@ -194,7 +199,7 @@ const articles = reactive({
         class="d-flex justify-center align-center error me-4"
       >
         <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
-        沒有符合搜尋條件的項目
+        {{ t("no_found") }}
       </div>
     </div>
   </div>
@@ -230,14 +235,14 @@ const articles = reactive({
   <div
     class="d-flex flex-column flex-sm-row align-center justify-space-between"
   >
-    <h2 class="title" id="repairCourse">修護課程</h2>
+    <h2 class="title" id="repairCourse">{{ t("restoration_courses") }}</h2>
     <!-- <div class="search">
       <span>
         <input
           v-model="searchInput"
           type="text"
           @keyup.enter="search()"
-          placeholder="關鍵字搜尋"
+          :placeholder="t('keyword_search')"
         />
         <button @click="search()">
           <img src="@/assets/img/news/news-search-icon.png" alt="臺灣工藝學校全球學習共享平台" />
@@ -248,7 +253,7 @@ const articles = reactive({
         class="d-flex justify-center align-center error me-4"
       >
         <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
-        沒有符合搜尋條件的項目
+        {{ t("no_found") }}
       </div>
     </div> -->
   </div>
@@ -317,7 +322,7 @@ const articles = reactive({
     </v-col>
   </v-row> -->
 
-  <h2 class="title" id="repairCourse">修護工藝</h2>
+  <h2 class="title" id="repairCourse">{{ t("craft_restoration") }}</h2>
 
   <v-row>
     <v-col cols="12" md="6">
@@ -383,7 +388,7 @@ const articles = reactive({
     <button class="past-btn">觀看已過期課程</button>
   </div> -->
 
-  <h2 class="title">工藝修護師</h2>
+  <h2 class="title">{{ t("craft_restorer") }}</h2>
 
   <v-row class="master-list">
     <v-col
@@ -426,123 +431,13 @@ const articles = reactive({
     </v-col>
   </v-row>
 
-  <h2 class="title" id="repairArticle">修護故事</h2>
+  <h2 class="title" id="repairArticle">{{ t("restoration_story") }}</h2>
 
   <ul class="story-list">
     <li v-for="(item, index) in articles.list" :key="index" class="mb-16">
       <ArticleCard :data="item" type="article" />
     </li>
   </ul>
-
-  <!-- <div
-    class="d-flex flex-column flex-sm-row align-center justify-space-between title"
-    id="campusCourse"
-  >
-    <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" v-if="campusLoading">
-    <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 campus.classes"
-      :key="index"
-      class="pa-5"
-    >
-      <CourseCard :data="item" />
-    </v-col>
-    <v-col cols="12">
-      <v-pagination
-        v-model="campusPageNum"
-        :length="campusTotalPages"
-        rounded="circle"
-        class="mt-16"
-      ></v-pagination>
-    </v-col>
-  </v-row> -->
-
-  <!-- <div
-    class="d-flex flex-column flex-sm-row align-center justify-space-between title"
-    id="pinkoiCourse"
-  >
-    <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" v-if="pinkoiLoading">
-    <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 pinkoi.classes"
-      :key="index"
-      class="pa-5"
-    >
-      <CourseCard :data="item" />
-    </v-col>
-    <v-col cols="12">
-      <v-pagination
-        v-model="pinkoiPageNum"
-        :length="pinkoiTotalPages"
-        rounded="circle"
-        class="mt-16"
-      ></v-pagination>
-    </v-col>
-  </v-row> -->
 </template>
 
 <style lang="scss" scoped>

+ 25 - 24
src/views/CollegeGroup/Cross.vue

@@ -1,23 +1,24 @@
 <script setup>
 import { ref, reactive, watch } from "vue";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 import axios from "axios";
-import moment from "moment";
 
+const { t } = useI18n();
 const store = useMainStore();
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "工藝學群",
+    title: "navbar.craft_groups",
     disabled: true,
   },
   {
-    title: "跨域增能",
+    title: "college_group_3",
     disabled: true,
   },
 ]);
@@ -124,16 +125,16 @@ async function handleSearch() {
 </script>
 
 <template>
-  <v-breadcrumbs
-    :items="breadcrumbs"
-    divider="/"
-    class="mt-10 pa-0"
-  ></v-breadcrumbs>
+  <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-10 pa-0">
+    <template v-slot:title="{ item }">
+      {{ t(item.title) }}
+    </template>
+  </v-breadcrumbs>
 
   <div class="list">
     <section>
-      <h3>跨域增能</h3>
-      <p>
+      <h3>{{ t("college_group_3") }}</h3>
+      <p class="text-left">
         培育能承接文化生產者的內涵與工藝知識,並轉化成一般人能同理感受並更進一步在生活中學習應用的人才。
         <br />
         主要專業並非工藝而是更偏向社會傳播,或其他八大藝術專業或民俗文化專業。但能進一步了解文化生產者。
@@ -143,15 +144,15 @@ async function handleSearch() {
     </section>
 
     <section>
-      <h3>提供多元領域專業課程</h3>
-      <p>
+      <h3>{{ t("variety_of_courses") }}</h3>
+      <p class="text-left">
         法務知識、財務稅務、資金籌措、藝術行政、市場分析、產品開發、消費者管理、藝術藏家、
         品牌設計、市場通路、行銷策略、展覽企劃、文案撰寫、公關溝通、其他類及,策略課程
       </p>
     </section>
 
     <section>
-      <h3>教學型態</h3>
+      <h3>{{ t("teaching_method") }}</h3>
       <div class="d-flex flex-column flex-sm-row justify-center">
         <p>
           素養型 <br />
@@ -173,14 +174,14 @@ async function handleSearch() {
     ref="listLocation"
     class="d-flex flex-column flex-sm-row align-center justify-space-between"
   >
-    <h2 class="title">所有課程</h2>
+    <h2 class="title">{{ t("all_courses") }}</h2>
     <div class="search">
       <span>
         <input
           v-model="searchInput"
           type="text"
           @keyup.enter="handleSearch()"
-          placeholder="關鍵字搜尋"
+          :placeholder="t('keyword_search')"
         />
         <button @click="handleSearch()">
           <img
@@ -194,7 +195,7 @@ async function handleSearch() {
         class="d-flex justify-center align-center error me-4"
       >
         <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
-        沒有符合搜尋條件的項目
+        {{ t("no_found") }}
       </div>
     </div>
   </div>
@@ -205,7 +206,7 @@ async function handleSearch() {
         <v-col cols="12" sm="6" md="12">
           <v-select
             v-model="selectCategory"
-            label="類別"
+            :label="t('category')"
             :items="[
               '全部',
               '財務稅務',
@@ -288,16 +289,16 @@ async function handleSearch() {
   ></v-pagination>
 
   <span v-if="courese.list.length" class="text-gray total-item"
-    >總筆數:{{ totalNum }}</span
+    >{{ t("total_count") }}:{{ totalNum }}</span
   >
 
-  <div class="mt-16">
+  <!-- <div class="mt-16">
     <h2 class="title">工藝中心開課</h2>
   </div>
 
-  <p class="cooming-soon">「籌備中 cooming soon」</p>
+  <p class="cooming-soon">「籌備中 cooming soon」</p> -->
 
-  <h2 class="title pb-10">臺灣工藝數位鏈</h2>
+  <h2 class="title pb-10">{{ t("craft_plus") }}</h2>
 
   <div class="craftplus-item mb-6"></div>
 
@@ -583,8 +584,8 @@ async function handleSearch() {
   .work-list {
     img {
       height: 280px;
-    width: 100%;
-    object-fit: cover;
+      width: 100%;
+      object-fit: cover;
       border-radius: 20px;
     }
 

+ 14 - 12
src/views/CollegeGroup/Future.vue

@@ -1,22 +1,24 @@
 <script setup>
 import { ref, reactive } from "vue";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 import axios from "axios";
 
+const { t } = useI18n();
 const store = useMainStore();
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "工藝學群",
+    title: "navbar.craft_groups",
     disabled: true,
   },
   {
-    title: "未來工藝",
+    title: "college_group_1",
     disabled: true,
   },
 ]);
@@ -40,13 +42,13 @@ let read = reactive({
 </script>
 
 <template>
-  <v-breadcrumbs
-    :items="breadcrumbs"
-    divider="/"
-    class="mt-10 p-0"
-  ></v-breadcrumbs>
+  <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-10 p-0">
+    <template v-slot:title="{ item }">
+      {{ t(item.title) }}
+    </template>
+  </v-breadcrumbs>
 
-  <h2 class="mb-0 title">研發補助</h2>
+  <h2 class="mb-0 title">{{ t("research_and_development_subsidy") }}</h2>
 
   <v-container class="pa-0 pa-sm-3">
     <div class="content">
@@ -99,7 +101,7 @@ let read = reactive({
     </div>
   </v-container>
 
-  <h2 class="title">研究計畫</h2>
+  <h2 class="title">{{ t("research_plan") }}</h2>
 
   <v-container class="book-block">
     <v-row
@@ -141,7 +143,7 @@ let read = reactive({
     </v-row>
   </v-container>
 
-  <div>
+  <!-- <div>
     <h2 class="title">國際工藝村</h2>
     <p class="cooming-soon">「籌備中 cooming soon」</p>
   </div>
@@ -149,7 +151,7 @@ let read = reactive({
   <div>
     <h2 class="title">青年工藝</h2>
     <p class="cooming-soon">「籌備中 cooming soon」</p>
-  </div>
+  </div> -->
 
   <!-- <button class="sloped-btn">點我閱讀</button> -->
 </template>

+ 9 - 7
src/views/CollegeGroup/Generation.vue

@@ -1,7 +1,9 @@
 <script setup>
 import { reactive } from "vue";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 
+const { t } = useI18n();
 const store = useMainStore();
 
 const data = reactive([
@@ -49,12 +51,12 @@ const data = reactive([
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "工藝學群",
+    title: "navbar.craft_groups",
     disabled: true,
   },
   {
@@ -65,11 +67,11 @@ const breadcrumbs = reactive([
 </script>
 
 <template>
-  <v-breadcrumbs
-    :items="breadcrumbs"
-    divider="/"
-    class="mt-10 p-0"
-  ></v-breadcrumbs>
+  <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-10 p-0">
+    <template v-slot:title="{ item }">
+      {{ t(item.title) }}
+    </template>
+  </v-breadcrumbs>
 
   <h2 class="title">人間國寶</h2>
 

+ 27 - 13
src/views/CollegeGroup/Life.vue

@@ -1,8 +1,10 @@
 <script setup>
 import { ref, reactive } from "vue";
-import axios from "axios";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
+import axios from "axios";
 
+const { t } = useI18n();
 const store = useMainStore();
 
 // 所有文章
@@ -24,33 +26,36 @@ let articles = reactive({
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "工藝學群",
+    title: "navbar.craft_groups",
     disabled: true,
   },
   {
-    title: "生活工藝",
+    title: "college_group_6",
     disabled: true,
   },
 ]);
 </script>
 
 <template>
-  <v-breadcrumbs
-    :items="breadcrumbs"
-    divider="/"
-    class="mt-10 pa-0"
-  ></v-breadcrumbs>
+  <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-10 pa-0">
+    <template v-slot:title="{ item }">
+      {{ t(item.title) }}
+    </template>
+  </v-breadcrumbs>
 
   <v-container class="pa-0 life-list">
     <router-link to="/college-group/life/shop">
       <v-row>
         <v-col cols="12" md="4" lg="5" class="pa-0 overflow-hidden">
-          <img src="@/assets/img/college-group/life/cover-01.png" alt="臺灣工藝學校全球學習共享平台" />
+          <img
+            src="@/assets/img/college-group/life/cover-01.png"
+            alt="臺灣工藝學校全球學習共享平台"
+          />
         </v-col>
         <v-col cols="12" md="8" lg="7" class="content">
           <h3 class="mb-5">旅物 SHOP</h3>
@@ -74,7 +79,10 @@ const breadcrumbs = reactive([
           </p>
         </v-col>
         <v-col cols="12" md="4" lg="5" class="pa-0 overflow-hidden">
-          <img src="@/assets/img/college-group/life/cover-02.png" alt="臺灣工藝學校全球學習共享平台" />
+          <img
+            src="@/assets/img/college-group/life/cover-02.png"
+            alt="臺灣工藝學校全球學習共享平台"
+          />
         </v-col>
       </v-row>
     </router-link>
@@ -92,7 +100,10 @@ const breadcrumbs = reactive([
           </p>
         </v-col>
         <v-col cols="12" md="4" lg="5" class="pa-0 overflow-hidden">
-          <img src="@/assets/img/college-group/life/cover-03.png" alt="臺灣工藝學校全球學習共享平台" />
+          <img
+            src="@/assets/img/college-group/life/cover-03.png"
+            alt="臺灣工藝學校全球學習共享平台"
+          />
         </v-col>
       </v-row>
     </router-link>
@@ -100,7 +111,10 @@ const breadcrumbs = reactive([
     <router-link to="/college-group/life/campus">
       <v-row>
         <v-col cols="12" md="4" lg="5" class="pa-0 overflow-hidden">
-          <img src="@/assets/img/college-group/life/cover-04.png" alt="臺灣工藝學校全球學習共享平台" />
+          <img
+            src="@/assets/img/college-group/life/cover-04.png"
+            alt="臺灣工藝學校全球學習共享平台"
+          />
         </v-col>
         <v-col cols="12" md="8" lg="7" class="content">
           <h3 class="mb-10">校園扎根</h3>

+ 13 - 4
src/views/CollegeGroup/Life/Apprentice/Course.vue

@@ -1,11 +1,17 @@
 <script setup>
-import { ref, reactive, watch } from "vue";
+import { ref, reactive, onMounted, watch } from "vue";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 import axios from "axios";
 import CourseCard from "@/components/CourseCard.vue";
 
+const { t } = useI18n();
 const store = useMainStore();
 
+onMounted(() => {
+  store.getFavoriteClass();
+});
+
 let pinkoiCourse = ref(null);
 let pinkoi = reactive({
   classes: [],
@@ -57,10 +63,13 @@ getClasses();
           v-model="searchInput"
           type="text"
           @keyup.enter="search()"
-          placeholder="關鍵字搜尋"
+          :placeholder="t('keyword_search')"
         />
         <button @click="search()">
-          <img src="@/assets/img/news/news-search-icon.png" alt="臺灣工藝學校全球學習共享平台" />
+          <img
+            src="@/assets/img/news/news-search-icon.png"
+            alt="臺灣工藝學校全球學習共享平台"
+          />
         </button>
       </span>
       <div
@@ -68,7 +77,7 @@ getClasses();
         class="d-flex justify-center align-center error me-4"
       >
         <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
-        沒有符合搜尋條件的項目
+        {{ t("no_found") }}
       </div>
     </div>
   </div>

+ 14 - 12
src/views/CollegeGroup/Life/Apprentice/Main.vue

@@ -1,15 +1,17 @@
 <script setup>
 import { ref, reactive } from "vue";
-import moment from "moment";
+import { useI18n } from "vue-i18n";
+
+const { t } = useI18n();
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "生活工藝",
+    title: "college_group_6",
     href: "/college-group/life",
     disabled: false,
   },
@@ -24,10 +26,10 @@ const categoryList = reactive([
     title: "關於一日學徒",
     path: "/college-group/life/apprentice/about",
   },
-  {
-    title: "學徒選課資訊",
-    path: "/college-group/life/apprentice/course",
-  },
+  // {
+  //   title: "學徒選課資訊",
+  //   path: "/college-group/life/apprentice/course",
+  // },
   // {
   //   title: "最新訊息",
   //   path: "/college-group/life/apprentice/news",
@@ -48,11 +50,11 @@ const categoryList = reactive([
 </script>
 
 <template>
-  <v-breadcrumbs
-    :items="breadcrumbs"
-    divider="/"
-    class="mt-10 p-0"
-  ></v-breadcrumbs>
+  <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-10 p-0">
+    <template v-slot:title="{ item }">
+      {{ t(item.title) }}
+    </template>
+  </v-breadcrumbs>
 
   <h2 class="title">一日學徒</h2>
 

+ 14 - 13
src/views/CollegeGroup/Life/Campus.vue

@@ -2,7 +2,9 @@
 import { ref, reactive } from "vue";
 import axios from "axios";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 
+const { t } = useI18n();
 const store = useMainStore();
 
 let campus = reactive({
@@ -27,12 +29,12 @@ let campus = reactive({
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "生活工藝",
+    title: "college_group_6",
     href: "/college-group/life",
     disabled: false,
   },
@@ -44,11 +46,11 @@ const breadcrumbs = reactive([
 </script>
 
 <template>
-  <v-breadcrumbs
-    :items="breadcrumbs"
-    divider="/"
-    class="mt-10 pa-0"
-  ></v-breadcrumbs>
+  <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-10 pa-0">
+    <template v-slot:title="{ item }">
+      {{ t(item.title) }}
+    </template>
+  </v-breadcrumbs>
 
   <h2 class="title">校園扎根</h2>
 
@@ -73,12 +75,11 @@ const breadcrumbs = reactive([
       :key="item"
       class="py-5 info"
     >
-      <a
-        :href="
-          $router.resolve(`/article-detail/${item.article_id}`).href
-        "
-      >
-        <img :src="`${store.imgUrl}/${item.cover_img}`" alt="臺灣工藝學校全球學習共享平台" />
+      <a :href="$router.resolve(`/article-detail/${item.article_id}`).href">
+        <img
+          :src="`${store.imgUrl}/${item.cover_img}`"
+          alt="臺灣工藝學校全球學習共享平台"
+        />
       </a>
       <section class="mt-4">
         <h3>{{ item.title }}</h3>

+ 10 - 12
src/views/CollegeGroup/Life/CraftJourney.vue

@@ -2,7 +2,9 @@
 import { reactive } from "vue";
 import axios from "axios";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 
+const { t } = useI18n();
 const store = useMainStore();
 
 let travel = reactive({
@@ -25,12 +27,12 @@ let travel = reactive({
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "生活工藝",
+    title: "college_group_6",
     href: "/college-group/life",
     disabled: false,
   },
@@ -42,11 +44,11 @@ const breadcrumbs = reactive([
 </script>
 
 <template>
-  <v-breadcrumbs
-    :items="breadcrumbs"
-    divider="/"
-    class="mt-10 pa-0"
-  ></v-breadcrumbs>
+  <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-10 pa-0">
+    <template v-slot:title="{ item }">
+      {{ t(item.title) }}
+    </template>
+  </v-breadcrumbs>
 
   <h2 class="title">工藝行旅</h2>
 
@@ -70,11 +72,7 @@ const breadcrumbs = reactive([
       :key="item"
       class="py-5 info"
     >
-      <a
-        :href="
-          $router.resolve(`/article-detail/${item.article_id}`).href
-        "
-      >
+      <a :href="$router.resolve(`/article-detail/${item.article_id}`).href">
         <v-img
           :lazy-src="`${store.imgUrl}/${item.cover_img}`"
           :src="`${store.imgUrl}/${item.cover_img}`"

+ 9 - 7
src/views/CollegeGroup/Life/Shop.vue

@@ -1,18 +1,20 @@
 <script setup>
 import { reactive } from "vue";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 import axios from "axios";
 
+const { t } = useI18n();
 const store = useMainStore();
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "生活工藝",
+    title: "college_group_6",
     href: "/college-group/life",
     disabled: false,
   },
@@ -102,11 +104,11 @@ const shopImgs = [
 </script>
 
 <template>
-  <v-breadcrumbs
-    :items="breadcrumbs"
-    divider="/"
-    class="mt-10 pa-0"
-  ></v-breadcrumbs>
+  <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-10 pa-0">
+    <template v-slot:title="{ item }">
+      {{ t(item.title) }}
+    </template>
+  </v-breadcrumbs>
 
   <h2 class="title">旅物 SHOP</h2>
 

+ 2 - 0
src/views/CollegeGroup/Main.vue

@@ -1,11 +1,13 @@
 <script setup>
 import { ref, reactive, computed, watch } from "vue";
 import { useRouter, useRoute } from "vue-router";
+import { useMainStore } from "@/stores/store";
 import axios from "axios";
 import Navbar from "@/components/Navbar.vue";
 
 const route = useRoute();
 const router = useRouter();
+const store = useMainStore();
 
 let isLifeChildren = ref(false);
 

+ 79 - 19
src/views/CollegeGroup/Online.vue

@@ -1,24 +1,30 @@
 <script setup>
-import { ref, reactive, watch } from "vue";
+import { ref, reactive, onMounted, watch } from "vue";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 import axios from "axios";
 import moment from "moment";
 import CourseCard from "@/components/CourseCard.vue";
 
+const { t } = useI18n();
 const store = useMainStore();
 
+onMounted(() => {
+  store.getFavoriteClass();
+});
+
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "工藝學群",
+    title: "navbar.craft_groups",
     disabled: true,
   },
   {
-    title: "線上工藝",
+    title: "college_group_4",
     disabled: true,
   },
 ]);
@@ -111,18 +117,32 @@ async function selectFilter(type, val) {
   getClass();
 }
 
+const searchLocation = ref(null);
 let imedia = reactive({
   list: [],
 });
+let imediaCurrentPage = ref(1); // 當前頁數(預設第一頁)
+let imediaTotalPages = ref(1); // 總頁數
+
+// 切換分頁時回到列表上方
+watch(imediaCurrentPage, () => {
+  getImediaVideo();
+  searchLocation.value.scrollIntoView({ behavior: "smooth", block: "start" });
+});
 
 async function getImediaVideo() {
-  let url = `${store.apiUrl}/api/get_media_data?media_categories_id=1256&page=1&page_size=8`;
+  let url = `${store.apiUrl}/api/get_media_data?media_categories_id=1256&page=${imediaCurrentPage.value}&page_size=8`;
 
   try {
+    imedia.list = [];
     const response = await axios.post(url);
     const list = JSON.parse(response.data.data);
     list.data.map((item) => imedia.list.push(item));
     console.log("Imedia data", response.data);
+
+    imediaTotalPages.value = store.getTotalPages(list.sum, 8); // 計算頁數
+    console.log("imediaTotalPages", imediaTotalPages.value);
+
     console.log("imedia list", imedia.list);
   } catch (error) {
     loading.value = false;
@@ -134,20 +154,23 @@ getImediaVideo();
 </script>
 
 <template>
-  <v-breadcrumbs
-    :items="breadcrumbs"
-    divider="/"
-    class="mt-10 p-0"
-  ></v-breadcrumbs>
+  <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-10 p-0">
+    <template v-slot:title="{ item }">
+      {{ t(item.title) }}
+    </template>
+  </v-breadcrumbs>
 
-  <div class="d-flex flex-column flex-sm-row align-center justify-end my-16">
+  <div
+    class="d-flex flex-column flex-sm-row align-center justify-end my-16"
+    ref="searchLocation"
+  >
     <div class="search">
       <span>
         <input
           v-model="searchInput"
           type="text"
           @keyup.enter="search()"
-          placeholder="關鍵字搜尋"
+          :placeholder="t('keyword_search')"
         />
         <button @click="search()">
           <img
@@ -161,7 +184,7 @@ getImediaVideo();
         class="d-flex justify-center align-center error me-4"
       >
         <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
-        沒有符合搜尋條件的項目
+        {{ t("no_found") }}
       </div>
     </div>
   </div>
@@ -197,8 +220,27 @@ getImediaVideo();
           >
             <template v-slot:activator="{ props }">
               <div class="img-box">
-                <img v-bind="props" :src="item.image_m" alt="" />
+                <v-img
+                  v-bind="props"
+                  class="mx-auto cover-img"
+                  :lazy-src="item.image_m"
+                  height="13.75em"
+                  cover
+                  :src="item.image_m"
+                >
+                  <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>
               </div>
+              <!-- <div class="img-box">
+                <img v-bind="props" :src="item.image_m" alt="" />
+              </div> -->
             </template>
             <template v-slot:default="{ isActive }">
               <v-card>
@@ -246,6 +288,12 @@ getImediaVideo();
           </div>
         </v-col>
       </v-row>
+
+      <v-pagination
+        v-model="imediaCurrentPage"
+        class="mt-16"
+        :length="imediaTotalPages"
+      ></v-pagination>
     </v-col>
     <!-- <v-col sm="10" cols="12">
       <v-row class="video-list">
@@ -276,14 +324,14 @@ getImediaVideo();
     ref="listLocation"
     class="d-flex flex-column flex-sm-row align-center justify-space-between"
   >
-    <h2 class="title">線上體驗課程</h2>
+    <h2 class="title">{{ t("online_experience_courses") }}</h2>
     <div class="search">
       <span>
         <input
           v-model="searchInput"
           type="text"
           @keyup.enter="search()"
-          placeholder="關鍵字搜尋"
+          :placeholder="t('keyword_search')"
         />
         <button @click="search()">
           <img
@@ -297,7 +345,7 @@ getImediaVideo();
         class="d-flex justify-center align-center error me-4"
       >
         <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
-        沒有符合搜尋條件的項目
+        {{ t("no_found") }}
       </div>
     </div>
   </div>
@@ -347,7 +395,9 @@ getImediaVideo();
     class="mt-16"
   ></v-pagination>
 
-  <span class="text-gray total-item">總筆數:{{ totalNum }}</span>
+  <span class="text-gray total-item"
+    >{{ t("total_count") }}:{{ totalNum }}</span
+  >
 </template>
 
 <style lang="scss" scoped>
@@ -379,10 +429,20 @@ getImediaVideo();
 
   .v-btn {
     width: 6.25em;
+    color: var(--purple);
     border-color: var(--purple);
     @media (max-width: 600px) {
       width: 9.375em;
     }
+
+    &:hover {
+      color: #fff;
+      background-color: var(--purple);
+    }
+
+    &:hover > .v-btn__overlay {
+      opacity: 0;
+    }
   }
 }
 .video-list {
@@ -417,7 +477,7 @@ getImediaVideo();
   .img-box {
     overflow: hidden;
     border-radius: 20px;
-    img {
+    .v-img {
       height: 260px;
       width: 100%;
       object-fit: cover;

+ 14 - 9
src/views/CollegeGroup/Proposal.vue

@@ -2,10 +2,12 @@
 import { ref, reactive } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 import axios from "axios";
 import moment from "moment";
 import Navbar from "@/components/Navbar.vue";
 
+const { t } = useI18n();
 const store = useMainStore();
 const route = useRoute();
 const router = useRouter();
@@ -15,17 +17,17 @@ console.log("courseId", courseId);
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "未來工藝",
+    title: "college_group_1",
     disabled: false,
     href: "/college-group/future",
   },
   {
-    title: "研究計畫",
+    title: "research_plan",
     disabled: true,
   },
 ]);
@@ -93,11 +95,11 @@ function setPDFUrl(pdf) {
 <template>
   <Navbar />
   <v-container>
-    <v-breadcrumbs
-      :items="breadcrumbs"
-      divider="/"
-      class="my-10 p-0"
-    ></v-breadcrumbs>
+    <v-breadcrumbs :items="breadcrumbs" divider="/" class="my-10 p-0">
+      <template v-slot:title="{ item }">
+        {{ t(item.title) }}
+      </template>
+    </v-breadcrumbs>
 
     <v-row class="my-16 proposal-container">
       <v-col cols="12" sm="8" md="9">
@@ -139,7 +141,10 @@ function setPDFUrl(pdf) {
             <ul>
               <li v-for="(item, index) in buyWay.list" :key="index">
                 <a :href="item.url" target="_blank">
-                  <img :src="handlerBuyImg(item.title)" alt="臺灣工藝學校全球學習共享平台" />
+                  <img
+                    :src="handlerBuyImg(item.title)"
+                    alt="臺灣工藝學校全球學習共享平台"
+                  />
                 </a>
                 <!-- <a href="">
                   <img

+ 31 - 17
src/views/CollegeGroup/Repair.vue

@@ -1,10 +1,12 @@
 <script setup>
 import { ref, reactive } from "vue";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 import axios from "axios";
 import moment from "moment";
 import ArticleCard from "@/components/ArticleCard.vue";
 
+const { t } = useI18n();
 const store = useMainStore();
 
 const articles = reactive({
@@ -28,16 +30,16 @@ let searchError = ref(false);
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "工藝學群",
+    title: "navbar.craft_groups",
     disabled: true,
   },
   {
-    title: "修護工藝",
+    title: "craft_restoration",
     disabled: true,
   },
 ]);
@@ -56,11 +58,11 @@ const testData = [
 </script>
 
 <template>
-  <v-breadcrumbs
-    :items="breadcrumbs"
-    divider="/"
-    class="mt-10 p-0"
-  ></v-breadcrumbs>
+  <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-10 p-0">
+    <template v-slot:title="{ item }">
+      {{ t(item.title) }}
+    </template>
+  </v-breadcrumbs>
 
   <v-row class="mt-16">
     <v-col cols="12" md="6">
@@ -125,17 +127,20 @@ const testData = [
   <div
     class="d-flex flex-column flex-sm-row align-center justify-space-between"
   >
-    <h2 class="title">修護課程</h2>
+    <h2 class="title">{{ t("restoration_courses") }}</h2>
     <div class="search">
       <span>
         <input
           v-model="searchInput"
           type="text"
           @keyup.enter="search()"
-          placeholder="關鍵字搜尋"
+          :placeholder="t('keyword_search')"
         />
         <button @click="search()">
-          <img src="@/assets/img/news/news-search-icon.png" alt="臺灣工藝學校全球學習共享平台" />
+          <img
+            src="@/assets/img/news/news-search-icon.png"
+            alt="臺灣工藝學校全球學習共享平台"
+          />
         </button>
       </span>
       <div
@@ -143,7 +148,7 @@ const testData = [
         class="d-flex justify-center align-center error me-4"
       >
         <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
-        沒有符合搜尋條件的項目
+        {{ t("no_found") }}
       </div>
     </div>
   </div>
@@ -154,7 +159,10 @@ const testData = [
         <h2 class="text-center ma-0 pa-3">{{ item.title }}</h2>
 
         <div class="d-flex flex-column flex-sm-row align-center content">
-          <img src="@/assets/img/img-01.jpg" alt="臺灣工藝學校全球學習共享平台" />
+          <img
+            src="@/assets/img/img-01.jpg"
+            alt="臺灣工藝學校全球學習共享平台"
+          />
           <section
             class="d-flex flex-column justify-space-between px-0 px-sm-10 pt-5 pt-md-0"
           >
@@ -192,7 +200,7 @@ const testData = [
     <button class="past-btn">觀看已過期課程</button>
   </div> -->
 
-  <h2 class="title">工藝修護師</h2>
+  <h2 class="title">{{ t("craft_restorer") }}</h2>
 
   <v-row class="master-list">
     <v-col
@@ -200,7 +208,10 @@ const testData = [
       md="6"
       class="v-col-md-6 v-col-12 d-flex flex-column flex-md-row align-center justify-md-center"
     >
-      <img src="@/assets/img/college-group/repair/修護-12.webp" alt="臺灣工藝學校全球學習共享平台" />
+      <img
+        src="@/assets/img/college-group/repair/修護-12.webp"
+        alt="臺灣工藝學校全球學習共享平台"
+      />
       <section class="mt-5 mt-md-0 ms-md-5">
         <h3>廖偉淇</h3>
         <span class="d-block mt-3 mb-5">金工木印工作室</span>
@@ -217,7 +228,10 @@ const testData = [
       md="6"
       class="v-col-md-6 v-col-12 d-flex flex-column flex-md-row align-center justify-md-center"
     >
-      <img src="@/assets/img/college-group/repair/修護-13.webp" alt="臺灣工藝學校全球學習共享平台" />
+      <img
+        src="@/assets/img/college-group/repair/修護-13.webp"
+        alt="臺灣工藝學校全球學習共享平台"
+      />
       <section class="mt-5 mt-md-0 ms-md-5">
         <h3>陳高登</h3>
         <span class="d-block mt-3 mb-5">金工木印工作室</span>
@@ -229,7 +243,7 @@ const testData = [
     </v-col>
   </v-row>
 
-  <h2 class="title">修護故事</h2>
+  <h2 class="title">{{ t("restoration_story") }}</h2>
 
   <ul class="story-list">
     <li v-for="(item, index) in articles.list" :key="index" class="mb-16">

+ 16 - 11
src/views/CollegeGroup/Teenager.vue

@@ -1,15 +1,17 @@
 <script setup>
 import { ref, reactive } from "vue";
-import moment from "moment";
+import { useI18n } from "vue-i18n";
+
+const { t } = useI18n();
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "工藝學群",
+    title: "navbar.craft_groups",
     disabled: true,
   },
   {
@@ -77,11 +79,11 @@ const categoryList = reactive([
 </script>
 
 <template>
-  <v-breadcrumbs
-    :items="breadcrumbs"
-    divider="/"
-    class="mt-10 p-0"
-  ></v-breadcrumbs>
+  <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-10 p-0">
+    <template v-slot:title="{ item }">
+      {{ t(item.title) }}
+    </template>
+  </v-breadcrumbs>
 
   <div class="d-flex flex-column flex-sm-row align-center justify-end title">
     <div class="search">
@@ -90,10 +92,13 @@ const categoryList = reactive([
           v-model="searchInput"
           type="text"
           @keyup.enter="search()"
-          placeholder="關鍵字搜尋"
+          :placeholder="t('keyword_search')"
         />
         <button @click="search()">
-          <img src="@/assets/img/news/news-search-icon.png" alt="臺灣工藝學校全球學習共享平台" />
+          <img
+            src="@/assets/img/news/news-search-icon.png"
+            alt="臺灣工藝學校全球學習共享平台"
+          />
         </button>
       </span>
       <div
@@ -101,7 +106,7 @@ const categoryList = reactive([
         class="d-flex justify-center align-center error me-4"
       >
         <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
-        沒有符合搜尋條件的項目
+        {{ t("no_found") }}
       </div>
     </div>
   </div>

+ 89 - 351
src/views/CourseDetail.vue

@@ -1,7 +1,8 @@
 <script setup>
-import { ref, reactive, computed, watch } from "vue";
+import { ref, reactive, computed, onMounted, watch } from "vue";
 import { useMainStore } from "@/stores/store";
 import { useRoute } from "vue-router";
+import { useI18n } from "vue-i18n";
 import VueDatePicker from "@vuepic/vue-datepicker";
 import "@vuepic/vue-datepicker/dist/main.css";
 import axios from "axios";
@@ -9,10 +10,15 @@ import moment from "moment";
 import Navbar from "@/components/Navbar.vue";
 import CourseCard from "@/components/CourseCard.vue";
 
+const { t } = useI18n();
 const route = useRoute();
 const store = useMainStore();
 const courseId = route.params.id; // 網址參數
 
+onMounted(() => {
+  store.getFavoriteClass();
+});
+
 // signUpDialog
 let step = ref(1);
 // let signUpDialog = ref(false);
@@ -34,17 +40,17 @@ let isSignUpLoading = ref(false);
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "探索課程",
+    title: "navbar.craft_course",
     disabled: false,
     href: "/course-list",
   },
   {
-    title: "課程清單",
+    title: "course_list",
     disabled: true,
   },
 ]);
@@ -113,16 +119,17 @@ let other = reactive({
 // 取得清單
 async function getOtherClass() {
   isLoading.value = true;
-  // let number = Math.floor(Math.random() * 300) + 1; // 在 1-300 間取一個隨機正整數
   try {
     const response = await axios.get(
       `${store.apiUrl}/api/get_class_name?is_check=1&category=${course.data.category}&page_num=1&page_amount=4`
     );
     console.log("其他 response", response);
+
     other.classes = response.data.classes.filter(
       (item) => item.class_name_id !== courseNameId.value
-    ); // 移除當前課程(保留3筆)
-    // other.classes = response.data.classes;
+    ); // 移除當前課程
+
+    other.classes = other.classes.slice(0, 3); // 只保留陣列前三筆
     isLoading.value = false;
   } catch (error) {
     console.error(error);
@@ -155,6 +162,7 @@ async function signUp(index) {
   let isLogin = store.checkToken();
   let token = store.token;
   console.log("isLogin", isLogin);
+  console.log("token", token);
 
   if (isLogin && token) {
     signUpDialog[index] = true;
@@ -180,7 +188,9 @@ async function signUp(index) {
     console.log("response", response);
   } else {
     signUpDialog[index] = false;
-    store.openLoginDialog();
+    alert("請先登入會員!");
+    return;
+    // store.openLoginDialog();
   }
 }
 
@@ -271,9 +281,9 @@ async function signUpSubmit(id) {
 let currentTitle = computed(() => {
   switch (step.value) {
     case 1:
-      return "報名同意書";
+      return "form.registration_consent";
     case 2:
-      return "課程報名表";
+      return "form.course_registration";
   }
 });
 
@@ -298,11 +308,11 @@ function isDateExpired(dateString) {
   <Navbar />
 
   <v-container class="mt-16 course-detail">
-    <v-breadcrumbs
-      :items="breadcrumbs"
-      divider="/"
-      class="mt-3 mb-16 pa-0"
-    ></v-breadcrumbs>
+    <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-3 mb-16 pa-0">
+      <template v-slot:title="{ item }">
+        {{ t(item.title) }}
+      </template>
+    </v-breadcrumbs>
 
     <v-row class="justify-center">
       <v-col cols="3" class="title pa-0">
@@ -326,20 +336,20 @@ function isDateExpired(dateString) {
           <table>
             <thead>
               <tr>
-                <th colspan="2">課程資訊</th>
+                <th colspan="2">{{ t("course_info") }}</th>
               </tr>
             </thead>
             <tbody>
               <tr>
-                <td>課程主題</td>
+                <td>{{ t("form.course_name") }}</td>
                 <td>{{ course.data.name }}</td>
               </tr>
               <tr>
-                <td>課程簡介</td>
+                <td>{{ t("form.course_intro") }}</td>
                 <td v-html="store.formatString(course.data.introduction)"></td>
               </tr>
               <tr v-show="course.data.category !== ''">
-                <td>課程類別</td>
+                <td>{{ t("form.craft_category") }}</td>
                 <td>
                   {{
                     course.data.group_id === 9
@@ -349,11 +359,11 @@ function isDateExpired(dateString) {
                 </td>
               </tr>
               <tr>
-                <td>課程地點</td>
+                <td>{{ t("form.course_location") }}</td>
                 <td>{{ course.data.address }}</td>
               </tr>
               <tr v-if="course.data.organizer !== ''">
-                <td>主辦單位</td>
+                <td>{{ t("form.organizer") }}</td>
                 <td>
                   {{
                     course.data.group_id === 9
@@ -367,7 +377,7 @@ function isDateExpired(dateString) {
                 <td>{{ course.data.address }}</td>
               </tr> -->
               <tr>
-                <td>所屬學群</td>
+                <td>{{ t("form.all_study_groups") }}</td>
                 <td>{{ groupName }}</td>
               </tr>
               <!-- <tr>
@@ -390,7 +400,7 @@ function isDateExpired(dateString) {
                   course.data.group_id !== 9
                 "
               >
-                <td>聯絡方式</td>
+                <td>{{ t("form.contact_information") }}</td>
                 <td>{{ session.data[0]?.contact }}</td>
               </tr>
               <tr
@@ -400,7 +410,7 @@ function isDateExpired(dateString) {
                   session.data[0]?.fee_payment !== null
                 "
               >
-                <td>繳費方式</td>
+                <td>{{ t("form.payment_method") }}</td>
                 <td>
                   {{ session.data[0]?.fee_payment }}
                   <span
@@ -412,7 +422,7 @@ function isDateExpired(dateString) {
                 </td>
               </tr>
               <tr v-if="isInner === 0">
-                <td>備註</td>
+                <td>{{ t("form.remarks") }}</td>
                 <td>此課程不會登錄至學習護照中</td>
               </tr>
             </tbody>
@@ -438,15 +448,15 @@ function isDateExpired(dateString) {
           <table>
             <thead>
               <tr>
-                <th>場次名稱</th>
-                <th>開始/結束時間</th>
-                <th v-show="session.data[0].location" style="width: 200px">
-                  課程教室
+                <th>{{ t("form.session_name") }}</th>
+                <th>{{ t("form.start_time") }} / {{ t("form.end_time") }}</th>
+                <th v-show="session.data[0].location">
+                  {{ t("form.classroom") }}
                 </th>
-                <th>課程費用</th>
-                <th>課程名額</th>
-                <th>講師</th>
-                <th>報名截止日</th>
+                <th>{{ t("form.course_price") }}</th>
+                <th>{{ t("form.course_quota") }}</th>
+                <th>{{ t("form.instructor") }}</th>
+                <th>{{ t("form.registration_deadline") }}</th>
                 <th></th>
               </tr>
             </thead>
@@ -467,12 +477,12 @@ function isDateExpired(dateString) {
                       .format("YYYY/MM/DD HH:mm")
                   }}
                 </td>
-                <td v-show="item.location !== ''">
+                <td width="15%" v-show="item.location !== ''">
                   {{ item.location }}
                 </td>
-                <td>{{ item.fee_method }}</td>
-                <td>{{ item.number_limit }}</td>
-                <td>{{ item.lecturer }}</td>
+                <td width="20%">{{ item.fee_method }}</td>
+                <td width="10%">{{ item.number_limit }}</td>
+                <td width="15%">{{ item.lecturer }}</td>
                 <td>
                   <span
                     v-if="item.registration_end && item.registration_end !== ''"
@@ -487,36 +497,34 @@ function isDateExpired(dateString) {
                 </td>
                 <td>
                   <v-btn v-if="isInner === 0" rounded="xl" color="brown">
-                    <a :href="item.URL" target="_blank">報名</a>
+                    <a :href="item.URL" target="_blank">{{ t("sign_up") }}</a>
                   </v-btn>
 
-                  <v-dialog
+                  <v-btn
+                    v-if="isDateExpired(item.registration_end)"
+                    @click="signUp(index)"
+                    :text="t('sign_up')"
+                    rounded="xl"
+                    color="brown"
+                  >
+                  </v-btn>
+
+                  <v-btn
                     v-else
+                    :text="t('registration_closed')"
+                    rounded="xl"
+                    color="brown"
+                    disabled
+                  >
+                  </v-btn>
+
+                  <v-dialog
+                    v-model="signUpDialog[index]"
                     width="auto"
                     class="pa-0 signup-dialog"
                     scrollable
                     persistent
                   >
-                    <template v-slot:activator="{ props }">
-                      <v-btn
-                        v-if="isDateExpired(item.registration_end)"
-                        @click="signUp(index)"
-                        v-bind="props"
-                        text="報名"
-                        rounded="xl"
-                        color="brown"
-                      >
-                      </v-btn>
-                      <v-btn
-                        v-else
-                        text="已截止"
-                        rounded="xl"
-                        color="brown"
-                        disabled
-                      >
-                      </v-btn>
-                    </template>
-
                     <template v-slot:default="{ isActive }">
                       <v-card class="mx-auto pa-3" max-width="1000">
                         <v-card-title
@@ -529,7 +537,7 @@ function isDateExpired(dateString) {
                             v-text="step"
                             class="me-3"
                           ></v-avatar>
-                          <span>{{ currentTitle }}</span>
+                          <span>{{ t(currentTitle) }}</span>
                         </v-card-title>
 
                         <v-window v-model="step">
@@ -573,7 +581,8 @@ function isDateExpired(dateString) {
                                   </h2>
                                   <v-label class="d-block">
                                     <p class="d-flex mb-5">
-                                      真實姓名<span class="mark">*</span>
+                                      {{ t("profile.full_name") }}
+                                      <span class="mark">*</span>
                                     </p>
                                     <v-text-field
                                       v-model="user.name"
@@ -587,7 +596,8 @@ function isDateExpired(dateString) {
 
                                   <v-label class="mt-1">
                                     <p class="d-flex mb-5">
-                                      生日<span class="mark">*</span>
+                                      {{ t("profile.birthday") }}
+                                      <span class="mark">*</span>
                                     </p>
                                   </v-label>
 
@@ -607,7 +617,8 @@ function isDateExpired(dateString) {
 
                                   <v-label class="d-block mt-5">
                                     <p class="d-flex mb-5">
-                                      手機號碼<span class="mark">*</span>
+                                      {{ t("profile.phoneNumber") }}
+                                      <span class="mark">*</span>
                                     </p>
                                     <v-text-field
                                       v-model="user.phone"
@@ -621,7 +632,8 @@ function isDateExpired(dateString) {
 
                                   <v-label class="d-block mt-1">
                                     <p class="d-flex mb-3">
-                                      性別<span class="mark">*</span>
+                                      {{ t("profile.gender") }}
+                                      <span class="mark">*</span>
                                     </p>
 
                                     <v-radio-group v-model="user.gender" inline>
@@ -646,7 +658,8 @@ function isDateExpired(dateString) {
 
                                   <v-label class="d-block">
                                     <p class="d-flex mb-5">
-                                      地址<span class="mark">*</span>
+                                      {{ t("profile.address") }}
+                                      <span class="mark">*</span>
                                     </p>
                                     <v-text-field
                                       v-model="user.address"
@@ -657,7 +670,9 @@ function isDateExpired(dateString) {
                                   </v-label>
 
                                   <v-label class="d-block">
-                                    <p class="d-flex mb-5">備註</p>
+                                    <p class="d-flex mb-5">
+                                      {{ t("form.remarks") }}
+                                    </p>
 
                                     <v-textarea
                                       placeholder="如有特殊需求或有需攜伴,請在備註欄告知老師"
@@ -677,7 +692,7 @@ function isDateExpired(dateString) {
                                           isActive.value = false;
                                           step = 1;
                                         "
-                                        >取消</v-btn
+                                        >{{ t("form.cancel") }}</v-btn
                                       >
                                     </v-col>
                                     <v-col cols="6">
@@ -689,7 +704,7 @@ function isDateExpired(dateString) {
                                         color="brown"
                                         :loading="isSignUpLoading"
                                         @click="signUpSubmit(item.event_id)"
-                                        >確定報名</v-btn
+                                        >{{ t("sign_up") }}</v-btn
                                       >
                                     </v-col>
                                   </v-row>
@@ -728,7 +743,7 @@ function isDateExpired(dateString) {
                                     step = 1;
                                   "
                                 >
-                                  關閉
+                                  {{ t("form.close") }}
                                 </v-btn>
                                 <v-btn
                                   color="brown"
@@ -758,7 +773,7 @@ function isDateExpired(dateString) {
                             variant="outlined"
                             @click="isActive.value = false"
                           >
-                            不同意
+                            {{ t("disagree") }}
                           </v-btn>
                           <v-btn
                             class="px-5"
@@ -767,299 +782,20 @@ function isDateExpired(dateString) {
                             :disabled="!checkConsent"
                             @click="step++"
                           >
-                            我同意
+                            {{ t("agree") }}
                           </v-btn>
                         </v-card-actions>
                       </v-card>
                     </template>
                   </v-dialog>
-
-                  <!-- <v-btn rounded="xl" color="brown">
-                    <a v-if="isInner === 0" :href="item.URL" target="_blank"
-                      >報名</a
-                    >
-
-                    <v-dialog
-                      v-else
-                      v-model="signUpDialog[index]"
-                      width="auto"
-                      scrollable
-                      persistent
-                      class="pa-0 signup-dialog"
-                    >
-                      <template v-slot:activator="{ props }">
-                        <p v-bind="props" @click="signUp(index)">報名</p>
-                      </template>
-                      <template v-slot:default="{ isActive }">
-                        <v-card class="mx-auto pa-3" max-width="1000">
-                          <v-card-title
-                            class="text-h6 font-weight-regular justify-space-between"
-                          >
-                            <v-avatar
-                              v-if="step !== 3"
-                              color="primary"
-                              size="26"
-                              v-text="step"
-                              class="me-3"
-                            ></v-avatar>
-                            <span>{{ currentTitle }}</span>
-                          </v-card-title>
-
-                          <v-window v-model="step">
-                            <v-window-item :value="1">
-                              <v-card-text>
-                                <section class="consent">
-                                  <h4 class="text-center">
-                                    個人資料使用同意書
-                                  </h4>
-                                  <p>
-                                    臺灣工藝學校全球學習共享平台〈以下簡稱工藝中心〉因辦理
-                                    <span class="font-weight-medium"
-                                      >【{{ item.class_name }}】</span
-                                    >
-                                    業務而獲取您的個人資料:姓名、連絡方式〈包括但不限於電話號碼、E-MAIL、居住或工作地址〉等得以直接或間接識別您個人之資料。若您所提供之個人資料,經檢舉或工藝中心發現不足以確認您的身分真實性或其他個人資料冒用、盜用、資料不實等情形,工藝中心有權暫時停止提供對您的服務。
-                                  </p>
-
-                                  <p>
-                                    工藝中心將基於個人資料保護法及相關法令之規定下,依隱私權保護政策,蒐集、處理及利用您的個人資料。您同意工藝中心以您所提供的個人資料確認您的身份、與您進行連絡、提供您相關服務及資訊,以及其他隱私權保護政策規範之使用方式。工藝中心針對您的個人資料利用之期間,自您簽署同意起至您請求刪除個資為止。
-                                  </p>
-
-                                  <p>
-                                    您可依個人資料保護法,就您的個人資料向工藝中心進行查詢或請求閱覽、請求給複製本、請求補充或更正、請求停止蒐集及處理與利用、請求刪除。但因您行使上述權利而導致工藝中心相關業務對您的權益產生減損時,工藝中心不負相關賠償責任。
-                                  </p>
-                                </section>
-
-                                <v-checkbox
-                                  v-model="checkConsent"
-                                  label="我已完全閱讀並同意以上內容"
-                                  color="brown"
-                                ></v-checkbox>
-                              </v-card-text>
-                            </v-window-item>
-
-                            <v-window-item :value="2">
-                              <v-card-text class="pa-0">
-                                <v-sheet class="mx-auto user-form">
-                                  <v-form @submit.prevent>
-                                    <h2
-                                      class="text-center font-weight-medium mb-8"
-                                    >
-                                      【{{ item.class_name }}】
-                                    </h2>
-                                    <v-label class="d-block">
-                                      <p class="d-flex mb-5">
-                                        真實姓名<span class="mark">*</span>
-                                      </p>
-                                      <v-text-field
-                                        v-model="user.name"
-                                        :rules="[
-                                          (v) => !!v || '請輸入您的真實姓名',
-                                        ]"
-                                        density="compact"
-                                        variant="outlined"
-                                      ></v-text-field>
-                                    </v-label>
-
-                                    <v-label class="mt-1">
-                                      <p class="d-flex mb-5">
-                                        生日<span class="mark">*</span>
-                                      </p>
-                                    </v-label>
-
-                                    <VueDatePicker
-                                      v-model="user.birthday"
-                                      :format="store.datePickerFormat"
-                                      :enable-time-picker="false"
-                                      :max-date="new Date()"
-                                      locale="cn"
-                                    ></VueDatePicker>
-
-                                    <span
-                                      v-if="birthdayError"
-                                      class="birthday-error"
-                                      >請選擇您的生日</span
-                                    >
-
-                                    <v-label class="d-block mt-5">
-                                      <p class="d-flex mb-5">
-                                        手機號碼<span class="mark">*</span>
-                                      </p>
-                                      <v-text-field
-                                        v-model="user.phone"
-                                        :rules="[
-                                          (v) => !!v || '請輸入您的手機號碼',
-                                        ]"
-                                        density="compact"
-                                        variant="outlined"
-                                      ></v-text-field>
-                                    </v-label>
-
-                                    <v-label class="d-block mt-1">
-                                      <p class="d-flex mb-3">
-                                        性別<span class="mark">*</span>
-                                      </p>
-
-                                      <v-radio-group
-                                        v-model="user.gender"
-                                        inline
-                                      >
-                                        <v-radio
-                                          label="男"
-                                          value="男"
-                                          color="brown"
-                                        ></v-radio>
-                                        <v-radio
-                                          label="女"
-                                          value="女"
-                                          class="mx-2"
-                                          color="brown"
-                                        ></v-radio>
-                                        <v-radio
-                                          label="多元"
-                                          value="多元"
-                                          color="brown"
-                                        ></v-radio>
-                                      </v-radio-group>
-                                    </v-label>
-
-                                    <v-label class="d-block">
-                                      <p class="d-flex mb-5">
-                                        地址<span class="mark">*</span>
-                                      </p>
-                                      <v-text-field
-                                        v-model="user.address"
-                                        :rules="[
-                                          (v) => !!v || '請輸入您的地址',
-                                        ]"
-                                        density="compact"
-                                        variant="outlined"
-                                      ></v-text-field>
-                                    </v-label>
-
-                                    <v-label class="d-block">
-                                      <p class="d-flex mb-5">備註</p>
-
-                                      <v-textarea
-                                        placeholder="如有特殊需求或有需攜伴,請在備註欄告知老師"
-                                        variant="outlined"
-                                        rows="2"
-                                      ></v-textarea>
-                                    </v-label>
-
-                                    <v-row class="mt-3">
-                                      <v-col cols="6">
-                                        <v-btn
-                                          rounded
-                                          class="w-100"
-                                          variant="outlined"
-                                          color="brown"
-                                          @click="closeSignUpDialog()"
-                                          >取消</v-btn
-                                        >
-                                      </v-col>
-                                      <v-col cols="6">
-                                        <v-btn
-                                          rounded
-                                          type="submit"
-                                          class="w-100"
-                                          variant="flat"
-                                          color="brown"
-                                          :loading="isSignUpLoading"
-                                          @click="signUpSubmit(item.event_id)"
-                                          >確定報名</v-btn
-                                        >
-                                      </v-col>
-                                    </v-row>
-                                  </v-form>
-                                </v-sheet>
-                              </v-card-text>
-                            </v-window-item>
-
-                            <v-window-item :value="3">
-                              <v-card-text class="px-0 px-sm-2">
-                                <v-sheet
-                                  width="300"
-                                  class="text-center mx-auto result-card"
-                                >
-                                  <v-icon
-                                    class="mb-5"
-                                    :color="resultCard.color"
-                                    :icon="resultCard.icon"
-                                    size="112"
-                                  ></v-icon>
-
-                                  <h2 class="text-h5 mb-6">
-                                    {{ resultCard.text }}
-                                  </h2>
-
-                                  <v-divider class="mb-7"></v-divider>
-
-                                  <v-btn
-                                    class="text-none me-3"
-                                    rounded
-                                    width="90"
-                                    color="brown"
-                                    variant="outlined"
-                                    @click="closeSignUpDialog()"
-                                  >
-                                    關閉
-                                  </v-btn>
-                                  <v-btn
-                                    color="brown"
-                                    class="text-none"
-                                    rounded
-                                    @click="closeSignUpDialog()"
-                                  >
-                                    <router-link to="/user/passport"
-                                      >前往會員專區</router-link
-                                    >
-                                  </v-btn>
-                                </v-sheet>
-                              </v-card-text>
-                            </v-window-item>
-                          </v-window>
-
-                          <v-divider v-if="step === 1"></v-divider>
-
-                          <v-card-actions v-if="step === 1" class="mt-2">
-                            <v-spacer></v-spacer>
-                            <v-btn
-                              class="me-2 px-5"
-                              color="brown"
-                              variant="outlined"
-                              @click="signUpDialog[index] = false"
-                            >
-                              不同意
-                            </v-btn>
-                            <v-btn
-                              class="px-5"
-                              color="brown"
-                              variant="flat"
-                              :disabled="!checkConsent"
-                              @click="step++"
-                            >
-                              我同意
-                            </v-btn>
-                          </v-card-actions>
-                        </v-card>
-                      </template>
-                    </v-dialog>
-                  </v-btn> -->
                 </td>
-                <!-- <td style="width: 0; padding: 0; border: none">
-                  <div class="signup-btn">
-                    <v-btn rounded="xl" color="brown">
-                      <span @click="signUp()">點我報名 a</span>
-                    </v-btn>
-                  </div>
-                </td> -->
               </tr>
             </tbody>
           </table>
         </div>
       </v-col>
       <v-col v-if="other.classes.length" cols="12" class="mb-16">
-        <p class="other-title">其他推薦課程</p>
+        <p class="other-title">{{ t("recommended_courses") }}</p>
         <div class="d-flex justify-center mb-10" v-if="isLoading">
           <v-progress-circular
             color="grey-lighten-4"
@@ -1188,11 +924,13 @@ function isDateExpired(dateString) {
         font-size: 1.125em;
         font-weight: 500;
         padding-bottom: 1.25em;
+        white-space: nowrap;
       }
       td {
         padding: 1em;
         text-align: center;
         vertical-align: middle;
+        white-space: nowrap;
       }
       tbody {
         background-color: #fff;

+ 33 - 68
src/views/CourseList.vue

@@ -1,11 +1,13 @@
 <script setup>
-import { ref, reactive, watch } from "vue";
+import { ref, reactive, watch, onMounted } from "vue";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 import axios from "axios";
 import Navbar from "@/components/Navbar.vue";
 import CourseCard from "@/components/CourseCard.vue";
 import TwCities from "@/assets/TwCities.json";
 
+const { t } = useI18n();
 const store = useMainStore();
 let pageNum = ref(1); // 頁數(預設第一頁)
 let pageAmount = ref(18); // 每頁顯示筆數
@@ -106,12 +108,12 @@ async function search() {
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "探索課程",
+    title: "navbar.craft_course",
     disabled: true,
   },
 ]);
@@ -202,15 +204,6 @@ function searchClear() {
   getClass();
 }
 
-// watch(selectCategory, (val) => {
-//   console.log("val", val);
-//   if (val.includes("全部")) {
-//     searchClear();
-//   } else {
-//     selectFilter("category", val);
-//   }
-// });
-
 async function searchClass() {
   isFilter.value = true;
   pageNum.value = 1; // 篩選時返回第一頁
@@ -229,65 +222,29 @@ async function searchClass() {
 
   getClass();
 }
+
+onMounted(() => {
+  store.getFavoriteClass();
+});
 </script>
 
 <template>
   <Navbar />
   <v-container fluid class="pb-16 px-0 college-content course-content">
-    <!-- <v-breadcrumbs
-      :items="breadcrumbs"
-      divider="/"
-      class="mt-10 pa-0"
-    ></v-breadcrumbs> -->
-
-    <!-- <div class="banner">
-        <img src="@/assets/img/course/banner.webp" alt="臺灣工藝學校全球學習共享平台" />
-      </div> -->
     <v-row class="main-block">
       <v-col cols="12">
-        <v-breadcrumbs
-          :items="breadcrumbs"
-          divider="/"
-          class="mt-10 pa-0"
-        ></v-breadcrumbs>
+        <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-10 pa-0">
+          <template v-slot:title="{ item }">
+            {{ t(item.title) }}
+          </template>
+        </v-breadcrumbs>
       </v-col>
       <v-col cols="12" md="3" lg="2">
         <v-row class="filter-list">
-          <!-- <v-col cols="12" sm="6" md="12">
-            <v-select
-              v-model="selectSort"
-              label="排序"
-              :items="['最新開課', '熱門課程', '推薦課程', '所有課程']"
-              hide-details
-            ></v-select>
-          </v-col>
-
-          <v-col cols="12" sm="6" md="12">
-            <v-select
-              v-model="selectLocation"
-              label="地點"
-              :items="[
-                '最新開課',
-                '熱門課程',
-                '推薦課程',
-                '所有課程',
-                '學習時數課程',
-              ]"
-              hide-details
-            ></v-select>
-          </v-col>
-          <v-col cols="12" sm="6" md="12">
-            <v-select
-              v-model="selectYear"
-              label="年份"
-              :items="['2022', '2023', '2024']"
-              hide-details
-            ></v-select>
-          </v-col> -->
           <v-col cols="12" sm="6" md="12" class="pb-0">
             <v-select
               v-model="selectCategory"
-              label="類別"
+              :label="t('category')"
               :items="[
                 '樹藝',
                 '漆藝',
@@ -311,16 +268,22 @@ async function searchClass() {
           <v-col cols="12" sm="6" md="12" class="pb-0">
             <v-select
               v-model="selectedCounty"
-              label="縣市"
+              :label="t('cities')"
               :items="counties.list"
               hide-details
             ></v-select>
           </v-col>
 
-          <v-col cols="12" sm="6" md="12" class="pb-0">
+          <v-col
+            cols="12"
+            sm="6"
+            md="12"
+            class="pb-0"
+            v-if="selectedCounty !== null"
+          >
             <v-select
               v-model="selectedDistrict"
-              label="區域"
+              :label="t('districts')"
               :items="district.list"
               hide-details
             ></v-select>
@@ -333,7 +296,7 @@ async function searchClass() {
               color="purple"
               class="w-100"
             >
-              查詢
+              {{ t("search") }}
             </v-btn>
             <v-btn
               @click="searchClear()"
@@ -341,7 +304,7 @@ async function searchClass() {
               color="gray"
               class="w-100 mt-5"
             >
-              重設
+              {{ t("reset") }}
             </v-btn>
           </v-col>
           <!-- <v-col cols="12">
@@ -364,14 +327,14 @@ async function searchClass() {
                 @click="selectTag('all')"
                 :class="{ active: assignTag === 'all' }"
               >
-                所有課程
+                {{ t("all_courses") }}
               </v-btn>
               <v-btn
                 variant="text"
                 @click="selectTag('hours')"
                 :class="{ active: assignTag === 'hours' }"
               >
-                學習時數課程
+                {{ t("learning_hours_courses") }}
               </v-btn>
               <!-- <h2 class="me-5">課程清單</h2> -->
             </div>
@@ -381,7 +344,7 @@ async function searchClass() {
                   v-model="searchInput"
                   type="text"
                   @keyup.enter="search()"
-                  placeholder="關鍵字搜尋"
+                  :placeholder="t('keyword_search')"
                 />
                 <button @click="search()">
                   <img
@@ -418,7 +381,7 @@ async function searchClass() {
             class="d-flex justify-center align-center pt-12 me-4"
           >
             <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
-            沒有符合搜尋條件的項目
+            {{ t("no_found") }}
           </div>
 
           <v-pagination
@@ -429,7 +392,9 @@ async function searchClass() {
             class="mt-16"
           ></v-pagination>
 
-          <span class="text-gray total-item">總筆數:{{ totalNum }}</span>
+          <span class="text-gray total-item"
+            >{{ t("total_count") }}:{{ totalNum }}</span
+          >
         </div>
       </v-col>
       <v-col cols="12" class="my-16">

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 209 - 297
src/views/Courses/Create.vue


+ 121 - 188
src/views/Courses/Proposal.vue

@@ -1,14 +1,12 @@
 <script setup>
 import { ref, reactive, watch, computed, onMounted } from "vue";
 import { useMainStore } from "@/stores/store";
-// import { Loader } from "@googlemaps/js-api-loader";
-// import VueDatePicker from "@vuepic/vue-datepicker";
+import { useI18n } from "vue-i18n";
 import "@vuepic/vue-datepicker/dist/main.css";
 import axios from "axios";
-import moment from "moment";
 import Navbar from "@/components/Navbar.vue";
-// import { LatLngBounds } from "leaflet";
 
+const { t } = useI18n();
 const store = useMainStore();
 const token = store.token;
 console.log("token", token);
@@ -21,21 +19,19 @@ let loading = ref(false);
 const computedTitle = computed(() => {
   switch (step.value) {
     case 1:
-      stepTitle.value = "Step1 填寫基本資料";
-      stepDescription.value = "創建課程之前,請先新增您的據點(上課地址)";
+      stepTitle.value = "enter_basic_info";
+      stepDescription.value = "before_create_course";
       break;
     case 2:
-      stepTitle.value = "Step2 指派工藝教育者";
-      stepDescription.value =
-        "開始打造屬於您的工藝履歷,讓學徒對你印象深刻吧!";
+      stepTitle.value = "assign_instructor";
+      stepDescription.value = "craft_resume_intro";
       break;
     case 3:
-      stepTitle.value = "Step3 規劃開課內容";
-      stepDescription.value =
-        "完整且清楚的課程建立,是連接工藝老師與學徒的重要橋梁!";
+      stepTitle.value = "plan_course_content";
+      stepDescription.value = "course_creation_bridge";
       break;
     case 4:
-      stepTitle.value = "恭喜您完成課程提案!";
+      stepTitle.value = "congratulations_course_proposal";
       stepDescription.value = "";
       break;
   }
@@ -44,17 +40,17 @@ const computedTitle = computed(() => {
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "我要開課",
+    title: "navbar.create_course",
     disabled: false,
     href: "/setup-courses",
   },
   {
-    title: "工藝教育者提案",
+    title: "proposal",
     disabled: true,
   },
 ]);
@@ -130,7 +126,7 @@ let locationId = ref("");
 
 // 新增履歷
 let resume = reactive({
-  teacher_name: "", // 老師姓名
+  teacher_name: "", // 工藝教育者姓名
   work_type: "", // 工作性質
   experience: "", // 教學經驗
   expertise: "", // 專長工藝技能
@@ -593,16 +589,16 @@ function removeTeacher(index) {
   <Navbar />
 
   <v-container class="mb-16 pb-16 proposal-container">
-    <v-breadcrumbs
-      :items="breadcrumbs"
-      divider="/"
-      class="py-10"
-    ></v-breadcrumbs>
+    <v-breadcrumbs :items="breadcrumbs" divider="/" class="py-10">
+      <template v-slot:title="{ item }">
+        {{ t(item.title) }}
+      </template>
+    </v-breadcrumbs>
 
     <v-card class="mx-auto pa-5 pa-sm-10">
       <v-card-title class="step-title">
-        <h5>{{ computedTitle.stepTitle }}</h5>
-        <p class="mt-5">{{ computedTitle.stepDescription }}</p>
+        <h5>Step{{ step }} {{ t(computedTitle.stepTitle) }}</h5>
+        <p class="mt-5">{{ t(computedTitle.stepDescription) }}</p>
       </v-card-title>
 
       <v-window v-model="step">
@@ -629,13 +625,13 @@ function removeTeacher(index) {
                 <v-col cols="12" md="6">
                   <v-label class="d-block mb-5">
                     <p class="d-flex mb-5">
-                      工坊名稱<span class="mark">*</span>
+                      {{ t("form.location_name") }}<span class="mark">*</span>
                     </p>
                     <v-text-field
                       v-model="location.location_name"
                       :rules="[requiredRule]"
                       title="工坊名稱"
-                      placeholder="可以是您的工作室或品牌名稱/教學單位/您的姓名"
+                      :placeholder="t('form.provide_name')"
                       density="compact"
                       variant="outlined"
                       counter
@@ -645,21 +641,22 @@ function removeTeacher(index) {
 
                   <v-label class="d-block mb-5">
                     <p class="d-flex mb-5">
-                      工坊地址<span class="mark">*</span>
+                      {{ t("form.location_address")
+                      }}<span class="mark">*</span>
                     </p>
                     <v-text-field
                       v-model="location.address"
                       :rules="[requiredRule]"
-                      title="工坊地址"
+                      :title="t('form.location_address')"
                       density="compact"
                       variant="outlined"
-                      placeholder="需在安全且便於學徒到達之地點開課"
+                      :placeholder="t('form.safe_accessible_location')"
                     ></v-text-field>
                   </v-label>
 
                   <v-label class="d-block mb-5">
                     <p class="d-flex mb-5">
-                      工坊 Email<span class="mark">*</span>
+                      {{ t("form.location_email") }}<span class="mark">*</span>
                     </p>
                     <v-text-field
                       v-model="location.email"
@@ -687,10 +684,11 @@ function removeTeacher(index) {
                   <v-label class="d-block mb-5">
                     <div class="d-flex">
                       <p class="d-flex mb-5">
-                        公開電話<span class="mark">*</span>
+                        {{ t("form.location_phone") }}
+                        <span class="mark">*</span>
                       </p>
-                      <span class="d-block ms-3 hint"
-                        >會顯示於課程介紹中,想了解課程資訊者聯繫用途
+                      <span class="d-block ms-3 hint">
+                        {{ t("form.display_on_course_intro") }}
                       </span>
                     </div>
 
@@ -699,13 +697,14 @@ function removeTeacher(index) {
                       :rules="[requiredRule]"
                       density="compact"
                       variant="outlined"
-                      placeholder="請輸入電話號碼包含區碼 (e.g., 02-23887066)"
+                      :placeholder="t('form.enter_telephone_number')"
                     ></v-text-field>
                   </v-label>
 
                   <v-label class="d-block">
                     <p class="d-flex mb-5">
-                      工坊簡介<span class="mark">*</span>
+                      {{ t("form.location_introduction") }}
+                      <span class="mark">*</span>
                     </p>
                     <v-textarea
                       v-model="location.school_introduction"
@@ -725,85 +724,6 @@ function removeTeacher(index) {
                 </v-col>
 
                 <v-col cols="12" md="6"> </v-col>
-
-                <!-- <v-col cols="12" md="6" class="px-0">
-                  <v-label class="d-block">
-                    <p class="d-flex mb-5">負責人<span class="mark">*</span></p>
-                    <v-text-field
-                      :rules="[requiredRule]"
-                      title="負責人"
-                      density="compact"
-                      variant="outlined"
-                      counter
-                      maxlength="60"
-                    ></v-text-field>
-                  </v-label>
-                </v-col>
-
-                <v-col cols="12" md="11" class="px-4">
-                  <v-label class="d-block">
-                    <p class="d-flex mb-5">
-                      工坊地址<span class="mark">*</span>
-                    </p>
-                    <v-text-field
-                      v-model="location.address"
-                      :rules="[requiredRule]"
-                      title="工坊地址"
-                      density="compact"
-                      variant="outlined"
-                      placeholder="需在安全且便於學徒到達之地點開課"
-                    ></v-text-field>
-                  </v-label>
-                </v-col>
-
-                <v-col cols="12" md="5" class="px-0">
-                  <v-label class="d-block">
-                    <p class="d-flex mb-5">
-                      據點 Email<span class="mark">*</span>
-                    </p>
-                    <v-text-field
-                      v-model="location.email"
-                      :rules="[requiredRule]"
-                      density="compact"
-                      variant="outlined"
-                      placeholder="請填寫 Email"
-                    ></v-text-field>
-                  </v-label>
-                </v-col>
-
-                <v-col cols="12" md="5" class="px-0">
-                  <v-label class="d-block">
-                    <div class="d-flex">
-                      <p class="d-flex mb-5">
-                        公開電話<span class="mark">*</span>
-                      </p>
-                      <span class="d-block ms-3 hint"
-                        >會顯示於課程介紹中,想了解課程資訊者聯繫用途
-                      </span>
-                    </div>
-
-                    <v-text-field
-                      v-model="location.phone"
-                      :rules="[requiredRule]"
-                      density="compact"
-                      variant="outlined"
-                    ></v-text-field>
-                  </v-label>
-                </v-col>
-
-                <v-col cols="12" md="11">
-                  <v-label class="d-block">
-                    <p class="d-flex mb-5">
-                      據點簡介<span class="mark">*</span>
-                    </p>
-                    <v-textarea
-                      v-model="location.school_introduction"
-                      :rules="[requiredRule]"
-                      rows="5"
-                      variant="outlined"
-                    ></v-textarea>
-                  </v-label>
-                </v-col> -->
               </v-row>
 
               <!-- <div class="d-flex flex-column justify-end ms-md-16 ps-md-5">
@@ -854,19 +774,20 @@ function removeTeacher(index) {
             <button class="resume-btn">
               <p class="d-flex flex-column align-center">
                 <v-icon icon="mdi-plus" size="x-large"></v-icon>
-                新增工藝教育者
+                {{ t("create_course_instructor") }}
               </p>
 
               <v-dialog v-model="resumeDialog" activator="parent" width="700">
                 <v-card class="pa-5">
-                  <v-card-title>工藝教育者履歷</v-card-title>
+                  <v-card-title>{{ t("instructor_resume") }}</v-card-title>
                   <v-card-text>
                     <v-form ref="resumeForm" lazy-validation @submit.prevent>
                       <v-row>
                         <v-col cols="12">
                           <v-label class="d-flex align-center">
                             <p class="pb-5 pe-3">
-                              老師姓名<span class="mark">*</span>
+                              {{ t("form.instructor_name")
+                              }}<span class="mark">*</span>
                             </p>
                             <v-text-field
                               v-model="resume.teacher_name"
@@ -882,12 +803,16 @@ function removeTeacher(index) {
                         <v-col cols="12">
                           <v-label class="d-block">
                             <p class="pb-5 pe-3">
-                              工作性質<span class="mark">*</span>
+                              {{ t("form.job_nature")
+                              }}<span class="mark">*</span>
                             </p>
                             <v-radio-group v-model="resume.work_type" inline>
-                              <v-radio label="全職" value="全職"></v-radio>
                               <v-radio
-                                label="兼職"
+                                :label="t('form.full_time')"
+                                value="全職"
+                              ></v-radio>
+                              <v-radio
+                                :label="t('form.part_time')"
                                 value="兼職"
                                 class="ps-3"
                               ></v-radio>
@@ -897,7 +822,9 @@ function removeTeacher(index) {
 
                         <v-col cols="12" class="py-0">
                           <v-label class="d-block">
-                            <p class="pb-5 pe-3">教學經驗</p>
+                            <p class="pb-5 pe-3">
+                              {{ t("form.teaching_experience") }}
+                            </p>
                             <v-radio-group v-model="resume.experience" inline>
                               <v-radio label="0-5 年" value="0-5 年"></v-radio>
                               <v-radio
@@ -922,7 +849,8 @@ function removeTeacher(index) {
                         <v-col cols="12">
                           <v-label class="d-block pb-3">
                             <p class="pb-5 pe-3">
-                              專長工藝技能<span class="mark">*</span>
+                              {{ t("form.craft_skills")
+                              }}<span class="mark">*</span>
                             </p>
                             <v-text-field
                               v-model="resume.expertise"
@@ -934,7 +862,8 @@ function removeTeacher(index) {
 
                           <v-label class="d-block pb-3">
                             <p class="pb-5 pe-3">
-                              工藝相關證照<span class="mark">*</span>
+                              {{ t("form.related_certifications")
+                              }}<span class="mark">*</span>
                             </p>
                             <v-text-field
                               v-model="resume.license"
@@ -946,7 +875,7 @@ function removeTeacher(index) {
                           </v-label>
 
                           <v-label class="d-block">
-                            <p class="d-flex">社群媒體</p>
+                            <p class="d-flex">{{ t("form.social_media") }}</p>
                             <span class="d-block py-3 hint"
                               >工藝老師經營之網站或社群媒體,可貼網址</span
                             >
@@ -968,7 +897,7 @@ function removeTeacher(index) {
                                     ref="portfolioImgRef"
                                     label="File input"
                                     variant="outlined"
-                                    placeholder="選擇相片上傳"
+                                    :placeholder="t('form.choose_image')"
                                     @change="handlePortfolioImg"
                                     style="display: none"
                                   ></v-file-input>
@@ -1005,7 +934,8 @@ function removeTeacher(index) {
                         <v-col cols="12">
                           <v-label class="d-block">
                             <p class="d-flex mb-5">
-                              工藝教育者介紹<span class="mark">*</span>
+                              {{ t("form.instructor_intro")
+                              }}<span class="mark">*</span>
                             </p>
                             <v-textarea
                               v-model="resume.introduction"
@@ -1029,32 +959,38 @@ function removeTeacher(index) {
                   <v-card-actions class="justify-center">
                     <v-spacer></v-spacer>
 
-                    <v-btn text="取消" @click="resumeDialog = false"></v-btn>
+                    <v-btn
+                      :text="t('form.cancel')"
+                      @click="resumeDialog = false"
+                    ></v-btn>
 
                     <v-btn
                       @click="insertResume()"
                       color="purple"
                       variant="flat"
                     >
-                      新增
+                      {{ t("form.create") }}
                     </v-btn>
                   </v-card-actions>
                 </v-card>
               </v-dialog>
             </button>
-            <h6 data-v-bbf03f1f="" class="mt-10 table-title">工藝教育者履歷</h6>
+            <h6 data-v-bbf03f1f="" class="mt-10 table-title">
+              {{ t("instructor_resume") }}
+            </h6>
             <div class="main-table">
-              <!-- <h6 data-v-bbf03f1f="" class="table-title">工藝教育者履歷</h6> -->
               <table>
                 <thead>
                   <tr>
-                    <th>姓名</th>
-                    <th>工作性質</th>
-                    <th>教學經驗</th>
-                    <th>專長工藝技能</th>
-                    <th>工藝相關證照</th>
-                    <th>社群媒體</th>
-                    <th style="width: 30%">老師介紹</th>
+                    <th>{{ t("form.name") }}</th>
+                    <th>{{ t("form.job_nature") }}</th>
+                    <th>{{ t("form.teaching_experience") }}</th>
+                    <th>{{ t("form.craft_skills") }}</th>
+                    <th>{{ t("form.related_certifications") }}</th>
+                    <th>{{ t("form.social_media") }}</th>
+                    <th style="width: 30%">
+                      {{ t("form.instructor_intro") }}
+                    </th>
                     <th></th>
                     <!-- <th width="15%">繳款資訊</th> -->
                   </tr>
@@ -1077,7 +1013,7 @@ function removeTeacher(index) {
                         <template v-slot:activator="{ props }">
                           <v-btn
                             v-bind="props"
-                            text="查看"
+                            :text="t('form.check')"
                             variant="tonal"
                             color="purple"
                             rounded="xl"
@@ -1097,7 +1033,7 @@ function removeTeacher(index) {
                               <v-spacer></v-spacer>
 
                               <v-btn
-                                text="關閉"
+                                :text="t('form.close')"
                                 @click="isActive.value = false"
                               ></v-btn>
                             </v-card-actions>
@@ -1115,27 +1051,6 @@ function removeTeacher(index) {
                       ></v-btn>
                       <!-- <v-icon icon="mdi-close" color="red"></v-icon> -->
                     </td>
-
-                    <!-- <td>{{ item.class_name }}</td>
-                <td>
-                  {{ moment(`${item.create_time}`).format("YYYY/MM/DD") }}
-                </td>
-                <td>
-                  {{ moment(`${item.start_time}`).format("YYYY/MM/DD H:mm") }}
-                  <br />
-                  ~ <br />
-                  {{ moment(`${item.end_time}`).format("YYYY/MM/DD H:mm") }}
-                </td>
-                <td>{{ item.hours }} 小時</td>
-                <td>
-                  <span class="finish-icon" v-if="item.reg_confirm">
-                    <v-icon icon="mdi-check" class="pb-1"></v-icon>
-                  </span>
-
-                  <span v-else class="d-flex align-center">
-                    <p style="width: 55px" class="text-grey-lighten-1">審核中</p>
-                  </span>
-                </td> -->
                   </tr>
                 </tbody>
               </table>
@@ -1145,7 +1060,7 @@ function removeTeacher(index) {
               <v-row>
                 <v-col cols="12" sm="6">
                   <v-label class="d-flex align-center pb-3">
-                    <p class="pb-5 pe-3">老師姓名<span class="mark">*</span></p>
+                    <p class="pb-5 pe-3">工藝教育者姓名<span class="mark">*</span></p>
                     <v-text-field
                       v-model="resume.teacher_name"
                       :rules="[requiredRule]"
@@ -1236,7 +1151,7 @@ function removeTeacher(index) {
                       ref="portfolioImgRef"
                       label="File input"
                       variant="outlined"
-                      placeholder="選擇相片上傳"
+                      :placeholder="t('form.choose_image')"
                       @change="handlePortfolioImg"
                       style="display: none"
                     ></v-file-input>
@@ -1284,7 +1199,9 @@ function removeTeacher(index) {
               <v-row class="justify-space-evenly">
                 <v-col cols="12" md="7">
                   <v-label class="d-block mb-5">
-                    <p class="pb-5 pe-3">課程名稱<span class="mark">*</span></p>
+                    <p class="pb-5 pe-3">
+                      {{ t("form.course_name") }}<span class="mark">*</span>
+                    </p>
                     <v-text-field
                       v-model="proposal.class_name"
                       :rules="[requiredRule]"
@@ -1294,7 +1211,9 @@ function removeTeacher(index) {
                   </v-label>
 
                   <v-label class="d-block mb-5">
-                    <p class="pb-5 pe-3">工藝類別<span class="mark">*</span></p>
+                    <p class="pb-5 pe-3">
+                      {{ t("form.craft_category") }}<span class="mark">*</span>
+                    </p>
                     <v-select
                       v-model="proposal.category"
                       placeholder="請選擇類別"
@@ -1321,10 +1240,10 @@ function removeTeacher(index) {
                   </v-label>
 
                   <v-label class="d-block mb-5">
-                    <p class="pb-5 pe-3">上課地點</p>
+                    <p class="pb-5 pe-3">{{ t("form.course_location") }}</p>
                     <v-text-field
                       v-model="location.address"
-                      label="自動帶入據點地址"
+                      :label="t('form.auto_fill_location_address')"
                       density="compact"
                       variant="outlined"
                       disabled
@@ -1332,10 +1251,10 @@ function removeTeacher(index) {
                   </v-label>
 
                   <v-label class="d-block mb-5">
-                    <p class="pb-5 pe-3">主辦單位</p>
+                    <p class="pb-5 pe-3">{{ t("form.organizer") }}</p>
                     <v-text-field
                       v-model="location.location_name"
-                      label="自動帶入據點名稱"
+                      :label="t('form.auto_fill_location_name')"
                       density="compact"
                       variant="outlined"
                       disabled
@@ -1343,10 +1262,12 @@ function removeTeacher(index) {
                   </v-label>
 
                   <v-label class="d-block mb-5">
-                    <p class="d-flex">課程簡介<span class="mark">*</span></p>
-                    <small class="my-2 hint"
-                      >內容不得少於 100 個字元,且不得含有工藝無關之資訊</small
-                    >
+                    <p class="d-flex">
+                      {{ t("form.course_intro") }}<span class="mark">*</span>
+                    </p>
+                    <small class="my-2 hint">{{
+                      t("form.content_requirements")
+                    }}</small>
                     <v-textarea
                       v-model="proposal.introduction"
                       rows="5"
@@ -1368,7 +1289,9 @@ function removeTeacher(index) {
                   </v-label> -->
 
                   <v-label class="d-block mb-5">
-                    <p class="pb-5 pe-3">課程價位<span class="mark">*</span></p>
+                    <p class="pb-5 pe-3">
+                      {{ t("form.course_price") }}<span class="mark">*</span>
+                    </p>
                     <v-text-field
                       v-model="proposal.fee_method"
                       type="number"
@@ -1378,7 +1301,9 @@ function removeTeacher(index) {
                   </v-label>
 
                   <v-label class="d-block mb-5">
-                    <p class="pb-5 pe-3">課程名額<span class="mark">*</span></p>
+                    <p class="pb-5 pe-3">
+                      {{ t("form.course_quota") }}<span class="mark">*</span>
+                    </p>
                     <v-text-field
                       v-model="proposal.number_limit"
                       density="compact"
@@ -1389,23 +1314,26 @@ function removeTeacher(index) {
 
                   <v-label class="d-block mb-5">
                     <p class="pb-5 pe-3">
-                      最低開課人數<span class="mark">*</span>
+                      {{ t("form.minimum_enrollment")
+                      }}<span class="mark">*</span>
                     </p>
                     <v-text-field
                       v-model="proposal.number_minimum"
                       density="compact"
                       variant="outlined"
-                      placeholder="請輸入開課門檻人數"
+                      :placeholder="t('form.enter_minimum_enrollment')"
                     ></v-text-field>
                   </v-label>
 
                   <v-label class="d-block mb-5">
-                    <p class="pb-5 pe-3">適合對象<span class="mark">*</span></p>
+                    <p class="pb-5 pe-3">
+                      {{ t("form.target_audience") }}<span class="mark">*</span>
+                    </p>
                     <v-textarea
                       v-model="proposal.people"
                       rows="2"
                       variant="outlined"
-                      placeholder="ex. 不拘、18-65 歲、大專院校工藝、美術、設計科系學生、從事木作相關之業者或個人工作室"
+                      :placeholder="t('form.target_placeholder')"
                       :rules="[requiredRule]"
                     ></v-textarea>
                   </v-label>
@@ -1427,7 +1355,8 @@ function removeTeacher(index) {
 
                   <v-label class="d-block">
                     <p class="d-flex">
-                      上傳課程封面<span class="mark">*</span>
+                      {{ t("form.upload_course_cover_image")
+                      }}<span class="mark">*</span>
                     </p>
 
                     <v-file-input
@@ -1436,7 +1365,7 @@ function removeTeacher(index) {
                       ref="coverImgRef"
                       label="File input"
                       variant="outlined"
-                      placeholder="選擇相片上傳"
+                      :placeholder="t('form.choose_image')"
                       @change="handleCoverImg"
                       style="display: none"
                     ></v-file-input>
@@ -1446,8 +1375,9 @@ function removeTeacher(index) {
                     @click="fileInputClick('cover')"
                     color="purple"
                     class="my-5"
-                    >選擇相片上傳</v-btn
                   >
+                    {{ t("form.choose_image") }}
+                  </v-btn>
 
                   <div class="step-01 image-preview">
                     <img
@@ -1460,7 +1390,7 @@ function removeTeacher(index) {
 
                 <v-col cols="12" md="5">
                   <div class="notes-block">
-                    <p class="mb-5">課程審核標準:</p>
+                    <p class="mb-5">{{ t("course_review_criteria") }}:</p>
                     <ul>
                       <li>
@@ -1495,7 +1425,7 @@ function removeTeacher(index) {
           @click="step--"
           class="px-7 me-2"
         >
-          上一步
+          {{ t("back") }}
         </v-btn>
         <v-spacer></v-spacer>
 
@@ -1506,7 +1436,7 @@ function removeTeacher(index) {
           @click="checkField(step)"
           class="px-7 ms-2"
         >
-          下一步
+          {{ t("next") }}
         </v-btn>
         <!-- <div
             v-if="errorField"
@@ -1548,12 +1478,14 @@ function removeTeacher(index) {
           variant="outlined"
           class="mb-5 mb-sm-0 me-sm-3"
         >
-          <router-link to="/" class="px-7">回到首頁</router-link>
+          <router-link to="/" class="px-7">{{
+            t("return_to_home")
+          }}</router-link>
         </v-btn>
         <v-btn v-if="step === 4" color="purple" variant="flat">
-          <router-link to="/user/courses" class="px-7"
-            >前往開課專區</router-link
-          >
+          <router-link to="/user/courses" class="px-7">{{
+            t("go_to_course_area")
+          }}</router-link>
         </v-btn>
       </v-card-actions>
     </v-card>
@@ -1629,6 +1561,7 @@ function removeTeacher(index) {
     .v-label {
       p {
         width: 5.9375em;
+        display: flex;
         text-align: end;
         line-height: 1.375em;
         @media (max-width: 600px) {

+ 26 - 37
src/views/Courses/SetUp.vue

@@ -1,61 +1,48 @@
 <script setup>
-import { ref, reactive, watch, onMounted } from "vue";
+import { ref, onMounted } from "vue";
 import { useMainStore } from "@/stores/store";
-import { Swiper, SwiperSlide } from "swiper/vue";
-import { Pagination } from "swiper/modules";
-import "swiper/css";
-import "swiper/css/pagination";
-import "swiper/css/navigation";
-import axios from "axios";
+import { useI18n } from "vue-i18n";
 import Navbar from "@/components/Navbar.vue";
-// import CoursesTutorial from "@/components/CoursesTutorial.vue";
 
+const { t } = useI18n();
 const store = useMainStore();
 let isLogin = store.checkToken();
-console.log("isLogin", isLogin);
-
-let userInfo = ref([]);
 let isLoading = ref(true);
 let isCrafts = ref(false); // 身份是否為工藝教育者
 
-onMounted(async () => {
-  let response = await store.getUserInfo();
-  console.log(response);
-  if (response) {
-    if (response.data.msg !== "no access") {
-      userInfo.value = response.data.user_inform[0];
-
-      let position = userInfo.value.position;
-      console.log("position", position);
-      if (position) {
-        if (position["開課工藝家"]) {
-          isCrafts.value = true;
-          isLoading.value = false;
-          console.log("isCrafts.value", isCrafts.value);
-        } else {
-          isCrafts.value = false;
-          isLoading.value = false;
-        }
+async function getUserInfo() {
+  await store.getUserInfo();
+  console.log("store.userInfo", store.userInfo);
+  if (Object.keys(store.userInfo).length) {
+    let position = store.userInfo.position;
+    console.log("身份:", position);
+    if (position) {
+      if (position["開課工藝家"]) {
+        isCrafts.value = true;
+        isLoading.value = false;
+        console.log("是否為開課工藝家", isCrafts.value);
       } else {
         isCrafts.value = false;
         isLoading.value = false;
       }
-
-      console.log("userInfo.value", userInfo.value);
     } else {
-      console.log('"no access"');
       isCrafts.value = false;
       isLoading.value = false;
-      console.log("isCrafts.value = false", isCrafts.value);
     }
   } else {
+    isLogin = false;
     isCrafts.value = false;
     isLoading.value = false;
+    store.loginState = false;
     console.log("尚未登入");
-    // store.openLoginDialog();
+    console.log("是否為開課工藝家", isCrafts.value);
   }
+}
 
-  console.log("isCrafts.value = false", isCrafts.value);
+onMounted(() => {
+  setTimeout(() => {
+    getUserInfo();
+  }, 500);
 });
 
 // 導向藝文中心登入頁面
@@ -82,7 +69,7 @@ async function login() {
             src="@/assets/img/setup-courses/素材-01.png"
             alt="臺灣工藝學校全球學習共享平台"
           />
-          <router-link to="/setup-courses/tutorial">觀看開課教學</router-link>
+          <router-link to="/setup-courses/tutorial">   {{ t("tutorial.title") }}</router-link>
         </div>
       </v-col>
 
@@ -100,7 +87,9 @@ async function login() {
             src="@/assets/img/setup-courses/素材-03.png"
             alt="臺灣工藝學校全球學習共享平台"
           />
-          <router-link to="/setup-courses/create">開始創建課程</router-link>
+          <router-link to="/setup-courses/create">
+            {{ t("tutorial.create") }}
+          </router-link>
         </div>
         <div v-else>
           <v-card class="text-center px-0 py-8 crafts-card">

+ 11 - 4
src/views/Courses/Tutorial.vue

@@ -1,21 +1,24 @@
 <script setup>
 import { ref, reactive } from "vue";
+import { useI18n } from "vue-i18n";
 import Navbar from "@/components/Navbar.vue";
 import CoursesTutorial from "@/components/CoursesTutorial.vue";
 
+const { t } = useI18n();
+
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "我要開課",
+    title: "navbar.create_course",
     disabled: false,
     href: "/setup-courses",
   },
   {
-    title: "開課教學",
+    title: "home.title_5",
     disabled: true,
   },
 ]);
@@ -25,7 +28,11 @@ const breadcrumbs = reactive([
   <Navbar />
 
   <div class="breadcrumbs-item">
-    <v-breadcrumbs :items="breadcrumbs" divider="/"></v-breadcrumbs>
+    <v-breadcrumbs :items="breadcrumbs" divider="/">
+      <template v-slot:title="{ item }">
+        {{ t(item.title) }}
+      </template>
+    </v-breadcrumbs>
   </div>
 
   <CoursesTutorial />

+ 27 - 25
src/views/Crafts.vue

@@ -2,6 +2,7 @@
 import { ref, reactive, watch, computed, onMounted } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 import axios from "axios";
 import Navbar from "@/components/Navbar.vue";
 import PDFViewer from "@/components/PDFViewer.vue";
@@ -9,6 +10,7 @@ import craftsPdfList from "@/utils/useCraftsPdf";
 import ArticleCard from "@/components/ArticleCard.vue";
 import CraftsArticle from "@/components/CraftsArticle.vue";
 
+const { t } = useI18n();
 const route = useRoute();
 const router = useRouter();
 const store = useMainStore();
@@ -111,12 +113,12 @@ const paginatedList = computed(() => {
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "工藝學",
+    title: "navbar.crafts",
     disabled: true,
     href: "/crafts",
   },
@@ -124,23 +126,23 @@ const breadcrumbs = reactive([
 
 const tagList = reactive([
   {
-    title: "國際專欄",
+    title: "crafts.title_1",
     url: "#articleList",
   },
   {
-    title: "線上閱讀",
+    title: "crafts.title_2",
     url: "#readList",
   },
   {
-    title: "工藝書單",
+    title: "crafts.title_3",
     url: "#bookList",
   },
   {
-    title: "工藝學刊",
+    title: "crafts.title_4",
     url: "#journal",
   },
   {
-    title: "碩博士論文補助",
+    title: "crafts.title_5",
     url: "#thesisGrant",
   },
 ]);
@@ -218,11 +220,11 @@ function handlePdfUrl(pdf) {
   <div class="bg-img">
     <v-container class="position-relative">
       <div class="article-content">
-        <v-breadcrumbs
-          :items="breadcrumbs"
-          divider="/"
-          class="my-5"
-        ></v-breadcrumbs>
+        <v-breadcrumbs :items="breadcrumbs" divider="/" class="my-5">
+          <template v-slot:title="{ item }">
+            {{ t(item.title) }}
+          </template>
+        </v-breadcrumbs>
 
         <v-row class="px-sm-10 pb-3 tag-btn">
           <v-col
@@ -237,16 +239,16 @@ function handlePdfUrl(pdf) {
               class="py-3 py-sm-6 item"
             >
               <!-- 錨點 -->
-              <h3>{{ item.title }}</h3>
+              <h3>{{ t(`${item.title}`) }}</h3>
             </a>
             <a v-else :href="item.url" target="_blank" class="py-3 py-lg-6">
               <!-- 網址 -->
-              <h3>{{ item.title }}</h3>
+              <h3>{{ t(`${item.title}`) }}</h3>
             </a>
           </v-col>
         </v-row>
 
-        <h2 id="articleList" class="mb-16 pb-5">國際專欄</h2>
+        <h2 id="articleList" class="mb-16 pb-5">{{ t("crafts.title_1") }}</h2>
 
         <!-- <div class="search pt-5 my-10 me-sm-16" ref="searchLocation">
           <span>
@@ -265,13 +267,13 @@ function handlePdfUrl(pdf) {
             class="d-flex justify-center align-center error me-4"
           >
             <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
-            沒有符合搜尋條件的項目
+            {{ t("no_found") }}
           </div>
         </div> -->
 
         <CraftsArticle />
 
-        <h2 ref="readRef" id="readList">線上閱讀</h2>
+        <h2 ref="readRef" id="readList">{{ t("crafts.title_2") }}</h2>
 
         <v-row class="justify-center align-start mt-16 read-list">
           <v-col
@@ -344,7 +346,7 @@ function handlePdfUrl(pdf) {
           <PDFViewer :file="fileName" />
         </div>
 
-        <h2 id="bookList" class="mb-16">工藝書單</h2>
+        <h2 id="bookList" class="mb-16">{{ t("crafts.title_3") }}</h2>
 
         <!-- <div class="search pt-5 my-10 me-sm-16" ref="searchLocation ">
           <span>
@@ -363,7 +365,7 @@ function handlePdfUrl(pdf) {
             class="d-flex justify-center align-center error me-4"
           >
             <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
-            沒有符合搜尋條件的項目
+            {{ t("no_found") }}
           </div>
         </div> -->
 
@@ -415,7 +417,7 @@ function handlePdfUrl(pdf) {
           :length="totalPages"
         ></v-pagination> -->
 
-        <h2 id="journal">工藝學刊</h2>
+        <h2 id="journal">{{ t("crafts.title_4") }}</h2>
 
         <div class="journal-content">
           <p>
@@ -476,7 +478,7 @@ function handlePdfUrl(pdf) {
           </div>
         </div>
 
-        <h2 id="thesisGrant">碩博士論文補助</h2>
+        <h2 id="thesisGrant">{{ t("crafts.title_5") }}</h2>
 
         <ul class="mt-10">
           <li
@@ -630,8 +632,8 @@ h2 {
       }
 
       &::after {
-        top: -0.4375em;
-        right: -0.25em;
+        top: -0.4em;
+        right: -0.2em;
         transform: rotate(-15deg);
 
         @media (max-width: 1200px) {
@@ -640,8 +642,8 @@ h2 {
         }
       }
       &::before {
-        bottom: -0.4375em;
-        right: -0.25em;
+        bottom: -0.4em;
+        right: -0.2em;
         transform: rotate(15deg);
 
         @media (max-width: 1200px) {

+ 23 - 18
src/views/Home.vue

@@ -18,12 +18,13 @@ let loading = ref(true);
 
 onMounted(() => {
   // 無障礙網頁需於 iframe 加上 title
-  let hubspot = document.querySelector("#hubspot-conversations-iframe");
-  if (hubspot) {
-    hubspot.title = "HubSpot iframe";
-  }
+  // let hubspot = document.querySelector("#hubspot-conversations-iframe");
+  // if (hubspot) {
+  //   hubspot.title = "HubSpot iframe";
+  // }
+  // console.log("hubspot", hubspot);
 
-  console.log("hubspot", hubspot);
+  store.getFavoriteClass();
 
   setTimeout(() => {
     loading.value = false;
@@ -405,18 +406,18 @@ let closeBanner = ref(false);
           @click="selectTag('news')"
           :class="{ active: assignTag === 'news' }"
         >
-          重要訊息
+          {{ t("news") }}
         </v-btn>
         <v-btn
           variant="text"
           @click="selectTag('exhibit')"
           :class="{ active: assignTag === 'exhibit' }"
         >
-          當期展覽
+          {{ t("exhibitions") }}
         </v-btn>
       </div>
-      <div class="d-flex justify-end">
-        <router-link to="/news">觀看更多訊息 >></router-link>
+      <div class="d-flex justify-end mb-5 mb-sm-0">
+        <router-link to="/news">{{ t("more_news") }} >></router-link>
       </div>
       <ul v-if="assignTag === 'news'">
         <li v-for="(item, index) in news.list" :key="index" class="mb-16 list">
@@ -462,7 +463,7 @@ let closeBanner = ref(false);
     <h2 class="my-10 title">{{ t("home.title_2") }}</h2>
     <v-row>
       <v-col cols="6" md="4">
-        <router-link to="/" class="img-info">
+        <router-link to="/college-group/future" class="img-info">
           <img
             src="@/assets/img/home/首頁元素-12.webp"
             alt="臺灣工藝學校全球學習共享平台"
@@ -484,7 +485,7 @@ let closeBanner = ref(false);
         </router-link>
       </v-col>
       <v-col cols="6" md="4">
-        <router-link to="/college-group/future" class="img-info">
+        <router-link to="/college-group/cross" class="img-info">
           <img
             src="@/assets/img/home/首頁元素-06.webp"
             alt="臺灣工藝學校全球學習共享平台"
@@ -495,7 +496,7 @@ let closeBanner = ref(false);
         </router-link>
       </v-col>
       <v-col cols="6" md="4">
-        <router-link to="/college-group/cross" class="img-info">
+        <router-link to="/college-group/online" class="img-info">
           <img
             src="@/assets/img/home/首頁元素-09.webp"
             alt="臺灣工藝學校全球學習共享平台"
@@ -536,7 +537,7 @@ let closeBanner = ref(false);
         <v-col cols="6" sm="3" md="2">
           <v-select
             v-model="selectedCounty"
-            label="縣市"
+            :label="t('cities')"
             :items="counties.list"
             density="compact"
             variant="outlined"
@@ -547,7 +548,7 @@ let closeBanner = ref(false);
         <v-col cols="6" sm="3" md="2">
           <v-select
             v-model="selectedDistrict"
-            label="區域"
+            :label="t('districts')"
             :items="district.list"
             density="compact"
             variant="outlined"
@@ -561,19 +562,19 @@ let closeBanner = ref(false);
             v-model="schoolKeyword"
             variant="outlined"
             density="compact"
-            placeholder="請輸入據點關鍵字"
+            :placeholder="t('location_keywords')"
             hide-details
           ></v-text-field>
         </v-col>
 
-        <v-col cols="4" sm="2" lg="1">
+        <v-col cols="4" sm="2" lg="2">
           <v-btn
             @click="searchSchool()"
             variant="flat"
             append-icon="mdi-magnify"
             color="purple"
           >
-            查詢
+            {{ t("search") }}
           </v-btn>
         </v-col>
       </v-row>
@@ -585,7 +586,10 @@ let closeBanner = ref(false);
           </div>
         </v-col>
         <v-col md="4" cols="12" :class="{ 'd-none': store.isMobile }">
-          <v-list lines="three" class="list pa-0">
+          <p v-if="!classes.data.length" class="text-center text-gray">
+            未找到符合條件的結果
+          </p>
+          <v-list v-else lines="three" class="list pa-0">
             <v-list-item v-for="item in classes.data" :key="item.id">
               <div class="d-flex align-center">
                 <router-link
@@ -639,6 +643,7 @@ let closeBanner = ref(false);
             </v-list-item>
           </v-list>
           <v-pagination
+            v-if="classes.data.length"
             v-model="pageNum"
             :length="totalPages"
             class="my-4"

+ 12 - 9
src/views/News.vue

@@ -1,11 +1,14 @@
 <script setup>
 import { ref, reactive, watch, computed } from "vue";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 import axios from "axios";
 import Navbar from "@/components/Navbar.vue";
 import ArticleCard from "@/components/ArticleCard.vue";
 
+const { t } = useI18n();
 const store = useMainStore();
+
 let loading = ref(false);
 let searchInput = ref("");
 let searchError = ref(false);
@@ -102,12 +105,12 @@ const paginatedList = computed(() => {
 
 const breadcrumbs = reactive([
   {
-    title: "首頁",
+    title: "home.title",
     disabled: false,
     href: "/",
   },
   {
-    title: "重要訊息",
+    title: "navbar.news",
     disabled: true,
     href: "/news",
   },
@@ -160,11 +163,11 @@ const categoryList = reactive([
           />
         </div>
 
-        <v-breadcrumbs
-          :items="breadcrumbs"
-          divider="/"
-          class="my-5"
-        ></v-breadcrumbs>
+        <v-breadcrumbs :items="breadcrumbs" divider="/" class="my-5">
+          <template v-slot:title="{ item }">
+            {{ t(item.title) }}
+          </template>
+        </v-breadcrumbs>
 
         <div class="search pt-5 mb-10 me-sm-16" ref="searchLocation">
           <span>
@@ -172,7 +175,7 @@ const categoryList = reactive([
               v-model="searchInput"
               type="text"
               @keyup.enter="search()"
-              placeholder="關鍵字搜尋"
+              :placeholder="t('keyword_search')"
             />
             <button @click="search()">
               <img
@@ -186,7 +189,7 @@ const categoryList = reactive([
             class="d-flex justify-center align-center error me-4"
           >
             <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
-            沒有符合搜尋條件的項目
+            {{ t("no_found") }}
           </div>
         </div>
 

+ 7 - 0
src/views/NewsDetail.vue

@@ -1,11 +1,13 @@
 <script setup>
 import { ref, reactive } from "vue";
 import { useRoute } from "vue-router";
+import { useMainStore } from "@/stores/store";
 import axios from "axios";
 import moment from "moment";
 import Navbar from "@/components/Navbar.vue";
 
 const route = useRoute();
+const store = useMainStore();
 const newsId = route.params.id; // 網址參數
 const news = reactive({
   data: [],
@@ -90,6 +92,11 @@ let loading = ref(false);
 </template>
 
 <style lang="scss" scoped>
+section {
+  line-height: 2;
+  letter-spacing: 1px;
+}
+
 .content {
   padding: 1.25em;
   background-image: url("@/assets/img/news/news-bg.webp");

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 194 - 218
src/views/User/Courses.vue


+ 29 - 36
src/views/User/Dashboard.vue

@@ -2,49 +2,30 @@
 import { ref, computed, onMounted } from "vue";
 import { useRouter } from "vue-router";
 import { useMainStore } from "@/stores/store";
-// import axios from "axios";
+import { useI18n } from "vue-i18n";
 import Navbar from "@/components/Navbar.vue";
 
+const { t } = useI18n();
 const router = useRouter();
 const store = useMainStore();
+
 let username = ref("");
 
 onMounted(async () => {
-  // await store.getProfile();
-  // username.value = store.profile.username;
+  if (!Object.keys(store.mocProfile).length) {
+    await store.getMocProfile();
+  }
 
-  await store.getMocProfile();
-  if (Object.keys(store.mocProfile).length === 0) {
+  if (!Object.keys(store.mocProfile).length) {
     console.log("無 MocProfile");
     router.push("/");
   } else {
     username.value = store.mocProfile.NAME;
   }
-  console.log(
-    "Object.keys(store.mocProfile).length",
-    Object.keys(store.mocProfile).length
-  );
   console.log("MOCProfile:", store.mocProfile);
-  // username.value = store.mocProfile.NAME;
+  username.value = store.mocProfile.NAME;
 });
 
-// (async () => {
-//   try {
-//     const response = await axios.get(
-//       `${store.apiUrl}/api/information?token=${token}`
-//     );
-//     console.log("response", response.data.msg);
-//     if (!response.data.msg) {
-//       store.logout();
-//       return;
-//     }
-//     store.setProfile(response.data.msg);
-//     username.value = response.data.msg.username;
-//   } catch (error) {
-//     console.error(error);
-//   }
-// })();
-
 const firstName = computed(() => {
   if (username.value) {
     const firstChar = username.value.charAt(0);
@@ -55,13 +36,13 @@ const firstName = computed(() => {
 let items = [
   { type: "divider" },
   {
-    title: "個人檔案",
+    title: "navbar.profile",
     icon: "mdi-account",
     url: "/user/profile",
     value: 1,
   },
   {
-    title: "學習護照",
+    title: "navbar.passport",
     icon: "mdi-book",
     url: "/user/passport",
     value: 2,
@@ -73,13 +54,13 @@ let items = [
   //   value: 3,
   // },
   {
-    title: "我的開課",
+    title: "navbar.courses",
     icon: "mdi-school",
     url: "/user/courses",
     value: 3,
   },
   {
-    title: "我的收藏",
+    title: "navbar.collections",
     icon: "mdi-bookmark",
     url: "/user/favorite-class",
     value: 4,
@@ -110,18 +91,26 @@ let items = [
           <v-list class="w-100 mt-1">
             <v-list-item v-for="(item, index) in items" :key="index">
               <v-divider v-if="item.type === 'divider'"></v-divider>
-              <div v-else class="d-flex justify-center">
+              <div v-else class="d-flex">
                 <router-link :to="item.url" class="d-flex">
                   <v-icon color="gray" class="me-2">{{ item.icon }}</v-icon>
-                  <v-list-item-title>{{ item.title }}</v-list-item-title>
+                  <v-list-item-title>{{ t(item.title) }}</v-list-item-title>
                 </router-link>
               </div>
             </v-list-item>
             <v-list-item>
-              <div class="d-flex justify-center">
-                <button class="d-flex" @click="store.logout()">
+              <div class="d-flex">
+                <button
+                  class="d-flex"
+                  @click="
+                    store.logout();
+                    store.mocLogout();
+                  "
+                >
                   <v-icon color="gray" class="me-2">mdi-logout</v-icon>
-                  <v-list-item-title>登出</v-list-item-title>
+                  <v-list-item-title>{{
+                    t("navbar.logout")
+                  }}</v-list-item-title>
                 </button>
                 <!-- <router-link to="/" class="d-flex">
                   <v-icon color="gray" class="me-2">mdi-logout</v-icon>
@@ -199,5 +188,9 @@ let items = [
       }
     }
   }
+
+  .v-list-item-title {
+    white-space: normal;
+  }
 }
 </style>

+ 22 - 9
src/views/User/FavoriteClass.vue

@@ -1,13 +1,21 @@
 <script setup>
-import { ref, reactive, watch } from "vue";
+import { ref, reactive, onMounted, watch } from "vue";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 import axios from "axios";
 import CourseCard from "@/components/CourseCard.vue";
 
+const { t } = useI18n();
 const store = useMainStore();
+
+onMounted(() => {
+  store.getFavoriteClass();
+});
+
 let favorites = reactive({
   list: [],
 });
+
 let favoritesAll = reactive({
   list: [],
 });
@@ -27,8 +35,12 @@ async function getFavoriteClass() {
       response.data.favorite_courses
     );
     progress.value = false;
-    favorites.list = response.data.favorite_courses;
-    favoritesAll.list = response.data.favorite_courses;
+    favorites.list = response.data.favorite_courses.filter(
+      (item) => item.msg !== `this class doesn't exit`
+    );
+    favoritesAll.list = response.data.favorite_courses.filter(
+      (item) => item.msg !== `this class doesn't exit`
+    );
   } catch (error) {
     progress.value = false;
     console.error(error);
@@ -88,17 +100,15 @@ const handleSearch = () => {
     >
       <template v-slot:label>
         <span>
-          搜尋課程<v-icon
-            icon="mdi-text-box-search-outline"
-            class="pb-1 ps-1"
-          ></v-icon>
+          {{ t("search_courses") }}
+          <v-icon icon="mdi-text-box-search-outline" class="pb-1 ps-1"></v-icon>
         </span>
       </template>
     </v-text-field>
     <div class="error">
       <div v-if="searchError" class="d-flex align-center ms-6 pt-2">
         <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
-        沒有符合搜尋條件的項目
+        {{ t("no_found") }}
       </div>
     </div>
     <v-container>
@@ -119,7 +129,10 @@ const handleSearch = () => {
           :key="index"
           class="pa-5"
         >
-          <CourseCard :data="item" />
+          <CourseCard
+            v-if="item.msg !== `this class doesn't exit`"
+            :data="item"
+          />
         </v-col>
       </v-row>
 

+ 136 - 86
src/views/User/Passport.vue

@@ -1,9 +1,11 @@
 <script setup>
-import { ref, reactive } from "vue";
+import { ref, reactive, onMounted } from "vue";
 import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
 import axios from "axios";
 import moment from "moment";
 
+const { t } = useI18n();
 const store = useMainStore();
 let token = store.token;
 console.log("學習護照", store.token);
@@ -22,9 +24,11 @@ let records = reactive({
   // 上課記錄
   list: [],
 });
-let userId = store.profile.id;
 
-console.log("profile", store.profile);
+onMounted(async () => {
+  await store.getUserInfo();
+  await getRecord();
+});
 
 async function getRegistration() {
   try {
@@ -41,14 +45,17 @@ async function getRegistration() {
 
 getRegistration();
 
+// 取得上課紀錄
 async function getRecord() {
-  await store.getProfile();
-  let userId = store.profile.id;
-  console.log("store.profile", store.profile);
+  // await store.getProfile();
+  console.log("store.userInfo", store.userInfo);
+  let userId = store.userInfo.user_id;
+  console.log("userId >>>", userId);
   try {
     const response = await axios.get(
       `${store.apiUrl}/api/get_attend_record?user_id=${userId}`
     );
+
     records.list = response.data.attend_record_list;
     records.list.reverse();
     console.log("上課紀錄", records.list);
@@ -57,12 +64,6 @@ async function getRecord() {
   }
 }
 
-getRecord();
-
-function formatDateTime(inputDateTime) {
-  return inputDateTime.substring(0, 16);
-}
-
 let isCheck = ref(null);
 let assignTag = ref("register");
 
@@ -78,30 +79,37 @@ function selectTag(btn) {
     assignTag.value = btn;
     data.list = records.list;
   }
+}
 
-  // getClass();
-
-  console.log("assignTag.value", assignTag.value);
+function getBankCode(string, type) {
+  if (type === "code") {
+    const match = string.match(/\((\d+)\)/);
+    return match ? match[1] : null;
+  } else if (type === "account") {
+    const match = string.match(/-(\d+)/);
+    return match ? match[1] : null;
+  }
 }
 </script>
 
 <template>
   <v-card class="h-100 user-courses">
     <div class="title">
-      <h4>學習護照</h4>
+      <h4>{{ t("navbar.passport") }}</h4>
     </div>
     <p class="text-center">
-      凡在平台選擇「學習時數課程」並報名 <br />
-      即可累積點數,兌換精美好禮-工藝材料包
+      {{ t("learning_hours.description_1") }} <br />
+      {{ t("learning_hours.description_2") }}
     </p>
 
     <v-row class="main-info mt-10">
       <v-col cols="12" md="6" class="d-flex flex-column align-center">
-        <h5>累積學習<span>時數</span></h5>
+        <h5>{{ t("learning_hours.total_learning") }}
+          <span>{{ t("learning_hours.hours") }}</span></h5>
         <div class="d-flex align-center position-relative">
           <p>
-            自 2023 年起至今 <br />
-            完成課程時數
+            {{ t("learning_hours.completed_hours") }} <br />
+            {{ t("learning_hours.completed_from") }}
           </p>
           <img
             src="@/assets/img/passport/icon-01.png"
@@ -115,17 +123,21 @@ function selectTag(btn) {
             alt="臺灣工藝學校全球學習共享平台"
           />
           <p>
-            總共 <br />
-            <strong> {{ store.profile.hours }} </strong> <small>小時</small>
+            {{ t("learning_hours.total") }} <br />
+            <strong> {{ store.userInfo.hours }} </strong>
+            <small>{{ t("learning_hours.hour") }}</small>
           </p>
         </div>
       </v-col>
       <v-col cols="12" md="6" class="d-flex flex-column align-center">
-        <h5>累積學習<span>點數</span></h5>
+        <h5>
+          {{ t("learning_hours.total_learning") }}
+          <span>{{ t("learning_hours.points") }}</span>
+        </h5>
         <div class="d-flex align-center position-relative">
           <p>
-            累積 20 點即可兌換 1 份 <br />
-            工藝材料包
+            {{ t("learning_hours.redeem_criteria") }} <br />
+            {{ t("learning_hours.craft_materials") }}
           </p>
           <img
             src="@/assets/img/passport/icon-02.png"
@@ -139,31 +151,13 @@ function selectTag(btn) {
             alt="臺灣工藝學校全球學習共享平台"
           />
           <p>
-            總共 <br />
-            <!-- 小數點無條件捨去 -->
-            <!-- <strong> {{ Math.floor(store.profile.points / 3) }} </strong> -->
-            <strong> {{ store.profile.points }} </strong>
-            <small class="ps-2">點</small>
+            {{ t("learning_hours.total") }} <br />
+            <strong> {{ store.userInfo.points }} </strong>
+            <small class="ps-2">{{ t("learning_hours.point") }}</small>
           </p>
         </div>
       </v-col>
       <v-col cols="12" class="mt-16">
-        <!-- <div class="d-flex justify-center tab-btn mt-5 mb-10">
-      <v-btn
-        variant="text"
-        @click="selectTag('all')"
-        :class="{ active: assignTag === 'all' }"
-      >
-      報名中課程
-      </v-btn>
-      <v-btn
-        variant="text"
-        @click="selectTag('0')"
-        :class="{ active: assignTag === '0' }"
-      >
-        上課紀錄
-      </v-btn>
-    </div> -->
         <div class="d-flex flex-column flex-sm-row justify-center tab-btn mt-5">
           <v-btn
             variant="text"
@@ -187,11 +181,12 @@ function selectTag(btn) {
           <table v-if="assignTag === 'register'">
             <thead>
               <tr>
-                <th>報名日期</th>
-                <th>課程名稱</th>
-                <th width="180px">課程時間</th>
-                <th width="110px">時數</th>
-                <th width="10%">報名狀態</th>
+                <th>{{ t("form.registration_start_date") }}</th>
+                <th width="300px">{{ t("form.course_name") }}</th>
+                <th width="180px">{{ t("form.course_duration") }}</th>
+                <th width="110px">{{ t("learning_hours.hours") }}</th>
+                <th width="110px">{{ t("form.payment") }}</th>
+                <!-- <th width="110px">報名狀態</th> -->
                 <!-- <th width="15%">繳款資訊</th> -->
               </tr>
             </thead>
@@ -224,8 +219,69 @@ function selectTag(btn) {
                       .format("YYYY/MM/DD HH:mm")
                   }}
                 </td>
-                <td>{{ item.hours }} 小時</td>
+                <td>{{ item.hours }} {{ t("learning_hours.hour") }}</td>
                 <td>
+                  <span v-if="item.fee_payment === '匯款'">
+                    {{ item.fee_payment }}
+                    <v-dialog width="500" class="transfer-dialog">
+                      <template v-slot:activator="{ props }">
+                        <!-- <button v-bind="props" class="dialog-btn">轉帳資訊</button> -->
+                        <v-btn
+                          size="small"
+                          v-bind="props"
+                          text="轉帳資訊"
+                          variant="tonal"
+                          class="mt-1 dialog-btn"
+                        >
+                        </v-btn>
+                      </template>
+
+                      <template v-slot:default="{ isActive }">
+                        <v-card title="匯款帳號">
+                          <v-card-text>
+                            <table class="mt-1">
+                              <tbody>
+                                <tr>
+                                  <td>銀行代號</td>
+                                  <td class="main-item">
+                                    {{ getBankCode(item.ATM_address, "code") }}
+                                  </td>
+                                </tr>
+                                <tr>
+                                  <td>銀行帳號</td>
+                                  <td class="main-item">
+                                    {{
+                                      getBankCode(item.ATM_address, "account")
+                                    }}
+                                  </td>
+                                </tr>
+                                <tr>
+                                  <td>課程金額</td>
+                                  <td>{{ item.fee_method }} 元</td>
+                                </tr>
+                              </tbody>
+                            </table>
+                          </v-card-text>
+
+                          <v-card-actions>
+                            <v-spacer></v-spacer>
+
+                            <v-btn
+                              :text="t('form.close')"
+                              @click="isActive.value = false"
+                            ></v-btn>
+                          </v-card-actions>
+                        </v-card>
+                      </template>
+                    </v-dialog>
+                    <!-- <v-chip class="ma-2" size="small" label>
+                      帳號: {{ item.ATM_address }}
+                    </v-chip> -->
+                  </span>
+                  <span v-else-if="item.fee_payment === 'None'">無</span>
+                  <span v-else>{{ item.fee_payment }}</span>
+                </td>
+                <!-- <td>
                   <span class="finish-icon" v-if="item.reg_confirm">
                     <v-icon icon="mdi-check" class="pb-1"></v-icon>
                   </span>
@@ -235,16 +291,7 @@ function selectTag(btn) {
                       審核中
                     </p>
                   </span>
-
-                  <!-- <div v-if="item.reg_confirm" class="d-flex align-center">
-                    <v-icon
-                      color="blue"
-                      icon="mdi-check-circle-outline"
-                      class="me-1"
-                    ></v-icon>
-                    <p style="width: 70px">報名成功</p>
-                  </div> -->
-                </td>
+                </td> -->
               </tr>
             </tbody>
           </table>
@@ -292,25 +339,6 @@ function selectTag(btn) {
         </router-link>
       </v-col>
 
-      <!-- <v-col cols="12" class="my-16">
-        <div class="main-table">
-          <h6 class="table-title">上課紀錄</h6>
-          <table>
-            <thead>
-              <tr>
-                <th>名稱</th>
-                <th width="30%">日期</th>
-                <th width="15%">時數</th>
-                <th width="15%">報名完成</th>
-              </tr>
-            </thead>
-            <tbody>
-              <tr></tr>
-            </tbody>
-          </table>
-        </div>
-      </v-col> -->
-
       <div class="dot-item">
         <span class="t-dot"></span>
         <span class="r-dot"></span>
@@ -421,10 +449,6 @@ table {
     }
   }
 
-  // .time-item {
-  //   font-size: 0.8em;
-  // }
-
   .finish-icon {
     padding: 0.5em;
     border: 0.125em solid var(--blue);
@@ -434,4 +458,30 @@ table {
     }
   }
 }
+
+.dialog-btn {
+  padding-bottom: 2px;
+  font-size: 0.9em !important;
+}
+
+.transfer-dialog {
+  table {
+    width: 100%;
+    border-collapse: collapse;
+    td {
+      height: 55px;
+      padding: 0 5%;
+      line-height: 1em;
+      border: 1px solid;
+      &:first-child {
+        text-align: center;
+      }
+    }
+    .main-item {
+      font-weight: 500;
+      font-size: 1.5em;
+      letter-spacing: 0.05em;
+    }
+  }
+}
 </style>

+ 113 - 276
src/views/User/Profile.vue

@@ -1,12 +1,11 @@
 <script setup>
 import { ref, reactive, watch, onMounted } from "vue";
 import { useMainStore } from "@/stores/store";
-import VueDatePicker from "@vuepic/vue-datepicker";
+import { useI18n } from "vue-i18n";
 import "@vuepic/vue-datepicker/dist/main.css";
-import phoneCodes from "@/assets/json/phoneCodes.json";
-import taiwanCities from "@/assets/json/taiwanCities.json";
 import axios from "axios";
 
+const { t } = useI18n();
 const store = useMainStore();
 
 let user = reactive({
@@ -28,7 +27,7 @@ let phone = ref("");
 let city = ref("臺北市");
 let address = ref("");
 let isLoading = ref(false);
-let alertState = ref(false);
+// let alertState = ref(false);
 let isExist = ref(false); // 資料是否已存在
 
 let token = store.token;
@@ -67,23 +66,15 @@ async function addUserInfo(formData) {
     );
     console.log("新增 response", response);
     if (response.status === 200) {
-      getUserInfo;
+      setUserInfo;
     }
     isLoading.value = false;
     alert("儲存成功!");
-    // handlerAlert();
   } catch (error) {
     console.error(error);
   }
 }
 
-function handlerAlert() {
-  alertState.value = true;
-  setTimeout(() => {
-    alertState.value = false;
-  }, 2000);
-}
-
 // 更新資料
 async function updateUserInfo(formData) {
   try {
@@ -93,32 +84,35 @@ async function updateUserInfo(formData) {
       formData
     );
     console.log("更新 response", response);
-    isLoading.value = false;
-    alert("儲存成功!");
-    // handlerAlert();
+
+    setTimeout(() => {
+      isLoading.value = false;
+      alert("儲存成功!");
+    }, 1000);
   } catch (error) {
     console.error(error);
   }
 }
 
 let isCrafts = ref(false); // 身份是否為工藝教育者
+let getUserLoading = ref(false);
 
-onMounted(() => {
+onMounted(async () => {
+  getUserLoading.value = true;
+  await store.getUserInfo();
+  await setUserInfo();
   // setTimeout(() => {
-  //   isMounted.value = true;
+  //   getUserInfo();
   // }, 500);
-  getUserInfo();
 });
+
 // 取得使用者資料
-async function getUserInfo() {
-  try {
-    console.log("getUserInfo");
-    console.log("store.token", store.token);
+async function setUserInfo() {
+  if (Object.keys(store.userInfo).length) {
+    let userInform = store.userInfo;
 
-    let response = await store.getUserInfo();
-    console.log("取得使用者資料", response);
+    console.log("Profile 取得使用者資料", userInform);
 
-    let userInform = response.data.user_inform[0];
     email.value = userInform.email;
     user.user_name = userInform.user_name;
 
@@ -174,34 +168,15 @@ async function getUserInfo() {
         address.value = userInform.address.substring(3);
       }
     }
-
-    // let response = await store.getUserInfo();
-    // console.log("取得使用者資料", response);
-
-    // const response = await axios.get(
-    //   `${store.apiUrl}/api/get_user_information?access_token=${token}`
-    // );
-    // console.log("getUserInfo response", response);
-    // console.log("response.data.msg", response.data.msg);
-
-    // 無法訪問
-    // if (response.data.msg === "no access") {
-    //   console.log("no access");
-    //   store.loginState = false;
-    //   localStorage.removeItem("token");
-    //   window.location.reload();
-    //   return;
-    // }
-  } catch (error) {
+    getUserLoading.value = false;
+  } else {
     store.loginState = false;
-    console.log("getUserInfo error");
+    console.log("setUserInfo error");
     localStorage.removeItem("token");
-    console.error(error);
+    // console.error(error);
   }
 }
 
-// getUserInfo();
-
 let tab = ref(null);
 const genderGroup = reactive(["男", "女", "多元"]);
 const displayText = (item) => `${item.country} ${item.code}`;
@@ -209,7 +184,7 @@ const requiredRule = (value) => !!value || "此欄位為必填";
 
 // 履歷
 let resume = reactive({
-  teacher_name: "", // 老師姓名
+  teacher_name: "", // 工藝教育者姓名
   work_type: "", // 工作性質
   experience: "", // 教學經驗
   expertise: "", // 專長工藝技能
@@ -226,7 +201,6 @@ let resume = reactive({
     const response = await axios.get(
       `${store.apiUrl}/api/get_user_resume?access_token=${token}`
     );
-    console.log("工藝教育者", response.data.user_resume);
 
     if (response.data.user_resume) {
       // 遍歷物件屬性
@@ -248,26 +222,22 @@ async function saveResume() {
   console.log("saveResume");
 }
 
-// 上傳圖片 Input
-const portfolioImgRef = ref(null);
+// // 上傳圖片 Input
+// const portfolioImgRef = ref(null);
 
-const fileInputClick = () => {
-  portfolioImgRef.value.click();
-};
+// const fileInputClick = () => {
+//   portfolioImgRef.value.click();
+// };
 
 // 作品集圖片
 let portfolioImg = ref([]);
 let portfolioImgList = ref([]);
 
 const handlePortfolioImg = (files) => {
-  console.log("files", files);
-  // portfolioImgList.value = []; // 清空陣列
   for (let index = 0; index < files.length; index++) {
     const file = files[index];
-    console.log("file", file);
     let url = URL.createObjectURL(file);
     portfolioImgList.value.push(url);
-    console.log("portfolioImgList", portfolioImgList);
   }
 };
 
@@ -283,7 +253,6 @@ async function editMocProfile() {
 }
 
 // 身份類別
-let selectType = ref([]);
 let typeList = [
   "系統管理者",
   "職能治療師",
@@ -311,17 +280,24 @@ let productList = ["機具", "織布"];
 </script>
 
 <template>
-  <v-card class="h-100 px-10 profile-card">
+  <div v-if="getUserLoading" class="d-flex justify-center py-16">
+    <v-progress-circular
+      color="grey-lighten-4"
+      indeterminate
+    ></v-progress-circular>
+  </div>
+
+  <v-card v-else class="h-100 px-10 profile-card">
     <v-tabs v-model="tab" color="purple" align-tabs="center" class="mb-16">
-      <v-tab :value="1">關於我</v-tab>
-      <v-tab :value="2">創作者</v-tab>
-      <v-tab :value="3">加工製造者</v-tab>
+      <v-tab :value="1">{{ t("profile.about_me") }}</v-tab>
+      <v-tab :value="2">{{ t("profile.creator") }}</v-tab>
+      <v-tab :value="3">{{ t("profile.processing_manufacturers") }}</v-tab>
       <!-- <v-tab v-if="isCrafts" :value="2">工藝教育者履歷</v-tab> -->
     </v-tabs>
     <v-window v-model="tab">
       <v-window-item :value="1">
         <v-form @submit.prevent>
-          <p class="mb-5">身份</p>
+          <p class="mb-5">{{ t("profile.identity") }}</p>
           <div class="d-flex">
             <v-chip color="purple"> 學習者 </v-chip>
             <v-chip v-if="isCrafts" color="purple" class="ms-2">
@@ -329,26 +305,10 @@ let productList = ["機具", "織布"];
             </v-chip>
           </div>
 
-          <!-- <v-label>
-            <p class="d-flex mb-5">身份<span class="mark">*</span></p>
-          </v-label>
-          <v-checkbox
-            v-model="positionList"
-            label="學習者"
-            value="1"
-            color="purple"
-            hide-details
-          ></v-checkbox>
-          <v-checkbox
-            v-model="positionList"
-            label="開課工藝教育者"
-            value="2"
-            color="purple"
-            hide-details
-          ></v-checkbox> -->
-
           <v-label class="mt-10">
-            <p class="d-flex mb-5">類別</p>
+            <p class="d-flex mb-5">
+              {{ t("category") }}
+            </p>
 
             <v-select
               v-model="identityList"
@@ -365,7 +325,9 @@ let productList = ["機具", "織布"];
           </v-label>
 
           <v-label class="mt-10">
-            <p class="d-flex mb-5">顯示名稱<span class="mark">*</span></p>
+            <p class="d-flex mb-5">
+              {{ t("profile.displayName") }}<span class="mark">*</span>
+            </p>
             <v-text-field
               v-model="user.user_name"
               :rules="[(v) => !!v || '請輸入您的名稱']"
@@ -376,7 +338,7 @@ let productList = ["機具", "織布"];
           </v-label>
 
           <v-label class="mt-3">
-            <p class="d-flex my-3">性別</p>
+            <p class="d-flex my-3">{{ t("profile.gender") }}</p>
             <v-chip-group
               v-model="user.gender"
               selected-class="text-purple"
@@ -400,28 +362,21 @@ let productList = ["機具", "織布"];
             class="px-8 mt-16"
             @click="saveUserInfo()"
           >
-            儲存變更
+            {{ t("profile.save") }}
           </v-btn>
 
           <v-divider class="my-16"></v-divider>
 
-          <p class="d-flex my-3">帳號/Email</p>
+          <p class="d-flex my-3">{{ t("profile.account") }}/Email</p>
           <p class="text-gray">{{ email }}</p>
 
           <v-label>
-            <p class="d-flex my-3">真實姓名</p>
+            <p class="d-flex my-3">{{ t("profile.account") }}</p>
             <p class="text-gray">{{ store.mocProfile.NAME }}</p>
-            <!-- <v-text-field
-              v-model="user.name"
-              :rules="[(v) => !!v || '請輸入您的姓名']"
-              variant="outlined"
-              density="comfortable"
-              class="d-block"
-            ></v-text-field> -->
           </v-label>
 
           <v-label>
-            <p class="d-flex my-3">生日</p>
+            <p class="d-flex my-3">{{ t("profile.birthday") }}</p>
             <p class="text-gray">
               {{ store.mocProfile.BIRTH_DATE_YEAR }}
               {{ store.mocProfile.BIRTH_DATE_MONTH }}
@@ -430,12 +385,14 @@ let productList = ["機具", "織布"];
           </v-label>
 
           <v-label>
-            <p class="d-flex my-3">手機號碼<span class="mark">*</span></p>
+            <p class="d-flex my-3">
+              {{ t("profile.phoneNumber") }}<span class="mark">*</span>
+            </p>
             <p class="text-gray">{{ store.mocProfile.MOBILEPHONE }}</p>
           </v-label>
 
           <v-label>
-            <p class="d-flex my-3">現居地</p>
+            <p class="d-flex my-3">{{ t("profile.address") }}</p>
             <p class="text-gray">{{ store.mocProfile.ZIP_NAME }}</p>
           </v-label>
 
@@ -446,134 +403,8 @@ let productList = ["機具", "織布"];
             class="px-8 mt-16 mb-10"
             @click="editMocProfile()"
           >
-            修改個人資料
+            {{ t("profile.edit_profile") }}
           </v-btn>
-
-          <!-- <p class="d-flex mb-5">帳號/Email</p>
-          <p class="text-gray">{{ email }}</p>
-
-          <v-label>
-            <p class="d-flex my-5">真實姓名<span class="mark">*</span></p>
-            <v-text-field
-              v-model="user.name"
-              :rules="[(v) => !!v || '請輸入您的姓名']"
-              variant="outlined"
-              density="comfortable"
-              class="d-block"
-            ></v-text-field>
-          </v-label>
-
-          <v-label>
-            <p class="d-flex mb-5">生日<span class="mark">*</span></p>
-          </v-label>
-
-          <VueDatePicker
-            v-model="user.birthday"
-            :format="store.datePickerFormat"
-            :enable-time-picker="false"
-            :max-date="new Date()"
-            locale="cn"
-          ></VueDatePicker>
-
-          <v-label>
-            <p class="d-flex mt-5 mb-3">性別</p>
-
-            <v-chip-group
-              v-model="user.gender"
-              selected-class="text-purple"
-              mandatory
-            >
-              <v-chip
-                v-for="item in genderGroup"
-                :key="item"
-                :value="item"
-                filter
-              >
-                {{ item }}
-              </v-chip>
-            </v-chip-group>
-          </v-label>
-
-          <v-label>
-            <p class="d-flex my-5">手機號碼<span class="mark">*</span></p>
-            <v-container class="pa-0 mx-0" style="min-width: 100%">
-              <v-row>
-                <v-col cols="12" md="3">
-                  <v-select
-                    v-model="phoneCode"
-                    :items="phoneCodes"
-                    :item-title="displayText"
-                    item-value="code"
-                    variant="outlined"
-                    density="comfortable"
-                  >
-                  </v-select>
-                </v-col>
-
-                <v-col cols="12" md="9">
-                  <v-text-field
-                    v-model="phone"
-                    :rules="[(v) => !!v || '請輸入您的號碼']"
-                    variant="outlined"
-                    density="comfortable"
-                    required
-                  ></v-text-field>
-                </v-col>
-              </v-row>
-            </v-container>
-          </v-label>
-
-          <v-label>
-            <p class="d-flex mb-5">現居地</p>
-            <v-container class="pa-0 mx-0" style="min-width: 100%">
-              <v-row>
-                <v-col cols="12" md="3">
-                  <v-select
-                    v-model="city"
-                    :items="taiwanCities"
-                    item-title="name"
-                    item-value="name"
-                    variant="outlined"
-                    density="comfortable"
-                  >
-                  </v-select>
-                </v-col>
-
-                <v-col cols="12" md="9">
-                  <v-text-field
-                    v-model="address"
-                    required
-                    placeholder="詳細地址"
-                    variant="outlined"
-                    density="comfortable"
-                  ></v-text-field>
-                </v-col>
-              </v-row>
-            </v-container>
-          </v-label> -->
-
-          <!-- <div class="d-flex justify-center mt-5 mb-10">
-            <Transition>
-              <v-alert
-                v-if="alertState"
-                type="success"
-                variant="outlined"
-                density="compact"
-              >
-                儲存成功
-              </v-alert>
-            </Transition>
-
-            <v-btn
-              :loading="isLoading"
-              variant="flat"
-              color="purple"
-              class="px-8"
-              @click="saveUserInfo()"
-            >
-              儲存
-            </v-btn>
-          </div> -->
         </v-form>
       </v-window-item>
 
@@ -581,7 +412,9 @@ let productList = ["機具", "織布"];
       <v-window-item :value="2">
         <v-form @submit.prevent>
           <v-label class="mt-5">
-            <p class="d-flex mb-5">創作者名稱<span class="mark">*</span></p>
+            <p class="d-flex mb-5">
+              {{ t("profile.creator_name") }}<span class="mark">*</span>
+            </p>
             <v-text-field
               :rules="[(v) => !!v || '請輸入您的姓名']"
               variant="outlined"
@@ -591,7 +424,7 @@ let productList = ["機具", "織布"];
           </v-label>
 
           <v-label class="mt-5">
-            <p class="d-flex mb-5">職稱</p>
+            <p class="d-flex mb-5">{{ t("profile.job_position") }}</p>
             <v-text-field
               variant="outlined"
               density="compact"
@@ -602,7 +435,7 @@ let productList = ["機具", "織布"];
           </v-label>
 
           <v-label class="mt-5">
-            <p class="d-flex mb-5">公司、工作室</p>
+            <p class="d-flex mb-5">{{ t("profile.company") }}</p>
             <v-text-field
               variant="outlined"
               density="compact"
@@ -611,16 +444,16 @@ let productList = ["機具", "織布"];
           </v-label>
 
           <v-label>
-            <p class="d-flex mb-5">關於我</p>
+            <p class="d-flex mb-5">{{ t("profile.about_me") }}</p>
             <v-textarea rows="5" variant="outlined"></v-textarea>
           </v-label>
 
           <v-divider class="mt-10 mb-13"></v-divider>
 
-          <p class="text-h5 font-weight-bold">技能</p>
+          <p class="text-h5 font-weight-bold">{{ t("skill") }}</p>
 
           <v-label class="mt-5">
-            <p class="d-flex mb-5">擅長材質標籤</p>
+            <p class="d-flex mb-5">{{ t("material_tags") }}</p>
 
             <v-select
               :items="materialList"
@@ -636,7 +469,7 @@ let productList = ["機具", "織布"];
           </v-label>
 
           <v-label class="mt-10">
-            <p class="d-flex mb-5">擅長技能標籤</p>
+            <p class="d-flex mb-5">{{ t("skill_tags") }}</p>
 
             <v-select
               :items="technicalTypeList"
@@ -653,13 +486,13 @@ let productList = ["機具", "織布"];
 
           <v-divider class="mt-15 mb-13"></v-divider>
 
-          <p class="text-h5 font-weight-bold">合作過的夥伴</p>
+          <p class="text-h5 font-weight-bold">{{ t("partners") }}</p>
 
           <ul class="mb-16">
             <li class="my-5">
               <v-btn color="purple" variant="outlined">
                 <v-icon icon="mdi-plus" class="pt-1 pe-3"> </v-icon>
-                <p>新增合作夥伴</p>
+                <p>{{ t("create_partners") }}</p>
               </v-btn>
             </li>
             <li class="d-flex justify-space-between">
@@ -674,17 +507,6 @@ let productList = ["機具", "織布"];
           </ul>
 
           <div class="d-flex justify-center mt-5 mb-10">
-            <Transition>
-              <v-alert
-                v-if="alertState"
-                type="success"
-                variant="outlined"
-                density="compact"
-              >
-                儲存成功
-              </v-alert>
-            </Transition>
-
             <v-btn
               :loading="isLoading"
               variant="flat"
@@ -692,7 +514,7 @@ let productList = ["機具", "織布"];
               class="px-8"
               @click="saveResume()"
             >
-              儲存
+              {{ t("form.save") }}
             </v-btn>
           </div>
         </v-form>
@@ -701,10 +523,14 @@ let productList = ["機具", "織布"];
       <!-- 加工製造者 -->
       <v-window-item :value="3">
         <v-form @submit.prevent>
-          <p class="text-h5 font-weight-bold">加工技法資訊</p>
+          <p class="text-h5 font-weight-bold">
+            {{ t("profile.processing_techniques_info") }}
+          </p>
 
           <v-label class="mt-10">
-            <p class="d-flex mb-5">技法名稱<span class="mark">*</span></p>
+            <p class="d-flex mb-5">
+              {{ t("profile.technique_name") }}<span class="mark">*</span>
+            </p>
             <v-text-field
               :rules="[requiredRule]"
               variant="outlined"
@@ -714,12 +540,14 @@ let productList = ["機具", "織布"];
           </v-label>
 
           <v-label class="mt-5">
-            <p class="d-flex mb-5">描述<span class="mark">*</span></p>
+            <p class="d-flex mb-5">
+              {{ t("description") }}<span class="mark">*</span>
+            </p>
             <v-textarea rows="5" variant="outlined"></v-textarea>
           </v-label>
 
           <v-label class="my-5">
-            <p class="d-flex mb-5">技法類型</p>
+            <p class="d-flex mb-5">{{ t("profile.technique_type") }}</p>
 
             <v-select
               :items="technicalTypeList"
@@ -735,7 +563,9 @@ let productList = ["機具", "織布"];
           </v-label>
 
           <v-label class="mt-5">
-            <p class="d-flex mb-5">應用產品<span class="mark">*</span></p>
+            <p class="d-flex mb-5">
+              {{ t("profile.applied_products") }}<span class="mark">*</span>
+            </p>
             <v-select
               :items="productList"
               multiple
@@ -750,7 +580,9 @@ let productList = ["機具", "織布"];
           </v-label>
 
           <v-label class="my-5">
-            <p class="d-flex mb-5">應用材質<span class="mark">*</span></p>
+            <p class="d-flex mb-5">
+              {{ t("profile.applied_materials") }}<span class="mark">*</span>
+            </p>
 
             <v-select
               :items="materialList"
@@ -766,7 +598,9 @@ let productList = ["機具", "織布"];
           </v-label>
 
           <v-label class="my-5">
-            <p class="d-flex mb-5">製成機具<span class="mark">*</span></p>
+            <p class="d-flex mb-5">
+              {{ t("profile.manufactured_tools") }}<span class="mark">*</span>
+            </p>
             <v-select
               :items="productList"
               multiple
@@ -782,7 +616,7 @@ let productList = ["機具", "織布"];
 
           <v-label class="my-5">
             <p class="d-flex mb-5">
-              技法標籤<span class="mark">*</span>(協助人們發現您的作品)
+              {{ t("profile.technique_tags") }}
             </p>
 
             <v-select
@@ -800,10 +634,14 @@ let productList = ["機具", "織布"];
 
           <v-divider class="mt-10 mb-13"></v-divider>
 
-          <p class="text-h5 font-weight-bold">合作案例資訊</p>
+          <p class="text-h5 font-weight-bold">
+            {{ t("profile.collaboration_case_info") }}
+          </p>
 
           <v-label class="mt-10">
-            <p class="d-flex mb-5">案例名稱<span class="mark">*</span></p>
+            <p class="d-flex mb-5">
+              {{ t("profile.case_name") }}<span class="mark">*</span>
+            </p>
             <v-text-field
               :rules="[requiredRule]"
               variant="outlined"
@@ -813,12 +651,16 @@ let productList = ["機具", "織布"];
           </v-label>
 
           <v-label class="mt-5">
-            <p class="d-flex mb-5">描述<span class="mark">*</span></p>
+            <p class="d-flex mb-5">
+              {{ t("description") }}<span class="mark">*</span>
+            </p>
             <v-textarea rows="5" variant="outlined"></v-textarea>
           </v-label>
 
           <v-label class="my-5">
-            <p class="d-flex mb-5">產品材質<span class="mark">*</span></p>
+            <p class="d-flex mb-5">
+              {{ t("profile.products_materials") }}<span class="mark">*</span>
+            </p>
 
             <v-select
               :items="materialList"
@@ -834,7 +676,9 @@ let productList = ["機具", "織布"];
           </v-label>
 
           <v-label class="my-5">
-            <p class="d-flex mb-5">應用技法<span class="mark">*</span></p>
+            <p class="d-flex mb-5">
+              {{ t("profile.applied_technique") }}<span class="mark">*</span>
+            </p>
 
             <v-select
               :items="technicalList"
@@ -850,7 +694,9 @@ let productList = ["機具", "織布"];
           </v-label>
 
           <v-label class="mt-5">
-            <p class="d-flex mb-5">生產數量<span class="mark">*</span></p>
+            <p class="d-flex mb-5">
+              {{ t("profile.production_quantity") }}<span class="mark">*</span>
+            </p>
             <v-text-field
               :rules="[requiredRule]"
               variant="outlined"
@@ -861,7 +707,9 @@ let productList = ["機具", "織布"];
           </v-label>
 
           <v-label class="my-5">
-            <p class="d-flex mb-5">合作標籤<span class="mark">*</span></p>
+            <p class="d-flex mb-5">
+              {{ t("profile.collaboration_tags") }}<span class="mark">*</span>
+            </p>
 
             <v-select
               :items="technicalList"
@@ -877,17 +725,6 @@ let productList = ["機具", "織布"];
           </v-label>
 
           <div class="d-flex justify-center mt-5 mb-10">
-            <Transition>
-              <v-alert
-                v-if="alertState"
-                type="success"
-                variant="outlined"
-                density="compact"
-              >
-                儲存成功
-              </v-alert>
-            </Transition>
-
             <v-btn
               :loading="isLoading"
               variant="flat"
@@ -895,7 +732,7 @@ let productList = ["機具", "織布"];
               class="px-8"
               @click="saveResume()"
             >
-              儲存
+              {{ t("form.save") }}
             </v-btn>
           </div>
         </v-form>
@@ -960,7 +797,7 @@ let productList = ["機具", "織布"];
               ref="portfolioImgRef"
               label="File input"
               variant="outlined"
-              placeholder="選擇相片上傳"
+              :placeholder="t('form.choose_image')"
               @change="handlePortfolioImg"
               style="display: none"
             ></v-file-input>

+ 18 - 26
src/views/User/Proposal.vue

@@ -1,16 +1,12 @@
 <script setup>
 import { ref, reactive, watch } from "vue";
 import { useMainStore } from "@/stores/store";
-// import { Loader } from "@googlemaps/js-api-loader";
-import { VDataTable } from "vuetify/labs/VDataTable";
-import VueDatePicker from "@vuepic/vue-datepicker";
 import "@vuepic/vue-datepicker/dist/main.css";
 import axios from "axios";
 import moment from "moment";
 
 const store = useMainStore();
 
-let token = store.token;
 let loading = ref(false);
 let title = ref(null);
 let isCheck = ref(null);
@@ -20,13 +16,9 @@ let totalPages = ref(1); // 總頁數
 let proposal = reactive({
   list: [],
 });
-// let classes = reactive({
-//   list: [],
-// });
 
 // 搜尋
 let searchInput = ref("");
-let searchState = ref(false);
 
 watch(pageNum, () => {
   getSchool();
@@ -74,14 +66,14 @@ function selectTag(btn) {
 <template>
   <v-card class="h-100 user-courses">
     <div ref="title" class="title">
-      <h4>我的提案</h4>
+      <h4>{{ t("my_proposal") }}</h4>
     </div>
 
     <div class="d-flex align-center ms-auto search-item">
       <v-text-field
         @keyup.enter="search()"
         v-model="searchInput"
-        label="搜尋關鍵字"
+        :label="t('keyword_search')"
         variant="outlined"
         density="compact"
         hide-details
@@ -102,28 +94,28 @@ function selectTag(btn) {
         @click="selectTag('all')"
         :class="{ active: assignTag === 'all' }"
       >
-        全部
+        {{ t("all") }}
       </v-btn>
       <v-btn
         variant="text"
         @click="selectTag('0')"
         :class="{ active: assignTag === '0' }"
       >
-        未審核
+        {{ t("awaiting_approval") }}
       </v-btn>
       <v-btn
         variant="text"
         @click="selectTag('1')"
         :class="{ active: assignTag === '1' }"
       >
-        已審核
+        {{ t("approved") }}
       </v-btn>
       <v-btn
         variant="text"
         @click="selectTag('2')"
         :class="{ active: assignTag === '2' }"
       >
-        已駁回
+        {{ t("rejected") }}
       </v-btn>
     </div>
 
@@ -140,21 +132,22 @@ function selectTag(btn) {
         v-if="!proposal.list.length && isCheck !== '2' && isCheck !== '3'"
         to="/setup-courses/proposal"
         class="hint-item mb-7"
-        >點此前往提案</router-link
       >
+        {{ t("form.go_to_proposal") }}
+      </router-link>
       <table v-else>
         <thead>
           <tr>
             <th width="10%"></th>
-            <th width="10%">提案日期</th>
-            <th width="70%">工坊名稱</th>
-            <th width="10%">提案內容</th>
+            <th width="10%">{{ t("form.proposal_date") }}</th>
+            <th width="70%">{{ t("form.location_name") }}</th>
+            <th width="10%">{{ t("form.proposal_content") }}</th>
           </tr>
         </thead>
         <tbody>
           <tr v-for="(item, index) in proposal.list" :key="index">
             <td>
-              <v-chip v-if="item.is_pass_proposal === 0"> 提案審核中 </v-chip>
+              <v-chip v-if="item.is_pass_proposal === 0">{{ t("proposal_awaiting_approval") }}</v-chip>
               <!-- <v-chip
                 variant="outlined"
                 color="green"
@@ -165,21 +158,21 @@ function selectTag(btn) {
               </v-chip> -->
 
               <v-chip v-if="item.is_pass_proposal === 1 && item.is_check === 0">
-                未審核
+                {{ t("awaiting_approval") }}
               </v-chip>
               <v-chip
                 v-else-if="item.is_check === 1"
                 color="green"
                 text-color="white"
               >
-                已審核
+                {{ t("approved") }}
               </v-chip>
               <v-chip
                 v-else-if="item.is_check === 2"
                 color="error"
                 text-color="white"
               >
-                已駁回
+                {{ t("rejected") }}
               </v-chip>
             </td>
 
@@ -192,10 +185,10 @@ function selectTag(btn) {
             <td>
               <div>
                 <v-btn color="purple" variant="outlined">
-                  <p>工藝教育者</p>
+                  <p>{{ t("instructor") }}</p>
                 </v-btn>
                 <v-btn color="purple" variant="flat" class="w-100 mt-3">
-                  <p>課程場次</p>
+                  <p>{{ t("session") }}</p>
                 </v-btn>
               </div>
             </td>
@@ -215,5 +208,4 @@ function selectTag(btn) {
   </v-card>
 </template>
 
-<style lang="scss">
-</style>
+<style lang="scss"></style>

+ 2 - 2
src/views/User/Setting.vue

@@ -2,12 +2,13 @@
 import { ref, reactive, watch } from "vue";
 import { useMainStore } from "@/stores/store";
 import axios from "axios";
-import CourseCard from "@/components/CourseCard.vue";
 
 const store = useMainStore();
+
 let favorites = reactive({
   list: [],
 });
+
 let favoritesAll = reactive({
   list: [],
 });
@@ -77,7 +78,6 @@ const handleSearch = () => {
     <div class="title">
       <h4>帳戶設定</h4>
     </div>
-    
   </v-card>
 </template>
 

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels