Browse Source

登录注册逻辑完善

冯诚 2 years ago
parent
commit
11640a130a
7 changed files with 208 additions and 23 deletions
  1. 3 1
      package.json
  2. 48 0
      src/hooks/useForm.ts
  3. 20 9
      src/pages/login/index.vue
  4. 55 9
      src/pages/password/index.vue
  5. 33 3
      src/pages/register/index.vue
  6. 1 0
      src/service/types/user.d.ts
  7. 48 1
      yarn.lock

+ 3 - 1
package.json

@@ -12,9 +12,11 @@
     "lodash-es": "^4.17.21",
     "nprogress": "^0.2.0",
     "vue": "^3.2.25",
-    "vue-router": "4"
+    "vue-router": "4",
+    "yup": "^0.32.11"
   },
   "devDependencies": {
+    "@types/lodash-es": "^4.17.6",
     "@types/node": "^17.0.8",
     "@vitejs/plugin-legacy": "^1.6.4",
     "@vitejs/plugin-vue": "^2.0.0",

+ 48 - 0
src/hooks/useForm.ts

@@ -0,0 +1,48 @@
+import { reactive } from 'vue'
+import { object, setLocale, AnySchema, ValidationError } from 'yup'
+import { debounce } from 'lodash-es'
+
+type FormSchemaShap<T> = {
+  [K in keyof T]?: AnySchema
+}
+
+interface FormOptions<T> {
+  initialValues?: Partial<T>
+  valuesRef?: T
+  schema: FormSchemaShap<T>
+}
+
+export default function useForm<Shap extends object>(
+  options: FormOptions<Shap>
+) {
+  const values: Partial<Shap> =
+    options.valuesRef || reactive<Partial<Shap>>(options.initialValues || {})
+
+  const handleSubmit = debounce(
+    function (
+      onSubmit: (values: Partial<Shap>) => any,
+      onInvalidSubmit?: (error: ValidationError) => any
+    ) {
+      object(options.schema as any)
+        .validate(values)
+        .then(() => onSubmit(values))
+        .catch((err: ValidationError) => {
+          console.error(err.message)
+          onInvalidSubmit?.(err)
+        })
+    },
+    500,
+    { leading: true, trailing: false }
+  )
+
+  return { values, handleSubmit }
+}
+
+setLocale({
+  mixed: {
+    required: '${path} is required',
+  },
+  string: {
+    email: 'invalid ${path}',
+  },
+})

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

@@ -9,21 +9,28 @@
         <div class="title">Sign in to PTC Care Plus</div>
         <div class="ptc-form-item">
           <input
-            v-model="loginData.email"
+            v-model="values.email"
             class="ptc-input"
             placeholder="email address"
           />
         </div>
         <div class="ptc-form-item">
           <input
-            v-model="loginData.password"
+            v-model="values.password"
             class="ptc-input"
             type="password"
             placeholder="password"
           />
         </div>
+        <div class="ptc-form-item" style="font-size: 0">
+          <router-link class="tip primary" to="/password/reset"
+            >Forgot password?</router-link
+          >
+        </div>
         <div class="ptc-form-item">
-          <button class="ptc-button" @click="handleLogin">SIGN IN</button>
+          <button class="ptc-button" @click="handleSubmit(handleLogin)">
+            SIGN IN
+          </button>
         </div>
         <div class="ptc-form-item">
           <p class="tip">
@@ -78,18 +85,22 @@ import { ref, reactive } from 'vue'
 import { useRouter } 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'
 
 const router = useRouter()
 const showSurprise = ref(false)
-const loginData = reactive<ApiUser.Login.Request>({
-  email: '',
-  password: '',
+const { values, handleSubmit } = useForm<ApiUser.Login.Request>({
+  schema: {
+    email: string().required().email(),
+    password: string().required(),
+  },
 })
 
 async function handleLogin() {
-  // await login(loginData)
-  // router.push('/')
-  router.push('/password/change')
+  await login(values as any)
+  router.push('/')
+  // router.push('/password/change')
 }
 </script>
 

+ 55 - 9
src/pages/password/index.vue

@@ -5,6 +5,7 @@
         <h3 class="title">Change Password</h3>
         <div class="ptc-form-item">
           <input
+            v-model="values.new_password"
             class="ptc-input"
             type="password"
             placeholder="Enter new password"
@@ -12,13 +13,16 @@
         </div>
         <div class="ptc-form-item">
           <input
+            v-model="values.new_password_confirmation"
             class="ptc-input"
             type="password"
             placeholder="Repeat new password"
           />
         </div>
         <div class="ptc-form-item">
-          <button class="ptc-button">SUBMIT</button>
+          <button class="ptc-button" @click="handleSubmit(applyChange)">
+            SUBMIT
+          </button>
         </div>
       </div>
     </div>
@@ -52,11 +56,11 @@
         We will send you an email( {{ resetForm.email }} )The password reset
         email has been sent. Please go to check it
       </div>
-      <div class="ptc-form">
+      <!-- <div class="ptc-form">
         <div class="ptc-form-item">
           <button class="ptc-button" @click="next">GO</button>
         </div>
-      </div>
+      </div> -->
     </div>
     <div v-if="step === 2" class="step ptc-inner">
       <h3 class="title">Recover password</h3>
@@ -68,12 +72,13 @@
       <div class="ptc-form">
         <div class="ptc-form-item">
           <input
+            v-model="resetForm.password"
             class="ptc-input"
             placeholder="6-20 digits password, case sensitive"
           />
         </div>
         <div class="ptc-form-item">
-          <button class="ptc-button" @click="next">SUBMIT</button>
+          <button class="ptc-button" @click="applyReset">SUBMIT</button>
         </div>
       </div>
     </div>
@@ -91,14 +96,32 @@
 import { reactive } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { state } from '@/store'
-import { sendPasswordEmail, resetPassword } from '@/service/user'
+import {
+  sendPasswordEmail,
+  resetPassword,
+  changePassword,
+} from '@/service/user'
+import { string } from 'yup'
+import { debounce } from 'lodash-es'
+import useForm from '@/hooks/useForm'
 
 const props = defineProps<{ action: 'change' | 'reset' }>()
 
 const router = useRouter()
-const { query } = useRoute()
+const { query } = useRoute() as any
 const step = query.step ? +(query.step as string) : 0
-const resetForm = reactive({ email: '' })
+const { values, handleSubmit } = useForm<ApiUser.PasswordChange.Request>({
+  schema: {
+    new_password: string().required(),
+    new_password_confirmation: string().required(),
+  },
+})
+const resetForm = reactive<ApiUser.PasswordReset.Request>({
+  email: step === 2 ? query.email : '',
+  token: query.token || '',
+  password: '',
+  password_confirmation: '',
+})
 
 state.bgWhite = true
 
@@ -109,9 +132,32 @@ function next() {
   })
 }
 
+async function applyChange() {
+  // await changePassword(values as any)
+}
+
 async function sendEmail() {
-  await sendPasswordEmail(resetForm.email)
-  next()
+  try {
+    await string()
+      .email('invalid email')
+      .required('email is required')
+      .validate(resetForm.email)
+    await sendPasswordEmail(resetForm.email)
+    next()
+  } catch (err) {
+    console.error(err.message)
+  }
+}
+
+async function applyReset() {
+  try {
+    await string().required('password is required').validate(resetForm.password)
+    resetForm.password_confirmation = resetForm.password
+    await resetPassword(resetForm)
+    next()
+  } catch (err) {
+    console.error(err.message)
+  }
 }
 </script>
 

+ 33 - 3
src/pages/register/index.vue

@@ -8,13 +8,24 @@
       <div class="ptc-form">
         <div class="title">Welcome to PTC Care Plus</div>
         <div class="ptc-form-item">
-          <input class="ptc-input" placeholder="email address" />
+          <input
+            v-model="values.email"
+            class="ptc-input"
+            placeholder="email address"
+          />
         </div>
         <div class="ptc-form-item">
-          <input class="ptc-input" type="password" placeholder="password" />
+          <input
+            v-model="values.password"
+            class="ptc-input"
+            type="password"
+            placeholder="password"
+          />
         </div>
         <div class="ptc-form-item">
-          <button class="ptc-button">CONTINUE</button>
+          <button class="ptc-button" @click="handleSubmit(onSubmit)">
+            CONTINUE
+          </button>
         </div>
         <div class="ptc-form-item">
           <p class="tip">
@@ -31,3 +42,22 @@
     </div>
   </div>
 </template>
+
+<script setup lang="ts">
+import { string } from 'yup'
+import useForm from '@/hooks/useForm'
+import { register } from '@/service/user'
+
+const { values, handleSubmit } = useForm<ApiUser.Register.Request>({
+  schema: {
+    email: string().email().required(),
+    password: string().required(),
+  },
+})
+
+async function onSubmit() {
+  values.name = values.email
+  values.password_confirmation = values.password
+  await register(values as any)
+}
+</script>

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

@@ -17,6 +17,7 @@ declare namespace ApiUser {
 
   namespace PasswordReset {
     interface Request {
+      token: string
       email: string
       password: string
       password_confirmation: string

+ 48 - 1
yarn.lock

@@ -28,6 +28,13 @@
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.7.tgz#d372dda9c89fcec340a82630a9f533f2fe15877e"
   integrity sha512-sR4eaSrnM7BV7QPzGfEX5paG/6wrZM3I0HDzfIAK06ESvo9oy3xBuVBxE3MbQaKNhvg8g/ixjMWo2CGpzpHsDA==
 
+"@babel/runtime@^7.15.4":
+  version "7.17.2"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941"
+  integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==
+  dependencies:
+    regenerator-runtime "^0.13.4"
+
 "@babel/standalone@^7.16.4":
   version "7.17.1"
   resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.17.1.tgz#fe62491d0ec6f442cba3895e6ec3fa6c61de129c"
@@ -99,6 +106,18 @@
   resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
   integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
 
+"@types/lodash-es@^4.17.6":
+  version "4.17.6"
+  resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.6.tgz#c2ed4c8320ffa6f11b43eb89e9eaeec65966a0a0"
+  integrity sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==
+  dependencies:
+    "@types/lodash" "*"
+
+"@types/lodash@*", "@types/lodash@^4.14.175":
+  version "4.14.179"
+  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.179.tgz#490ec3288088c91295780237d2497a3aa9dfb5c5"
+  integrity sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w==
+
 "@types/node@^17.0.8":
   version "17.0.8"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.8.tgz#50d680c8a8a78fe30abe6906453b21ad8ab0ad7b"
@@ -1321,6 +1340,11 @@ ms@2.1.2:
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
   integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
 
+nanoclone@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4"
+  integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==
+
 nanoid@^3.1.30:
   version "3.1.30"
   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362"
@@ -1453,6 +1477,11 @@ promise@^7.0.1:
   dependencies:
     asap "~2.0.3"
 
+property-expr@^2.0.4:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.5.tgz#278bdb15308ae16af3e3b9640024524f4dc02cb4"
+  integrity sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==
+
 pug-attrs@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/pug-attrs/-/pug-attrs-3.0.0.tgz#b10451e0348165e31fad1cc23ebddd9dc7347c41"
@@ -1568,7 +1597,7 @@ readdirp@~3.6.0:
   dependencies:
     picomatch "^2.2.1"
 
-regenerator-runtime@^0.13.9:
+regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.9:
   version "0.13.9"
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
   integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
@@ -1769,6 +1798,11 @@ token-stream@1.0.0:
   resolved "https://registry.yarnpkg.com/token-stream/-/token-stream-1.0.0.tgz#cc200eab2613f4166d27ff9afc7ca56d49df6eb4"
   integrity sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=
 
+toposort@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
+  integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=
+
 tslib@^1.8.1:
   version "1.14.1"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
@@ -2031,3 +2065,16 @@ yallist@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
   integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+
+yup@^0.32.11:
+  version "0.32.11"
+  resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5"
+  integrity sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==
+  dependencies:
+    "@babel/runtime" "^7.15.4"
+    "@types/lodash" "^4.14.175"
+    lodash "^4.17.21"
+    lodash-es "^4.17.21"
+    nanoclone "^0.2.1"
+    property-expr "^2.0.4"
+    toposort "^2.0.2"