SyuanYu hai 1 ano
pai
achega
70d30b8fa5
Modificáronse 81 ficheiros con 7686 adicións e 1465 borrados
  1. 156 7
      package-lock.json
  2. 2 0
      package.json
  3. 25 3
      src/App.vue
  4. 259 20
      src/assets/css/style.css
  5. 0 0
      src/assets/css/style.css.map
  6. 297 24
      src/assets/css/style.scss
  7. BIN=BIN
      src/assets/img/college-group/banner-back.png
  8. BIN=BIN
      src/assets/img/college-group/banner-mb.png
  9. BIN=BIN
      src/assets/img/college-group/banner.png
  10. BIN=BIN
      src/assets/img/college-group/craft/banner.png
  11. BIN=BIN
      src/assets/img/college-group/cross/banner.png
  12. BIN=BIN
      src/assets/img/college-group/future/banner.png
  13. BIN=BIN
      src/assets/img/college-group/generation/01.png
  14. BIN=BIN
      src/assets/img/college-group/generation/02.png
  15. BIN=BIN
      src/assets/img/college-group/generation/03.png
  16. BIN=BIN
      src/assets/img/college-group/generation/04.png
  17. BIN=BIN
      src/assets/img/college-group/generation/05.png
  18. BIN=BIN
      src/assets/img/college-group/generation/06.png
  19. BIN=BIN
      src/assets/img/college-group/generation/07.png
  20. BIN=BIN
      src/assets/img/college-group/generation/08.png
  21. BIN=BIN
      src/assets/img/college-group/generation/banner.png
  22. BIN=BIN
      src/assets/img/college-group/repair/banner.png
  23. BIN=BIN
      src/assets/img/college-group/teenager/banner.png
  24. BIN=BIN
      src/assets/img/course/banner-mb.png
  25. BIN=BIN
      src/assets/img/course/banner.png
  26. BIN=BIN
      src/assets/img/course/探索課程素材-15.png
  27. 0 0
      src/assets/img/default.png
  28. 0 0
      src/assets/img/home/banner-back.png
  29. BIN=BIN
      src/assets/img/home/banner.webp
  30. BIN=BIN
      src/assets/img/home/carousel-01.jpg
  31. BIN=BIN
      src/assets/img/home/news-bg.png
  32. BIN=BIN
      src/assets/img/passport/banner.png
  33. BIN=BIN
      src/assets/img/setup-courses/tutorial/background.mp4
  34. BIN=BIN
      src/assets/img/setup-courses/tutorial/素材-13.png
  35. BIN=BIN
      src/assets/img/setup-courses/tutorial/素材-14.png
  36. BIN=BIN
      src/assets/img/setup-courses/tutorial/素材-15.png
  37. 18 10
      src/components/ArticleCard.vue
  38. 31 10
      src/components/CourseCard.vue
  39. 474 0
      src/components/CoursesTutorial.vue
  40. 138 0
      src/components/CraftsArticle.vue
  41. 107 0
      src/components/HomeList.vue
  42. 47 23
      src/components/Navbar.vue
  43. 6 4
      src/components/PDFViewer.vue
  44. 109 0
      src/components/TermsList.vue
  45. 149 0
      src/language/en.json
  46. 149 0
      src/language/zh.json
  47. 2 1
      src/main.js
  48. 16 0
      src/plugins/i18n.js
  49. 77 32
      src/router/index.js
  50. 40 0
      src/utils/useCraftsPdf.js
  51. 1 1
      src/views/Article.vue
  52. 24 35
      src/views/ArticleDetail.vue
  53. 317 0
      src/views/CollegeGroup/Cfa.vue
  54. 396 17
      src/views/CollegeGroup/Craft.vue
  55. 231 181
      src/views/CollegeGroup/Cross.vue
  56. 12 0
      src/views/CollegeGroup/Future.vue
  57. 7 282
      src/views/CollegeGroup/Life.vue
  58. 101 3
      src/views/CollegeGroup/Life/Apprentice/Course.vue
  59. 4 4
      src/views/CollegeGroup/Life/Apprentice/Main.vue
  60. 5 177
      src/views/CollegeGroup/Life/Apprentice/Terms.vue
  61. 1 1
      src/views/CollegeGroup/Life/Campus.vue
  62. 2 2
      src/views/CollegeGroup/Life/CraftJourney.vue
  63. 69 5
      src/views/CollegeGroup/Main.vue
  64. 339 0
      src/views/CollegeGroup/Online.vue
  65. 12 7
      src/views/CollegeGroup/Repair.vue
  66. 29 35
      src/views/CourseDetail.vue
  67. 55 60
      src/views/CourseList.vue
  68. 1513 0
      src/views/Courses/Create.vue
  69. 4 23
      src/views/Courses/SetUp.vue
  70. 41 0
      src/views/Courses/Tutorial.vue
  71. 281 115
      src/views/Crafts.vue
  72. 471 109
      src/views/Home.vue
  73. 52 30
      src/views/Login.vue
  74. 2 2
      src/views/News.vue
  75. 12 2
      src/views/NewsDetail.vue
  76. 1086 0
      src/views/User/Courses.vue
  77. 14 8
      src/views/User/Dashboard.vue
  78. 9 0
      src/views/User/FavoriteClass.vue
  79. 10 97
      src/views/User/Passport.vue
  80. 396 135
      src/views/User/Profile.vue
  81. 88 0
      src/views/User/Setting.vue

+ 156 - 7
package-lock.json

@@ -8,6 +8,7 @@
       "name": "ntcri",
       "version": "0.0.0",
       "dependencies": {
+        "@googlemaps/js-api-loader": "^1.16.2",
         "@mdi/font": "^7.2.96",
         "@vuepic/vue-datepicker": "^5.4.0",
         "animate.css": "^4.1.1",
@@ -18,6 +19,7 @@
         "pinia": "^2.1.4",
         "swiper": "^10.1.0",
         "vue": "^3.2.47",
+        "vue-i18n": "^9.2.2",
         "vue-router": "^4.2.2",
         "vuetify": "^3.3.2"
       },
@@ -403,6 +405,71 @@
         "node": ">=12"
       }
     },
+    "node_modules/@googlemaps/js-api-loader": {
+      "version": "1.16.2",
+      "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.2.tgz",
+      "integrity": "sha512-psGw5u0QM6humao48Hn4lrChOM2/rA43ZCm3tKK9qQsEj1/VzqkCqnvGfEOshDbBQflydfaRovbKwZMF4AyqbA==",
+      "dependencies": {
+        "fast-deep-equal": "^3.1.3"
+      }
+    },
+    "node_modules/@intlify/core-base": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz",
+      "integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==",
+      "dependencies": {
+        "@intlify/devtools-if": "9.2.2",
+        "@intlify/message-compiler": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@intlify/devtools-if": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz",
+      "integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==",
+      "dependencies": {
+        "@intlify/shared": "9.2.2"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@intlify/message-compiler": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz",
+      "integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==",
+      "dependencies": {
+        "@intlify/shared": "9.2.2",
+        "source-map": "0.6.1"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@intlify/shared": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
+      "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==",
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@intlify/vue-devtools": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz",
+      "integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==",
+      "dependencies": {
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
     "node_modules/@jridgewell/gen-mapping": {
       "version": "0.3.3",
       "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
@@ -825,6 +892,11 @@
       "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
       "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
     },
+    "node_modules/fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+    },
     "node_modules/fill-range": {
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -1173,9 +1245,6 @@
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
       "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-      "dev": true,
-      "optional": true,
-      "peer": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -1324,6 +1393,23 @@
         "@vue/shared": "3.3.4"
       }
     },
+    "node_modules/vue-i18n": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz",
+      "integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==",
+      "dependencies": {
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2",
+        "@vue/devtools-api": "^6.2.1"
+      },
+      "engines": {
+        "node": ">= 14"
+      },
+      "peerDependencies": {
+        "vue": "^3.0.0"
+      }
+    },
     "node_modules/vue-router": {
       "version": "4.2.2",
       "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.2.tgz",
@@ -1540,6 +1626,56 @@
       "dev": true,
       "optional": true
     },
+    "@googlemaps/js-api-loader": {
+      "version": "1.16.2",
+      "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.2.tgz",
+      "integrity": "sha512-psGw5u0QM6humao48Hn4lrChOM2/rA43ZCm3tKK9qQsEj1/VzqkCqnvGfEOshDbBQflydfaRovbKwZMF4AyqbA==",
+      "requires": {
+        "fast-deep-equal": "^3.1.3"
+      }
+    },
+    "@intlify/core-base": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz",
+      "integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==",
+      "requires": {
+        "@intlify/devtools-if": "9.2.2",
+        "@intlify/message-compiler": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2"
+      }
+    },
+    "@intlify/devtools-if": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz",
+      "integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==",
+      "requires": {
+        "@intlify/shared": "9.2.2"
+      }
+    },
+    "@intlify/message-compiler": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz",
+      "integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==",
+      "requires": {
+        "@intlify/shared": "9.2.2",
+        "source-map": "0.6.1"
+      }
+    },
+    "@intlify/shared": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
+      "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q=="
+    },
+    "@intlify/vue-devtools": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz",
+      "integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==",
+      "requires": {
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2"
+      }
+    },
     "@jridgewell/gen-mapping": {
       "version": "0.3.3",
       "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
@@ -1898,6 +2034,11 @@
       "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
       "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
     },
+    "fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+    },
     "fill-range": {
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -2102,10 +2243,7 @@
     "source-map": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-      "dev": true,
-      "optional": true,
-      "peer": true
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
     },
     "source-map-js": {
       "version": "1.0.2",
@@ -2186,6 +2324,17 @@
         "@vue/shared": "3.3.4"
       }
     },
+    "vue-i18n": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz",
+      "integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==",
+      "requires": {
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2",
+        "@vue/devtools-api": "^6.2.1"
+      }
+    },
     "vue-router": {
       "version": "4.2.2",
       "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.2.tgz",

+ 2 - 0
package.json

@@ -9,6 +9,7 @@
     "preview": "vite preview"
   },
   "dependencies": {
+    "@googlemaps/js-api-loader": "^1.16.2",
     "@mdi/font": "^7.2.96",
     "@vuepic/vue-datepicker": "^5.4.0",
     "animate.css": "^4.1.1",
@@ -19,6 +20,7 @@
     "pinia": "^2.1.4",
     "swiper": "^10.1.0",
     "vue": "^3.2.47",
+    "vue-i18n": "^9.2.2",
     "vue-router": "^4.2.2",
     "vuetify": "^3.3.2"
   },

+ 25 - 3
src/App.vue

@@ -13,8 +13,24 @@ const scrollToTop = () => {
   window.scrollTo({ top: 0, behavior: "smooth" });
 };
 
+let isMounted = ref(false);
+
 onMounted(() => {
+  setTimeout(() => {
+    isMounted.value = true;
+  }, 500);
+  
   window.addEventListener("scroll", checkScrollPosition);
+
+  // 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);
+  // End of HubSpot Embed Code
 });
 
 onBeforeUnmount(() => {
@@ -33,14 +49,20 @@ onBeforeUnmount(() => {
       class="top-btn"
     ></v-btn>
   </Transition>
-  <Footer />
+  <Footer v-show="isMounted" />
 </template>
 
 <style lang="scss">
 .top-btn {
+  width: 60px !important;
+  height: 60px !important;
   position: fixed !important;
-  right: 20px;
-  bottom: 20px;
+  right: 16px;
+  bottom: 90px;
   z-index: 100;
+  box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 4px, rgba(0, 0, 0, 0.2) 0px 2px 12px !important;
+  .v-icon {
+    font-size: 33px;
+  }
 }
 </style>

+ 259 - 20
src/assets/css/style.css

@@ -51,6 +51,7 @@ a {
 :root {
   --main-color: #C39F68;
   --sub-color: #E9F1F4;
+  --gray: #888888;
   --brown: #3E3A39;
   --blue: #A7C1CC;
   --purple: #BCA2B5;
@@ -83,6 +84,20 @@ input:focus-visible {
   opacity: 0;
 }
 
+.v-label {
+  width: 100%;
+  opacity: 1 !important;
+}
+.v-label .v-input {
+  width: 100%;
+}
+
+.mark {
+  display: inline-block;
+  margin-left: 1px;
+  color: red;
+}
+
 @media (min-width: 1920px) {
   .v-container {
     max-width: 1300px !important;
@@ -126,13 +141,14 @@ input:focus-visible {
 .search button img {
   width: 25px;
   position: absolute;
-  top: 2px;
+  top: 5px;
   right: 0;
 }
 .search .error {
   position: absolute;
   right: 30px;
   bottom: -30px;
+  font-size: 14px;
 }
 
 .college-bg-img {
@@ -156,33 +172,96 @@ input:focus-visible {
 .college-banner img {
   width: 100%;
 }
-.college-banner h1 {
+@media (max-width: 475px) {
+  .college-banner img {
+    height: 300px;
+    -o-object-fit: cover;
+       object-fit: cover;
+    -o-object-position: left;
+       object-position: left;
+    border-radius: 10px 10px 0 0;
+  }
+}
+.college-banner .description-item {
   position: absolute;
-  top: 45%;
-  right: 3%;
+  right: 15px;
+  bottom: 28%;
+  z-index: 1000;
+}
+@media (max-width: 1280px) {
+  .college-banner .description-item {
+    bottom: 35%;
+  }
+}
+@media (max-width: 960px) {
+  .college-banner .description-item {
+    bottom: 30%;
+  }
+}
+@media (max-width: 600px) {
+  .college-banner .description-item {
+    bottom: 25%;
+  }
+}
+.college-banner .description-item h1 {
+  text-align: end;
+  margin-bottom: 35px;
   font-size: 40px;
   font-weight: 500;
-  color: #fff;
-  text-shadow: 2px 2px 10px #555555;
+  text-shadow: 2px 2px 10px #333;
   word-wrap: break-word;
 }
-@media (max-width: 1200px) {
-  .college-banner h1 {
-    right: 5%;
+@media (max-width: 1280px) {
+  .college-banner .description-item h1 {
+    margin-bottom: 20px;
     font-size: 36px;
   }
 }
 @media (max-width: 960px) {
-  .college-banner h1 {
+  .college-banner .description-item h1 {
+    font-size: 32px;
+  }
+}
+@media (max-width: 600px) {
+  .college-banner .description-item h1 {
     font-size: 24px;
+    margin-bottom: 15px;
+  }
+}
+.college-banner .description-item p {
+  width: 515px;
+  font-size: 18px;
+  line-height: 30px;
+  text-shadow: 2px 2px 4px #333;
+}
+@media (max-width: 1280px) {
+  .college-banner .description-item p {
+    font-size: 16px;
+  }
+}
+@media (max-width: 600px) {
+  .college-banner .description-item p {
+    width: 270px;
+    font-size: 16px;
+    line-height: 22px;
   }
 }
 @media (max-width: 600px) {
-  .college-banner h1 {
-    font-size: 20px;
-    top: 45%;
+  .college-banner .description-item p {
+    font-size: 14px;
   }
 }
+.college-banner .description-item h1,
+.college-banner .description-item p {
+  color: #fff;
+  letter-spacing: 1px;
+}
+
+.cooming-soon {
+  font-size: 20px;
+  color: var(--gray);
+  letter-spacing: 1px;
+}
 
 .college-content {
   padding: 0;
@@ -196,7 +275,7 @@ input:focus-visible {
 }
 .college-content .main-block {
   padding: 150px 80px;
-  margin-top: -20%;
+  margin-top: -21%;
   background-image: url("@/assets/img/course/background.png");
   background-size: cover;
   position: relative;
@@ -214,11 +293,11 @@ input:focus-visible {
 @media (max-width: 600px) {
   .college-content .main-block {
     padding: 50px 20px;
+    margin-top: -24%;
   }
 }
 .college-content .main-block .title {
   padding: 80px 0;
-  margin-left: 10px;
   font-size: 30px;
   line-height: 32px;
   letter-spacing: 1px;
@@ -245,6 +324,16 @@ input:focus-visible {
   }
 }
 
+.tab-btn button {
+  height: 45px !important;
+  font-size: 28px;
+  font-weight: 500;
+  color: #ccc;
+}
+.tab-btn button.active {
+  color: #000;
+}
+
 .main-card {
   height: 100%;
   letter-spacing: 1px;
@@ -306,9 +395,52 @@ input:focus-visible {
   line-height: 20px;
 }
 
+.dot-item span {
+  position: absolute;
+  display: block;
+  width: 15px;
+  height: 15px;
+  border-radius: 100px;
+  background-color: var(--blue);
+}
+.dot-item .t-dot {
+  top: 15px;
+  left: 15px;
+}
+.dot-item .r-dot {
+  top: 15px;
+  right: 15px;
+}
+.dot-item .b-dot {
+  bottom: 15px;
+  right: 15px;
+}
+.dot-item .l-dot {
+  bottom: 15px;
+  left: 15px;
+}
+
+.hint-item {
+  width: 100%;
+  max-width: 500px;
+  display: block;
+  padding: 20px;
+  margin: 30px auto 0;
+  border-radius: 10px;
+  box-shadow: 2px 2px 8px #ccc;
+  border: 1px solid var(--purple);
+  letter-spacing: 2px;
+  text-align: center;
+  line-height: 26px;
+  transition: all 0.3s;
+}
+.hint-item:hover {
+  box-shadow: 2px 2px 12px #bcbcbc;
+}
+
 .favorites-btn {
   position: absolute;
-  bottom: 20px;
+  bottom: 15px;
   right: 15px;
 }
 
@@ -331,19 +463,67 @@ input:focus-visible {
   display: flex;
   align-items: center;
   justify-content: center;
-  color: #bda2b5;
-  border: 1px solid #bda2b5;
+  color: var(--purple);
+  border: 1px solid var(--purple);
   border-radius: 20px;
   text-align: center;
   transition: all 0.3s;
 }
 .tag-btn .item:hover {
-  color: #bb8bad;
-  border-color: #bb8bad;
+  color: #fff;
+  border-color: var(--purple);
+  background-color: var(--purple);
 }
 .tag-btn .item a {
   width: 100%;
   display: block;
+  line-height: 24px;
+  letter-spacing: 1px;
+}
+
+.hint {
+  font-size: 14px;
+  color: #919191;
+  letter-spacing: 1px;
+}
+
+.total-item {
+  margin-top: 15px;
+  display: block;
+  font-size: 14px;
+  letter-spacing: 2px;
+  text-align: center;
+}
+
+.filter-list .v-select {
+  margin-bottom: 10px;
+}
+.filter-list .v-select .v-field {
+  overflow: hidden;
+}
+.filter-list .v-select .v-field__overlay {
+  opacity: 1 !important;
+  border: 1px solid #ccc;
+  background-color: #fff;
+  border-radius: 100px !important;
+}
+.filter-list .v-select .v-field__outline {
+  display: none;
+}
+.filter-list .v-select .v-field__input {
+  padding-top: 15px;
+}
+.filter-list .v-select .v-field__field {
+  height: 45px;
+}
+.filter-list .v-select .v-select__selection {
+  overflow: hidden;
+}
+.filter-list .v-select .v-select__selection-text {
+  white-space: nowrap;
+}
+.filter-list .v-select .v-label.v-field-label--floating {
+  top: 3px !important;
 }
 
 .trave-content .info,
@@ -473,10 +653,69 @@ input:focus-visible {
   opacity: 0.8;
 }
 
+.main-table {
+  overflow-x: auto;
+}
+.main-table .table-title {
+  padding: 10px;
+  color: #fff;
+  font-size: 20px;
+  font-weight: 400;
+  text-align: center;
+}
+@media (max-width: 1280px) {
+  .main-table .table-title {
+    width: 800px;
+  }
+}
+.main-table table {
+  width: 100%;
+  margin-bottom: 20px;
+  position: relative;
+  border-collapse: collapse;
+}
+@media (max-width: 1280px) {
+  .main-table table {
+    width: 800px;
+  }
+}
+.main-table table thead th {
+  padding: 20px 0;
+  font-weight: 500;
+}
+.main-table table tbody td {
+  padding: 20px 0;
+  text-align: center;
+  letter-spacing: 1px;
+}
+
 .v-chip {
   letter-spacing: 1px;
 }
 
+.v-pagination {
+  margin: auto;
+  max-width: 500px;
+}
+
+::-webkit-scrollbar {
+  width: 10px;
+}
+
+::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 10px;
+}
+
+::-webkit-scrollbar-thumb {
+  background: #b6b6b6;
+  border-radius: 10px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+  background: #777777;
+}
+
 .dp__input {
   padding: 15px 40px;
   background-color: #f5f5f5;

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
src/assets/css/style.css.map


+ 297 - 24
src/assets/css/style.scss

@@ -54,6 +54,7 @@ a {
 :root {
   --main-color: #C39F68;
   --sub-color: #E9F1F4;
+  --gray: #888888;
   --brown: #3E3A39;
   --blue: #A7C1CC; // 霧藍
   --purple: #BCA2B5; // 粉紫
@@ -87,6 +88,21 @@ input:focus-visible {
   opacity: 0;
 }
 
+.v-label {
+  width: 100%;
+  opacity: 1 !important;
+
+  .v-input {
+    width: 100%;
+  }
+}
+
+.mark {
+  display: inline-block;
+  margin-left: 1px;
+  color: red;
+}
+
 .v-container {
   @media (min-width: 1920px) {
     max-width: 1300px !important;
@@ -130,7 +146,7 @@ input:focus-visible {
     img {
       width: 25px;
       position: absolute;
-      top: 2px;
+      top: 5px;
       right: 0;
     }
   }
@@ -139,6 +155,7 @@ input:focus-visible {
     position: absolute;
     right: 30px;
     bottom: -30px;
+    font-size: 14px;
   }
 }
 
@@ -162,34 +179,91 @@ input:focus-visible {
 
   img {
     width: 100%;
+
+    @media (max-width: 475px) {
+      height: 300px;
+      object-fit: cover;
+      object-position: left;
+      border-radius: 10px 10px 0 0;
+    }
   }
 
-  h1 {
+  .description-item {
     position: absolute;
-    top: 45%;
-    right: 3%;
-    font-size: 40px;
-    font-weight: 500;
-    color: #fff;
-    text-shadow: 2px 2px 10px #555555;
-    word-wrap: break-word;
+    right: 15px;
+    bottom: 28%;
+    z-index: 1000;
 
-    @media (max-width: 1200px) {
-      right: 5%;
-      font-size: 36px;
+    @media (max-width: 1280px) {
+      bottom: 35%;
     }
 
     @media (max-width: 960px) {
-      font-size: 24px;
+      bottom: 30%;
     }
 
     @media (max-width: 600px) {
-      font-size: 20px;
-      top: 45%;
+      bottom: 25%;
+    }
+
+    h1 {
+      text-align: end;
+      margin-bottom: 35px;
+      font-size: 40px;
+      font-weight: 500;
+      text-shadow: 2px 2px 10px #333;
+      word-wrap: break-word;
+
+      @media (max-width: 1280px) {
+        margin-bottom: 20px;
+        font-size: 36px;
+      }
+
+      @media (max-width: 960px) {
+        font-size: 32px;
+      }
+
+      @media (max-width: 600px) {
+        font-size: 24px;
+        margin-bottom: 15px;
+      }
+    }
+
+    p {
+      width: 515px;
+      font-size: 18px;
+      line-height: 30px;
+      text-shadow: 2px 2px 4px #333;
+
+      @media (max-width: 1280px) {
+        font-size: 16px;
+      }
+
+      @media (max-width: 600px) {
+        width: 270px;
+        font-size: 16px;
+        line-height: 22px;
+      }
+
+      @media (max-width: 600px) {
+        font-size: 14px;
+      }
+    }
+
+    h1,
+    p {
+      color: #fff;
+      letter-spacing: 1px;
     }
   }
 }
 
+.cooming-soon {
+  font-size: 20px;
+  color: var(--gray);
+  letter-spacing: 1px;
+}
+
 .college-content {
   padding: 0;
   width: 1300px !important;
@@ -201,14 +275,13 @@ input:focus-visible {
 
   .main-block {
     padding: 150px 80px;
-    margin-top: -20%;
+    margin-top: -21%;
     background-image: url('@/assets/img/course/background.png');
     background-size: cover;
     position: relative;
     left: 0;
     right: 0;
     z-index: 10;
-    // background-color: #fff;
 
     @media (max-width: 960px) {
       padding: 100px 50px;
@@ -218,11 +291,19 @@ input:focus-visible {
 
     @media (max-width: 600px) {
       padding: 50px 20px;
+      margin-top: -24%;
     }
 
+    // &.life {
+    //   padding: 50px 80px;
+    //   margin-top: 20px;
+    //   background-image: none;
+    //   background-color: #fff;
+    //   border-radius: 20px;
+    // }
+
     .title {
       padding: 80px 0;
-      margin-left: 10px;
       font-size: 30px;
       line-height: 32px;
       letter-spacing: 1px;
@@ -249,6 +330,19 @@ input:focus-visible {
   }
 }
 
+.tab-btn {
+  button {
+    height: 45px !important;
+    font-size: 28px;
+    font-weight: 500;
+    color: #ccc;
+
+    &.active {
+      color: #000;
+    }
+  }
+}
+
 .main-card {
   height: 100%;
   letter-spacing: 1px;
@@ -322,9 +416,61 @@ input:focus-visible {
   }
 }
 
+.dot-item {
+  span {
+    position: absolute;
+    display: block;
+    width: 15px;
+    height: 15px;
+    border-radius: 100px;
+    background-color: var(--blue);
+  }
+
+  .t-dot {
+    top: 15px;
+    left: 15px;
+  }
+
+  .r-dot {
+    top: 15px;
+    right: 15px;
+  }
+
+  .b-dot {
+    bottom: 15px;
+    right: 15px;
+  }
+
+  .l-dot {
+    bottom: 15px;
+    left: 15px;
+  }
+}
+
+.hint-item {
+  width: 100%;
+  max-width: 500px;
+  display: block;
+  padding: 20px;
+  margin: 30px auto 0;
+  border-radius: 10px;
+  box-shadow: 2px 2px 8px #ccc;
+  border: 1px solid var(--purple);
+  letter-spacing: 2px;
+  text-align: center;
+  line-height: 26px;
+  transition: all .3s;
+
+  &:hover {
+    // color: #fff;
+    // background-color: var(--purple);
+    box-shadow: 2px 2px 12px #bcbcbc;
+  }
+}
+
 .favorites-btn {
   position: absolute;
-  bottom: 20px;
+  bottom: 15px;
   right: 15px;
 }
 
@@ -347,20 +493,79 @@ input:focus-visible {
     display: flex;
     align-items: center;
     justify-content: center;
-    color: #bda2b5;
-    border: 1px solid #bda2b5;
+    color: var(--purple);
+    border: 1px solid var(--purple);
     border-radius: 20px;
     text-align: center;
     transition: all .3s;
 
     &:hover {
-      color: #bb8bad;
-      border-color: #bb8bad;
+      color: #fff;
+      border-color: var(--purple);
+      background-color: var(--purple);
     }
 
     a {
       width: 100%;
       display: block;
+      line-height: 24px;
+      letter-spacing: 1px;
+    }
+  }
+}
+
+.hint {
+  font-size: 14px;
+  color: #919191;
+  letter-spacing: 1px;
+}
+
+.total-item {
+  margin-top: 15px;
+  display: block;
+  font-size: 14px;
+  letter-spacing: 2px;
+  text-align: center;
+}
+
+.filter-list {
+  .v-select {
+    margin-bottom: 10px;
+
+    .v-field {
+      overflow: hidden;
+    }
+
+    .v-field__overlay {
+      opacity: 1 !important;
+      border: 1px solid #ccc;
+      background-color: #fff;
+      border-radius: 100px !important;
+    }
+
+    .v-field__outline {
+      display: none;
+    }
+
+
+    .v-field__input {
+      padding-top: 15px;
+    }
+
+    .v-field__field {
+      height: 45px;
+    }
+
+    .v-select__selection {
+      overflow: hidden;
+    }
+
+    .v-select__selection-text {
+      white-space: nowrap;
+    }
+
+    .v-label.v-field-label--floating {
+      top: 3px !important;
     }
   }
 }
@@ -469,6 +674,7 @@ input:focus-visible {
   background-color: var(--brown);
   border: 2px solid transparent;
   transition: all 0.5s;
+
   &:hover {
     color: var(--brown);
     border: 2px solid var(--brown);
@@ -486,15 +692,82 @@ input:focus-visible {
   border-radius: 100px;
   background-color: var(--brown);
   transition: all .3s;
+
   &:hover {
     opacity: .8;
   }
 }
 
+.main-table {
+  overflow-x: auto;
+
+  .table-title {
+    padding: 10px;
+    color: #fff;
+    font-size: 20px;
+    font-weight: 400;
+    text-align: center;
+
+    @media (max-width: 1280px) {
+      width: 800px;
+    }
+  }
+
+  table {
+    width: 100%;
+    margin-bottom: 20px;
+    position: relative;
+    border-collapse: collapse;
+
+    @media (max-width: 1280px) {
+      width: 800px;
+    }
+
+    thead {
+      th {
+        padding: 20px 0;
+        font-weight: 500;
+      }
+    }
+
+    tbody {
+      td {
+        padding: 20px 0;
+        text-align: center;
+        letter-spacing: 1px;
+      }
+    }
+  }
+}
+
 .v-chip {
   letter-spacing: 1px;
 }
 
+.v-pagination {
+  margin: auto;
+  max-width: 500px;
+}
+
+// scrollbar
+::-webkit-scrollbar {
+  width: 10px;
+}
+
+::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 10px;
+}
+
+::-webkit-scrollbar-thumb {
+  background: #b6b6b6;
+  border-radius: 10px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+  background: #777777;
+}
+
 // Vue DatePicker
 .dp__input {
   padding: 15px 40px;
@@ -538,4 +811,4 @@ input:focus-visible {
 // input:-webkit-autofill:focus,
 // input:-webkit-autofill:active {
 //   transition: background-color 0s 50000s, color 0s 50000s !important;
-// }
+// }

BIN=BIN
src/assets/img/college-group/banner-back.png


BIN=BIN
src/assets/img/college-group/banner-mb.png


BIN=BIN
src/assets/img/college-group/banner.png


BIN=BIN
src/assets/img/college-group/craft/banner.png


BIN=BIN
src/assets/img/college-group/cross/banner.png


BIN=BIN
src/assets/img/college-group/future/banner.png


BIN=BIN
src/assets/img/college-group/generation/01.png


BIN=BIN
src/assets/img/college-group/generation/02.png


BIN=BIN
src/assets/img/college-group/generation/03.png


BIN=BIN
src/assets/img/college-group/generation/04.png


BIN=BIN
src/assets/img/college-group/generation/05.png


BIN=BIN
src/assets/img/college-group/generation/06.png


BIN=BIN
src/assets/img/college-group/generation/07.png


BIN=BIN
src/assets/img/college-group/generation/08.png


BIN=BIN
src/assets/img/college-group/generation/banner.png


BIN=BIN
src/assets/img/college-group/repair/banner.png


BIN=BIN
src/assets/img/college-group/teenager/banner.png


BIN=BIN
src/assets/img/course/banner-mb.png


BIN=BIN
src/assets/img/course/banner.png


BIN=BIN
src/assets/img/course/探索課程素材-15.png


+ 0 - 0
src/assets/img/一日學徒.png → src/assets/img/default.png


+ 0 - 0
src/assets/img/home/banner.png → src/assets/img/home/banner-back.png


BIN=BIN
src/assets/img/home/banner.webp


BIN=BIN
src/assets/img/home/carousel-01.jpg


BIN=BIN
src/assets/img/home/news-bg.png


BIN=BIN
src/assets/img/passport/banner.png


BIN=BIN
src/assets/img/setup-courses/tutorial/background.mp4


BIN=BIN
src/assets/img/setup-courses/tutorial/素材-13.png


BIN=BIN
src/assets/img/setup-courses/tutorial/素材-14.png


BIN=BIN
src/assets/img/setup-courses/tutorial/素材-15.png


+ 18 - 10
src/components/ArticleCard.vue

@@ -28,8 +28,8 @@ const props = defineProps({
     class="d-flex flex-md-row flex-column align-center pa-5"
   >
     <v-row class="align-center">
-      <v-col cols="4" v-if="type === 'article'">
-        <router-link :to="`/article-detail/article/${data.article_id}`">
+      <v-col cols="12" sm="4" v-if="type === 'article'">
+        <router-link :to="`/article-detail/${data.article_id}`">
           <v-img
             cover
             class="mx-auto cover-img"
@@ -47,13 +47,14 @@ const props = defineProps({
           </v-img>
         </router-link>
       </v-col>
-      <v-col :cols="type === 'article' ? 8 : 12">
+      <v-col cols="12" :sm="type === 'article' ? 8 : 12">
         <router-link
           :to="
             type === 'article'
-              ? `/article-detail/article/${data.article_id}`
+              ? `/article-detail/${data.article_id}`
               : `/news/${data.news_id}`
           "
+          class="title-link"
         >
           <h3>{{ data.title }}</h3>
         </router-link>
@@ -61,7 +62,7 @@ const props = defineProps({
         <router-link
           :to="
             type === 'article'
-              ? `/article-detail/article/${data.article_id}`
+              ? `/article-detail/${data.article_id}`
               : `/news/${data.news_id}`
           "
           class="d-block pa-3"
@@ -75,16 +76,21 @@ const props = defineProps({
 
 <style lang="scss">
 .v-card {
-  border-radius: 10px;
   padding: 15px;
+  border-radius: 10px;
+  border-color: var(--purple);
+
+  .title-link {
+    display: block;
+    padding-bottom: 15px;
+    border-bottom: 1px solid #e0e0e0;
+  }
 
   h3 {
-    padding: 15px;
     font-size: 22px;
     font-weight: 500;
     line-height: 32px;
     letter-spacing: 1px;
-    border-bottom: 1px solid #e0e0e0;
   }
 
   h3,
@@ -109,7 +115,7 @@ const props = defineProps({
   section {
     -webkit-line-clamp: 4;
     line-height: 28px;
-  letter-spacing: 1px;
+    letter-spacing: 1px;
   }
 
   .cover-img {
@@ -121,6 +127,8 @@ const props = defineProps({
 .category {
   display: flex;
   align-items: center;
+  color: var(--purple);
+  text-shadow: 1px 1px 5px #fff;
 
   span {
     display: block;
@@ -128,7 +136,7 @@ const props = defineProps({
     height: 8px;
     margin-right: 10px;
     border-radius: 10px;
-    background-color: #000;
+    background-color: var(--purple);
   }
 }
 </style>

+ 31 - 10
src/components/CourseCard.vue

@@ -68,19 +68,28 @@ async function deleteFavoriteClass(classId) {
 
 // 檢查收藏狀態
 function isClassFavorite(classId) {
-  let list = favorites.list.map((e) => e.class_name_id);
-  return list.includes(classId);
+  if (favorites.list) {
+    let list = favorites.list.map((e) => e.class_name_id);
+    return list.includes(classId);
+  }
 }
 </script>
 
 <template>
   <div class="main-card">
     <section class="card-title">
-      <h3>{{ data.name }}</h3>
+      <h3>{{ data.org === "Udemy" ? data.title : data.name }}</h3>
     </section>
     <div class="card-info">
       <!-- <router-link :to="`/course-detail/${data.class_name_id}`"> -->
-      <a :href="$router.resolve(`/course-detail/${data.class_name_id}`).href">
+      <a
+        :href="
+          data.org !== 'Udemy'
+            ? $router.resolve(`/course-detail/${data.class_name_id}`).href
+            : data.video_url
+        "
+        target="_blank"
+      >
         <div class="overflow-hidden">
           <v-img
             class="mx-auto cover-img"
@@ -88,16 +97,20 @@ function isClassFavorite(classId) {
               data.is_inner === 0
                 ? data.cover_img
                 : data.special_class_list_name === 'one_day_class'
-                ? `../src/assets/img/一日學徒.png`
+                ? store.getImageUrl('default.png')
+                : data.org === 'Udemy'
+                ? data.cover_img
                 : `https://ntcri.org/${data.cover_img}`
             "
-            height="200px"
+            height="220px"
             cover
             :src="
               data.is_inner === 0
                 ? data.cover_img
                 : data.special_class_list_name === 'one_day_class'
-                ? '../src/assets/img/一日學徒.png'
+                ? store.getImageUrl('default.png')
+                : data.org === 'Udemy'
+                ? data.cover_img
                 : `https://ntcri.org/${data.cover_img}`
             "
           >
@@ -113,16 +126,16 @@ function isClassFavorite(classId) {
         </div>
       </a>
       <!-- </router-link> -->
-      <ul>
+      <ul v-if="data.org !== 'Udemy'">
         <li class="d-flex align-center mb-5">
-          <p class="text-gray font-weight-light">
+          <p class="text-gray pt-3">
             {{ data.introduction }}
           </p>
         </li>
         <li class="d-flex align-center mt-auto">
           <v-icon color="primary" icon="mdi-map-marker" class="me-1"></v-icon>
           <p class="mb-0 pe-5">
-            {{ data.school }}
+            {{ data.location_name }}
           </p>
         </li>
         <li>
@@ -144,6 +157,14 @@ function isClassFavorite(classId) {
           </button>
         </li>
       </ul>
+
+      <ul v-else class="justify-center">
+        <li>
+          <p class="text-gray pt-3">
+            {{ data.content }}
+          </p>
+        </li>
+      </ul>
     </div>
   </div>
 </template>

+ 474 - 0
src/components/CoursesTutorial.vue

@@ -0,0 +1,474 @@
+<script setup>
+import { ref } from "vue";
+import { useI18n } from "vue-i18n";
+import { Swiper, SwiperSlide } from "swiper/vue";
+import { Navigation, Pagination } from "swiper/modules";
+import "swiper/css";
+import "swiper/css/pagination";
+import "swiper/css/navigation";
+
+const { t } = useI18n();
+const modules = [Navigation, Pagination];
+const navigation = ref({
+  nextEl: ".swiper-button-next",
+  prevEl: ".swiper-button-prev",
+});
+</script>
+
+<template>
+  <v-container fluid class="pa-0">
+    <div class="video-background">
+      <video autoplay muted loop>
+        <source
+          src="@/assets/img/setup-courses/tutorial/background.mp4"
+          type="video/mp4"
+        />
+      </video>
+
+      <!-- 垂直輪播 -->
+      <swiper
+        :direction="'vertical'"
+        :navigation="navigation"
+        :modules="modules"
+        navigation
+      >
+        <swiper-slide>
+          <div class="main-item">
+            <img src="@/assets/img/setup-courses/tutorial/素材-13.png" alt="" />
+            <p class="ps-sm-10" v-html="t('tutorial.step')"></p>
+          </div>
+        </swiper-slide>
+
+        <swiper-slide>
+          <div class="step-item">
+            <h3>{{ t("tutorial.step_1_title") }}</h3>
+            <p>{{ t("tutorial.step_1_1") }}</p>
+            <strong>{{ t("tutorial.step_1_2") }}</strong>
+
+            <ul>
+              <li>
+                <p>{{ t("tutorial.step_1_2_1") }}<span class="mark">*</span></p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_1_2_2") }}<span class="mark">*</span></p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_1_2_3") }}<span class="mark">*</span></p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_1_2_4") }}<span class="mark">*</span></p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_1_2_5") }}</p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_1_2_6") }}<span class="mark">*</span></p>
+              </li>
+            </ul>
+
+            <p v-html="t('tutorial.step_1_3')"></p>
+
+            <div class="icon">
+              <img
+                src="@/assets/img/setup-courses/tutorial/素材-13.png"
+                alt=""
+              />
+            </div>
+          </div>
+        </swiper-slide>
+
+        <swiper-slide>
+          <div class="step-item">
+            <h3>{{ t("tutorial.step_2_title") }}</h3>
+            <p v-html="t('tutorial.step_2_1')"></p>
+            <strong>{{ t("tutorial.step_2_2") }}</strong>
+
+            <ul class="list">
+              <li>
+                <p>{{ t("tutorial.step_2_2_1") }}<span class="mark">*</span></p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_2_2_2") }}<span class="mark">*</span></p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_2_2_3") }}<span class="mark">*</span></p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_2_2_4") }}<span class="mark">*</span></p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_2_2_5") }}</p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_2_2_6") }}</p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_2_2_7") }}</p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_2_2_8") }}</p>
+              </li>
+            </ul>
+
+            <p v-html="t('tutorial.step_2_3')"></p>
+
+            <div>
+              <img
+                src="@/assets/img/setup-courses/tutorial/素材-14.png"
+                alt=""
+              />
+            </div>
+          </div>
+        </swiper-slide>
+
+        <swiper-slide>
+          <div class="step-item">
+            <h3>{{ t("tutorial.step_3_title") }}</h3>
+            <p v-html="t('tutorial.step_3_1')"></p>
+            <strong>{{ t("tutorial.step_3_2") }}</strong>
+
+            <ul class="list">
+              <li>
+                <p>{{ t("tutorial.step_3_2_1") }}<span class="mark">*</span></p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_3_2_2") }}<span class="mark">*</span></p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_3_2_3") }}</p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_3_2_4") }}<span class="mark">*</span></p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_3_2_5") }}<span class="mark">*</span></p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_3_2_6") }}<span class="mark">*</span></p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_3_2_7") }}<span class="mark">*</span></p>
+              </li>
+              <li>
+                <p>{{ t("tutorial.step_3_2_8") }}<span class="mark">*</span></p>
+              </li>
+            </ul>
+
+            <!-- <p>沒建立完成可以建立草稿、建立後仍可以編輯</p> -->
+
+            <div>
+              <img
+                src="@/assets/img/setup-courses/tutorial/素材-15.png"
+                alt=""
+              />
+            </div>
+          </div>
+        </swiper-slide>
+
+        <swiper-slide>
+          <div class="main-item">
+            <img src="@/assets/img/setup-courses/tutorial/素材-15.png" alt="" />
+            <section class="d-flex flex-column ps-sm-10">
+              <h3 v-html="t('tutorial.step_4_title')"></h3>
+              <p
+                class="my-5 my-sm-10 text-center"
+                v-html="t('tutorial.step_4_1')"
+              ></p>
+              <p class="text-center" v-html="t('tutorial.step_4_2')"></p>
+            </section>
+          </div>
+        </swiper-slide>
+
+        <!-- 左右箭頭 -->
+        <div class="swiper-button-prev" @click.stop="prevEl(item, index)" />
+        <div class="swiper-button-next" @click.stop="nextEl" />
+      </swiper>
+    </div>
+
+    <div class="create-link">
+      <router-link to="/setup-courses/create">
+        {{ t("tutorial.create") }}
+      </router-link>
+    </div>
+  </v-container>
+</template>
+
+<style lang="scss" scoped>
+.v-container {
+  position: relative;
+  max-width: 100% !important;
+}
+
+.create-link {
+  position: absolute;
+  right: 3%;
+  bottom: 5%;
+  z-index: 100;
+  font-size: 20px;
+  letter-spacing: 2px;
+  color: #636363;
+  transition: all 0.3s;
+  &:hover {
+    opacity: 0.7;
+  }
+  @media (max-width: 600px) {
+    font-size: 16px;
+  }
+}
+
+h3,
+h4,
+p {
+  color: var(--main-color);
+}
+
+h3 {
+  font-weight: 500;
+  font-size: 30px;
+  line-height: 40px;
+  letter-spacing: 2px;
+
+  @media (max-width: 600px) {
+    font-size: 24px;
+    line-height: 36px;
+  }
+}
+
+.video-background {
+  position: relative;
+  width: 100%;
+  height: 100vh;
+  overflow: hidden;
+
+  @media (max-width: 960px) {
+    padding: 0 20px;
+  }
+
+  @media (max-width: 490px) {
+    padding: 0 10px;
+  }
+
+  video {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+  }
+
+  .main-item {
+    display: flex;
+    align-items: center;
+
+    @media (max-width: 600px) {
+      flex-direction: column;
+    }
+
+    img {
+      max-width: 400px;
+
+      @media (max-width: 960px) {
+        max-width: 30vw;
+      }
+
+      @media (max-width: 600px) {
+        max-width: 50vw;
+        margin-bottom: 20px;
+      }
+
+      @media (max-width: 490px) {
+        max-width: 40vw;
+      }
+    }
+
+    p {
+      max-width: 600px;
+      font-size: 20px;
+      line-height: 36px;
+      text-align: start;
+      font-weight: 500;
+      letter-spacing: 1px;
+
+      @media (max-width: 600px) {
+        font-size: 18px;
+        text-align: center;
+      }
+
+      @media (max-width: 490px) {
+        font-size: 16px;
+        line-height: 28px;
+      }
+    }
+  }
+
+  .step-item {
+    h3 {
+      margin-bottom: 60px;
+      @media (max-width: 600px) {
+        margin-bottom: 30px;
+      }
+
+      @media (max-width: 490px) {
+        margin-bottom: 10px;
+      }
+    }
+
+    p,
+    li,
+    strong {
+      font-size: 20px;
+      line-height: 26px;
+      letter-spacing: 2px;
+
+      @media (max-width: 960px) {
+        font-size: 18px;
+      }
+      @media (max-width: 490px) {
+        font-size: 16px;
+        line-height: 28px;
+      }
+    }
+
+    strong {
+      display: block;
+      margin-top: 15px;
+      color: #779faa;
+      font-weight: 400;
+    }
+
+    ul {
+      max-width: 700px;
+      margin: 40px auto 80px;
+      display: flex;
+      flex-wrap: wrap;
+      justify-content: center;
+
+      @media (max-width: 760px) {
+        margin: 40px auto;
+      }
+
+      @media (max-width: 490px) {
+        margin: 20px auto;
+      }
+
+      li {
+        margin: 10px 5px;
+
+        @media (max-width: 960px) {
+          margin: 10px;
+        }
+
+        p {
+          padding: 10px 15px;
+          color: #fff;
+          border-radius: 5px;
+          background-color: var(--blue);
+          font-size: 18px;
+
+          @media (max-width: 960px) {
+            font-size: 16px;
+          }
+          @media (max-width: 490px) {
+            font-size: 14px;
+          }
+        }
+      }
+    }
+
+    .icon {
+      img {
+        @media (max-width: 490px) {
+          max-width: 90px;
+        }
+      }
+    }
+
+    .list {
+      max-width: 800px;
+      // li {
+      //   width: 180px;
+
+      //   @media (max-width: 960px) {
+      //     width: 155px;
+      //   }
+
+      //   @media (max-width: 490px) {
+      //     width: 145px;
+      //   }
+      // }
+    }
+
+    img {
+      max-width: 180px;
+      height: 100px;
+      left: 5%;
+      bottom: 10%;
+      position: absolute;
+      z-index: 1000;
+      overflow: unset;
+      @media (max-width: 760px) {
+        max-width: 15vw;
+        bottom: 5vw;
+      }
+
+      @media (max-width: 490px) {
+        max-width: 70px;
+        bottom: 5px;
+      }
+    }
+  }
+
+  .swiper-button-next,
+  .swiper-button-prev {
+    left: 50%;
+    color: #fff;
+    text-shadow: 1px 1px 5px #9b9b9b;
+    transform: rotate(90deg);
+  }
+
+  .swiper-button-next {
+    height: 90%;
+  }
+
+  .swiper-button-prev {
+    height: 20%;
+    top: 0;
+  }
+
+  .swiper-button-prev.swiper-button-disabled,
+  .swiper-button-next.swiper-button-disabled {
+    opacity: 0;
+  }
+}
+
+// Swiper
+.background-image {
+  background-image: url("@/assets/img/default.png");
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  background-size: cover;
+  z-index: -1;
+}
+
+.swiper {
+  width: 100%;
+  height: 100%;
+}
+
+.swiper-slide {
+  text-align: center;
+  font-size: 18px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  img {
+    display: block;
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+  }
+}
+</style>

+ 138 - 0
src/components/CraftsArticle.vue

@@ -0,0 +1,138 @@
+<script setup>
+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();
+// const articleList = reactive([
+//   {
+//     title: "crafts.article.title_1",
+//     depiction: "CMA, artisans of the new economy_Magazin",
+//     img: store.getImageUrl("crafts/知識文章-14.png"),
+//     url: "https://www.artisanat.fr/magazine/actus",
+//   },
+//   {
+//     title: "crafts.article.title_2",
+//     depiction: "8 Skills of French Craftsmen",
+//     img: store.getImageUrl("crafts/知識文章-15.png"),
+//     url: "https://www.france.fr/zh-Hant/%E6%97%85%E9%81%8A%E7%9B%AE%E7%9A%84%E5%9C%B0/%E6%B8%85%E5%96%AE/ateliers-savoir-faire-francais-%E6%B3%95%E5%9C%8B%E8%83%BD%E5%B7%A5%E5%B7%A7%E5%8C%A0%E7%9A%84-8-%E9%A0%85%E6%8A%80%E8%97%9D",
+//   },
+//   {
+//     title: "crafts.article.title_3",
+//     depiction: "Approaching Lyon's silk",
+//     img: store.getImageUrl("crafts/知識文章-16.png"),
+//     url: "https://www.france.fr/zh-Hant/%E9%87%8C%E6%98%82/%E6%B8%85%E5%96%AE/experiences-soie-%E8%B5%B0%E8%BF%91%E9%87%8C%E6%98%82%E7%9A%84%E7%B5%B2%E7%B6%A2",
+//   },
+//   {
+//     title: "crafts.article.title_4",
+//     depiction: "Artisanat verrier vénitien",
+//     img: store.getImageUrl("crafts/知識文章-17.png"),
+//     url: "https://www.sightseeinginitaly.com/fr/le-nord-est-de-litalie/region-venetie/artisanat-de-la-venetie/verre-de-murano-technique-verriere-artisanale/",
+//   },
+//   {
+//     title: "crafts.article.title_5",
+//     depiction: "小樽ガラスの特徴|歴史ある有名工芸品の魅力を探る",
+//     img: store.getImageUrl("crafts/知識文章-18.png"),
+//     url: "https://chiikihyaku-jp.translate.goog/goods/734.html?_x_tr_sl=ja&_x_tr_tl=zh-TW&_x_tr_hl=zh-TW&_x_tr_pto=sc",
+//   },
+//   {
+//     title: "crafts.article.title_6",
+//     depiction: "GALLERY JAPAN",
+//     img: store.getImageUrl("crafts/知識文章-19.png"),
+//     url: "https://www-galleryjapan-com.translate.goog/locale/ja_JP/technique/urushiwork/?_x_tr_sl=ja&_x_tr_tl=zh-TW&_x_tr_hl=zh-TW&_x_tr_pto=sc",
+//   },
+//   {
+//     title: "crafts.article.title_7",
+//     depiction: "Puppetry",
+//     img: store.getImageUrl("crafts/知識文章-20.png"),
+//     url: "https://www.visitczechrepublic.com/en-US/ff6094fa-b99b-4bb5-9643-2f4d2d375710/place/c-unesco-puppetry",
+//   },
+//   {
+//     title: "crafts.article.title_8",
+//     depiction: "Southwestern Arts and Crafts",
+//     img: store.getImageUrl("crafts/知識文章-21.png"),
+//     url: "https://www.desertusa.com/desert-people/indian-craft.html",
+//   },
+// ]);
+
+let loading = ref(false);
+let data = reactive({
+  list: [],
+});
+
+// 國際專欄
+(async () => {
+  loading.value = true;
+  try {
+    const response = await axios.get(
+      "https://cmm.ai:8088/api/get_article?group_sort=國際專欄"
+    );
+    data.list = response.data.articles;
+    loading.value = false;
+    console.log("國際專欄", data.list);
+  } catch (error) {
+    console.error(error);
+  }
+})();
+</script>
+
+<template>
+  <div v-if="loading" class="d-flex justify-center my-10">
+    <v-progress-circular
+      color="grey-lighten-4"
+      indeterminate
+    ></v-progress-circular>
+  </div>
+
+  <v-row v-else class="list">
+    <v-col cols="12" sm="6" v-for="(item, index) in data.list" :key="index">
+      <a :href="item.url" target="_blank">
+        <!-- <img :src="`https://ntcri.org/${item.cover_img}`" alt="" /> -->
+        <div class="overflow-hidden">
+          <v-img
+            class="mx-auto cover-img"
+            :lazy-src="`https://ntcri.org/${item.cover_img}`"
+            cover
+            :src="`https://ntcri.org/${item.cover_img}`"
+          >
+            <template v-slot:placeholder>
+              <div class="d-flex align-center justify-center fill-height">
+                <v-progress-circular
+                  color="grey-lighten-4"
+                  indeterminate
+                ></v-progress-circular>
+              </div>
+            </template>
+          </v-img>
+        </div>
+      </a>
+      <section>
+        <p>{{ item.depiction }}</p>
+        <h3 v-html="item.title"></h3>
+      </section>
+    </v-col>
+  </v-row>
+</template>
+
+<style lang="scss" scoped>
+.cover-img {
+  transition: all 0.5s;
+
+  &:hover {
+    transform: translate(0px, -10px);
+  }
+}
+.list {
+  p {
+    margin-bottom: 10px;
+  }
+  h3,
+  p {
+    font-weight: 500;
+    text-align: center;
+    line-height: 30px;
+  }
+}
+</style>

+ 107 - 0
src/components/HomeList.vue

@@ -0,0 +1,107 @@
+<script setup>
+import { useMainStore } from "@/stores/store";
+import moment from "moment";
+
+const store = useMainStore();
+const props = defineProps({
+  type: {
+    type: String,
+  },
+  data: {
+    type: Object,
+  },
+});
+</script>
+
+<template>
+  <section class="d-flex">
+    <p class="category mb-5">
+      <span></span>
+      {{ data.category }}
+    </p>
+    <p class="ms-5">
+      {{ moment(`${data.create_time}`).format("YYYY-MM-DD") }}
+    </p>
+  </section>
+  <v-card
+    variant="outlined"
+    class="d-flex flex-md-row flex-column align-center pa-5"
+  >
+    <v-row class="align-center">
+      <!-- <v-col cols="12" sm="4">
+        <router-link :to="`/news/${data.news_id}`" class="cover-img">
+          <img src="@/assets/img/img-04.jpg" alt="" />
+        </router-link>
+      </v-col> -->
+      <v-col cols="12">
+        <router-link :to="`/news/${data.news_id}`" class="cover-img">
+          <section class="d-flex flex-column pa-3">
+            <h3>{{ data.title }}</h3>
+            <p v-html="data.content"></p>
+          </section>
+        </router-link>
+      </v-col>
+    </v-row>
+  </v-card>
+</template>
+
+<style lang="scss" scoped>
+.v-card {
+  border-color: var(--gray);
+  border-radius: 20px;
+  background-image: url("@/assets/img/home/news-bg.png");
+  h3 {
+    margin-bottom: 20px;
+    padding-bottom: 20px;
+    font-size: 22px !important;
+    font-weight: 500;
+    line-height: 32px;
+    letter-spacing: 1px;
+    border-bottom: 1px solid #ccc;
+  }
+
+  h3,
+  p {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    line-break: after-white-space;
+    transition: all 0.3s;
+
+    &:hover {
+      opacity: 0.7;
+    }
+  }
+
+  h3 {
+    -webkit-line-clamp: 2;
+  }
+
+  p {
+    -webkit-line-clamp: 3;
+    line-height: 28px;
+    letter-spacing: 1px;
+  }
+
+  .cover-img {
+    height: 235px;
+    border-radius: 5px;
+  }
+}
+
+.category {
+  display: flex;
+  align-items: center;
+  color: #000;
+
+  span {
+    display: block;
+    width: 8px;
+    height: 8px;
+    margin-right: 10px;
+    border-radius: 10px;
+    background-color: #000;
+  }
+}
+</style>

+ 47 - 23
src/components/Navbar.vue

@@ -1,11 +1,13 @@
 <script setup>
-import { ref, reactive } from "vue";
+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";
 
 const router = useRouter();
 const store = useMainStore();
+const { t, locale } = useI18n();
 let menuShow = ref(false);
 let collegeMenuShow = ref(false);
 let otherMenuShow = ref(false);
@@ -48,46 +50,63 @@ const collegeList = reactive([
     title: "技藝工藝",
     url: "/college-group/craft",
   },
-  {
-    title: "修護工藝",
-    url: "/college-group/repair",
-  },
   {
     title: "跨域增能",
     url: "/college-group/cross",
   },
   {
     title: "線上工藝",
-    url: "/college-group/craft",
+    url: "/college-group/online",
   },
   {
     title: "希望工程",
-    url: "/college-group/craft",
+    url: "/college-group/craft-for-all",
   },
   {
     title: "生活工藝",
     url: "/college-group/life",
   },
   // {
+  //   title: "修護工藝",
+  //   url: "/college-group/repair",
+  // },
+  // {
   //   title: "世代工藝",
   //   url: "/college-group/generation",
   // },
-  {
-    title: "青年工藝",
-    url: "/college-group/teenager",
-  },
+  // {
+  //   title: "青年工藝",
+  //   url: "/college-group/teenager",
+  // },
 ]);
 
 const otherList = reactive([
-  {
-    title: "我要開課",
-    url: "/setup-courses",
-  },
-  {
-    title: "網站架構",
-    url: "/",
-  },
+  // {
+  //   title: "我要開課",
+  //   url: "/setup-courses",
+  // },
+  // {
+  //   title: "網站架構",
+  //   url: "/",
+  // },
 ]);
+
+onMounted(()=>{
+  console.log('onMounted');
+  localStorage.setItem("lang", "zh");
+})
+
+function changeLang() {
+  let lang =  localStorage.getItem("lang");
+  console.log('lang',lang);
+  if (lang === "zh") {
+    locale.value = "en";
+    localStorage.setItem("lang", "en");
+  } else if (lang === "en") {
+    locale.value = "zh";
+    localStorage.setItem("lang", "zh");
+  }
+}
 </script>
 
 <template>
@@ -151,13 +170,18 @@ const otherList = reactive([
           v-if="!store.loginState"
         >
           <template v-slot:activator="{ props }">
-            <a href="javascript:;" v-bind="props">員登入</a>
+            <a href="javascript:;" v-bind="props">員登入</a>
           </template>
           <Login @close="handleClose" />
         </v-dialog>
-        <router-link :to="'/user/profile'" v-else>學員專區</router-link>
+        <router-link :to="'/user/profile'" v-else>會員專區</router-link>
+      </li>
+      <li>
+        <router-link :to="'/setup-courses'">我要開課</router-link>
+      </li>
+      <li>
+        <a href="javascript:;" @click="changeLang()">中/EN</a>
       </li>
-      <!-- <li>EN</li> -->
       <li class="d-none d-lg-block">
         <v-icon icon="mdi-magnify"></v-icon>
       </li>
@@ -260,7 +284,7 @@ const otherList = reactive([
     }
 
     & > li {
-      margin-left: 28px;
+      margin-left: 27px;
       font-weight: 400;
 
       @media (max-width: 1280px) {

+ 6 - 4
src/components/PDFViewer.vue

@@ -1,5 +1,5 @@
 <script setup>
-import { ref, onMounted, watch } from "vue";
+import { ref, watch } from "vue";
 import { useMainStore } from "@/stores/store";
 
 const store = useMainStore();
@@ -19,9 +19,11 @@ watch(
   }
 );
 
-function setPDFUrl(name) {
-  console.log("setPDFUrl name", name);
-  let url = `https://ntcri.org/pdf/${name}.pdf`;
+function setPDFUrl(pdf) {
+  // let url = `https://ntcri.org/pdf/${name}.pdf`;
+  let json = pdf.replace(/'/g, '"');
+  let file = JSON.parse(json);
+  let url = file.file1;
 
   // 更新 iframe 的 src
   if (pdfIframe.value) {

+ 109 - 0
src/components/TermsList.vue

@@ -0,0 +1,109 @@
+<script setup>
+import { useI18n } from "vue-i18n";
+
+const { t } = useI18n();
+</script>
+
+<template>
+  <ul>
+    <li>
+      <h4>{{ t("terms.list.title_1") }}</h4>
+      <p>{{ t("terms.list.content_1") }}</p>
+    </li>
+    <li>
+      <h4>{{ t("terms.list.title_2") }}</h4>
+      <ul>
+        <li>{{ t("terms.list.content_2_1") }}</li>
+        <li>{{ t("terms.list.content_2_2") }}</li>
+        <li>{{ t("terms.list.content_2_3") }}</li>
+      </ul>
+    </li>
+    <li>
+      <h4>{{ t("terms.list.title_3") }}</h4>
+      <p>{{ t("terms.list.content_3") }}</p>
+      <ul>
+        <li>{{ t("terms.list.content_3_1") }}</li>
+        <li>{{ t("terms.list.content_3_2") }}</li>
+        <li>{{ t("terms.list.content_3_3") }}</li>
+        <li>{{ t("terms.list.content_3_4") }}</li>
+        <li>{{ t("terms.list.content_3_5") }}</li>
+        <li>{{ t("terms.list.content_3_6") }}</li>
+        <li>{{ t("terms.list.content_3_7") }}</li>
+        <li>{{ t("terms.list.content_3_8") }}</li>
+        <li>{{ t("terms.list.content_3_9") }}</li>
+        <li>{{ t("terms.list.content_3_10") }}</li>
+        <li>{{ t("terms.list.content_3_11") }}</li>
+        <li>{{ t("terms.list.content_3_12") }}</li>
+        <li>{{ t("terms.list.content_3_13") }}</li>
+        <li>{{ t("terms.list.content_3_14") }}</li>
+      </ul>
+    </li>
+    <li>
+      <h4>{{ t("terms.list.title_4") }}</h4>
+      <ul>
+        <li>{{ t("terms.list.content_4_1") }}</li>
+        <li>{{ t("terms.list.content_4_2") }}</li>
+      </ul>
+    </li>
+    <li>
+      <h4>{{ t("terms.list.title_5") }}</h4>
+      <ul>
+        <li>{{ t("terms.list.content_5_1") }}</li>
+        <li>{{ t("terms.list.content_5_2") }}</li>
+        <ul>
+          <li>{{ t("terms.list.content_5_2_1") }}</li>
+          <li>{{ t("terms.list.content_5_2_2") }}</li>
+          <li>{{ t("terms.list.content_5_2_3") }}</li>
+          <li>
+            {{ t("terms.list.content_5_2_4") }}
+            <ul>
+              <li>{{ t("terms.list.content_5_2_4_1") }}</li>
+              <li>{{ t("terms.list.content_5_2_4_2") }}</li>
+            </ul>
+          </li>
+          <li>
+            <h4>{{ t("terms.list.title_6") }}</h4>
+            <ul>
+              <li>{{ t("terms.list.content_6_1") }}</li>
+              <li>{{ t("terms.list.content_6_2") }}</li>
+              <li>{{ t("terms.list.content_6_3") }}</li>
+            </ul>
+          </li>
+          <li>
+            <h4>{{ t("terms.list.title_7") }}</h4>
+            <ul>
+              <li>{{ t("terms.list.content_7_1") }}</li>
+              <li>{{ t("terms.list.content_7_2") }}</li>
+              <li>{{ t("terms.list.content_7_3") }}</li>
+              <li>{{ t("terms.list.content_7_4") }}</li>
+            </ul>
+          </li>
+          <li>
+            <h4>{{ t("terms.list.title_8") }}</h4>
+            <ul>
+              <li>{{ t("terms.list.content_8_1") }}</li>
+              <li>{{ t("terms.list.content_8_2") }}</li>
+            </ul>
+          </li>
+        </ul>
+      </ul>
+    </li>
+  </ul>
+</template>
+
+<style lang="scss" scoped>
+p,
+li {
+  text-indent: 2em; // 縮排
+}
+h4 {
+  margin: 20px 0;
+  font-size: 18px;
+  font-weight: 500;
+}
+li {
+  margin: 10px 0;
+  line-height: 30px;
+  letter-spacing: 1px;
+}
+</style>

+ 149 - 0
src/language/en.json

@@ -0,0 +1,149 @@
+{
+  "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_6": "Life Craft",
+  "home": {
+    "title_1": "NTCRI",
+    "title_2": "Craft Groups",
+    "title_3": "Taiwan Craft Map",
+    "title_4": "Craft Course Recommendations",
+    "title_5": "開課教學",
+    "title_6": "Course FAQ",
+    "content": "With the goal of laying out a craft learning and sharing platform with an international perspective, through the main concept of “craft school”, we promote the global learning platform of International Craft Learning Platform co-ops, and use a lifelong craft platform run on the values of sharing, friendliness, and holisticness to design and integrate craft resources such as talents, courses, knowledge and teaching materials, and provide different types of learning experiences in local ways, both online and offline.",
+    "faq": {
+      "q_1": "Why should I open courses on this platform?",
+      "a_1_1": "Passing down skills and memories",
+      "a_1_2": "Do you want to let more people learn about lost handicrafts? Do you want to share the sweet memories of making handicrafts during your childhood? By opening courses on this platform, you can realize your wishes in a short time!",
+      "a_2_1": "Extra income",
+      "a_2_2": "Perhaps being a craft instructor is your side job. The NTCRI Platform allows you to quickly manage courses and groups, and NTCRI has no platform fee. You can open courses whenever and wherever convenient, allowing you to earn extra income from your interest in crafts!",
+      "a_3_1": "Realizing dreams",
+      "a_3_2": "Do you want to be a craft instructor and build your own personal brand? You can place your own craft creations on the platform and we can help you promote it! You can then focus on the things that you are interested in.",
+      "a_4_1": "View count booster: Increasing attraction",
+      "a_4_2": "NTCRI’s Course Platform and ChoozMo’s smart AI keyword recommendations unite in force to attract learners to register for the courses!",
+      "q_2": "What kinds of courses can be added to the NTCRI School Platform?",
+      "a_2": "All uploaded course content must be within the range of handicrafts. Course prices must be reasonable, and craftspersons must ensure that all uploaded content do not violate others’ intellectual property rights or trade secrets. <br> Course locations must be at a safe and accessible place. If the course location has to be changed, all students must be informed by mail or phone regarding the change.",
+      "q_3": "Are there any fees for using the NTCRI School Platform?",
+      "a_3": "NTCRI does not collect any fees for opening courses on the Platform, and will NOT collect any fees from craftspersons or students. Craftspersons are responsible for communicating all course details and fees to students.",
+      "q_4": "How to upload a course?",
+      "a_4": "To upload a course, craftspersons can log in to the Platform, and go to the “Open A Course” field to enter course details as instructed by the webpage. After the registration period has ended for a course, craftspersons can use their account to see the list of students and communicate the course details with students.",
+      "q_5": "How can I know that a course has been successfully added?",
+      "a_5": "After filling in and submitting the course details as instructed, the system will review the course information and send an email to inform craftspersons whether or not the course is added."
+    }
+  },
+  "crafts": {
+    "title": "Crafts",
+    "title_1": "International Topics",
+    "title_2": "Read Online",
+    "title_3": "Craft Books",
+    "title_4": "Craft Academic Journals",
+    "title_5": "Masters’ Thesis Funding",
+    "article": {
+      "title_1": "Chambres de Métiers et de l’Artisanat Magazine",
+      "title_2": "Craftspersons from different regions of France and Local Craft Works",
+      "title_3": "Evolution of Lyon’s silk craft",
+      "title_4": "Venice glass craft",
+      "title_5": "Glass craft in Otaru, Hokkaido",
+      "title_6": "Introduction to Japanese lacquer craft",
+      "title_7": "Czech wooden dolls",
+      "title_8": "An introduction to American Indian traditional crafts"
+    },
+    "see_more": "Read more craft knowledge"
+  },
+  "terms": {
+    "terms_of_service": "TERMS OF SERVICE",
+    "note": "NOTE: The following Terms of Service is a translation of the Terms of Service into English from the original version in Chinese. Should there be any discrepancies in translation, the Chinese version shall prevail.",
+    "list": {
+      "title_1": "I. Purpose",
+      "content_1": "International Craft Learning Platform co-ops. (“Website”, “Platform”) is a website for integrating craft-related information such as courses, cross-domain courses, experience events, strategies, and exhibitions run by National Taiwan Craft Research Institute (“Institute”). The Institute provides the above mentioned services (“Service”) in accordance with the following Terms of Service. By using this Service, you are assumed to have read, understood, and agreed to the Terms of Service. The Institute has the right to amend the Terms of Service wherever the Institute sees fit. By continuing the use of the Service after the amendment of the Terms of Service, you are assumed to have read, understood, and agreed to the changes of the Terms of Service. If you do not agree to the Terms of Service, or if the country or region you are situated in excludes all or part of the content of the Terms of Service, you are required to immediately cease the use of the Service.",
+      "title_2": "II. Account, Login, and Use of Service",
+      "content_2_1": "1. User is allowed to use a third-party platform account (e.g. Facebook, Google, or any third-party platform authorized by Website) to log in to this Website. The Institute will save any required identifying information from the third-party platform for the purpose of identifying the User.",
+      "content_2_2": "2. If you are a course organizer or instructor and would like to use the Website to promote courses or events, you are required to sign a “International Craft Learning Platform co-ops. Contract” before uploading any courses.",
+      "content_2_3": "3. Course organizers or instructors and students are required to conduct the course or event as described or agreed on the course information page or course registration. If there is any failure to conduct as described or agreed, upon verification the Institute has the right to terminate the provision of Service. If there are any changes to a course or event, the course instructor is required to contact the students at once, and retain related evidence in order to prevent disputes.",
+      "title_3": "III. User Obligations",
+      "content_3": "User agrees and ensures that the Service may not be used to harm others’ rights or perform illegal activities, which include, but not limited to the following:",
+      "content_3_1": "1. Uploading, publishing, or announcing any content that is untrue, fraudulent, defaming, harassing, threatening, offensive, indecent, obscene, or in violation of public order or morals",
+      "content_3_2": "2. Violating the reputation, privacy, business confidentiality, trademarks, copyrights, patents or any intellectual rights or other rights of others",
+      "content_3_3": "3. Breaching confidentiality agreements as described in laws or agreements",
+      "content_3_4": "4. Using the Service using others’ identity or anonymously",
+      "content_3_5": "5. Uploading, publishing, or transmitting computer viruses or any program that can disrupt, damage, or limit the operation of computer software or hardware",
+      "content_3_6": "6. Uploading, publishing, or transmitting advertisements, promotions, or sales",
+      "content_3_7": "7. Performing illegal transactions or publishing false information which attract persons to perform a crime",
+      "content_3_8": "8. Providing of prohibited items such as weapons, drugs, narcotics, or pirated software",
+      "content_3_9": "9. Providing of gambling information or attracting persons to participate in gambling",
+      "content_3_10": "10. Harming minors in any way",
+      "content_3_11": "11. Falsification of information or interference with the identification of the source of the transmission",
+      "content_3_12": "12. Interfering with or interrupting the service or servers or networks connected to the service, or failure to comply with the relevant requirements, procedures, policies or rules connected to the service",
+      "content_3_13": "13. Tracking others or otherwise interfering with others or collecting or storing personal information of others for the aforementioned purposes",
+      "content_3_14": "14. Any acts deemed inappropriate at the Institute’s discretion",
+      "title_4": "IV. License",
+      "content_4_1": "1. User agrees that by publishing courses or events on the Website (including feedback and suggestions related to said courses or events), User authorizes the Institute to make these information available for viewing by others or use these information as promotional or advertising material on websites or mobile applications run by the Institute for purposes of Website or course promotion. User must ensure that the Institute has been given authorization to use the above information beforehand. Should the User not have full authorization rights, User is not allowed to upload or publish such information to the Website.",
+      "content_4_2": "2. For other information not related to courses or events, such as other events held by the Website or the User’s public interaction or communication with the Website, User authorizes the Website to use this information for Website operational purposes.",
+      "title_5": "V. Privacy Policy",
+      "content_5_1": "1. Scope of Privacy Policy: The Following Privacy Policy applies to the collection, usage, and protection of any personal information involved in the User’s interaction with the Website; however, it does not apply to functionalities linked between this Website and the websites of government agencies or other websites. User acknowledges and understands that any website that links to this Website has its own privacy policy and is independently responsible for executing the policy. This Website, or any of its personnel, administrators, organizers or any other users are not liable for the protection of your personal information on other websites.",
+      "content_5_2": "2. Collection and Use of Personal Information",
+      "content_5_2_1": "1. When using this Service to publish related course or event information, to ensure the User is able to obtain information for contact and registration purposes, course organizers or instructors agree to publicize your information such as name, telephone number, and email. When User uses this Service to register for a course or event, all personal information provided  to the Website will only be provided to the course organizers or instructors for course-related and contact affairs.",
+      "content_5_2_2": "2. To ensure that the course or event is conducted smoothly, the Service will retain all related registration information provided by the User at the Platform until six (6) months after the course or event has started. After the above period has passed, Service will still retain the above information for a period of time according to system design; however; User has the right to request deletion of registration information from the Platform by sending an email at any time. After registration information has been deleted, User is no longer able to view registration information from their own account. NOTE: To prevent misuse of User’s personal information by course organizers or instructors, deletion can be requested after a certain period of time.",
+      "content_5_2_3": "3. Course organizers or instructors agree that course or event registration information obtained from this Website can only be used for purposes related to the course or event. After the course or event has ended, course organizers or instructors are prohibited from using the above information for any other purposes without prior consent from those involved. Course organizers or instructors agree to abide by data protection laws and are responsible for protecting said personal information.",
+      "content_5_2_4": "4. Unless required by law or consented by those involved, the Institute will NEVER sell, exchange, or rent any of the User’s personal information to other organizations, persons, or businesses, with the following exceptions:",
+      "content_5_2_4_1": "a. cooperation with legal investigations by judicial units",
+      "content_5_2_4_2": "b. based on the good faith belief that the disclosure is required by law, or used for management to maintain and improve website services.",
+      "title_6": "VI. Limitation of Responsibility",
+      "content_6_1": "1. This Website is a platform for integrating craft-related information and cross-domain courses and events, and provides course organizers or instructors a way to publish course information. Course organizers or instructors are responsible for student registration affairs. The Website does not guarantee the legality or quality of courses published by course organizers or instructors, nor is it responsible for the success of the course, as the Website is purely for integrating and providing course information. The Website is not responsible for fee collection or management of courses, and does not bear any legal liability for the execution of the course.",
+      "content_6_2": "2. All Services are provided by the Institute “AS IS”. The Institute does not guarantee that its Services satisfy the needs of the User and does not guarantee that its Services are free from defects, including but not limited to service interruptions, service delays, safety issues, viruses, or errors.",
+      "content_6_3": "3. User is responsible for backing up all transmitted or published content including text, images, and other information. The Institute is not responsible for any data loss or damage caused by any reason.",
+      "title_7": "VII. Termination",
+      "content_7_1": "1. If the Service is terminated with reasons attributable to the Institute, the Institute will issue a notice on the Website three (3) months before termination of Service and notify User via email to the email address specified by User.",
+      "content_7_2": "2. If User’s use of the service causes any damage to the Institute or any personnel of the Institute by a third party, the Institute has the right to request compensation of damages, which include but not limited to litigation costs and lawyer fees.",
+      "content_7_3": "3. Unless otherwise agreed, if User is found to be in violation of the Terms of Service or any additional terms and conditions for this Service, the Institute has the right to temporarily or permanently suspend User from using the Service or part of the Service without notice.",
+      "content_7_4": "4. Termination of this Service does not affect the effectiveness of any acts performed by User during the use of this Service. User is still required to conduct the course or event as described or agreed on the course information page or course registration. After termination of Service, all information on the Website will be retained for three (3) months and will be deleted after this period. If User has to back up any information, User has to perform it during the availability period as described above.",
+      "title_8": "VIII. Others",
+      "content_8_1": "1. Any additional terms and conditions for this Service shall constitute a part of this Terms of Service.",
+      "content_8_2": "2. Any dispute arising from the Terms of Service will be handled in accordance with the laws of the Republic of China (Taiwan)."
+    }
+  },
+  "tutorial": {
+    "title": "How To Open A Couse",
+    "create": "Create a Course",
+    "step": "Course Publishing Tutorial <br> Every craft expert has a shining seed of craft <br> Plant this seed of craft in NTCRI <br> Hoping for the day when it flowers all over <br> Become a craft expert by following the following instructions",
+    "step_1_title": "Step 1: Add a location",
+    "step_1_1": "Finding the right place to plant the seed of craft is crucial.",
+    "step_1_2": "Please provide the following information:",
+    "step_1_2_1": "Name",
+    "step_1_2_2": "Address",
+    "step_1_2_3": "Telephone",
+    "step_1_2_4": "Emial",
+    "step_1_2_5": "Photo",
+    "step_1_2_6": "Description",
+    "step_1_3": "Your location will be added to the Taiwan Craft Map. <br> Nearby users can then quickly find your course.",
+    "step_2_title": "Step 2: Craftsperson Teaching Resume",
+    "step_2_1": "Every craftsperson has their own resume. <br> Your resume will be shown below your course, so that students can get to know you!",
+    "step_2_2": "Please provide the following information:",
+    "step_2_2_1": "Instructor name",
+    "step_2_2_2": "Full-time/Part-time",
+    "step_2_2_3": "Teaching experience",
+    "step_2_2_4": "Craft skills",
+    "step_2_2_5": "Introduction",
+    "step_2_2_6": "Photo",
+    "step_2_2_7": "Certifications",
+    "step_2_2_8": "Social Media",
+    "step_2_3": "Your introduction will be placed after the course introduction. <br> Students can trust their instructor more if the information provided is more complete.",
+    "step_3_title": "Step 3: Create a Course",
+    "step_3_1": "A complete and concise course is the key to connecting craft instructors and students! <br> Many themed courses held at different times can be created at the same location.",
+    "step_3_2": "Please provide the following information:",
+    "step_3_2_1": "Course name",
+    "step_3_2_2": "Price",
+    "step_3_2_3": "Tags/Hashtags",
+    "step_3_2_4": "Class capacity",
+    "step_3_2_5": "Course location",
+    "step_3_2_6": "Photo",
+    "step_3_2_7": "Course time",
+    "step_3_2_8": " Suitable audience",
+    "step_4_title": "When you receive an email, <br> you have successfully created a course!!",
+    "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"
+}

+ 149 - 0
src/language/zh.json

@@ -0,0 +1,149 @@
+{
+  "college_group_1": "線上工藝",
+  "college_group_2": "技藝工藝",
+  "college_group_3": "未來工藝",
+  "college_group_4": "跨域增能",
+  "college_group_5": "希望工程",
+  "college_group_6": "生活工藝",
+  "home": {
+    "title_1": "臺灣工藝學校",
+    "title_2": "工藝學群",
+    "title_3": "全台工藝地圖",
+    "title_4": "工藝課程推薦",
+    "title_5": "開課教學",
+    "title_6": "工藝學校開課 FAQ",
+    "content": "以佈局具國際視野之工藝學習共享平台為目標,藉由「工藝學校」的主體概念,推動臺灣工藝學校全球學習平台,以共享、友善、全人、全民的終身工藝手作平台進行人才、課程、知識、教材之工藝資源嫁接媒合與內容設計,以在地、就近、線上、線下等多元方式提供不同型態之學習體驗內容及選擇。",
+    "faq": {
+      "q_1": "為何要在平台架設課程?",
+      "a_1_1": "傳承技藝-記憶",
+      "a_1_2": "也許您會想讓更多學徒學會這項快失傳的手工藝,也許您想把童年動手做那份快樂記憶傳承給學徒,不管是哪一種,只要在平台開課,都能在最短時間實踐您的願望!",
+      "a_2_1": "賺取額外收入",
+      "a_2_2": "也許工藝老師是您的副業或兼職,工藝中心平台讓您不用多花時間經營客群、也不需要支付平台費用,您可以在方便的時間與地點開課,讓工藝這件事變成興趣同時也能帶來額外收入!",
+      "a_3_1": "實現理想",
+      "a_3_2": "夢想成為工藝老師、打造個人品牌嗎?透過把精緻的工藝作品放在平台上,不用自己耗費精力宣傳,有我們幫您!您可以把更多時間投資在自己喜歡的事情上",
+      "a_4_1": "流量加速器提升關注度",
+      "a_4_2": "藉由工藝中心全台課程平台再加上 ChoozMo 的 AI 智能關鍵字推播,雙管齊下,成功吸引全台學徒報名!",
+      "q_2": "什麼樣的課程可以在「工藝學校平台」上架?",
+      "a_2": "工藝家所上傳之課程內容應符合手作工藝之範疇,所訂定之價格應屬合理之範圍,並須確保上傳內容未侵害第三人之智慧財產權或營業秘密等相關法律問題。 <br> 開課地點必須在安全、且便於到達的地方,如果有更換地點的臨時需求,必須以信件或電話公告給報名學員得知。",
+      "q_3": "使用「工藝學校平台」開課需要付費嗎?",
+      "a_3": "在工藝學校平台開設手作工藝課程是完全不需要付費,平台不會向工藝家及報名學員收取任何費用。上課細節及收費方式,也完全由工藝家與學員自行聯繫溝通。",
+      "q_4": "工藝家該如何上傳課程?",
+      "a_4": "工藝家只要登入平台,就可以在「我要開課」的欄位處,按照網站的指示步驟輸入內容資訊,就可以輕鬆上傳自己的課程。未來課程報名截止後,也只要用開課時所登錄的帳號,即可查詢學員名單,主動聯繫學員上課細節。",
+      "q_5": "工藝家上傳課程後,要如何知道該課程是否上傳成功?",
+      "a_5": "工藝家按照步驟指示依序填寫課程資料並確認送出後,待系統完成審核後,將寄送 E-mail 通知您課程上架是否成功。"
+    }
+  },
+  "crafts": {
+    "title": "工藝學",
+    "title_1": "國際專欄",
+    "title_2": "線上閱讀",
+    "title_3": "工藝書單",
+    "title_4": "工藝學刊",
+    "title_5": "碩博士論文補助",
+    "article": {
+      "title_1": "法國工藝中心雜誌",
+      "title_2": "法國不同地區的工藝匠結合各地特色的作品",
+      "title_3": "從古至今的里昂絲綢工藝演變",
+      "title_4": "義大利威尼斯琉璃工藝",
+      "title_5": "日本北海道小樽玻璃工藝",
+      "title_6": "日本漆藝介紹",
+      "title_7": "捷克木偶工藝",
+      "title_8": "美國印地安人傳統工藝品介紹"
+    },
+    "see_more": "閱讀更多工藝知識"
+  },
+  "terms": {
+    "terms_of_service": "開課服務條款",
+    "note": "注意:以下服務條款為中文翻譯成英文的版本,若中文版與英文版有不相符之處,最終解釋以中文版為準。",
+    "list": {
+      "title_1": "一、認知與接受條款",
+      "content_1": "「一日學徒平台(簡稱本網站)」係由國立台灣工藝研究發展中心(以下稱本中心)建置之工藝相關課程、體驗活動等資訊媒合網站,並依據本服務條款提供服務(以下稱「本服務」)。當您使用本服務時,即表示您已閱讀、瞭解並同意接受本服務條款之所有內容。本中心有權依據本網站提供服務之需求,於任何時間修改或變更本服務條款之內容,建議您隨時注意該等修改或變更。您於任何修改或變更後繼續使用本服務,視為您已閱讀、瞭解並同意接受該等修改或變更。如果您不同意本服務條款的內容,或者您所屬的國家或地域排除本服務條款內容之全部或一部時,您應立即停止使用本服務。",
+      "title_2": "二、帳號登入與本服務使用",
+      "content_2_1": "(一) 使用者得透過第三方平台帳戶(如:FB、Google等,以本網站當時支援之第三方平台為準)登入使用本網站。本中心將儲存各該第三方平台所允許儲存作為識別使用者帳戶之資訊。",
+      "content_2_2": "(二) 若您為工藝家,擬透過本網站刊登工藝相關課程或體驗活動等資訊,您須另行同意「工藝家上傳課程同意規範」,始得上傳課程;若您擬透過本網站報名相關課程,您須另行同意「民眾報名須知」,始得報名課程。",
+      "content_2_3": "(三) 無論是工藝家或是報名的學員,皆應依據其刊登或報名之課程或活動資訊履約,如有未依約履行經查證屬實,本中心得終止或暫停本服務之提供。如有更改課程或活動舉辦或參加之需求,應由工藝家與報名的學員自行聯繫處理,並保留相關證據資料,以避免爭議。",
+      "title_3": "三、使用者的守法義務及承諾",
+      "content_3": "使用者同意並保證不得利用本服務從事侵害他人權益或違法之行為,包括但不限於:",
+      "content_3_1": "(一) 上載、張貼、公布或傳送任何不實、詐欺、誹謗、侮辱、具威脅性、攻擊性、不雅、猥褻、不實、違反公共秩序或善良風俗或其他不法之文字、圖片、影音或任何形式的檔案於本服務上。",
+      "content_3_2": "(二) 侵害他人名譽、隱私權、營業秘密、商標權、著作權、專利權、其他智慧財產權及其他權利。",
+      "content_3_3": "(三) 違反依法律或契約所應負之保密義務。",
+      "content_3_4": "(四) 冒用他人名義或以匿名方式使用本服務。",
+      "content_3_5": "(五) 上載、張貼、傳輸或散佈任何含有電腦病毒或任何對電腦軟、硬體產生中斷、破壞或限制功能之程式碼之資料。",
+      "content_3_6": "(六) 上載、張貼、傳輸或散布有關廣告、贊助、促銷、銷售之訊息。",
+      "content_3_7": "(七) 從事不法交易行為或張貼虛假不實、引人犯罪之訊息。",
+      "content_3_8": "(八) 提供槍枝、毒品、禁藥、盜版軟體或其他違禁物之訊息。",
+      "content_3_9": "(九) 提供賭博資訊或以任何方式引誘他人參與賭博。",
+      "content_3_10": "(十) 以任何方法傷害未成年人。",
+      "content_3_11": "(十一) 偽造訊息來源或以任何方式干擾傳輸來源之認定。",
+      "content_3_12": "(十二) 干擾或中斷本服務或伺服器或連結本服務之網路,或不遵守連結至本服務之相關需求、程序、政策或規則等。",
+      "content_3_13": "(十三) 追蹤他人或其他干擾他人或為前述目前蒐集或儲存他人之個人資訊。",
+      "content_3_14": "(十四) 本中心有正當理由認為不適當之行為。",
+      "title_4": "四、授權條款",
+      "content_4_1": "(一) 您同意在本網站刊登之課程或體驗活動等相關資訊(包含與課程有關之回饋或建議等),授權本中心得為推廣本網站或該等課程之目的,刊登於本中心經營之其他網站或行動應用軟體系統(APP),提供予他人瀏覽,並得作為廣告素材使用。您須擔保您有權授權本中心為前開利用,如您無授權之完整權利者,請勿將該等資訊上傳或刊登於本網站。",
+      "content_4_2": "(二) 非屬於課程或體驗活動等其他資訊,例如:本網站另外舉辦之其他活動或您與本網站間各種公開的互動、往來資訊,您同意授權本中心得為經營本網站之目的使用。",
+      "title_5": "五、隱私權政策",
+      "content_5_1": "(一) 隱私權政策適用範圍:以下的隱私權政策,適用於您在本網站活動時,所涉及的個人資料蒐集、運用與保護,但不適用於與本網站功能連結之各政府機關網站或其他網站。您認知並了解凡經由本網站連結之網 站,各網站均有其專屬之隱私權政策,並由各該網站之經營者獨立向您負責,本網站或它的成員、管理者、組織者或其他用戶對您於該等網站上之個資事宜,不會與該等網站負任何連帶責任。",
+      "content_5_2": "(二) 個人資料之蒐集及運用",
+      "content_5_2_1": "1. 利用本服務刊登相關課程或活動資訊時,為確保使用者得透過本網站所提供的資訊聯絡報名事宜,開課單位/老師同意將其姓名、電話、電子郵件等正確資料對外公開;使用者透過本服務報名相關課程或活動時,依報名表單所填寫之個人資料,僅作為本網站提供服務及各單位/老師提供課程相關服務及各項聯絡使用。",
+      "content_5_2_2": "2. 為確保課程或活動之順利履行,本服務將於平台上保留使用者報名相關資料至各該課程或活動開始後6個月。前開期間屆滿後,本服務將依系統設計繼續保留課程報名資料一定期間,惟使用者隨時可透過電子郵件通知本網站將其特定課程之報名資料自平台刪除。刪除後您將無法自您的帳戶查詢您的報名資料。【註:為避免開課單位/老師透過本服務濫用個人資料,建議一定期間後可以刪除。】",
+      "content_5_2_3": "3. 開課單位/老師同意透過本服務所取得使用者報名課程或活動之個人資料,僅得作為課程或活動相關目的之使用,課程或活動結束後,除經當事人同意外,不得另行利用該等資料對使用者為行銷或其他不當利用;開課單位/老師同意遵守個人資料保護法之規範,善盡保護個人資料之責任。",
+      "content_5_2_4": "4. 本中心除依法或取得當事人同意者外,絕不會任意提供出售、交換、或出租任何您的個人資料給其他團體、個人或私人企業。但有下列情形者除外:",
+      "content_5_2_4_1": "(1) 配合司法單位合法的調查。",
+      "content_5_2_4_2": "(2) 基於善意相信揭露為法律需要,或為維護和改進網站服務而用於管理。",
+      "title_6": "六、責任限制",
+      "content_6_1": "(一) 本網站為工藝相關及跨域課程或體驗活動之資訊媒合平台,提供開課單位/老師刊登課程資訊,並由開課單位/老師自行處理學員報名事宜。本網站不對開課單位/老師開設該等課程之合法性或其課程服務之品質為任何保證或承諾,並不擔保該 等課程之順利舉辦,僅單純提供課程資訊之媒合,未對課程為任何收費或管理,不對課程之履約負任何法律上之責任。",
+      "content_6_2": "(二) 本中心係以本服務之「現狀」提供服務,不保證所提供之服務完全符合使用者的需要,亦不擔保本服務無瑕疵,包括但不限於本服務穩定不中斷、準時、安全、不具病毒或沒有錯誤等。",
+      "content_6_3": "(三) 使用者所傳輸或張貼於本服務之文字、圖片及其它資料,應自行備份;本中心對於任何原因導致前述資料全部或一部之滅失、毀損,不負任何責任。",
+      "title_7": "七、服務終止及違約終止",
+      "content_7_1": "(一) 如本中心因故須終止提供本服務,本中心將於終止前三個月於本網站公告,並將以電子郵件寄送至您註冊或登入時所留存之電子郵件信箱。",
+      "content_7_2": "(二) 若因您使用本服務之任何行為,導致本中心或本中心相關人員遭第三人主張或請求時,本中心有權向您請求因此所生之損害賠償,包括但不限於訴訟費用及律師費等。",
+      "content_7_3": "(三) 除另有約定者外,若您有任何違法或違反本服務條款或本服務其他約定時,本中心得不經通知暫時或永久終止您使用本服務全部或部分之權利。",
+      "content_7_4": "(四) 本服務之終止(無論本中心或使用者所為之終止),不影響使用者於本服務使用期間所進行相關行為之效力,使用者仍須依其原刊登或報名之課程履行。終止後本網站將保留資料3個月,3個月後將刪除該等資 料,若使用者有備份相關資料之需求,應自行於前開期間內處理。",
+      "title_8": "八、其他",
+      "content_8_1": "(一) 本服務其他規範,亦構成本服務條款之一部分。",
+      "content_8_2": "(二) 因本服務所生之爭議,依中華民國法律解釋適用之。"
+    }
+  },
+  "tutorial": {
+    "title": "開課教學",
+    "create": "開始創建課程",
+    "step": "每個工藝師傅心中都有一顆閃閃發亮的工藝種子 <br> 在工藝中心平台種下一顆工藝種子 <br> 期待有天它能在全台各地開花 <br> 只要依循著以下種子說明書三點即可輕鬆成為工藝師傅",
+    "step_1_title": "Step1 新增據點",
+    "step_1_1": "工藝老師幫助自己的工藝種子找到適合栽種的地區是一件很重要的事",
+    "step_1_2": "此處會需要填寫:",
+    "step_1_2_1": "名稱",
+    "step_1_2_2": "地址",
+    "step_1_2_3": "電話",
+    "step_1_2_4": "Emial",
+    "step_1_2_5": "照片",
+    "step_1_2_6": "簡介",
+    "step_1_3": "您的據點將會收納進「全台工藝地圖」中 <br> 讓在附近的使用者,可以快速地找到您的課程",
+    "step_2_title": "Step2 工藝家教學履歷",
+    "step_2_1": "每位工藝家的專屬履歷 <br> 會顯示於您的課程下方,讓學員更認識您!",
+    "step_2_2": "此處會需要填寫:",
+    "step_2_2_1": "老師姓名",
+    "step_2_2_2": "全職/兼職",
+    "step_2_2_3": "教學經驗",
+    "step_2_2_4": "工藝技能",
+    "step_2_2_5": "自我介紹",
+    "step_2_2_6": "上傳照片",
+    "step_2_2_7": "相關證照",
+    "step_2_2_8": "社群媒體",
+    "step_2_3": "您的介紹將會顯示於課程介紹後方 <br> 填寫越完整、越能讓學員信任老師",
+    "step_3_title": "Step3 創建課程",
+    "step_3_1": "完整且清楚的課程建立,是連接工藝老師與學徒的重要橋樑! <br> 在相同的據點底下,可以創建很多不同主題、不同時段的課程",
+    "step_3_2": "此處會需要填寫:",
+    "step_3_2_1": "課程名稱",
+    "step_3_2_2": "價格",
+    "step_3_2_3": "標籤 Hastag",
+    "step_3_2_4": "學徒名額",
+    "step_3_2_5": "上課地點",
+    "step_3_2_6": "上傳照片",
+    "step_3_2_7": "課程時間",
+    "step_3_2_8": "適合報名對象",
+    "step_4_title": "當您收到 Email 就完成開課啦!!!",
+    "step_4_1": "等待後台人員審核資料後 <br> 就會寄送開課完成通知 Email 給您",
+    "step_4_2": "工藝種子已經成長茁壯 <br> 是不是很簡單 <br> 隨時前往【我的開課】就可以看到課程資訊、報名狀況 <br> 也可以編輯文字內容"
+  },
+  "see_more": "點我看更多課程"
+}

+ 2 - 1
src/main.js

@@ -2,6 +2,7 @@ import { createApp, markRaw } from 'vue'
 import '@/assets/css/style.css'
 import App from './App.vue'
 import router from "./router"
+import i18n from './plugins/i18n'
 import vuetify from './plugins/vuetify'
 import { createPinia } from 'pinia'
 
@@ -20,6 +21,6 @@ document.head.appendChild(script);
 
 // 加載完成後再掛載 Vue
 script.onload = () => {
-    createApp(App).use(router).use(vuetify).use(pinia).mount('#app')
+    createApp(App).use(router).use(i18n).use(vuetify).use(pinia).mount('#app')
 };
 

+ 16 - 0
src/plugins/i18n.js

@@ -0,0 +1,16 @@
+import { createI18n } from 'vue-i18n';
+import zh from "../language/zh.json";
+import en from "../language/en.json";
+
+const i18n = createI18n({
+  legacy: false,
+  locale: localStorage.getItem("lang") ?? "zh",
+  fallbackLocale: "zh",
+  globalInjection: true,
+  messages: {
+    "zh": zh,
+    "en": en,
+  }
+})
+
+export default i18n

+ 77 - 32
src/router/index.js

@@ -11,17 +11,24 @@ const Article = defineAsyncComponent(() => import('@/views/Article.vue'));
 const ArticleDetail = defineAsyncComponent(() => import('@/views/ArticleDetail.vue'));
 const CourseList = defineAsyncComponent(() => import('@/views/CourseList.vue'));
 const CourseDetail = defineAsyncComponent(() => import('@/views/CourseDetail.vue'));
-const SetUpCourses = defineAsyncComponent(() => import('@/views/SetUpCourses.vue'));
+
+// 開課流程
+const SetUpCourses = defineAsyncComponent(() => import('@/views/Courses/SetUp.vue'));
+const CreateCourses = defineAsyncComponent(() => import('@/views/Courses/Create.vue'));
+const TutorialCourses = defineAsyncComponent(() => import('@/views/Courses/Tutorial.vue'));
 
 // 八大學群
 const CollegeGroup = defineAsyncComponent(() => import('@/views/CollegeGroup/Main.vue'));
+const Online = defineAsyncComponent(() => import('@/views/CollegeGroup/Online.vue'));
 const Craft = defineAsyncComponent(() => import('@/views/CollegeGroup/Craft.vue'));
-const Generation = defineAsyncComponent(() => import('@/views/CollegeGroup/Generation.vue'));
 const Future = defineAsyncComponent(() => import('@/views/CollegeGroup/Future.vue'));
-const Repair = defineAsyncComponent(() => import('@/views/CollegeGroup/Repair.vue'));
-const Teenager = defineAsyncComponent(() => import('@/views/CollegeGroup/Teenager.vue'));
 const Cross = defineAsyncComponent(() => import('@/views/CollegeGroup/Cross.vue'));
 const Life = defineAsyncComponent(() => import('@/views/CollegeGroup/Life.vue'));
+const Cfa = defineAsyncComponent(() => import('@/views/CollegeGroup/Cfa.vue'));
+// const Repair = defineAsyncComponent(() => import('@/views/CollegeGroup/Repair.vue'));
+// const Teenager = defineAsyncComponent(() => import('@/views/CollegeGroup/Teenager.vue'));
+// const Generation = defineAsyncComponent(() => import('@/views/CollegeGroup/Generation.vue'));
+
 // 一日學徒
 const Apprentice = defineAsyncComponent(() => import('@/views/CollegeGroup/Life/Apprentice/Main.vue'));
 const ApprenticeAbout = defineAsyncComponent(() => import('@/views/CollegeGroup/Life/Apprentice/About.vue'));
@@ -37,9 +44,11 @@ const Shop = defineAsyncComponent(() => import('@/views/CollegeGroup/Life/Shop.v
 
 // 學員頁面
 const Dashboard = defineAsyncComponent(() => import('@/views/User/Dashboard.vue'));
-const Profile = defineAsyncComponent(() => import('@/views/User/Profile.vue'));
-const Passport = defineAsyncComponent(() => import('@/views/User/Passport.vue'));
-const FavoriteClass = defineAsyncComponent(() => import('@/views/User/FavoriteClass.vue'));
+const Profile = defineAsyncComponent(() => import('@/views/User/Profile.vue')); // 個人檔案
+const Passport = defineAsyncComponent(() => import('@/views/User/Passport.vue')); // 學習護照
+const Courses = defineAsyncComponent(() => import('@/views/User/Courses.vue')); // 我的開課
+const FavoriteClass = defineAsyncComponent(() => import('@/views/User/FavoriteClass.vue')); // 我的收藏
+const Setting = defineAsyncComponent(() => import('@/views/User/Setting.vue')); // 帳戶設定
 
 const routes = [
   {
@@ -72,11 +81,21 @@ const routes = [
         name: 'Passport',
         component: Passport,
       },
+      {
+        path: 'courses',
+        name: 'Courses',
+        component: Courses,
+      },
       {
         path: 'favorite-class',
         name: 'FavoriteClass',
         component: FavoriteClass,
       },
+      {
+        path: 'setting',
+        name: 'Setting',
+        component: Setting,
+      },
     ],
   },
   {
@@ -97,14 +116,6 @@ const routes = [
           title: '技藝工藝',
         },
       },
-      {
-        path: 'generation',
-        name: 'Generation',
-        component: Generation,
-        meta: {
-          title: '世代工藝',
-        },
-      },
       {
         path: 'future',
         name: 'Future',
@@ -113,22 +124,6 @@ const routes = [
           title: '未來工藝',
         },
       },
-      {
-        path: 'repair',
-        name: 'Repair',
-        component: Repair,
-        meta: {
-          title: '修護工藝',
-        },
-      },
-      {
-        path: 'teenager',
-        name: 'Teenager',
-        component: Teenager,
-        meta: {
-          title: '青年工藝',
-        },
-      },
       {
         path: 'cross',
         name: 'Cross',
@@ -145,6 +140,46 @@ const routes = [
           title: '生活工藝',
         },
       },
+      {
+        path: 'online',
+        name: 'Online',
+        component: Online,
+        meta: {
+          title: '線上工藝',
+        },
+      },
+      {
+        path: 'craft-for-all',
+        name: 'Cfa',
+        component: Cfa,
+        meta: {
+          title: '希望工程',
+        },
+      },
+      // {
+      //   path: 'generation',
+      //   name: 'Generation',
+      //   component: Generation,
+      //   meta: {
+      //     title: '世代工藝',
+      //   },
+      // },
+      // {
+      //   path: 'teenager',
+      //   name: 'Teenager',
+      //   component: Teenager,
+      //   meta: {
+      //     title: '青年工藝',
+      //   },
+      // },
+      // {
+      //   path: 'repair',
+      //   name: 'Repair',
+      //   component: Repair,
+      //   meta: {
+      //     title: '修護工藝',
+      //   },
+      // },
       {
         path: 'life/apprentice',
         name: 'Apprentice',
@@ -228,7 +263,7 @@ const routes = [
     component: Article,
   },
   {
-    path: '/article-detail/:category/:id',
+    path: '/article-detail/:id',
     name: 'ArticleDetail',
     component: ArticleDetail,
   },
@@ -247,6 +282,16 @@ const routes = [
     name: 'SetUpCourses',
     component: SetUpCourses,
   },
+  {
+    path: '/setup-courses/create',
+    name: 'CreateCourses',
+    component: CreateCourses,
+  },
+  {
+    path: '/setup-courses/tutorial',
+    name: 'TutorialCourses',
+    component: TutorialCourses,
+  },
   // {
   //   path: '/college-group/craft',
   //   name: 'Craft',

+ 40 - 0
src/utils/useCraftsPdf.js

@@ -0,0 +1,40 @@
+const craftsPdfList = [
+  {
+    title: "技術導向創新模式之研究—以木材雷雕工藝為例",
+    en_title: "A study of Technology-driven Innovative Model-Taking Laser Engraving Craft as an Example",
+    author: "陳殿禮、歐陽宏柏、莊喻",
+    en_author: "Chen tien-li , Ou Yang Hung Po , Chuang Yu",
+    keywords: "木質材料、雷雕技術、創新模式、技術導向",
+    en_keywords: "Wood material, laser engraving craft, innovative model, Technologydriven",
+    fileName: "TechOriented"
+  },
+  {
+    title: "材料體驗設計方法介紹及其在臺灣工藝中的推展報告",
+    en_title: "An introduction to the materials experience design method and its promotion report in Taiwan crafts",
+    author: "姚仁寬",
+    en_author: "Yao Ren-kuan",
+    keywords: "工藝、材料體驗、材料趨動設計方法(MDD)、在地材料、工藝材質自造實驗室(CAMEL)",
+    en_keywords: "Craft, Materials Experience, Material Driven Design method(MDD), local material, Craft Application and Materials Experience Lab.(CAMEL)",
+    fileName: "MaterialExploration"
+  },
+  {
+    title: "結合瓷土與活性碳複合材料之研究與產品開發",
+    en_title: "Study of the Porcelain with Active Carbon Composites and Products Development",
+    author: "沈俊良",
+    en_author: "Jyung-Lyang Shen",
+    keywords: "瓷土、活性碳、遠紅外線放射率、XRD 礦物相鑑定、ICP 溶出試驗",
+    en_keywords: "Porcelain clay, activated carbon, emissivity, XRD mineral identification, ICP dissolution test.",
+    fileName: "ClayIntegration"
+  },
+  {
+    title: "工不可沒.藝不可失:探討工藝產業的變通之道",
+    en_title: "Research on the Model of Crafts Industry based on the Philosophy of “Tao-Qi-Pien-Tong”",
+    author: "吳杰的、林榮泰、孫怡康、江怡瑩",
+    en_author: "Jiede Wu , Rungtai Lin , Yikang Sun , I-Ying Chiang",
+    keywords: "工藝產業、文化創意、商業模式、道器變通",
+    en_keywords: "Crafts Industry, Culture Creative, Business Model, Tao-Qi-Pien-Tong",
+    fileName: "EssentialCraft"
+  }
+];
+
+export default craftsPdfList

+ 1 - 1
src/views/Article.vue

@@ -338,7 +338,7 @@ function isAnchorLink(url) {
             :key="index"
             class="position-relative mt-10 mb-16 pb-16"
           >
-            <a :href="$router.resolve(`/article-detail/book/${index}`).href">
+            <a :href="$router.resolve(`/article-detail/${index}`).href">
               <div class="overflow-hidden">
                 <v-img
                   class="mx-auto cover-img"

+ 24 - 35
src/views/ArticleDetail.vue

@@ -4,7 +4,6 @@ import { useRoute, useRouter } from "vue-router";
 import { useMainStore } from "@/stores/store";
 import axios from "axios";
 import moment from "moment";
-import bookList from "@/utils/useBookList";
 import Navbar from "@/components/Navbar.vue";
 
 const route = useRoute();
@@ -13,38 +12,34 @@ const store = useMainStore();
 
 // 網址參數
 const articleId = route.params.id;
-const articleCategory = route.params.category;
+// const articleCategory = route.params.category;
+// console.log("articleCategory", articleCategory);
 
-console.log("articleId", articleId);
-console.log("articleCategory", articleCategory);
+// if (articleCategory === "book") {
+//   getArticle();
+// } else if (articleCategory === "article") {
+//   getArticle();
+// }
 
-let list = reactive({
-  data: {},
-});
-
-if (articleCategory === "book") {
-  list.data = bookList[articleId];
-} else if (articleCategory === "article") {
-  getArticle();
-}
+// let filePath = ref("");
 
-let filePath = ref("");
 let file = reactive({
   list: [],
 });
 
+let list = reactive({
+  data: {},
+});
+
 async function getArticle() {
   try {
     const response = await axios.get(
       `https://cmm.ai:8088/api/get_article?article_id=${articleId}`
     );
-
     console.log("response", response.data.articles[0]);
     list.data = response.data.articles[0];
-
     const fileData = response.data.articles[0].files;
-    // 字符串轉物件
-    const fileObject = JSON.parse(fileData.replace(/'/g, '"'));
+    const fileObject = JSON.parse(fileData.replace(/'/g, '"')); // 字符串轉物件
 
     // 遍歷物件後將值存進陣列
     file.list = Object.keys(fileObject).map((key) => ({
@@ -58,6 +53,8 @@ async function getArticle() {
     console.error(error);
   }
 }
+
+getArticle();
 </script>
 
 <template>
@@ -71,29 +68,17 @@ async function getArticle() {
             <v-col cols="12">
               <div class="d-flex align-center mb-5">
                 <v-chip size="large" variant="elevated" class="px-8 me-3">
-                  {{
-                    articleCategory === "book"
-                      ? list.data.category
-                      : list.data.group_sort
-                  }}
+                  {{ list.data.group_sort }}
                 </v-chip>
-                <!-- <p>
+                <p>
                   {{ moment(`${list.data.create_time}`).format("YYYY.MM.DD") }}
-                </p> -->
+                </p>
               </div>
               <h2 class="text-center">{{ list.data.title }}</h2>
               <v-img
                 class="cover-img my-10"
-                :lazy-src="
-                  articleCategory === 'book'
-                    ? list.data.img
-                    : `https://ntcri.org/${list.data.cover_img}`
-                "
-                :src="
-                  articleCategory === 'book'
-                    ? list.data.img
-                    : `https://ntcri.org/${list.data.cover_img}`
-                "
+                :lazy-src="`https://ntcri.org/${list.data.cover_img}`"
+                :src="`https://ntcri.org/${list.data.cover_img}`"
               >
                 <template v-slot:placeholder>
                   <div class="d-flex align-center justify-center fill-height">
@@ -149,6 +134,10 @@ async function getArticle() {
 </template>
 
 <style lang="scss" scoped>
+p {
+  text-align: center;
+}
+
 ul {
   a {
     line-height: 24px;

+ 317 - 0
src/views/CollegeGroup/Cfa.vue

@@ -0,0 +1,317 @@
+<script setup>
+import { ref, reactive, watch } from "vue";
+import { useMainStore } from "@/stores/store";
+import axios from "axios";
+import moment from "moment";
+
+const store = useMainStore();
+const breadcrumbs = reactive([
+  {
+    title: "首頁",
+    disabled: false,
+    href: "/",
+  },
+  {
+    title: "工藝學群",
+    disabled: true,
+  },
+  {
+    title: "希望工程",
+    disabled: true,
+  },
+]);
+
+const faqList = [
+  {
+    q: "問:社區關懷據點(或其他弱勢族群)想導入工藝手作陪伴,應如何做?",
+    a: "答:原有執行之其他課程,經評估可部分以工藝手作取代,確認日期及時段,可洽 049-2334141 分機 237 楊小姐,媒合就地或鄰近工藝師前往教學。",
+  },
+  {
+    q: "問:職能治療師若想加入「臺灣綠工藝希望工程」工藝手作陪伴,應如何參與?",
+    a: "答:請與 049-2334141 分機 237 楊小姐聯繫,將會媒合就地或鄰近有教學需求之單位或團體。",
+  },
+  {
+    q: "問:工藝手作陪伴有什麼好處?",
+    a: "答:工藝手作活動可以達到認知上的訓練,最主要是因為工藝可提供豐富的感官刺激,透過創作的想像力、創意,以及作品自我表達能力的滿足感,在認知上的訓練就非常有效果。在運用手部動作過程中,可刺激活化腦部功能,延緩失智失能。在社交人際互動過程中,在製作工藝品中,成員間互相協助幫忙,形成良好互動,甚至是在完成作品與其他的好朋友互相分享。",
+  },
+  {
+    q: "問:想要投入弱勢族群教學的工藝師,有限定其工藝種類嗎?",
+    a: "答:無限制工藝種類,但工藝師規劃手作時以安全性高、操作不複雜為考量。",
+  },
+  {
+    q: "問:工藝師應規劃幾堂的課程較為適合?",
+    a: "答:以每週執行一次,每次 2 小時,持續 6 至 8 週為宜,若對於未曾執行過工藝手作之族群,可先安排體驗式課程,再漸進式導入,惟仍依各單位需求彈性調整。",
+  },
+  {
+    q: "問:工藝師若想加入「臺灣綠工藝希望工程」工藝手作陪伴,應如何參與?",
+    a: "答:請與 049-2334141 分機 237 楊小姐聯繫,請規劃並提供適合的課程,將會媒合就地或鄰近有教學需求之單位或團體。",
+  },
+];
+
+// 活動現場
+let activityPageNum = ref(1); // 頁數(預設第一頁)
+let activityPageAmount = ref(12); // 每頁顯示筆數
+let activityTotalPages = ref(1); // 總頁數
+
+// 媒體報導
+let mediaPageNum = ref(1); // 頁數(預設第一頁)
+let mediaPageAmount = ref(12); // 每頁顯示筆數
+let mediaTotalPages = ref(1); // 總頁數
+
+let loading = ref(false);
+const activityList = ref(null);
+const mediaList = ref(null);
+
+watch(activityPageNum, () => {
+  getClasses();
+  activityList.value.scrollIntoView({ behavior: "smooth", block: "start" });
+});
+
+watch(mediaPageNum, () => {
+  getTgcReport();
+  mediaList.value.scrollIntoView({ behavior: "smooth", block: "start" });
+});
+
+const tgcImg = reactive({
+  classes: [],
+});
+
+// 獲取活動現場資料
+async function getClasses() {
+  loading.value = true;
+  let url = `https://cmm.ai:8088/api/get_tgc_img?page_num=${activityPageNum.value}&page_amount=${activityPageAmount.value}`;
+
+  try {
+    const response = await axios.get(url);
+    tgcImg.classes = response.data.tgc_pic;
+    activityTotalPages.value = store.getTotalPages(response.data.total_num, 12);
+    console.log(response);
+  } catch (error) {
+    loading.value = false;
+    console.error(error);
+  }
+}
+
+getClasses();
+
+const tgcReport = reactive({
+  classes: [],
+});
+
+// 獲取媒體報導資料
+async function getTgcReport() {
+  loading.value = true;
+  let url = `https://cmm.ai:8088/api/get_tgc_report?page_num=${mediaPageNum.value}&page_amount=${mediaPageAmount.value}`;
+
+  try {
+    const response = await axios.get(url);
+    tgcReport.classes = response.data.tgc_report;
+    mediaTotalPages.value = store.getTotalPages(response.data.total_num, 12);
+
+    console.log("report", response);
+  } catch (error) {
+    loading.value = false;
+    console.error(error);
+  }
+}
+
+getTgcReport();
+</script>
+
+<template>
+  <v-breadcrumbs
+    :items="breadcrumbs"
+    divider="/"
+    class="mt-10 pa-0"
+  ></v-breadcrumbs>
+
+  <div class="title">
+    <h2>關於臺灣綠工藝希望工程</h2>
+  </div>
+
+  <v-row>
+    <v-col cols="12" md="6">
+      <p class="mb-5">
+        以工藝為媒介,實踐社會價值功能。國立臺灣工藝研究發展中心啟動「臺灣綠工藝希望工程」,本於自然、循環、平衡、寬容、生命力等綠工藝精神,融合人文關懷,深化工藝與社會的互動連結,為所有人帶來「希望‧療癒‧陪伴」的力量。
+      </p>
+      <p>
+        「臺灣綠工藝希望工程」是公民互助精神的體現,以「One Community, One
+        Craft(OCOC)」一社群一工藝理念,凝聚社會力量,以工藝師、職能治療師提供核心支持服務,以社區、NGO團體、相關民間組織及政府單位為平台,在社福體系中的醫療機構、長照機構與社區照顧關懷據點等,向社會上包含樂齡長者、兒童、婦女、身心障礙者等所有人,傳達工藝手作的溫暖。以工藝投入社會服務,引領身心靈向上提升,也形成一股陪伴的安定力量,促進社會關係的融合與共好。
+      </p>
+    </v-col>
+    <v-col cols="12" md="6">
+      <div class="about-img">
+        <img
+          class="img-fluid"
+          src="@/assets/img/college-group/life/生活工藝素材-24.png"
+          alt=""
+        />
+      </div>
+    </v-col>
+  </v-row>
+
+  <div class="title" ref="activityList">
+    <h2>活動現場</h2>
+  </div>
+
+  <v-row>
+    <v-col
+      sm="6"
+      md="4"
+      cols="12"
+      class="pa-5"
+      v-for="(item, index) in tgcImg.classes"
+      :key="index"
+    >
+      <div class="main-card">
+        <div class="card-info">
+          <div class="overflow-hidden">
+            <v-img
+              class="mx-auto cover-img"
+              :lazy-src="item.pic_url"
+              :src="item.pic_url"
+              height="270px"
+              cover
+            >
+              <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="card-title">
+            <h3>{{ item.pic_name }}</h3>
+          </div>
+        </div>
+      </div>
+
+      <!-- <div class="main-card">
+        <div class="card-info">
+          <img :src="item.pic_url" alt="" class="cover-img" />
+        </div>
+        <section class="card-title">
+          <h3>{{ item.pic_name }}</h3>
+        </section>
+      </div> -->
+    </v-col>
+  </v-row>
+
+  <v-pagination
+    v-model="activityPageNum"
+    :length="activityTotalPages"
+    rounded="circle"
+    class="mt-16"
+  ></v-pagination>
+
+  <div class="title" ref="mediaList">
+    <h2>媒體報導</h2>
+  </div>
+
+  <ul class="tgc-report">
+    <li
+      class="d-flex align-center justify-space-between"
+      v-for="(item, index) in tgcReport.classes"
+      :key="index"
+    >
+      <div class="info">
+        <a :href="item.url" target="_blank">
+          <p class="report-title font-weight-normal">
+            {{ item.title }}
+          </p>
+        </a>
+      </div>
+      <div class="date time-block">
+        <span> {{ moment(`${item.create_time}`).format("YYYY-MM-DD") }}</span>
+      </div>
+    </li>
+  </ul>
+  <v-pagination
+    v-model="mediaPageNum"
+    :length="mediaTotalPages"
+    rounded="circle"
+    class="mt-16"
+  ></v-pagination>
+
+  <div class="title">
+    <h2>常見問題</h2>
+  </div>
+
+  <v-expansion-panels>
+    <v-expansion-panel v-for="item in faqList" :key="item">
+      <v-expansion-panel-title>{{ item.q }}</v-expansion-panel-title>
+      <v-expansion-panel-text v-html="item.a"> </v-expansion-panel-text>
+    </v-expansion-panel>
+  </v-expansion-panels>
+</template>
+
+<style lang="scss" scoped>
+.about-img {
+  width: 80%;
+  height: auto;
+  margin: auto;
+}
+
+.card-info {
+  height: 100%;
+  padding-bottom: 0;
+  .card-title {
+    margin: auto;
+    border-bottom: none;
+    cursor: pointer;
+    h3 {
+      font-weight: 500;
+    }
+  }
+}
+
+p {
+  font-weight: 400;
+  letter-spacing: 1px;
+  line-height: 30px;
+}
+
+.tgc-report {
+  li {
+    padding: 30px 0;
+    border-bottom: 1px solid #c9c9c9;
+    .info {
+      a {
+        transition: all 0.3s;
+        &:hover {
+          opacity: 0.7;
+        }
+      }
+    }
+  }
+  .report-title {
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    @media (max-width: 960px) {
+      white-space: normal;
+    }
+  }
+}
+.time-block {
+  padding: 0 0 0 20px;
+  span {
+    width: 90px;
+    display: block;
+  }
+}
+.v-expansion-panel-title,
+.v-expansion-panel-text {
+  font-size: 16px;
+  line-height: 30px;
+  letter-spacing: 1px;
+}
+.v-expansion-panel-text {
+  padding: 15px 20px;
+  font-size: 16px;
+}
+</style>

+ 396 - 17
src/views/CollegeGroup/Craft.vue

@@ -2,7 +2,9 @@
 import { ref, reactive, watch } from "vue";
 import { useMainStore } from "@/stores/store";
 import axios from "axios";
+import moment from "moment";
 import CourseCard from "@/components/CourseCard.vue";
+import ArticleCard from "@/components/ArticleCard.vue";
 
 const store = useMainStore();
 
@@ -110,6 +112,56 @@ const breadcrumbs = reactive([
     disabled: true,
   },
 ]);
+
+// const tagList = reactive([
+//   {
+//     title: "修護課程",
+//     url: "#repairCourse",
+//   },
+//   {
+//     title: "修護故事",
+//     url: "#repairArticle",
+//   },
+//   {
+//     title: "校園扎根 <br> 教師限定課程",
+//     url: "#campusCourse",
+//   },
+//   {
+//     title: "專業工藝課程",
+//     url: "#lightCraft",
+//   },
+//   {
+//     title: "手作體驗課程",
+//     url: "#pinkoiCourse",
+//   },
+// ]);
+
+// 修護課程
+const classes = reactive({
+  list: [],
+});
+
+// 修護故事
+const articles = reactive({
+  list: [],
+});
+
+(async () => {
+  // let url = `https://cmm.ai:8088/api/get_group_classes_and_articles?group_id=6`;
+  let classUrl = "https://cmm.ai:8088/api/get_class_name?group_id=6";
+  let articlesUrl = "https://cmm.ai:8088/api/get_article?group_id=6";
+
+  try {
+    const classData = await axios.get(classUrl);
+    const articlesData = await axios.get(articlesUrl);
+    classes.list = classData.data.classes;
+    articles.list = articlesData.data.articles;
+    console.log("classes.list", classes.list);
+    console.log("articles.list", articles.list);
+  } catch (error) {
+    console.error(error);
+  }
+})();
 </script>
 
 <template>
@@ -121,8 +173,9 @@ const breadcrumbs = reactive([
 
   <div
     class="d-flex flex-column flex-sm-row align-center justify-space-between title"
+    id="lightCraft"
   >
-    <h2>校園扎根-教師限定課程</h2>
+    <h2>專業工藝課程</h2>
     <div class="search">
       <span>
         <input
@@ -145,7 +198,7 @@ const breadcrumbs = reactive([
     </div>
   </div>
 
-  <div class="d-flex justify-center" v-if="campusLoading">
+  <div class="d-flex justify-center" v-if="lightCraftLoading">
     <v-progress-circular
       color="grey-lighten-4"
       indeterminate
@@ -157,7 +210,7 @@ const breadcrumbs = reactive([
       sm="6"
       md="4"
       cols="12"
-      v-for="(item, index) in campus.classes"
+      v-for="(item, index) in lightCraft.classes"
       :key="index"
       class="pa-5"
     >
@@ -165,8 +218,8 @@ const breadcrumbs = reactive([
     </v-col>
     <v-col cols="12">
       <v-pagination
-        v-model="campusPageNum"
-        :length="campusTotalPages"
+        v-model="lightCraftPageNum"
+        :length="lightCraftTotalPages"
         rounded="circle"
         class="mt-16"
       ></v-pagination>
@@ -174,9 +227,208 @@ const breadcrumbs = reactive([
   </v-row>
 
   <div
+    class="d-flex flex-column flex-sm-row align-center justify-space-between"
+  >
+    <h2 class="title" id="repairCourse">修護課程</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>
+
+  <ul class="course-list mt-16 mt-sm-0">
+    <li v-for="(item, index) in classes.list" :key="index" class="mb-10">
+      <v-card variant="outlined" class="d-flex flex-column align-center pa-0">
+        <h2 class="text-center ma-0 pa-3">{{ item.name }}</h2>
+        <v-row class="justify-center align-center content">
+          <v-col cols="12" md="4">
+            <a
+              :href="
+                $router.resolve(`/course-detail/${item.class_name_id}`).href
+              "
+            >
+              <img :src="`https://ntcri.org/${item.cover_img}`" alt="" />
+            </a>
+          </v-col>
+          <v-col cols="12" md="8">
+            <section
+              class="d-flex flex-column justify-space-between pt-5 pt-md-0"
+            >
+              <a
+                :href="
+                  $router.resolve(`/course-detail/${item.class_name_id}`).href
+                "
+              >
+                <p>{{ item.introduction }}</p>
+              </a>
+              <div class="mt-10">
+                <!-- <span class="d-flex align-center">
+                <v-icon
+                  color="gray"
+                  icon="mdi-calendar-range"
+                  class="me-2"
+                ></v-icon>
+                <p>
+                  {{ moment(`${item.start_time}`).format("YYYY/MM/DD") }}
+                  ~ {{ moment(`${item.end_time}`).format("YYYY/MM/DD") }}
+                </p>
+              </span> -->
+                <span class="d-flex align-center mt-1">
+                  <v-icon
+                    color="gray"
+                    icon="mdi-map-marker"
+                    class="me-2"
+                  ></v-icon>
+                  <p>
+                    {{ item.address }}
+                  </p>
+                </span>
+              </div>
+            </section>
+          </v-col>
+        </v-row>
+      </v-card>
+    </li>
+  </ul>
+
+  <!-- <v-row class="mt-10 px-10 pt-16 pb-3 tag-btn">
+    <v-col v-for="(item, index) in tagList" :key="index" class="ma-2 item">
+      <a :href="item.url" class="py-3" v-html="item.title"> </a>
+    </v-col>
+  </v-row> -->
+
+  <h2 class="title" id="repairCourse">修護工藝</h2>
+
+  <v-row>
+    <v-col cols="12" md="6">
+      <h3 class="text-center mb-5">國家工藝檢測修護平臺</h3>
+      <div class="video">
+        <iframe
+          width="560"
+          height="315"
+          src="https://www.youtube.com/embed/eIOkU2q9WHA"
+          title="YouTube video player"
+          frameborder="0"
+          allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
+          allowfullscreen
+        ></iframe>
+      </div>
+    </v-col>
+    <v-col cols="12" md="6">
+      <h3 class="text-center mb-5">臺灣工藝聯合醫院-草鞋墩分院</h3>
+      <div class="video">
+        <iframe
+          width="560"
+          height="315"
+          src="https://www.youtube.com/embed/Rb318IkMo80"
+          title="YouTube video player"
+          frameborder="0"
+          allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
+          allowfullscreen
+        ></iframe>
+      </div>
+    </v-col>
+  </v-row>
+
+  <v-row class="mt-16 article-block">
+    <v-col cols="12" sm="7" md="8">
+      <h4 class="mb-5">首創工藝聯合醫院,診斷修復再現舊物新生命</h4>
+      <p>
+        工藝中心在愛物惜物、自然永續的綠工藝精神下,成立「臺灣工藝檢測修護聯盟」,串聯專業學術單位、團體或個人工作室等成為一個工藝的照護聯合網,就像一所工藝的聯合醫院,為博物館、常民工藝或是生活收藏等需求者,提供檢測、維護及修復的服務,猶如醫院的體檢、衛教保健到診斷治療,全臺分區各有據點,提供大眾就近在地的友善服務。
+        「臺灣工藝聯合醫院-草鞋墩分院」就是照護聯合網絡的一個據點,由廖偉淇、蔣昆原、張璽元、葉璨榮、陳宜妙5位修復師進駐,服務項目有陶瓷、金屬、漆及複合媒材等修復,歡迎大家隨時來問診交流、學習工藝保養維護,親自體驗舊物新生的感動。
+        <br />
+        「國家工藝檢測修護平臺」開始受理申請,有檢測、修復及維護需求者,皆可透過「國家工藝檢測修護平臺」網站提出申請,或電話撥打0800-222-800洽詢。
+      </p>
+    </v-col>
+    <v-col
+      cols="12"
+      sm="5"
+      md="4"
+      class="d-flex flex-column align-center align-md-end justify-space-around"
+    >
+      <section>
+        <h4>檢測x修復x維護</h4>
+        <h4>保存x循環x再生</h4>
+      </section>
+      <span class="btn mt-10 mt-sm-0">
+        <v-btn block size="large"
+          >點我前往 <br />
+          「國家工藝檢測修護平臺」</v-btn
+        >
+      </span>
+    </v-col>
+  </v-row>
+
+  <!-- <div class="d-flex justify-end">
+    <button class="past-btn">觀看已過期課程</button>
+  </div> -->
+
+  <h2 class="title">工藝修護師</h2>
+
+  <v-row class="master-list">
+    <v-col
+      cols="12"
+      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.png" alt="" />
+      <section class="mt-5 mt-md-0 ms-md-5">
+        <h3>廖偉淇</h3>
+        <span class="d-block mt-3 mb-5">金工木印工作室</span>
+        <ul>
+          <li>主要修護媒材</li>
+          <li>金屬:金/銀首飾、銀/銅茶具、餐具。</li>
+          <li>陶瓷:花器、餐具、茶道具、香道具。</li>
+          <li>主要修復技法:鋦瓷、漆繕、金工鍛造、脫腊鑄造。</li>
+        </ul>
+      </section>
+    </v-col>
+    <v-col
+      cols="12"
+      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.png" alt="" />
+      <section class="mt-5 mt-md-0 ms-md-5">
+        <h3>陳高登</h3>
+        <span class="d-block mt-3 mb-5">金工木印工作室</span>
+        <ul>
+          <li>主要修護媒材</li>
+          <li>陶瓷器物</li>
+        </ul>
+      </section>
+    </v-col>
+  </v-row>
+
+  <h2 class="title" id="repairArticle">修護故事</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>
+    <h2>校園扎根-教師限定課程</h2>
     <div class="search">
       <span>
         <input
@@ -199,7 +451,7 @@ const breadcrumbs = reactive([
     </div>
   </div>
 
-  <div class="d-flex justify-center" v-if="lightCraftLoading">
+  <div class="d-flex justify-center" v-if="campusLoading">
     <v-progress-circular
       color="grey-lighten-4"
       indeterminate
@@ -211,7 +463,7 @@ const breadcrumbs = reactive([
       sm="6"
       md="4"
       cols="12"
-      v-for="(item, index) in lightCraft.classes"
+      v-for="(item, index) in campus.classes"
       :key="index"
       class="pa-5"
     >
@@ -219,16 +471,17 @@ const breadcrumbs = reactive([
     </v-col>
     <v-col cols="12">
       <v-pagination
-        v-model="lightCraftPageNum"
-        :length="lightCraftTotalPages"
+        v-model="campusPageNum"
+        :length="campusTotalPages"
         rounded="circle"
         class="mt-16"
       ></v-pagination>
     </v-col>
-  </v-row>
+  </v-row> -->
 
-  <div
+  <!-- <div
     class="d-flex flex-column flex-sm-row align-center justify-space-between title"
+    id="pinkoiCourse"
   >
     <h2>手作體驗課程</h2>
     <div class="search">
@@ -279,17 +532,143 @@ const breadcrumbs = reactive([
         class="mt-16"
       ></v-pagination>
     </v-col>
-  </v-row>
+  </v-row> -->
 </template>
 
 <style lang="scss" scoped>
+h3 {
+  font-weight: 400;
+}
+
+.video {
+  position: relative;
+  padding-bottom: 56%;
+  overflow: hidden;
+  iframe {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100% !important;
+    height: 100% !important;
+  }
+}
+
+p {
+  font-weight: 400;
+  letter-spacing: 1px;
+  line-height: 28px;
+}
+
+.article-block {
+  h4 {
+    font-size: 26px;
+    font-weight: 400;
+    line-height: 40px;
+  }
+
+  .v-btn {
+    height: 70px;
+    color: #fff;
+    font-weight: 400;
+    border-radius: 100px;
+    background-color: var(--brown);
+    @media (max-width: 960px) {
+      font-size: 14px;
+    }
+    @media (max-width: 600px) {
+      font-size: 16px;
+    }
+  }
+}
+
+.course-list {
+  .v-card {
+    border: none;
+    border-radius: 20px;
+    background-color: #e9f1f4;
+    h2 {
+      width: 100%;
+      font-size: 24px;
+      line-height: 30px;
+      letter-spacing: 1px;
+      border-bottom: 3px solid #fff;
+      @media (max-width: 600px) {
+        font-size: 22px;
+      }
+    }
+    .content {
+      padding: 20px 0;
+      p:first-child {
+        font-weight: 400;
+        line-height: 28px;
+        color: #606064;
+      }
+    }
+  }
+}
+
+.course-list,
+.story-list {
+  img {
+    width: 100%;
+    max-width: 300px;
+    object-fit: cover;
+    @media (max-width: 960px) {
+      max-width: 100%;
+      height: auto;
+    }
+  }
+  h3 {
+    font-size: 24px;
+    font-weight: 500;
+    line-height: 30px;
+    letter-spacing: 1px;
+  }
+  p {
+    // 超過兩行則省略
+    overflow: hidden;
+    text-overflow: ellipsis;
+    display: -webkit-box;
+    -webkit-line-clamp: 4;
+    -webkit-box-orient: vertical;
+    line-break: after-white-space;
+  }
+
+  .date {
+    font-size: 20px;
+    font-weight: 400;
+  }
+}
+
+.master-list {
+  img {
+    width: 250px;
+    height: 250px;
+    object-fit: cover;
+  }
+  h3 {
+    font-size: 20px;
+    color: #000;
+  }
+  section {
+    text-align: center;
+    color: #606064;
+    li {
+      line-height: 28px;
+    }
+  }
+}
+
+.past-btn {
+  padding: 15px 40px;
+  color: #606064;
+  border-radius: 100px;
+  border: 2px solid #569f33;
+}
+
 .content {
   padding: 0;
   width: 90%;
-
-  @media (max-width: 600px) {
-    width: 85%;
-  }
   .main-block {
     padding: 100px 80px;
     margin-top: -30%;

+ 231 - 181
src/views/CollegeGroup/Cross.vue

@@ -1,8 +1,8 @@
 <script setup>
-import { ref, reactive } from "vue";
+import { ref, reactive, watch } from "vue";
+import { useMainStore } from "@/stores/store";
 import axios from "axios";
 import moment from "moment";
-import { useMainStore } from "@/stores/store";
 
 const store = useMainStore();
 
@@ -22,35 +22,105 @@ const breadcrumbs = reactive([
   },
 ]);
 
-const testData = [
-  {
-    title: "種子教師研習",
-    start_time: "2023/06/15",
-    end_time: "2023/06/20",
-    address: "地方工藝館 工藝教室",
-    introduction:
-      "為推動工藝融入學校教育,促進工藝美學校園扎根,透過手作體驗課程,培育學校教師成為種子教師,並回到教學現場發展工藝體驗課程,傳遞工藝所能帶來的生活連結與美好體驗,誘發學生「動手做工藝」的學習興趣,開啟工藝與教育融合共好的無限可能性! 以「綠工藝」核心理念~自然、循環、平衡、寬容、生命力,將工藝體驗課程引介進入教育系統,透過辦理工藝手作課程,培育校園工藝種子人才,發展手作體驗課程,讓工藝融入教學,傳遞工藝所能帶來的生活連結與美好體驗,達到臺灣工藝價值的社會實踐。",
-    img: store.getImageUrl("college-group/img.jpg"),
-  },
-  {
-    title: "種子教師研習",
-    start_time: "2023/06/15",
-    end_time: "2023/06/20",
-    address: "地方工藝館 工藝教室",
-    introduction:
-      "為推動工藝融入學校教育,促進工藝美學校園扎根,透過手作體驗課程,培育學校教師成為種子教師,並回到教學現場發展工藝體驗課程,傳遞工藝所能帶來的生活連結與美好體驗,誘發學生「動手做工藝」的學習興趣,開啟工藝與教育融合共好的無限可能性! 以「綠工藝」核心理念~自然、循環、平衡、寬容、生命力,將工藝體驗課程引介進入教育系統,透過辦理工藝手作課程,培育校園工藝種子人才,發展手作體驗課程,讓工藝融入教學,傳遞工藝所能帶來的生活連結與美好體驗,達到臺灣工藝價值的社會實踐。",
-    img: store.getImageUrl("college-group/img.jpg"),
-  },
-  {
-    title: "種子教師研習",
-    start_time: "2023/06/15",
-    end_time: "2023/06/20",
-    address: "地方工藝館 工藝教室",
-    introduction:
-      "為推動工藝融入學校教育,促進工藝美學校園扎根,透過手作體驗課程,培育學校教師成為種子教師,並回到教學現場發展工藝體驗課程,傳遞工藝所能帶來的生活連結與美好體驗,誘發學生「動手做工藝」的學習興趣,開啟工藝與教育融合共好的無限可能性! 以「綠工藝」核心理念~自然、循環、平衡、寬容、生命力,將工藝體驗課程引介進入教育系統,透過辦理工藝手作課程,培育校園工藝種子人才,發展手作體驗課程,讓工藝融入教學,傳遞工藝所能帶來的生活連結與美好體驗,達到臺灣工藝價值的社會實踐。",
-    img: store.getImageUrl("college-group/img.jpg"),
-  },
-];
+let loading = ref(false);
+let pageNum = ref(1); // 頁數(預設第一頁)
+let pageAmount = ref(18); // 每頁顯示筆數
+let totalPages = ref(1); // 總頁數
+let totalNum = ref(0); // 資料總筆數
+let selectCategory = ref([]); // 選擇類別
+let isFilter = ref(false); // 篩選狀態
+let filterList = reactive([]); // 篩選條件
+let isSearch = ref(false); // 搜尋狀態
+let searchInput = ref(""); // 關鍵字
+let searchError = ref(false); // 搜尋錯誤訊息
+
+watch(searchInput, (val) => {
+  if (val === "") {
+    searchError.value = false;
+    getClass();
+  }
+});
+
+const courese = reactive({
+  list: [],
+});
+
+async function getClass() {
+  loading.value = true;
+  let url = `https://cmm.ai:8088/api/get_online_courese?no_org=udemy&page_num=${pageNum.value}&page_amount=${pageAmount.value}`;
+
+  // 篩選類別
+  if (isFilter.value) {
+    filterList.map((item) => {
+      url += item;
+    });
+  }
+
+  // 關鍵字搜尋
+  if (isSearch.value) {
+    url += `&key_word=${searchInput.value}`;
+  }
+
+  try {
+    const response = await axios.get(url);
+    courese.list = response.data.online_coures;
+    console.log("response.data", response.data);
+    totalNum.value = response.data.total_num;
+    totalPages.value = store.getTotalPages(response.data.total_num, 18);
+    loading.value = false;
+
+    if (!response.data.online_coures.length) {
+      searchError.value = true;
+    } else {
+      searchError.value = false;
+    }
+  } catch (error) {
+    loading.value = false;
+    console.error(error);
+  }
+}
+
+getClass();
+
+const listLocation = ref(null);
+
+watch(pageNum, () => {
+  getClass();
+  listLocation.value.scrollIntoView({ behavior: "smooth", block: "start" });
+});
+
+watch(selectCategory, (val) => {
+  if (val.includes("全部")) {
+    selectCategory.value = [];
+    isFilter.value = false;
+    pageNum.value = 1;
+    getClass();
+  } else {
+    selectFilter("category", val);
+  }
+});
+
+// 篩選課程
+async function selectFilter(type, val) {
+  isFilter.value = true;
+  pageNum.value = 1; // 篩選時返回第一頁
+  filterList.splice(0, filterList.length); // 清空陣列
+
+  if (type === "category") {
+    filterList.push(`&category=${val}`);
+  }
+
+  getClass();
+}
+
+async function handleSearch() {
+  if (searchInput.value !== "") {
+    isSearch.value = true;
+  } else {
+    isSearch.value = false;
+  }
+  getClass();
+}
 </script>
 
 <template>
@@ -100,18 +170,19 @@ const testData = [
   </div>
 
   <div
+    ref="listLocation"
     class="d-flex flex-column flex-sm-row align-center justify-space-between title"
   >
-    <h2>智財</h2>
+    <h2>所有課程</h2>
     <div class="search">
       <span>
         <input
           v-model="searchInput"
           type="text"
-          @keyup.enter="search()"
+          @keyup.enter="handleSearch()"
           placeholder="關鍵字搜尋"
         />
-        <button @click="search()">
+        <button @click="handleSearch()">
           <img src="@/assets/img/news/news-search-icon.png" alt="" />
         </button>
       </span>
@@ -126,161 +197,102 @@ const testData = [
   </div>
 
   <v-row>
-    <v-col
-      sm="6"
-      md="4"
-      cols="12"
-      v-for="(item, index) in testData"
-      :key="index"
-      class="pa-5"
-    >
-      <div class="main-card">
-        <section class="card-title">
-          <h3>{{ item.title }}</h3>
-        </section>
-        <div class="card-info">
-          <img :src="item.img" alt="" class="cover-img" />
-          <ul>
-            <li class="d-flex align-center my-2">
-              <img src="@/assets/img/icon/date_icon.png" alt="" />
-              <p class="mb-0 ms-3">
-                {{ moment(`${item.start_time}`).format("YYYY/MM/DD") }} -
-                {{ moment(`${item.end_time}`).format("YYYY/MM/DD") }}
-              </p>
-            </li>
-            <li class="d-flex align-center">
-              <img src="@/assets/img/icon/location_icon.png" alt="" />
-              <p class="mb-0 ms-3">
-                {{ item.address }}
-              </p>
-            </li>
-          </ul>
-        </div>
-      </div>
+    <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="selectCategory"
+            label="類別"
+            :items="[
+              '全部',
+              '財務稅務',
+              '市場分析',
+              '行銷策略',
+              '公關溝通',
+              '法務知識',
+              '產品開發',
+              '品牌設計',
+              '消費者管理',
+              '策劃展覽',
+              '藝術行政',
+              '文案撰寫',
+              '教學影音',
+              '品牌企劃',
+            ]"
+            multiple
+            hide-details
+          ></v-select>
+        </v-col>
+        <!-- <v-col cols="12">
+            <v-btn
+              @click="selectFilter()"
+              class="w-100"
+              variant="flat"
+              color="brown"
+              rounded="xl"
+            >
+              搜尋
+            </v-btn>
+          </v-col>
+          <v-col cols="12">
+            <v-btn class="w-100" variant="outlined" color="brown" rounded="xl">
+              清除重填
+            </v-btn>
+          </v-col> -->
+      </v-row>
     </v-col>
-  </v-row>
+    <v-col cols="12" md="9" lg="10">
+      <v-row class="cross-courese">
+        <v-col
+          cols="12"
+          md="6"
+          lg="4"
+          v-for="(item, index) in courese.list"
+          :key="index"
+          class="pa-5"
+        >
+          <div class="main-card">
+            <a :href="item.video_url" target="_blank">
+              <section class="card-title">
+                <h3>{{ item.title }}</h3>
+              </section>
+            </a>
 
-  <div
-    class="d-flex flex-column flex-sm-row align-center justify-space-between title"
-  >
-    <h2>法律</h2>
-    <div class="search">
-      <span>
-        <input
-          v-model="searchInput"
-          type="text"
-          @keyup.enter="search()"
-          placeholder="關鍵字搜尋"
-        />
-        <button @click="search()">
-          <img src="@/assets/img/news/news-search-icon.png" alt="" />
-        </button>
-      </span>
-      <div
-        v-if="searchError"
-        class="d-flex justify-center align-center error me-4"
-      >
-        <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
-        沒有符合搜尋條件的項目
-      </div>
-    </div>
-  </div>
-
-  <v-row>
-    <v-col
-      sm="6"
-      md="4"
-      cols="12"
-      v-for="(item, index) in testData"
-      :key="index"
-      class="pa-5"
-    >
-      <div class="main-card">
-        <section class="card-title">
-          <h3>{{ item.title }}</h3>
-        </section>
-        <div class="card-info">
-          <img :src="item.img" alt="" class="cover-img" />
-          <ul>
-            <li class="d-flex align-center my-2">
-              <img src="@/assets/img/icon/date_icon.png" alt="" />
-              <p class="mb-0 ms-3">
-                {{ moment(`${item.start_time}`).format("YYYY/MM/DD") }} -
-                {{ moment(`${item.end_time}`).format("YYYY/MM/DD") }}
-              </p>
-            </li>
-            <li class="d-flex align-center">
-              <img src="@/assets/img/icon/location_icon.png" alt="" />
-              <p class="mb-0 ms-3">
-                {{ item.address }}
-              </p>
-            </li>
-          </ul>
-        </div>
-      </div>
+            <a :href="item.video_url" target="_blank">
+              <div class="card-info">
+                <ul>
+                  <li class="d-flex align-center">
+                    <p class="mb-0 ms-3">分類: {{ item.category }}</p>
+                  </li>
+                  <li class="d-flex align-center">
+                    <p class="mb-0 ms-3">來源: {{ item.content }}</p>
+                  </li>
+                </ul>
+              </div>
+            </a>
+          </div>
+        </v-col>
+      </v-row>
     </v-col>
   </v-row>
 
-  <div
-    class="d-flex flex-column flex-sm-row align-center justify-space-between title"
+  <v-pagination
+    v-if="courese.list.length"
+    v-model="pageNum"
+    :length="totalPages"
+    rounded="circle"
+    class="mt-16"
+  ></v-pagination>
+
+  <span v-if="courese.list.length" class="text-gray total-item"
+    >總筆數:{{ totalNum }}</span
   >
-    <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 class="title mt-16">
+    <h2>工藝中心開課</h2>
   </div>
 
-  <v-row>
-    <v-col
-      sm="6"
-      md="4"
-      cols="12"
-      v-for="(item, index) in testData"
-      :key="index"
-      class="pa-5"
-    >
-      <div class="main-card">
-        <section class="card-title">
-          <h3>{{ item.title }}</h3>
-        </section>
-        <div class="card-info">
-          <img :src="item.img" alt="" class="cover-img" />
-          <ul>
-            <li class="d-flex align-center my-2">
-              <img src="@/assets/img/icon/date_icon.png" alt="" />
-              <p class="mb-0 ms-3">
-                {{ moment(`${item.start_time}`).format("YYYY/MM/DD") }} -
-                {{ moment(`${item.end_time}`).format("YYYY/MM/DD") }}
-              </p>
-            </li>
-            <li class="d-flex align-center">
-              <img src="@/assets/img/icon/location_icon.png" alt="" />
-              <p class="mb-0 ms-3">
-                {{ item.address }}
-              </p>
-            </li>
-          </ul>
-        </div>
-      </div>
-    </v-col>
-  </v-row>
+  <p class="cooming-soon">「籌備中 cooming soon」</p>
 </template>
 
 <style lang="scss" scoped>
@@ -292,6 +304,44 @@ const testData = [
   }
 }
 
+.v-input {
+  @media (max-width: 600px) {
+    max-width: 250px;
+    margin: auto;
+    margin-top: -20px;
+  }
+}
+
+.cross-courese {
+  .main-card {
+    .card-title {
+      height: 40%;
+
+      h3 {
+        font-size: 18px;
+        font-weight: 500;
+      }
+    }
+
+    .card-info {
+      padding: 0;
+      height: 60%;
+
+      ul {
+        justify-content: center;
+
+        li:first-child {
+          margin-bottom: 5px;
+        }
+
+        p {
+          font-size: 14px;
+        }
+      }
+    }
+  }
+}
+
 .list {
   max-width: 850px;
   margin: 100px auto 50px;

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

@@ -119,6 +119,18 @@ const bookList = readList.slice().splice(2, 4);
     </v-row>
   </v-container>
 
+  <div class="title">
+    <h2>國際工藝村</h2>
+  </div>
+
+  <p class="cooming-soon">「籌備中 cooming soon」</p>
+
+  <div class="title">
+    <h2>青年工藝</h2>
+  </div>
+
+  <p class="cooming-soon">「籌備中 cooming soon」</p>
+
   <!-- <button class="sloped-btn">點我閱讀</button> -->
 </template>
 

+ 7 - 282
src/views/CollegeGroup/Life.vue

@@ -5,26 +5,6 @@ import { useMainStore } from "@/stores/store";
 
 const store = useMainStore();
 
-// 工藝行旅
-let travel = reactive({
-  list: [],
-});
-
-// 希望工程
-let hope = reactive({
-  list: [],
-});
-
-// 一日學徒
-let exhibit = reactive({
-  list: [],
-});
-
-// 校園扎根
-let campus = reactive({
-  list: [],
-});
-
 // 所有文章
 let articles = reactive({
   list: [],
@@ -33,32 +13,10 @@ let articles = reactive({
 (async () => {
   try {
     const response = await axios.get(
-      "https://cmm.ai:8088/api/get_group_classes_and_articles?group_id=3"
+      "https://cmm.ai:8088/api/get_article?group_id=3"
     );
     console.log("response", response);
-
-    travel.list = response.data.articles.filter(
-      (e) => e.group_sort === "工藝行旅"
-    );
-
-    hope.list = response.data.articles.filter(
-      (e) => e.group_sort === "希望工程"
-    );
-
-    exhibit.list = response.data.classes.filter(
-      (e) => e.group_sort === "一日學徒 - 展覽"
-    );
-
-    campus.list = response.data.articles.filter(
-      (e) => e.group_sort === "校園扎根"
-    );
-
-    console.log("一日學徒 - 展覽", exhibit.list);
-    console.log("希望工程", hope.list);
-    console.log("工藝行旅", travel.list);
-
     articles.list = response.data.articles;
-    console.log("articles.list", articles.list);
   } catch (error) {
     console.error(error);
   }
@@ -79,45 +37,6 @@ const breadcrumbs = reactive([
     disabled: true,
   },
 ]);
-
-const shopImgs = [
-  {
-    img: store.getImageUrl("college-group/life/shop/旅物shop-01.png"),
-  },
-  {
-    img: store.getImageUrl("college-group/life/shop/旅物shop-02.png"),
-  },
-  {
-    img: store.getImageUrl("college-group/life/shop/旅物shop-03.png"),
-  },
-  {
-    img: store.getImageUrl("college-group/life/shop/旅物shop-04.png"),
-  },
-  {
-    img: store.getImageUrl("college-group/life/shop/旅物shop-05.png"),
-  },
-  {
-    img: store.getImageUrl("college-group/life/shop/旅物shop-06.png"),
-  },
-  {
-    img: store.getImageUrl("college-group/life/shop/旅物shop-07.png"),
-  },
-  {
-    img: store.getImageUrl("college-group/life/shop/旅物shop-08.png"),
-  },
-  {
-    img: store.getImageUrl("college-group/life/shop/旅物shop-09.png"),
-  },
-  {
-    img: store.getImageUrl("college-group/life/shop/旅物shop-10.png"),
-  },
-  {
-    img: store.getImageUrl("college-group/life/shop/旅物shop-11.png"),
-  },
-  {
-    img: store.getImageUrl("college-group/life/shop/旅物shop-12.png"),
-  },
-];
 </script>
 
 <template>
@@ -194,213 +113,16 @@ const shopImgs = [
       </v-row>
     </router-link>
   </v-container>
-
-  <!-- <h2 class="title">旅物 SHOP</h2>
-
-  <div class="px-md-8">
-    <v-slide-group center-active show-arrows>
-      <v-slide-group-item v-for="item in shopImgs" :key="item">
-        <div class="ma-3">
-          <v-img :src="item.img" height="200px" cover></v-img>
-        </div>
-      </v-slide-group-item>
-    </v-slide-group>
-
-    <v-row class="justify-space-around mt-10 mb-16 px-0 px-md-16 px-lg-8">
-      <v-col cols="12" md="8">
-        <p>
-          《旅物 •
-          SHOP》位於自然生態豐富的臺灣工藝文化園區,明亮穿透的空間設計,連結窗外工藝植物園,到室內的生活工藝選物,這是一間由土地孕育而生的工藝選品店,從採擷、手作,到用物於生活,是材料與手、物與物、人與物、人與人彼此流動交會,共度美好日常的店頭。包含崇尚自然循環,創造生活風格的臺灣在地「臺灣綠工藝」品牌、扶植青年創業的「青年孵化器」、工藝中心開發的周邊商品「品工藝」以及在地咖啡品牌「一日咖啡」等四個子品牌,推廣「自然、循環、平衡、寬容、生命力」的綠工藝精神,更是鼓勵青年實驗開店、青年工藝家的孵化平臺。
-        </p>
-      </v-col>
-      <v-col cols="12" md="4" lg="3" class="mt-auto d-flex justify-end">
-        <a
-          href="https://luwushop.ntcri.gov.tw/home/zh-tw"
-          class="link-btn"
-          target="_blank"
-        >
-          點我前往 <br />
-          「旅物 SHOP」官網
-        </a>
-      </v-col>
-    </v-row>
-  </div> -->
-
-  <!-- <h2 class="title">工藝行旅</h2>
-
-  <div class="px-md-8 trave-content">
-    <v-slide-group center-active show-arrows>
-      <v-slide-group-item v-for="item in travel.list" :key="item">
-        <div class="ma-3 info">
-          <a
-            :href="
-              $router.resolve(`/article-detail/article/${item.article_id}`).href
-            "
-          >
-            <img :src="`https://ntcri.org/${item.cover_img}`" alt="" />
-          </a>
-          <section>
-            <h3>{{ item.title }}</h3>
-            <p>{{ item.depiction }}</p>
-          </section>
-        </div>
-      </v-slide-group-item>
-    </v-slide-group>
-
-    <section class="mt-3 ps-sm-12 ps-md-16 w-100">
-      <h4>工藝文化深度旅遊,品味在地、標記生活</h4>
-      <p>
-        打造以工藝為核心的文化深度旅遊路徑,結合工藝體驗、自然風土、地方美食、在地物產、
-        <br />
-        建築等觀光資源,旅人緩步慢行,暫離喧囂、以手作沉澱自我,發現生命中的寧靜與感動。
-        <br />
-        現在一起出發,感受工藝行旅的魅力和樂趣!
-      </p>
-    </section>
-  </div>
-
-  <h2 class="title">臺灣綠工藝 希望工程</h2>
-
-  <v-row class="align-center justify-center px-md-8 hope-content">
-    <v-col cols="12" sm="10" md="5" class="d-flex">
-      <img
-        src="@/assets/img/college-group/life/生活工藝素材-24.png"
-        alt=""
-        class="cover-img"
-      />
-    </v-col>
-    <v-col cols="12" sm="10" md="7">
-      <h4>
-        工藝做為一種社會處方箋 <br />
-        陪伴 x 療癒 x 認知
-      </h4>
-      <p>
-        以工藝為媒介,實踐社會價值功能。國立臺灣工藝研究發展中心啟動「臺灣綠工藝希望工程」,本於自然、循環、平衡、寬容、生命力等綠工藝精神,融合人文關懷,深化工藝與社會的互動連結,為所有人帶來「希望‧療癒‧陪伴」的力量。
-        <br />
-        <br />
-        「臺灣綠工藝希望工程」是公民互助精神的體現,以「One Community, One
-        Craft(OCOC)」一社群一工藝理念,凝聚社會力量,以工藝師、職能治療師提供核心支持服務,以社區、NGO團體、相關民間組織及政府單位為平台,在社福體系中的醫療機構、長照機構與社區照顧關懷據點等,向社會上包含樂齡長者、兒童、婦女、身心障礙者等所有人,傳達工藝手作的溫暖。以工藝投入社會服務,引領身心靈向上提升,也形成一股陪伴的安定力量,促進社會關係的融合與共好。
-      </p>
-    </v-col>
-    <v-col cols="12" class="d-flex">
-      <a
-        href="https://tgc.ntcri.gov.tw/home/zh-tw"
-        class="link-btn ms-auto"
-        target="_blank"
-      >
-        點我前往 <br />
-        「台灣綠工藝希望工程」官網
-      </a>
-    </v-col>
-    <v-col cols="12" class="mt-10">
-      <v-slide-group center-active show-arrows>
-        <v-slide-group-item v-for="item in hope.list" :key="item">
-          <div class="ma-3 info">
-            <a
-              :href="
-                $router.resolve(`/article-detail/article/${item.article_id}`)
-                  .href
-              "
-            >
-              <img :src="`https://ntcri.org/${item.cover_img}`" alt="" />
-            </a>
-            <section>
-              <h3>{{ item.title }}</h3>
-              <p>{{ item.depiction }}</p>
-            </section>
-          </div>
-        </v-slide-group-item>
-      </v-slide-group>
-    </v-col>
-  </v-row>
-
-  <h2 class="title">一日學徒</h2>
-
-  <v-row class="justify-center px-md-8">
-    <v-col cols="12" sm="10" md="11">
-      <h4>
-        一日學徒,全民工藝教育倡議平台 <br />
-        生活 x 手作 x 體驗
-      </h4>
-      <p>
-        以「手做」,經歷生活中的一切美好! <br />
-        用「一日」,體會過去師徒制三年四個月的工藝學習精神與態度! <br />
-        由工藝家親自規畫各類手作工藝課程,隨時提供最新課程資訊,民眾依據喜好選擇課程,
-        <br />
-        體驗半天或一天的手作工藝學習,尋找就近的工藝家學習各種小物知識經驗與技藝。
-      </p>
-    </v-col>
-
-    <v-col cols="12" sm="10" md="11" class="mt-16">
-      <h4 class="mb-8">展覽活動</h4>
-    </v-col>
-
-    <v-col cols="12" md="11" class="exhibit-content">
-      <v-slide-group center-active show-arrows>
-        <v-slide-group-item v-for="item in exhibit.list" :key="item">
-          <div class="ma-3 info">
-            <router-link :to="`/course-detail/${item.class_name_id}`">
-              <img :src="`https://ntcri.org/${item.cover_img}`" alt="" />
-            </router-link>
-
-            <router-link :to="`/course-detail/${item.class_name_id}`">
-              <section class="mt-4">
-                <h3>{{ item.name }}</h3>
-                <p v-html="item.introduction"></p>
-              </section>
-            </router-link>
-          </div>
-        </v-slide-group-item>
-      </v-slide-group>
-    </v-col>
-  </v-row> -->
-
-  <!-- <h2 class="title">校園扎根</h2>
-
-  <v-row class="justify-center px-md-8">
-    <v-col cols="12" sm="10" md="11">
-      <h4>
-        老師 x 工藝家 x 學生 <br />
-        講述 x 手作 <br />
-        價值 x 實踐 <br />
-        扎根 x 擴散
-      </h4>
-      <p class="mt-10">
-        工藝校園扎根計畫以工藝融入學校教育,媒合教師與工藝家,跨域合作開發具創造力、觀察力、美學素養、批判思考以及手作體驗的工藝教材教案,實踐工藝之於學習、生活的價值。符合教育部12年國教課綱的「核心素養」,以及STEAM教育的五大精神「跨領域、動手做、生活應用、解決問題及五感學習」,以「做中學,學中做」培育新世代的工藝人才。
-      </p>
-    </v-col>
-
-    <v-col cols="12" md="11" class="mt-16 campus-content">
-      <v-slide-group center-active show-arrows>
-        <v-slide-group-item v-for="item in campus.list" :key="item">
-          <div class="ma-3 info">
-            <a
-              :href="
-                $router.resolve(`/article-detail/article/${item.article_id}`)
-                  .href
-              "
-            >
-              <img :src="`https://ntcri.org/${item.cover_img}`" alt="" />
-            </a>
-            <section class="mt-4">
-              <h3>{{ item.title }}</h3>
-            </section>
-          </div>
-        </v-slide-group-item>
-      </v-slide-group>
-    </v-col>
-  </v-row> -->
 </template>
 
 <style lang="scss" scoped>
 .life-list {
-  margin-top: 150px;
-  @media (max-width: 960px) {
-    margin-top: 100px;
-  }
+  margin-top: 100px;
+
   @media (max-width: 600px) {
     margin-top: 0;
   }
+
   a {
     display: block;
     margin: 50px 0;
@@ -413,10 +135,12 @@ const shopImgs = [
       }
     }
   }
+
   h3 {
     font-size: 24px;
     font-weight: 500;
   }
+
   img {
     width: 100%;
     height: 330px;
@@ -430,6 +154,7 @@ const shopImgs = [
       height: 200px;
     }
   }
+
   .content {
     padding: 35px 60px;
     display: flex;

+ 101 - 3
src/views/CollegeGroup/Life/Apprentice/Course.vue

@@ -1,5 +1,103 @@
+<script setup>
+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 pinkoiCourse = ref(null);
+let pinkoi = reactive({
+  classes: [],
+});
+
+let loading = ref(false);
+let pageNum = ref(1); // 頁數
+let totalPages = ref(1); // 總頁數
+let pageAmount = ref(12); // 每頁顯示筆數
+
+watch(pageNum, () => {
+  getClasses();
+});
+
+// 取得清單
+async function getClasses() {
+  loading.value = true;
+  try {
+    const response = await axios.get(
+      `https://cmm.ai:8088/api/get_class_name?group_id=2&group_sort=pinkoi&page_num=${pageNum.value}&page_amount=${pageAmount.value}`
+    );
+    totalPages.value = store.getTotalPages(
+      response.data.total_num,
+      pageAmount.value
+    );
+    pinkoi.classes = response.data.classes;
+    setTimeout(() => {
+      pinkoiCourse.value.scrollIntoView({ behavior: "smooth", block: "start" });
+    }, 300);
+    loading.value = false;
+  } catch (error) {
+    loading.value = false;
+    console.error(error);
+  }
+}
+
+getClasses();
+</script>
+
 <template>
-    <div>
-        選課資訊
+  <div
+    class="d-flex flex-column flex-sm-row align-center justify-space-between pb-10"
+    ref="pinkoiCourse"
+  >
+    <h3 class="mb-0 sub-title">手作體驗課程</h3>
+    <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>
-</template>
+  </div>
+
+  <div class="d-flex justify-center" v-if="loading">
+    <v-progress-circular
+      color="grey-lighten-4"
+      indeterminate
+    ></v-progress-circular>
+  </div>
+
+  <v-row>
+    <v-col
+      sm="6"
+      md="4"
+      cols="12"
+      v-for="(item, index) in pinkoi.classes"
+      :key="index"
+      class="pa-5"
+    >
+      <CourseCard :data="item" />
+    </v-col>
+    <v-col cols="12">
+      <v-pagination
+        v-model="pageNum"
+        :length="totalPages"
+        rounded="circle"
+        class="mt-16"
+      ></v-pagination>
+    </v-col>
+  </v-row>
+</template>

+ 4 - 4
src/views/CollegeGroup/Life/Apprentice/Main.vue

@@ -28,10 +28,10 @@ const categoryList = reactive([
     title: "學徒選課資訊",
     path: "/college-group/life/apprentice/course",
   },
-  {
-    title: "最新訊息",
-    path: "/college-group/life/apprentice/news",
-  },
+  // {
+  //   title: "最新訊息",
+  //   path: "/college-group/life/apprentice/news",
+  // },
   {
     title: "聯絡我們",
     path: "/college-group/life/apprentice/contact",

+ 5 - 177
src/views/CollegeGroup/Life/Apprentice/Terms.vue

@@ -1,182 +1,10 @@
+<script setup>
+import TermsList from "@/components/TermsList.vue";
+</script>
+
 <template>
   <div>
     <h3 class="sub-title">網站服務條款</h3>
-    <ul>
-      <li>
-        <h4>一、認知與接受條款</h4>
-        <p>
-          「一日學徒平台(簡稱本網站)」係由國立台灣工藝研究發展中心(以下稱本中心)建置之工藝相關課程、體驗活動等資訊媒合網站,並依據本服務條款提供服務(以下稱「本服務」)。當您使用本服務時,即表示您已
-          閱讀、瞭解並同意接受本服務條款之所有內容。本中心有權依據本網站提供服務之需求,於任何時間修改或變更本服務條款之內容,建議您隨時注意該等修改或變更。您於任何修改或變更後繼續使用本服務,視為您
-          已閱讀、瞭解並同意接受該等修改或變更。如果您不同意本服務條款的內容,或者您所屬的國家或地域排除本服務條款內容之全部或一部時,您應立即停止使用本服務。
-        </p>
-      </li>
-      <li>
-        <h4>二、帳號登入與本服務使用</h4>
-        <ul>
-          <li>
-            (一)
-            使用者得透過第三方平台帳戶(如:FB、Google等,以本網站當時支援之第三方平台為準)登入使用本網站。本中心將儲存各該第三方平台所允許儲存作為識別使用者帳戶之資訊。
-          </li>
-          <li>
-            (二)
-            若您為工藝家,擬透過本網站刊登工藝相關課程或體驗活動等資訊,您須另行同意「工藝家上傳課程同意規範」,始得上傳課程;若您擬透過本網站報名相關課程,您須另行同意「民眾報名須知」,始得報名課程。
-          </li>
-          <li>
-            (三)
-            無論是工藝家或是報名的學員,皆應依據其刊登或報名之課程或活動資訊履約,如有未依約履行經查證屬實,本中心得終止或暫停本服務之提供。如有更改課程或活動舉辦或參加之需求,應由工藝家與報名的學
-            員自行聯繫處理,並保留相關證據資料,以避免爭議。
-          </li>
-        </ul>
-      </li>
-      <li>
-        <h4>三、使用者的守法義務及承諾</h4>
-        <p>
-          使用者同意並保證不得利用本服務從事侵害他人權益或違法之行為,包括但不限於:
-        </p>
-        <ul>
-          <li>
-            (一)
-            上載、張貼、公布或傳送任何不實、詐欺、誹謗、侮辱、具威脅性、攻擊性、不雅、猥褻、不實、違反公共秩序或善良風俗或其他不法之文字、圖片、影音或任何形式的檔案於本服務上。
-          </li>
-          <li>
-            (二)
-            侵害他人名譽、隱私權、營業秘密、商標權、著作權、專利權、其他智慧財產權及其他權利。
-          </li>
-          <li>(三) 違反依法律或契約所應負之保密義務。</li>
-          <li>(四) 冒用他人名義或以匿名方式使用本服務。</li>
-          <li>
-            (五)
-            上載、張貼、傳輸或散佈任何含有電腦病毒或任何對電腦軟、硬體產生中斷、破壞或限制功能之程式碼之資料。
-          </li>
-          <li>(六) 上載、張貼、傳輸或散布有關廣告、贊助、促銷、銷售之訊息。</li>
-          <li>(七) 從事不法交易行為或張貼虛假不實、引人犯罪之訊息。</li>
-          <li>(八) 提供槍枝、毒品、禁藥、盜版軟體或其他違禁物之訊息。</li>
-          <li>(九) 提供賭博資訊或以任何方式引誘他人參與賭博。</li>
-          <li>(十) 以任何方法傷害未成年人。</li>
-          <li>(十一) 偽造訊息來源或以任何方式干擾傳輸來源之認定。</li>
-          <li>
-            (十二)
-            干擾或中斷本服務或伺服器或連結本服務之網路,或不遵守連結至本服務之相關需求、程序、政策或規則等。
-          </li>
-          <li>
-            (十三) 追蹤他人或其他干擾他人或為前述目前蒐集或儲存他人之個人資訊。
-          </li>
-          <li>(十四) 本中心有正當理由認為不適當之行為。</li>
-        </ul>
-      </li>
-      <li>
-        <h4>四、授權條款</h4>
-        <ul>
-          <li>
-            (一)
-            您同意在本網站刊登之課程或體驗活動等相關資訊(包含與課程有關之回饋或建議等),授權本中心得為推廣本網站或該等課程之目的,刊登於本中心經營之其他網站或行動應用軟體系統(APP),提供予他人
-            瀏覽,並得作為廣告素材使用。您須擔保您有權授權本中心為前開利用,如您無授權之完整權利者,請勿將該等資訊上傳或刊登於本網站。
-          </li>
-          <li>
-            (二)
-            非屬於課程或體驗活動等其他資訊,例如:本網站另外舉辦之其他活動或您與本網站間各種公開的互動、往來資訊,您同意授權本中心得為經營本網站之目的使用。
-          </li>
-        </ul>
-      </li>
-      <li>
-        <h4>五、隱私權政策</h4>
-        <ul>
-          <li>
-            (一)
-            隱私權政策適用範圍:以下的隱私權政策,適用於您在本網站活動時,所涉及的個人資料蒐集、運用與保護,但不適用於與本網站功能連結之各政府機關網站或其他網站。您認知並了解凡經由本網站連結之網
-            站,各網站均有其專屬之隱私權政策,並由各該網站之經營者獨立向您負責,本網站或它的成員、管理者、組織者或其他用戶對您於該等網站上之個資事宜,不會與該等網站負任何連帶責任。
-          </li>
-          <li>(二) 個人資料之蒐集及運用</li>
-          <ul>
-            <li>
-              1.
-              利用本服務刊登相關課程或活動資訊時,為確保使用者得透過本網站所提供的資訊聯絡報名事宜,工藝家同意將其姓名、電話、電子郵件等正確資料對外公開;使用者透過本服務報名相關課程或活動時,依報名表單所填寫之個人資料,僅作為本網站提供服務及各該工藝家提供課程相關服務及各項聯絡使用。
-            </li>
-            <li>
-              2.
-              為確保課程或活動之順利履行,本服務將於平台上保留使用者報名相關資料至各該課程或活動開始後6個月。前開期間屆滿後,本服務將依系統設計繼續保留課程報名資料一定期間,惟使用者隨時可透過電子郵件通知本網站將其特定課程之報名資料自平台刪除。刪除後您將無法自您的帳戶查詢您的報名資料。【註:為避免工藝家透過本服務濫用個人資料,建議一定期間後可以刪除。】
-            </li>
-            <li>
-              3.
-              工藝家同意透過本服務所取得使用者報名課程或活動之個人資料,僅得作為課程或活動相關目的之使用,課程或活動結束後,除經當事人同意外,不得另行利用該等資料對使用者為行銷或其他不當利用;工藝家並同意遵守個人資料保護法之規範,善盡保護個人資料之責任。
-            </li>
-            <li>
-              4.
-              本中心除依法或取得當事人同意者外,絕不會任意提供出售、交換、或出租任何您的個人資料給其他團體、個人或私人企業。但有下列情形者除外:
-              <ul>
-                <li>(1)配合司法單位合法的調查。</li>
-                <li>
-                  (2)基於善意相信揭露為法律需要,或為維護和改進網站服務而用於管理。
-                </li>
-              </ul>
-            </li>
-            <li>
-              <h4>六、責任限制</h4>
-              <ul>
-                <li>
-                  (一)
-                  本網站為工藝相關課程或體驗活動之資訊媒合平台,提供工藝家刊登課程資訊,並由工藝家自行處理學員報名事宜。本網站不對工藝家開設該等課程之合法性或其課程服務之品質為任何保證或承諾,並不擔保該
-                  等課程之順利舉辦,僅單純提供課程資訊之媒合,未對課程為任何收費或管理,不對課程之履約負任何法律上之責任。
-                </li>
-                <li>
-                  (二)
-                  本中心係以本服務之「現狀」提供服務,不保證所提供之服務完全符合使用者的需要,亦不擔保本服務無瑕疵,包括但不限於本服務穩定不中斷、準時、安全、不具病毒或沒有錯誤等。
-                </li>
-                <li>
-                  (三)
-                  使用者所傳輸或張貼於本服務之文字、圖片及其它資料,應自行備份;本中心對於任何原因導致前述資料全部或一部之滅失、毀損,不負任何責任。
-                </li>
-              </ul>
-            </li>
-            <li>
-              <h4>七、服務終止及違約終止</h4>
-              <ul>
-                <li>
-                  (一)
-                  如本中心因故須終止提供本服務,本中心將於終止前三個月於本網站公告,並將以電子郵件寄送至您註冊或登入時所留存之電子郵件信箱。
-                </li>
-                <li>
-                  (二)
-                  若因您使用本服務之任何行為,導致本中心或本中心相關人員遭第三人主張或請求時,本中心有權向您請求因此所生之損害賠償,包括但不限於訴訟費用及律師費等。
-                </li>
-                <li>
-                  (三)
-                  除另有約定者外,若您有任何違法或違反本服務條款或本服務其他約定時,本中心得不經通知暫時或永久終止您使用本服務全部或部分之權利。
-                </li>
-                <li>
-                  (四)
-                  本服務之終止(無論本中心或使用者所為之終止),不影響使用者於本服務使用期間所進行相關行為之效力,使用者仍須依其原刊登或報名之課程履行。終止後本網站將保留資料3個月,3個月後將刪除該等資
-                  料,若使用者有備份相關資料之需求,應自行於前開期間內處理。
-                </li>
-              </ul>
-            </li>
-            <li>
-              <h4>八、其他</h4>
-              <ul>
-                <li>(一) 本服務其他規範,亦構成本服務條款之一部分。</li>
-                <li>(二) 因本服務所生之爭議,依中華民國法律解釋適用之。</li>
-              </ul>
-            </li>
-          </ul>
-        </ul>
-      </li>
-    </ul>
+    <TermsList />
   </div>
 </template>
-
-<style lang="scss" scoped>
-p,
-li {
-  text-indent: 2em; // 縮排
-}
-h4 {
-  margin: 50px 0 20px;
-  font-size: 18px;
-  font-weight: 500;
-}
-li {
-  margin: 10px 0;
-  line-height: 30px;
-  letter-spacing: 1px;
-}
-</style>

+ 1 - 1
src/views/CollegeGroup/Life/Campus.vue

@@ -72,7 +72,7 @@ const breadcrumbs = reactive([
     >
       <a
         :href="
-          $router.resolve(`/article-detail/article/${item.article_id}`).href
+          $router.resolve(`/article-detail/${item.article_id}`).href
         "
       >
         <img :src="`https://ntcri.org/${item.cover_img}`" alt="" />

+ 2 - 2
src/views/CollegeGroup/Life/CraftJourney.vue

@@ -9,7 +9,7 @@ let travel = reactive({
 (async () => {
   try {
     const response = await axios.get(
-      "https://cmm.ai:8088/api/get_group_classes_and_articles?group_id=3"
+      "https://cmm.ai:8088/api/get_article?group_id=3"
     );
 
     travel.list = response.data.articles.filter(
@@ -69,7 +69,7 @@ const breadcrumbs = reactive([
     >
       <a
         :href="
-          $router.resolve(`/article-detail/article/${item.article_id}`).href
+          $router.resolve(`/article-detail/${item.article_id}`).href
         "
       >
         <v-img

+ 69 - 5
src/views/CollegeGroup/Main.vue

@@ -12,10 +12,56 @@ const store = useMainStore();
 
 let title = ref("");
 title.value = route.meta.title;
+console.log("route.meta", route);
+
+let isLifeChildren = ref(false);
 
-// 切換路由時取得 metaTitle
 router.beforeEach((to, from) => {
   title.value = to.meta.title;
+  console.log("title.value", title.value);
+  console.log("to", to);
+  console.log(
+    "是否包含 /college-group/life/",
+    to.path.includes("/college-group/life/")
+  );
+  // 判斷是否前往生活工藝內頁
+  if (to.path.includes("/college-group/life/")) {
+    isLifeChildren.value = true;
+  } else {
+    isLifeChildren.value = false;
+  }
+});
+
+const description = computed(() => {
+  let text = "";
+  switch (title.value) {
+    case "未來工藝":
+      text =
+        "深入探索未來工藝的發展趨勢,以 Metacraft 為核心,專注於工藝領域的未來研究與設計。整合研發補助和研究報告等多項計畫。";
+      break;
+    case "技藝工藝":
+      text =
+        "技藝工藝旨在培養專業型、旗艦型工藝人才。我們提供專業工藝課程、修復工藝、青年培訓及國際交流。深掘台灣在地工藝智慧,並與國際接軌。";
+      break;
+    case "跨域增能":
+      text =
+        "您可以在跨域增能獲得多元知識的學習機會、培養工藝師傅的綜合能力,其中包括智財、法律、商業等全方位的領域。";
+      break;
+    case "線上工藝":
+      text =
+        "線上工藝包含中國古典文化的古老工藝、具有台灣在地特色的民俗工藝、工藝匠人的創作故事,是工藝家的影音寶庫!";
+      break;
+    case "希望工程":
+      text =
+        "希望工程以綠工藝精神結合人文關懷,串聯工藝、社會,我們希望以「One Community, One Craft」理念帶來「希望‧療癒‧陪伴」的力量、讓工藝治癒身心靈,促進社會共好。";
+      break;
+    case "生活工藝":
+      text =
+        "從精選綠色工藝品牌「旅物SHOP」,到深度工藝之旅「工藝行旅」。「一日學徒」帶您用一天的時間體會工藝師的一生。以及培養新一代工藝家啓發潛能的「校園扎根」。";
+      break;
+  }
+
+  return text;
 });
 </script>
 
@@ -23,11 +69,29 @@ router.beforeEach((to, from) => {
   <div class="college-bg-img">
     <Navbar />
     <v-container fluid class="college-content pb-16 px-lg-0">
-      <div class="college-banner">
-        <img src="@/assets/img/college-group/banner.png" alt="" />
-        <h1>{{ title }}</h1>
+      <div v-if="!isLifeChildren" class="college-banner">
+        <img
+          class="d-none d-md-block"
+          src="@/assets/img/college-group/banner.png"
+          alt=""
+        />
+        <img
+          class="d-block d-md-none"
+          src="@/assets/img/college-group/banner-mb.png"
+          alt=""
+        />
+        <div class="description-item">
+          <h1>{{ title }}</h1>
+          <p>
+            {{ description }}
+          </p>
+        </div>
+        <!-- <h1>{{ title }}</h1>
+        <p class="description-item">
+          {{ description }}
+        </p> -->
       </div>
-      <div class="main-block">
+      <div class="main-block" :class="{ life: isLifeChildren }">
         <router-view></router-view>
       </div>
     </v-container>

+ 339 - 0
src/views/CollegeGroup/Online.vue

@@ -0,0 +1,339 @@
+<script setup>
+import { ref, reactive, watch } from "vue";
+import { useMainStore } from "@/stores/store";
+import axios from "axios";
+import moment from "moment";
+import CourseCard from "@/components/CourseCard.vue";
+
+const store = useMainStore();
+
+const breadcrumbs = reactive([
+  {
+    title: "首頁",
+    disabled: false,
+    href: "/",
+  },
+  {
+    title: "工藝學群",
+    disabled: true,
+  },
+  {
+    title: "線上工藝",
+    disabled: true,
+  },
+]);
+
+const videoData = [
+  {
+    title: "手作學習系列14_筑子-竹編",
+    video:
+      "https://ntcri.org/video/%E6%89%8B%E4%BD%9C%E5%AD%B8%E7%BF%92%E7%B3%BB%E5%88%9714_%E7%AD%91%E5%AD%90-%E7%AB%B9%E7%B7%A8-1080.mp4",
+    date: "2022-02-07",
+    ctr: "172",
+    introduction:
+      "筑子是以台灣的原生竹材作編織的工藝品牌,相信無碳的手工生產方式是對土地最好的回饋。蒐集生活中的感受與天然素材,融入手藝之中來實踐生活。創作過程中,將天然植物、藥草傳統但迷人的天然染色帶入竹編配色之中。僅管需耗費長時間,但能呈現竹材顏色細膩的風味。以民生藝品、工藝工作坊、編織藝術創作,讓經典竹編工藝陪伴日常。",
+  },
+  {
+    title: "手作學習系列12_余宛庭-木食器",
+    video:
+      "https://ntcri.org/video/%E6%89%8B%E4%BD%9C%E5%AD%B8%E7%BF%92%E7%B3%BB%E5%88%9712_%E4%BD%99%E5%AE%9B%E5%BA%AD-%E6%9C%A8%E9%A3%9F%E5%99%A8-1080.mp4",
+    date: "2022-02-07",
+    ctr: "115",
+    introduction:
+      "余宛庭是木頭及編織複合媒材創作者,從2012的夏天開始持續參與“簡單市集”,將充滿心意的木頭與鉤織作品帶到市集與大家面對面交流,持續用木質及織品的溫潤帶給大家美好生活的體驗。",
+  },
+];
+
+const categoryList = reactive([
+  {
+    title: "匠作之手",
+  },
+  {
+    title: "手作學習",
+  },
+  {
+    title: "他山之石",
+  },
+  {
+    title: "推薦影片",
+  },
+  {
+    title: "最新影片",
+  },
+]);
+
+let loading = ref(false);
+let pageNum = ref(1); // 頁數(預設第一頁)
+let pageAmount = ref(18); // 每頁顯示筆數
+let totalPages = ref(1); // 總頁數
+let selectCategory = ref([]); // 選擇類別
+let isFilter = ref(false); // 篩選狀態
+let searchList = reactive([]); // 篩選條件
+let totalNum = ref(0); // 資料總筆數
+
+const courese = reactive({
+  list: [],
+});
+
+async function getClass() {
+  loading.value = true;
+  let url = `https://cmm.ai:8088/api/get_online_courese?org=udemy&page_num=${pageNum.value}&page_amount=${pageAmount.value}`;
+
+  // 判斷篩選
+  if (isFilter.value) {
+    searchList.map((item) => {
+      url += item;
+    });
+  }
+
+  try {
+    const response = await axios.get(url);
+    courese.list = response.data.online_coures;
+    totalPages.value = store.getTotalPages(response.data.total_num, 18);
+    loading.value = false;
+    totalNum.value = response.data.total_num;
+    console.log("courese.list", courese.list);
+  } catch (error) {
+    loading.value = false;
+    console.error(error);
+  }
+}
+
+getClass();
+
+const listLocation = ref(null);
+
+watch(pageNum, () => {
+  getClass();
+  listLocation.value.scrollIntoView({ behavior: "smooth", block: "start" });
+});
+
+watch(selectCategory, (val) => {
+  if (val.includes("全部")) {
+    selectCategory.value = [];
+    isFilter.value = false;
+    pageNum.value = 1;
+    getClass();
+  } else {
+    selectFilter("category", val);
+  }
+});
+
+// 篩選課程
+async function selectFilter(type, val) {
+  isFilter.value = true;
+  pageNum.value = 1; // 篩選時返回第一頁
+  searchList.splice(0, searchList.length); // 清空陣列
+
+  if (type === "category") {
+    searchList.push(`&category=${val}`);
+  }
+
+  getClass();
+}
+</script>
+
+<template>
+  <v-breadcrumbs
+    :items="breadcrumbs"
+    divider="/"
+    class="mt-10 p-0"
+  ></v-breadcrumbs>
+
+  <div class="d-flex flex-column flex-sm-row align-center justify-end title">
+    <div class="search">
+      <span>
+        <input
+          v-model="searchInput"
+          type="text"
+          @keyup.enter="search()"
+          placeholder="關鍵字搜尋"
+        />
+        <button @click="search()">
+          <img src="@/assets/img/news/news-search-icon.png" alt="" />
+        </button>
+      </span>
+      <div
+        v-if="searchError"
+        class="d-flex justify-center align-center error me-4"
+      >
+        <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
+        沒有符合搜尋條件的項目
+      </div>
+    </div>
+  </div>
+
+  <v-row>
+    <v-col sm="2" cols="12" class="px-0">
+      <ul class="btn-list">
+        <li v-for="(item, index) in categoryList" :key="index" class="mx-3">
+          <v-btn class="mb-5" variant="outlined">
+            {{ item.title }}
+          </v-btn>
+        </li>
+      </ul>
+    </v-col>
+    <v-col sm="10" cols="12">
+      <v-row class="video-list">
+        <v-col
+          cols="12"
+          md="6"
+          v-for="(item, index) in videoData"
+          :key="index"
+          class="mb-5 mb-md-0"
+        >
+          <video controls class="w-100">
+            <source :src="item.video" type="video/mp4" />
+            <a :href="item.video"></a>
+          </video>
+          <h3>{{ item.title }}</h3>
+          <p>{{ item.introduction }}</p>
+          <div class="d-flex justify-end mt-3">
+            <span class="text-gray"
+              >{{ item.date }}|點閱次數:{{ item.ctr }}</span
+            >
+          </div>
+        </v-col>
+      </v-row>
+    </v-col>
+  </v-row>
+
+  <div
+    ref="listLocation"
+    class="d-flex flex-column flex-sm-row align-center justify-space-between title"
+  >
+    <h2>線上體驗課程</h2>
+    <div class="search">
+      <span>
+        <input
+          v-model="searchInput"
+          type="text"
+          @keyup.enter="search()"
+          placeholder="關鍵字搜尋"
+        />
+        <button @click="search()">
+          <img src="@/assets/img/news/news-search-icon.png" alt="" />
+        </button>
+      </span>
+      <div
+        v-if="searchError"
+        class="d-flex justify-center align-center error me-4"
+      >
+        <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
+        沒有符合搜尋條件的項目
+      </div>
+    </div>
+  </div>
+
+  <v-row>
+    <v-col cols="12">
+      <v-row>
+        <v-col
+          cols="12"
+          md="6"
+          lg="4"
+          v-for="(item, index) in courese.list"
+          :key="index"
+          class="pa-5"
+        >
+          <CourseCard :data="item" />
+
+          <!-- <div class="main-card">
+            <a :href="item.video_url" target="_blank">
+              <section class="card-title">
+                <h3>{{ item.title }}</h3>
+              </section>
+            </a>
+
+            <a :href="item.video_url" target="_blank">
+              <div class="card-info">
+                <ul>
+                  <li class="d-flex align-center">
+                    <p class="mb-0 ms-3">分類: {{ item.category }}</p>
+                  </li>
+                  <li class="d-flex align-center">
+                    <p class="mb-0 ms-3">來源: {{ item.content }}</p>
+                  </li>
+                </ul>
+              </div>
+            </a>
+          </div> -->
+        </v-col>
+      </v-row>
+    </v-col>
+  </v-row>
+
+  <v-pagination
+    v-model="pageNum"
+    :length="totalPages"
+    rounded="circle"
+    class="mt-16"
+  ></v-pagination>
+
+  <span class="text-gray total-item">總筆數:{{ totalNum }}</span>
+</template>
+
+<style lang="scss" scoped>
+.main-block {
+  h2 {
+    @media (max-width: 600px) {
+      margin-bottom: 30px;
+    }
+  }
+}
+
+.v-input {
+  @media (max-width: 600px) {
+    max-width: 250px;
+    margin: auto;
+    margin-top: -20px;
+  }
+}
+
+.btn-list {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(110px, max-content));
+  justify-content: center;
+  padding: initial;
+
+  li {
+    list-style-type: none;
+  }
+
+  .v-btn {
+    width: 100px;
+    border-color: var(--purple);
+    @media (max-width: 600px) {
+      width: 150px;
+    }
+  }
+}
+.video-list {
+  h3 {
+    margin: 20px 0;
+    font-size: 18px;
+    font-weight: 500;
+  }
+  p {
+    // 超過兩行則省略
+    overflow: hidden;
+    text-overflow: ellipsis;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+    line-break: after-white-space;
+  }
+  h3,
+  p,
+  span {
+    line-height: 24px;
+    letter-spacing: 1px;
+  }
+  span {
+    font-size: 14px;
+  }
+}
+video {
+  border-radius: 20px;
+}
+</style>

+ 12 - 7
src/views/CollegeGroup/Repair.vue

@@ -148,7 +148,7 @@ const testData = [
     </div>
   </div>
 
-  <ul class="course-list">
+  <ul class="course-list mt-16 mt-sm-0">
     <li v-for="(item, index) in testData" :key="index" class="mb-10">
       <v-card variant="outlined" class="d-flex flex-column align-center pa-0">
         <h2 class="text-center ma-0 pa-3">{{ item.title }}</h2>
@@ -156,7 +156,7 @@ const testData = [
         <div class="d-flex flex-column flex-sm-row align-center content">
           <img src="@/assets/img/img-04.jpg" alt="" />
           <section
-            class="d-flex flex-column justify-space-between px-5 px-sm-10 py-5 py-md-0"
+            class="d-flex flex-column justify-space-between px-0 px-sm-10 pt-5 pt-md-0"
           >
             <p>{{ item.describe }}</p>
             <div class="mt-10">
@@ -232,8 +232,8 @@ const testData = [
   <h2 class="title">修護故事</h2>
 
   <ul class="story-list">
-    <li v-for="(item, index) in articles.list" :key="index" class="mb-5">
-      <ArticleCard :data="item" />
+    <li v-for="(item, index) in articles.list" :key="index" class="mb-16">
+      <ArticleCard :data="item" type="article" />
     </li>
   </ul>
 </template>
@@ -257,7 +257,7 @@ h3 {
 }
 
 p {
-  font-weight: 300;
+  font-weight: 400;
   letter-spacing: 1px;
   line-height: 28px;
 }
@@ -272,7 +272,7 @@ p {
   .v-btn {
     height: 65px;
     color: #fff;
-    font-weight: 300;
+    font-weight: 400;
     border-radius: 100px;
     background-color: #569f33;
     @media (max-width: 960px) {
@@ -292,12 +292,17 @@ p {
     h2 {
       width: 100%;
       font-size: 24px;
+      line-height: 30px;
+      letter-spacing: 1px;
       border-bottom: 3px solid #fff;
+      @media (max-width: 600px) {
+        font-size: 22px;
+      }
     }
     .content {
       padding: 20px;
       p:first-child {
-        font-weight: 300;
+        font-weight: 400;
         line-height: 28px;
         color: #606064;
       }

+ 29 - 35
src/views/CourseDetail.vue

@@ -49,6 +49,8 @@ const breadcrumbs = reactive([
   },
 ]);
 
+let courseNameId = ref(null);
+
 // 取得資料
 (async () => {
   try {
@@ -58,6 +60,8 @@ const breadcrumbs = reactive([
     course.data = response.data.classes[0];
     isInner.value = course.data.is_inner;
     console.log("courseData", course.data);
+    courseNameId.value = course.data.class_name_id;
+    console.log(" courseNameId.value", courseNameId.value);
     getOtherClass();
 
     if (course.data.group_id === 1) {
@@ -92,7 +96,6 @@ let session = reactive({
     const response = await axios.get(
       `https://cmm.ai:8088/api/get_event?class_name_id=${courseId}`
     );
-    // course.data = response.data.classes[0];
     session.data = response.data.classes;
     console.log("session.data", session.data);
     console.log("response", response.data.classes[0]);
@@ -111,29 +114,19 @@ async function getOtherClass() {
   // let number = Math.floor(Math.random() * 300) + 1; // 在 1-300 間取一個隨機正整數
   try {
     const response = await axios.get(
-      `https://cmm.ai:8088/api/get_class_name?category=${course.data.category}&page_num=1&page_amount=3`
+      `https://cmm.ai:8088/api/get_class_name?category=${course.data.category}&page_num=1&page_amount=4`
     );
     console.log("其他 response", response);
-    other.classes = response.data.classes;
+    other.classes = response.data.classes.filter(
+      (item) => item.class_name_id !== courseNameId.value
+    ); // 移除當前課程(保留3筆)
+    // other.classes = response.data.classes;
     isLoading.value = false;
   } catch (error) {
     console.error(error);
   }
 }
 
-// 隨機取值
-// function handleRandom(arr, length) {
-//   let newArr = [];
-//   for (let i = 0; i < length; i++) {
-//     let index = Math.floor(Math.random() * arr.length);
-//     console.log('index',index);
-//     let item = arr[index];
-//     newArr.push(item);
-//     arr.splice(index, 1);
-//   }
-//   return newArr.reverse();
-// }
-
 const dynamicCols = computed(() => {
   return isInner.value === 0 ? "5" : "8";
 });
@@ -166,7 +159,7 @@ async function signUp(index) {
     let response = await axios.get(
       `https://cmm.ai:8088/api/get_user_information?get_all=0&get_detail_information=1&access_token=${token}`
     );
-
+    console.log("response.data user_inform", response.data);
     Object.keys(response.data.user_inform[0]).forEach((key) => {
       if (user[key] !== undefined) {
         user[key] = response.data.user_inform[0][key];
@@ -285,7 +278,7 @@ let currentTitle = computed(() => {
             isInner === 0
               ? course.data.cover_img
               : course.data.special_class_list_name === 'one_day_class'
-              ? `../src/assets/img/一日學徒.png`
+              ? store.getImageUrl('default.png')
               : `https://ntcri.org/${course.data.cover_img}`
           "
           alt=""
@@ -320,7 +313,7 @@ let currentTitle = computed(() => {
               </tr>
               <tr>
                 <td>課程地點</td>
-                <td>{{ course.data.school }}</td>
+                <td>{{ course.data.address }}</td>
               </tr>
               <tr>
                 <td>所屬學群</td>
@@ -364,7 +357,13 @@ let currentTitle = computed(() => {
           </div>
         </div>
       </v-col>
-      <v-col cols="12" v-if="isInner !== 0 && session.data.length">
+      <v-col
+        cols="12"
+        v-if="isInner !== 0 && session.data.length"
+        :class="{
+          hide: course.data.special_class_list_name === 'one_day_class',
+        }"
+      >
         <div class="sessions">
           <table>
             <thead>
@@ -456,7 +455,7 @@ let currentTitle = computed(() => {
                             </v-window-item>
 
                             <v-window-item :value="2">
-                              <v-card-text>
+                              <v-card-text class="pa-0">
                                 <v-sheet class="mx-auto user-form">
                                   <v-form @submit.prevent>
                                     <h2
@@ -629,7 +628,7 @@ let currentTitle = computed(() => {
                                     @click="closeSignUpDialog()"
                                   >
                                     <router-link to="/user/passport"
-                                      >前往員專區</router-link
+                                      >前往員專區</router-link
                                     >
                                   </v-btn>
                                 </v-sheet>
@@ -709,6 +708,14 @@ let currentTitle = computed(() => {
 </template>
 
 <style lang="scss">
+.v-window {
+  overflow-y: auto;
+}
+
+.hide {
+  display: none !important;
+}
+
 .course-detail {
   .cover-img {
     // width: 100%;
@@ -840,19 +847,6 @@ let currentTitle = computed(() => {
     font-size: 18px;
   }
 
-  .v-label {
-    width: 100%;
-    opacity: 1;
-    .v-input {
-      width: 100%;
-    }
-    .mark {
-      display: inline-block;
-      margin-left: 1px;
-      color: red;
-    }
-  }
-
   .result-card {
     position: absolute;
     top: 50%;

+ 55 - 60
src/views/CourseList.vue

@@ -13,6 +13,7 @@ let totalPages = ref(1); // 總頁數
 let loading = ref(false);
 let searchInput = ref("");
 let searchError = ref(false);
+
 const courseAll = reactive({
   classes: [],
 });
@@ -24,6 +25,7 @@ const listLocation = ref(null);
 let isInternal = ref(false); // 是否選取學習時數課程(只顯示內課)
 let isFilter = ref(false); // 篩選狀態
 let searchList = reactive([]); // 篩選條件
+let totalNum = ref(0); // 資料總筆數
 
 // 切換分頁時回到列表上方
 watch(pageNum, () => {
@@ -33,7 +35,7 @@ watch(pageNum, () => {
 
 async function getClass() {
   loading.value = true;
-  let url = `https://cmm.ai:8088/api/get_class_name?page_num=${pageNum.value}&page_amount=${pageAmount.value}`;
+  let url = `https://cmm.ai:8088/api/get_class_name?is_check=1&page_num=${pageNum.value}&page_amount=${pageAmount.value}`;
   console.log("isFilter", isFilter.value);
 
   // 排除外部課程
@@ -54,6 +56,7 @@ async function getClass() {
     totalPages.value = store.getTotalPages(response.data.total_num, 18);
     courseAll.classes = response.data.classes;
     courseData.classes = response.data.classes;
+    totalNum.value = response.data.total_num;
     loading.value = false;
     console.log("response", response);
   } catch (error) {
@@ -64,6 +67,36 @@ async function getClass() {
 
 getClass();
 
+// 搜尋
+async function search() {
+  console.log("search", searchInput.value);
+
+  if (searchInput.value === "") {
+    courseData.classes = courseAll.classes;
+    return;
+  }
+
+  try {
+    loading.value = true;
+    const response = await axios.get(
+      `https://cmm.ai:8088/api/search_class_like?keyword=${searchInput.value}`
+    );
+    totalPages.value = store.getTotalPages(response.data.total_num, 18);
+    courseData.classes = response.data.classes;
+    loading.value = false;
+    console.log("搜尋 response", response);
+
+    if (!response.data.total_num) {
+      searchError.value = true;
+    } else {
+      searchError.value = false;
+    }
+  } catch (error) {
+    loading.value = false;
+    console.error(error);
+  }
+}
+
 let progress = ref(false);
 
 const breadcrumbs = reactive([
@@ -81,7 +114,6 @@ const breadcrumbs = reactive([
 let assignTag = ref("all");
 
 watch(assignTag, (val) => {
-  console.log("assignTag", val);
   if (val === "all") {
     isInternal.value = false;
   } else {
@@ -102,7 +134,7 @@ function selectTag(btn) {
 let selectSort = ref();
 let selectLocation = ref();
 let selectYear = ref();
-let selectCategory = ref();
+let selectCategory = ref([]);
 
 // 篩選課程
 async function selectFilter(type, val) {
@@ -119,7 +151,8 @@ async function selectFilter(type, val) {
 }
 
 watch(selectCategory, (val) => {
-  if (val === "全部") {
+  if (val.includes("全部")) {
+    selectCategory.value = [];
     isFilter.value = false;
     pageNum.value = 1;
     getClass();
@@ -130,7 +163,6 @@ watch(selectCategory, (val) => {
 </script>
 
 <template>
-  <!-- <div class="college-bg-img"> -->
   <Navbar />
   <v-container fluid class="pb-16 px-0 college-content course-content">
     <!-- <v-breadcrumbs
@@ -152,7 +184,7 @@ watch(selectCategory, (val) => {
       </v-col>
       <v-col cols="12" md="3" lg="2">
         <v-row class="filter-list">
-          <v-col cols="12" sm="6" md="12">
+          <!-- <v-col cols="12" sm="6" md="12">
             <v-select
               v-model="selectSort"
               label="排序"
@@ -182,7 +214,7 @@ watch(selectCategory, (val) => {
               :items="['2022', '2023', '2024']"
               hide-details
             ></v-select>
-          </v-col>
+          </v-col> -->
           <v-col cols="12" sm="6" md="12">
             <v-select
               v-model="selectCategory"
@@ -197,13 +229,13 @@ watch(selectCategory, (val) => {
                 '金工/飾品',
                 '蠟燭/香氛/調香',
                 '植栽/花藝',
-                '烘焙/烹飪/料理',
                 '插畫/繪畫/寫字',
                 '皮件/皮革',
                 '木工/竹藝',
                 '陶藝/玻璃',
                 '編織/羊毛氈/縫紉',
               ]"
+              multiple
               hide-details
             ></v-select>
           </v-col>
@@ -261,13 +293,6 @@ watch(selectCategory, (val) => {
                   <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>
 
@@ -297,17 +322,31 @@ watch(selectCategory, (val) => {
               color="primary"
             ></v-progress-circular>
           </div>
+
+          <div
+            v-if="searchError"
+            class="d-flex justify-center align-center pt-12 me-4"
+          >
+            <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
+            沒有符合搜尋條件的項目
+          </div>
+
           <v-pagination
+            v-if="!searchError"
             v-model="pageNum"
             :length="totalPages"
             rounded="circle"
             class="mt-16"
           ></v-pagination>
+
+          <span class="text-gray total-item">總筆數:{{ totalNum }}</span>
         </div>
       </v-col>
+      <v-col cols="12" class="my-16">
+        <img src="@/assets/img/course/banner.png" alt="" />
+      </v-col>
     </v-row>
   </v-container>
-  <!-- </div> -->
 </template>
 
 <style lang="scss">
@@ -338,50 +377,6 @@ watch(selectCategory, (val) => {
     @media (max-width: 960px) {
       margin: 20px 0;
     }
-    .v-select {
-      margin-bottom: 10px;
-      .v-field__overlay {
-        opacity: 1 !important;
-        border: 1px solid #ccc;
-        background-color: #fff;
-        border-radius: 100px !important;
-      }
-      .v-field__outline {
-        display: none;
-      }
-      .v-field__input {
-        padding-top: 15px;
-      }
-      .v-field__field {
-        height: 45px;
-      }
-      .v-select__selection {
-        overflow: hidden;
-      }
-      .v-select__selection-text {
-        white-space: nowrap;
-      }
-      .v-label.v-field-label--floating {
-        top: 3px !important;
-      }
-    }
-  }
-
-  .v-pagination {
-    margin: auto;
-    max-width: 500px;
-  }
-
-  .tab-btn {
-    button {
-      height: 45px;
-      font-size: 28px;
-      font-weight: 500;
-      color: #ccc;
-      &.active {
-        color: #000;
-      }
-    }
   }
 }
 </style>

+ 1513 - 0
src/views/Courses/Create.vue

@@ -0,0 +1,1513 @@
+<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 "@vuepic/vue-datepicker/dist/main.css";
+import axios from "axios";
+import moment from "moment";
+import Navbar from "@/components/Navbar.vue";
+
+const store = useMainStore();
+const token = store.token;
+console.log("token", token);
+
+let step = ref(1);
+let stepTitle = ref("");
+let stepDescription = ref("");
+let loading = ref(false);
+
+const computedTitle = computed(() => {
+  switch (step.value) {
+    case 1:
+      stepTitle.value = "Step1 新增據點";
+      stepDescription.value = "創建課程之前,請先新增您的據點(上課地址)";
+      break;
+    case 2:
+      stepTitle.value = "Step2 工藝家教學履歷";
+      stepDescription.value =
+        "開始打造屬於您的工藝履歷,讓學徒對你印象深刻吧!";
+      break;
+    case 3:
+      stepTitle.value = "Step3 創建課程";
+      stepDescription.value =
+        "完整且清楚的課程建立,是連接工藝老師與學徒的重要橋梁!";
+      break;
+    case 4:
+      stepTitle.value = "恭喜您完成創建課程!";
+      stepDescription.value = "";
+      break;
+  }
+  return { stepTitle: stepTitle.value, stepDescription: stepDescription.value };
+});
+
+let date = reactive({
+  start_date: "", // 起始日期
+  start_time: "", // 起始時間
+  end_date: "", // 結束日期
+  end_time: "", // 結束時間
+  registration_start_date: "", // 報名起始日期
+  registration_start_time: "", // 報名起始時間
+  registration_end_date: "", // 報名截止日期
+  registration_end_time: "", // 報名截止時間
+});
+
+// 處理時間格式(日期+時間)
+function mergeAndFormatDateTime(dateString, timeInfo) {
+  const date = new Date(dateString);
+
+  date.setHours(timeInfo.hours);
+  date.setMinutes(timeInfo.minutes);
+  date.setSeconds(timeInfo.seconds);
+
+  const year = date.getUTCFullYear();
+  const month = String(date.getUTCMonth() + 1).padStart(2, "0");
+  const day = String(date.getUTCDate()).padStart(2, "0");
+  const hours = String(date.getUTCHours()).padStart(2, "0");
+  const minutes = String(date.getUTCMinutes()).padStart(2, "0");
+  const seconds = String(date.getUTCSeconds()).padStart(2, "0");
+
+  return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.000Z`;
+}
+
+const breadcrumbs = reactive([
+  {
+    title: "首頁",
+    disabled: false,
+    href: "/",
+  },
+  {
+    title: "我要開課",
+    disabled: false,
+    href: "/setup-courses",
+  },
+  {
+    title: "創建課程",
+    disabled: true,
+  },
+]);
+
+// Google Map
+const states = reactive({
+  google: null,
+  map: null,
+  markers: null,
+});
+
+const initMap = async () => {
+  const loader = new Loader({
+    apiKey: "AIzaSyAzDeviZ-TpwzT1atlnshNJRjBgndP05Mw",
+    version: "weekly",
+    libraries: ["places"],
+    language: "zh-TW",
+  });
+  states.google = await loader.load();
+  states.map = new states.google.maps.Map(document.getElementById("map"), {
+    center: { lat: 25.0425, lng: 121.5468 },
+    zoom: 11,
+    mapTypeControl: false,
+    fullscreenControl: false,
+  });
+};
+
+onMounted(async () => {
+  await initMap();
+});
+
+let location = reactive({
+  location_name: "",
+  Lng: "",
+  Lat: "",
+  address: "",
+  introduction: "",
+  email: "",
+  phone: "",
+  access_token: token,
+});
+
+let locationId = ref("");
+
+async function insertSchool() {
+  const formData = new FormData();
+  for (const key in location) {
+    formData.append(key, location[key]);
+  }
+
+  try {
+    const response = await axios.post(
+      "https://cmm.ai:8088/api/insert_school",
+      formData
+    );
+
+    locationId.value = response.data.location_id;
+    console.log("新增據點 response", response);
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+// 新增履歷
+let resume = reactive({
+  teacher_name: "", // 老師姓名
+  work_type: "", // 工作性質
+  experience: "", // 教學經驗
+  expertise: "", // 專長工藝技能
+  license: "", // 工藝相關證照
+  media: "", // 社群媒體
+  files: [], // 作品集
+  introduction: "", // 老師介紹
+});
+
+async function insertUserResume() {
+  console.log("insertUserResume", resume);
+
+  if (portfolioImg.value.length) {
+    resume.files = portfolioImg.value;
+  }
+
+  const formData = new FormData();
+  for (const key in resume) {
+    if (key === "files") {
+      resume.files.forEach((file) => {
+        formData.append("files", file);
+      });
+    } else {
+      formData.append(key, resume[key]);
+    }
+  }
+
+  let token = store.token;
+
+  try {
+    const response = await axios.post(
+      `https://cmm.ai:8088/api/input_user_resume?access_token=${token}`,
+      formData
+    );
+    console.log("新增履歷 response", response);
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+// 新增課程
+let course = reactive({
+  name: "", // 課程名稱
+  location_id: "", // 據點編號
+  category: "", // 工藝類別
+  introduction: "", // 課程簡介
+  organizer: "", // 主辦單位
+  cover_img_file: "", // 課程圖片
+  group_id: 2, // 學群編號(技藝)
+  group_sort: "", // 學群細分
+  special_class_list_name: "",
+  recommend: 0, // 是否推薦
+  is_inner: 1, // 內課課程
+  is_check: 0, // 審核結果
+  access_token: token,
+});
+
+watch(location, (data) => {
+  course.organizer = data.location_name; // 據點名稱 = 主辦單位
+});
+
+let classNameId = ref("");
+
+async function insertClassName() {
+  console.log("insertClassName", course);
+
+  course.location_id = locationId.value;
+
+  if (coverImg.value !== "") {
+    course.cover_img_file = coverImg.value;
+  }
+
+  const formData = new FormData();
+  for (const key in course) {
+    formData.append(key, course[key]);
+  }
+
+  try {
+    const response = await axios.post(
+      "https://cmm.ai:8088/api/insert_class_name",
+      formData
+    );
+
+    console.log("新增課程 response", response);
+    classNameId.value = response.data.new_class_name_id;
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+// 新增場次
+let event = reactive({
+  name_id: "", // 課程名稱編號(需先新增課程取得Id)
+  event: "", // 場次名稱
+  start_time: "", // 課程起始日
+  end_time: "", // 課程結束日
+  contact: "", // 聯絡資訊
+  lecturer: "", // 課程講師
+  location: "",
+  content: "", // 課程內容
+  URL: "", // 外部連結
+  people: "不拘", // 對象
+  fee_method: "", // 課程價格
+  fee_payment: "", // 收費方式
+  registration_way: "", // 報名方式
+  registration_start: "", // 報名起始日
+  registration_end: "", // 報名截止日
+  number_limit: "", // 人數限制
+  remark: "", // 備註
+  ATM_address: "", // 匯款帳號(銀行代碼+帳號)
+  access_token: token,
+});
+
+let eventType = ref("");
+let isOneDay = ref(true);
+
+watch(eventType, (val) => {
+  if (val === "週期課程(例:2023/10/1~2023/10/30 每週一三上課)") {
+    isOneDay.value = false;
+  } else {
+    isOneDay.value = true;
+  }
+});
+
+let eventData = reactive({
+  list: [],
+});
+
+let isRemit = ref(false); // 是否顯示匯款資訊欄位
+let bankCode = ref("");
+let bankAccount = ref("");
+
+watch(event, (data) => {
+  if (data.fee_payment === "匯款") {
+    isRemit.value = true;
+  } else {
+    isRemit.value = false;
+  }
+});
+
+function addEventData() {
+  // 處理時間格式
+  event.start_time = mergeAndFormatDateTime(date.start_date, date.start_time);
+  event.end_time = mergeAndFormatDateTime(date.end_date, date.end_time);
+  event.registration_start = mergeAndFormatDateTime(
+    date.registration_start_date,
+    date.registration_start_time
+  );
+  event.registration_end = mergeAndFormatDateTime(
+    date.registration_end_date,
+    date.registration_end_time
+  );
+
+  if (isRemit && bankCode.value !== "" && bankAccount.value !== "") {
+    event.ATM_address = `(${bankCode.value})-${bankAccount.value}`;
+  }
+  eventData.list.push(JSON.parse(JSON.stringify(event)));
+  sessionsDialog.value = false;
+}
+
+function convertDateFormat(inputDateStr) {
+  const inputDate = new Date(inputDateStr);
+  const formattedDate = inputDate.toISOString();
+  return formattedDate;
+}
+
+async function insertEvent() {
+  console.log("insertEvent", event);
+
+  if (classNameId.value !== "") {
+    event.name_id = classNameId.value;
+  }
+
+  event.start_time = convertDateFormat(event.start_time);
+  event.end_time = convertDateFormat(event.end_time);
+  event.registration_start = convertDateFormat(event.registration_start);
+  event.registration_end = convertDateFormat(event.registration_end);
+
+  console.log("檢查日期格式", event);
+
+  const formData = new FormData();
+  for (const key in event) {
+    formData.append(key, event[key]);
+  }
+
+  try {
+    const response = await axios.post(
+      "https://cmm.ai:8088/api/insert_event",
+      formData
+    );
+
+    console.log("新增場次 response", response);
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+// 創建課程
+async function create() {
+  loading.value = true;
+
+  try {
+    await insertSchool(); // 新增據點
+    await insertUserResume(); // 新增履歷
+    await insertClassName(); // 新增課程
+    await insertEvent(); // 新增場次
+    loading.value = false;
+    step.value++;
+  } catch (error) {
+    console.error(error);
+    loading.value = false;
+  }
+}
+
+const getCoordinates = async () => {
+  const geocoder = new states.google.maps.Geocoder();
+  geocoder.geocode({ address: location.address }, (results, status) => {
+    if (status === states.google.maps.GeocoderStatus.OK) {
+      location.Lat = results[0].geometry.location.lat();
+      location.Lng = results[0].geometry.location.lng();
+    }
+
+    // 將地圖中心設為取得的經緯度,並調整縮放等級
+    states.map.setCenter({ lat: location.Lat, lng: location.Lng });
+    states.map.setZoom(15);
+
+    // 設定地址圖標
+    const marker = new google.maps.Marker({
+      position: { lat: location.Lat, lng: location.Lng },
+      map: states.map,
+      title: location.address,
+      icon: store.getImageUrl("map-icon/icon_house05.png"),
+    });
+  });
+};
+
+// 上傳圖片 Input
+const portfolioImgRef = ref(null);
+const coverImgRef = ref(null);
+
+const fileInputClick = (ref) => {
+  if (ref === "portfolio") {
+    portfolioImgRef.value.click();
+  } else if (ref === "cover") {
+    coverImgRef.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);
+  }
+};
+
+watch(portfolioImg, (newFiles) => {
+  if (newFiles.length > 0) {
+    handlePortfolioImg(newFiles);
+  }
+});
+
+// 封面圖片
+let coverImg = ref("");
+let coverImgUrl = ref("");
+
+const handleCoverImg = (event) => {
+  const file = event.target.files[0];
+  console.log("選擇檔案", file);
+  if (file) {
+    coverImg.value = file;
+    coverImgUrl.value = URL.createObjectURL(file); // 設定圖片預覽 URL
+  }
+};
+
+// let age = ref("不拘");
+let sessionsDialog = ref(false);
+
+const requiredRule = (value) => !!value || "此欄位為必填";
+
+const weekList = reactive(["一", "二", "三", "四", "五", "六", "日"]);
+</script>
+
+<template>
+  <Navbar />
+
+  <v-container class="mb-16 pb-16">
+    <v-breadcrumbs
+      :items="breadcrumbs"
+      divider="/"
+      class="py-10"
+    ></v-breadcrumbs>
+
+    <v-card class="mx-auto pa-10">
+      <v-card-title class="step-title">
+        <h5>{{ computedTitle.stepTitle }}</h5>
+        <p class="mt-5">{{ computedTitle.stepDescription }}</p>
+      </v-card-title>
+
+      <v-window v-model="step">
+        <v-window-item :value="1">
+          <v-card-text>
+            <v-label class="d-flex align-center pb-3">
+              <p class="pb-5 pe-3">據點名稱<span class="mark">*</span></p>
+              <v-text-field
+                v-model="location.location_name"
+                :rules="[requiredRule]"
+                placeholder="可以是您的工作室或品牌名稱/教學單位/您的姓名"
+                density="compact"
+                variant="outlined"
+                counter
+                maxlength="60"
+              ></v-text-field>
+            </v-label>
+
+            <v-label class="d-flex align-center pb-3 w-100">
+              <p class="pb-5 pe-3">據點地址<span class="mark">*</span></p>
+              <v-text-field
+                v-model="location.address"
+                :rules="[requiredRule]"
+                density="compact"
+                variant="outlined"
+                placeholder="需在安全且便於學徒到達之地點開課"
+              ></v-text-field>
+              <v-btn
+                @click="getCoordinates"
+                color="purple"
+                variant="flat"
+                class="ms-3 mb-6 px-8"
+              >
+                查詢
+              </v-btn>
+            </v-label>
+
+            <div class="d-flex flex-column justify-end ms-16 ps-5">
+              <div
+                class="map"
+                id="map"
+                style="width: 100%; height: 500px"
+              ></div>
+
+              <v-row class="mt-3">
+                <v-col cols="12" md="6">
+                  <v-label class="d-flex align-center">
+                    <p class="pb-5 pe-3">經度</p>
+                    <v-text-field
+                      v-model="location.Lng"
+                      density="compact"
+                      variant="outlined"
+                      disabled
+                    ></v-text-field>
+                  </v-label>
+                </v-col>
+                <v-col cols="12" md="6">
+                  <v-label class="d-flex align-center">
+                    <p class="pb-5 pe-3">緯度</p>
+                    <v-text-field
+                      v-model="location.Lat"
+                      density="compact"
+                      variant="outlined"
+                      disabled
+                    ></v-text-field>
+                  </v-label>
+                </v-col>
+              </v-row>
+            </div>
+
+            <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-row class="mt-3">
+              <v-col cols="12" md="12">
+                <v-label class="d-block">
+                  <p class="d-flex">公開電話<span class="mark">*</span></p>
+                  <span class="d-block py-3 hint"
+                    >會顯示於課程介紹中,想了解課程資訊者聯繫用途</span
+                  >
+                  <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="6">
+                <v-label class="d-block">
+                  <p class="d-flex">聯繫電話<span class="mark">*</span></p>
+                  <span class="d-block py-3 hint">平台專員連繫重要事項用</span>
+                  <v-text-field
+                    :rules="[(v) => !!v || '請輸入電話']"
+                    density="compact"
+                    variant="outlined"
+                  ></v-text-field>
+                </v-label>
+              </v-col> -->
+              <!-- <v-col cols="12" md="6">
+                <v-label class="d-block">
+                  <p class="d-flex">上傳據點照片</p>
+
+                  <v-file-input
+                    ref="fileInputRef"
+                    label="File input"
+                    variant="outlined"
+                    placeholder="選擇相片上傳"
+                    @change="handlePortfolioImg"
+                    style="display: none"
+                  ></v-file-input>
+                </v-label>
+                <v-btn @click="openFileInput" color="purple" class="my-5"
+                  >選擇相片上傳</v-btn
+                >
+                <div class="step-01 image-preview">
+                  <img
+                    v-if="selectedFile"
+                    :src="selectedFileUrl"
+                    alt="上傳圖片預覽"
+                  />
+                </div>
+              </v-col> -->
+
+              <v-col cols="12" md="12">
+                <v-label class="d-block">
+                  <p class="d-flex mb-5">據點簡介<span class="mark">*</span></p>
+                  <v-textarea
+                    v-model="location.introduction"
+                    rows="5"
+                    variant="outlined"
+                  ></v-textarea>
+                </v-label>
+              </v-col>
+            </v-row>
+          </v-card-text>
+        </v-window-item>
+
+        <v-window-item :value="2">
+          <v-card-text>
+            <v-row>
+              <v-col cols="6">
+                <v-label class="d-flex align-center pb-3">
+                  <p class="pb-5 pe-3">老師姓名<span class="mark">*</span></p>
+                  <v-text-field
+                    v-model="resume.teacher_name"
+                    :rules="[requiredRule]"
+                    density="compact"
+                    variant="outlined"
+                  ></v-text-field>
+                </v-label>
+              </v-col>
+
+              <v-col cols="6"></v-col>
+
+              <v-col cols="3">
+                <v-label class="d-block">
+                  <p class="pb-5 pe-3">工作性質<span class="mark">*</span></p>
+                  <v-radio-group v-model="resume.work_type" inline>
+                    <v-radio label="全職" value="全職"></v-radio>
+                    <v-radio label="兼職" value="兼職" class="ps-3"></v-radio>
+                  </v-radio-group>
+                </v-label>
+              </v-col>
+
+              <v-col cols="9">
+                <v-label class="d-block">
+                  <p class="pb-5 pe-3">教學經驗</p>
+                  <v-radio-group v-model="resume.experience" inline>
+                    <v-radio label="0-5 年" value="0-5 年"></v-radio>
+                    <v-radio
+                      label="5-10 年"
+                      value="5-10 年"
+                      class="ps-3"
+                    ></v-radio>
+                    <v-radio
+                      label="10-20 年"
+                      value="10-20 年"
+                      class="ps-3"
+                    ></v-radio>
+                    <v-radio
+                      label="20 年以上"
+                      value="20 年以上"
+                      class="ps-3"
+                    ></v-radio>
+                  </v-radio-group>
+                </v-label>
+              </v-col>
+
+              <v-col cols="6">
+                <v-label class="d-block pb-3">
+                  <p class="pb-5 pe-3">
+                    專長工藝技能<span class="mark">*</span>
+                  </p>
+                  <v-text-field
+                    v-model="resume.expertise"
+                    :rules="[requiredRule]"
+                    density="compact"
+                    variant="outlined"
+                  ></v-text-field>
+                </v-label>
+
+                <v-label class="d-block pb-3">
+                  <p class="pb-5 pe-3">工藝相關證照</p>
+                  <v-text-field
+                    v-model="resume.license"
+                    density="compact"
+                    variant="outlined"
+                  ></v-text-field>
+                </v-label>
+
+                <v-label class="d-block">
+                  <p class="d-flex">社群媒體</p>
+                  <span class="d-block py-3 hint"
+                    >工藝老師經營之網站或社群媒體,可貼網址</span
+                  >
+                  <v-text-field
+                    v-model="resume.media"
+                    density="compact"
+                    variant="outlined"
+                  ></v-text-field>
+                </v-label>
+              </v-col>
+
+              <v-col cols="6">
+                <v-label class="d-block">
+                  <p class="d-flex">講師作品集</p>
+
+                  <v-file-input
+                    multiple
+                    v-model="portfolioImg"
+                    ref="portfolioImgRef"
+                    label="File input"
+                    variant="outlined"
+                    placeholder="選擇相片上傳"
+                    @change="handlePortfolioImg"
+                    style="display: none"
+                  ></v-file-input>
+                </v-label>
+
+                <v-btn
+                  @click="fileInputClick('portfolio')"
+                  color="purple"
+                  class="my-5"
+                  >選擇相片上傳</v-btn
+                >
+
+                <v-row class="img-list">
+                  <v-col cols="4" v-for="(item, index) in 6" :key="index">
+                    <div v-if="!portfolioImgList.length" class="item"></div>
+                    <div
+                      v-else
+                      class="item"
+                      :style="{
+                        backgroundImage: `url('${portfolioImgList[index]}')`,
+                      }"
+                    ></div>
+                  </v-col>
+                </v-row>
+
+                <!-- <div class="step-02 image-preview">
+                  <img
+                    v-if="selectedFile"
+                    :src="selectedFileUrl"
+                    alt="上傳圖片預覽"
+                  />
+                </div> -->
+              </v-col>
+
+              <v-col cols="12">
+                <v-label class="d-block">
+                  <p class="d-flex mb-5">老師自我介紹</p>
+                  <v-textarea
+                    v-model="resume.introduction"
+                    rows="5"
+                    variant="outlined"
+                  ></v-textarea>
+                </v-label>
+              </v-col>
+            </v-row>
+          </v-card-text>
+        </v-window-item>
+
+        <v-window-item :value="3">
+          <v-card-text class="mb-7">
+            <v-row class="justify-space-evenly">
+              <v-col cols="12" md="5">
+                <v-label class="d-block pb-3">
+                  <p class="pb-5 pe-3">課程名稱<span class="mark">*</span></p>
+                  <v-text-field
+                    v-model="course.name"
+                    :rules="[requiredRule]"
+                    density="compact"
+                    variant="outlined"
+                  ></v-text-field>
+                </v-label>
+              </v-col>
+
+              <!-- <v-col cols="12" md="5">
+                <v-label class="d-block pb-3">
+                  <p class="pb-5 pe-3">課程價格<span class="mark">*</span></p>
+                  <v-text-field
+                    :rules="[requiredRule]"
+                    density="compact"
+                    variant="outlined"
+                  ></v-text-field>
+                </v-label>
+              </v-col> -->
+
+              <v-col cols="12" md="5">
+                <v-label class="d-block pb-3">
+                  <p class="pb-5 pe-3">工藝類別<span class="mark">*</span></p>
+                  <v-select
+                    v-model="course.category"
+                    placeholder="請選擇類別"
+                    :items="[
+                      '樹藝',
+                      '漆藝',
+                      '藍染',
+                      '蠟雕',
+                      '竹工藝籃',
+                      '金工/飾品',
+                      '蠟燭/香氛/調香',
+                      '植栽/花藝',
+                      '插畫/繪畫/寫字',
+                      '皮件/皮革',
+                      '木工/竹藝',
+                      '陶藝/玻璃',
+                      '編織/羊毛氈/縫紉',
+                      '其他',
+                    ]"
+                    :rules="[requiredRule]"
+                    hide-details
+                    density="compact"
+                    variant="outlined"
+                  ></v-select>
+                </v-label>
+              </v-col>
+
+              <!-- <v-col cols="12" md="5">
+                <v-label class="d-block pb-3">
+                  <p class="pb-5 pe-3">
+                    適合報名對象(年紀)<span class="mark">*</span>
+                  </p>
+
+                  <v-select
+                    v-model="age"
+                    :items="[
+                      '不拘',
+                      '7 歲以下',
+                      '7-18 歲',
+                      '18-65 歲',
+                      '65 歲以上',
+                    ]"
+                    :rules="[requiredRule]"
+                    density="compact"
+                    variant="outlined"
+                    outlined
+                  ></v-select>
+                </v-label>
+              </v-col> -->
+
+              <v-col cols="12" md="5">
+                <v-label class="d-block pb-3">
+                  <p class="pb-5 pe-3">上課地點</p>
+                  <v-text-field
+                    v-model="location.address"
+                    label="自動帶入據點地址"
+                    density="compact"
+                    variant="outlined"
+                  ></v-text-field>
+                </v-label>
+              </v-col>
+
+              <v-col cols="12" md="5">
+                <v-label class="d-block pb-3">
+                  <p class="pb-5 pe-3">主辦單位</p>
+                  <v-text-field
+                    v-model="location.location_name"
+                    label="自動帶入據點名稱"
+                    density="compact"
+                    variant="outlined"
+                  ></v-text-field>
+                </v-label>
+              </v-col>
+
+              <v-col cols="12" md="11" class="px-7">
+                <v-label class="d-block">
+                  <p class="d-flex">課程時間<span class="mark">*</span></p>
+
+                  <!-- <v-btn color="purple" class="my-5"
+                  >
+                  <v-icon icon="mdi-plus" class="me-1"></v-icon>
+                  建立新場次</v-btn
+                > -->
+
+                  <v-dialog v-model="sessionsDialog" persistent width="800">
+                    <template v-slot:activator="{ props }">
+                      <v-btn class="my-5" color="purple" v-bind="props">
+                        <v-icon icon="mdi-plus" class="me-1"></v-icon>
+                        建立新場次
+                      </v-btn>
+                    </template>
+                    <v-card class="sessions-card pb-3">
+                      <v-card-title>
+                        <span class="d-block ps-10 py-5">建立新場次</span>
+                      </v-card-title>
+                      <v-card-text class="pt-0 px-8">
+                        <v-container class="py-0">
+                          <v-row>
+                            <v-col cols="12" sm="6" class="date-item">
+                              <p class="mb-0 pe-3">
+                                起始日期<span class="mark">*</span>
+                              </p>
+                              <VueDatePicker
+                                v-model="date.start_date"
+                                :min-date="new Date()"
+                                :enable-time-picker="false"
+                                :format="store.datePickerFormat"
+                                locale="cn"
+                              ></VueDatePicker>
+                            </v-col>
+
+                            <v-col cols="12" sm="6" class="date-item">
+                              <p class="mb-0 pe-3">
+                                起始時間<span class="mark">*</span>
+                              </p>
+                              <VueDatePicker
+                                v-model="date.start_time"
+                                time-picker
+                              />
+                            </v-col>
+
+                            <v-col cols="12" sm="6" class="date-item">
+                              <p class="mb-0 pe-3">
+                                結束日期<span class="mark">*</span>
+                              </p>
+                              <VueDatePicker
+                                v-model="date.end_date"
+                                :min-date="new Date()"
+                                :enable-time-picker="false"
+                                :format="store.datePickerFormat"
+                                locale="cn"
+                              ></VueDatePicker>
+                            </v-col>
+
+                            <v-col cols="12" sm="6" class="date-item">
+                              <p class="mb-0 pe-3">
+                                結束時間<span class="mark">*</span>
+                              </p>
+                              <VueDatePicker
+                                v-model="date.end_time"
+                                time-picker
+                              />
+                            </v-col>
+
+                            <v-divider class="mt-5 mb-8"></v-divider>
+
+                            <v-col cols="12" sm="6" class="py-0">
+                              <v-label class="d-flex align-center py-2">
+                                <p class="pb-5 pe-3">
+                                  場次名稱<span class="mark">*</span>
+                                </p>
+                                <v-text-field
+                                  v-model="event.event"
+                                  :rules="[requiredRule]"
+                                  density="compact"
+                                  variant="outlined"
+                                ></v-text-field>
+                              </v-label>
+                            </v-col>
+
+                            <v-col cols="12" sm="6" class="py-0">
+                              <v-label class="d-flex align-center py-2">
+                                <p class="pb-5 pe-3">
+                                  課程講師<span class="mark">*</span>
+                                </p>
+                                <v-text-field
+                                  v-model="event.lecturer"
+                                  :rules="[requiredRule]"
+                                  density="compact"
+                                  variant="outlined"
+                                ></v-text-field>
+                              </v-label>
+                            </v-col>
+
+                            <v-col cols="12" class="py-0">
+                              <v-label class="d-flex align-center py-2">
+                                <p class="pb-5 pe-3">
+                                  聯絡資訊<span class="mark">*</span>
+                                </p>
+                                <v-text-field
+                                  v-model="event.contact"
+                                  :rules="[requiredRule]"
+                                  density="compact"
+                                  variant="outlined"
+                                ></v-text-field>
+                              </v-label>
+                            </v-col>
+
+                            <v-col cols="12" class="py-0">
+                              <v-label class="d-flex align-center py-2">
+                                <p class="pb-5 pe-3">
+                                  收費方式<span class="mark">*</span>
+                                </p>
+
+                                <v-select
+                                  v-model="event.fee_payment"
+                                  :items="['現場收費', '匯款']"
+                                  :rules="[requiredRule]"
+                                  density="compact"
+                                  variant="outlined"
+                                  outlined
+                                ></v-select>
+                              </v-label>
+                            </v-col>
+
+                            <v-col cols="12" class="py-0">
+                              <v-label
+                                v-if="isRemit"
+                                class="d-flex align-center py-2"
+                              >
+                                <p class="pb-5 pe-3">
+                                  匯款資訊<span class="mark">*</span>
+                                </p>
+
+                                <div class="d-flex w-100">
+                                  <v-text-field
+                                    v-model="bankCode"
+                                    label="銀行代碼"
+                                    :rules="[requiredRule]"
+                                    density="compact"
+                                    variant="outlined"
+                                    style="width: 30%"
+                                    class="me-3"
+                                  ></v-text-field>
+                                  <v-text-field
+                                    v-model="bankAccount"
+                                    label="匯款帳號 (10-16 碼)"
+                                    :rules="[requiredRule]"
+                                    density="compact"
+                                    variant="outlined"
+                                    style="width: 70%"
+                                  ></v-text-field>
+                                </div>
+                              </v-label>
+                            </v-col>
+
+                            <v-col cols="12" sm="6" class="py-0">
+                              <v-label class="d-flex align-center py-2">
+                                <p class="pb-5 pe-3">
+                                  課程價格<span class="mark">*</span>
+                                </p>
+                                <v-text-field
+                                  v-model="event.fee_method"
+                                  :rules="[requiredRule]"
+                                  density="compact"
+                                  variant="outlined"
+                                ></v-text-field>
+                              </v-label>
+                            </v-col>
+
+                            <v-col cols="12" sm="6" class="py-0">
+                              <v-label class="d-flex align-center">
+                                <p class="pb-3 pe-5">
+                                  適合報名<br />對象<span class="mark pb-4"
+                                    >*</span
+                                  >
+                                </p>
+                                <v-select
+                                  v-model="event.people"
+                                  :items="[
+                                    '不拘',
+                                    '7 歲以下',
+                                    '7-18 歲',
+                                    '18-65 歲',
+                                    '65 歲以上',
+                                  ]"
+                                  :rules="[requiredRule]"
+                                  density="compact"
+                                  variant="outlined"
+                                  outlined
+                                ></v-select>
+                                <!-- <v-text-field
+                                    :rules="[requiredRule]"
+                                    density="compact"
+                                    variant="outlined"
+                                  ></v-text-field> -->
+                              </v-label>
+                            </v-col>
+
+                            <v-col cols="12" class="py-0 position-relative">
+                              <v-label class="d-flex align-center py-2">
+                                <p class="pb-3 pe-3">
+                                  課程類型<span class="mark pb-4">*</span>
+                                </p>
+                                <v-select
+                                  v-model="eventType"
+                                  :items="[
+                                    '一日課程(例:2023/10/2 週一上課)',
+                                    '週期課程(例:2023/10/1~2023/10/30 每週一三上課)',
+                                  ]"
+                                  :rules="[requiredRule]"
+                                  density="compact"
+                                  variant="outlined"
+                                  outlined
+                                ></v-select>
+                              </v-label>
+
+                              <!-- <small
+                                v-show="eventType !== ''"
+                                class="type-hint"
+                                >{{
+                                  isOneDay
+                                    ? "例:2023/10/2 週一上課"
+                                    : "例:2023/10/1~2023/10/30 每週一三上課"
+                                }}</small
+                              > -->
+                            </v-col>
+
+                            <v-col
+                              v-if="!isOneDay"
+                              cols="12"
+                              class="d-flex py-0"
+                            >
+                              <p class="pt-1 pe-3">
+                                重複週期<span class="mark pb-4">*</span>
+                              </p>
+
+                              <ul class="d-flex week-list">
+                                <li
+                                  v-for="(item, index) in weekList"
+                                  :key="index"
+                                >
+                                  <button>
+                                    {{ item }}
+                                  </button>
+                                </li>
+                              </ul>
+                            </v-col>
+
+                            <v-col
+                              v-if="!isOneDay"
+                              cols="12"
+                              class="time-item pt-5 pb-7"
+                            >
+                              <p class="mb-0">
+                                課程時間<span class="mark">*</span>
+                              </p>
+                              <div class="d-flex w-100">
+                                <VueDatePicker
+                                  v-model="date.end_time"
+                                  time-picker
+                                />
+                                <span class="d-flex align-center mx-2">~</span>
+                                <VueDatePicker
+                                  v-model="date.end_time"
+                                  time-picker
+                                />
+                              </div>
+                            </v-col>
+
+                            <v-col cols="12" class="py-0">
+                              <v-label class="d-flex align-center py-2">
+                                <p class="pe-3">備註</p>
+                                <v-text-field
+                                  v-model="event.remark"
+                                  density="compact"
+                                  variant="outlined"
+                                  hide-details
+                                ></v-text-field>
+                              </v-label>
+                            </v-col>
+
+                            <v-divider class="my-8"></v-divider>
+
+                            <v-col cols="12" sm="6" class="date-item">
+                              <p class="mb-0 pe-3">
+                                報名日期<span class="mark pb-4">*</span>
+                              </p>
+                              <VueDatePicker
+                                v-model="date.registration_start_date"
+                                :min-date="new Date()"
+                                :enable-time-picker="false"
+                                :format="store.datePickerFormat"
+                                locale="cn"
+                              ></VueDatePicker>
+                            </v-col>
+
+                            <v-col cols="12" sm="6" class="date-item">
+                              <p class="mb-0 pe-3">
+                                報名時間<span class="mark pb-4">*</span>
+                              </p>
+                              <VueDatePicker
+                                v-model="date.registration_start_time"
+                                time-picker
+                              />
+                            </v-col>
+
+                            <v-col cols="12" sm="6" class="date-item">
+                              <p class="mb-0 pe-3">
+                                報名截止<span class="mark pb-4">*</span>
+                              </p>
+                              <VueDatePicker
+                                v-model="date.registration_end_date"
+                                :min-date="new Date()"
+                                :enable-time-picker="false"
+                                :format="store.datePickerFormat"
+                                locale="cn"
+                              ></VueDatePicker>
+                            </v-col>
+
+                            <v-col cols="12" sm="6" class="date-item">
+                              <p class="mb-0 pe-3">
+                                截止時間<span class="mark pb-4">*</span>
+                              </p>
+                              <VueDatePicker
+                                v-model="date.registration_end_time"
+                                time-picker
+                              />
+                            </v-col>
+                          </v-row>
+                        </v-container>
+                      </v-card-text>
+
+                      <v-card-actions class="justify-center pt-10 pb-5">
+                        <v-btn
+                          color="gray"
+                          variant="tonal"
+                          class="me-3 px-10"
+                          @click="sessionsDialog = false"
+                        >
+                          取消
+                        </v-btn>
+                        <v-btn
+                          color="purple"
+                          variant="flat"
+                          @click="addEventData()"
+                          class="px-10"
+                        >
+                          新增
+                        </v-btn>
+                      </v-card-actions>
+                    </v-card>
+                  </v-dialog>
+
+                  <div v-if="eventData.list.length" class="main-table">
+                    <h6 class="table-title">場次資訊</h6>
+                    <table>
+                      <thead>
+                        <tr>
+                          <th>名稱</th>
+                          <th>日期</th>
+                          <th width="20%">課程講師</th>
+                          <th width="20%">收費方式</th>
+                        </tr>
+                      </thead>
+                      <tbody>
+                        <tr
+                          v-for="(item, index) in eventData.list"
+                          :key="index"
+                        >
+                          <td>{{ item.event }}</td>
+                          <td>
+                            {{
+                              moment(`${item.start_time}`).format("YYYY/MM/DD")
+                            }}
+                            <br />
+                            ~
+                            <br />
+                            {{
+                              moment(`${item.end_time}`).format("YYYY/MM/DD")
+                            }}
+                          </td>
+                          <td>{{ item.lecturer }}</td>
+                          <td>{{ item.fee_method }}</td>
+                        </tr>
+                      </tbody>
+                    </table>
+                  </div>
+                </v-label>
+              </v-col>
+
+              <v-col cols="12" md="11" class="px-7">
+                <v-label class="d-block">
+                  <p class="d-flex">課程簡介<span class="mark">*</span></p>
+                  <small class="d-block text-gray my-2"
+                    >內容不得少於 100 字元,且不得含有工藝無關之資訊</small
+                  >
+                  <v-textarea
+                    v-model="course.introduction"
+                    rows="5"
+                    variant="outlined"
+                  ></v-textarea>
+                </v-label>
+              </v-col>
+
+              <v-col cols="12" md="6" class="me-auto ms-3 ps-16">
+                <v-label class="d-block">
+                  <p class="d-flex">上傳課程封面<span class="mark">*</span></p>
+
+                  <v-file-input
+                    multiple
+                    v-model="coverImg"
+                    ref="coverImgRef"
+                    label="File input"
+                    variant="outlined"
+                    placeholder="選擇相片上傳"
+                    @change="handleCoverImg"
+                    style="display: none"
+                  ></v-file-input>
+                </v-label>
+
+                <v-btn
+                  @click="fileInputClick('cover')"
+                  color="purple"
+                  class="my-5"
+                  >選擇相片上傳</v-btn
+                >
+
+                <div class="step-01 image-preview">
+                  <img v-if="coverImg" :src="coverImgUrl" alt="上傳圖片預覽" />
+                </div>
+
+                <!-- <v-row class="img-list">
+                  <v-col cols="4" v-for="(item, index) in 6" :key="index">
+                    <div v-if="!portfolioImgList.length" class="item"></div>
+                    <div
+                      v-else
+                      class="item"
+                      :style="{
+                        backgroundImage: `url('${portfolioImgList[index]}')`,
+                      }"
+                    ></div>
+                  </v-col>
+                </v-row> -->
+              </v-col>
+            </v-row>
+          </v-card-text>
+        </v-window-item>
+
+        <v-window-item :value="4">
+          <v-card-text class="mb-7 finish-step">
+            <p>
+              請等待後台人員確認您的課程資訊 <br />
+              待您收到開課完成的 Email <br />
+              就可以前往
+              <router-link to="/user/courses">【我的開課】</router-link>
+              觀看及修改您的
+            </p>
+            <ul>
+              <li>(1) 據點資訊</li>
+              <li>(2) 工藝家履歷</li>
+              <li>(3) 課程清單</li>
+            </ul>
+          </v-card-text>
+        </v-window-item>
+      </v-window>
+
+      <v-divider></v-divider>
+
+      <v-card-actions class="justify-center mt-7">
+        <v-btn
+          v-if="step > 1 && step !== 4"
+          color="gray"
+          variant="outlined"
+          @click="step--"
+          class="px-7 me-2"
+        >
+          上一步
+        </v-btn>
+        <v-spacer></v-spacer>
+        <v-btn
+          v-if="step < 3"
+          color="purple"
+          variant="flat"
+          @click="step++"
+          class="px-7"
+        >
+          下一步
+        </v-btn>
+        <v-btn
+          v-if="step === 3"
+          color="purple"
+          variant="flat"
+          @click="create()"
+          :loading="loading"
+          class="px-7"
+        >
+          創建
+        </v-btn>
+        <v-btn v-if="step === 4" color="purple" variant="outlined" class="me-3">
+          <router-link to="/" class="px-7">回到首頁</router-link>
+        </v-btn>
+        <v-btn v-if="step === 4" color="purple" variant="flat">
+          <router-link to="/user/courses" class="px-7"
+            >前往開課專區</router-link
+          >
+        </v-btn>
+      </v-card-actions>
+    </v-card>
+  </v-container>
+</template>
+
+<style lang="scss" scoped>
+.step-title {
+  text-align: center;
+  h5 {
+    font-size: 28px;
+    font-weight: 500;
+    letter-spacing: 2px;
+  }
+  p {
+    font-size: 16px;
+    font-weight: 400;
+    letter-spacing: 1px;
+    color: #919191;
+  }
+}
+
+.step-01 {
+  &.image-preview {
+    height: 285px;
+  }
+}
+
+.step-02 {
+  &.image-preview {
+    height: 235px;
+  }
+}
+
+.image-preview {
+  img {
+    height: 100%;
+    object-fit: contain;
+  }
+}
+
+.flex-grow-1 {
+  display: none !important;
+}
+
+.img-list {
+  .item {
+    height: 105px;
+    border-radius: 5px;
+    background-color: #ccc; // 預設灰底
+    background-repeat: no-repeat;
+    background-position: center;
+    background-size: cover;
+  }
+}
+
+.sessions-card {
+  border-radius: 30px 5px 5px 30px !important;
+
+  .v-label {
+    p {
+      width: 95px;
+      text-align: end;
+      line-height: 22px;
+      @media (max-width: 600px) {
+        width: 123px;
+      }
+    }
+  }
+}
+
+.date-item {
+  display: flex;
+  margin-bottom: 10px;
+  p {
+    width: 114px;
+    display: flex;
+    align-items: center;
+    justify-content: end;
+    white-space: nowrap;
+    line-height: 22px;
+    @media (max-width: 600px) {
+      width: 125px;
+    }
+  }
+}
+
+.main-table {
+  margin: 50px 0;
+  .table-title {
+    background-color: var(--purple);
+  }
+
+  table {
+    thead {
+      border-bottom: 2px solid var(--purple);
+    }
+    tbody {
+      td {
+        border-bottom: 1px solid var(--purple);
+      }
+    }
+  }
+}
+
+.finish-step {
+  line-height: 50px;
+  font-size: 22px;
+  text-align: center;
+  letter-spacing: 1px;
+}
+
+.type-hint {
+  width: 100%;
+  display: block;
+  position: absolute;
+  bottom: 1px;
+  left: 105px;
+  color: var(--gray);
+}
+
+.week-list {
+  height: 70%;
+  align-items: center;
+  li {
+    margin: 0 5px;
+    button {
+      color: var(--purple);
+      padding: 10px;
+      border-radius: 100px;
+      border: 1px solid var(--purple);
+      transition: all 0.3s;
+      &:hover {
+        color: #fff;
+        background-color: var(--purple);
+      }
+    }
+  }
+}
+
+.time-item {
+  display: flex;
+  align-items: center;
+  p {
+    width: 97px;
+    flex-wrap: nowrap;
+    display: flex;
+    white-space: nowrap;
+  }
+}
+</style>

+ 4 - 23
src/views/SetUpCourses.vue → src/views/Courses/SetUp.vue

@@ -24,7 +24,7 @@ const modules = [Pagination];
       <v-col cols="12" md="6">
         <div class="options">
           <img src="@/assets/img/setup-courses/素材-01.png" alt="" />
-          <router-link to="/">觀看開課教學</router-link>
+          <router-link to="/setup-courses/tutorial">觀看開課教學</router-link>
         </div>
       </v-col>
       <v-col cols="12" md="6" class="mt-16 mt-md-0">
@@ -34,29 +34,10 @@ const modules = [Pagination];
         </div>
         <div v-else class="options">
           <img src="@/assets/img/setup-courses/素材-03.png" alt="" />
-          <router-link to="/">開始創建課程</router-link>
+          <router-link to="/setup-courses/create">開始創建課程</router-link>
         </div>
       </v-col>
     </v-row>
-
-    <!-- 垂直輪播測試 -->
-    <!-- <div style="height: 500px; position: relative">
-      <div class="background-image"></div>
-      <swiper
-        :direction="'vertical'"
-        :pagination="{
-          clickable: true,
-        }"
-        :modules="modules"
-      >
-        <swiper-slide>Slide 1</swiper-slide>
-        <swiper-slide>Slide 2</swiper-slide>
-        <swiper-slide>Slide 3</swiper-slide>
-        左右箭頭
-        <div class="swiper-button-prev" @click.stop="prevEl(item, index)" />
-        <div class="swiper-button-next" @click.stop="nextEl" />
-      </swiper>
-    </div> -->
   </v-container>
 </template>
 
@@ -79,7 +60,7 @@ const modules = [Pagination];
     transition: all 0.3s;
 
     &:hover {
-      box-shadow: 0 0 10px var(--purple);
+      box-shadow: 0 0 8px var(--purple);
     }
 
     @media (max-width: 600px) {
@@ -94,7 +75,7 @@ const modules = [Pagination];
 
 // Swiper
 .background-image {
-  background-image: url("../src/assets/img/一日學徒.png");
+  background-image: url("@/assets/img/default.png");
   width: 100%;
   height: 100%;
   position: absolute;

+ 41 - 0
src/views/Courses/Tutorial.vue

@@ -0,0 +1,41 @@
+<script setup>
+import { ref, reactive } from "vue";
+import Navbar from "@/components/Navbar.vue";
+import CoursesTutorial from "@/components/CoursesTutorial.vue";
+
+const breadcrumbs = reactive([
+  {
+    title: "首頁",
+    disabled: false,
+    href: "/",
+  },
+  {
+    title: "我要開課",
+    disabled: false,
+    href: "/setup-courses",
+  },
+  {
+    title: "開課教學",
+    disabled: true,
+  },
+]);
+</script>
+
+<template>
+  <Navbar />
+
+  <div class="breadcrumbs-item">
+    <v-breadcrumbs :items="breadcrumbs" divider="/"></v-breadcrumbs>
+  </div>
+
+  <CoursesTutorial />
+</template>
+
+<style lang="scss" scoped>
+.breadcrumbs-item {
+  padding: 30px 0 30px 16vw;
+  @media (max-width: 1800px) {
+    padding: 30px 0 30px 5vw;
+  }
+}
+</style>

+ 281 - 115
src/views/Crafts.vue

@@ -5,15 +5,25 @@ import { useMainStore } from "@/stores/store";
 import axios from "axios";
 import Navbar from "@/components/Navbar.vue";
 import PDFViewer from "@/components/PDFViewer.vue";
-import bookList from "@/utils/useBookList";
 import readList from "@/utils/useReadList";
+import craftsPdfList from "@/utils/useCraftsPdf";
 import ArticleCard from "@/components/ArticleCard.vue";
+import CraftsArticle from "@/components/CraftsArticle.vue";
 
 const route = useRoute();
 const router = useRouter();
 const store = useMainStore();
+const routeHash = route.hash; // 網址參數(錨點)
 const bookName = route.params.id; // 網址參數(pdf書名)
-console.log("bookName", bookName);
+
+if (routeHash) {
+  setTimeout(() => {
+    const targetElement = document.querySelector(routeHash);
+    if (targetElement) {
+      targetElement.scrollIntoView({ behavior: "smooth" });
+    }
+  }, 500);
+}
 
 onMounted(() => {
   if (bookName) {
@@ -133,64 +143,13 @@ const tagList = reactive([
   },
 ]);
 
-const articleList = reactive([
-  {
-    title: "法國工藝中心雜誌",
-    depiction: "CMA, artisans of the new economy_Magazin",
-    img: store.getImageUrl("crafts/知識文章-14.png"),
-    url: "https://www.artisanat.fr/magazine/actus",
-  },
-  {
-    title: "法國不同地區的工藝匠結合各地特色的作品",
-    depiction: "8 Skills of French Craftsmen",
-    img: store.getImageUrl("crafts/知識文章-15.png"),
-    url: "https://www.france.fr/zh-Hant/%E6%97%85%E9%81%8A%E7%9B%AE%E7%9A%84%E5%9C%B0/%E6%B8%85%E5%96%AE/ateliers-savoir-faire-francais-%E6%B3%95%E5%9C%8B%E8%83%BD%E5%B7%A5%E5%B7%A7%E5%8C%A0%E7%9A%84-8-%E9%A0%85%E6%8A%80%E8%97%9D",
-  },
-  {
-    title: "從古至今的里昂絲綢工藝演變",
-    depiction: "Approaching Lyon's silk",
-    img: store.getImageUrl("crafts/知識文章-16.png"),
-    url: "https://www.france.fr/zh-Hant/%E9%87%8C%E6%98%82/%E6%B8%85%E5%96%AE/experiences-soie-%E8%B5%B0%E8%BF%91%E9%87%8C%E6%98%82%E7%9A%84%E7%B5%B2%E7%B6%A2",
-  },
-  {
-    title: "義大利威尼斯琉璃工藝",
-    depiction: "Artisanat verrier vénitien",
-    img: store.getImageUrl("crafts/知識文章-17.png"),
-    url: "https://www.sightseeinginitaly.com/fr/le-nord-est-de-litalie/region-venetie/artisanat-de-la-venetie/verre-de-murano-technique-verriere-artisanale/",
-  },
-  {
-    title: "日本北海道小樽玻璃工藝",
-    depiction: "小樽ガラスの特徴|歴史ある有名工芸品の魅力を探る",
-    img: store.getImageUrl("crafts/知識文章-18.png"),
-    url: "https://chiikihyaku-jp.translate.goog/goods/734.html?_x_tr_sl=ja&_x_tr_tl=zh-TW&_x_tr_hl=zh-TW&_x_tr_pto=sc",
-  },
-  {
-    title: "日本漆藝介紹",
-    depiction: "GALLERY JAPAN",
-    img: store.getImageUrl("crafts/知識文章-19.png"),
-    url: "https://www-galleryjapan-com.translate.goog/locale/ja_JP/technique/urushiwork/?_x_tr_sl=ja&_x_tr_tl=zh-TW&_x_tr_hl=zh-TW&_x_tr_pto=sc",
-  },
-  {
-    title: "捷克木偶工藝",
-    depiction: "Puppetry",
-    img: store.getImageUrl("crafts/知識文章-20.png"),
-    url: "https://www.visitczechrepublic.com/en-US/ff6094fa-b99b-4bb5-9643-2f4d2d375710/place/c-unesco-puppetry",
-  },
-  {
-    title: "美國印地安人傳統工藝品介紹",
-    depiction: "Southwestern Arts and Crafts",
-    img: store.getImageUrl("crafts/知識文章-21.png"),
-    url: "https://www.desertusa.com/desert-people/indian-craft.html",
-  },
-]);
-
 // function getPDF(name) {
 //   return `https://ntcri.org/pdf/${name}.pdf`;
 // }
 
 let fileName = ref(""); // PDFViewer Props
-const read = ref(null);
-const viewer = ref(null);
+const readRef = ref(null);
+const viewerRef = ref(null);
 
 function updatePDF(name) {
   // 更新 pdf 後移除網址參數
@@ -198,7 +157,7 @@ function updatePDF(name) {
   fileName.value = name;
   if (!store.isMobile) {
     setTimeout(() => {
-      viewer.value.scrollIntoView({ behavior: "smooth", block: "start" });
+      viewerRef.value.scrollIntoView({ behavior: "smooth", block: "start" });
     }, 300);
   }
 }
@@ -209,30 +168,58 @@ function isAnchorLink(url) {
   return url.startsWith("#");
 }
 
-const testData = [
-  {
-    title: "博碩士生研撰臺灣工藝相關研究論文獎助申請,申請至6月30日止",
-    date: "2023.06.07",
-    category: "獎助申請",
-    introduction:
-      "國立臺灣工藝硏究發展中心溈提升臺灣工藝文化之主體性及營造工藝學領域發展環境,鼓勵國內大學校院培養工藝研究人才,發表研究成果,促使工藝升級、文化深耕,並建構相關資源支持體系,獎助國內各大學院校撰寫有闕工藝相關硏究學位論文之在學博士班、碩士班學生,最高#新臺幣12萬元",
-  },
-  {
-    title:
-      "國立臺灣工藝研究發展中心獎助博碩士生研撰臺灣工藝相關研究論文作業要點",
-    date: "2023.08.26",
-    category: "工藝政策",
-    introduction:
-      "國立臺灣工藝研究發展中心為提升臺灣工藝文化之主體性及營造工藝學領域發展環境,鼓勵國內大學校院培養工藝研究人才,發表研究成果,促使工藝升級、文化深耕,並建構相關資源支持體系,特訂定本要點,獎助內容如下:",
-  },
-];
+let read = reactive({
+  list: [],
+});
+
+// 線上閱讀
+(async () => {
+  try {
+    const response = await axios.get(
+      "https://cmm.ai:8088/api/get_article?group_sort=線上閱讀"
+    );
+    read.list = response.data.articles;
+    console.log("線上閱讀", read.list);
+  } catch (error) {
+    console.error(error);
+  }
+})();
+
+let book = reactive({
+  list: [],
+});
+
+// 工藝書單
+(async () => {
+  try {
+    const response = await axios.get(
+      "https://cmm.ai:8088/api/get_article?group_sort=工藝書單"
+    );
+    book.list = response.data.articles;
+    console.log("工藝書單", response.data.articles);
+  } catch (error) {
+    console.error(error);
+  }
+})();
+
+// 正則表達式移除 style 屬性
+const filteredContent = (content) => {
+  const regex = /style="text-align: center;"/g;
+  return content.replace(regex, "");
+};
+
+function handlePdfUrl(pdf) {
+  let json = pdf.replace(/'/g, '"');
+  let file = JSON.parse(json);
+  let url = file.file1;
+  return url;
+}
 </script>
 
 <template>
   <Navbar />
   <div class="bg-img">
     <v-container class="position-relative">
-      <!-- <img src="@/assets/img/crafts/background.png" alt="" class="bg-img" /> -->
       <div class="article-content">
         <v-breadcrumbs
           :items="breadcrumbs"
@@ -261,9 +248,9 @@ const testData = [
           </v-col>
         </v-row>
 
-        <h2 id="articleList">國際專欄</h2>
+        <h2 id="articleList" class="mb-16 pb-5">國際專欄</h2>
 
-        <div class="search pt-5 my-10 me-sm-16" ref="searchLocation">
+        <!-- <div class="search pt-5 my-10 me-sm-16" ref="searchLocation">
           <span>
             <input
               v-model="searchInput"
@@ -282,50 +269,68 @@ const testData = [
             <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
             沒有符合搜尋條件的項目
           </div>
-        </div>
+        </div> -->
 
-        <v-row class="list">
-          <v-col
-            cols="12"
-            sm="6"
-            v-for="(item, index) in articleList"
-            :key="index"
-          >
-            <a :href="item.url" target="_blank">
-              <img :src="item.img" alt="" />
-            </a>
-            <section>
-              <p>{{ item.depiction }}</p>
-              <h3>{{ item.title }}</h3>
-            </section>
-          </v-col>
-        </v-row>
+        <CraftsArticle />
 
-        <h2 ref="read" id="readList">線上閱讀</h2>
+        <h2 ref="readRef" id="readList">線上閱讀</h2>
 
         <v-row class="justify-center mt-16 read-list">
           <v-col
             cols="12"
             sm="6"
             md="4"
-            v-for="(item, index) in readList"
+            v-for="(item, index) in read.list"
             :key="index"
             class="d-flex flex-column align-center px-10"
           >
             <a
               v-if="store.isMobile"
-              :href="store.getPDF(item.fileName)"
+              :href="handlePdfUrl(item.files)"
               target="_blank"
             >
-              <img :src="item.img" alt="" />
+              <v-img
+                class="mx-auto cover-img"
+                :lazy-src="`https://ntcri.org/${item.cover_img}`"
+                cover
+                :src="`https://ntcri.org/${item.cover_img}`"
+              >
+                <template v-slot:placeholder>
+                  <div class="d-flex align-center justify-center fill-height">
+                    <v-progress-circular
+                      color="grey-lighten-4"
+                      indeterminate
+                    ></v-progress-circular>
+                  </div>
+                </template>
+              </v-img>
+              <!-- <img :src="item.img" alt="" /> -->
             </a>
 
-            <img
+            <!-- <img
               v-else
               :src="item.img"
               alt=""
               @click="updatePDF(item.fileName)"
-            />
+            /> -->
+
+            <v-img
+              v-else
+              class="mx-auto cover-img"
+              :lazy-src="`https://ntcri.org/${item.cover_img}`"
+              cover
+              :src="`https://ntcri.org/${item.cover_img}`"
+              @click="updatePDF(item.files)"
+            >
+              <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>
 
             <!-- <a :href="item.url" target="_blank">
               <img :src="item.img" alt="" />
@@ -337,13 +342,13 @@ const testData = [
           </v-col>
         </v-row>
 
-        <div ref="viewer">
+        <div ref="viewerRef">
           <PDFViewer :file="fileName" />
         </div>
 
-        <h2 id="bookList">工藝書單</h2>
+        <h2 id="bookList" class="mb-16">工藝書單</h2>
 
-        <div class="search pt-5 my-10 me-sm-16" ref="searchLocation ">
+        <!-- <div class="search pt-5 my-10 me-sm-16" ref="searchLocation ">
           <span>
             <input
               v-model="searchInput"
@@ -362,24 +367,26 @@ const testData = [
             <v-icon color="primary" icon="mdi-alert" class="me-2"></v-icon>
             沒有符合搜尋條件的項目
           </div>
-        </div>
+        </div> -->
 
         <v-row class="justify-center book-list">
           <v-col
             cols="12"
             sm="10"
             md="6"
-            v-for="(item, index) in bookList"
+            v-for="(item, index) in book.list"
             :key="index"
             class="position-relative mt-10 mb-16 pb-16"
           >
-            <a :href="$router.resolve(`/article-detail/book/${index}`).href">
+            <a
+              :href="$router.resolve(`/article-detail/${item.article_id}`).href"
+            >
               <div class="overflow-hidden">
                 <v-img
                   class="mx-auto cover-img"
-                  :lazy-src="item.img"
+                  :lazy-src="`https://ntcri.org/${item.cover_img}`"
                   cover
-                  :src="item.img"
+                  :src="`https://ntcri.org/${item.cover_img}`"
                 >
                   <template v-slot:placeholder>
                     <div class="d-flex align-center justify-center fill-height">
@@ -397,8 +404,8 @@ const testData = [
               <img :src="item.img" alt="" />
             </a> -->
             <section class="info">
-              <p v-html="item.content"></p>
-              <span>{{ item.category }}</span>
+              <p v-html="filteredContent(item.content)"></p>
+              <span>{{ JSON.parse(item.tags)[0] }}</span>
             </section>
           </v-col>
         </v-row>
@@ -437,6 +444,37 @@ const testData = [
               </li>
             </ul>
           </div>
+
+          <div class="pdf-list">
+            <div class="side-title">
+              <h5>研究論文</h5>
+              <h5>實務報告</h5>
+            </div>
+            <ul>
+              <li v-for="(item, index) in craftsPdfList" :key="index">
+                <h3>{{ item.title }}</h3>
+                <h4>{{ item.en_title }}</h4>
+                <p>{{ item.author }} {{ item.en_author }}</p>
+                <p>
+                  關鍵字:{{ item.keywords }} <br />
+                  keywords:{{ item.en_keywords }}
+                </p>
+                <a
+                  :href="`https://ntcri.org/pdf/crafts/${item.fileName}.pdf`"
+                  download
+                >
+                  <span>
+                    <v-icon
+                      icon="mdi-download"
+                      color="gray"
+                      class="me-2 pt-1"
+                    ></v-icon>
+                    全文下載
+                  </span>
+                </a>
+              </li>
+            </ul>
+          </div>
         </div>
 
         <h2 id="thesisGrant">碩博士論文補助</h2>
@@ -478,7 +516,6 @@ h2 {
   }
 }
 
-.list,
 .read-list,
 .book-list {
   p {
@@ -493,7 +530,7 @@ h2 {
 }
 
 .read-list {
-  img {
+  .v-img {
     width: 100%;
     height: 450px;
     object-fit: cover;
@@ -550,14 +587,140 @@ h2 {
   }
 }
 
+.pdf-list {
+  display: flex;
+  margin: 100px 0;
+  .side-title {
+    padding: 70px 0 70px 10px;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    border-radius: 5px;
+    background-color: var(--purple);
+    h5 {
+      padding: 20px 10px;
+      position: relative;
+      font-size: 26px;
+      font-weight: 400;
+      writing-mode: vertical-rl; // 垂直
+      color: var(--purple);
+      letter-spacing: 2px;
+      background-color: #fff;
+
+      @media (max-width: 600px) {
+        font-size: 20px;
+      }
+
+      &::after,
+      &::before {
+        content: "";
+        display: block;
+        height: 15px;
+        width: 105%;
+        background: #fff;
+        position: absolute;
+      }
+
+      &::after {
+        top: -7px;
+        right: -4px;
+        transform: rotate(-15deg);
+
+        @media (max-width: 1200px) {
+          top: -8px;
+          right: -3px;
+        }
+      }
+      &::before {
+        bottom: -7px;
+        right: -4px;
+        transform: rotate(15deg);
+
+        @media (max-width: 1200px) {
+          bottom: -8px;
+          right: -3px;
+        }
+      }
+    }
+  }
+  ul {
+    background-color: #fff;
+    padding: 50px 50px 0 50px;
+
+    @media (max-width: 600px) {
+      padding: 25px 25px 0 25px;
+    }
+
+    li {
+      margin-bottom: 25px;
+      padding-bottom: 25px;
+      border-bottom: 1px dashed #ccc;
+
+      &:last-child {
+        margin-bottom: 0;
+        // padding-bottom: 0;
+      }
+      h3 {
+        font-size: 22px;
+        line-height: 30px;
+        @media (max-width: 600px) {
+          font-size: 18px;
+        }
+      }
+      h4 {
+        margin: 15px 0;
+        font-size: 18px;
+        line-height: 26px;
+        color: var(--gray);
+        @media (max-width: 600px) {
+          font-size: 16px;
+        }
+      }
+      h3,
+      h4 {
+        font-weight: 500;
+      }
+      h3,
+      h4,
+      p {
+        letter-spacing: 1px;
+      }
+      p {
+        font-size: 14px;
+        color: var(--gray);
+      }
+      a {
+        display: inline-block;
+        padding: 5px 10px;
+        margin-top: 20px;
+        font-size: 14px;
+        border-radius: 5px;
+        border: 2px solid var(--gray);
+        transition: all 0.3s;
+
+        &:hover {
+          opacity: 0.7;
+        }
+
+        span {
+          display: flex;
+          align-items: center;
+        }
+      }
+    }
+  }
+}
+
 .journal-content {
   margin-top: 80px;
   p {
     line-height: 28px;
   }
   .list {
+    letter-spacing: 1px;
     .title {
       margin-top: 100px;
+      padding-bottom: 20px;
       font-size: 20px;
       border-bottom: 2px solid var(--purple);
     }
@@ -569,11 +732,14 @@ h2 {
     }
 
     ul {
+      margin-top: 20px;
       li {
-        margin-top: 5px;
-        &:first-child {
-          border-bottom: 2px dashed var(--purple);
-        }
+        margin-top: 10px;
+        padding-bottom: 10px;
+        border-bottom: 2px dashed #dddddd;
+        // &:first-child {
+        //   border-bottom: 2px dashed #ccc;
+        // }
         p {
           font-weight: 400;
         }

+ 471 - 109
src/views/Home.vue

@@ -1,12 +1,28 @@
 <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 moment from "moment";
 import Map from "@/components/Map.vue";
 import Navbar from "@/components/Navbar.vue";
+import HomeList from "@/components/HomeList.vue";
+import TermsList from "@/components/TermsList.vue";
+import CourseCard from "@/components/CourseCard.vue";
+import CoursesTutorial from "@/components/CoursesTutorial.vue";
+import CraftsArticle from "@/components/CraftsArticle.vue";
 
 const store = useMainStore();
+const { t } = useI18n();
+
+let loading = ref(true);
+
+onMounted(() => {
+  setTimeout(() => {
+    loading.value = false;
+  }, 300);
+});
+
 let pageNum = ref(1); // 頁數(預設第一頁)
 let pageAmount = ref(4); // 每頁顯示筆數
 let totalPages = ref(1); // 總頁數
@@ -24,8 +40,8 @@ let assignState = ref(false);
 let assignLocationId = ref(null);
 
 async function getClass() {
-  let url = `https://cmm.ai:8088/api/get_class_name?page_num=${pageNum.value}&page_amount=${pageAmount.value}`;
-  let assignUrl = `https://cmm.ai:8088/api/get_class_name?page_num=${pageNum.value}&page_amount=${pageAmount.value}&location_id=${assignLocationId.value}`;
+  let url = `https://cmm.ai:8088/api/get_class_name?is_check=1&page_num=${pageNum.value}&page_amount=${pageAmount.value}`;
+  let assignUrl = `https://cmm.ai:8088/api/get_class_name?is_check=1&page_num=${pageNum.value}&page_amount=${pageAmount.value}&location_id=${assignLocationId.value}`;
 
   try {
     const response = await axios.get(assignState.value ? assignUrl : url);
@@ -55,6 +71,87 @@ const getClassList = async (locationId) => {
   }
 };
 
+let recommend = reactive({
+  list: [],
+});
+
+// 推薦課程
+(async () => {
+  try {
+    const response = await axios.get(
+      "https://cmm.ai:8088/api/get_class_name?recommend=1"
+    );
+    console.log("推薦課程", response);
+    recommend.list = response.data.classes;
+    console.log(" recommend.list", recommend.list);
+  } catch (error) {
+    console.error(error);
+  }
+})();
+
+let panel = ref([]);
+
+const tagList = reactive([
+  {
+    title: "crafts.title_1",
+    url: "/crafts#articleList",
+  },
+  {
+    title: "crafts.title_2",
+    url: "/crafts#readList",
+  },
+  {
+    title: "crafts.title_3",
+    url: "/crafts#bookList",
+  },
+  {
+    title: "crafts.title_4",
+    url: "/crafts#journal",
+  },
+  {
+    title: "crafts.title_5",
+    url: "/crafts#thesisGrant",
+  },
+]);
+
+let news = reactive({
+  list: [],
+});
+
+// 重要訊息
+(async () => {
+  // 取得最新兩篇
+  try {
+    const response = await axios.get(
+      "https://cmm.ai:8088/api/get_news?page_num=1&page_amount=2"
+    );
+    news.list = response.data.news;
+    console.log("重要訊息", news.list);
+  } catch (error) {
+    console.error(error);
+  }
+})();
+
+let assignTag = ref("news");
+
+// watch(assignTag, (val) => {
+//   if (val === "all") {
+//     isInternal.value = false;
+//   } else {
+//     isInternal.value = true;
+//   }
+//   pageNum.value = 1;
+//   getClass();
+// });
+
+function selectTag(btn) {
+  if (btn === "news") {
+    assignTag.value = "news";
+  } else {
+    assignTag.value = "exhibit";
+  }
+}
+
 // 使用 ref 定義底部彈出視窗的狀態
 // const isBottomSheetOpen = ref(false);
 
@@ -92,16 +189,27 @@ const getClassList = async (locationId) => {
 </script>
 
 <template>
+  <Transition>
+    <div class="loading-item" v-if="loading">
+      <v-progress-circular
+        color="grey-lighten-4"
+        indeterminate
+      ></v-progress-circular>
+    </div>
+  </Transition>
+
   <div class="banner">
-    <img src="@/assets/img/home/banner.png" alt="" class="cover" />
+    <img src="@/assets/img/home/banner.webp" alt="" class="cover" />
     <img src="@/assets/img/home/logo.png" alt="" class="logo" />
   </div>
   <Navbar />
-
-  <v-container class="px-md-0">
+  <v-container class="px-md-0 pb-16 mb-16">
     <section class="text-center intro">
-      <h2 class="title">臺灣工藝學校</h2>
+      <h2 class="title">{{ t("home.title_1") }}</h2>
       <p class="my-10">
+        {{ t("home.content") }}
+      </p>
+      <!-- <p class="my-10">
         以佈局具國際視野之工藝學習共享平台為目標,藉由「工藝學校」的主體概念,推動臺灣工藝學校全球學習平台,以共享、友善、全人、全民的終身工藝手作平台進行人才、課程、知識、教材之工藝資源嫁接媒合與內容設計,以在地、就近、線上、線下等多元方式提供不同型態之學習體驗內容及選擇。
       </p>
       <p>
@@ -113,16 +221,81 @@ const getClassList = async (locationId) => {
         integrate craft resources such as talents, courses, knowledge and
         teaching materials, and provide different types of learning experiences
         in local ways, both online and offline.
-      </p>
+      </p> -->
     </section>
 
-    <h2 class="mb-10 title">工藝學群</h2>
+    <v-carousel
+      cycle
+      height="400"
+      hide-delimiters
+      hide-delimiter-background
+      show-arrows="hover"
+    >
+      <v-carousel-item>
+        <div class="d-flex h-100 align-center">
+          <img src="@/assets/img/home/carousel-01.jpg" alt="" class="w-100" />
+        </div>
+      </v-carousel-item>
+    </v-carousel>
+
+    <div class="news-content">
+      <div class="d-flex tab-btn mb-16">
+        <v-btn
+          variant="text"
+          @click="selectTag('news')"
+          :class="{ active: assignTag === 'news' }"
+        >
+          重要訊息
+        </v-btn>
+        <v-btn
+          variant="text"
+          @click="selectTag('exhibit')"
+          :class="{ active: assignTag === 'exhibit' }"
+        >
+          當期展覽
+        </v-btn>
+      </div>
+     <div class="d-flex justify-end">
+      <router-link to="/news">觀看更多訊息 >></router-link>
+     </div>
+      <ul>
+        <li v-for="(item, index) in news.list" :key="index" class="mb-16 list">
+          <HomeList :data="item" />
+          <!-- <section class="d-flex">
+            <p class="category mb-5">
+              <span></span>
+              {{ item.category }}
+            </p>
+            <p class="ms-5">
+              {{ moment(`${item.create_time}`).format("YYYY-MM-DD") }}
+            </p>
+          </section>
+          <v-card
+            variant="outlined"
+            class="d-flex flex-md-row flex-column align-center pa-5"
+          >
+            <v-row class="align-center">
+              <v-col cols="12">
+                <router-link :to="`/news/${item.news_id}`" class="cover-img">
+                  <section class="d-flex flex-column pa-3">
+                    <h3>{{ item.title }}</h3>
+                    <p v-html="item.content"></p>
+                  </section>
+                </router-link>
+              </v-col>
+            </v-row>
+          </v-card> -->
+        </li>
+      </ul>
+    </div>
+
+    <h2 class="my-10 title">{{ t("home.title_2") }}</h2>
     <v-row>
       <v-col cols="12" sm="6" md="4">
         <router-link to="/" class="img-info">
           <img src="@/assets/img/home/首頁元素-12.png" alt="" />
           <section>
-            <p>線上工藝</p>
+            <p>{{ t("college_group_1") }}</p>
           </section>
         </router-link>
       </v-col>
@@ -130,7 +303,7 @@ const getClassList = async (locationId) => {
         <router-link to="/college-group/craft" class="img-info">
           <img src="@/assets/img/home/首頁元素-11.png" alt="" />
           <section>
-            <p>技藝工藝</p>
+            <p>{{ t("college_group_2") }}</p>
           </section>
         </router-link>
       </v-col>
@@ -138,7 +311,7 @@ const getClassList = async (locationId) => {
         <router-link to="/college-group/future" class="img-info">
           <img src="@/assets/img/home/首頁元素-06.png" alt="" />
           <section>
-            <p>未來工藝</p>
+            <p>{{ t("college_group_3") }}</p>
           </section>
         </router-link>
       </v-col>
@@ -146,100 +319,30 @@ const getClassList = async (locationId) => {
         <router-link to="/college-group/cross" class="img-info">
           <img src="@/assets/img/home/首頁元素-09.png" alt="" />
           <section>
-            <p>跨域增能</p>
+            <p>{{ t("college_group_4") }}</p>
           </section>
         </router-link>
       </v-col>
       <v-col cols="12" sm="6" md="4">
-        <router-link to="/college-group/repair" class="img-info">
+        <router-link to="/college-group/craft-for-all" class="img-info">
           <img src="@/assets/img/home/臺灣綠工藝希望工程.png" alt="" />
           <section>
-            <p>希望工程</p>
+            <p>{{ t("college_group_5") }}</p>
           </section>
         </router-link>
       </v-col>
       <v-col cols="12" sm="6" md="4">
-        <router-link to="/" class="img-info">
+        <router-link to="/college-group/life" class="img-info">
           <img src="@/assets/img/home/首頁元素-07.png" alt="" />
           <section>
-            <p>生活工藝</p>
+            <p>{{ t("college_group_6") }}</p>
           </section>
         </router-link>
       </v-col>
     </v-row>
 
-    <!-- <v-row>
-      <v-col cols="4">
-        <router-link to="/" class="img-info">
-          <img src="@/assets/img/home/首頁元素-12.png" alt="" />
-          <section>
-            <p>線上工藝</p>
-          </section>
-        </router-link>
-      </v-col>
-      <v-col cols="4">
-        <router-link to="/college-group/craft" class="img-info">
-          <img src="@/assets/img/home/首頁元素-11.png" alt="" />
-          <section>
-            <p>技藝工藝</p>
-          </section>
-        </router-link>
-      </v-col>
-      <v-col cols="4">
-        <router-link to="/college-group/repair" class="img-info">
-          <img src="@/assets/img/home/首頁元素-10.png" alt="" />
-          <section>
-            <p>修復工藝</p>
-          </section>
-        </router-link>
-      </v-col>
-      <v-col cols="4">
-        <router-link to="/college-group/cross" class="img-info">
-          <img src="@/assets/img/home/首頁元素-09.png" alt="" />
-          <section>
-            <p>跨域增能</p>
-          </section>
-        </router-link>
-      </v-col>
-      <v-col cols="4" class="d-flex justify-center align-center">
-        <h3 class="title">八大工藝學群</h3>
-      </v-col>
-      <v-col cols="4">
-        <router-link to="/college-group/generation" class="img-info">
-          <img src="@/assets/img/home/首頁元素-05.png" alt="" />
-          <section>
-            <p>世代工藝</p>
-          </section>
-        </router-link>
-      </v-col>
-      <v-col cols="4">
-        <router-link to="/college-group/future" class="img-info">
-          <img src="@/assets/img/home/首頁元素-06.png" alt="" />
-          <section>
-            <p>未來工藝</p>
-          </section>
-        </router-link>
-      </v-col>
-      <v-col cols="4">
-        <router-link to="/" class="img-info">
-          <img src="@/assets/img/home/首頁元素-07.png" alt="" />
-          <section>
-            <p>生活工藝</p>
-          </section>
-        </router-link>
-      </v-col>
-      <v-col cols="4">
-        <router-link to="/college-group/teenager" class="img-info">
-          <img src="@/assets/img/home/首頁元素-08.png" alt="" />
-          <section>
-            <p>青年工藝</p>
-          </section>
-        </router-link>
-      </v-col>
-    </v-row> -->
-
     <div class="map-block">
-      <h3 class="mb-10 title">全台工藝地圖</h3>
+      <h3 class="mb-10 title">{{ t("home.title_3") }}</h3>
       <div class="v-row">
         <v-col md="8" cols="12">
           <div class="map">
@@ -254,26 +357,12 @@ const getClassList = async (locationId) => {
                   :to="`/course-detail/${item.class_name_id}`"
                   class="link"
                 >
-                  <!-- <v-img
-                    class="mx-auto cover-img"
-                    :lazy-src="
-                      item.group_sort === 'pinkoi'
-                        ? item.cover_img
-                        : `https://ntcri.org/${item.cover_img}`
-                    "
-                    height="200px"
-                    cover
-                    :src="
-                      item.group_sort === 'pinkoi'
-                        ? item.cover_img
-                        : `https://ntcri.org/${item.cover_img}`
-                    "
-                  > -->
-
                   <v-img
                     :lazy-src="
-                      item.group_sort === 'pinkoi'
+                      item.is_inner === 0
                         ? item.cover_img
+                        : item.special_class_list_name === 'one_day_class'
+                        ? store.getImageUrl('default.png')
                         : `https://ntcri.org/${item.cover_img}`
                     "
                     cover
@@ -281,7 +370,7 @@ const getClassList = async (locationId) => {
                       item.is_inner === 0
                         ? item.cover_img
                         : item.special_class_list_name === 'one_day_class'
-                        ? 'src/assets/img/一日學徒.png'
+                        ? store.getImageUrl('default.png')
                         : `https://ntcri.org/${item.cover_img}`
                     "
                   >
@@ -296,8 +385,6 @@ const getClassList = async (locationId) => {
                       </div>
                     </template>
                   </v-img>
-
-                  <!-- <img :src="item.cover_img"  :lazy-src="item.cover_img" alt="" /> -->
                 </router-link>
                 <section>
                   <h2>{{ item.name }}</h2>
@@ -315,7 +402,7 @@ const getClassList = async (locationId) => {
                       icon="mdi-map-marker"
                       class="me-2 pt-1"
                     ></v-icon>
-                    <p>{{ item.school }}</p>
+                    <p>{{ item.location_name }}</p>
                   </div>
                 </section>
               </div>
@@ -334,6 +421,26 @@ const getClassList = async (locationId) => {
       </div> -->
     </div>
 
+    <h2 class="mb-10 title">{{ t("home.title_4") }}</h2>
+    <v-row>
+      <v-col
+        cols="12"
+        sm="6"
+        lg="4"
+        v-for="(item, index) in recommend.list"
+        :key="index"
+        class="pa-5"
+      >
+        <CourseCard :data="item" />
+      </v-col>
+      <v-col cols="12">
+        <router-link to="/course-list" class="course-link">
+          <img src="@/assets/img/course/探索課程素材-15.png" alt="" />
+          <p>{{ t("see_more") }}</p>
+        </router-link>
+      </v-col>
+    </v-row>
+
     <!-- 手機版底部視窗 -->
     <!-- <div
       class="bottom-sheet"
@@ -348,6 +455,126 @@ const getClassList = async (locationId) => {
       </div>
     </div> -->
   </v-container>
+
+  <v-container fluid class="pa-0 pt-sm-16 tutorial-block">
+    <h2 class="mb-10 title">{{ t("tutorial.title") }}</h2>
+    <CoursesTutorial />
+  </v-container>
+
+  <v-container class="px-md-0 my-16">
+    <h2 class="mb-10 title">{{ t("home.title_6") }}</h2>
+    <v-expansion-panels v-model="panel" multiple>
+      <v-expansion-panel elevation="0">
+        <v-expansion-panel-title>{{
+          t("home.faq.q_1")
+        }}</v-expansion-panel-title>
+        <v-expansion-panel-text>
+          <ul>
+            <li>
+              <h4><span></span> {{ t("home.faq.a_1_1") }}</h4>
+              <p>
+                {{ t("home.faq.a_1_2") }}
+              </p>
+            </li>
+            <li>
+              <h4><span></span> {{ t("home.faq.a_2_1") }}</h4>
+              <p>
+                {{ t("home.faq.a_2_2") }}
+              </p>
+            </li>
+            <li>
+              <h4><span></span> {{ t("home.faq.a_3_1") }}</h4>
+              <p>
+                {{ t("home.faq.a_3_2") }}
+              </p>
+            </li>
+            <li>
+              <h4><span></span> {{ t("home.faq.a_4_1") }}</h4>
+              <p>
+                {{ t("home.faq.a_4_2") }}
+              </p>
+            </li>
+          </ul>
+        </v-expansion-panel-text>
+      </v-expansion-panel>
+
+      <v-expansion-panel elevation="0">
+        <v-expansion-panel-title>
+          {{ t("terms.terms_of_service") }}</v-expansion-panel-title
+        >
+        <v-expansion-panel-text>
+          <TermsList />
+        </v-expansion-panel-text>
+      </v-expansion-panel>
+
+      <v-expansion-panel elevation="0">
+        <v-expansion-panel-title>{{
+          t("home.faq.q_2")
+        }}</v-expansion-panel-title>
+        <v-expansion-panel-text>
+          <p v-html="$t('home.faq.a_2')"></p>
+        </v-expansion-panel-text>
+      </v-expansion-panel>
+
+      <v-expansion-panel elevation="0">
+        <v-expansion-panel-title>{{
+          t("home.faq.q_3")
+        }}</v-expansion-panel-title>
+        <v-expansion-panel-text>
+          {{ t("home.faq.a_3") }}
+        </v-expansion-panel-text>
+      </v-expansion-panel>
+
+      <v-expansion-panel elevation="0">
+        <v-expansion-panel-title>{{
+          t("home.faq.q_4")
+        }}</v-expansion-panel-title>
+        <v-expansion-panel-text>
+          {{ t("home.faq.a_4") }}
+        </v-expansion-panel-text>
+      </v-expansion-panel>
+
+      <v-expansion-panel elevation="0">
+        <v-expansion-panel-title>{{
+          t("home.faq.q_5")
+        }}</v-expansion-panel-title>
+        <v-expansion-panel-text>
+          {{ t("home.faq.a_5") }}
+        </v-expansion-panel-text>
+      </v-expansion-panel>
+    </v-expansion-panels>
+
+    <h2 class="mb-10 mt-16 title">{{ t("crafts.title") }}</h2>
+
+    <v-row class="px-10 mt-16 mb-10 tag-btn">
+      <v-col v-for="(item, index) in tagList" :key="index" class="ma-2 item">
+        <a :href="item.url" target="_blank" class="py-3">
+          <!-- 網址 -->
+          {{ t(`${item.title}`) }}
+        </a>
+      </v-col>
+    </v-row>
+
+    <CraftsArticle />
+
+    <router-link to="/crafts" class="crafts-link">
+      <img src="@/assets/img/course/探索課程素材-15.png" alt="" />
+      <p>{{ t("crafts.see_more") }}</p>
+    </router-link>
+
+    <div class="mt-16">
+      <img
+        class="d-none d-md-block"
+        src="@/assets/img/course/banner.png"
+        alt=""
+      />
+      <img
+        class="d-block d-md-none"
+        src="@/assets/img/course/banner-mb.png"
+        alt=""
+      />
+    </div>
+  </v-container>
 </template>
 
 <style lang="scss" scoped>
@@ -412,6 +639,10 @@ const getClassList = async (locationId) => {
   }
 }
 
+.news-content {
+  margin: 100px auto;
+}
+
 .map-block {
   margin: 100px auto;
   .map {
@@ -494,6 +725,137 @@ const getClassList = async (locationId) => {
   }
 }
 
+.course-link {
+  margin-top: 15px;
+  color: #000;
+  background-color: var(--sub-color);
+}
+
+.crafts-link {
+  margin: 100px 0;
+  color: #fff;
+  background-color: var(--purple);
+  img {
+    filter: invert(88%) sepia(100%) saturate(0%) hue-rotate(151deg)
+      brightness(103%) contrast(103%);
+  }
+}
+
+.crafts-link,
+.course-link {
+  padding: 15px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 10px;
+  box-shadow: 2px 2px 10px #aaaaaa;
+  &:hover {
+    img {
+      transform: rotateZ(10deg);
+    }
+  }
+  img {
+    max-width: 85px;
+    padding-right: 20px;
+    transition: all 0.3s;
+  }
+  p {
+    font-size: 20px;
+    letter-spacing: 2px;
+  }
+}
+
+.tutorial-block {
+  max-width: 100% !important;
+}
+
+.loading-item {
+  position: sticky;
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 100vh;
+  z-index: 3000;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #fff;
+}
+
+// Vuetify Expansion 樣式
+.v-expansion-panel {
+  margin-bottom: 30px;
+  line-height: 30px;
+  border-radius: 10px !important;
+  li:first-child {
+    h4 {
+      margin-top: 0;
+    }
+  }
+  h4 {
+    display: flex;
+    align-items: center;
+    margin-top: 20px;
+    font-size: 18px;
+    font-weight: 500;
+    span {
+      display: block;
+      width: 8px;
+      height: 8px;
+      margin-top: 5px;
+      margin-right: 10px;
+      border-radius: 10px;
+      background-color: var(--blue);
+    }
+  }
+}
+
+.v-expansion-panel-title {
+  font-size: 16px;
+  padding: 20px 30px;
+  border: 1px solid var(--blue);
+}
+
+.v-expansion-panel-text {
+  padding-top: 8px;
+}
+
+.v-expansion-panel-title__overlay {
+  opacity: 0 !important;
+}
+
+.v-expansion-panels:not(.v-expansion-panels--variant-accordion)
+  > :first-child:not(:last-child):not(.v-expansion-panel--active):not(
+    .v-expansion-panel--before-active
+  ),
+.v-expansion-panels:not(.v-expansion-panels--variant-accordion)
+  > :last-child:not(:first-child):not(.v-expansion-panel--active):not(
+    .v-expansion-panel--after-active
+  ),
+.v-expansion-panels:not(.v-expansion-panels--variant-accordion)
+  > :not(:first-child):not(:last-child):not(.v-expansion-panel--active):not(
+    .v-expansion-panel--after-active
+  ),
+.v-expansion-panels:not(.v-expansion-panels--variant-accordion)
+  > :not(:first-child):not(:last-child):not(.v-expansion-panel--active):not(
+    .v-expansion-panel--before-active
+  ),
+.v-expansion-panel--active > .v-expansion-panel-title {
+  border-top-left-radius: 10px !important;
+  border-top-right-radius: 10px !important;
+  border-bottom-left-radius: 10px !important;
+  border-bottom-right-radius: 10px !important;
+}
+.v-expansion-panel:not(:first-child)::after {
+  border-top-style: none;
+}
+
+.v-expansion-panel-title--active:hover > .v-expansion-panel-title__overlay,
+.v-expansion-panel-title[aria-haspopup="menu"][aria-expanded="true"]:hover
+  > .v-expansion-panel-title__overlay {
+  opacity: 0;
+}
+
 /* 底部彈出視窗的樣式 */
 .bottom-sheet {
   display: none;

+ 52 - 30
src/views/Login.vue

@@ -222,6 +222,13 @@ const expirationDate = new Date(expirationTime * 1000); // JS 時間戳以毫秒
 const formattedExpiration = expirationDate.toLocaleString();
 
 console.log("JWT 過期時間:", formattedExpiration);
+
+let showLogin = ref(false);
+
+function toggleLoginInput() {
+  console.log("showLogin");
+  showLogin.value = !showLogin.value;
+}
 </script>
 
 <template>
@@ -265,36 +272,47 @@ console.log("JWT 過期時間:", formattedExpiration);
             data-logo_alignment="left"
           ></div>
 
-          <p class="text-center my-7">使用平台帳號登入</p>
-
-          <v-form @submit.prevent>
-            <v-text-field
-              v-model="userLogin.username"
-              label="信箱"
-              :rules="[(v) => !!v || '請輸入您的信箱']"
-              prepend-inner-icon="mdi-account"
-              variant="solo"
-              density="compact"
-            ></v-text-field>
-            <v-text-field
-              v-model="userLogin.password"
-              label="密碼"
-              :rules="[(v) => !!v || '請輸入您的密碼']"
-              prepend-inner-icon="mdi-lock"
-              variant="solo"
-              density="compact"
-              type="password"
-            ></v-text-field>
-            <v-btn
-              type="submit"
-              size="large"
-              color="primary"
-              block
-              @click="login()"
-              :loading="isLoading"
-              >登入</v-btn
-            >
-          </v-form>
+          <!-- <p class="text-center my-7">使用平台帳號登入</p> -->
+
+          <v-btn
+            @click="toggleLoginInput()"
+            class="login-btn"
+            variant="outlined"
+            color="primary"
+          >
+            使用平台帳號登入
+          </v-btn>
+          <Transition>
+            <v-form v-if="showLogin" @submit.prevent class="mt-7">
+              <v-text-field
+                v-model="userLogin.username"
+                label="信箱"
+                :rules="[(v) => !!v || '請輸入您的信箱']"
+                prepend-inner-icon="mdi-account"
+                variant="solo"
+                density="compact"
+              ></v-text-field>
+              <v-text-field
+                v-model="userLogin.password"
+                label="密碼"
+                :rules="[(v) => !!v || '請輸入您的密碼']"
+                prepend-inner-icon="mdi-lock"
+                variant="solo"
+                density="compact"
+                type="password"
+              ></v-text-field>
+              <v-btn
+                type="submit"
+                size="large"
+                color="primary"
+                block
+                @click="login()"
+                :loading="isLoading"
+                >登入</v-btn
+              >
+            </v-form>
+          </Transition>
+          
           <div class="alert" v-if="loginAlert">
             <v-alert border="start" border-color="primary" elevation="2">
               {{ alertText }}
@@ -371,6 +389,10 @@ console.log("JWT 過期時間:", formattedExpiration);
 </template>
 
 <style lang="scss">
+.login-btn {
+  width: 190px;
+  margin-top: 20px;
+}
 .login-card {
   .v-tab {
     padding-bottom: 20px;

+ 2 - 2
src/views/News.vue

@@ -2,7 +2,6 @@
 import { ref, reactive, watch, computed } from "vue";
 import { useMainStore } from "@/stores/store";
 import axios from "axios";
-import moment from "moment";
 import Navbar from "@/components/Navbar.vue";
 import ArticleCard from "@/components/ArticleCard.vue";
 
@@ -173,7 +172,7 @@ const categoryList = reactive([
         </div>
 
         <ul>
-          <li v-for="(item, index) in paginatedList" :key="index" class="mb-10">
+          <li v-for="(item, index) in paginatedList" :key="index" class="mb-16">
 
             <ArticleCard :data="item" type="news" />
 
@@ -210,6 +209,7 @@ const categoryList = reactive([
             </v-card> -->
           </li>
         </ul>
+
         <div class="pagination-item">
           <v-pagination
             v-model="currentPage"

+ 12 - 2
src/views/NewsDetail.vue

@@ -1,5 +1,5 @@
 <script setup>
-import { reactive } from "vue";
+import { ref, reactive } from "vue";
 import { useRoute } from "vue-router";
 import axios from "axios";
 import moment from "moment";
@@ -10,13 +10,16 @@ const newsId = route.params.id; // 網址參數
 const news = reactive({
   data: [],
 });
+let loading = ref(false);
 
 (async function getData() {
+  loading.value = true;
   try {
     const response = await axios.get(
       `https://cmm.ai:8088/api/get_news?news_id=${newsId}`
     );
     news.data = response.data.news[0];
+    loading.value = false;
     console.log("news.data", news.data);
   } catch (error) {
     console.error(error);
@@ -30,7 +33,14 @@ const news = reactive({
     <img src="@/assets/img/news/news-01.png" alt="" class="material-img" />
     <v-container class="pa-0 py-16 position-relative">
       <div class="content">
-        <div class="article">
+        <div v-if="loading" class="d-flex justify-center my-10">
+          <v-progress-circular
+            color="grey-lighten-4"
+            indeterminate
+          ></v-progress-circular>
+        </div>
+
+        <div v-else class="article">
           <v-row>
             <v-col cols="12">
               <div class="d-flex align-center mb-5">

+ 1086 - 0
src/views/User/Courses.vue

@@ -0,0 +1,1086 @@
+<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 axios from "axios";
+import moment from "moment";
+
+const store = useMainStore();
+
+let token = store.token;
+let loading = ref(false);
+let courses = reactive({
+  list: [],
+});
+let classes = reactive({
+  list: [],
+});
+
+// 取得開課紀錄
+async function getClass() {
+  console.log("getClass");
+  loading.value = true;
+  let token = store.token;
+
+  try {
+    let response = await axios.get(
+      `https://cmm.ai:8088/api/get_class_name?access_token=${token}`
+    );
+    classes.list = response.data.classes;
+    // let coursesData = response.data.classes[0];
+    // classNameId.value = coursesData.class_name_id;
+
+    // 存入課程資料
+    // for (const key in coursesData) {
+    //   if (course.hasOwnProperty(key) && coursesData[key] !== undefined) {
+    //     course[key] = coursesData[key];
+    //   }
+
+    //   if (coursesData["cover_img"] !== "") {
+    //     coverImgUrl.value = `https://ntcri.org/${coursesData["cover_img"]}`;
+    //   }
+    // }
+
+    // console.log('course',course);
+
+    loading.value = false;
+    console.log("開課資料", classes.list);
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+getClass();
+
+// 彈跳視窗狀態
+let schoolDialog = ref(false); // 據點
+let courseDialog = ref(false); // 課程
+let enrolledDialog = reactive([]); // 報名清單
+let sessionsDialog = ref(false); // 場次
+
+watch(schoolDialog, () => {
+  initMap();
+});
+
+// 據點參數
+let location = reactive({
+  location_name: "",
+  Lng: "",
+  Lat: "",
+  address: "",
+  introduction: "",
+  email: "",
+  phone: "",
+  access_token: token,
+});
+
+// Google Map
+const states = reactive({
+  google: null,
+  map: null,
+  markers: null,
+});
+
+const initMap = async () => {
+  const loader = new Loader({
+    apiKey: "AIzaSyAzDeviZ-TpwzT1atlnshNJRjBgndP05Mw",
+    version: "weekly",
+    libraries: ["places"],
+    language: "zh-TW",
+  });
+  states.google = await loader.load();
+  let dom = document.getElementById("map");
+  states.map = new states.google.maps.Map(document.getElementById("map"), {
+    center: { lat: 25.0425, lng: 121.5468 },
+    zoom: 11,
+    mapTypeControl: false,
+    fullscreenControl: false,
+  });
+};
+
+// 查詢 Google 地圖
+const getCoordinates = async () => {
+  const geocoder = new states.google.maps.Geocoder();
+  geocoder.geocode({ address: location.address }, (results, status) => {
+    if (status === states.google.maps.GeocoderStatus.OK) {
+      location.Lat = results[0].geometry.location.lat();
+      location.Lng = results[0].geometry.location.lng();
+    }
+
+    // 將地圖中心設為取得的經緯度,並調整縮放等級
+    states.map.setCenter({ lat: location.Lat, lng: location.Lng });
+    states.map.setZoom(15);
+
+    // 設定地址圖標
+    const marker = new google.maps.Marker({
+      position: { lat: location.Lat, lng: location.Lng },
+      map: states.map,
+      title: location.address,
+      icon: store.getImageUrl("map-icon/icon_house05.png"),
+    });
+  });
+};
+
+// 更新按鈕狀態
+function updateButtonStatus(name) {
+  if (name === "location") {
+    locationBtnLoading.value = false;
+    locationBtnText.value = "儲存成功!";
+    setTimeout(() => {
+      // schoolDialog.value = false; // 關閉彈跳視窗
+      locationBtnText.value = "儲存修改";
+    }, 3000);
+  } else if (name === "course") {
+    courseBtnLoading.value = false;
+    courseBtnText.value = "儲存成功!";
+    setTimeout(() => {
+      // courseDialog.value = false; // 關閉彈跳視窗
+      courseBtnText.value = "儲存修改";
+    }, 3000);
+  }
+}
+
+let schoolData = reactive({});
+let locationId = ref(null);
+
+// 取得據點
+async function getSchool(id) {
+  console.log("getSchool id", id);
+  locationId.value = id;
+
+  try {
+    const response = await axios.get(
+      // `https://cmm.ai:8088/api/get_school?location_id=${id}&access_token=${token}` // 暫時不帶 token
+      `https://cmm.ai:8088/api/get_school?location_id=${id}`
+    );
+    schoolData = response.data.schools[0];
+    console.log("據點", schoolData);
+    for (const key in schoolData) {
+      if (location.hasOwnProperty(key) && schoolData[key] !== undefined) {
+        location[key] = schoolData[key];
+      }
+      if (key === "school_introduction") {
+        location["introduction"] = schoolData["school_introduction"];
+      }
+    }
+
+    // 設定地圖
+    getCoordinates();
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+// 儲存按鈕狀態
+let locationBtnLoading = ref(false);
+let locationBtnText = ref("儲存修改");
+
+// 更新據點
+async function saveLocation() {
+  locationBtnLoading.value = true;
+  let data = reactive({
+    location_id: locationId.value,
+    location_name: "",
+    Lng: "",
+    Lat: "",
+    address: "",
+    introduction: "",
+    email: "",
+    phone: "",
+  });
+
+  for (const key in location) {
+    if (data.hasOwnProperty(key) && location[key] !== undefined) {
+      data[key] = location[key];
+    }
+  }
+
+  const formData = new FormData();
+  for (const key in data) {
+    formData.append(key, data[key]);
+  }
+
+  try {
+    await axios.post("https://cmm.ai:8088/api/update_school", formData);
+    setTimeout(() => {
+      updateButtonStatus("location");
+    }, 500);
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+// 課程參數
+let course = reactive({
+  name: "", // 課程名稱
+  location_id: "", // 據點編號
+  category: "", // 工藝類別
+  introduction: "", // 課程簡介
+  organizer: "", // 主辦單位
+  cover_img_file: "", // 課程圖片
+  group_id: 2, // 學群編號(技藝)
+  group_sort: "", // 學群細分
+  special_class_list_name: "",
+  recommend: 0, // 是否推薦
+  is_inner: 1, // 內課課程
+  is_check: 0, // 審核結果
+  address: "", // 上課地點
+  access_token: token,
+});
+
+let classNameId = ref(null);
+let courseLoading = ref(false);
+let coverImgLoading = ref(false);
+
+// 取得課程
+async function getCourse(id) {
+  classNameId.value = id;
+  console.log("取得課程", id);
+  courseLoading.value = true;
+
+  let token = store.token;
+
+  try {
+    let response = await axios.get(
+      `https://cmm.ai:8088/api/get_class_name?class_name_id=${id}&access_token=${token}`
+    );
+
+    // console.log('課程資料',response);
+    courses.list = response.data.classes;
+    console.log("課程資料", courses.list);
+    let coursesData = response.data.classes[0];
+    console.log("coursesData", coursesData);
+    // classNameId.value = coursesData.class_name_id;
+
+    // 存入課程資料
+    for (const key in coursesData) {
+      if (course.hasOwnProperty(key) && coursesData[key] !== undefined) {
+        course[key] = coursesData[key];
+      }
+
+      if (coursesData["cover_img"] !== "") {
+        coverImgLoading.value = true;
+        coverImgUrl.value = `https://ntcri.org/${coursesData["cover_img"]}`;
+
+        setTimeout(() => {
+          coverImgLoading.value = false;
+        }, 1500);
+      }
+    }
+
+    console.log("最終 course", course);
+
+    loading.value = false;
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+// 儲存按鈕狀態
+let courseBtnLoading = ref(false);
+let courseBtnText = ref("儲存修改");
+
+// 更新課程
+async function saveCourse() {
+  courseBtnLoading.value = true;
+  delete course.access_token;
+
+  course["class_name_id"] = classNameId.value;
+  if (coverImg.value) {
+    course["cover_img_file"] = coverImg.value;
+  }
+
+  const formData = new FormData();
+  for (const key in course) {
+    formData.append(key, course[key]);
+  }
+
+  try {
+    await axios.post("https://cmm.ai:8088/api/update_class_name", formData);
+
+    setTimeout(() => {
+      updateButtonStatus("course");
+    }, 500);
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+// 上傳圖片 Input
+const coverImgRef = ref(null);
+
+const fileInputClick = (ref) => {
+  if (ref === "cover") {
+    if (coverImgRef.value) {
+      coverImgRef.value[0].click();
+    }
+  }
+};
+
+// 封面圖片
+let coverImg = ref("");
+let coverImgUrl = ref("");
+
+const handleCoverImg = (event) => {
+  const file = event.target.files[0];
+  if (file) {
+    coverImg.value = file;
+    coverImgUrl.value = URL.createObjectURL(file); // 設定圖片預覽 URL
+  }
+};
+
+// 取得場次
+async function getEvent(id) {
+  console.log("getEvent id", id);
+
+  try {
+    const response = await axios.get(
+      `https://cmm.ai:8088/api/get_event?class_name_id=${id}`
+    );
+    // event_id
+    console.log("場次資料", response);
+    let eventId = response.data.classes[0].event_id;
+    getEnrolledData(eventId);
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+let enrolledLoading = ref(false);
+let registrations = reactive({
+  list: [],
+});
+
+// 取得報名清單
+async function getEnrolledData(eventId) {
+  enrolledLoading.value = true;
+  console.log("getEnrolledData eventId", eventId);
+  try {
+    const response = await axios.get(
+      `https://cmm.ai:8088/api/get_registration_class?event_id=${eventId}`
+    );
+    console.log("取得報名", response);
+    registrations.list = response.data.registrations;
+    console.log("報名清單", registrations.list);
+
+    registrations.list.map((item) => {
+      if (item.payment_status) {
+        item.payment_status = true;
+      } else {
+        item.payment_status = false;
+      }
+    });
+
+    setTimeout(() => {
+      enrolledLoading.value = false;
+    }, 1000);
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+// 場次
+let eventData = reactive({
+  list: [],
+});
+
+// 場次參數
+let event = reactive({
+  name_id: "", // 課程名稱編號(需先新增課程取得Id)
+  event: "", // 場次名稱
+  start_time: "", // 課程起始日
+  end_time: "", // 課程結束日
+  contact: "", // 聯絡資訊
+  lecturer: "", // 課程講師
+  location: "",
+  content: "", // 課程內容
+  URL: "", // 外部連結
+  people: "", // 對象
+  fee_method: "", // 收費方式
+  registration_way: "", // 報名方式
+  registration_start: "", // 報名起始日
+  registration_end: "", // 報名截止日
+  number_limit: "", // 人數限制
+  remark: "", // 備註
+  ATM_address: "", // 匯款帳號(銀行代碼+帳號)
+  access_token: token,
+});
+
+let date = reactive({
+  start_date: "", // 起始日期
+  start_time: "", // 起始時間
+  end_date: "", // 結束日期
+  end_time: "", // 結束時間
+  registration_start_date: "", // 報名起始日期
+  registration_start_time: "", // 報名起始時間
+  registration_end_date: "", // 報名截止日期
+  registration_end_time: "", // 報名截止時間
+});
+
+let itemsPerPage = ref(5); // 每頁顯示筆數(查看出缺席)
+let headers = reactive([
+  {
+    title: "姓名",
+    align: "start",
+    key: "real_name",
+  },
+  { title: "電話", key: "phone" },
+  { title: "信箱", key: "email" },
+  { title: "出席", key: "enrolled" },
+  { title: "付款狀態", key: "payment" },
+]);
+</script>
+
+<template>
+  <v-card class="h-100 user-courses">
+    <div class="title">
+      <h4>我的開課</h4>
+    </div>
+
+    <div class="d-flex justify-center my-10" v-if="loading">
+      <v-progress-circular
+        color="grey-lighten-4"
+        indeterminate
+      ></v-progress-circular>
+    </div>
+
+    <div v-else class="main-table mt-3">
+      <h6 class="table-title">開課紀錄</h6>
+      <router-link
+        v-if="!classes.list.length"
+        to="/setup-courses"
+        class="hint-item mb-7"
+        >點此進入開課專區</router-link
+      >
+      <table v-else>
+        <thead>
+          <tr>
+            <th></th>
+            <th>名稱</th>
+            <th>開課日期</th>
+            <th width="20%">狀態</th>
+            <th>出/缺席</th>
+            <th></th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="(item, index) in classes.list" :key="index">
+            <td>
+              <v-chip v-if="!item.is_check"> 未審核 </v-chip>
+              <v-chip
+                v-else
+                variant="outlined"
+                color="green"
+                text-color="white"
+              >
+                已審核
+              </v-chip>
+            </td>
+            <td>{{ item.name }}</td>
+            <td>
+              {{ moment(`${item.update_time}`).format("YYYY/MM/DD") }}
+            </td>
+            <td>
+              開放報名
+              <!-- {{ item.state }} -->
+            </td>
+            <td>
+              <v-dialog v-model="enrolledDialog[index]" width="1000px">
+                <template v-slot:activator="{ props }">
+                  <v-btn
+                    @click="getEvent(item.class_name_id)"
+                    v-bind="props"
+                    color="blue"
+                    variant="flat"
+                    rounded="xl"
+                    class="me-3"
+                  >
+                    <p class="text-white">查看</p>
+                  </v-btn>
+                </template>
+
+                <v-card class="pa-5">
+                  <v-card-text>
+                    <div class="d-flex justify-center" v-if="enrolledLoading">
+                      <v-progress-circular
+                        color="grey-lighten-4"
+                        indeterminate
+                      ></v-progress-circular>
+                    </div>
+                    <v-data-table
+                      v-else
+                      v-model:items-per-page="itemsPerPage"
+                      :headers="headers"
+                      :items="registrations.list"
+                      item-value="name"
+                      class="elevation-1 courses-table"
+                    >
+                      <template v-slot:item.enrolled="{ item }">
+                        <v-checkbox hide-details></v-checkbox>
+                      </template>
+                      <template v-slot:item.payment="{ item }">
+                        <!-- <v-select
+                          :items="['已付款', '未付款']"
+                          density="compact"
+                          variant="outlined"
+                          hide-details
+                          class="my-3"
+                        ></v-select> -->
+                        <div class="d-flex align-center my-2">
+                          <v-checkbox
+                            v-model="item.selectable.payment_status"
+                            color="purple"
+                            hide-details
+                            label="已付款"
+                          ></v-checkbox>
+                          <v-text-field
+                            density="compact"
+                            variant="outlined"
+                            hide-details
+                            label="帳號末五碼"
+                            class="account-item"
+                            :disabled="!item.selectable.payment_status"
+                          ></v-text-field>
+                        </div>
+                      </template>
+                    </v-data-table>
+                  </v-card-text>
+                  <v-card-actions class="d-flex justify-center mt-3">
+                    <v-btn
+                      color="purple"
+                      variant="outlined"
+                      class="me-3"
+                      @click="enrolledDialog[index] = false"
+                      >關閉</v-btn
+                    >
+                    <v-btn variant="flat" color="purple" class="px-6"
+                      >儲存修改</v-btn
+                    >
+                  </v-card-actions>
+                </v-card>
+              </v-dialog>
+            </td>
+            <td>
+              <v-menu>
+                <template v-slot:activator="{ props }">
+                  <v-btn color="blue" v-bind="props" variant="outlined">
+                    <p>編輯</p>
+                  </v-btn>
+
+                  <v-btn color="blue" variant="outlined" class="ms-3">
+                    <a
+                      :href="
+                        $router.resolve(`/course-detail/${item.class_name_id}`)
+                          .href
+                      "
+                      class="text-blue"
+                      target="_blank"
+                      >前往課程</a
+                    >
+                    <!-- <router-link :to="class_name_id" class="text-white">前往課程</router-link> -->
+                  </v-btn>
+                </template>
+                <v-list>
+                  <v-list-item>
+                    <v-list-item-title>
+                      <v-btn
+                        variant="text"
+                        @click="getSchool(item.location_id)"
+                      >
+                        據點
+
+                        <v-dialog
+                          v-model="schoolDialog"
+                          activator="parent"
+                          width="auto"
+                        >
+                          <v-card class="pa-5">
+                            <v-card-title class="ps-5 pb-5"
+                              >編輯據點</v-card-title
+                            >
+                            <v-card-text>
+                              <v-label class="d-flex align-center pb-3">
+                                <p class="pb-5 pe-3">
+                                  據點名稱<span class="mark">*</span>
+                                </p>
+                                <v-text-field
+                                  v-model="location.location_name"
+                                  :rules="[requiredRule]"
+                                  placeholder="可以是您的工作室或品牌名稱/教學單位/您的姓名"
+                                  density="compact"
+                                  variant="outlined"
+                                  counter
+                                  maxlength="60"
+                                ></v-text-field>
+                              </v-label>
+
+                              <v-label class="d-flex align-center pb-3 w-100">
+                                <p class="pb-5 pe-3">
+                                  據點地址<span class="mark">*</span>
+                                </p>
+                                <v-text-field
+                                  v-model="location.address"
+                                  :rules="[requiredRule]"
+                                  density="compact"
+                                  variant="outlined"
+                                  placeholder="需在安全且便於學徒到達之地點開課"
+                                ></v-text-field>
+                                <v-btn
+                                  @click="getCoordinates"
+                                  color="purple"
+                                  variant="flat"
+                                  class="ms-3 mb-6 px-8"
+                                >
+                                  查詢
+                                </v-btn>
+                              </v-label>
+
+                              <div
+                                class="d-flex flex-column justify-end ms-16 ps-5"
+                              >
+                                <div
+                                  class="map"
+                                  id="map"
+                                  style="width: 100%; height: 500px"
+                                ></div>
+
+                                <v-row class="mt-3">
+                                  <v-col cols="12" md="6">
+                                    <v-label class="d-flex align-center">
+                                      <p class="pb-5 pe-3">經度</p>
+                                      <v-text-field
+                                        v-model="location.Lng"
+                                        density="compact"
+                                        variant="outlined"
+                                        disabled
+                                      ></v-text-field>
+                                    </v-label>
+                                  </v-col>
+                                  <v-col cols="12" md="6">
+                                    <v-label class="d-flex align-center">
+                                      <p class="pb-5 pe-3">緯度</p>
+                                      <v-text-field
+                                        v-model="location.Lat"
+                                        density="compact"
+                                        variant="outlined"
+                                        disabled
+                                      ></v-text-field>
+                                    </v-label>
+                                  </v-col>
+                                </v-row>
+                              </div>
+
+                              <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-row class="mt-3">
+                                <v-col cols="12" md="12">
+                                  <v-label class="d-block">
+                                    <p class="d-flex">
+                                      公開電話<span class="mark">*</span>
+                                    </p>
+                                    <span class="d-block pt-1 pb-3 hint"
+                                      >會顯示於課程介紹中,想了解課程資訊者聯繫用途</span
+                                    >
+                                    <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="12">
+                                  <v-label class="d-block">
+                                    <p class="d-flex mb-5">
+                                      據點簡介<span class="mark">*</span>
+                                    </p>
+                                    <v-textarea
+                                      v-model="location.introduction"
+                                      rows="5"
+                                      variant="outlined"
+                                    ></v-textarea>
+                                  </v-label>
+                                </v-col>
+                              </v-row>
+                            </v-card-text>
+                            <v-card-actions class="d-flex justify-center">
+                              <v-btn
+                                variant="outlined"
+                                color="purple"
+                                @click="schoolDialog = false"
+                                class="me-2 px-3"
+                                >關閉</v-btn
+                              >
+                              <v-btn
+                                variant="flat"
+                                color="purple"
+                                class="px-6"
+                                :loading="locationBtnLoading"
+                                @click="saveLocation()"
+                                >{{ locationBtnText }}</v-btn
+                              >
+                            </v-card-actions>
+                          </v-card>
+                        </v-dialog>
+                      </v-btn>
+                    </v-list-item-title>
+                  </v-list-item>
+                  <v-list-item>
+                    <v-list-item-title>
+                      <!-- {{ item.title }} -->
+
+                      <v-btn
+                        @click="getCourse(item.class_name_id)"
+                        variant="text"
+                      >
+                        課程
+
+                        <v-dialog
+                          v-model="courseDialog"
+                          activator="parent"
+                          width="600"
+                        >
+                          <v-card class="pa-5">
+                            <v-card-title class="ps-5 pb-8"
+                              >編輯課程</v-card-title
+                            >
+                            <v-card-text class="mb-7">
+                              <v-row class="justify-space-evenly">
+                                <v-col cols="12" class="py-0">
+                                  <v-label class="d-block pb-3">
+                                    <p class="pb-5 pe-3">
+                                      課程名稱<span class="mark">*</span>
+                                    </p>
+                                    <v-text-field
+                                      v-model="course.name"
+                                      :rules="[requiredRule]"
+                                      density="compact"
+                                      variant="outlined"
+                                    ></v-text-field>
+                                  </v-label>
+                                </v-col>
+
+                                <!-- <v-col cols="12" md="6">
+                                  <v-label class="d-block pb-3">
+                                    <p class="pb-5 pe-3">
+                                      課程價格<span class="mark">*</span>
+                                    </p>
+                                    <v-text-field
+                                      :rules="[requiredRule]"
+                                      density="compact"
+                                      variant="outlined"
+                                    ></v-text-field>
+                                  </v-label>
+                                </v-col> -->
+
+                                <v-col cols="12" class="py-0">
+                                  <v-label class="d-block pb-3">
+                                    <p class="pb-5 pe-3">
+                                      工藝類別<span class="mark">*</span>
+                                    </p>
+                                    <v-select
+                                      v-model="course.category"
+                                      placeholder="請選擇類別"
+                                      :items="[
+                                        '樹藝',
+                                        '漆藝',
+                                        '藍染',
+                                        '蠟雕',
+                                        '竹工藝籃',
+                                        '金工/飾品',
+                                        '蠟燭/香氛/調香',
+                                        '植栽/花藝',
+                                        '插畫/繪畫/寫字',
+                                        '皮件/皮革',
+                                        '木工/竹藝',
+                                        '陶藝/玻璃',
+                                        '編織/羊毛氈/縫紉',
+                                        '其他',
+                                      ]"
+                                      :rules="[requiredRule]"
+                                      density="compact"
+                                      variant="outlined"
+                                    ></v-select>
+                                  </v-label>
+                                </v-col>
+
+                                <!-- <v-col cols="12" md="6">
+                                  <v-label class="d-block pb-3">
+                                    <p class="pb-5 pe-3">
+                                      適合報名對象(年紀)<span class="mark"
+                                        >*</span
+                                      >
+                                    </p>
+
+                                    <v-select
+                                      v-model="age"
+                                      :items="[
+                                        '不拘',
+                                        '7 歲以下',
+                                        '7-18 歲',
+                                        '18-65 歲',
+                                        '65 歲以上',
+                                      ]"
+                                      :rules="[requiredRule]"
+                                      density="compact"
+                                      variant="outlined"
+                                      outlined
+                                    ></v-select>
+                                  </v-label>
+                                </v-col> -->
+
+                                <v-col cols="12" class="py-0">
+                                  <v-label class="d-block pb-3">
+                                    <p class="pb-5 pe-3">上課地點</p>
+                                    <v-text-field
+                                      v-model="course.address"
+                                      density="compact"
+                                      variant="outlined"
+                                    ></v-text-field>
+                                  </v-label>
+                                </v-col>
+
+                                <v-col cols="12" class="py-0">
+                                  <v-label class="d-block pb-3">
+                                    <p class="pb-5 pe-3">主辦單位</p>
+                                    <v-text-field
+                                      v-model="course.organizer"
+                                      density="compact"
+                                      variant="outlined"
+                                    ></v-text-field>
+                                  </v-label>
+                                </v-col>
+
+                                <v-col cols="12" class="py-0">
+                                  <v-label class="d-block">
+                                    <p class="d-flex">
+                                      課程簡介<span class="mark">*</span>
+                                    </p>
+                                    <small class="d-block text-gray my-2"
+                                      >內容不得少於 100
+                                      字元,且不得含有工藝無關之資訊</small
+                                    >
+                                    <v-textarea
+                                      v-model="course.introduction"
+                                      rows="5"
+                                      variant="outlined"
+                                    ></v-textarea>
+                                  </v-label>
+                                </v-col>
+
+                                <v-col cols="12" class="me-auto">
+                                  <v-label class="d-block">
+                                    <p class="d-flex">
+                                      課程封面<span class="mark">*</span>
+                                    </p>
+
+                                    <v-file-input
+                                      multiple
+                                      v-model="coverImg"
+                                      id="coverImgRef"
+                                      ref="coverImgRef"
+                                      label="File input"
+                                      variant="outlined"
+                                      placeholder="選擇相片上傳"
+                                      @change="handleCoverImg"
+                                      style="display: none"
+                                    ></v-file-input>
+                                  </v-label>
+
+                                  <v-btn
+                                    @click="fileInputClick('cover')"
+                                    color="purple"
+                                    class="my-5"
+                                    >選擇相片上傳</v-btn
+                                  >
+
+                                  <div class="step-01 image-preview">
+                                    <div
+                                      class="d-flex justify-center"
+                                      v-if="coverImgLoading"
+                                    >
+                                      <v-progress-circular
+                                        color="grey-lighten-4"
+                                        indeterminate
+                                      ></v-progress-circular>
+                                    </div>
+
+                                    <img
+                                      v-if="
+                                        coverImgUrl !== '' && !coverImgLoading
+                                      "
+                                      :src="coverImgUrl"
+                                      alt="上傳圖片預覽"
+                                    />
+                                  </div>
+                                </v-col>
+                              </v-row>
+                            </v-card-text>
+                            <v-card-actions class="d-flex justify-center">
+                              <v-btn
+                                variant="outlined"
+                                color="purple"
+                                @click="courseDialog = false"
+                                class="me-2 px-3"
+                                >關閉</v-btn
+                              >
+                              <v-btn
+                                variant="flat"
+                                color="purple"
+                                class="px-6"
+                                :loading="courseBtnLoading"
+                                @click="saveCourse()"
+                                >{{ courseBtnText }}</v-btn
+                              >
+                            </v-card-actions>
+                          </v-card>
+                        </v-dialog>
+                      </v-btn>
+                    </v-list-item-title>
+                  </v-list-item>
+                </v-list>
+              </v-menu>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </v-card>
+</template>
+
+<style lang="scss">
+.user-courses {
+  p,
+  a {
+    font-size: 16px;
+    line-height: 30px;
+  }
+
+  .v-table {
+    padding: 20px;
+  }
+
+  .main-info {
+    margin: 30px;
+    padding: 50px;
+    position: relative;
+    border: 2px solid var(--blue);
+    border-radius: 20px;
+
+    @media (max-width: 600px) {
+      margin: 0;
+      padding: 50px 20px 100px;
+    }
+
+    h5 {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      font-size: 20px;
+      font-weight: 500;
+      letter-spacing: 1px;
+      span {
+        display: block;
+        margin: 5px 0 15px;
+        font-size: 36px;
+      }
+    }
+
+    p {
+      text-align: center;
+      line-height: 22px;
+      @media (max-width: 490px) {
+        font-size: 14px;
+      }
+    }
+
+    .icon {
+      width: 50px;
+      position: absolute;
+      right: -50px;
+    }
+
+    .record-item {
+      margin-top: 20px;
+      position: relative;
+      p {
+        width: 100%;
+        position: absolute;
+        z-index: 100;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+        color: #fff;
+        font-size: 30px;
+      }
+      strong {
+        display: inline-block;
+        margin: 25px 0;
+        font-size: 56px;
+        font-weight: 500;
+        @media (max-width: 600px) {
+          font-size: 50px;
+        }
+      }
+      small {
+        font-size: 20px;
+      }
+      img {
+        max-width: 260px;
+        @media (max-width: 600px) {
+          max-width: 220px;
+        }
+      }
+    }
+  }
+
+  .table-title {
+    background-color: var(--blue);
+  }
+
+  table {
+    thead {
+      border-bottom: 2px solid var(--blue);
+    }
+    tbody {
+      td {
+        border-bottom: 1px solid var(--blue);
+      }
+    }
+    .finish-icon {
+      padding: 8px;
+      border: 2px solid var(--blue);
+      border-radius: 100px;
+      .v-icon {
+        color: var(--blue);
+      }
+    }
+  }
+}
+
+.courses-table {
+  .v-data-table-footer {
+    padding: 15px 0;
+  }
+  .account-item {
+    .v-field__input {
+      padding: 0 20px;
+    }
+    .v-label {
+      color: var(--gray);
+    }
+  }
+}
+</style>

+ 14 - 8
src/views/User/Dashboard.vue

@@ -45,17 +45,23 @@ let items = [
     value: 2,
   },
   {
-    title: "我的收藏",
-    icon: "mdi-bookmark",
-    url: "/user/favorite-class",
+    title: "我的開課",
+    icon: "mdi-school",
+    url: "/user/courses",
     value: 3,
   },
   {
-    title: "帳號設定",
-    icon: "mdi-cog",
-    url: "/",
+    title: "我的收藏",
+    icon: "mdi-bookmark",
+    url: "/user/favorite-class",
     value: 4,
   },
+  // {
+  //   title: "帳戶設定",
+  //   icon: "mdi-cog",
+  //   url: "/user/setting",
+  //   value: 5,
+  // },
   { type: "divider" },
 ];
 </script>
@@ -119,7 +125,7 @@ let items = [
       font-size: 22px;
       font-weight: 500;
       text-align: center;
-      border-bottom: 3px solid var(--main-color);
+      border-bottom: 3px solid var(--purple);
     }
   }
 
@@ -130,7 +136,7 @@ let items = [
     .img {
       width: 70px;
       height: 70px;
-      background-color: var(--main-color);
+      background-color: var(--purple);
       display: flex;
       align-items: center;
       justify-content: center;

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

@@ -100,6 +100,15 @@ const handleSearch = () => {
     </div>
     <v-container>
       <v-row>
+        <v-col cols="12" v-if="!favorites.list.length">
+          <router-link to="/setup-courses" class="hint-item">
+            <v-icon color="purple" icon="mdi-bookmark" class="mb-2"></v-icon>
+            <p>
+              收藏是空的喔! <br />
+              趕快前往探索課程找尋感興趣的課程吧!
+            </p>
+          </router-link>
+        </v-col>
         <v-col
           sm="6"
           cols="12"

+ 10 - 97
src/views/User/Passport.vue

@@ -71,15 +71,15 @@ let register = reactive({
         </div>
       </v-col>
       <v-col cols="12" class="my-16">
-        <div style="overflow-x: auto">
+        <div class="main-table">
           <h6 class="table-title">報名中課程</h6>
           <table>
             <thead>
               <tr>
                 <th>名稱</th>
-                <th>日期</th>
-                <th width="20%">時數</th>
-                <th width="20%">報名完成</th>
+                <th width="30%">日期</th>
+                <th width="15%">時數</th>
+                <th width="15%">報名完成</th>
               </tr>
             </thead>
             <tbody v-if="register.list.length">
@@ -117,15 +117,15 @@ let register = reactive({
       </v-col>
 
       <v-col cols="12" class="my-16">
-        <div style="overflow-x: auto">
+        <div class="main-table">
           <h6 class="table-title">上課紀錄</h6>
           <table>
             <thead>
               <tr>
                 <th>名稱</th>
-                <th>日期</th>
-                <th width="20%">時數</th>
-                <th width="20%">報名完成</th>
+                <th width="30%">日期</th>
+                <th width="15%">時數</th>
+                <th width="15%">報名完成</th>
               </tr>
             </thead>
             <tbody>
@@ -135,28 +135,6 @@ let register = reactive({
         </div>
       </v-col>
 
-      <v-col cols="12" class="my-16">
-        <div style="overflow-x: auto">
-          <h6 class="table-title">開課紀錄</h6>
-          <table>
-            <thead>
-              <tr>
-                <th>名稱</th>
-                <th>日期</th>
-                <th width="20%">時數</th>
-                <th width="20%">報名完成</th>
-              </tr>
-            </thead>
-            <tbody>
-              <tr>
-                <!-- <a href="" class="hint-item">點此進入開課專區</a> -->
-              </tr>
-            </tbody>
-          </table>
-        </div>
-        <a href="" class="hint-item">點此進入開課專區</a>
-      </v-col>
-
       <div class="dot-item">
         <span class="t-dot"></span>
         <span class="r-dot"></span>
@@ -244,68 +222,21 @@ p {
       }
     }
   }
-
-  .dot-item {
-    span {
-      position: absolute;
-      display: block;
-      width: 15px;
-      height: 15px;
-      border-radius: 100px;
-      background-color: var(--blue);
-    }
-    .t-dot {
-      top: 15px;
-      left: 15px;
-    }
-    .r-dot {
-      top: 15px;
-      right: 15px;
-    }
-    .b-dot {
-      bottom: 15px;
-      right: 15px;
-    }
-    .l-dot {
-      bottom: 15px;
-      left: 15px;
-    }
-  }
 }
 
 .table-title {
-  padding: 10px;
-  color: #fff;
-  font-size: 20px;
-  font-weight: 400;
-  text-align: center;
   background-color: var(--blue);
-  @media (max-width: 1280px) {
-    width: 800px;
-  }
 }
 
 table {
-  width: 100%;
-  margin-bottom: 20px;
-  position: relative;
-  border-collapse: collapse;
-  @media (max-width: 1280px) {
-    width: 800px;
-  }
   thead {
     border-bottom: 2px solid var(--blue);
-    th {
-      padding: 20px 0;
-      font-weight: 500;
-    }
   }
   tbody {
     td {
-      padding: 20px 0;
-      text-align: center;
-      letter-spacing: 1px;
       border-bottom: 1px solid var(--blue);
+      line-height: 24px;
+      padding: 15px;
     }
   }
   .finish-icon {
@@ -317,22 +248,4 @@ table {
     }
   }
 }
-
-.hint-item {
-  width: 100%;
-  max-width: 500px;
-  display: block;
-  padding: 20px;
-  margin: 30px auto 0;
-  // position: absolute;
-  // top: 200%;
-  // left: 50%;
-  // transform: translate(-50%, -50%);
-  border-radius: 10px;
-  box-shadow: 2px 2px 8px #ccc;
-  border: 1px solid var(--purple);
-  letter-spacing: 2px;
-  text-align: center;
-  line-height: 26px;
-}
 </style>

+ 396 - 135
src/views/User/Profile.vue

@@ -16,8 +16,10 @@ let user = reactive({
   gender: "",
   phone: "",
   address: "",
+  position: [], // 身份
 });
 
+let positionList = ref([]);
 let email = ref("");
 let phoneCode = ref("+886");
 let phone = ref("");
@@ -29,12 +31,14 @@ let isExist = ref(false); // 資料是否已存在
 
 let token = store.token;
 
-async function save() {
+// 更新使用者資料
+async function saveUserInfo() {
   isLoading.value = true;
 
   user.phone = `${phoneCode.value} ${phone.value}`;
   user.address = city.value + address.value;
   user.birthday = store.formatDate(user.birthday);
+  user.position = `[${positionList.value.join(",")}]`; // 陣列轉字串
 
   console.log("user", user);
 
@@ -91,7 +95,7 @@ async function updateUserInfo(formData) {
   }
 }
 
-// 取得資料
+// 取得使用者資料
 async function getUserInfo() {
   try {
     const response = await axios.get(
@@ -99,6 +103,15 @@ async function getUserInfo() {
     );
     console.log("getUserInfo response", response);
 
+    let position = response.data.user_inform[0].position;
+    // 判斷身份
+    if (position["學員"]) {
+      positionList.value.push("1");
+    }
+    if (position["開課工藝家"]) {
+      positionList.value.push("2");
+    }
+
     email.value = response.data.user_inform[0].email;
     user.user_name = response.data.user_inform[0].user_name;
 
@@ -135,141 +148,357 @@ async function getUserInfo() {
 
 getUserInfo();
 
+let tab = ref(null);
 const genderGroup = reactive(["男", "女", "多元"]);
-
 const displayText = (item) => `${item.country} ${item.code}`;
+const requiredRule = (value) => !!value || "此欄位為必填";
+
+// 履歷
+let resume = reactive({
+  teacher_name: "", // 老師姓名
+  work_type: "", // 工作性質
+  experience: "", // 教學經驗
+  expertise: "", // 專長工藝技能
+  license: "", // 工藝相關證照
+  media: "", // 社群媒體
+  files: [], // 作品集
+  introduction: "", // 老師介紹
+});
+
+// 取得工藝家履歷
+(async () => {
+  try {
+    const response = await axios.get(
+      `https://cmm.ai:8088/api/get_user_resume?access_token=${token}`
+    );
+    console.log("工藝家", response.data.user_resume);
+
+    // 遍歷物件屬性
+    Object.keys(response.data.user_resume).forEach((key) => {
+      if (resume[key] !== undefined) {
+        resume[key] = response.data.user_resume[key];
+      }
+    });
+
+    console.log("resume", resume);
+  } catch (error) {
+    console.error(error);
+  }
+})();
+
+// 更新工藝家履歷
+async function saveResume() {
+  console.log("saveResume");
+}
+
+// 上傳圖片 Input
+const portfolioImgRef = ref(null);
+
+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);
+  }
+};
+
+watch(portfolioImg, (newFiles) => {
+  if (newFiles.length > 0) {
+    handlePortfolioImg(newFiles);
+  }
+});
 </script>
 
 <template>
   <v-card class="h-100 profile-card">
-    <div class="title">
-      <h4>關於我</h4>
-    </div>
-
-    <v-form @submit.prevent>
-      <v-label>
-        <p class="d-flex mb-5">顯示名稱<span class="mark">*</span></p>
-        <v-text-field
-          v-model="user.user_name"
-          :rules="[(v) => !!v || '請輸入您的名稱']"
-          class="d-block"
-        ></v-text-field>
-      </v-label>
-      <v-divider class="mt-3 mb-8"></v-divider>
-
-      <p class="d-flex mb-5">帳號/Email</p>
-      <p class="text-gray">{{ email }}(欲變更帳號或密碼,請至帳號設定)</p>
-
-      <v-label class="mt-10">
-        <p class="d-flex mb-5">真實姓名<span class="mark">*</span></p>
-        <v-text-field
-          v-model="user.name"
-          :rules="[(v) => !!v || '請輸入您的姓名']"
-          class="d-block"
-        ></v-text-field>
-      </v-label>
-
-      <v-label class="mt-10">
-        <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 class="mt-10">
-        <p class="d-flex mb-3">性別</p>
-
-        <v-chip-group
-          v-model="user.gender"
-          selected-class="text-primary"
-          mandatory
-        >
-          <v-chip v-for="item in genderGroup" :key="item" :value="item" filter>
-            {{ item }}
-          </v-chip>
-        </v-chip-group>
-      </v-label>
-
-      <v-label class="mt-10">
-        <p class="d-flex mb-5">手機號碼<span class="mark">*</span></p>
-        <v-container class="pa-0">
-          <v-row>
-            <v-col cols="12" md="3">
-              <v-select
-                v-model="phoneCode"
-                :items="phoneCodes"
-                :item-title="displayText"
-                item-value="code"
+    <v-tabs v-model="tab" color="purple" align-tabs="center">
+      <v-tab :value="1">關於我</v-tab>
+      <v-tab :value="2">工藝家履歷</v-tab>
+    </v-tabs>
+    <v-window v-model="tab">
+      <v-window-item :value="1">
+        <v-form @submit.prevent>
+          <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-5">
+            <p class="d-flex mb-5">顯示名稱<span class="mark">*</span></p>
+            <v-text-field
+              v-model="user.user_name"
+              :rules="[(v) => !!v || '請輸入您的名稱']"
+              class="d-block"
+            ></v-text-field>
+          </v-label>
+          <v-divider class="mt-3 mb-8"></v-divider>
+
+          <p class="d-flex mb-5">帳號/Email</p>
+          <p class="text-gray">{{ email }}</p>
+          <!-- <p class="text-gray">{{ email }}(欲變更帳號或密碼,請至帳號設定)</p> -->
+
+          <v-label>
+            <p class="d-flex mb-5">真實姓名<span class="mark">*</span></p>
+            <v-text-field
+              v-model="user.name"
+              :rules="[(v) => !!v || '請輸入您的姓名']"
+              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 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
               >
-              </v-select>
-            </v-col>
-
-            <v-col cols="12" md="9">
-              <v-text-field
-                v-model="phone"
-                :rules="[(v) => !!v || '請輸入您的號碼']"
-                required
-              ></v-text-field>
-            </v-col>
-          </v-row>
-        </v-container>
-      </v-label>
-
-      <v-label class="mt-10">
-        <p class="d-flex mb-5">現居地</p>
-        <v-container class="pa-0">
-          <v-row>
-            <v-col cols="12" md="3">
-              <v-select
-                v-model="city"
-                :items="taiwanCities"
-                item-title="name"
-                item-value="name"
+                {{ item }}
+              </v-chip>
+            </v-chip-group>
+          </v-label>
+
+          <v-label>
+            <p class="d-flex mb-5">手機號碼<span class="mark">*</span></p>
+            <v-container class="pa-0">
+              <v-row>
+                <v-col cols="12" md="3">
+                  <v-select
+                    v-model="phoneCode"
+                    :items="phoneCodes"
+                    :item-title="displayText"
+                    item-value="code"
+                  >
+                  </v-select>
+                </v-col>
+
+                <v-col cols="12" md="9">
+                  <v-text-field
+                    v-model="phone"
+                    :rules="[(v) => !!v || '請輸入您的號碼']"
+                    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">
+              <v-row>
+                <v-col cols="12" md="3">
+                  <v-select
+                    v-model="city"
+                    :items="taiwanCities"
+                    item-title="name"
+                    item-value="name"
+                  >
+                  </v-select>
+                </v-col>
+
+                <v-col cols="12" md="9">
+                  <v-text-field
+                    v-model="address"
+                    required
+                    placeholder="詳細地址"
+                  ></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-select>
-            </v-col>
+                儲存成功
+              </v-alert>
+            </Transition>
+
+            <!-- <v-btn variant="outlined" color="grey-lighten-1" class="me-3">
+              捨棄變更
+            </v-btn> -->
+            <v-btn
+              :loading="isLoading"
+              variant="flat"
+              color="purple"
+              class="px-8"
+              @click="saveUserInfo()"
+            >
+              儲存
+            </v-btn>
+          </div>
+        </v-form>
+      </v-window-item>
+
+      <!-- 工藝家履歷 -->
+      <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>
+            <v-text-field
+              v-model="resume.teacher_name"
+              :rules="[(v) => !!v || '請輸入您的姓名']"
+              class="d-block"
+            ></v-text-field>
+          </v-label>
+
+          <v-label>
+            <p class="pb-5 pe-3">工作性質<span class="mark">*</span></p>
+            <v-radio-group v-model="resume.work_type" inline color="purple">
+              <v-radio label="全職" value="全職"></v-radio>
+              <v-radio label="兼職" value="兼職" class="ps-3"></v-radio>
+            </v-radio-group>
+          </v-label>
+
+          <v-label>
+            <p class="pb-5 pe-3">教學經驗</p>
+            <v-radio-group v-model="resume.experience" inline color="purple">
+              <v-radio label="0-5 年" value="0-5 年"></v-radio>
+              <v-radio label="5-10 年" value="5-10 年" class="ps-3"></v-radio>
+              <v-radio label="10-20 年" value="10-20 年" class="ps-3"></v-radio>
+              <v-radio
+                label="20 年以上"
+                value="20 年以上"
+                class="ps-3"
+              ></v-radio>
+            </v-radio-group>
+          </v-label>
+
+          <v-label>
+            <p class="pb-5 pe-3">專長工藝技能<span class="mark">*</span></p>
+            <v-text-field
+              v-model="resume.expertise"
+              :rules="[requiredRule]"
+            ></v-text-field>
+          </v-label>
+
+          <v-label>
+            <p class="d-flex">社群媒體</p>
+            <span class="d-block py-3 hint"
+              >工藝老師經營之網站或社群媒體,可貼網址</span
+            >
+            <v-text-field v-model="resume.media"></v-text-field>
+          </v-label>
+
+          <v-label class="d-block">
+            <p class="d-flex">講師作品集</p>
+
+            <v-file-input
+              multiple
+              v-model="portfolioImg"
+              ref="portfolioImgRef"
+              label="File input"
+              variant="outlined"
+              placeholder="選擇相片上傳"
+              @change="handlePortfolioImg"
+              style="display: none"
+            ></v-file-input>
+          </v-label>
+
+          <v-btn @click="fileInputClick()" color="purple" class="my-5"
+            >選擇相片上傳</v-btn
+          >
 
-            <v-col cols="12" md="9">
-              <v-text-field
-                v-model="address"
-                required
-                placeholder="詳細地址"
-              ></v-text-field>
+          <v-row class="img-list">
+            <v-col cols="4" v-for="(item, index) in 6" :key="index">
+              <div v-if="!portfolioImgList.length" class="item"></div>
+              <div
+                v-else
+                class="item"
+                :style="{
+                  backgroundImage: `url('${portfolioImgList[index]}')`,
+                }"
+              ></div>
             </v-col>
           </v-row>
-        </v-container>
-      </v-label>
-
-      <div class="d-flex justify-end mt-5 mb-10">
-        <Transition>
-          <v-alert
-            v-if="alertState"
-            type="success"
-            variant="outlined"
-            density="compact"
-          >
-            儲存成功
-          </v-alert>
-        </Transition>
-
-        <v-btn variant="outlined" color="grey-lighten-1" class="me-3">
-          捨棄變更
-        </v-btn>
-        <v-btn
-          :loading="isLoading"
-          variant="flat"
-          color="primary"
-          @click="save()"
-        >
-          儲存
-        </v-btn>
-      </div>
-    </v-form>
+
+          <v-label>
+            <p class="d-flex mb-5">工藝家自我介紹</p>
+            <v-textarea v-model="resume.introduction" rows="5"></v-textarea>
+          </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 variant="outlined" color="grey-lighten-1" class="me-3">
+              捨棄變更
+            </v-btn> -->
+            <v-btn
+              :loading="isLoading"
+              variant="flat"
+              color="purple"
+              class="px-8"
+              @click="saveResume()"
+            >
+              儲存
+            </v-btn>
+          </div>
+        </v-form>
+      </v-window-item>
+    </v-window>
+
+    <!-- <div class="title">
+      <h4>關於我</h4>
+    </div> -->
   </v-card>
 </template>
 
@@ -278,21 +507,33 @@ const displayText = (item) => `${item.country} ${item.code}`;
   p {
     letter-spacing: 1px;
   }
+
+  .img-list {
+    .item {
+      height: 170px;
+      border-radius: 5px;
+      background-color: #ccc; // 預設灰底
+      background-repeat: no-repeat;
+      background-position: center;
+      background-size: cover;
+    }
+  }
+
   .v-btn {
     font-size: 16px !important;
   }
+
   .v-label {
-    width: 100%;
+    margin-top: 30px;
     flex-direction: column;
     align-items: flex-start;
-    opacity: 1;
-    color: #000;
-    .v-input {
-      width: 100%;
-    }
-    .mark {
-      display: inline-block;
-      color: red;
+    justify-content: center;
+  }
+
+  .v-checkbox,
+  .v-radio-group {
+    .v-label {
+      margin-top: 0;
     }
   }
 
@@ -306,5 +547,25 @@ const displayText = (item) => `${item.country} ${item.code}`;
     right: 223px;
     transition: all 0.3s;
   }
+
+  .v-tabs {
+    margin: 20px 0;
+
+    .v-tab__slider {
+      height: 3px;
+    }
+
+    .v-slide-group__container {
+      overflow: unset;
+    }
+
+    .v-btn__content {
+      font-size: 22px;
+    }
+  }
+
+  .v-tabs--density-default {
+    --v-tabs-height: 65px;
+  }
 }
 </style>

+ 88 - 0
src/views/User/Setting.vue

@@ -0,0 +1,88 @@
+<script setup>
+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: [],
+});
+
+let token = store.token;
+
+// 取得收藏課程
+async function getFavoriteClass() {
+  try {
+    const response = await axios.get(
+      `https://cmm.ai:8088/api/get_favorite_class?access_token=${token}`
+    );
+    console.log(
+      "response.data.favorite_courses",
+      response.data.favorite_courses
+    );
+    favorites.list = response.data.favorite_courses;
+    favoritesAll.list = response.data.favorite_courses;
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+getFavoriteClass();
+
+let progress = ref(false);
+let timeoutId = null;
+let searchError = ref(false);
+const searchText = ref("");
+const filtered = reactive({
+  list: [],
+});
+
+watch(searchText, (val) => {
+  if (val === "") {
+    favorites.list = favoritesAll.list;
+  }
+});
+
+// 延遲觸發
+const handleInput = () => {
+  clearTimeout(timeoutId);
+  timeoutId = setTimeout(handleSearch, 500);
+};
+
+// 課程搜尋
+const handleSearch = () => {
+  filtered.list = favorites.list.filter((item) =>
+    item.name.includes(searchText.value)
+  );
+  if (filtered.list.length) {
+    favorites.list = filtered.list;
+    searchError.value = false;
+  } else {
+    favorites.list = favoritesAll.list;
+    searchError.value = true;
+    setTimeout(() => {
+      searchError.value = false;
+    }, 2000);
+  }
+  console.log("filtered", filtered);
+};
+</script>
+
+<template>
+  <v-card class="h-100">
+    <div class="title">
+      <h4>帳戶設定</h4>
+    </div>
+    
+  </v-card>
+</template>
+
+<style lang="scss" scoped>
+.error {
+  height: 25px;
+}
+</style>

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio