Browse Source

用户相关接口联调

冯诚 2 years ago
parent
commit
66b480302f

+ 71 - 26
src/components/nav-bar/index.vue

@@ -4,7 +4,7 @@
       <div class="nav-icons">
         <i v-if="showNavIcons" class="icon-menu" @click="showMenu = true"></i>
         <router-link class="icon-logo" to="/"></router-link>
-        <i v-if="showNavIcons" class="icon-mine" @click="showMine = true"></i>
+        <i v-if="showNavIcons" class="icon-mine" @click="onClickMine"></i>
       </div>
       <transition
         v-if="showNavIcons"
@@ -35,9 +35,11 @@
       </transition>
     </div>
     <div v-if="showNavIcons" class="nav-bar-right">
-      <div class="user flex-ac" @click="showMine = !showMine">
+      <div class="user pointer flex-ac" @click="onClickMine">
         <i class="icon-mine"></i>
-        <span class="user-name">Rebecca</span>
+        <span class="user-name">{{
+          state.userInfo ? state.userInfo.name : 'Sign in'
+        }}</span>
       </div>
       <div class="contact">
         <i class="icon-phone"></i>
@@ -49,9 +51,10 @@
     </div>
     <transition enter-active-class="fadeIn" leave-active-class="fadeOut">
       <div
+        v-if="state.userInfo"
         v-show="showMine"
         class="nav-menu nav-dropdown"
-        :class="{ 'is-member': 1 }"
+        :class="{ 'is-member': state.userInfo.orders.length }"
       >
         <div class="nav-menu-header">
           <i class="logo"></i>
@@ -60,12 +63,16 @@
         <div class="nav-menu-body">
           <div class="info">
             <div class="p1">
-              <p class="name">Hi, Rebecca</p>
-              <p class="intro">Ordinary member</p>
+              <p class="name">Hi, {{ state.userInfo.name }}</p>
+              <p class="intro">
+                {{ state.userInfo.orders.length ? 'Pro' : 'Ordinary' }} member
+              </p>
             </div>
-            <div v-if="0" class="p2">
+            <div v-if="!state.userInfo.orders.length" class="p2">
               <p class="txt">You have not purchased a member</p>
-              <button class="btn">BUY</button>
+              <button class="btn" @click="$router.push('/fill-order')">
+                BUY
+              </button>
             </div>
             <div v-else class="p3">
               <div class="txt">
@@ -73,35 +80,54 @@
                 <strong class="primary">Buy now ></strong>
               </div>
               <div class="swiper">
-                <div class="swiper-item">
+                <div
+                  v-for="(item, index) of state.userInfo.orders"
+                  :key="index"
+                  class="swiper-item"
+                >
                   <div class="service">
                     <div class="service-title">
                       <i class="service-icon icon-lite"></i>
-                      <div class="service-type">Lite</div>
-                      <div class="service-period">2021/09/01 to 2022/08/31</div>
+                      <div class="service-type">{{ item.product_name }}</div>
+                      <div class="service-period">
+                        {{ item.start_time }} to {{ item.end_time }}
+                      </div>
                     </div>
-                    <div class="service-model tac">iPhone 12</div>
-                    <div class="service-code tac">QWERTYUIO12345678</div>
+                    <div class="service-model tac">{{ item.phone_info }}</div>
+                    <div class="service-code tac">{{ item.phone_imei }}</div>
                   </div>
                 </div>
               </div>
-              <div class="pagination">
-                <i class="dot active"></i>
-                <i class="dot"></i>
-                <i class="dot"></i>
+              <div
+                v-if="state.userInfo.orders.length > Infinity"
+                class="pagination"
+              >
+                <i
+                  v-for="n of state.userInfo.orders.length"
+                  :key="n"
+                  class="dot active"
+                ></i>
               </div>
             </div>
           </div>
           <div class="role" @click="$router.push('/account')">
-            {{ 1 ? 'Pro  Member >' : 'Ordinary  Member >' }}
+            {{
+              state.userInfo.orders.length
+                ? 'Pro  Member >'
+                : 'Ordinary  Member >'
+            }}
           </div>
           <ul class="dropdown-list">
