Login.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. <script setup>
  2. import { ref, reactive, onMounted, watch } from "vue";
  3. import { useMainStore } from "@/stores/store";
  4. import axios from "axios";
  5. const store = useMainStore();
  6. const emit = defineEmits(["close"]);
  7. const emailRegex = /^(?:\s*|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/;
  8. let tab = ref("");
  9. function close() {
  10. emit("close", false);
  11. }
  12. // Google Login
  13. const handleSignIn = async (response) => {
  14. const credential = response.credential;
  15. const profile = JSON.parse(
  16. decodeURIComponent(
  17. escape(
  18. window.atob(
  19. credential.split(".")[1].replace(/-/g, "+").replace(/_/g, "/")
  20. )
  21. )
  22. )
  23. );
  24. console.log("profile", profile);
  25. if (profile) {
  26. let data = {
  27. username: profile.name,
  28. password: "googleLogin",
  29. email: profile.email,
  30. };
  31. const formData = new FormData();
  32. for (const key in data) {
  33. formData.append(key, data[key]);
  34. }
  35. formData.forEach((value, key) => {
  36. console.log(`${key}: ${value}`);
  37. });
  38. try {
  39. const response = await axios.post(
  40. "https://cmm.ai:8088/api/login/google/access-token",
  41. formData
  42. );
  43. console.log("response", response);
  44. console.log("response access_token", response.data.access_token);
  45. if (response.data.access_token) {
  46. alertText.value = "登入成功";
  47. loginAlert.value = true;
  48. isLoading.value = false;
  49. alertClose(2000);
  50. setTimeout(() => {
  51. localStorage.setItem("token", response.data.access_token);
  52. store.loginState = true;
  53. }, 1500);
  54. }
  55. } catch (error) {
  56. console.log("google login error");
  57. console.log(error);
  58. }
  59. }
  60. };
  61. let isLoading = ref(false);
  62. let loginAlert = ref(false);
  63. let userLogin = reactive({
  64. username: "",
  65. password: "",
  66. });
  67. // 登入
  68. async function login() {
  69. if (!emailRegex.test(userLogin.username)) {
  70. alertText.value = "請檢查 E-mail 格式";
  71. loginAlert.value = true;
  72. alertClose(1500);
  73. return;
  74. }
  75. const formData = new FormData();
  76. for (const key in userLogin) {
  77. formData.append(key, userLogin[key]);
  78. }
  79. try {
  80. isLoading.value = true;
  81. const response = await axios.post(
  82. "https://cmm.ai:8088/api/login",
  83. formData
  84. );
  85. console.log("response", response);
  86. if (response.data.msg) {
  87. alertText.value = response.data.msg;
  88. loginAlert.value = true;
  89. isLoading.value = false;
  90. alertClose(2000);
  91. }
  92. setTimeout(() => {
  93. localStorage.setItem("token", response.data.access_token);
  94. store.loginState = true;
  95. }, 1500);
  96. } catch (error) {
  97. console.error(error);
  98. }
  99. }
  100. let userRegister = reactive({
  101. email: "",
  102. username: "",
  103. password: "",
  104. re_password: "",
  105. });
  106. // 註冊
  107. async function register() {
  108. const formData = new FormData();
  109. for (const key in userRegister) {
  110. formData.append(key, userRegister[key]);
  111. }
  112. try {
  113. isLoading.value = true;
  114. const response = await axios.post("https://cmm.ai:8088/api/add", formData);
  115. console.log("response", response);
  116. if (response.data.msg) {
  117. alertText.value = response.data.msg;
  118. registerAlert.value = true;
  119. alertClose(2000);
  120. }
  121. isLoading.value = false;
  122. if (response.data.code === 200) {
  123. setTimeout(() => {
  124. tab.value = 1;
  125. }, 3000);
  126. }
  127. } catch (error) {
  128. console.error(error);
  129. }
  130. }
  131. // 檢查欄位是否為空
  132. function checkEmptyFields() {
  133. for (const key in userRegister) {
  134. if (userRegister[key] === "") {
  135. return false;
  136. }
  137. }
  138. return true;
  139. }
  140. let registerAlert = ref(false);
  141. let alertText = ref("");
  142. // 檢查註冊欄位
  143. function validateRegister() {
  144. const isValid = checkEmptyFields();
  145. if (!isValid) {
  146. return;
  147. }
  148. if (!emailRegex.test(userRegister.email)) {
  149. alertText.value = "請檢查 E-mail 格式";
  150. registerAlert.value = true;
  151. alertClose(1500);
  152. return;
  153. }
  154. if (userRegister.password !== userRegister.re_password) {
  155. alertText.value = "密碼與確認密碼不相符";
  156. registerAlert.value = true;
  157. alertClose(1500);
  158. return;
  159. }
  160. register();
  161. }
  162. function alertClose(second) {
  163. setTimeout(() => {
  164. loginAlert.value = false;
  165. registerAlert.value = false;
  166. }, second);
  167. }
  168. onMounted(() => {
  169. const script = document.createElement("script");
  170. script.src = "https://accounts.google.com/gsi/client";
  171. script.async = true;
  172. script.defer = true;
  173. document.head.appendChild(script);
  174. window.onSignIn1 = handleSignIn;
  175. });
  176. let token =
  177. "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIiLCJleHAiOjE2ODg2MjU5MzN9.rMJGZxHeKaxBQx0VuTBk8AtYhhLyIPxNUHBrsKWXJjE";
  178. // 解碼 JWT
  179. const base64Url = token.split(".")[1]; // 取得 JWT 的 payload
  180. const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); // 處理 URL 安全的 Base64 字串
  181. const decodedPayload = atob(base64); // Base64 解碼
  182. // 將解碼後的 payload 轉換為 JSON 物件
  183. const payload = JSON.parse(decodedPayload);
  184. // 獲取過期時間(exp)
  185. const expirationTime = payload.exp; // 過期時間的 UNIX 時間戳
  186. // 創建 Date 物件並設定時間戳
  187. const expirationDate = new Date(expirationTime * 1000); // JS 時間戳以毫秒為單位故乘以 1000
  188. // 取得日期和時間的字串表示
  189. const formattedExpiration = expirationDate.toLocaleString();
  190. console.log("JWT 過期時間:", formattedExpiration);
  191. let showLogin = ref(false);
  192. function toggleLoginInput() {
  193. console.log("showLogin");
  194. showLogin.value = !showLogin.value;
  195. }
  196. </script>
  197. <template>
  198. <!-- <Navbar />
  199. <div>Login</div> -->
  200. <!-- <v-container class="pa-0">
  201. <v-row>
  202. <v-col cols="6">
  203. <img src="@/assets/img/img-08.jpg" alt="">
  204. </v-col>
  205. <v-col cols="6"></v-col>
  206. </v-row>
  207. </v-container> -->
  208. <v-card class="login-card">
  209. <div class="d-flex justify-end pa-3">
  210. <v-icon
  211. size="large"
  212. icon="mdi-close"
  213. color="gray"
  214. @click.prevent="close()"
  215. ></v-icon>
  216. </div>
  217. <v-tabs v-model="tab" color="primary" align-tabs="center">
  218. <v-tab :value="1" class="me-5">登入</v-tab>
  219. <v-tab :value="2">註冊</v-tab>
  220. </v-tabs>
  221. <v-window v-model="tab">
  222. <v-window-item :value="1">
  223. <div class="px-15 pt-10 d-flex flex-column align-center">
  224. <div
  225. id="g_id_onload"
  226. data-client_id="1019468024352-ktbehd28aia7gjcl53sgaid1ucqb8gcd.apps.googleusercontent.com"
  227. data-callback="onSignIn1"
  228. ></div>
  229. <div
  230. class="g_id_signin"
  231. data-type="standard"
  232. data-theme="outline"
  233. data-size="large"
  234. data-logo_alignment="left"
  235. ></div>
  236. <!-- <p class="text-center my-7">使用平台帳號登入</p> -->
  237. <v-btn
  238. @click="toggleLoginInput()"
  239. class="login-btn"
  240. variant="outlined"
  241. color="primary"
  242. >
  243. 使用平台帳號登入
  244. </v-btn>
  245. <Transition>
  246. <v-form v-if="showLogin" @submit.prevent class="mt-7">
  247. <v-text-field
  248. v-model="userLogin.username"
  249. label="信箱"
  250. :rules="[(v) => !!v || '請輸入您的信箱']"
  251. prepend-inner-icon="mdi-account"
  252. variant="solo"
  253. density="compact"
  254. ></v-text-field>
  255. <v-text-field
  256. v-model="userLogin.password"
  257. label="密碼"
  258. :rules="[(v) => !!v || '請輸入您的密碼']"
  259. prepend-inner-icon="mdi-lock"
  260. variant="solo"
  261. density="compact"
  262. type="password"
  263. ></v-text-field>
  264. <v-btn
  265. type="submit"
  266. size="large"
  267. color="primary"
  268. block
  269. @click="login()"
  270. :loading="isLoading"
  271. >登入</v-btn
  272. >
  273. </v-form>
  274. </Transition>
  275. <div class="alert" v-if="loginAlert">
  276. <v-alert border="start" border-color="primary" elevation="2">
  277. {{ alertText }}
  278. </v-alert>
  279. </div>
  280. </div>
  281. </v-window-item>
  282. <v-window-item :value="2">
  283. <div class="d-flex flex-column align-center px-15 pt-10">
  284. <v-form @submit.prevent>
  285. <v-select
  286. label="身份"
  287. :items="['學員', '開課工藝家', '其他']"
  288. variant="solo"
  289. density="compact"
  290. ></v-select>
  291. <v-text-field
  292. v-model="userRegister.email"
  293. label="信箱"
  294. :rules="[(v) => !!v || '請輸入您的信箱']"
  295. prepend-inner-icon="mdi-email"
  296. variant="solo"
  297. density="compact"
  298. ></v-text-field>
  299. <v-text-field
  300. v-model="userRegister.username"
  301. label="帳號"
  302. :rules="[(v) => !!v || '請輸入您的帳號']"
  303. prepend-inner-icon="mdi-account"
  304. variant="solo"
  305. density="compact"
  306. ></v-text-field>
  307. <v-text-field
  308. v-model="userRegister.password"
  309. label="密碼"
  310. :rules="[(v) => !!v || '請輸入您的密碼']"
  311. prepend-inner-icon="mdi-lock"
  312. variant="solo"
  313. density="compact"
  314. type="password"
  315. ></v-text-field>
  316. <v-text-field
  317. v-model="userRegister.re_password"
  318. label="確認密碼"
  319. :rules="[(v) => !!v || '請輸入確認密碼']"
  320. prepend-inner-icon="mdi-lock-check"
  321. variant="solo"
  322. density="compact"
  323. type="password"
  324. ></v-text-field>
  325. <v-btn
  326. block
  327. type="submit"
  328. size="large"
  329. color="primary"
  330. @click="validateRegister()"
  331. :loading="isLoading"
  332. >註冊</v-btn
  333. >
  334. </v-form>
  335. <div class="alert" v-if="registerAlert">
  336. <v-alert border="start" border-color="primary" elevation="2">
  337. {{ alertText }}
  338. </v-alert>
  339. </div>
  340. </div>
  341. </v-window-item>
  342. </v-window>
  343. <v-card-actions class="d-flex justify-center py-10 forget">
  344. <a href="">忘記密碼</a><span class="mx-1">/</span><a href="">忘記帳號</a>
  345. </v-card-actions>
  346. </v-card>
  347. </template>
  348. <style lang="scss">
  349. .login-btn {
  350. width: 190px;
  351. margin-top: 20px;
  352. }
  353. .login-card {
  354. .v-tab {
  355. padding-bottom: 20px;
  356. font-size: 28px;
  357. }
  358. .v-form {
  359. width: 100%;
  360. }
  361. .tab {
  362. button {
  363. margin: 0 20px;
  364. padding-bottom: 20px;
  365. font-size: 28px;
  366. font-weight: 500;
  367. text-align: center;
  368. color: #d9d9d9;
  369. border-bottom: 3px solid transparent;
  370. transition: all 0.3s;
  371. &.active {
  372. color: var(--main-color);
  373. border-bottom: 3px solid var(--main-color);
  374. }
  375. }
  376. }
  377. h3,
  378. p {
  379. color: #595959;
  380. }
  381. .google-btn {
  382. width: 130px;
  383. margin: auto;
  384. }
  385. .v-text-field .v-input__details {
  386. position: relative;
  387. top: -4px;
  388. }
  389. .forget {
  390. a,
  391. span {
  392. color: #969696;
  393. }
  394. a {
  395. transition: all 0.3s;
  396. &:hover {
  397. opacity: 0.8;
  398. }
  399. }
  400. }
  401. .alert {
  402. top: 50%;
  403. position: absolute;
  404. z-index: 1;
  405. .v-alert {
  406. color: var(--main-color);
  407. background-color: #fff;
  408. letter-spacing: 1px;
  409. }
  410. }
  411. }
  412. </style>