فهرست منبع

update qrcode add time

SyuanYu 1 سال پیش
والد
کامیت
b9072f76c8

+ 12 - 9
frontend/src/App.vue

@@ -1,11 +1,3 @@
-<template>
-  <v-app>
-    <LoadingView v-if="loggedIn === null" />
-    <RouterView v-else />
-    <NotificationsManager />
-  </v-app>
-</template>
-
 <script setup lang="ts">
 import { RouterView } from "vue-router";
 import { useMainStore } from "@/stores/main";
@@ -22,10 +14,21 @@ const loggedIn = mainStoreRef.readIsLoggedIn;
 
 //lifecycle
 onMounted(() => {
-  mainStore.checkLoggedIn();
+  let path = location.pathname;
+  if (path !== "/qrcode") {
+    mainStore.checkLoggedIn();
+  }
 });
 </script>
 
+<template>
+  <v-app>
+    <LoadingView v-if="loggedIn === null" />
+    <RouterView v-else />
+    <NotificationsManager />
+  </v-app>
+</template>
+
 <style lang="scss">
 :root {
   --main-color: #ea5413;

+ 5 - 2
frontend/src/api.ts

@@ -23,7 +23,7 @@ export const api = {
     params.append("username", username);
     params.append("password", password);
 
-    return axios.post(`${apiUrl}/api/v1/login/access-token?add-time-code=${ser_no}`, params);
+    return axios.post(`${apiUrl}/api/v1/login/access-token?add_time_code=${ser_no}`, params);
   },
   async googleLogin(username: string) {
     const params = new URLSearchParams();
@@ -36,7 +36,10 @@ export const api = {
     const params = new URLSearchParams();
     params.append("username", username);
     params.append("password", "google");
-    return axios.post(`${apiUrl}/api/v1/login/google/access-token?add-time-code=${ser_no}`, params);
+    return axios.post(`${apiUrl}/api/v1/login/google/access-token?add_time_code=${ser_no}`, params);
+  },
+  async qrAddTime(token: string, code: string) {
+    return axios.get(`${apiUrl}/api/v1/ser_nos/add-time?code=${code}`, authHeaders(token));
   },
   async getMe(token: string) {
     return axios.get<IUserProfile>(`${apiUrl}/api/v1/users/me`, authHeaders(token));

+ 7 - 2
frontend/src/components/Dialog.vue

@@ -21,6 +21,10 @@ const props = defineProps({
     type: String,
     default: "info"
   },
+  qrcode: {
+    type: Boolean,
+    default: false,
+  }
 });
 
 const emit = defineEmits(["close"]);
@@ -44,8 +48,9 @@ function close() {
           <p  v-html="msg" class="mt-3 text-center"></p>
         </section>
       </v-card-text>
-      <v-card-actions>
-        <v-btn @click="close()" class="mx-auto"> {{ t("close") }}</v-btn>
+      <v-card-actions class="d-flex justify-space-evenly">
+        <v-btn @click="close()" v-show="!qrcode || (state === 'error' && qrcode)">{{ t("close") }}</v-btn>
+        <router-link v-show="(state === 'success' || state === 'error') && qrcode" to="/main/dashboard">返回使用者頁面</router-link>
       </v-card-actions>
     </v-card>
   </v-dialog>

+ 1 - 1
frontend/src/interfaces/index.ts

@@ -44,7 +44,7 @@ export interface MainState {
 export interface Video {
   id: number;
   title: string;
-  stored_file_name: string;
+  stored_filename: string;
   progress_state: string;
 }
 

+ 8 - 8
frontend/src/router/index.ts

@@ -6,10 +6,10 @@ const router = createRouter({
   history: createWebHistory(import.meta.env.BASE_URL),
   routes: [
     {
-      path: "/",
-      name:'/',
+      path: '/',
+      name: '/',
       component: () => import(/* webpackChunkName: "start" */ '@/views/main/Start.vue'),
-      children:[
+      children: [
         {
           path: 'login',
           name: 'login',
@@ -34,7 +34,7 @@ const router = createRouter({
           component: () => import(/* webpackChunkName: "reset-password" */ '@/views/ResetPassword.vue'),
         },
         {
-          path: 'qrcode/:ser_no',
+          path: 'qrcode',
           name: 'qrcode',
           component: () => import(/* webpackChunkName: "reset-password" */ '@/views/Qrcode.vue'),
         },
@@ -51,22 +51,22 @@ const router = createRouter({
             {
               path: 'make-video',
               name: 'make-video',
-              component: () => import ('@/views/main/Upload.vue'),
+              component: () => import('@/views/main/Upload.vue'),
             },
             {
               path: 'make-article',
               name: 'make-article',
-              component: () => import ('@/views/main/Article.vue'),
+              component: () => import('@/views/main/Article.vue'),
             },
             {
               path: 'make-image',
               name: 'make-image',
-              component: () => import ('@/views/main/Image.vue'),
+              component: () => import('@/views/main/Image.vue'),
             },
             {
               path: 'progress',
               name: 'progress',
-              component: () => import ('@/views/main/Progress.vue'),
+              component: () => import('@/views/main/Progress.vue'),
             },
             {
               path: 'profile',

+ 41 - 43
frontend/src/stores/main.ts

@@ -75,32 +75,12 @@ export const useMainStore = defineStore("MainStoreId", {
         await this.logOut();
       }
     },
-    async qrLogIn(username: string, password: string,ser_no: string) {
-      console.log('qrLogIn');
-      
+    async qrLogIn(username: string, password: string, ser_no: string) {
       try {
-        const response = await api.qrLogInGetToken(username, password,ser_no);
-        // const token: string = response.data.access_token;
-        console.log('response',response);
+        const response = await api.qrLogInGetToken(username, password, ser_no);
         return response;
-        // console.log('token',token);
-        // if (token) {
-        //   saveLocalToken(token);
-        //   this.setToken(token);
-        //   this.setLoggedIn(true);
-        //   this.setLogInError(false);
-        //   await this.getUserProfile();
-        //   await this.routeLoggedIn();
-        //   this.addNotification({ content: i18n.global.t("loggedIn"), color: "success" });
-        // } else {
-        //   await this.logOut();
-        // }
-      } catch (err) {
-        console.log('qrLogIn err',err);
-        
-        // this.addNotification({ content: i18n.global.t("loggedError"), color: "error" });
-        // this.setLogInError(true);
-        // await this.logOut();
+      } catch (error) {
+        console.log('error', error);
       }
     },
     async googleLogin(username: string) {
@@ -123,27 +103,21 @@ export const useMainStore = defineStore("MainStoreId", {
         await this.logOut();
       }
     },
-    async qrGoogleLogin(username: string,ser_no: string) {
-      console.log('qrGoogleLogin');
-      
+    async qrGoogleLogin(username: string, ser_no: string) {
       try {
-        const response = await api.qrGoogleLogin(username,ser_no);
+        const response = await api.qrGoogleLogin(username, ser_no);
         return response;
-        // const token: string = response.data.access_token;
-        // if (token) {
-        //   saveLocalToken(token);
-        //   this.setToken(token);
-        //   this.setLoggedIn(true);
-        //   this.setLogInError(false);
-        //   await this.getUserProfile();
-        //   await this.routeLoggedIn();
-        //   this.addNotification({ content: i18n.global.t("loggedIn"), color: "success" });
-        // } else {
-        //   await this.logOut();
-        // }
-      } catch (err) {
-        this.setLogInError(true);
-        // await this.logOut();
+      } catch (error) {
+        console.log('error', error);
+      }
+    },
+    async qrAddTime(code: string) {
+      try {
+        const response = await api.qrAddTime(this.token, code);
+        return response;
+      } catch (error) {
+        console.log('error',error);
+        return error;
       }
     },
     async getUserProfile() {
@@ -200,6 +174,30 @@ export const useMainStore = defineStore("MainStoreId", {
         }
       }
     },
+    async qrCheckLoggedIn() {
+      if (!this.isLoggedIn) {
+        let token = this.token;
+        if (!token) {
+          const localToken = getLocalToken();
+          if (localToken) {
+            this.setToken(localToken);
+            token = localToken;
+          }
+        }
+        if (token) {
+          try {
+            const response = await api.getMe(token);
+            this.setLoggedIn(true);
+            this.setUserProfile(response.data);
+            // router.push("/main/dashboard");
+          } catch (error) {
+            await this.removeLogIn();
+          }
+        } else {
+          await this.removeLogIn();
+        }
+      }
+    },
     async removeLogIn() {
       removeLocalToken();
       this.setToken("");

+ 132 - 48
frontend/src/views/Qrcode.vue

@@ -1,20 +1,15 @@
 <script setup lang="ts">
-// import { appName, openRegisration } from "@/env";
 import { ref, reactive, computed, onMounted } from "vue";
 import { useMainStore } from "@/stores/main";
 import { useDisplay } from "vuetify";
 import { useRoute } from "vue-router";
-import { storeToRefs } from "pinia";
 import { useI18n } from "vue-i18n";
-// import router from "@/router";
-// import { saveLocalToken } from "@/utils";
 import { googleTokenLogin, decodeCredential } from "vue3-google-login";
 import type { CallbackTypes } from "vue3-google-login";
-import Navbar from "@/components/Navbar.vue";
+// import Navbar from "@/components/Navbar.vue";
 import Dialog from "@/components/Dialog.vue";
 
 const mainStore = useMainStore();
-const mainStoreRef = storeToRefs(mainStore);
 
 // variable
 const { t } = useI18n();
@@ -24,6 +19,8 @@ const email = ref("");
 const password = ref("");
 let ser_no: any = ref("");
 let showPassword = ref(false);
+let loginState = ref(false);
+let loading = ref(false);
 let dialog = reactive({
   msg: "",
   state: "",
@@ -32,7 +29,6 @@ let dialog = reactive({
 });
 
 // getter
-const loginError = mainStoreRef.readLoginError;
 const width = computed(() => {
   switch (name.value) {
     case "xs":
@@ -42,51 +38,97 @@ const width = computed(() => {
   }
 });
 
+function setDialog(status: Boolean, msg: String = "") {
+  if (status) {
+    dialog.show = true;
+    dialog.state = "success";
+    dialog.msg =
+      "儲值成功!<br/>已獲得價值 1000 元的 120 秒影片製作時間<br/>(儲值成功後即可登入電腦版進行影片製作)";
+    dialog.icon = "check_circle";
+  } else {
+    dialog.show = true;
+    dialog.state = "error";
+    dialog.msg = `${msg}`;
+    dialog.icon = "highlight_off";
+  }
+}
+
 // action
 async function submit() {
-  console.log("submit ser_no.value", ser_no.value);
+  loading.value = true;
+  if (email.value === "" || password.value === "") {
+    loading.value = false;
+    return;
+  }
+
   let response = await mainStore.qrLogIn(
     email.value,
     password.value,
     ser_no.value
   );
-  console.log("response", response);
-  if (response?.status === 200) {
-    console.log("加值成功");
+
+  loading.value = false;
+
+  if (response?.data.time_added === -1) {
+    setDialog(false, "此序號無效");
+  } else if (response?.status === 200) {
     setTimeout(() => {
-      dialog.show = true;
-      dialog.state = "success";
-      dialog.msg =
-        "儲值成功!<br/>已獲得價值 1000 元的 2 分鐘影片製作時間<br/>(加值成功後即可登入電腦版進行影片製作)";
-      dialog.icon = "check_circle";
+      setDialog(true);
     }, 500);
-  } else {
-    dialog.show = true;
-    dialog.state = "error";
-    dialog.msg = "查無此帳號/序號有誤";
-    dialog.icon = "highlight_off";
   }
 }
 
 // lifecycle
 onMounted(() => {
   console.log("onMounted");
-  if (route.params.ser_no) {
-    ser_no.value = route.params.ser_no;
-    console.log("ser_no.value", ser_no.value);
+  if (route.query["add_time_code"]) {
+    ser_no.value = route.query["add_time_code"];
   }
+  mainStore.qrCheckLoggedIn();
+  if (mainStore.token) {
+    checkCode("");
+  } else {
+    loginState.value = false;
+  }
+
+  async function checkCode(method: string = "") {
+    loading.value = true;
+    let response: any = await mainStore.qrAddTime(ser_no.value);
+    loading.value = false;
+    if (response.status === 200) {
+      loginState.value = true;
+      setDialog(true);
+    } else if (response.response.status === 400) {
+      setDialog(
+        false,
+        "此序號已被使用 <br> This serial number is already used"
+      );
+    }
+  }
+
+  // if (route.params.ser_no) {
+  //   ser_no.value = route.params.ser_no;
+  //   console.log("ser_no.value", ser_no.value);
+  // }
 });
 
-const callback: CallbackTypes.CredentialCallback = (response: any) => {
-  console.log("Handle the response", response);
+const callback: CallbackTypes.CredentialCallback = async (response: any) => {
+  loading.value = true;
   const userData: any = decodeCredential(response.credential);
-  console.log("Handle the userData", userData);
-  mainStore.qrGoogleLogin(userData.email, ser_no.value);
+  let res = await mainStore.qrGoogleLogin(userData.email, ser_no.value);
+  loading.value = false;
+  if (res?.data.time_added === -1) {
+    setDialog(false, "此序號無效");
+  } else if (res?.status === 200) {
+    setTimeout(() => {
+      setDialog(true);
+    }, 500);
+  }
 };
 </script>
 
 <template>
-  <Navbar />
+  <!-- <Navbar /> -->
   <v-container fluid class="pa-0 overflow-hidden">
     <div class="ai_anchor3_content">
       <div class="ai_anchor3_content_box">
@@ -107,14 +149,16 @@ const callback: CallbackTypes.CredentialCallback = (response: any) => {
         />
       </div>
 
-      <!-- <v-row
+      <v-row
         align="center"
+        justify="center"
         no-gutters
         class="overflow-hidden mx-auto login-form"
+        v-if="!loginState"
       >
-        <v-col :cols="width" class="px-6 my-8 my-md-0">
+        <v-col cols="12" class="px-6 my-8 my-md-0">
           <div class="form-title">
-            <h3>{{ t("login") }}</h3>
+            <h3>登入後即可獲得儲值金</h3>
             <span></span>
           </div>
           <v-form ref="form" lazy-validation>
@@ -144,13 +188,13 @@ const callback: CallbackTypes.CredentialCallback = (response: any) => {
             >
             </v-text-field>
 
-            <p class="text-center">
+            <!-- <p class="text-center">
               {{ t("haventAccount") }}
               <router-link to="/signup">{{ t("register") }}</router-link> /
               <router-link to="/recover-password">{{
                 t("forgotPsd")
               }}</router-link>
-            </p>
+            </p> -->
 
             <div class="d-flex flex-column">
               <v-btn rounded="pill" @click.prevent="submit" class="login-btn">
@@ -158,8 +202,14 @@ const callback: CallbackTypes.CredentialCallback = (response: any) => {
               </v-btn>
 
               <section class="line">
-                <p>&ensp;沒有帳號嗎?使用 Google 快速註冊 &ensp;</p>
-                <span></span>
+                <p class="d-none d-sm-block">
+                  沒有帳號嗎?使用 Google 快速註冊
+                </p>
+                <p class="d-block d-sm-none">
+                  沒有帳號嗎? <br />
+                  使用 Google 快速註冊
+                </p>
+                <!-- <span></span> -->
               </section>
 
               <div class="mx-auto mt-5" style="max-width: 235px">
@@ -173,7 +223,7 @@ const callback: CallbackTypes.CredentialCallback = (response: any) => {
             </div>
           </v-form>
         </v-col>
-      </v-row> -->
+      </v-row>
 
       <div class="ai_anchor_moichiu left-70" style="background: #67b5b5">
         <v-row align="center" no-gutters class="px-0 mx-0">
@@ -246,11 +296,21 @@ const callback: CallbackTypes.CredentialCallback = (response: any) => {
           </v-row>
         </div>
       </div> -->
-      
+
+      <div class="progress-item text-center">
+        <v-progress-circular
+          indeterminate
+          color="primary"
+          :size="50"
+          v-if="loading"
+        ></v-progress-circular>
+      </div>
+
       <div class="CTA_Button_div text-center">
         <button type="button" class="CTA_Button">
           <!-- 點我開始製作 AI 主播!<br />Log In -->
-          AI 三代主播系統 <br> 於 2023/04/06 正式上線!
+          AI 三代主播系統 <br />
+          於 2023/04/06 正式上線!
         </button>
       </div>
 
@@ -287,6 +347,7 @@ const callback: CallbackTypes.CredentialCallback = (response: any) => {
       :state="dialog.state"
       :dialog="dialog.show"
       :icon="dialog.icon"
+      :qrcode="true"
       @close="dialog.show = false"
     ></Dialog>
   </v-container>
@@ -311,15 +372,32 @@ const callback: CallbackTypes.CredentialCallback = (response: any) => {
       position: relative;
       z-index: 1;
       color: #fff;
-      background-color: #140720;
       letter-spacing: 1px;
-    }
-    span {
-      display: block;
-      border-top: 1px solid #fff;
-      width: 400px;
-      position: absolute;
-      bottom: 12px;
+      font-size: 24px;
+      font-weight: bold;
+      text-align: center;
+      @media (max-width: 600px) {
+        font-size: 18px;
+      }
+      &::after,
+      &::before {
+        content: "";
+        display: block;
+        height: 1px;
+        width: 50px;
+        background: #fff;
+        position: absolute;
+        bottom: 18px;
+        @media (max-width: 600px) {
+          bottom: 20px;
+        }
+      }
+      &::after {
+        left: -55px;
+      }
+      &::before {
+        right: -55px;
+      }
     }
   }
   .login-btn {
@@ -453,7 +531,7 @@ const callback: CallbackTypes.CredentialCallback = (response: any) => {
   margin: 0 auto;
   text-align: center;
   color: #fff;
-  margin-bottom: 80px;
+  margin-bottom: 15px;
   h1 {
     margin-bottom: -60px;
   }
@@ -679,4 +757,10 @@ const callback: CallbackTypes.CredentialCallback = (response: any) => {
   max-width: 100%;
   height: auto;
 }
+.progress-item {
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
 </style>

+ 41 - 37
frontend/src/views/main/Start.vue

@@ -1,47 +1,51 @@
-
 <template>
   <router-view></router-view>
 </template>
-  
+
 <script lang="ts">
-  import { onBeforeRouteUpdate } from 'vue-router'
-  import type { RouteLocationNormalized , NavigationGuardNext} from 'vue-router';
-  import { useMainStore } from '@/stores/main';
-  import { storeToRefs } from "pinia";
-  export default {
-    setup(){
-      onBeforeRouteUpdate((to, from, next) =>  {
-        startRouteGuard(to, from, next);
-      });
-    },
-    beforeRouteEnter(to, from, next){
+import { onBeforeRouteUpdate } from "vue-router";
+import type { RouteLocationNormalized, NavigationGuardNext } from "vue-router";
+import { useMainStore } from "@/stores/main";
+import { storeToRefs } from "pinia";
+export default {
+  setup() {
+    onBeforeRouteUpdate((to, from, next) => {
       startRouteGuard(to, from, next);
-    }
+    });
+  },
+  beforeRouteEnter(to, from, next) {
+    startRouteGuard(to, from, next);
+  },
+};
 
-  } 
-  // variable
+//function
+const startRouteGuard = async (
+  to: RouteLocationNormalized,
+  from: RouteLocationNormalized,
+  next: NavigationGuardNext
+) => {
+  const mainStore = useMainStore();
+  const mainStoreRef = storeToRefs(mainStore);
 
-  // excute in setup
-  
-  //lifecycle
-  
-  //function
-  const startRouteGuard = async (to:RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
-    const mainStore = useMainStore();
-    const mainStoreRef = storeToRefs(mainStore);
+  if (to.path === "/qrcode") {
+    next();
+    mainStore.qrCheckLoggedIn();
+  } else {
     mainStore.checkLoggedIn();
-    if (mainStoreRef.readIsLoggedIn.value) {
-      if (to.path === '/login' || to.path === '/') {
-        next('/main/dashboard');
-      } else {
-        next();
-      }
-    } else if (mainStoreRef.readIsLoggedIn.value === false) {
-      if (to.path === '/' || (to.path as string).startsWith('/main')) {
-        next('/login');
-      } else {
-        next();
-      }
+  }
+
+  if (mainStoreRef.readIsLoggedIn.value) {
+    if (to.path === "/login" || to.path === "/") {
+      next("/main/dashboard");
+    } else {
+      next();
+    }
+  } else if (mainStoreRef.readIsLoggedIn.value === false) {
+    if (to.path === "/" || (to.path as string).startsWith("/main")) {
+      next("/login");
+    } else {
+      next();
     }
-  };
+  }
+};
 </script>