SyuanYu 1 year ago
parent
commit
4ac014749c

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

@@ -716,6 +716,169 @@ input:focus-visible {
   letter-spacing: 0.0625em;
 }
 
+.user-courses .v-table {
+  padding: 1.25em;
+}
+.user-courses .tab-btn {
+  padding: 10px;
+  background-color: var(--blue);
+}
+.user-courses .tab-btn button {
+  margin: 0 10px;
+  color: #fff;
+  height: 2.2em !important;
+}
+.user-courses .tab-btn button.active {
+  color: var(--blue);
+  border-radius: 0;
+  background-color: #fff;
+  border-radius: 5px;
+}
+.user-courses .search-item {
+  width: 300px;
+}
+.user-courses .search-item img {
+  width: 1.5625em;
+}
+.user-courses .main-table {
+  margin-top: -35px;
+}
+.user-courses .table-title {
+  background-color: var(--blue);
+}
+.user-courses table thead {
+  border-bottom: 0.125em solid var(--blue);
+}
+.user-courses table tbody td {
+  padding: 1.25em 0.625em;
+  line-height: 1.375em;
+  border-bottom: 0.0625em solid var(--blue);
+}
+.user-courses .edit-item {
+  font-size: 0.8em;
+}
+.user-courses .edit-item .v-btn__content {
+  padding-bottom: 1px;
+}
+.user-courses .courses-table tr:hover {
+  transition: all 0.3s;
+}
+.user-courses .courses-table tr:hover > td {
+  cursor: pointer;
+  background-color: #f5f5f5 !important;
+}
+.user-courses .courses-table tr td {
+  text-align: center;
+}
+.user-courses .courses-table .v-data-table-footer {
+  padding: 1.25em 0;
+}
+.user-courses .courses-table .v-selection-control {
+  justify-content: center;
+}
+.user-courses .courses-table .account-item .v-field__input {
+  padding: 0 1.25em;
+}
+.user-courses .courses-table .account-item .v-label {
+  color: var(--gray);
+}
+.user-courses .v-data-table-header__content {
+  justify-content: center;
+}
+
+.courses-tab .v-tab {
+  font-size: 1.125em;
+}
+.courses-tab .badge {
+  display: block;
+  height: 0.5em;
+  width: 0.5em;
+  background-color: red;
+  border-radius: 6.25em;
+  position: absolute;
+  right: 0;
+  top: 0.625em;
+}
+
+.resume-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.resume-content .table-title {
+  width: 1000px;
+  padding: 0.625em;
+  color: #fff;
+  font-weight: 400;
+  text-align: center;
+  font-size: 1.3em;
+  letter-spacing: 2px;
+  background-color: var(--purple);
+}
+.resume-content .main-table {
+  max-width: 100%;
+  margin: 0 0 3.125em;
+  overflow-x: auto;
+}
+.resume-content .main-table table {
+  margin: auto;
+  width: 1000px;
+  font-size: 1em;
+}
+.resume-content .main-table table thead {
+  border-bottom: 0.125em solid var(--purple);
+}
+.resume-content .main-table table tbody td {
+  border-bottom: 0.0625em solid var(--purple);
+}
+.resume-content .resume-btn {
+  width: 120px;
+  height: 120px;
+  border-radius: 100px;
+  background-color: #f6f6f6;
+}
+
+.date-item {
+  display: flex;
+  margin-bottom: 0.625em;
+}
+.date-item p {
+  width: 7.125em;
+  display: flex;
+  align-items: center;
+  justify-content: end;
+  white-space: nowrap;
+  line-height: 1.375em;
+}
+@media (max-width: 600px) {
+  .date-item p {
+    width: 7.8125em;
+  }
+}
+
+.week-list {
+  height: 70%;
+  align-items: center;
+}
+.week-list li {
+  margin: 0.9375em 0;
+}
+.week-list li .item {
+  color: var(--purple);
+  padding: 0.625em;
+  border-radius: 6.25em;
+  border: 0.0625em solid var(--purple);
+  transition: all 0.3s;
+}
+.week-list li .item:hover {
+  color: #fff;
+  background-color: var(--purple);
+}
+.week-list li.active .item {
+  color: #fff;
+  background-color: var(--purple);
+}
+
 .v-chip {
   letter-spacing: 0.0625em;
 }

File diff suppressed because it is too large
+ 0 - 0
src/assets/css/style.css.map


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

@@ -786,6 +786,227 @@ input:focus-visible {
   }
 }
 
+.user-courses {
+  // p,
+  // a {
+  //   font-size: 0.8em;
+  // }
+
+  .v-table {
+    padding: 1.25em;
+  }
+
+  .tab-btn {
+    padding: 10px;
+    background-color: var(--blue);
+
+    button {
+      margin: 0 10px;
+      color: #fff;
+      height: 2.2em !important;
+
+      &.active {
+        color: var(--blue);
+        border-radius: 0;
+        background-color: #fff;
+        border-radius: 5px;
+      }
+    }
+  }
+
+  .search-item {
+    width: 300px;
+
+    img {
+      width: 1.5625em;
+    }
+  }
+
+  .main-table {
+    margin-top: -35px;
+  }
+
+  .table-title {
+    background-color: var(--blue);
+  }
+
+  table {
+    thead {
+      border-bottom: 0.125em solid var(--blue);
+    }
+
+    tbody {
+      td {
+        padding: 1.25em 0.625em;
+        line-height: 1.375em;
+        border-bottom: 0.0625em solid var(--blue);
+      }
+    }
+  }
+
+  .edit-item {
+    font-size: 0.8em;
+
+    .v-btn__content {
+      padding-bottom: 1px;
+    }
+  }
+
+  .courses-table {
+    tr {
+      &:hover {
+        transition: all 0.3s;
+
+        &>td {
+          cursor: pointer;
+          background-color: #f5f5f5 !important;
+        }
+      }
+
+      td {
+        text-align: center;
+      }
+    }
+
+    .v-data-table-footer {
+      padding: 1.25em 0;
+    }
+
+    .v-selection-control {
+      justify-content: center;
+    }
+
+    .account-item {
+      .v-field__input {
+        padding: 0 1.25em;
+      }
+
+      .v-label {
+        color: var(--gray);
+      }
+    }
+  }
+
+  .v-data-table-header__content {
+    justify-content: center;
+  }
+}
+
+.courses-tab {
+  .v-tab {
+    font-size: 1.125em;
+  }
+
+  .badge {
+    display: block;
+    height: 0.5em;
+    width: 0.5em;
+    background-color: red;
+    border-radius: 6.25em;
+    position: absolute;
+    right: 0;
+    top: 0.625em;
+  }
+}
+
+.resume-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+
+  .table-title {
+    width: 1000px;
+    padding: 0.625em;
+    color: #fff;
+    font-weight: 400;
+    text-align: center;
+    font-size: 1.3em;
+    letter-spacing: 2px;
+    background-color: var(--purple);
+  }
+
+  .main-table {
+    max-width: 100%;
+    margin: 0 0 3.125em;
+    overflow-x: auto;
+
+    // .table-title {
+    //   font-size: 1.3em;
+    //   background-color: var(--purple);
+    // }
+
+    table {
+      margin: auto;
+      width: 1000px;
+      font-size: 1em;
+
+      thead {
+        border-bottom: 0.125em solid var(--purple);
+      }
+
+      tbody {
+        td {
+          border-bottom: 0.0625em solid var(--purple);
+        }
+      }
+    }
+  }
+
+  .resume-btn {
+    width: 120px;
+    height: 120px;
+    border-radius: 100px;
+    background-color: #f6f6f6;
+  }
+}
+
+.date-item {
+  display: flex;
+  margin-bottom: 0.625em;
+
+  p {
+    width: 7.125em;
+    display: flex;
+    align-items: center;
+    justify-content: end;
+    white-space: nowrap;
+    line-height: 1.375em;
+
+    @media (max-width: 600px) {
+      width: 7.8125em;
+    }
+  }
+}
+
+.week-list {
+  height: 70%;
+  align-items: center;
+
+  li {
+    margin: 0.9375em 0;
+
+    .item {
+      color: var(--purple);
+      padding: 0.625em;
+      border-radius: 6.25em;
+      border: 0.0625em solid var(--purple);
+      transition: all 0.3s;
+
+      &:hover {
+        color: #fff;
+        background-color: var(--purple);
+      }
+    }
+
+    &.active {
+      .item {
+        color: #fff;
+        background-color: var(--purple);
+      }
+    }
+  }
+}
+
 .v-chip {
   letter-spacing: 0.0625em;
 }

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


+ 1 - 0
src/components/ArticleCard.vue

