Kaynağa Gözat

add login logout functionality

fengchang 1 yıl önce
ebeveyn
işleme
466a938028

+ 20 - 4
hichina-main-back/src/main/java/com/hichina/main/back/hichinamainback/controller/UserController.java

@@ -3,10 +3,7 @@ package com.hichina.main.back.hichinamainback.controller;
 import com.hichina.main.back.hichinamainback.config.CustomAuthenticationProvider;
 import com.hichina.main.back.hichinamainback.config.EnableHichinaAutoLog;
 import com.hichina.main.back.hichinamainback.mapper.UserMapper;
-import com.hichina.main.back.hichinamainback.model.DTO.GeneralSingleStringDTO;
-import com.hichina.main.back.hichinamainback.model.DTO.HichinaResponse;
-import com.hichina.main.back.hichinamainback.model.DTO.UpdatePasswordDTO;
-import com.hichina.main.back.hichinamainback.model.DTO.UserUpdateRequestDTO;
+import com.hichina.main.back.hichinamainback.model.DTO.*;
 import com.hichina.main.back.hichinamainback.model.User;
 import com.hichina.main.back.hichinamainback.utils.UserUtil;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -72,6 +69,25 @@ public class UserController {
         return currentUser();
     }
 
+    private SimplifiedUserDTO simplifyUser(User user){
+        SimplifiedUserDTO ret = new SimplifiedUserDTO();
+        ret.setUsername(currentUser());
+        ret.setProfileImageUrl(user.getProfileImageUrl());
+        return ret;
+    }
+
+    @GetMapping(value="/whoamiv2")
+    @EnableHichinaAutoLog(description = "find who am i v2 with profile image")
+    public HichinaResponse whoamiV2(){
+        HichinaResponse ret = new HichinaResponse();
+        User user = UserUtil.getUserByCurrentPrincipal(userMapper, UserController.currentUser());
+        ret.setOk(true);
+        SimplifiedUserDTO simplifiedUserDTO = simplifyUser(user);
+        ret.setData(simplifiedUserDTO);
+        ret.setMessage("Successfully got my user simplified info");
+        return ret;
+    }
+
     public static String currentUser(){
         Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
         if (!(authentication instanceof AnonymousAuthenticationToken)) {

+ 11 - 0
hichina-main-back/src/main/java/com/hichina/main/back/hichinamainback/model/DTO/SimplifiedUserDTO.java

@@ -0,0 +1,11 @@
+package com.hichina.main.back.hichinamainback.model.DTO;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+public class SimplifiedUserDTO {
+    private String username;
+    private String profileImageUrl;
+}

+ 2 - 2
hichina-main-front-mobile-first/quasar.config.js

@@ -84,7 +84,7 @@ module.exports = configure(function (ctx) {
 
     // https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-framework
     framework: {
-      config: {},
+      config: { notify: {} },
 
       // iconSet: 'material-icons', // Quasar icon set
       // lang: 'en-US', // Quasar language pack
@@ -97,7 +97,7 @@ module.exports = configure(function (ctx) {
       // directives: [],
 
       // Quasar plugins
-      plugins: [],
+      plugins: ["Notify"],
     },
 
     // animations: 'all', // --- includes all animations

BIN
hichina-main-front-mobile-first/src/assets/profileplaceholder.png


+ 47 - 16
hichina-main-front-mobile-first/src/boot/globalMixin.js

@@ -1,28 +1,59 @@
 import { boot } from "quasar/wrappers";
-
 // "async" is optional;
 // more info on params: https://v2.quasar.dev/quasar-cli/boot-files
-export default boot(async (/* { app, router, ... } */ { app }) => {
+export default boot(async (/* { app, router, ... } */ { app, router }) => {
   // something to do
+  const testGlobal2 = () => {
+    console.log("testGlobal2");
+  };
+
+  const normalizeMultiImageUrl = (input) => {
+    if (input.indexOf(",") > -1) {
+      return input.split(",").shift();
+    } else if (input.indexOf(";") > -1) {
+      return input.split(";").shift();
+    }
+    return input;
+  };
+
+  const removeHtmlTag = (input) => {
+    var div = document.createElement("div");
+    div.innerHTML = input;
+    var text = div.textContent || div.innerText || "";
+    return text;
+  };
+
+  const generalNotify = (q, isPositive, message) => {
+    q.notify({
+      position: "top-right",
+      timeout: 2500,
+      color: isPositive ? "positive" : "negative",
+      textColor: "white",
+      message: message,
+      actions: [{ icon: "close", color: "white" }],
+    });
+  };
+
+  const goPage = (val) => {
+    router.push(val);
+  };
+
+  app.config.globalProperties.$testGlobal2 = testGlobal2;
+  app.config.globalProperties.$normalizeMultiImageUrl = normalizeMultiImageUrl;
+  app.config.globalProperties.$removeHtmlTag = removeHtmlTag;
+  app.config.globalProperties.$generalNotify = generalNotify;
+  app.config.globalProperties.$goPage = goPage;
+
   app.mixin({
     methods: {
-      testGlobalMethod() {
-        console.log("from global mixin method");
-      },
-      normalizeMultiImageUrl(input) {
-        if (input.indexOf(",") > -1) {
-          return input.split(",").shift();
-        } else if (input.indexOf(";") > -1) {
-          return input.split(";").shift();
-        }
-        return input;
-      },
+      testGlobal2,
+      generalNotify,
+      normalizeMultiImageUrl,
+      removeHtmlTag,
       gotoUrl(url) {
         window.location = url;
       },
-      goPage(val) {
-        this.$router.push(val);
-      },
+      goPage,
     },
   });
 });

+ 73 - 50
hichina-main-front-mobile-first/src/layouts/MainLayout.vue

@@ -57,7 +57,7 @@
         <!-- this is the trick -->
         <q-space />
 
-        <div class="row no-wrap">
+        <div v-if="currentUser === ''" class="row no-wrap">
           <q-btn
             flat
             dense
@@ -80,6 +80,26 @@
             v-if="$q.screen.gt.xs"
           />
         </div>
+        <div v-if="currentUser != ''" class="row">
+          <q-btn v-if="$q.screen.gt.xs" round flat class="q-mr-md">
+            <q-avatar size="36px">
+              <img :src="currentProfileImage" />
+            </q-avatar>
+            <q-tooltip>{{ currentUser }}</q-tooltip>
+          </q-btn>
+        </div>
+        <div v-if="currentUser != ''" class="row no-wrap">
+          <q-btn
+            @click="logout()"
+            v-if="$q.screen.gt.xs"
+            round
+            dense
+            color="primary"
+            class="q-mr-md"
+            icon="logout"
+            ><q-tooltip>logout</q-tooltip></q-btn
+          >
+        </div>
         <div>
           <q-btn
             v-if="!$q.screen.gt.xs"
@@ -132,7 +152,7 @@
 
           <q-separator class="q-my-md" />
 
-          <q-item v-ripple clickable>
+          <q-item v-if="currentUser === ''" v-ripple clickable>
             <q-item-section avatar>
               <q-icon color="grey" name="login" />
             </q-item-section>
@@ -140,7 +160,7 @@
               <q-item-label>Login</q-item-label>
             </q-item-section>
           </q-item>
-          <q-item v-ripple clickable>
+          <q-item v-if="currentUser === ''" v-ripple clickable>
             <q-item-section avatar>
               <q-icon color="grey" name="account_circle" />
             </q-item-section>
@@ -224,52 +244,8 @@
 </template>
 
 <script>
-import { defineComponent, ref } from "vue";
-
-const linksList = [
-  {
-    title: "Docs",
-    caption: "quasar.dev",
-    icon: "school",
-    link: "https://quasar.dev",
-  },
-  {
-    title: "Github",
-    caption: "github.com/quasarframework",
-    icon: "code",
-    link: "https://github.com/quasarframework",
-  },
-  {
-    title: "Discord Chat Channel",
-    caption: "chat.quasar.dev",
-    icon: "chat",
-    link: "https://chat.quasar.dev",
-  },
-  {
-    title: "Forum",
-    caption: "forum.quasar.dev",
-    icon: "record_voice_over",
-    link: "https://forum.quasar.dev",
-  },
-  {
-    title: "Twitter",
-    caption: "@quasarframework",
-    icon: "rss_feed",
-    link: "https://twitter.quasar.dev",
-  },
-  {
-    title: "Facebook",
-    caption: "@QuasarFramework",
-    icon: "public",
-    link: "https://facebook.quasar.dev",
-  },
-  {
-    title: "Quasar Awesome",
-    caption: "Community Quasar projects",
-    icon: "favorite",
-    link: "https://awesome.quasar.dev",
-  },
-];
+import { defineComponent, onMounted, ref } from "vue";
+import { api } from "boot/axios";
 
 export default defineComponent({
   name: "MainLayout",
@@ -278,10 +254,57 @@ export default defineComponent({
 
   setup() {
     const leftDrawerOpen = ref(false);
+    const currentUser = ref("");
+    const currentProfileImage = ref("");
 
+    function whoami() {
+      api
+        .get("/api/v1/user/whoamiv2")
+        .then(function (response) {
+          console.log("current user:" + response.data.data.username);
+          console.log(
+            "current user profile image:" + response.data.data.profileImageUrl
+          );
+          currentUser.value = response.data.data.username;
+          currentProfileImage.value = response.data.data.profileImageUrl;
+          if (
+            currentProfileImage.value == null ||
+            currentProfileImage.value.length < 1
+          ) {
+            currentProfileImage.value =
+              "https://photoprism.hichinatravel.com/api/v1/t/8623903789c65a160279faa0b33159413cb18af4/32mcf2k4/fit_2048";
+          }
+          console.log("currentProfileImage.value");
+          console.log(currentProfileImage.value);
+        })
+        .catch(function (error) {
+          console.log("not logged in err");
+          // router.push({name: 'home'})
+        });
+    }
+    function logout() {
+      console.log("logging out...");
+      api
+        .post(
+          "/logout",
+          {},
+          { headers: { "Content-Type": "application/x-www-form-urlencoded" } }
+        )
+        .then((response) => {
+          location.reload();
+        })
+        .catch((e) => {
+          location.reload();
+        });
+    }
+    onMounted(() => {
+      whoami();
+    });
     return {
-      essentialLinks: linksList,
       leftDrawerOpen,
+      currentUser,
+      currentProfileImage,
+      logout,
       toggleLeftDrawer() {
         leftDrawerOpen.value = !leftDrawerOpen.value;
       },

+ 9 - 0
hichina-main-front-mobile-first/src/pages/BlogDetailPage.vue

@@ -0,0 +1,9 @@
+<template>
+  <q-page> blog detail </q-page>
+</template>
+
+<script>
+export default {
+  name: "BlogDetail",
+};
+</script>

+ 188 - 0
hichina-main-front-mobile-first/src/pages/DestinationDetailPage.vue

@@ -0,0 +1,188 @@
+<template>
+  <q-page>
+    <div class="row" style="height: 110px; background-color: #e5f2fa">
+      <div class="col-sm-2"></div>
+      <div class="col-12 col-sm-10 column">
+        <div class="q-pt-md q-pl-md text-black text-weight-bold">
+          Home > Destination > {{ destinationName }}
+        </div>
+        <div class="col text-weight-bold q-pl-md text-h3">
+          {{ destinationName }}
+        </div>
+      </div>
+    </div>
+    <div class="row">
+      <div class="col-12 col-sm-5 row q-px-md q-py-lg" style="height: 450px">
+        <div class="col-12" style="background-color: white">
+          <q-img :src="destinationProfileImage" fit="fill"> </q-img>
+        </div>
+      </div>
+      <div class="col-12 col-sm-7 row q-px-md q-py-lg" style="height: 450px">
+        <div class="column col-12">
+          <div
+            class="col-9 q-pa-md text-subtitle1"
+            style="background-color: #fff9c6"
+            v-html="description"
+          ></div>
+          <div class="col-3 row">
+            <div class="col-6 text-h4 flex flex-center">
+              Guidbook of {{ destinationName }}
+            </div>
+            <div class="col-6 flex flex-center">
+              <q-btn class="glossy" rounded color="blue-6"
+                ><q-icon left size="3em" name="download" />
+                <div>Download</div></q-btn
+              >
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div
+      class="row flex flex-center text-h4"
+      style="border-top: 1px solid gray; height: 80px"
+    >
+      Places to go in {{ destinationName }}
+    </div>
+    <div
+      v-for="item in childDestinations"
+      v-bind:key="item.destinationId"
+      class="row q-pa-md"
+      style="width: 90%; min-height: 200px; margin: 0 auto"
+    >
+      <div class="col-3 q-pa-md" style="border-right: 1px solid gray">
+        <q-img fit="fill" :src="item.destinationProfileImage"></q-img>
+      </div>
+      <div class="col-9 column q-pa-md">
+        <div class="text-h4 text-weight-bold">{{ item.destinationName }}</div>
+        <div class="q-pt-md">
+          {{ removeHtmlTag(item.description) }}
+        </div>
+      </div>
+    </div>
+  </q-page>
+</template>
+
+<script>
+import { ref, onMounted, getCurrentInstance } from "vue";
+import { api } from "boot/axios";
+import { useRoute } from "vue-router";
+import { useSeoMeta } from "unhead";
+export default {
+  name: "DestinationDetailPage",
+  setup() {
+    const app = getCurrentInstance().appContext.app;
+    const gp = app.config.globalProperties;
+
+    const route = useRoute();
+
+    const description = ref("");
+    const destinationProfileImage = ref("");
+    const downloadUrl = ref("");
+    const destinationName = ref("");
+    const childDestinations = ref([]);
+    const relevantToursProduct = ref([]);
+
+    function logPv() {
+      api
+        .post(
+          "/api/public/pagestats/pv/destination-detail-" +
+            route.params.destinationId
+        )
+        .then((res) => {
+          console.log("log pv:");
+          console.log(res.data);
+        })
+        .catch((err) => {
+          console.error("Error:", err);
+        });
+    }
+    function logView() {
+      api
+        .post(
+          "/api/public/pagestats/view-destination/" + route.params.destinationId
+        )
+        .then((res) => {
+          console.log("view cnt of this destination:");
+          console.log(res.data);
+        })
+        .catch((err) => {
+          console.error("Error:", err);
+        });
+    }
+
+    function loadRelatedTours() {
+      api
+        .get(
+          "/api/public/destination/relavanttourproduct/" +
+            route.params.destinationId
+        )
+        .then((response) => {
+          if (response.data.ok === true) {
+            relevantToursProduct.value = response.data.data;
+          } else {
+            relevantToursProduct.value = [];
+          }
+        })
+        .catch((e) => {
+          console.log("get relevant tours product err");
+          console.log(e);
+        });
+    }
+
+    function loadChildrenDestination() {
+      api
+        .get("/api/public/destination/children/" + route.params.destinationId)
+        .then((response) => {
+          childDestinations.value = response.data.data;
+        })
+        .catch((e) => {
+          console.log("get destination detail err");
+          console.log(e);
+        });
+    }
+
+    function loadDestinations() {
+      api
+        .get("/api/public/destination/" + route.params.destinationId)
+        .then((response) => {
+          description.value = response.data.data.description;
+          destinationProfileImage.value = gp.$normalizeMultiImageUrl(
+            response.data.data.destinationProfileImage
+          );
+          console.log(destinationProfileImage.value);
+          downloadUrl.value = response.data.data.downloadUrl;
+          destinationName.value = response.data.data.destinationName;
+          useSeoMeta({
+            title: destinationName.value,
+            description: description.value,
+            ogDescription: description.value,
+            ogTitle: destinationName.value,
+            ogImage:
+              "https://www.hichinatravel.com/static/png/name-67280b81.png",
+            twitterCard: "summary_large_image",
+          });
+        })
+        .catch((e) => {
+          console.log("get destination detail err");
+          console.log(e);
+        });
+    }
+
+    onMounted(() => {
+      logPv();
+      logView();
+      console.log("on mounted destination detail page");
+      loadDestinations();
+      loadChildrenDestination();
+      loadRelatedTours();
+    });
+    return {
+      destinationName,
+      destinationProfileImage,
+      description,
+      childDestinations,
+    };
+  },
+};
+</script>

+ 0 - 5
hichina-main-front-mobile-first/src/pages/IndexPage.vue

@@ -283,11 +283,6 @@ export default defineComponent({
           );
 
           loading.value = false;
-
-          console.log(
-            "globalUnifiedItemList.value with currentPage: " + currentPage.value
-          );
-          console.log(globalUnifiedItemList.value);
         })
         .catch(function (error) {
           console.log(error);

+ 77 - 3
hichina-main-front-mobile-first/src/pages/LoginPage.vue

@@ -9,21 +9,34 @@
           Login
         </div>
         <div class="col-12 q-pa-md">
-          <q-input color="blue-12" v-model="text" label="Enter your username">
+          <q-input
+            :rules="[(val) => !!val || 'Field is required']"
+            color="blue-12"
+            v-model="username"
+            label="Enter your username"
+            ref="usernameInput"
+          >
             <template v-slot:prepend>
               <q-icon name="account_circle" />
             </template>
           </q-input>
         </div>
         <div class="col-12 q-pa-md">
-          <q-input color="blue-12" v-model="text" label="Enter your password">
+          <q-input
+            :rules="[(val) => !!val || 'Field is required']"
+            color="blue-12"
+            v-model="password"
+            type="password"
+            label="Enter your password"
+            ref="passwordInput"
+          >
             <template v-slot:prepend>
               <q-icon name="lock" />
             </template>
           </q-input>
         </div>
         <div class="col-12 q-pa-md">
-          <q-btn color="primary" label="Login" />
+          <q-btn color="primary" @click="login()" label="Login" />
         </div>
       </div>
     </div>
@@ -31,8 +44,69 @@
 </template>
 
 <script>
+import { ref, onMounted, getCurrentInstance } from "vue";
+import { useQuasar } from "quasar";
+import { api } from "boot/axios";
+import Qs from "qs";
 export default {
   name: "LoginPage",
+  setup() {
+    const instance = getCurrentInstance();
+    const app = getCurrentInstance().appContext.app;
+    const gp = app.config.globalProperties;
+    const $q = useQuasar();
+
+    const username = ref("");
+    const password = ref("");
+
+    function login() {
+      const isValidPassword = instance.refs.passwordInput.validate();
+      const isValidUsername = instance.refs.usernameInput.validate();
+      if (isValidUsername && isValidPassword) {
+        // do real login logic
+        var data = {
+          username: username.value,
+          password: password.value,
+        };
+
+        api
+          .post("/login", Qs.stringify(data), {
+            headers: { "Content-Type": "application/x-www-form-urlencoded" },
+          })
+          .then((response) => {
+            gp.$generalNotify($q, true, "Login succeed!");
+            location.reload();
+          })
+          .catch((e) => {
+            gp.$generalNotify($q, false, "Fail login error message: " + e);
+          });
+      } else {
+        gp.$generalNotify($q, false, "error");
+      }
+    }
+
+    function whoami() {
+      api
+        .get("/api/v1/user/whoami")
+        .then(function (response) {
+          console.log("current user in setup: " + response.data);
+          gp.$goPage("/");
+        })
+        .catch(function (error) {
+          console.log("currently not logged in setup: " + error);
+        });
+    }
+
+    onMounted(() => {
+      whoami();
+    });
+
+    return {
+      login,
+      username,
+      password,
+    };
+  },
 };
 </script>
 <style lang="sass" scoped>

+ 0 - 7
hichina-main-front-mobile-first/src/pages/ProductDetailPage.vue

@@ -329,13 +329,6 @@ export default {
       },
     };
 
-    function removeHtmlTag(input) {
-      var div = document.createElement("div");
-      div.innerHTML = input;
-      var text = div.textContent || div.innerText || "";
-      return text;
-    }
-
     function logPv() {
       api
         .post(

+ 14 - 0
hichina-main-front-mobile-first/src/router/routes.js

@@ -42,6 +42,20 @@ const routes = [
       { path: "", component: () => import("pages/ProductDetailPage.vue") },
     ],
   },
+  {
+    path: "/destination-detail/:destinationId",
+    component: () => import("layouts/MainLayout.vue"),
+    children: [
+      { path: "", component: () => import("pages/DestinationDetailPage.vue") },
+    ],
+  },
+  {
+    path: "/blog-detail/:blogI",
+    component: () => import("layouts/MainLayout.vue"),
+    children: [
+      { path: "", component: () => import("pages/BlogDetailPage.vue") },
+    ],
+  },
 
   // Always leave this as last one,
   // but you can also remove it