Эх сурвалжийг харах

新增 登录界面新增手机号登录
新增 短信验证码组件发送短信功能
更新 admin接口

zhontai 2 жил өмнө
parent
commit
f22a7519dd

+ 20 - 0
src/api/admin/Auth.ts

@@ -11,6 +11,7 @@
 
 import {
   AuthLoginInput,
+  AuthMobileLoginInput,
   ResultOutputAuthGetPasswordEncryptKeyOutput,
   ResultOutputAuthGetUserInfoOutput,
   ResultOutputAuthGetUserPermissionsOutput,
@@ -127,6 +128,25 @@ export class AuthApi<SecurityDataType = unknown> extends HttpClient<SecurityData
       ...params,
     })
   /**
+   * No description
+   *
+   * @tags auth
+   * @name MobileLogin
+   * @summary 手机号登录
+   * @request POST:/api/admin/auth/mobile-login
+   * @secure
+   */
+  mobileLogin = (data: AuthMobileLoginInput, params: RequestParams = {}) =>
+    this.request<ResultOutputObject, any>({
+      path: `/api/admin/auth/mobile-login`,
+      method: 'POST',
+      body: data,
+      secure: true,
+      type: ContentType.Json,
+      format: 'json',
+      ...params,
+    })
+  /**
  * No description
  *
  * @tags auth

+ 20 - 1
src/api/admin/Captcha.ts

@@ -9,7 +9,7 @@
  * ---------------------------------------------------------------
  */
 
-import { ResultOutputCaptchaData, ResultOutputValidateResult, SlideTrack } from './data-contracts'
+import { ResultOutputCaptchaData, ResultOutputString, ResultOutputValidateResult, SendSmsCodeInput, SlideTrack } from './data-contracts'
 import { ContentType, HttpClient, RequestParams } from './http-client'
 
 export class CaptchaApi<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
@@ -64,4 +64,23 @@ export class CaptchaApi<SecurityDataType = unknown> extends HttpClient<SecurityD
       format: 'json',
       ...params,
     })
+  /**
+   * No description
+   *
+   * @tags captcha
+   * @name SendSmsCode
+   * @summary 发送短信验证码
+   * @request POST:/api/admin/captcha/send-sms-code
+   * @secure
+   */
+  sendSmsCode = (data: SendSmsCodeInput, params: RequestParams = {}) =>
+    this.request<ResultOutputString, any>({
+      path: `/api/admin/captcha/send-sms-code`,
+      method: 'POST',
+      body: data,
+      secure: true,
+      type: ContentType.Json,
+      format: 'json',
+      ...params,
+    })
 }

+ 36 - 0
src/api/admin/data-contracts.ts

@@ -250,6 +250,25 @@ export interface AuthLoginInput {
   captchaData?: string | null
 }
 
