From c36302bf50c5a98b60fbeceeb9ba690762e1dcd7 Mon Sep 17 00:00:00 2001 From: pan <13329870472@163.com> Date: Tue, 12 Mar 2024 16:41:03 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yaml | 32 +++ locales/zh-CN.yaml | 32 +++ package.json | 2 + pnpm-lock.yaml | 111 +++++++-- src/components/ReImageVerify/index.ts | 7 + src/components/ReImageVerify/src/hooks.ts | 85 +++++++ src/components/ReImageVerify/src/index.vue | 46 ++++ src/components/ReQrcode/index.ts | 7 + src/components/ReQrcode/src/index.scss | 9 + src/components/ReQrcode/src/index.tsx | 261 +++++++++++++++++++++ src/components/ReTypeit/index.ts | 8 + src/components/ReTypeit/src/index.tsx | 56 +++++ src/store/modules/types.ts | 2 + src/store/modules/user.ts | 12 + src/views/login/components/phone.vue | 105 +++++++++ src/views/login/components/qrCode.vue | 25 ++ src/views/login/components/regist.vue | 192 +++++++++++++++ src/views/login/components/update.vue | 153 ++++++++++++ src/views/login/index.vue | 209 ++++++++++++++--- src/views/login/utils/enums.ts | 34 +++ src/views/login/utils/rule.ts | 101 +++++++- src/views/login/utils/verifyCode.ts | 50 ++++ 22 files changed, 1486 insertions(+), 53 deletions(-) create mode 100644 src/components/ReImageVerify/index.ts create mode 100644 src/components/ReImageVerify/src/hooks.ts create mode 100644 src/components/ReImageVerify/src/index.vue create mode 100644 src/components/ReQrcode/index.ts create mode 100644 src/components/ReQrcode/src/index.scss create mode 100644 src/components/ReQrcode/src/index.tsx create mode 100644 src/components/ReTypeit/index.ts create mode 100644 src/components/ReTypeit/src/index.tsx create mode 100644 src/views/login/components/phone.vue create mode 100644 src/views/login/components/qrCode.vue create mode 100644 src/views/login/components/regist.vue create mode 100644 src/views/login/components/update.vue create mode 100644 src/views/login/utils/enums.ts create mode 100644 src/views/login/utils/verifyCode.ts diff --git a/locales/en.yaml b/locales/en.yaml index f5e6941..73586de 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -37,9 +37,41 @@ status: login: username: Username password: Password + verifyCode: VerifyCode remember: days no need to login rememberInfo: After checking and logging in, will automatically log in to the system without entering your username and password within the specified number of days. + sure: Sure Password + forget: Forget Password? login: Login + thirdLogin: Third Login + phoneLogin: Phone Login + qRCodeLogin: QRCode Login + register: Register + weChatLogin: WeChat Login + alipayLogin: Alipay Login + qqLogin: QQ Login + weiboLogin: Weibo Login + phone: Phone + smsVerifyCode: SMS VerifyCode + back: Back + test: Mock Test + tip: After scanning the code, click "Confirm" to complete the login + definite: Definite + loginSuccess: Login Success + registerSuccess: Regist Success + tickPrivacy: Please tick Privacy Policy + readAccept: I have read it carefully and accept + privacyPolicy: Privacy Policy + getVerifyCode: Get VerifyCode + info: Seconds usernameReg: Please enter username passwordReg: Please enter password + verifyCodeReg: Please enter verify code + verifyCodeCorrectReg: Please enter correct verify code + verifyCodeSixReg: Please enter a 6-digit verify code + phoneReg: Please enter the phone + phoneCorrectReg: Please enter the correct phone number format passwordRuleReg: The password format should be any combination of 8-18 digits + passwordSureReg: Please enter confirm password + passwordDifferentReg: The two passwords do not match! + passwordUpdateReg: Password has been updated diff --git a/locales/zh-CN.yaml b/locales/zh-CN.yaml index 7b45448..0491c68 100644 --- a/locales/zh-CN.yaml +++ b/locales/zh-CN.yaml @@ -37,9 +37,41 @@ status: login: username: 账号 password: 密码 + verifyCode: 验证码 remember: 天内免登录 rememberInfo: 勾选并登录后,规定天数内无需输入用户名和密码会自动登入系统 + sure: 确认密码 + forget: 忘记密码? login: 登录 + thirdLogin: 第三方登录 + phoneLogin: 手机登录 + qRCodeLogin: 二维码登录 + register: 注册 + weChatLogin: 微信登录 + alipayLogin: 支付宝登录 + qqLogin: QQ登录 + weiboLogin: 微博登录 + phone: 手机号码 + smsVerifyCode: 短信验证码 + back: 返回 + test: 模拟测试 + tip: 扫码后点击"确认",即可完成登录 + definite: 确定 + loginSuccess: 登录成功 + registerSuccess: 注册成功 + tickPrivacy: 请勾选隐私政策 + readAccept: 我已仔细阅读并接受 + privacyPolicy: 《隐私政策》 + getVerifyCode: 获取验证码 + info: 秒后重新获取 usernameReg: 请输入账号 passwordReg: 请输入密码 + verifyCodeReg: 请输入验证码 + verifyCodeCorrectReg: 请输入正确的验证码 + verifyCodeSixReg: 请输入6位数字验证码 + phoneReg: 请输入手机号码 + phoneCorrectReg: 请输入正确的手机号码格式 passwordRuleReg: 密码格式应为8-18位数字、字母、符号的任意两种组合 + passwordSureReg: 请输入确认密码 + passwordDifferentReg: 两次密码不一致! + passwordUpdateReg: 修改密码成功 diff --git a/package.json b/package.json index 1f82871..5252636 100644 --- a/package.json +++ b/package.json @@ -65,9 +65,11 @@ "path": "^0.12.7", "pinia": "^2.1.7", "pinyin-pro": "^3.19.6", + "qrcode": "^1.5.3", "qs": "^6.11.2", "responsive-storage": "^2.2.0", "sortablejs": "^1.15.2", + "typeit": "8.7.1", "vue": "^3.4.21", "vue-i18n": "^9.10.1", "vue-router": "^4.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 060972c..6c7c32f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ dependencies: pinyin-pro: specifier: ^3.19.6 version: 3.19.6 + qrcode: + specifier: ^1.5.3 + version: 1.5.3 qs: specifier: ^6.11.2 version: 6.11.2 @@ -65,6 +68,9 @@ dependencies: sortablejs: specifier: ^1.15.2 version: 1.15.2 + typeit: + specifier: 8.7.1 + version: 8.7.1 vue: specifier: ^3.4.21 version: 3.4.21(typescript@5.3.3) @@ -1984,7 +1990,6 @@ packages: /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - dev: true /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} @@ -2002,7 +2007,6 @@ packages: engines: {node: '>=8'} dependencies: color-convert: 2.0.1 - dev: true /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} @@ -2204,7 +2208,6 @@ packages: /camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} - dev: true /camelcase@6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} @@ -2298,6 +2301,14 @@ packages: string-width: 7.1.0 dev: true + /cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: false + /cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -2322,14 +2333,12 @@ packages: engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - dev: true /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true /color-string@1.9.1: resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} @@ -2747,7 +2756,6 @@ packages: /decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} - dev: true /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -2788,6 +2796,10 @@ packages: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: true + /dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + dev: false + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -2915,12 +2927,15 @@ packages: /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} dev: true + /encode-utf8@1.0.3: + resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==} + dev: false + /entities@2.2.0: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} dev: true @@ -3277,7 +3292,6 @@ packages: dependencies: locate-path: 5.0.0 path-exists: 4.0.0 - dev: true /find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} @@ -3400,7 +3414,6 @@ packages: /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - dev: true /get-east-asian-width@1.2.0: resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} @@ -3773,7 +3786,6 @@ packages: /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - dev: true /is-fullwidth-code-point@4.0.0: resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} @@ -4043,7 +4055,6 @@ packages: engines: {node: '>=8'} dependencies: p-locate: 4.1.0 - dev: true /locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} @@ -4511,7 +4522,6 @@ packages: engines: {node: '>=6'} dependencies: p-try: 2.2.0 - dev: true /p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} @@ -4525,7 +4535,6 @@ packages: engines: {node: '>=8'} dependencies: p-limit: 2.3.0 - dev: true /p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} @@ -4537,7 +4546,6 @@ packages: /p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - dev: true /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -4563,7 +4571,6 @@ packages: /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - dev: true /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} @@ -4674,6 +4681,11 @@ packages: mlly: 1.6.1 pathe: 1.1.2 + /pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + dev: false + /popmotion@11.0.5: resolution: {integrity: sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==} dependencies: @@ -5419,6 +5431,17 @@ packages: engines: {node: '>=6'} dev: true + /qrcode@1.5.3: + resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==} + engines: {node: '>=10.13.0'} + hasBin: true + dependencies: + dijkstrajs: 1.0.3 + encode-utf8: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + dev: false + /qs@6.11.2: resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==} engines: {node: '>=0.6'} @@ -5495,13 +5518,16 @@ packages: /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - dev: true /require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} dev: true + /require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + dev: false + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -5667,6 +5693,10 @@ packages: dependencies: lru-cache: 6.0.0 + /set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + dev: false + /set-function-length@1.2.1: resolution: {integrity: sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==} engines: {node: '>= 0.4'} @@ -5833,7 +5863,6 @@ packages: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - dev: true /string-width@5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} @@ -5864,7 +5893,6 @@ packages: engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - dev: true /strip-ansi@7.1.0: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} @@ -6363,6 +6391,11 @@ packages: engines: {node: '>=14.16'} dev: true + /typeit@8.7.1: + resolution: {integrity: sha512-Bx/O4NMz10NWh9FWYtVwV4XwGHF9UDJfpCZPJRtw2/oUcahFAStU8J0t19aroPfTV6s1UlS5ICoqilOqmEnh2Q==} + requiresBuild: true + dev: false + /typescript@5.3.3: resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} engines: {node: '>=14.17'} @@ -6685,6 +6718,10 @@ packages: /webpack-virtual-modules@0.6.1: resolution: {integrity: sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==} + /which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + dev: false + /which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true @@ -6706,6 +6743,15 @@ packages: string-width: 5.1.2 dev: true + /wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: false + /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -6750,6 +6796,10 @@ packages: engines: {node: '>=12'} dev: true + /y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + dev: false + /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -6786,6 +6836,14 @@ packages: hasBin: true dev: true + /yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + dev: false + /yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} @@ -6796,6 +6854,23 @@ packages: engines: {node: '>=12'} dev: true + /yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + dev: false + /yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} diff --git a/src/components/ReImageVerify/index.ts b/src/components/ReImageVerify/index.ts new file mode 100644 index 0000000..4215c52 --- /dev/null +++ b/src/components/ReImageVerify/index.ts @@ -0,0 +1,7 @@ +import reImageVerify from "./src/index.vue"; +import { withInstall } from "@pureadmin/utils"; + +/** 图形验证码组件 */ +export const ReImageVerify = withInstall(reImageVerify); + +export default ReImageVerify; diff --git a/src/components/ReImageVerify/src/hooks.ts b/src/components/ReImageVerify/src/hooks.ts new file mode 100644 index 0000000..71fcdfc --- /dev/null +++ b/src/components/ReImageVerify/src/hooks.ts @@ -0,0 +1,85 @@ +import { ref, onMounted } from "vue"; + +/** + * 绘制图形验证码 + * @param width - 图形宽度 + * @param height - 图形高度 + */ +export const useImageVerify = (width = 120, height = 40) => { + const domRef = ref(); + const imgCode = ref(""); + + function setImgCode(code: string) { + imgCode.value = code; + } + + function getImgCode() { + if (!domRef.value) return; + imgCode.value = draw(domRef.value, width, height); + } + + onMounted(() => { + getImgCode(); + }); + + return { + domRef, + imgCode, + setImgCode, + getImgCode + }; +}; + +function randomNum(min: number, max: number) { + const num = Math.floor(Math.random() * (max - min) + min); + return num; +} + +function randomColor(min: number, max: number) { + const r = randomNum(min, max); + const g = randomNum(min, max); + const b = randomNum(min, max); + return `rgb(${r},${g},${b})`; +} + +function draw(dom: HTMLCanvasElement, width: number, height: number) { + let imgCode = ""; + + const NUMBER_STRING = "0123456789"; + + const ctx = dom.getContext("2d"); + if (!ctx) return imgCode; + + ctx.fillStyle = randomColor(180, 230); + ctx.fillRect(0, 0, width, height); + for (let i = 0; i < 4; i += 1) { + const text = NUMBER_STRING[randomNum(0, NUMBER_STRING.length)]; + imgCode += text; + const fontSize = randomNum(18, 41); + const deg = randomNum(-30, 30); + ctx.font = `${fontSize}px Simhei`; + ctx.textBaseline = "top"; + ctx.fillStyle = randomColor(80, 150); + ctx.save(); + ctx.translate(30 * i + 15, 15); + ctx.rotate((deg * Math.PI) / 180); + ctx.fillText(text, -15 + 5, -15); + ctx.restore(); + } + for (let i = 0; i < 5; i += 1) { + ctx.beginPath(); + ctx.moveTo(randomNum(0, width), randomNum(0, height)); + ctx.lineTo(randomNum(0, width), randomNum(0, height)); + ctx.strokeStyle = randomColor(180, 230); + ctx.closePath(); + ctx.stroke(); + } + for (let i = 0; i < 41; i += 1) { + ctx.beginPath(); + ctx.arc(randomNum(0, width), randomNum(0, height), 1, 0, 2 * Math.PI); + ctx.closePath(); + ctx.fillStyle = randomColor(150, 200); + ctx.fill(); + } + return imgCode; +} diff --git a/src/components/ReImageVerify/src/index.vue b/src/components/ReImageVerify/src/index.vue new file mode 100644 index 0000000..03d0662 --- /dev/null +++ b/src/components/ReImageVerify/src/index.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/components/ReQrcode/index.ts b/src/components/ReQrcode/index.ts new file mode 100644 index 0000000..48037a8 --- /dev/null +++ b/src/components/ReQrcode/index.ts @@ -0,0 +1,7 @@ +import reQrcode from "./src/index"; +import { withInstall } from "@pureadmin/utils"; + +/** 二维码组件 */ +export const ReQrcode = withInstall(reQrcode); + +export default ReQrcode; diff --git a/src/components/ReQrcode/src/index.scss b/src/components/ReQrcode/src/index.scss new file mode 100644 index 0000000..66ff634 --- /dev/null +++ b/src/components/ReQrcode/src/index.scss @@ -0,0 +1,9 @@ +.qrcode { + &--disabled { + background: rgb(255 255 255 / 95%); + + & > div { + transform: translate(-50%, -50%); + } + } +} diff --git a/src/components/ReQrcode/src/index.tsx b/src/components/ReQrcode/src/index.tsx new file mode 100644 index 0000000..8d51754 --- /dev/null +++ b/src/components/ReQrcode/src/index.tsx @@ -0,0 +1,261 @@ +import { + ref, + unref, + watch, + nextTick, + computed, + type PropType, + defineComponent +} from "vue"; +import "./index.scss"; +import propTypes from "@/utils/propTypes"; +import { isString, cloneDeep } from "@pureadmin/utils"; +import QRCode, { type QRCodeRenderersOptions } from "qrcode"; +import RefreshRight from "@iconify-icons/ep/refresh-right"; + +interface QrcodeLogo { + src?: string; + logoSize?: number; + bgColor?: string; + borderSize?: number; + crossOrigin?: string; + borderRadius?: number; + logoRadius?: number; +} + +const props = { + // img 或者 canvas,img不支持logo嵌套 + tag: propTypes.string + .validate((v: string) => ["canvas", "img"].includes(v)) + .def("canvas"), + // 二维码内容 + text: { + type: [String, Array] as PropType, + default: null + }, + // qrcode.js配置项 + options: { + type: Object as PropType, + default: (): QRCodeRenderersOptions => ({}) + }, + // 宽度 + width: propTypes.number.def(200), + // logo + logo: { + type: [String, Object] as PropType | string>, + default: (): QrcodeLogo | string => "" + }, + // 是否过期 + disabled: propTypes.bool.def(false), + // 过期提示内容 + disabledText: propTypes.string.def("") +}; + +export default defineComponent({ + name: "ReQrcode", + props, + emits: ["done", "click", "disabled-click"], + setup(props, { emit }) { + const { toCanvas, toDataURL } = QRCode; + const loading = ref(true); + const wrapRef = ref>(null); + const renderText = computed(() => String(props.text)); + const wrapStyle = computed(() => { + return { + width: props.width + "px", + height: props.width + "px" + }; + }); + const initQrcode = async () => { + await nextTick(); + const options = cloneDeep(props.options || {}); + if (props.tag === "canvas") { + // 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率 + options.errorCorrectionLevel = + options.errorCorrectionLevel || + getErrorCorrectionLevel(unref(renderText)); + const _width: number = await getOriginWidth(unref(renderText), options); + options.scale = + props.width === 0 ? undefined : (props.width / _width) * 4; + const canvasRef: any = await toCanvas( + unref(wrapRef) as HTMLCanvasElement, + unref(renderText), + options + ); + if (props.logo) { + const url = await createLogoCode(canvasRef); + emit("done", url); + loading.value = false; + } else { + emit("done", canvasRef.toDataURL()); + loading.value = false; + } + } else { + const url = await toDataURL(renderText.value, { + errorCorrectionLevel: "H", + width: props.width, + ...options + }); + (unref(wrapRef) as any).src = url; + emit("done", url); + loading.value = false; + } + }; + watch( + () => renderText.value, + val => { + if (!val) return; + initQrcode(); + }, + { + deep: true, + immediate: true + } + ); + const createLogoCode = (canvasRef: HTMLCanvasElement) => { + const canvasWidth = canvasRef.width; + const logoOptions: QrcodeLogo = Object.assign( + { + logoSize: 0.15, + bgColor: "#ffffff", + borderSize: 0.05, + crossOrigin: "anonymous", + borderRadius: 8, + logoRadius: 0 + }, + isString(props.logo) ? {} : props.logo + ); + const { + logoSize = 0.15, + bgColor = "#ffffff", + borderSize = 0.05, + crossOrigin = "anonymous", + borderRadius = 8, + logoRadius = 0 + } = logoOptions; + const logoSrc = isString(props.logo) ? props.logo : props.logo.src; + const logoWidth = canvasWidth * logoSize; + const logoXY = (canvasWidth * (1 - logoSize)) / 2; + const logoBgWidth = canvasWidth * (logoSize + borderSize); + const logoBgXY = (canvasWidth * (1 - logoSize - borderSize)) / 2; + const ctx = canvasRef.getContext("2d"); + if (!ctx) return; + // logo 底色 + canvasRoundRect(ctx)( + logoBgXY, + logoBgXY, + logoBgWidth, + logoBgWidth, + borderRadius + ); + ctx.fillStyle = bgColor; + ctx.fill(); + // logo + const image = new Image(); + if (crossOrigin || logoRadius) { + image.setAttribute("crossOrigin", crossOrigin); + } + (image as any).src = logoSrc; + // 使用image绘制可以避免某些跨域情况 + const drawLogoWithImage = (image: HTMLImageElement) => { + ctx.drawImage(image, logoXY, logoXY, logoWidth, logoWidth); + }; + // 使用canvas绘制以获得更多的功能 + const drawLogoWithCanvas = (image: HTMLImageElement) => { + const canvasImage = document.createElement("canvas"); + canvasImage.width = logoXY + logoWidth; + canvasImage.height = logoXY + logoWidth; + const imageCanvas = canvasImage.getContext("2d"); + if (!imageCanvas || !ctx) return; + imageCanvas.drawImage(image, logoXY, logoXY, logoWidth, logoWidth); + canvasRoundRect(ctx)(logoXY, logoXY, logoWidth, logoWidth, logoRadius); + if (!ctx) return; + const fillStyle = ctx.createPattern(canvasImage, "no-repeat"); + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + // 将 logo绘制到 canvas上 + return new Promise((resolve: any) => { + image.onload = () => { + logoRadius ? drawLogoWithCanvas(image) : drawLogoWithImage(image); + resolve(canvasRef.toDataURL()); + }; + }); + }; + // 得到原QrCode的大小,以便缩放得到正确的QrCode大小 + const getOriginWidth = async ( + content: string, + options: QRCodeRenderersOptions + ) => { + const _canvas = document.createElement("canvas"); + await toCanvas(_canvas, content, options); + return _canvas.width; + }; + // 对于内容少的QrCode,增大容错率 + const getErrorCorrectionLevel = (content: string) => { + if (content.length > 36) { + return "M"; + } else if (content.length > 16) { + return "Q"; + } else { + return "H"; + } + }; + // 用于绘制圆角 + const canvasRoundRect = (ctx: CanvasRenderingContext2D) => { + return (x: number, y: number, w: number, h: number, r: number) => { + const minSize = Math.min(w, h); + if (r > minSize / 2) { + r = minSize / 2; + } + ctx.beginPath(); + ctx.moveTo(x + r, y); + ctx.arcTo(x + w, y, x + w, y + h, r); + ctx.arcTo(x + w, y + h, x, y + h, r); + ctx.arcTo(x, y + h, x, y, r); + ctx.arcTo(x, y, x + w, y, r); + ctx.closePath(); + return ctx; + }; + }; + const clickCode = () => { + emit("click"); + }; + const disabledClick = () => { + emit("disabled-click"); + }; + return () => ( + <> +
+ {props.tag === "canvas" ? ( + + ) : ( + + )} + {props.disabled && ( +
+
+ +
{props.disabledText}
+
+
+ )} +
+ + ); + } +}); diff --git a/src/components/ReTypeit/index.ts b/src/components/ReTypeit/index.ts new file mode 100644 index 0000000..dd6f2ca --- /dev/null +++ b/src/components/ReTypeit/index.ts @@ -0,0 +1,8 @@ +import typeIt from "./src/index"; +import type { TypeItOptions } from "typeit"; + +const TypeIt = typeIt; + +export { TypeIt, TypeItOptions }; + +export default TypeIt; diff --git a/src/components/ReTypeit/src/index.tsx b/src/components/ReTypeit/src/index.tsx new file mode 100644 index 0000000..9e61b85 --- /dev/null +++ b/src/components/ReTypeit/src/index.tsx @@ -0,0 +1,56 @@ +import type { El } from "typeit/dist/types"; +import TypeIt, { type TypeItOptions } from "typeit"; +import { ref, defineComponent, onMounted, type PropType } from "vue"; + +// 打字机效果组件(配置项详情请查阅 https://www.typeitjs.com/docs/vanilla/usage#options) +export default defineComponent({ + name: "TypeIt", + props: { + options: { + type: Object as PropType, + default: () => ({}) as TypeItOptions + } + }, + setup(props, { slots, expose }) { + /** + * 输出错误信息 + * @param message 错误信息 + */ + function throwError(message: string) { + throw new TypeError(message); + } + + /** + * 获取浏览器默认语言 + */ + function getBrowserLanguage() { + return navigator.language; + } + + const typedItRef = ref(null); + + onMounted(() => { + const $typed = typedItRef.value!.querySelector(".type-it") as El; + + if (!$typed) { + const errorMsg = + getBrowserLanguage() === "zh-CN" + ? "请确保有且只有一个具有class属性为 'type-it' 的元素" + : "Please make sure that there is only one element with a Class attribute with 'type-it'"; + throwError(errorMsg); + } + + const typeIt = new TypeIt($typed, props.options).go(); + + expose({ + typeIt + }); + }); + + return () => ( +
+ {slots.default?.() ?? } +
+ ); + } +}); diff --git a/src/store/modules/types.ts b/src/store/modules/types.ts index 24e47c1..ebfc91f 100644 --- a/src/store/modules/types.ts +++ b/src/store/modules/types.ts @@ -38,6 +38,8 @@ export type setType = { export type userType = { username?: string; roles?: Array; + verifyCode?: string; + currentPage?: number; isRemembered?: boolean; loginDay?: number; }; diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 00a0f71..f2ecab4 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -16,6 +16,10 @@ export const useUserStore = defineStore({ username: storageLocal().getItem>(userKey)?.username ?? "", // 页面级别权限 roles: storageLocal().getItem>(userKey)?.roles ?? [], + // 前端生成的验证码(按实际需求替换) + verifyCode: "", + // 判断登录页面显示哪个组件(0:登录(默认)、1:手机登录、2:二维码登录、3:注册、4:忘记密码) + currentPage: 0, // 是否勾选了登录页的免登录 isRemembered: false, // 登录页的免登录存储几天,默认7天 @@ -30,6 +34,14 @@ export const useUserStore = defineStore({ SET_ROLES(roles: Array) { this.roles = roles; }, + /** 存储前端生成的验证码 */ + SET_VERIFYCODE(verifyCode: string) { + this.verifyCode = verifyCode; + }, + /** 存储登录页面显示哪个组件 */ + SET_CURRENTPAGE(value: number) { + this.currentPage = value; + }, /** 存储是否勾选了登录页的免登录 */ SET_ISREMEMBERED(bool: boolean) { this.isRemembered = bool; diff --git a/src/views/login/components/phone.vue b/src/views/login/components/phone.vue new file mode 100644 index 0000000..e298145 --- /dev/null +++ b/src/views/login/components/phone.vue @@ -0,0 +1,105 @@ + + + diff --git a/src/views/login/components/qrCode.vue b/src/views/login/components/qrCode.vue new file mode 100644 index 0000000..762a971 --- /dev/null +++ b/src/views/login/components/qrCode.vue @@ -0,0 +1,25 @@ + + + diff --git a/src/views/login/components/regist.vue b/src/views/login/components/regist.vue new file mode 100644 index 0000000..2c35a8b --- /dev/null +++ b/src/views/login/components/regist.vue @@ -0,0 +1,192 @@ + + + diff --git a/src/views/login/components/update.vue b/src/views/login/components/update.vue new file mode 100644 index 0000000..bd7f866 --- /dev/null +++ b/src/views/login/components/update.vue @@ -0,0 +1,153 @@ + + + diff --git a/src/views/login/index.vue b/src/views/login/index.vue index 6c58a09..59b9f31 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -4,15 +4,24 @@ import Motion from "./utils/motion"; import { useRouter } from "vue-router"; import { message } from "@/utils/message"; import { loginRules } from "./utils/rule"; +import phone from "./components/phone.vue"; +import TypeIt from "@/components/ReTypeit"; +import { debounce } from "@pureadmin/utils"; +import qrCode from "./components/qrCode.vue"; +import regist from "./components/regist.vue"; +import update from "./components/update.vue"; import { useNav } from "@/layout/hooks/useNav"; +import { useEventListener } from "@vueuse/core"; import type { FormInstance } from "element-plus"; import { $t, transformI18n } from "@/plugins/i18n"; +import { operates, thirdParty } from "./utils/enums"; import { useLayout } from "@/layout/hooks/useLayout"; import { useUserStoreHook } from "@/store/modules/user"; import { initRouter, getTopMenu } from "@/router/utils"; import { bg, avatar, illustration } from "./utils/static"; +import { ReImageVerify } from "@/components/ReImageVerify"; +import { ref, toRaw, reactive, watch, computed } from "vue"; import { useRenderIcon } from "@/components/ReIcon/src/hooks"; -import { ref, reactive, toRaw, onMounted, onBeforeUnmount } from "vue"; import { useTranslationLang } from "@/layout/hooks/useTranslationLang"; import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange"; @@ -22,64 +31,84 @@ import globalization from "@/assets/svg/globalization.svg?component"; import Lock from "@iconify-icons/ri/lock-fill"; import Check from "@iconify-icons/ep/check"; import User from "@iconify-icons/ri/user-3-fill"; +import Info from "@iconify-icons/ri/information-line"; defineOptions({ name: "Login" }); + +const imgCode = ref(""); +const loginDay = ref(7); const router = useRouter(); const loading = ref(false); +const checked = ref(false); +const disabled = ref(false); const ruleFormRef = ref(); - -const { initStorage } = useLayout(); -initStorage(); +const currentPage = computed(() => { + return useUserStoreHook().currentPage; +}); const { t } = useI18n(); -const { dataTheme, dataThemeChange } = useDataThemeChange(); -dataThemeChange(); +const { initStorage } = useLayout(); +initStorage(); +const { dataTheme, overallStyle, dataThemeChange } = useDataThemeChange(); +dataThemeChange(overallStyle.value); const { title, getDropdownItemStyle, getDropdownItemClass } = useNav(); const { locale, translationCh, translationEn } = useTranslationLang(); const ruleForm = reactive({ username: "admin", - password: "admin123" + password: "admin123", + verifyCode: "" }); const onLogin = async (formEl: FormInstance | undefined) => { - loading.value = true; if (!formEl) return; await formEl.validate((valid, fields) => { if (valid) { + loading.value = true; useUserStoreHook() .loginByUsername({ username: ruleForm.username, password: "admin123" }) .then(res => { if (res.success) { // 获取后端路由 - initRouter().then(() => { - router.push(getTopMenu(true).path); - message("登录成功", { type: "success" }); + return initRouter().then(() => { + disabled.value = true; + router + .push(getTopMenu(true).path) + .then(() => { + message("登录成功", { type: "success" }); + }) + .finally(() => (disabled.value = false)); }); } - }); + }) + .finally(() => (loading.value = false)); } else { - loading.value = false; return fields; } }); }; -/** 使用公共函数,避免`removeEventListener`失效 */ -function onkeypress({ code }: KeyboardEvent) { - if (code === "Enter") { - onLogin(ruleFormRef.value); - } -} +const immediateDebounce: any = debounce( + formRef => onLogin(formRef), + 1000, + true +); -onMounted(() => { - window.document.addEventListener("keypress", onkeypress); +useEventListener(document, "keypress", ({ code }) => { + if (code === "Enter" && !disabled.value && !loading.value) + immediateDebounce(ruleFormRef.value); }); -onBeforeUnmount(() => { - window.document.removeEventListener("keypress", onkeypress); +watch(imgCode, value => { + useUserStoreHook().SET_VERIFYCODE(value); +}); +watch(checked, bool => { + useUserStoreHook().SET_ISREMEMBERED(bool); +}); +watch(loginDay, value => { + useUserStoreHook().SET_LOGINDAY(value); }); @@ -136,10 +165,15 @@ onBeforeUnmount(() => { +
+ Copyright © 2020-present + +  {{ title }} + +
diff --git a/src/views/login/utils/enums.ts b/src/views/login/utils/enums.ts new file mode 100644 index 0000000..b95fa07 --- /dev/null +++ b/src/views/login/utils/enums.ts @@ -0,0 +1,34 @@ +import { $t } from "@/plugins/i18n"; + +const operates = [ + { + title: $t("login.phoneLogin") + }, + { + title: $t("login.qRCodeLogin") + }, + { + title: $t("login.register") + } +]; + +const thirdParty = [ + { + title: $t("login.weChatLogin"), + icon: "wechat" + }, + { + title: $t("login.alipayLogin"), + icon: "alipay" + }, + { + title: $t("login.qqLogin"), + icon: "qq" + }, + { + title: $t("login.weiboLogin"), + icon: "weibo" + } +]; + +export { operates, thirdParty }; diff --git a/src/views/login/utils/rule.ts b/src/views/login/utils/rule.ts index a68a9d4..274ab8a 100644 --- a/src/views/login/utils/rule.ts +++ b/src/views/login/utils/rule.ts @@ -1,13 +1,110 @@ import { reactive } from "vue"; +import { isPhone } from "@pureadmin/utils"; import type { FormRules } from "element-plus"; import { $t, transformI18n } from "@/plugins/i18n"; +import { useUserStoreHook } from "@/store/modules/user"; + +/** 6位数字验证码正则 */ +export const REGEXP_SIX = /^\d{6}$/; /** 密码正则(密码格式应为8-18位数字、字母、符号的任意两种组合) */ export const REGEXP_PWD = /^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/; /** 登录校验 */ -const loginRules = reactive({ +const loginRules = reactive({ + password: [ + { + validator: (rule, value, callback) => { + if (value === "") { + callback(new Error(transformI18n($t("login.passwordReg")))); + } else if (!REGEXP_PWD.test(value)) { + callback(new Error(transformI18n($t("login.passwordRuleReg")))); + } else { + callback(); + } + }, + trigger: "blur" + } + ], + verifyCode: [ + { + validator: (rule, value, callback) => { + if (value === "") { + callback(new Error(transformI18n($t("login.verifyCodeReg")))); + } else if (useUserStoreHook().verifyCode !== value) { + callback(new Error(transformI18n($t("login.verifyCodeCorrectReg")))); + } else { + callback(); + } + }, + trigger: "blur" + } + ] +}); + +/** 手机登录校验 */ +const phoneRules = reactive({ + phone: [ + { + validator: (rule, value, callback) => { + if (value === "") { + callback(new Error(transformI18n($t("login.phoneReg")))); + } else if (!isPhone(value)) { + callback(new Error(transformI18n($t("login.phoneCorrectReg")))); + } else { + callback(); + } + }, + trigger: "blur" + } + ], + verifyCode: [ + { + validator: (rule, value, callback) => { + if (value === "") { + callback(new Error(transformI18n($t("login.verifyCodeReg")))); + } else if (!REGEXP_SIX.test(value)) { + callback(new Error(transformI18n($t("login.verifyCodeSixReg")))); + } else { + callback(); + } + }, + trigger: "blur" + } + ] +}); + +/** 忘记密码校验 */ +const updateRules = reactive({ + phone: [ + { + validator: (rule, value, callback) => { + if (value === "") { + callback(new Error(transformI18n($t("login.phoneReg")))); + } else if (!isPhone(value)) { + callback(new Error(transformI18n($t("login.phoneCorrectReg")))); + } else { + callback(); + } + }, + trigger: "blur" + } + ], + verifyCode: [ + { + validator: (rule, value, callback) => { + if (value === "") { + callback(new Error(transformI18n($t("login.verifyCodeReg")))); + } else if (!REGEXP_SIX.test(value)) { + callback(new Error(transformI18n($t("login.verifyCodeSixReg")))); + } else { + callback(); + } + }, + trigger: "blur" + } + ], password: [ { validator: (rule, value, callback) => { @@ -24,4 +121,4 @@ const loginRules = reactive({ ] }); -export { loginRules }; +export { loginRules, phoneRules, updateRules }; diff --git a/src/views/login/utils/verifyCode.ts b/src/views/login/utils/verifyCode.ts new file mode 100644 index 0000000..b6d89a7 --- /dev/null +++ b/src/views/login/utils/verifyCode.ts @@ -0,0 +1,50 @@ +import type { FormInstance, FormItemProp } from "element-plus"; +import { clone } from "@pureadmin/utils"; +import { ref } from "vue"; + +const isDisabled = ref(false); +const timer = ref(null); +const text = ref(""); + +export const useVerifyCode = () => { + const start = async ( + formEl: FormInstance | undefined, + props: FormItemProp, + time = 60 + ) => { + if (!formEl) return; + const initTime = clone(time, true); + await formEl.validateField(props, isValid => { + if (isValid) { + clearInterval(timer.value); + isDisabled.value = true; + text.value = `${time}`; + timer.value = setInterval(() => { + if (time > 0) { + time -= 1; + text.value = `${time}`; + } else { + text.value = ""; + isDisabled.value = false; + clearInterval(timer.value); + time = initTime; + } + }, 1000); + } + }); + }; + + const end = () => { + text.value = ""; + isDisabled.value = false; + clearInterval(timer.value); + }; + + return { + isDisabled, + timer, + text, + start, + end + }; +};