@@ -129,6 +129,7 @@ const props = defineProps({
   display: flex;
   align-items: center;
   color: var(--purple);
+  font-size: 1.1em;
   text-shadow: 0.0625em 0.0625em 0.3125em #fff;
 
   span {

+ 13 - 9
src/components/Map.vue

@@ -20,15 +20,19 @@ onMounted(() => {
 
   // 監聽 Pinia 存儲中的值變化
   store.$subscribe((item) => {
-    console.log("經緯度", item.events.target);
-
-    const newCenter = {
-      lat: item.events.target.lat,
-      lng: item.events.target.lng,
-    };
-    
-    // 將地圖中心設為指定的經緯度
-    map.setView([newCenter.lat, newCenter.lng], map.getZoom());
+    if (item.events.key === "lng") {
+      console.log("經緯度", item.events.target);
+
+      const newCenter = {
+        lat: item.events.target.lat,
+        lng: item.events.target.lng,
+      };
+
+      console.log("newCenter", newCenter);
+
+      // 將地圖中心設為指定的經緯度
+      map.setView([newCenter.lat, newCenter.lng], map.getZoom());
+    }
   });
 
   (async function getData() {

+ 49 - 2
src/components/Navbar.vue

@@ -12,6 +12,7 @@ let menuShow = ref(false);
 let collegeMenuShow = ref(false);
 let otherMenuShow = ref(false);
 let loginMenuShow = ref(false);
+let coursesMenuShow = ref(false);
 
 function toggleMenu() {
   collegeMenuShow.value = false;
@@ -27,6 +28,8 @@ function handleMouseEvents(name, event) {
   if (screenWidth >= 1280) {
     if (name === "college") {
       collegeMenuShow.value = event;
+    } else if (name === "courses") {
+      coursesMenuShow.value = event;
     } else if (name === "other") {
       otherMenuShow.value = event;
     } else {
@@ -184,8 +187,49 @@ function handleLogout() {
         <router-link :to="'/article'">知識文章</router-link>
       </li> -->
 
-      <li>
+      <!-- <li>
         <router-link :to="'/setup-courses'">我要開課</router-link>
+      </li> -->
+
+      <li class="position-relative">
+        <router-link
+          :to="'/setup-courses'"
+          @mouseover="coursesMenuShow = true"
+          @mouseleave="coursesMenuShow = false"
+          class="d-none d-lg-flex align-center"
+          title="前往我要開課頁面"
+          >我要開課<v-icon icon="mdi-chevron-down"></v-icon
+        ></router-link>
+
+        <a
+          href="javascript:;"
+          @click="coursesMenuShow = !coursesMenuShow"
+          class="d-block d-lg-none"
+          title="前往我要開課頁面"
+          >我要開課
+          <v-icon
+            icon="mdi-chevron-down"
+            class="toggle-icon"
+            :class="{ slider: coursesMenuShow }"
+          ></v-icon
+        ></a>
+        <div
+          class="college-slider"
+          :class="{ slider: coursesMenuShow }"
+          @mouseover="handleMouseEvents('courses', true)"
+          @mouseleave="handleMouseEvents('courses', false)"
+        >
+          <ul>
+            <li>
+              <router-link :to="'/setup-courses/tutorial'"
+                >開課教學</router-link
+              >
+            </li>
+            <li>
+              <router-link :to="'/setup-courses'">創建課程</router-link>
+            </li>
+          </ul>
+        </div>
       </li>
 
       <li class="position-relative">
@@ -329,7 +373,10 @@ function handleLogout() {
     max-width: 400px;
     margin-top: 0.3125em;
     @media (max-width: 1400px) {
-      max-width: 330px;
+      max-width: 300px;
+    }
+    @media (max-width: 1280px) {
+      max-width: 400px;
     }
     @media (max-width: 600px) {
       max-width: 300px;

+ 1 - 1
src/components/NavbarSub.vue

@@ -21,7 +21,7 @@ function toggleMenu() {
       <li>瀕危工藝</li>
       <li>知識文章</li>
       <li>
-        <router-link :to="'/login'">學登入</router-link>
+        <router-link :to="'/login'">學習者登入</router-link>
       </li>
       <li>EN</li>
       <li>

+ 12 - 12
src/language/zh.json

@@ -24,13 +24,13 @@
       "a_4_1": "流量加速器提升關注度",
       "a_4_2": "藉由工藝中心全台課程平台再加上 ChoozMo 的 AI 智能關鍵字推播,雙管齊下,成功吸引全台學徒報名!",
       "q_2": "什麼樣的課程可以在「工藝學校平台」上架?",
-      "a_2": "工藝所上傳之課程內容應符合手作工藝之範疇,所訂定之價格應屬合理之範圍,並須確保上傳內容未侵害第三人之智慧財產權或營業秘密等相關法律問題。 <br> 開課地點必須在安全、且便於到達的地方,如果有更換地點的臨時需求,必須以信件或電話公告給報名學得知。",
+      "a_2": "工藝教育者所上傳之課程內容應符合手作工藝之範疇,所訂定之價格應屬合理之範圍,並須確保上傳內容未侵害第三人之智慧財產權或營業秘密等相關法律問題。 <br> 開課地點必須在安全、且便於到達的地方,如果有更換地點的臨時需求,必須以信件或電話公告給報名學習者得知。",
       "q_3": "使用「工藝學校平台」開課需要付費嗎?",
-      "a_3": "在工藝學校平台開設手作工藝課程是完全不需要付費,平台不會向工藝家及報名學員收取任何費用。上課細節及收費方式,也完全由工藝家與學員自行聯繫溝通。",
-      "q_4": "工藝該如何上傳課程?",
-      "a_4": "工藝只要登入平台,就可以在「我要開課」的欄位處,按照網站的指示步驟輸入內容資訊,就可以輕鬆上傳自己的課程。未來課程報名截止後,也只要用開課時所登錄的帳號,即可查詢學員名單,主動聯繫學員上課細節。",
-      "q_5": "工藝上傳課程後,要如何知道該課程是否上傳成功?",
-      "a_5": "工藝按照步驟指示依序填寫課程資料並確認送出後,待系統完成審核後,將寄送 E-mail 通知您課程上架是否成功。"
+      "a_3": "在工藝學校平台開設手作工藝課程是完全不需要付費,平台不會向工藝教育者及報名學習者收取任何費用。上課細節及收費方式,也完全由工藝教育者與學習者自行聯繫溝通。",
+      "q_4": "工藝教育者該如何上傳課程?",
+      "a_4": "工藝教育者只要登入平台,就可以在「我要開課」的欄位處,按照網站的指示步驟輸入內容資訊,就可以輕鬆上傳自己的課程。未來課程報名截止後,也只要用開課時所登錄的帳號,即可查詢學習者名單,主動聯繫學習者上課細節。",
+      "q_5": "工藝教育者上傳課程後,要如何知道該課程是否上傳成功?",
+      "a_5": "工藝教育者按照步驟指示依序填寫課程資料並確認送出後,待系統完成審核後,將寄送 E-mail 通知您課程上架是否成功。"
     }
   },
   "crafts": {
@@ -60,8 +60,8 @@
       "content_1": "「一日學徒平台(簡稱本網站)」係由國立台灣工藝研究發展中心(以下稱本中心)建置之工藝相關課程、體驗活動等資訊媒合網站,並依據本服務條款提供服務(以下稱「本服務」)。當您使用本服務時,即表示您已閱讀、瞭解並同意接受本服務條款之所有內容。本中心有權依據本網站提供服務之需求,於任何時間修改或變更本服務條款之內容,建議您隨時注意該等修改或變更。您於任何修改或變更後繼續使用本服務,視為您已閱讀、瞭解並同意接受該等修改或變更。如果您不同意本服務條款的內容,或者您所屬的國家或地域排除本服務條款內容之全部或一部時,您應立即停止使用本服務。",
       "title_2": "二、帳號登入與本服務使用",
       "content_2_1": "(一) 使用者得透過第三方平台帳戶(如:FB、Google等,以本網站當時支援之第三方平台為準)登入使用本網站。本中心將儲存各該第三方平台所允許儲存作為識別使用者帳戶之資訊。",
-      "content_2_2": "(二) 若您為工藝家,擬透過本網站刊登工藝相關課程或體驗活動等資訊,您須另行同意「工藝家上傳課程同意規範」,始得上傳課程;若您擬透過本網站報名相關課程,您須另行同意「民眾報名須知」,始得報名課程。",
-      "content_2_3": "(三) 無論是工藝家或是報名的學員,皆應依據其刊登或報名之課程或活動資訊履約,如有未依約履行經查證屬實,本中心得終止或暫停本服務之提供。如有更改課程或活動舉辦或參加之需求,應由工藝家與報名的學員自行聯繫處理,並保留相關證據資料,以避免爭議。",
+      "content_2_2": "(二) 若您為工藝教育者,擬透過本網站刊登工藝相關課程或體驗活動等資訊,您須另行同意「工藝教育者上傳課程同意規範」,始得上傳課程;若您擬透過本網站報名相關課程,您須另行同意「民眾報名須知」,始得報名課程。",
+      "content_2_3": "(三) 無論是工藝教育者或是報名的學習者,皆應依據其刊登或報名之課程或活動資訊履約,如有未依約履行經查證屬實,本中心得終止或暫停本服務之提供。如有更改課程或活動舉辦或參加之需求,應由工藝教育者與報名的學習者自行聯繫處理,並保留相關證據資料,以避免爭議。",
       "title_3": "三、使用者的守法義務及承諾",
       "content_3": "使用者同意並保證不得利用本服務從事侵害他人權益或違法之行為,包括但不限於:",
       "content_3_1": "(一) 上載、張貼、公布或傳送任何不實、詐欺、誹謗、侮辱、具威脅性、攻擊性、不雅、猥褻、不實、違反公共秩序或善良風俗或其他不法之文字、圖片、影音或任何形式的檔案於本服務上。",
@@ -91,7 +91,7 @@
       "content_5_2_4_1": "(1) 配合司法單位合法的調查。",
       "content_5_2_4_2": "(2) 基於善意相信揭露為法律需要,或為維護和改進網站服務而用於管理。",
       "title_6": "六、責任限制",
-      "content_6_1": "(一) 本網站為工藝相關及跨域課程或體驗活動之資訊媒合平台,提供開課單位/老師刊登課程資訊,並由開課單位/老師自行處理學報名事宜。本網站不對開課單位/老師開設該等課程之合法性或其課程服務之品質為任何保證或承諾,並不擔保該 等課程之順利舉辦,僅單純提供課程資訊之媒合,未對課程為任何收費或管理,不對課程之履約負任何法律上之責任。",
+      "content_6_1": "(一) 本網站為工藝相關及跨域課程或體驗活動之資訊媒合平台,提供開課單位/老師刊登課程資訊,並由開課單位/老師自行處理學習者報名事宜。本網站不對開課單位/老師開設該等課程之合法性或其課程服務之品質為任何保證或承諾,並不擔保該 等課程之順利舉辦,僅單純提供課程資訊之媒合,未對課程為任何收費或管理,不對課程之履約負任何法律上之責任。",
       "content_6_2": "(二) 本中心係以本服務之「現狀」提供服務,不保證所提供之服務完全符合使用者的需要,亦不擔保本服務無瑕疵,包括但不限於本服務穩定不中斷、準時、安全、不具病毒或沒有錯誤等。",
       "content_6_3": "(三) 使用者所傳輸或張貼於本服務之文字、圖片及其它資料,應自行備份;本中心對於任何原因導致前述資料全部或一部之滅失、毀損,不負任何責任。",
       "title_7": "七、服務終止及違約終止",
@@ -118,8 +118,8 @@
     "step_1_2_5": "照片",
     "step_1_2_6": "簡介",
     "step_1_3": "您的據點將會收納進「全台工藝地圖」中 <br> 讓在附近的使用者,可以快速地找到您的課程",
-    "step_2_title": "Step2 工藝教學履歷",
-    "step_2_1": "每位工藝家的專屬履歷 <br> 會顯示於您的課程下方,讓學員更認識您!",
+    "step_2_title": "Step2 工藝教育者教學履歷",
+    "step_2_1": "每位工藝教育者的專屬履歷 <br> 會顯示於您的課程下方,讓學習者更認識您!",
     "step_2_2": "此處會需要填寫:",
     "step_2_2_1": "老師姓名",
     "step_2_2_2": "全職/兼職",
@@ -129,7 +129,7 @@
     "step_2_2_6": "上傳照片",
     "step_2_2_7": "相關證照",
     "step_2_2_8": "社群媒體",
-    "step_2_3": "您的介紹將會顯示於課程介紹後方 <br> 填寫越完整、越能讓學信任老師",
+    "step_2_3": "您的介紹將會顯示於課程介紹後方 <br> 填寫越完整、越能讓學習者信任老師",
     "step_3_title": "Step3 創建課程",
     "step_3_1": "完整且清楚的課程建立,是連接工藝老師與學徒的重要橋樑! <br> 在相同的據點底下,可以創建很多不同主題、不同時段的課程",
     "step_3_2": "此處會需要填寫:",

+ 9 - 3
src/router/index.js

@@ -44,11 +44,12 @@ const Campus = defineAsyncComponent(() => import('@/views/CollegeGroup/Life/Camp
 const CraftJourney = defineAsyncComponent(() => import('@/views/CollegeGroup/Life/CraftJourney.vue')); // 工藝行旅
 const Shop = defineAsyncComponent(() => import('@/views/CollegeGroup/Life/Shop.vue')); // 旅物 Shop
 
-// 學頁面
+// 學習者頁面
 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 Courses = defineAsyncComponent(() => import('@/views/User/Courses.vue')); // 我的開課
+const userProposal = defineAsyncComponent(() => import('@/views/User/Proposal.vue')); // 我的提案
+const userCourses = defineAsyncComponent(() => import('@/views/User/Courses.vue')); // 我的開課
 const FavoriteClass = defineAsyncComponent(() => import('@/views/User/FavoriteClass.vue')); // 我的收藏
 const Setting = defineAsyncComponent(() => import('@/views/User/Setting.vue')); // 帳戶設定
 
@@ -83,10 +84,15 @@ const routes = [
         name: 'Passport',
         component: Passport,
       },
+      {
+        path: 'proposal',
+        name: 'userProposal',
+        component: userProposal,
+      },
       {
         path: 'courses',
         name: 'Courses',
-        component: Courses,
+        component: userCourses,
       },
       {
         path: 'favorite-class',

+ 28 - 5
src/stores/store.js

@@ -33,11 +33,12 @@ export const useMainStore = defineStore('mainStore', {
         this.token = null;
         this.loginState = false;
       }
-      
+
       return this.loginState;
     },
     logout() {
       localStorage.removeItem("token");
+      console.log('logout');
       return this.router.push("/");
     },
     getImageUrl(name) {
@@ -55,8 +56,9 @@ export const useMainStore = defineStore('mainStore', {
           `https://cmm.ai:8088/api/information?token=${this.token}`
         );
         console.log('response', response);
-        if (!response.data.msg) {
-          store.logout();
+        if (response.data.msg === "no access" || !response.data.msg) {
+          console.log('getProfile logout');
+          this.logout();
           return;
         } else {
           this.setProfile(response.data.msg);
@@ -98,6 +100,23 @@ export const useMainStore = defineStore('mainStore', {
 
       return `${year}-${month}-${day}`;
     },
+    // 處理時間格式(日期+時間)
+    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`;
+    },
     getPDF(name) {
       return `https://ntcri.org/pdf/${name}.pdf`;
     },
@@ -108,7 +127,7 @@ export const useMainStore = defineStore('mainStore', {
     // 開啟登入視窗
     openLoginDialog() {
       this.loginDialog = true;
-      console.log('this.loginDialog',this.loginDialog);
+      console.log('this.loginDialog', this.loginDialog);
     },
     // 調整字串
     formatString(content) {
@@ -116,7 +135,7 @@ export const useMainStore = defineStore('mainStore', {
       const decodedText = parser.parseFromString(content, "text/html").body.textContent;
 
       return decodedText;
-      
+
       // if (content) {
       //   content = content.replace(/&lt;/g, "<");
       //   content = content.replace(/&gt;/g, ">");
@@ -142,6 +161,10 @@ export const useMainStore = defineStore('mainStore', {
         return `https://ntcri.org/${data.cover_img}`;
       }
     },
+    setAlert(title) {
+      alert(`${title}!點擊確認後將重新整理頁面。`);
+      window.location.reload();
+    },
     checkTokenExpiration() {
       if (this.token) {
         // 解碼 JWT

+ 1 - 1
src/utils/useReadList.js

@@ -28,7 +28,7 @@ const readList = [
     img: store.getImageUrl("crafts/知識文章-25.png"),
     fileName: "Fiber_With_Crafts",
     content:
-      "繼 2018 年工藝中心邀請荷蘭 MDD (Material Driven Design)計畫主持教授 Elvin Karana 以台灣東部花蓮新社葛瑪蘭族香蕉絲纖維工藝作為 CAMEL.實驗室示範先導工作化並出版《新纖維》一書後,2019 年隨即由 CAMEL.實驗室再次策畫為期 8 天協同設計工作坊,邀請法國巴黎布勒學院 École Boulle Antoine FERMEY 教授並帶領 14 位法國的設計系學生來台,與臺灣資深纖維工藝家邱秀蓮、王梅容老師、檳榔工藝推廣劉大衛老師以及徵選的 14 位臺灣工藝家,共同以跨國界分組的方式,從工藝技術與設計方法並行來探索臺灣構樹與檳榔材料的在地文化與價值轉化。不同於2018年《新纖維》計畫在材質修補 (Material tinkering) 的階段性,這一次的協同設計工作坊,以更完整的材料性質瞭解來展開工藝技術性的可能發展...",
+      "繼 2018 年工藝中心邀請荷蘭 MDD (Material Driven Design)計畫主持教授 Elvin Karana 以台灣東部花蓮新社葛瑪蘭族香蕉絲纖維工藝作為 CAMEL.實驗室示範先導工作化並出版《新纖維》一書後,2019 年隨即由 CAMEL.實驗室再次策畫為期 8 天協同設計工作坊,邀請法國巴黎布勒學院 École Boulle Antoine FERMEY 教授並帶領 14 位法國的設計系學生來台,與臺灣資深纖維工藝教育者邱秀蓮、王梅容老師、檳榔工藝推廣劉大衛老師以及徵選的 14 位臺灣工藝教育者,共同以跨國界分組的方式,從工藝技術與設計方法並行來探索臺灣構樹與檳榔材料的在地文化與價值轉化。不同於2018年《新纖維》計畫在材質修補 (Material tinkering) 的階段性,這一次的協同設計工作坊,以更完整的材料性質瞭解來展開工藝技術性的可能發展...",
   },
   {
     title: "循絲 <br> 臺灣蠶絲材料探究及新用",

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

@@ -56,7 +56,7 @@ const breadcrumbs = reactive([
           <h3 class="mb-5">旅物 SHOP</h3>
           <p>
             《旅物 •
-            SHOP》位於自然生態豐富的臺灣工藝文化園區,明亮穿透的空間設計,連結窗外工藝植物園,到室內的生活工藝選物,這是一間由土地孕育而生的工藝選品店,從採擷、手作,到用物於生活,是材料與手、物與物、人與物、人與人彼此流動交會,共度美好日常的店頭。包含崇尚自然循環,創造生活風格的臺灣在地「臺灣綠工藝」品牌、扶植青年創業的「青年孵化器」、工藝中心開發的周邊商品「品工藝」以及在地咖啡品牌「一日咖啡」等四個子品牌,推廣「自然、循環、平衡、寬容、生命力」的綠工藝精神,更是鼓勵青年實驗開店、青年工藝的孵化平臺。
+            SHOP》位於自然生態豐富的臺灣工藝文化園區,明亮穿透的空間設計,連結窗外工藝植物園,到室內的生活工藝選物,這是一間由土地孕育而生的工藝選品店,從採擷、手作,到用物於生活,是材料與手、物與物、人與物、人與人彼此流動交會,共度美好日常的店頭。包含崇尚自然循環,創造生活風格的臺灣在地「臺灣綠工藝」品牌、扶植青年創業的「青年孵化器」、工藝中心開發的周邊商品「品工藝」以及在地咖啡品牌「一日咖啡」等四個子品牌,推廣「自然、循環、平衡、寬容、生命力」的綠工藝精神,更是鼓勵青年實驗開店、青年工藝教育者的孵化平臺。
           </p>
         </v-col>
       </v-row>
@@ -87,8 +87,8 @@ const breadcrumbs = reactive([
             全民工藝教育倡議平台|生活 x 手作 x 體驗 <br />
             以「手做」,經歷生活中的一切美好! <br />
             用「一日」,體會過去師徒制三年四個月的工藝學習精神與態度! <br />
-            由工藝親自規畫各類手作工藝課程,隨時提供最新課程資訊,民眾依據喜好選擇課程,
-            體驗半天或一天的手作工藝學習,尋找就近的工藝學習各種小物知識經驗與技藝。
+            由工藝教育者親自規畫各類手作工藝課程,隨時提供最新課程資訊,民眾依據喜好選擇課程,
+            體驗半天或一天的手作工藝學習,尋找就近的工藝教育者學習各種小物知識經驗與技藝。
           </p>
         </v-col>
         <v-col cols="12" md="4" lg="5" class="pa-0 overflow-hidden">
@@ -105,7 +105,7 @@ const breadcrumbs = reactive([
         <v-col cols="12" md="8" lg="7" class="content">
           <h3 class="mb-10">校園扎根</h3>
           <p>
-            工藝校園扎根計畫以工藝融入學校教育,媒合教師與工藝,跨域合作開發具創造力、觀察力、美學素養、批判思考以及手作體驗的工藝教材教案,實踐工藝之於學習、生活的價值。符合教育部
+            工藝校園扎根計畫以工藝融入學校教育,媒合教師與工藝教育者,跨域合作開發具創造力、觀察力、美學素養、批判思考以及手作體驗的工藝教材教案,實踐工藝之於學習、生活的價值。符合教育部
             12 年國教課綱的「核心素養」,以及 STEAM
             教育的五大精神「跨領域、動手做、生活應用、解決問題及五感學習」,以「做中學,學中做」培育新世代的工藝人才。
           </p>

+ 1 - 1
src/views/CollegeGroup/Life/Apprentice/About.vue

@@ -14,7 +14,7 @@
       <br />
       用「手做」,來經歷生活中一切的美好! <br />
       用「一日」,來體會傳統師徒制三年四個月的工藝學習精神與態度! <br />
-      透過「一日學徒」網站,由工藝親自規畫各類手作工藝課程,並隨時提供最新課程資訊,民眾可以依據喜好選擇課程,體驗半天或一天的手作工藝學習,尋找就近的工藝學習各種小物知識經驗與技藝。
+      透過「一日學徒」網站,由工藝教育者親自規畫各類手作工藝課程,並隨時提供最新課程資訊,民眾可以依據喜好選擇課程,體驗半天或一天的手作工藝學習,尋找就近的工藝教育者學習各種小物知識經驗與技藝。
       「一日學徒」不僅提供工藝社群持續創作,也希望讓一般民眾隨時隨地擁有學習手作工藝的愉悅體驗及感動,以積極推廣全民工藝教育的理念,達成
       Craft For All 的目標。
     </p>

+ 16 - 16
src/views/CollegeGroup/Life/Apprentice/FAQ.vue

@@ -4,47 +4,47 @@ import { ref, reactive } from "vue";
 const faqList = [
   {
     q: "工藝中心創立「一日學徒」平台的主要用意是什麼?",
-    a: "為了提供工藝家 (課程供應端) 全國統一招生平台,擴大招生對象,讓工藝家可以持續創作且簡易上傳課程訊息。也提供一般民眾 (學習需求端) 隨時查詢課程資訊,就地選擇有興趣的課程,推廣工藝學習教育。透過工藝輕體驗過程,來體會傳統師徒制三年四個月的工藝學習精神與態度,也為工坊帶來人流,促進文化與經濟之加成效益。",
+    a: "為了提供工藝教育者 (課程供應端) 全國統一招生平台,擴大招生對象,讓工藝教育者可以持續創作且簡易上傳課程訊息。也提供一般民眾 (學習需求端) 隨時查詢課程資訊,就地選擇有興趣的課程,推廣工藝學習教育。透過工藝輕體驗過程,來體會傳統師徒制三年四個月的工藝學習精神與態度,也為工坊帶來人流,促進文化與經濟之加成效益。",
   },
   {
-    q: "工藝開課需要具備什麼資格或條件嗎?",
-    a: "工藝所上傳之課程內容應符合手作工藝之範疇,所訂定之價格應屬合理之範圍,並須確保上傳內容未侵害第三人之智慧財產權或營業秘密等相關法律問題。",
+    q: "工藝教育者開課需要具備什麼資格或條件嗎?",
+    a: "工藝教育者所上傳之課程內容應符合手作工藝之範疇,所訂定之價格應屬合理之範圍,並須確保上傳內容未侵害第三人之智慧財產權或營業秘密等相關法律問題。",
   },
   {
     q: "使用「一日學徒」平台開設課程需要收費嗎?",
-    a: "在「一日學徒」平台開設手作工藝課程是完全不需要付費,工藝中心也不會向工藝家及報名學員收取任何費用。上課細節及收費方式,也完全由工藝家與學員自行聯繫溝通。",
+    a: "在「一日學徒」平台開設手作工藝課程是完全不需要付費,工藝中心也不會向工藝教育者及報名學習者收取任何費用。上課細節及收費方式,也完全由工藝教育者與學習者自行聯繫溝通。",
   },
   {
-    q: "工藝該如何上傳自己的課程呢?",
-    a: "工藝家只要用 Google 或臉書(FB)帳號登入「一日學徒」平台,就可以在「工藝家開課」的欄位處,按照網站的指示步驟輸入內容資訊,就可以輕鬆上傳自己的課程。未來課程報名截止後,也只要用開課時所登錄的帳號,即可查詢學員名單,主動聯繫學員上課細節。",
+    q: "工藝教育者該如何上傳自己的課程呢?",
+    a: "工藝教育者只要用 Google 或臉書(FB)帳號登入「一日學徒」平台,就可以在「工藝教育者開課」的欄位處,按照網站的指示步驟輸入內容資訊,就可以輕鬆上傳自己的課程。未來課程報名截止後,也只要用開課時所登錄的帳號,即可查詢學習者名單,主動聯繫學習者上課細節。",
   },
   {
-    q: "工藝開課的授課計畫表時間應如何設定?",
-    a: "在授課計畫表裡會有(1)授課日期的起迄日(2)報名截止日(3)授課時間。工藝可先設定要上課的起始與結束日期(月曆選單);然後選擇報名截止日期(為了方便工藝們準備耗材及課程,我們建議報名截止日期設定在開課前7天);最後,選擇您上課的時段(時間選單),即可完成授課日期與時間設定。如果工藝規劃的是週期性課程,我們則建議您在備註欄位增加說明(例如:本課程為每周六上午 9:00-12:00 等補充說明)",
+    q: "工藝教育者開課的授課計畫表時間應如何設定?",
+    a: "在授課計畫表裡會有(1)授課日期的起迄日(2)報名截止日(3)授課時間。工藝教育者可先設定要上課的起始與結束日期(月曆選單);然後選擇報名截止日期(為了方便工藝教育者們準備耗材及課程,我們建議報名截止日期設定在開課前7天);最後,選擇您上課的時段(時間選單),即可完成授課日期與時間設定。如果工藝教育者規劃的是週期性課程,我們則建議您在備註欄位增加說明(例如:本課程為每周六上午 9:00-12:00 等補充說明)",
   },
   {
     q: "民眾報名課程應如何收費?",
-    a: "在授課計畫表裡工藝家應標明課程所需費用(新台幣)及繳費方式。本平台目前僅提供工藝家現場收費方式,工藝家如需請學員事先匯款或收取保證金等要求,請於課程費用欄位中備註說明須繳交之金額並提供帳戶,俾便學匯款。",
+    a: "在授課計畫表裡工藝教育者應標明課程所需費用(新台幣)及繳費方式。本平台目前僅提供工藝教育者現場收費方式,工藝教育者如需請學習者事先匯款或收取保證金等要求,請於課程費用欄位中備註說明須繳交之金額並提供帳戶,俾便學習者匯款。",
   },
   {
-    q: "工藝上傳課程後,要如何知道該課程是否上傳成功?",
-    a: "工藝按照步驟指示依序填寫課程資料並確認送出後,系統將主動寄送 E-mail 通知您課程審核中的訊息,本中心也將儘快審核工藝家課程並協助上架。待課程通過審核上架後,系統將再次寄送 E-mail 通知工藝家課程上架是否成功。",
+    q: "工藝教育者上傳課程後,要如何知道該課程是否上傳成功?",
+    a: "工藝教育者按照步驟指示依序填寫課程資料並確認送出後,系統將主動寄送 E-mail 通知您課程審核中的訊息,本中心也將儘快審核工藝教育者課程並協助上架。待課程通過審核上架後,系統將再次寄送 E-mail 通知工藝教育者課程上架是否成功。",
   },
   {
-    q: "工藝家該如何知道報名學員的人數或聯絡資訊呢?",
-    a: "報名期限截止後,系統將會寄送 E-mail 通知報名學的名單給您。您也可以利用開課時所登錄的帳號(臉書 Facebook 或 Google 帳號)進到課程中,即可檢視該課程學員名單,也請您主動聯繫學員並說明上課細節及應注意事項。 <br> 報名期間內如您需要了解當下有多少學員報名,也可至藝文活動平台→活動分類→一日學徒區塊中,找到您所開立的課程,即可檢視該課程當下報名人數(因個資因素,藝文平台僅提供人數及學姓名)。 <br> 藝文活動平台網址:<a href='https://event.culture.tw/NTCRI/portal/Index/IndexAction' target='_blank'>https://event.culture.tw/NTCRI/portal/Index/IndexAction</a>",
+    q: "工藝教育者該如何知道報名學習者的人數或聯絡資訊呢?",
+    a: "報名期限截止後,系統將會寄送 E-mail 通知報名學習者的名單給您。您也可以利用開課時所登錄的帳號(臉書 Facebook 或 Google 帳號)進到課程中,即可檢視該課程學習者名單,也請您主動聯繫學習者並說明上課細節及應注意事項。 <br> 報名期間內如您需要了解當下有多少學習者報名,也可至藝文活動平台→活動分類→一日學徒區塊中,找到您所開立的課程,即可檢視該課程當下報名人數(因個資因素,藝文平台僅提供人數及學習者姓名)。 <br> 藝文活動平台網址:<a href='https://event.culture.tw/NTCRI/portal/Index/IndexAction' target='_blank'>https://event.culture.tw/NTCRI/portal/Index/IndexAction</a>",
   },
   {
     q: "授課計畫表內的課程類別是否需要勾選?",
-    a: "工藝家可以根據您教學的課程內容勾選課程類別,方便學員在使用搜尋功能時可立即找到想要學習的媒材項目,如不勾選,也可按下一步送出資訊。",
+    a: "工藝教育者可以根據您教學的課程內容勾選課程類別,方便學習者在使用搜尋功能時可立即找到想要學習的媒材項目,如不勾選,也可按下一步送出資訊。",
   },
   {
     q: "授課計畫表內的完成圖片是否一定要上傳?",
-    a: "不一定要放照片。不過建議如果工藝家可以提供精采作品照片(或是創作過程照片),將讓學員更加了解課程實際完成的成果或工藝家創作工序,尤其是拍攝精美的照片,更容易達到推廣與招生的效果。",
+    a: "不一定要放照片。不過建議如果工藝教育者可以提供精采作品照片(或是創作過程照片),將讓學習者更加了解課程實際完成的成果或工藝教育者創作工序,尤其是拍攝精美的照片,更容易達到推廣與招生的效果。",
   },
   {
     q: "民眾要如何選擇課程呢?",
-    a: "民眾可在一日學徒平台瀏覽各類型的手作工藝課程,也可以利用關鍵字、地區或材質類型搜尋有興趣的課程。當您選定課程後,只要按下課程下方的「我要報名」鍵,系統將立即引導您連結至報名網站,您只要按系統指引的報名步驟,就可以輕鬆完成課程報名。如果對於課程內容想要了解更多,也可在課程資訊中找到工藝家的聯絡方式,主動聯絡工藝家以了解更多相關內容。",
+    a: "民眾可在一日學徒平台瀏覽各類型的手作工藝課程,也可以利用關鍵字、地區或材質類型搜尋有興趣的課程。當您選定課程後,只要按下課程下方的「我要報名」鍵,系統將立即引導您連結至報名網站,您只要按系統指引的報名步驟,就可以輕鬆完成課程報名。如果對於課程內容想要了解更多,也可在課程資訊中找到工藝教育者的聯絡方式,主動聯絡工藝教育者以了解更多相關內容。",
   },
   {
     q: "操作平台過程中如有任何問題,該如何處理?",

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

@@ -52,13 +52,13 @@ const breadcrumbs = reactive([
   <v-row class="px-md-8 campus-content">
     <v-col cols="12">
       <h4>
-        老師 x 工藝 x 學生 <br />
+        老師 x 工藝教育者 x 學生 <br />
         講述 x 手作 <br />
         價值 x 實踐 <br />
         扎根 x 擴散
       </h4>
       <p class="mt-10">
-        工藝校園扎根計畫以工藝融入學校教育,媒合教師與工藝,跨域合作開發具創造力、觀察力、美學素養、批判思考以及手作體驗的工藝教材教案,實踐工藝之於學習、生活的價值。符合教育部12年國教課綱的「核心素養」,以及STEAM教育的五大精神「跨領域、動手做、生活應用、解決問題及五感學習」,以「做中學,學中做」培育新世代的工藝人才。
+        工藝校園扎根計畫以工藝融入學校教育,媒合教師與工藝教育者,跨域合作開發具創造力、觀察力、美學素養、批判思考以及手作體驗的工藝教材教案,實踐工藝之於學習、生活的價值。符合教育部12年國教課綱的「核心素養」,以及STEAM教育的五大精神「跨領域、動手做、生活應用、解決問題及五感學習」,以「做中學,學中做」培育新世代的工藝人才。
       </p>
     </v-col>
 

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

@@ -128,7 +128,7 @@ const youthImgs = [
       <v-col cols="12">
         <p>
           《旅物 •
-          SHOP》位於自然生態豐富的臺灣工藝文化園區,明亮穿透的空間設計,連結窗外工藝植物園,到室內的生活工藝選物,這是一間由土地孕育而生的工藝選品店,從採擷、手作,到用物於生活,是材料與手、物與物、人與物、人與人彼此流動交會,共度美好日常的店頭。包含崇尚自然循環,創造生活風格的臺灣在地「臺灣綠工藝」品牌、扶植青年創業的「青年孵化器」、工藝中心開發的周邊商品「品工藝」以及在地咖啡品牌「一日咖啡」等四個子品牌,推廣「自然、循環、平衡、寬容、生命力」的綠工藝精神,更是鼓勵青年實驗開店、青年工藝的孵化平臺。
+          SHOP》位於自然生態豐富的臺灣工藝文化園區,明亮穿透的空間設計,連結窗外工藝植物園,到室內的生活工藝選物,這是一間由土地孕育而生的工藝選品店,從採擷、手作,到用物於生活,是材料與手、物與物、人與物、人與人彼此流動交會,共度美好日常的店頭。包含崇尚自然循環,創造生活風格的臺灣在地「臺灣綠工藝」品牌、扶植青年創業的「青年孵化器」、工藝中心開發的周邊商品「品工藝」以及在地咖啡品牌「一日咖啡」等四個子品牌,推廣「自然、循環、平衡、寬容、生命力」的綠工藝精神,更是鼓勵青年實驗開店、青年工藝教育者的孵化平臺。
         </p>
       </v-col>
       <v-col cols="12" md="6" lg="5">
@@ -197,7 +197,7 @@ const youthImgs = [
       <h3 class="block-title">青年孵化器</h3>
       <p class="ma-5 ma-sm-10">
         青年孵化器,青年工藝創業實驗區 <br />
-        鼓勵青年實驗開店投入工藝文化產業經營,讓投身於創作的青年工藝,亦有機會找出新方法來重新定義產品、服務及市場,提升未來青年投入工藝文化產業意願,減緩工藝產業人力流失與斷鏈危機。
+        鼓勵青年實驗開店投入工藝文化產業經營,讓投身於創作的青年工藝教育者,亦有機會找出新方法來重新定義產品、服務及市場,提升未來青年投入工藝文化產業意願,減緩工藝產業人力流失與斷鏈危機。
       </p>
       <v-row class="youth-imgs">
         <v-col

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

@@ -78,7 +78,7 @@ async function getGroup(id) {
 //       break;
 //     case "線上工藝":
 //       text =
-//         "線上工藝包含中國古典文化的古老工藝、具有台灣在地特色的民俗工藝、工藝匠人的創作故事,是工藝的影音寶庫!";
+//         "線上工藝包含中國古典文化的古老工藝、具有台灣在地特色的民俗工藝、工藝匠人的創作故事,是工藝教育者的影音寶庫!";
 //       break;
 //     case "希望工程":
 //       text =
@@ -86,7 +86,7 @@ async function getGroup(id) {
 //       break;
 //     case "生活工藝":
 //       text =
-//         "從精選綠色工藝品牌「旅物SHOP」,到深度工藝之旅「工藝行旅」。「一日學徒」帶您用一天的時間體會工藝師的一生。以及培養新一代工藝啓發潛能的「校園扎根」。";
+//         "從精選綠色工藝品牌「旅物SHOP」,到深度工藝之旅「工藝行旅」。「一日學徒」帶您用一天的時間體會工藝師的一生。以及培養新一代工藝教育者啓發潛能的「校園扎根」。";
 //       break;
 //   }
 

+ 39 - 9
src/views/CourseDetail.vue

@@ -276,6 +276,22 @@ let currentTitle = computed(() => {
       return "課程報名表";
   }
 });
+
+// 判斷報名時間是否已截止
+function isDateExpired(dateString) {
+  const registrationEndDate = new Date(dateString);
+  // 獲取當前時間
+  const currentDate = new Date();
+
+  console.log('registrationEndDate',registrationEndDate);
+  console.log('currentDate',currentDate);
+
+  if (registrationEndDate > currentDate) {
+    return true; // 日期已過期
+  } else {
+    return false; // 日期未過期
+  }
+}
 </script>
 
 <template>
@@ -332,6 +348,10 @@ let currentTitle = computed(() => {
                   }}
                 </td>
               </tr>
+              <tr>
+                <td>課程地點</td>
+                <td>{{ course.data.address }}</td>
+              </tr>
               <tr v-if="course.data.organizer !== ''">
                 <td>主辦單位</td>
                 <td>
@@ -420,7 +440,7 @@ let currentTitle = computed(() => {
               <tr>
                 <th>場次名稱</th>
                 <th>開始/結束時間</th>
-                <th>課程地點</th>
+                <th v-show="session.data[0].location">課程教室</th>
                 <th>課程費用</th>
                 <th>課程名額</th>
                 <th>講師</th>
@@ -430,15 +450,15 @@ let currentTitle = computed(() => {
             </thead>
             <tbody>
               <tr v-for="(item, index) in session.data" :key="index">
-                <td>{{ item.class_name }}</td>
+                <td width="20%">{{ item.class_name }}</td>
                 <td width="20%">
                   {{ moment(`${item.start_time}`).format("YYYY/MM/DD H:mm") }}
                   <br />
                   ~ <br />
                   {{ moment(`${item.end_time}`).format("YYYY/MM/DD H:mm") }}
                 </td>
-                <td width="20%">
-                  {{ isInner === 0 ? course.data.school : item.location }}
+                <td v-show="item.location !== ''">
+                  {{ item.location }}
                 </td>
                 <td>{{ item.fee_method }}</td>
                 <td>{{ item.number_limit }}</td>
@@ -469,6 +489,7 @@ let currentTitle = computed(() => {
                   >
                     <template v-slot:activator="{ props }">
                       <v-btn
+                      v-if="isDateExpired(item.registration_end)"
                         @click="signUp(index)"
                         v-bind="props"
                         text="報名"
@@ -476,6 +497,14 @@ let currentTitle = computed(() => {
                         color="brown"
                       >
                       </v-btn>
+                      <v-btn
+                      v-else
+                        text="已截止"
+                        rounded="xl"
+                        color="brown"
+                       disabled
+                      >
+                      </v-btn>
                     </template>
 
                     <template v-slot:default="{ isActive }">
@@ -503,7 +532,7 @@ let currentTitle = computed(() => {
                                   <span class="font-weight-medium"
                                     >【{{ item.class_name }}】</span
                                   >
-                                  業務而獲取您的個人資料:姓名、身分證字號、連絡方式〈包括但不限於電話號碼、E-MAIL、居住或工作地址〉等得以直接或間接識別您個人之資料。若您所提供之個人資料,經檢舉或工藝中心發現不足以確認您的身分真實性或其他個人資料冒用、盜用、資料不實等情形,工藝中心有權暫時停止提供對您的服務。
+                                  業務而獲取您的個人資料:姓名、連絡方式〈包括但不限於電話號碼、E-MAIL、居住或工作地址〉等得以直接或間接識別您個人之資料。若您所提供之個人資料,經檢舉或工藝中心發現不足以確認您的身分真實性或其他個人資料冒用、盜用、資料不實等情形,工藝中心有權暫時停止提供對您的服務。
                                 </p>
 
                                 <p>
@@ -778,7 +807,7 @@ let currentTitle = computed(() => {
                                     <span class="font-weight-medium"
                                       >【{{ item.class_name }}】</span
                                     >
-                                    業務而獲取您的個人資料:姓名、身分證字號、連絡方式〈包括但不限於電話號碼、E-MAIL、居住或工作地址〉等得以直接或間接識別您個人之資料。若您所提供之個人資料,經檢舉或工藝中心發現不足以確認您的身分真實性或其他個人資料冒用、盜用、資料不實等情形,工藝中心有權暫時停止提供對您的服務。
+                                    業務而獲取您的個人資料:姓名、連絡方式〈包括但不限於電話號碼、E-MAIL、居住或工作地址〉等得以直接或間接識別您個人之資料。若您所提供之個人資料,經檢舉或工藝中心發現不足以確認您的身分真實性或其他個人資料冒用、盜用、資料不實等情形,工藝中心有權暫時停止提供對您的服務。
                                   </p>
 
                                   <p>
@@ -1133,8 +1162,8 @@ let currentTitle = computed(() => {
   }
 
   .sessions {
-    padding: 1.5625em 3.125em 3.125em;
-    letter-spacing: 0.0625em;
+    padding: 1.5em 1.5em .5em;
+    letter-spacing: 0.1em;
     overflow-x: scroll;
     table {
       width: 100%;
@@ -1151,7 +1180,8 @@ let currentTitle = computed(() => {
         padding-bottom: 1.25em;
       }
       td {
-        padding: 1.25em;
+        padding: 1em;
+        text-align: center;
         vertical-align: middle;
       }
       tbody {

+ 123 - 21
src/views/CourseList.vue

@@ -4,6 +4,7 @@ import { useMainStore } from "@/stores/store";
 import axios from "axios";
 import Navbar from "@/components/Navbar.vue";
 import CourseCard from "@/components/CourseCard.vue";
+import TwCities from "@/assets/TwCities.json";
 
 const store = useMainStore();
 let pageNum = ref(1); // 頁數(預設第一頁)
@@ -26,6 +27,7 @@ let isInternal = ref(false); // 是否選取學習時數課程(只顯示內課)
 let isFilter = ref(false); // 篩選狀態
 let searchList = reactive([]); // 篩選條件
 let totalNum = ref(0); // 資料總筆數
+let locationKeyword = ref(""); // 縣市搜尋關鍵字
 
 // 切換分頁時回到列表上方
 watch(pageNum, () => {
@@ -49,6 +51,11 @@ async function getClass() {
     searchList.map((item) => {
       url += item;
     });
+
+    // 地區搜尋
+    if (locationKeyword.value !== "") {
+      url += `&location_keyword=${locationKeyword.value}`;
+    }
   }
 
   try {
@@ -143,21 +150,85 @@ async function selectFilter(type, val) {
   if (type === "category") {
     searchList.push(`&category=${val}`);
     console.log("searchList", searchList);
+  } else if (type === "location") {
+    locationKeyword.value = val;
   }
 
   getClass();
 }
 
-watch(selectCategory, (val) => {
-  if (val.includes("全部")) {
-    selectCategory.value = [];
-    isFilter.value = false;
-    pageNum.value = 1;
-    getClass();
-  } else {
-    selectFilter("category", val);
-  }
+// // 取得全部課程
+// function getAllClass() {
+//   // selectCategory.value = [];
+//     isFilter.value = false;
+//     pageNum.value = 1;
+//     getClass();
+// }
+
+// 地圖搜尋
+let selectedCounty = ref(null); // 縣市
+let selectedDistrict = ref(null); // 區域
+
+// 縣市
+let counties = reactive({
+  list: [],
+});
+
+TwCities.map((e) => counties.list.push(e.name));
+
+// 區域
+let district = reactive({
+  list: [],
+});
+
+// 選擇縣市後顯示對應區域
+watch(selectedCounty, (newValue) => {
+  district.list = [];
+  selectedDistrict.value = null;
+  TwCities.map((e) => {
+    if (e.name === newValue) {
+      e.districts.map((item) => district.list.push(item.name));
+    }
+  });
 });
+
+// 清除重填(取得全部課程)
+function searchClear() {
+  selectCategory.value = [];
+  selectedCounty.value = null;
+  selectedDistrict.value = null;
+  isFilter.value = false;
+  pageNum.value = 1;
+  getClass();
+}
+
+// watch(selectCategory, (val) => {
+//   console.log("val", val);
+//   if (val.includes("全部")) {
+//     searchClear();
+//   } else {
+//     selectFilter("category", val);
+//   }
+// });
+
+async function searchClass() {
+  isFilter.value = true;
+  pageNum.value = 1; // 篩選時返回第一頁
+  searchList.splice(0, searchList.length); // 清空陣列
+
+  let categoryVal = selectCategory.value;
+  if (categoryVal.length) {
+    searchList.push(`&category=${categoryVal}`);
+  }
+
+  if (selectedCounty.value && selectedDistrict.value) {
+    locationKeyword.value = `${selectedCounty.value}${selectedDistrict.value}`;
+  } else if (selectedCounty.value && !selectedDistrict.value) {
+    locationKeyword.value = `${selectedCounty.value}`;
+  }
+
+  getClass();
+}
 </script>
 
 <template>
@@ -213,12 +284,11 @@ watch(selectCategory, (val) => {
               hide-details
             ></v-select>
           </v-col> -->
-          <v-col cols="12" sm="6" md="12">
+          <v-col cols="12" sm="6" md="12" class="pb-0">
             <v-select
               v-model="selectCategory"
               label="類別"
               :items="[
-                '全部',
                 '樹藝',
                 '漆藝',
                 '藍染',
@@ -237,19 +307,45 @@ watch(selectCategory, (val) => {
               hide-details
             ></v-select>
           </v-col>
-          <!-- <v-col cols="12">
+
+          <v-col cols="12" sm="6" md="12" class="pb-0">
+            <v-select
+              v-model="selectedCounty"
+              label="縣市"
+              :items="counties.list"
+              hide-details
+            ></v-select>
+          </v-col>
+
+          <v-col cols="12" sm="6" md="12" class="pb-0">
+            <v-select
+              v-model="selectedDistrict"
+              label="區域"
+              :items="district.list"
+              hide-details
+            ></v-select>
+          </v-col>
+
+          <v-col cols="12">
             <v-btn
-              @click="selectFilter()"
-              class="w-100"
+              @click="searchClass()"
               variant="flat"
-              color="brown"
-              rounded="xl"
+              color="purple"
+              class="w-100"
             >
-              搜尋
+              查詢
+            </v-btn>
+            <v-btn
+              @click="searchClear()"
+              variant="outlined"
+              color="gray"
+              class="w-100 mt-5"
+            >
+              重設
             </v-btn>
           </v-col>
-          <v-col cols="12">
-            <v-btn class="w-100" variant="outlined" color="brown" rounded="xl">
+          <!-- <v-col cols="12">
+            <v-btn class="w-100" variant="outlined" color="brown">
               清除重填
             </v-btn>
           </v-col> -->
@@ -288,7 +384,10 @@ watch(selectCategory, (val) => {
                   placeholder="關鍵字搜尋"
                 />
                 <button @click="search()">
-                  <img src="@/assets/img/news/news-search-icon.png" alt="臺灣工藝學校全球學習共享平台" />
+                  <img
+                    src="@/assets/img/news/news-search-icon.png"
+                    alt="臺灣工藝學校全球學習共享平台"
+                  />
                 </button>
               </span>
             </div>
@@ -334,7 +433,10 @@ watch(selectCategory, (val) => {
         </div>
       </v-col>
       <v-col cols="12" class="my-16">
-        <img src="@/assets/img/course/banner.png" alt="臺灣工藝學校全球學習共享平台" />
+        <img
+          src="@/assets/img/course/banner.png"
+          alt="臺灣工藝學校全球學習共享平台"
+        />
       </v-col>
     </v-row>
   </v-container>

File diff suppressed because it is too large
+ 560 - 182
src/views/Courses/Create.vue


File diff suppressed because it is too large
+ 201 - 591
src/views/Courses/Proposal.vue


+ 56 - 19
src/views/Courses/SetUp.vue

@@ -1,13 +1,14 @@
 <script setup>
 import { ref, reactive, watch, onMounted } from "vue";
 import { useMainStore } from "@/stores/store";
-import axios from "axios";
-import Navbar from "@/components/Navbar.vue";
 import { Swiper, SwiperSlide } from "swiper/vue";
 import { Pagination } from "swiper/modules";
 import "swiper/css";
 import "swiper/css/pagination";
 import "swiper/css/navigation";
+import axios from "axios";
+import Navbar from "@/components/Navbar.vue";
+// import CoursesTutorial from "@/components/CoursesTutorial.vue";
 
 const store = useMainStore();
 let isLogin = store.checkToken();
@@ -15,7 +16,7 @@ console.log("isLogin", isLogin);
 
 let userInfo = ref([]);
 let isLoading = ref(true);
-let isCrafts = ref(false); // 身份是否為工藝
+let isCrafts = ref(false); // 身份是否為工藝教育者
 
 onMounted(async () => {
   let response = await store.getUserInfo();
@@ -31,21 +32,24 @@ onMounted(async () => {
         isCrafts.value = true;
         isLoading.value = false;
         console.log("isCrafts.value", isCrafts.value);
+      } else {
+        isCrafts.value = false;
+        isLoading.value = false;
       }
     } else {
       isCrafts.value = false;
       isLoading.value = false;
     }
   } else {
+    isCrafts.value = false;
+    isLoading.value = false;
     console.log("尚未登入");
-    store.openLoginDialog();
+    // store.openLoginDialog();
   }
   // userInfo.value = response.data.user_inform[0];
 
   console.log("userInfo.value", userInfo.value);
 });
-
-const modules = [Pagination];
 </script>
 
 <template>
@@ -59,7 +63,7 @@ const modules = [Pagination];
   </div>
 
   <v-container v-else class="my-16 py-16">
-    <v-row v-if="isCrafts">
+    <v-row class="align-center">
       <v-col cols="12" md="6">
         <div class="options">
           <img
@@ -77,21 +81,49 @@ const modules = [Pagination];
           />
           <button @click="store.openLoginDialog()">登入會員開課</button>
         </div>
-        <div v-else class="options">
+        <div v-else-if="isCrafts" class="options">
           <img
             src="@/assets/img/setup-courses/素材-03.png"
             alt="臺灣工藝學校全球學習共享平台"
           />
           <router-link to="/setup-courses/create">開始創建課程</router-link>
         </div>
+        <div v-else>
+          <v-card class="text-center px-0 py-8 crafts-card">
+            <v-card-item>
+              <v-card-title>歡迎加入工藝教育者的行列</v-card-title>
+
+              <!-- <v-card-subtitle class="my-5"
+                >讓我們一步一步完成提案吧! <br></v-card-subtitle
+              > -->
+
+              <p class="my-8">讓我們一步一步完成提案吧!</p>
+            </v-card-item>
+
+            <v-card-actions class="d-flex flex-column">
+              <v-btn
+                :loading="isLoading"
+                variant="flat"
+                color="purple"
+                class="px-8 mt-10"
+                size="large"
+              >
+                <router-link to="/setup-courses/proposal">開始提案</router-link>
+              </v-btn>
+              <small class="text-gray mt-8"
+                >若提案通過後無法創建課程,請聯絡管理員。</small
+              >
+            </v-card-actions>
+          </v-card>
+        </div>
       </v-col>
     </v-row>
 
-    <v-row v-else>
+    <!-- <v-row v-if="isLogin && !isCrafts">
       <v-col cols="12" class="px-0">
         <v-card class="text-center px-0 py-8 crafts-card">
           <v-card-item>
-            <v-card-title>歡迎加入工藝家的行列</v-card-title>
+            <v-card-title>歡迎加入工藝教育者的行列</v-card-title>
 
             <v-card-subtitle class="my-5"
               >讓我們一步一步完成提案吧!</v-card-subtitle
@@ -110,23 +142,28 @@ const modules = [Pagination];
             </v-btn>
           </v-card-text>
         </v-card>
-
-        <!-- <v-card
-          class="text-center crafts-card"
-          title="歡迎加入工藝家的行列"
-          subtitle="讓我們一步一步完成提案吧!"
-          text="..."
-        ></v-card> -->
-        <!-- <h3>歡迎加入工藝家的行列</h3> -->
       </v-col>
-    </v-row>
+    </v-row> -->
   </v-container>
 </template>
 
 <style lang="scss" scoped>
 .crafts-card {
+  margin-top: 120px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 400px;
+  letter-spacing: 1px;
+
+  @media (max-width: 960px) {
+    margin-top: 0;
+  }
+
   .v-card-title {
     font-size: 1.8em;
+    letter-spacing: 2px;
   }
   .v-card-subtitle {
     font-size: 1em;

+ 46 - 5
src/views/Home.vue

@@ -75,10 +75,8 @@ async function searchSchool() {
 
   if (selectedCounty.value && selectedDistrict.value) {
     location = `${selectedCounty.value}${selectedDistrict.value}`;
-    console.log("location &&", location);
   } else if (selectedCounty.value) {
     location = `${selectedCounty.value}`;
-    console.log("location >>", location);
   }
 
   if (schoolKeyword.value !== "" && location !== "") {
@@ -326,6 +324,8 @@ onMounted(() => {
     });
   }
 });
+
+let closeBanner = ref(false);
 </script>
 
 <template>
@@ -338,7 +338,7 @@ onMounted(() => {
     </div>
   </Transition>
 
-  <div class="banner">
+  <div class="banner" :class="{ close: closeBanner }">
     <img
       src="@/assets/img/home/banner.webp"
       alt="臺灣工藝學校全球學習共享平台"
@@ -350,6 +350,14 @@ onMounted(() => {
       class="logo"
     />
   </div>
+
+  <button
+    @click="closeBanner = true"
+    class="close-btn"
+    :class="{ close: closeBanner }"
+  >
+    <v-icon icon="mdi-close"></v-icon>
+  </button>
   <Navbar />
   <v-container class="px-md-0 pb-16 mb-16">
     <section class="text-center intro">
@@ -746,10 +754,10 @@ onMounted(() => {
     </v-row>
   </v-container>
 
-  <v-container fluid class="pa-0 pt-sm-16 tutorial-block">
+  <!-- <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> -->
 
   <v-container class="px-md-0 my-16">
     <h2 class="mb-10 title">{{ t("home.title_6") }}</h2>
@@ -881,6 +889,8 @@ onMounted(() => {
   align-items: center;
   justify-content: center;
   position: relative;
+  opacity: 1;
+  transition: all 0.3s;
   .cover {
     width: 100vw;
     height: 100vh;
@@ -891,6 +901,11 @@ onMounted(() => {
     position: absolute;
     z-index: 10;
   }
+
+  &.close {
+    opacity: 0;
+    position: absolute;
+  }
 }
 
 .intro {
@@ -908,7 +923,19 @@ onMounted(() => {
 }
 
 .navbar {
+  margin-top: 40px;
   margin-bottom: 0;
+  position: sticky;
+  top: 0;
+  z-index: 2000;
+  background: #fff;
+
+  &::before {
+    top: unset;
+    left: unset;
+    width: auto;
+    height: 100%;
+  }
 }
 
 .img-info {
@@ -1156,6 +1183,20 @@ onMounted(() => {
   }
 }
 
+.close-btn {
+  position: absolute;
+  right: 15px;
+  top: 5px;
+  .v-icon {
+    color: #fff;
+    font-size: 40px;
+    text-shadow: 1px 1px 3px #333;
+  }
+  &.close {
+    display: none;
+  }
+}
+
 // Vuetify Expansion 樣式
 .v-expansion-panel {
   margin-bottom: 1.875em;

+ 47 - 12
src/views/Login.vue

@@ -59,6 +59,7 @@ const handleSignIn = async (response) => {
         setTimeout(() => {
           localStorage.setItem("token", response.data.access_token);
           store.loginState = true;
+          window.location.reload();
         }, 1500);
       }
     } catch (error) {
@@ -95,20 +96,40 @@ async function login() {
       "https://cmm.ai:8088/api/login",
       formData
     );
-    console.log("response", response);
+    console.log("登入 response", response);
 
-    if (response.data.msg) {
+    if (response.data.message === "查無此人") {
+      alertText.value = "此帳號不存在,請先完成註冊";
+      loginAlert.value = true;
+      isLoading.value = false;
+      alertClose(2000);
+
+      return;
+    } else if (response.data.message === "Invalid username or password") {
+      alertText.value = "帳號或密碼錯誤";
+      loginAlert.value = true;
+      isLoading.value = false;
+      alertClose(2000);
+
+      return;
+    } else if (response.data.msg) {
       alertText.value = response.data.msg;
       loginAlert.value = true;
       isLoading.value = false;
       alertClose(2000);
+
+      setTimeout(() => {
+        localStorage.setItem("token", response.data.access_token);
+        store.loginState = true;
+        window.location.reload();
+      }, 1500);
     }
 
-    setTimeout(() => {
-      localStorage.setItem("token", response.data.access_token);
-      store.loginState = true;
-      window.location.reload();
-    }, 1500);
+    // setTimeout(() => {
+    //   localStorage.setItem("token", response.data.access_token);
+    //   store.loginState = true;
+    //   window.location.reload();
+    // }, 1500);
   } catch (error) {
     console.error(error);
   }
@@ -302,12 +323,12 @@ function toggleLoginInput() {
       <v-window-item :value="2">
         <div class="d-flex flex-column align-center px-15 pt-10">
           <v-form @submit.prevent>
-            <v-select
+            <!-- <v-select
               label="身份"
-              :items="['學員', '開課工藝家', '其他']"
+              :items="['學習者', '開課工藝教育者', '其他']"
               variant="solo"
               density="compact"
-            ></v-select>
+            ></v-select> -->
             <v-text-field
               v-model="userRegister.email"
               label="信箱"
@@ -361,8 +382,14 @@ function toggleLoginInput() {
       </v-window-item>
     </v-window>
 
-    <v-card-actions class="d-flex justify-center py-10 forget">
-      <a href="">忘記密碼</a><span class="mx-1">/</span><a href="">忘記帳號</a>
+    <v-card-actions class="d-flex flex-column py-10 forget">
+      <p class="directions">
+        登入會員後、凡報名課程即可享有學習護照的點數累積、兌換精美好禮,系統會紀錄您的選課與開課、輕鬆開啟您的工藝學習旅程。
+      </p>
+      <div class="d-flex justify-center pt-10">
+        <a href="">忘記密碼</a><span class="mx-1">/</span
+        ><a href="">忘記帳號</a>
+      </div>
     </v-card-actions>
   </v-card>
 </template>
@@ -431,5 +458,13 @@ function toggleLoginInput() {
       letter-spacing: 0.0625em;
     }
   }
+
+  .directions {
+    padding: 0 30px 0;
+    line-height: 1.5;
+    letter-spacing: 1px;
+    text-align: justify;
+    color: #969696;
+  }
 }
 </style>

+ 70 - 24
src/views/News.vue

@@ -12,26 +12,52 @@ let searchError = ref(false);
 let currentPage = ref(1); // 當前頁數(預設第一頁)
 let itemsPerPage = ref(5); // 每頁顯示筆數
 let totalPages = ref(1); // 總頁數
-const newsAll = reactive({
+let newsAll = reactive({
   list: [],
 });
-const newsData = reactive({
+let newsData = reactive({
   list: [],
 });
+let category = ref([]);
+let categoryVal = ref("");
 
-(async function getData() {
+function filterCategory(item) {
+  categoryVal.value = item;
+  console.log("filterCategory", item);
+  getData();
+}
+
+async function getData() {
   loading.value = true;
+  let url = `https://cmm.ai:8088/api/get_news`;
+
+  if (categoryVal.value !== "") {
+    url += `?category=${categoryVal.value}`;
+  }
+
   try {
-    const response = await axios.get("https://cmm.ai:8088/api/get_news");
+    const response = await axios.get(url);
     console.log("response", response.data.news);
     newsAll.list = response.data.news;
     newsData.list = response.data.news;
+
+    // 存 category
+    const categories = response.data.news.map((item) => item.category);
+    categories.forEach((item) => {
+      if (!category.value.includes(item)) {
+        category.value.push(item);
+      }
+    });
+    console.log("category", category.value);
+
     totalPages.value = store.getTotalPages(response.data.news.length, 5); // 計算頁數
     loading.value = false;
   } catch (error) {
     console.error(error);
   }
-})();
+}
+
+getData();
 
 // 搜尋
 async function search() {
@@ -115,13 +141,23 @@ const categoryList = reactive([
 <template>
   <Navbar />
   <div class="position-relative">
-    <img src="@/assets/img/news/news-01.png" alt="臺灣工藝學校全球學習共享平台" class="material-img" />
+    <img
+      src="@/assets/img/news/news-01.png"
+      alt="臺灣工藝學校全球學習共享平台"
+      class="material-img"
+    />
     <v-container>
-      <img src="@/assets/img/news/news-banner.png" alt="臺灣工藝學校全球學習共享平台" />
+      <img
+        src="@/assets/img/news/news-banner.png"
+        alt="臺灣工藝學校全球學習共享平台"
+      />
 
       <div class="content py-10 mt-16">
         <div class="bg-img">
-          <img src="@/assets/img/news/news-02.png" alt="臺灣工藝學校全球學習共享平台" />
+          <img
+            src="@/assets/img/news/news-02.png"
+            alt="臺灣工藝學校全球學習共享平台"
+          />
         </div>
 
         <v-breadcrumbs
@@ -130,19 +166,6 @@ const categoryList = reactive([
           class="my-5"
         ></v-breadcrumbs>
 
-        <!-- <div class="category-btn">
-          <v-btn
-            v-for="(item, index) in categoryList"
-            :key="index"
-            rounded="xl"
-            color="brown"
-            :class="{ 'me-5': index !== categoryList.length - 1 }"
-            class="mb-5"
-          >
-            {{ item.title }}
-          </v-btn>
-        </div> -->
-
         <div class="search pt-5 mb-10 me-sm-16" ref="searchLocation">
           <span>
             <input
@@ -152,7 +175,10 @@ const categoryList = reactive([
               placeholder="關鍵字搜尋"
             />
             <button @click="search()">
-              <img src="@/assets/img/news/news-search-icon.png" alt="臺灣工藝學校全球學習共享平台" />
+              <img
+                src="@/assets/img/news/news-search-icon.png"
+                alt="臺灣工藝學校全球學習共享平台"
+              />
             </button>
           </span>
           <div
@@ -164,6 +190,20 @@ const categoryList = reactive([
           </div>
         </div>
 
+        <div class="category-btn pt-5 pb-10">
+          <v-btn
+            v-for="(item, index) in category"
+            :key="index"
+            rounded="xl"
+            color="purple"
+            :class="{ 'me-5': index !== category.length - 1 }"
+            class="mb-5"
+            @click="filterCategory(item)"
+          >
+            {{ item }}
+          </v-btn>
+        </div>
+
         <div class="d-flex justify-center mb-10" v-if="loading">
           <v-progress-circular
             color="grey-lighten-4"
@@ -173,7 +213,6 @@ const categoryList = reactive([
 
         <ul>
           <li v-for="(item, index) in paginatedList" :key="index" class="mb-16">
-
             <ArticleCard :data="item" type="news" />
 
             <!-- <section class="d-flex">
@@ -219,7 +258,11 @@ const categoryList = reactive([
         </div>
       </div>
     </v-container>
-    <img src="@/assets/img/news/news-01.png" alt="臺灣工藝學校全球學習共享平台" class="material-img" />
+    <img
+      src="@/assets/img/news/news-01.png"
+      alt="臺灣工藝學校全球學習共享平台"
+      class="material-img"
+    />
   </div>
 </template>
 
@@ -243,12 +286,15 @@ const categoryList = reactive([
 }
 
 .category-btn {
+  max-width: 1000px;
+  margin: auto;
   display: flex;
   flex-wrap: wrap;
   justify-content: center;
 
   .v-btn {
     width: 8.125em;
+    font-size: 1em;
   }
 }
 

File diff suppressed because it is too large
+ 474 - 196
src/views/User/Courses.vue


+ 6 - 0
src/views/User/Dashboard.vue

@@ -49,6 +49,12 @@ let items = [
     url: "/user/passport",
     value: 2,
   },
+  // {
+  //   title: "我的提案",
+  //   icon: "mdi-list-box",
+  //   url: "/user/proposal",
+  //   value: 3,
+  // },
   {
     title: "我的開課",
     icon: "mdi-school",

+ 81 - 14
src/views/User/Passport.vue

@@ -8,26 +8,51 @@ const store = useMainStore();
 let token = store.token;
 console.log("學習護照", store.token);
 
+let pageNum = ref(1); // 頁數(預設第一頁)
+let pageAmount = ref(5); // 每頁顯示筆數
+let totalPages = ref(1); // 總頁數
 let register = reactive({
   list: [],
 });
 
 console.log("profile", store.profile);
-(async () => {
+
+async function getRegistration() {
   try {
     const response = await axios.get(
       `https://cmm.ai:8088/api/get_registration?access_token=${token}`
     );
     register.list = response.data.registrations;
-    console.log("response", response.data.registrations);
+    console.log("報名資料", response.data.registrations);
   } catch (error) {
     console.error(error);
   }
-})();
+}
+
+getRegistration();
+
+let isCheck = ref(null);
+let assignTag = ref("all");
+
+function selectTag(btn) {
+  pageNum.value = 1;
+
+  if (btn === "all") {
+    isCheck.value = null;
+    assignTag.value = "all";
+  } else {
+    isCheck.value = btn;
+    assignTag.value = btn;
+  }
+
+  // getClass();
+
+  console.log("assignTag.value", assignTag.value);
+}
 </script>
 
 <template>
-  <v-card class="h-100">
+  <v-card class="h-100 user-courses">
     <div class="title">
       <h4>學習護照</h4>
     </div>
@@ -57,7 +82,7 @@ console.log("profile", store.profile);
           />
           <p>
             總共 <br />
-            <strong> {{ store.profile.points }} </strong> <small>小時</small>
+            <strong> {{ store.profile.hours }} </strong> <small>小時</small>
           </p>
         </div>
       </v-col>
@@ -82,20 +107,55 @@ console.log("profile", store.profile);
           <p>
             總共 <br />
             <!-- 小數點無條件捨去 -->
-            <strong> {{ Math.floor(store.profile.points / 3) }} </strong>
+            <!-- <strong> {{ Math.floor(store.profile.points / 3) }} </strong> -->
+            <strong> {{ store.profile.points }} </strong>
             <small class="ps-2">點</small>
           </p>
         </div>
       </v-col>
       <v-col cols="12" class="mt-16">
+        <!-- <div class="d-flex justify-center tab-btn mt-5 mb-10">
+      <v-btn
+        variant="text"
+        @click="selectTag('all')"
+        :class="{ active: assignTag === 'all' }"
+      >
+      報名中課程
+      </v-btn>
+      <v-btn
+        variant="text"
+        @click="selectTag('0')"
+        :class="{ active: assignTag === '0' }"
+      >
+        上課紀錄
+      </v-btn>
+    </div> -->
+
         <div class="main-table">
-          <h6 class="table-title">報名中課程</h6>
+          <div class="d-flex justify-center tab-btn mt-5">
+            <v-btn
+              variant="text"
+              @click="selectTag('all')"
+              :class="{ active: assignTag === 'all' }"
+            >
+              報名中課程
+            </v-btn>
+            <v-btn
+              variant="text"
+              @click="selectTag('0')"
+              :class="{ active: assignTag === '0' }"
+            >
+              上課紀錄
+            </v-btn>
+          </div>
+
+          <!-- <h6 class="table-title">報名中課程</h6> -->
           <table>
             <thead>
               <tr>
-                <th>名稱</th>
                 <th>報名日期</th>
-                <th width="30%">課程時間</th>
+                <th>課程名稱</th>
+                <th width="150px">課程時間</th>
                 <th width="110px">時數</th>
                 <th width="10%">報名狀態</th>
                 <!-- <th width="15%">繳款資訊</th> -->
@@ -108,11 +168,11 @@ console.log("profile", store.profile);
                 </p>
               </tr> -->
               <tr v-for="(item, index) in register.list" :key="index">
-                <td>{{ item.class_name }}</td>
                 <td>
                   {{ moment(`${item.create_time}`).format("YYYY/MM/DD") }}
                 </td>
-                <td>
+                <td>{{ item.class_name }}</td>
+                <td class="time-item">
                   {{ moment(`${item.start_time}`).format("YYYY/MM/DD H:mm") }}
                   <br />
                   ~ <br />
@@ -125,7 +185,9 @@ console.log("profile", store.profile);
                   </span>
 
                   <span v-else class="d-flex align-center">
-                    <p style="width: 55px" class="text-grey-lighten-1">審核中</p>
+                    <p style="width: 55px" class="text-grey-lighten-1">
+                      審核中
+                    </p>
                   </span>
 
                   <!-- <div v-if="item.reg_confirm" class="d-flex align-center">
@@ -151,7 +213,7 @@ console.log("profile", store.profile);
         </router-link>
       </v-col>
 
-      <v-col cols="12" class="my-16">
+      <!-- <v-col cols="12" class="my-16">
         <div class="main-table">
           <h6 class="table-title">上課紀錄</h6>
           <table>
@@ -168,7 +230,7 @@ console.log("profile", store.profile);
             </tbody>
           </table>
         </div>
-      </v-col>
+      </v-col> -->
 
       <div class="dot-item">
         <span class="t-dot"></span>
@@ -274,6 +336,11 @@ table {
       padding: 0.9375em;
     }
   }
+
+  .time-item {
+    font-size: 0.8em;
+  }
+
   .finish-icon {
     padding: 0.5em;
     border: 0.125em solid var(--blue);

+ 52 - 42
src/views/User/Profile.vue

@@ -101,7 +101,7 @@ async function updateUserInfo(formData) {
   }
 }
 
-let isCrafts = ref(false); // 身份是否為工藝
+let isCrafts = ref(false); // 身份是否為工藝教育者
 
 // 取得使用者資料
 async function getUserInfo() {
@@ -124,55 +124,65 @@ async function getUserInfo() {
       return;
     }
 
-    let position = response.data.user_inform[0].position;
-    console.log("user.position", position);
+    let userInform = response.data.user_inform[0];
+    email.value = userInform.email;
+    user.user_name = userInform.user_name;
 
-    // 判斷身份
-    if (position["學員"]) {
-      positionList.value.push("1");
-    }
-    if (position["開課工藝家"]) {
-      isCrafts.value = true;
-      positionList.value.push("2");
-    }
+    if (userInform.position) {
+      let position = userInform.position;
+      console.log("user.position", position);
 
-    let identifyStr = response.data.user_inform[0].identify.substring(
-      1,
-      response.data.user_inform[0].identify.length - 1
-    ); // 移除 [] 後將字串轉為陣列
-    identityList.value = identifyStr.split(",");
+      // 判斷身份
+      if (position["學習者"]) {
+        positionList.value.push("1");
+      }
+      if (position["開課工藝家"]) {
+        isCrafts.value = true;
+        positionList.value.push("2");
+      }
+    }
 
-    email.value = response.data.user_inform[0].email;
-    user.user_name = response.data.user_inform[0].user_name;
+    if (userInform.identify) {
+      let identifyStr = userInform.identify.substring(
+        1,
+        userInform.identify.length - 1
+      ); // 移除 [] 後將字串轉為陣列
+      identityList.value = identifyStr.split(",");
+    }
 
-    if (!response.data.user_inform[0].exist) {
+    if (!userInform.exist) {
       isExist.value = false;
     } else {
       isExist.value = true;
 
       // 遍歷物件屬性
-      Object.keys(response.data.user_inform[0]).forEach((key) => {
+      Object.keys(userInform).forEach((key) => {
         if (user[key] !== undefined) {
-          user[key] = response.data.user_inform[0][key];
+          user[key] = userInform[key];
         }
       });
 
-      // 手機
-      const phoneSplit = response.data.user_inform[0].phone.split(" ");
+      if (userInform.phone) {
+        // 手機
+        const phoneSplit = userInform.phone.split(" ");
 
-      if (phoneSplit.length === 2) {
-        phoneCode.value = phoneSplit[0];
-        phone.value = phoneSplit[1];
-      } else {
-        phone.value = phoneSplit[1];
+        if (phoneSplit.length === 2) {
+          phoneCode.value = phoneSplit[0];
+          phone.value = phoneSplit[1];
+        } else {
+          phone.value = phoneSplit[1];
+        }
       }
 
-      // 地址
-      city.value = response.data.user_inform[0].address.substring(0, 3);
-      address.value = response.data.user_inform[0].address.substring(3);
+      if (userInform.address) {
+        // 地址
+        city.value = userInform.address.substring(0, 3);
+        address.value = userInform.address.substring(3);
+      }
     }
   } catch (error) {
     store.loginState = false;
+    console.log("getUserInfo error");
     localStorage.removeItem("token");
     console.error(error);
   }
@@ -197,13 +207,13 @@ let resume = reactive({
   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);
+    console.log("工藝教育者", response.data.user_resume);
 
     if (response.data.user_resume) {
       // 遍歷物件屬性
@@ -220,7 +230,7 @@ let resume = reactive({
   }
 })();
 
-// 更新工藝履歷
+// 更新工藝教育者履歷
 async function saveResume() {
   console.log("saveResume");
 }
@@ -277,16 +287,16 @@ let typeList = [
   <v-card class="h-100 profile-card">
     <v-tabs v-model="tab" color="purple" align-tabs="center" class="mb-16">
       <v-tab :value="1">關於我</v-tab>
-      <v-tab v-if="isCrafts" :value="2">工藝履歷</v-tab>
+      <v-tab v-if="isCrafts" :value="2">工藝教育者履歷</v-tab>
     </v-tabs>
     <v-window v-model="tab">
       <v-window-item :value="1">
         <v-form @submit.prevent>
           <p class="mb-5">身份</p>
           <div class="d-flex">
-            <v-chip color="purple"> 學 </v-chip>
+            <v-chip color="purple"> 學習者 </v-chip>
             <v-chip v-if="isCrafts" color="purple" class="ms-2">
-              開課工藝
+              開課工藝教育者
             </v-chip>
           </div>
 
@@ -295,14 +305,14 @@ let typeList = [
           </v-label>
           <v-checkbox
             v-model="positionList"
-            label="學"
+            label="學習者"
             value="1"
             color="purple"
             hide-details
           ></v-checkbox>
           <v-checkbox
             v-model="positionList"
-            label="開課工藝"
+            label="開課工藝教育者"
             value="2"
             color="purple"
             hide-details
@@ -469,11 +479,11 @@ let typeList = [
         </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>
+            <p class="d-flex mb-5">工藝教育者姓名<span class="mark">*</span></p>
             <v-text-field
               v-model="resume.teacher_name"
               :rules="[(v) => !!v || '請輸入您的姓名']"
@@ -552,7 +562,7 @@ let typeList = [
           </v-row>
 
           <v-label>
-            <p class="d-flex mb-5">工藝自我介紹</p>
+            <p class="d-flex mb-5">工藝教育者自我介紹</p>
             <v-textarea v-model="resume.introduction" rows="5"></v-textarea>
           </v-label>
 

+ 219 - 0
src/views/User/Proposal.vue

@@ -0,0 +1,219 @@
+<script setup>
+import { ref, reactive, watch } from "vue";
+import { useMainStore } from "@/stores/store";
+// import { Loader } from "@googlemaps/js-api-loader";
+import { VDataTable } from "vuetify/labs/VDataTable";
+import VueDatePicker from "@vuepic/vue-datepicker";
+import "@vuepic/vue-datepicker/dist/main.css";
+import axios from "axios";
+import moment from "moment";
+
+const store = useMainStore();
+
+let token = store.token;
+let loading = ref(false);
+let title = ref(null);
+let isCheck = ref(null);
+let pageNum = ref(1); // 頁數(預設第一頁)
+let pageAmount = ref(5); // 每頁顯示筆數
+let totalPages = ref(1); // 總頁數
+let proposal = reactive({
+  list: [],
+});
+// let classes = reactive({
+//   list: [],
+// });
+
+// 搜尋
+let searchInput = ref("");
+let searchState = ref(false);
+
+watch(pageNum, () => {
+  getSchool();
+});
+
+// 取得提案
+async function getSchool() {
+  let token = store.token;
+  let url = `https://cmm.ai:8088/api/get_school?access_token=${token}&page_num=${pageNum.value}&page_amount=${pageAmount.value}`;
+  try {
+    let response = await axios.get(url);
+    proposal.list = response.data.schools;
+    totalPages.value = store.getTotalPages(response.data.total_num, 5);
+
+    setTimeout(() => {
+      loading.value = false;
+    }, 500);
+
+    console.log("取得提案", response);
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+getSchool();
+
+let assignTag = ref("all");
+
+function selectTag(btn) {
+  pageNum.value = 1;
+  if (btn === "all") {
+    isCheck.value = null;
+    assignTag.value = "all";
+  } else {
+    isCheck.value = btn;
+    assignTag.value = btn;
+  }
+
+  getClass();
+
+  console.log("assignTag.value", assignTag.value);
+}
+</script>
+
+<template>
+  <v-card class="h-100 user-courses">
+    <div ref="title" class="title">
+      <h4>我的提案</h4>
+    </div>
+
+    <div class="d-flex align-center ms-auto search-item">
+      <v-text-field
+        @keyup.enter="search()"
+        v-model="searchInput"
+        label="搜尋關鍵字"
+        variant="outlined"
+        density="compact"
+        hide-details
+        clearable
+      ></v-text-field>
+
+      <button @click="search()" class="ms-2">
+        <img
+          src="@/assets/img/news/news-search-icon.png"
+          alt="臺灣工藝學校全球學習共享平台"
+        />
+      </button>
+    </div>
+
+    <div class="d-flex justify-center tab-btn mt-5 mb-10">
+      <v-btn
+        variant="text"
+        @click="selectTag('all')"
+        :class="{ active: assignTag === 'all' }"
+      >
+        全部
+      </v-btn>
+      <v-btn
+        variant="text"
+        @click="selectTag('0')"
+        :class="{ active: assignTag === '0' }"
+      >
+        未審核
+      </v-btn>
+      <v-btn
+        variant="text"
+        @click="selectTag('1')"
+        :class="{ active: assignTag === '1' }"
+      >
+        已審核
+      </v-btn>
+      <v-btn
+        variant="text"
+        @click="selectTag('2')"
+        :class="{ active: assignTag === '2' }"
+      >
+        已駁回
+      </v-btn>
+    </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">
+      <!-- <h6 class="table-title">開課紀錄</h6> -->
+      <router-link
+        v-if="!proposal.list.length && isCheck !== '2' && isCheck !== '3'"
+        to="/setup-courses/proposal"
+        class="hint-item mb-7"
+        >點此前往提案</router-link
+      >
+      <table v-else>
+        <thead>
+          <tr>
+            <th width="10%"></th>
+            <th width="10%">提案日期</th>
+            <th width="70%">工坊名稱</th>
+            <th width="10%">提案內容</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="(item, index) in proposal.list" :key="index">
+            <td>
+              <v-chip v-if="item.is_pass_proposal === 0"> 提案審核中 </v-chip>
+              <!-- <v-chip
+                variant="outlined"
+                color="green"
+                text-color="white"
+                v-else
+              >
+                已通過提案
+              </v-chip> -->
+
+              <v-chip v-if="item.is_pass_proposal === 1 && item.is_check === 0">
+                未審核
+              </v-chip>
+              <v-chip
+                v-else-if="item.is_check === 1"
+                color="green"
+                text-color="white"
+              >
+                已審核
+              </v-chip>
+              <v-chip
+                v-else-if="item.is_check === 2"
+                color="error"
+                text-color="white"
+              >
+                已駁回
+              </v-chip>
+            </td>
+
+            <td>
+              {{ moment(`${item.update_time}`).format("YYYY/MM/DD") }}
+            </td>
+
+            <td>{{ item.location_name }}</td>
+
+            <td>
+              <div>
+                <v-btn color="purple" variant="outlined">
+                  <p>工藝教育者</p>
+                </v-btn>
+                <v-btn color="purple" variant="flat" class="w-100 mt-3">
+                  <p>課程場次</p>
+                </v-btn>
+              </div>
+            </td>
+
+            <td></td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <v-pagination
+      v-if="!searchError"
+      v-model="pageNum"
+      :length="totalPages"
+      rounded="circle"
+      class="mt-16"
+    ></v-pagination>
+  </v-card>
+</template>
+
+<style lang="scss">
+</style>

Some files were not shown because too many files changed in this diff