+/** 手机号登录信息 */
+export interface AuthMobileLoginInput {
+  /**
+   * 手机号
+   * @minLength 1
+   */
+  mobile: string
+  /**
+   * 验证码
+   * @minLength 1
+   */
+  code: string
+  /**
+   * 验证码Id
+   * @minLength 1
+   */
+  codeId: string
+}
+
 export interface AuthUserMenuDto {
   /**
    * 权限Id
@@ -2907,6 +2926,23 @@ export interface RoleUpdateInput {
   id: number
 }
 
+/** 发送短信验证码 */
+export interface SendSmsCodeInput {
+  /**
+   * 手机号
+   * @minLength 1
+   */
+  mobile: string
+  /** 验证码Id */
+  codeId?: string | null
+  /**
+   * 验证码Id
+   * @minLength 1
+   */
+  captchaId: string
+  track: SlideTrack
+}
+
 /**
  * 性别:Unknown(未知)=0,Male(男)=1,Female(女)=2
  * @format int32

+ 2 - 3
src/components/my-captcha/index.vue

@@ -15,11 +15,10 @@
 import { defineAsyncComponent, ref, reactive } from 'vue'
 import { CaptchaApi } from '/@/api/admin/Captcha'
 
-const emits = defineEmits(['ok'])
-
 const SlideCaptcha = defineAsyncComponent(() => import('./slide-captcha.vue'))
 
 const slideCaptchaRef = ref()
+const emits = defineEmits(['ok'])
 
 const state = reactive({
   requestId: '',
@@ -53,7 +52,7 @@ const onFinish = async (data: any) => {
     slideCaptchaRef.value.endRequestVerify(success)
     if (success) {
       //验证成功
-      emits('ok', { captchaId: state.requestId, captchaData: data })
+      emits('ok', { captchaId: state.requestId, track: data })
     } else {
       setTimeout(() => {
         onGenerate()

+ 42 - 12
src/components/my-input-code/index.vue

@@ -1,17 +1,18 @@
 <template>
   <div class="w100">
-    <el-input text maxlength="4" placeholder="请输入验证码" autocomplete="off" v-bind="$attrs">
+    <el-input text :maxlength="props.maxlength" placeholder="请输入验证码" autocomplete="off" v-bind="$attrs">
       <template #prefix>
         <el-icon class="el-input__icon"><ele-Message /></el-icon>
       </template>
       <template #suffix>
-        <el-link
-          type="primary"
-          :underline="false"
+        <el-button
           v-show="state.status !== 'countdown'"
+          :loading="state.loading.getCode"
+          type="primary"
+          link
           :disabled="state.status === 'countdown'"
           @click.prevent.stop="onGetCode"
-          >{{ text }}</el-link
+          >{{ text }}</el-button
         >
         <el-countdown
           v-show="state.status === 'countdown'"
@@ -30,13 +31,20 @@
 import { reactive, defineAsyncComponent, ref, computed } from 'vue'
 import { isMobile } from '/@/utils/test'
 import { ElMessage } from 'element-plus'
+import { CaptchaApi } from '/@/api/admin/Captcha'
 
 const MyCaptchaDialog = defineAsyncComponent(() => import('/@/components/my-captcha/dialog.vue'))
 
+const emits = defineEmits(['send'])
+
 const props = defineProps({
+  maxlength: {
+    type: Number,
+    default: 6,
+  },
   seconds: {
     type: Number,
-    default: 10,
+    default: 60,
   },
   startText: {
     type: String,
@@ -44,11 +52,11 @@ const props = defineProps({
   },
   changeText: {
     type: String,
-    default: 's秒重新获取',
+    default: 's秒后重发',
   },
   endText: {
     type: String,
-    default: '重新获取',
+    default: '重新发送验证码',
   },
   mobile: {
     type: String,
@@ -71,9 +79,13 @@ const state = reactive({
   countdown: countdown,
 
   showDialog: false,
-  requestId: '',
+  codeId: '',
+  loading: {
+    getCode: false,
+  },
 })
 
+//获取验证码文本
 const text = computed(() => {
   return state.status === 'ready' ? state.startText : state.endText
 })
@@ -100,11 +112,29 @@ const onChange = (value: number) => {
   if (state.countdown != countdown && value < 1000) state.status = 'finish'
 }
 
-//验证通过 data: any
-const onOk = () => {
+//验证通过
+const onOk = async (data: any) => {
   state.showDialog = false
-  startCountdown()
+
   //发送短信验证码
+  state.loading.getCode = true
+  const res = await new CaptchaApi()
+    .sendSmsCode({
+      mobile: props.mobile,
+      captchaId: data.captchaId,
+      track: data.track,
+      codeId: state.codeId,
+    })
+    .catch(() => {})
+    .finally(() => {
+      state.loading.getCode = false
+    })
+
+  if (res?.success && res.data) {
+    state.codeId = res.data
+    emits('send', res.data)
+    startCountdown()
+  }
 }
 
 //获得验证码

+ 1 - 1
src/views/admin/login/component/account.vue

@@ -127,7 +127,7 @@ const onOk = (data: any) => {
   state.showDialog = false
   //开始登录
   state.ruleForm.captchaId = data.captchaId
-  state.ruleForm.captchaData = JSON.stringify(data.captchaData)
+  state.ruleForm.captchaData = JSON.stringify(data.track)
   login()
 }
 

+ 74 - 5
src/views/admin/login/component/mobile.vue

@@ -25,32 +25,47 @@
         </el-input>
       </el-form-item>
       <el-form-item class="login-animation2" prop="code" :rules="[{ required: true, message: '请输入短信验证码', trigger: ['blur', 'change'] }]">
-        <MyInputCode v-model="state.ruleForm.code" @keyup.enter="onSignIn" :mobile="state.ruleForm.mobile" :validate="validate" />
+        <MyInputCode v-model="state.ruleForm.code" @keyup.enter="onSignIn" :mobile="state.ruleForm.mobile" :validate="validate" @send="onSend" />
       </el-form-item>
       <el-form-item class="login-animation3">
         <el-button round type="primary" v-waves class="login-content-submit" @click="onSignIn" :loading="state.loading.signIn">
           <span>{{ $t('message.mobile.btnText') }}</span>
         </el-button>
       </el-form-item>
-      <div class="font12 mt30 login-animation4 login-msg">{{ $t('message.mobile.msgText') }}</div>
+      <!-- <div class="font12 mt30 login-animation4 login-msg">{{ $t('message.mobile.msgText') }}</div> -->
     </el-form>
   </div>
 </template>
 
 <script lang="ts" setup name="loginMobile">
-import { reactive, defineAsyncComponent, ref } from 'vue'
+import { reactive, defineAsyncComponent, ref, computed } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { ElMessage } from 'element-plus'
 import { testMobile } from '/@/utils/test'
+import { AuthApi } from '/@/api/admin/Auth'
+import { AuthMobileLoginInput } from '/@/api/admin/data-contracts'
+import { useUserInfo } from '/@/stores/userInfo'
+import { initBackEndControlRoutes } from '/@/router/backEnd'
+import { Session } from '/@/utils/storage'
+import { NextLoading } from '/@/utils/loading'
+import { useI18n } from 'vue-i18n'
+import { formatAxis } from '/@/utils/formatTime'
 
 const MyInputCode = defineAsyncComponent(() => import('/@/components/my-input-code/index.vue'))
 
+const { t } = useI18n()
+const route = useRoute()
+const router = useRouter()
+
 const formRef = ref()
 const phoneRef = ref()
 // 定义变量内容
 const state = reactive({
   ruleForm: {
-    mobile: '',
+    mobile: '13122223333',
     code: '',
-  },
+    codeId: '',
+  } as AuthMobileLoginInput,
   loading: {
     signIn: false,
   },
@@ -67,12 +82,66 @@ const validate = (callback: Function) => {
   })
 }
 
+// 时间获取
+const currentTime = computed(() => {
+  return formatAxis(new Date())
+})
+
+//发送验证码
+const onSend = (codeId: string) => {
+  state.ruleForm.codeId = codeId
+}
+
 // 登录
 const onSignIn = async () => {
   formRef.value.validate(async (valid: boolean) => {
     if (!valid) return
+
+    state.loading.signIn = true
+    const res = await new AuthApi().mobileLogin(state.ruleForm).catch(() => {
+      state.loading.signIn = false
+    })
+    if (!res?.success) {
+      state.loading.signIn = false
+      return
+    }
+
+    const token = res.data?.token
+    useUserInfo().setToken(token)
+    // 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
+    const isNoPower = await initBackEndControlRoutes()
+    // 执行完 initBackEndControlRoutes,再执行 signInSuccess
+    signInSuccess(isNoPower)
   })
 }
+
+// 登录成功后的跳转
+const signInSuccess = (isNoPower: boolean | undefined) => {
+  if (isNoPower) {
+    ElMessage.warning('抱歉,您没有分配权限,请联系管理员')
+    useUserInfo().removeToken()
+    Session.clear()
+  } else {
+    // 初始化登录成功时间问候语
+    let currentTimeInfo = currentTime.value
+    // 登录成功,跳到转首页
+    // 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
+    if (route.query?.redirect) {
+      router.push({
+        path: <string>route.query?.redirect,
+        query: Object.keys(<string>route.query?.params).length > 0 ? JSON.parse(<string>route.query?.params) : '',
+      })
+    } else {
+      router.push('/')
+    }
+    // 登录成功提示
+    const signInText = t('message.signInText')
+    ElMessage.success(`${currentTimeInfo},${signInText}`)
+    // 添加 loading,防止第一次进入界面时出现短暂空白
+    NextLoading.start()
+  }
+  state.loading.signIn = false
+}
 </script>
 
 <style scoped lang="scss">