-            <li class="dropdown-item">
-              <router-link to="/repaire/history">MY REPAIR REQUEST</router-link>
-            </li>
-            <li class="dropdown-item">
-              <router-link to="/order">MY ORDER</router-link>
-            </li>
+            <template v-if="state.userInfo.orders.length">
+              <li class="dropdown-item">
+                <router-link to="/repaire/history"
+                  >MY REPAIR REQUEST</router-link
+                >
+              </li>
+              <li class="dropdown-item">
+                <router-link to="/order">MY ORDER</router-link>
+              </li>
+            </template>
             <li class="dropdown-item">
               <router-link to="/gift-card">MY DISCOUNT COUPON</router-link>
             </li>
@@ -114,7 +140,7 @@
             <li class="dropdown-item">
               <router-link to="/account">ACCOUNT INFORMATION</router-link>
             </li>
-            <li class="dropdown-item">SIGN OUT</li>
+            <li class="dropdown-item" @click="signOut">SIGN OUT</li>
           </ul>
         </div>
       </div>
@@ -124,7 +150,9 @@
 
 <script setup lang="ts">
 import { ref, watch } from 'vue'
-import { useRoute } from 'vue-router'
+import { useRoute, useRouter } from 'vue-router'
+import { state, logout } from '@/store'
+import Dialog from '@/components/dialog'
 
 interface Props {
   showNavIcons?: boolean
@@ -133,6 +161,7 @@ interface Props {
 
 defineProps<Props>()
 
+const router = useRouter()
 const route = useRoute()
 const showMenu = ref(false)
 const showMine = ref(false)
@@ -143,6 +172,22 @@ watch(
     showMenu.value = showMine.value = false
   }
 )
+
+function onClickMine() {
+  if (state.userInfo) {
+    showMine.value = !showMine.value
+  } else {
+    router.push('/login')
+  }
+}
+
+async function signOut() {
+  await Dialog.confirm('TIPS', 'Are you sure you want to sign out?', {
+    confirmText: 'YES',
+  })
+  await logout()
+  router.push('/')
+}
 </script>
 
 <style lang="scss">

+ 2 - 2
src/hooks/useForm.ts

@@ -21,12 +21,12 @@ export default function useForm<Shap extends object>(
 
   const handleSubmit = debounce(
     function (
-      onSubmit: (values: Partial<Shap>) => any,
+      onSubmit: (values: Shap) => any,
       onInvalidSubmit?: (error: ValidationError) => any
     ) {
       object(options.schema as any)
         .validate(values)
-        .then(() => onSubmit(values))
+        .then(() => onSubmit(values as any))
         .catch((err: ValidationError) => {
           Toast(err.message)
           onInvalidSubmit?.(err)

+ 5 - 2
src/main.ts

@@ -1,9 +1,12 @@
 import { createApp } from 'vue'
 import App from './App.vue'
 import router from './router'
+import { getUserInfo } from './store'
 // import './utils/rem'
 import 'nprogress/nprogress.css'
 import './style'
 
-const app = createApp(App)
-app.use(router).mount('#app')
+getUserInfo(false).finally(() => {
+  const app = createApp(App)
+  app.use(router).mount('#app')
+})

+ 54 - 60
src/pages/account/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div v-if="state === 0 && info" class="p-account">
+  <div v-if="status === 0 && info" class="p-account">
     <h3 class="ptc-title">Account Information</h3>
     <div class="ptc-wrapper">
       <div class="ptc-block">
@@ -18,7 +18,11 @@
             <p class="ptc-label">Password</p>
             <p class="ptc-value">
               <span>********</span>
-              <span class="primary ml354" @click="state = 1">Edit ></span>
+              <span
+                class="primary pointer ml354"
+                @click="$router.push('/password/change')"
+                >Edit ></span
+              >
             </p>
           </div>
         </div>
@@ -43,7 +47,7 @@
       </div>
     </div>
     <h3 class="ptc-title pr">
-      Login Detail<button class="stroke-btn" @click="state = 2">Edit</button>
+      Login Detail<button class="stroke-btn" @click="status = 1">Edit</button>
     </h3>
     <div class="ptc-block">
       <div class="ptc-inner">
@@ -63,35 +67,7 @@
     </div>
   </div>
 
-  <div v-else-if="state === 1" class="p-account">
-    <h3 class="ptc-title">Change Password</h3>
-    <div class="ptc-wrapper">
-      <div class="ptc-block">
-        <div class="ptc-inner">
-          <div class="ptc-cell">
-            <p class="ptc-label">New password</p>
-            <p class="ptc-value">
-              <input
-                class="ptc-input"
-                type="password"
-                placeholder="Please enter"
-              />
-            </p>
-          </div>
-        </div>
-      </div>
-      <div class="ptc-button-group">
-        <div class="ptc-inner">
-          <button class="ptc-button">SUBMIT</button>
-          <button class="ptc-button ptc-button--stroke" @click="state = 0">
-            BACK
-          </button>
-        </div>
-      </div>
-    </div>
-  </div>
-
-  <div v-else-if="state === 2" class="p-account">
+  <div v-else-if="status === 1" class="p-account">
     <h3 class="ptc-title">Modify My profile</h3>
     <div class="ptc-wrapper">
       <div class="ptc-block">
@@ -100,6 +76,7 @@
             <p class="ptc-label">Name</p>
             <p class="ptc-value">
               <input
+                v-model="values.name"
                 class="ptc-input"
                 placeholder="Please enter your real name"
               />
@@ -109,6 +86,7 @@
             <p class="ptc-label">Phone Number</p>
             <p class="ptc-value">
               <input
+                v-model="values.mobile"
                 class="ptc-input"
                 placeholder="Please enter your phone number"
               />
@@ -117,17 +95,31 @@
           <div class="ptc-cell">
             <p class="ptc-label">Address</p>
             <p class="ptc-value">
-              <input class="ptc-input mb24" placeholder="Road" />
-              <input class="ptc-input mb24" placeholder="State" />
-              <input class="ptc-input" placeholder="Zip code" />
+              <input
+                v-model="address[0]"
+                class="ptc-input mb24"
+                placeholder="Road"
+              />
+              <input
+                v-model="address[1]"
+                class="ptc-input mb24"
+                placeholder="State"
+              />
+              <input
+                v-model="address[2]"
+                class="ptc-input"
+                placeholder="Zip code"
+              />
             </p>
           </div>
         </div>
       </div>
       <div class="ptc-button-group">
         <div class="ptc-inner">
-          <button class="ptc-button">SUBMIT</button>
-          <button class="ptc-button ptc-button--stroke" @click="state = 0">
+          <button class="ptc-button" @click="handleSubmit(applyUpdate)">
+            SUBMIT
+          </button>
+          <button class="ptc-button ptc-button--stroke" @click="status = 0">
             BACK
           </button>
         </div>
@@ -136,33 +128,35 @@
   </div>
 </template>
 
-<script>
-import { defineComponent } from 'vue'
-import { getUserInfo } from '@/service/user'
+<script setup lang="ts">
+import { ref, computed, watch } from 'vue'
+import { state, updateUserInfo } from '@/store'
+import useForm from '@/hooks/useForm'
+import { string } from 'yup'
+import Toast from '@/components/toast'
 
-export default defineComponent({
-  name: 'Account',
-  async beforeRouteEnter(to, from, next) {
-    try {
-      const { results } = await getUserInfo()
-      next(vm => (vm.info = results))
-    } catch (err) {
-      next(new Error(err.message))
-    }
+const status = ref(0)
+const address = ref<string[]>([])
+const info = computed(() => state.userInfo)
+const { values, handleSubmit } = useForm<ApiUser.Update.Request>({
+  initialValues: {
+    name: state.userInfo?.name,
+    mobile: state.userInfo?.mobile,
   },
-  data() {
-    return {
-      /** @type {ApiUser.Info.Response} */
-      info: null,
-      state: 0,
-    }
-  },
-  watch: {
-    state() {
-      window.scrollTo(0, 0)
-    },
+  schema: {
+    name: string().required(),
+    mobile: string().required(),
   },
 })
+
+watch(status, () => window.scrollTo(0, 0))
+
+async function applyUpdate() {
+  values.address = address.value.filter(Boolean).join(',')
+  await updateUserInfo(values as any)
+  Toast('success')
+  status.value = 0
+}
 </script>
 
 <style lang="scss">

+ 25 - 13
src/pages/invite/index.vue

@@ -29,7 +29,12 @@
                 readonly
                 value="http://www.ptcplis.com/2333453"
               />
-              <button class="ptc-button">Copy Link</button>
+              <button
+                class="ptc-button"
+                @click="copy('http://www.ptcplis.com/2333453')"
+              >
+                Copy Link
+              </button>
             </p>
           </div>
           <div class="method">
@@ -45,18 +50,20 @@
     </div>
 
     <div class="p-bottom">
-      <h3 class="ptc-title">My Reward ${{ totalAmount }}</h3>
-      <div class="block records">
-        <div class="ptc-inner-md">
-          <ul>
-            <li v-for="(award, index) of awards" :key="index" class="record">
-              <span class="left">{{ award.title }}</span>
-              <span class="center">{{ award.created_at }}</span>
-              <span class="right">${{ award.gift_card_amount }}</span>
-            </li>
-          </ul>
+      <template v-if="awards.length">
+        <h3 class="ptc-title">My Reward ${{ totalAmount }}</h3>
+        <div class="block records">
+          <div class="ptc-inner-md">
+            <ul>
+              <li v-for="(award, index) of awards" :key="index" class="record">
+                <span class="left">{{ award.title }}</span>
+                <span class="center">{{ award.created_at }}</span>
+                <span class="right">${{ award.gift_card_amount }}</span>
+              </li>
+            </ul>
+          </div>
         </div>
-      </div>
+      </template>
       <h3 class="ptc-title">Activity Details</h3>
       <div class="block details">
         <div class="ptc-inner-md">
@@ -83,6 +90,7 @@ import { defineComponent } from 'vue'
 import { string } from 'yup'
 import { inviteFriends, getInviteRewards } from '@/service/user'
 import Toast from '@/components/toast'
+import copy from '@/utils/clipboard'
 
 export default defineComponent({
   name: 'InviteFriends',
@@ -112,11 +120,15 @@ export default defineComponent({
             string().email('Please check email addresses').validateSync(email)
           )
         await inviteFriends(this.emails)
-        Toast('done')
+        Toast('success')
       } catch (err) {
         Toast(err.message)
       }
     },
+    copy(content: string) {
+      copy(content)
+      Toast('copy success')
+    },
   },
 })
 </script>

+ 9 - 4
src/pages/login/index.vue

@@ -20,6 +20,7 @@
             class="ptc-input"
             type="password"
             placeholder="password"
+            @keydown="$event.keyCode === 13 && handleSubmit(handleLogin)"
           />
         </div>
         <div class="ptc-form-item" style="font-size: 0">
@@ -82,13 +83,16 @@
 
 <script setup lang="ts">
 import { ref, reactive } from 'vue'
-import { useRouter } from 'vue-router'
+import { useRouter, useRoute } from 'vue-router'
 // import NavBar from '@/components/nav-bar/index.vue'
 import { login } from '@/service/user'
 import useForm from '@/hooks/useForm'
 import { string } from 'yup'
+import { getUserInfo } from '@/store'
+import Toast from '@/components/toast'
 
 const router = useRouter()
+const { from } = useRoute().query as any
 const showSurprise = ref(false)
 const { values, handleSubmit } = useForm<ApiUser.Login.Request>({
   schema: {
@@ -98,9 +102,10 @@ const { values, handleSubmit } = useForm<ApiUser.Login.Request>({
 })
 
 async function handleLogin() {
-  await login(values as any)
-  router.push('/')
-  // router.push('/password/change')
+  const { message } = await login(values as any)
+  Toast(message)
+  const { need_change } = await getUserInfo()
+  ;+need_change ? router.push('/password/change') : router.replace(from || '/')
 }
 </script>
 

+ 14 - 1
src/pages/password/index.vue

@@ -5,6 +5,14 @@
         <h3 class="title">Change Password</h3>
         <div class="ptc-form-item">
           <input
+            v-model="values.old_password"
+            class="ptc-input"
+            type="password"
+            placeholder="Enter old password"
+          />
+        </div>
+        <div class="ptc-form-item">
+          <input
             v-model="values.new_password"
             class="ptc-input"
             type="password"
@@ -110,9 +118,11 @@ const props = defineProps<{ action: 'change' | 'reset' }>()
 
 const router = useRouter()
 const { query } = useRoute() as any
+const fromPath = history.state.back
 const step = query.step ? +(query.step as string) : 0
 const { values, handleSubmit } = useForm<ApiUser.PasswordChange.Request>({
   schema: {
+    old_password: string().required(),
     new_password: string().required(),
     new_password_confirmation: string().required(),
   },
@@ -134,7 +144,10 @@ function next() {
 }
 
 async function applyChange() {
-  // await changePassword(values as any)
+  const { message } = await changePassword(values as any)
+  Toast(message)
+  state.userInfo = null
+  fromPath.startsWith('/login') ? router.back() : router.push('/login')
 }
 
 async function sendEmail() {

+ 27 - 1
src/router.ts

@@ -1,9 +1,16 @@
 import { createRouter, createWebHistory } from 'vue-router'
+import { state } from './store'
 // @ts-ignore
 import NProgress from 'nprogress'
 
 NProgress.configure({ showSpinner: false })
 
+declare module 'vue-router' {
+  interface RouteMeta {
+    auth?: boolean
+  }
+}
+
 const router = createRouter({
   history: createWebHistory(import.meta.env.BASE_URL),
   scrollBehavior() {
@@ -27,6 +34,7 @@ const router = createRouter({
       path: '/imei/:action',
       component: () => import('./pages/imei/index.vue'),
       props: true,
+      meta: { auth: true },
     },
     {
       path: '/fill-order',
@@ -40,45 +48,63 @@ const router = createRouter({
     {
       path: '/account',
       component: () => import('./pages/account/index.vue'),
+      meta: { auth: true },
     },
     {
       path: '/order',
       component: () => import('./pages/my-order/index.vue'),
+      meta: { auth: true },
     },
     {
       path: '/order/:id',
       component: () => import('./pages/benefits/index.vue'),
+      meta: { auth: true },
     },
     {
       path: '/gift-card',
       component: () => import('./pages/gift-card/index.vue'),
+      meta: { auth: true },
     },
     {
       path: '/mailing',
       component: () => import('./pages/mailing/index.vue'),
+      meta: { auth: true },
     },
     {
       path: '/renewal',
       component: () => import('./pages/renewal/index.vue'),
       props: true,
+      meta: { auth: true },
     },
     {
       path: '/repaire/appointment',
       component: () => import('./pages/repaire/appointment.vue'),
       props: true,
+      meta: { auth: true },
     },
     {
       path: '/repaire/history',
       component: () => import('./pages/repaire/history.vue'),
+      meta: { auth: true },
     },
     {
       path: '/invite',
       component: () => import('./pages/invite/index.vue'),
+      meta: { auth: true },
     },
   ],
 })
 
-router.beforeEach(() => {
+router.beforeEach(to => {
+  if (to.meta.auth && !state.userInfo) {
+    return {
+      path: '/login',
+      query: {
+        from: to.fullPath,
+      },
+      replace: true,
+    }
+  }
   NProgress.start()
 })
 router.afterEach(() => NProgress.done())

+ 3 - 0
src/service/types/user.d.ts

@@ -36,6 +36,7 @@ declare namespace ApiUser {
     interface Request {
       name: string
       mobile: string
+      address: string
     }
   }
 
@@ -48,6 +49,8 @@ declare namespace ApiUser {
       address: string
       /** 注册来源:1邮箱;2Facebook;3Google;4Apple */
       from: number
+      /** 是否需要修改密码 */
+      need_change: 0 | 1
       num_gift_card: number
       num_order: number
       num_repair: number

+ 2 - 2
src/service/user.ts

@@ -28,8 +28,8 @@ export function updateUserInfo(data: ApiUser.Update.Request) {
   return request.post('/user/update', data)
 }
 
-export function getUserInfo() {
-  return request.get<ApiUser.Info.Response>('/user/info')
+export function getUserInfo(reportError?: boolean) {
+  return request.get<ApiUser.Info.Response>('/user/info', { reportError })
 }
 
 export function inviteFriends(emails: string) {

+ 18 - 0
src/store.ts

@@ -1,5 +1,23 @@
 import { reactive } from 'vue'
+import * as api from '@/service/user'
 
 export const state = reactive({
   bgWhite: false,
+  userInfo: null as ApiUser.Info.Response | null,
 })
+
+export async function getUserInfo(reportError?: boolean) {
+  const { results } = await api.getUserInfo(reportError)
+  state.userInfo = results
+  return results
+}
+
+export async function updateUserInfo(data: ApiUser.Update.Request) {
+  await api.updateUserInfo(data)
+  Object.assign(state.userInfo as ApiUser.Info.Response, data)
+}
+
+export async function logout() {
+  await api.logout()
+  state.userInfo = null
+}

+ 10 - 0
src/utils/clipboard.ts

@@ -0,0 +1,10 @@
+export default function copyToClipboard(content: string) {
+  const input = document.createElement('input')
+  input.style.position = 'absolute'
+  input.style.left = '9999px'
+  input.value = content
+  document.body.appendChild(input)
+  input.select()
+  document.execCommand('copy')
+  document.body.removeChild(input)
+}