feat:增加 code-login 手机验证码的接入
							parent
							
								
									5b60c93e4e
								
							
						
					
					
						commit
						fa8e7bdc12
					
				|  | @ -24,13 +24,13 @@ export namespace AuthApi { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** 手机验证码获取接口参数 */ |   /** 手机验证码获取接口参数 */ | ||||||
|   export interface SmsCodeVO { |   export interface SmsCodeParams { | ||||||
|     mobile: string; |     mobile: string; | ||||||
|     scene: number; |     scene: number; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** 手机验证码登录接口参数 */ |   /** 手机验证码登录接口参数 */ | ||||||
|   export interface SmsLoginVO { |   export interface SmsLoginParams { | ||||||
|     mobile: string; |     mobile: string; | ||||||
|     code: string; |     code: string; | ||||||
|   } |   } | ||||||
|  | @ -84,3 +84,13 @@ export async function getCaptcha(data: any) { | ||||||
| export async function checkCaptcha(data: any) { | export async function checkCaptcha(data: any) { | ||||||
|   return baseRequestClient.post('/system/captcha/check', data); |   return baseRequestClient.post('/system/captcha/check', data); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** 获取登录验证码 */ | ||||||
|  | export const sendSmsCode = (data: AuthApi.SmsCodeParams) => { | ||||||
|  |   return requestClient.post('/system/auth/send-sms-code', data ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 短信验证码登录 */ | ||||||
|  | export const smsLogin = (data: AuthApi.SmsLoginParams) => { | ||||||
|  |   return requestClient.post('/system/auth/sms-login', data) | ||||||
|  | } | ||||||
|  | @ -9,7 +9,7 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; | ||||||
| import { notification } from 'ant-design-vue'; | import { notification } from 'ant-design-vue'; | ||||||
| import { defineStore } from 'pinia'; | import { defineStore } from 'pinia'; | ||||||
| 
 | 
 | ||||||
| import { getAuthPermissionInfoApi, loginApi, logoutApi} from '#/api'; | import { type AuthApi, getAuthPermissionInfoApi, loginApi, logoutApi, smsLogin } from '#/api'; | ||||||
| import { $t } from '#/locales'; | import { $t } from '#/locales'; | ||||||
| 
 | 
 | ||||||
| export const useAuthStore = defineStore('auth', () => { | export const useAuthStore = defineStore('auth', () => { | ||||||
|  | @ -22,9 +22,12 @@ export const useAuthStore = defineStore('auth', () => { | ||||||
|   /** |   /** | ||||||
|    * 异步处理登录操作 |    * 异步处理登录操作 | ||||||
|    * Asynchronously handle the login process |    * Asynchronously handle the login process | ||||||
|  |    * @param type 登录类型 | ||||||
|    * @param params 登录表单数据 |    * @param params 登录表单数据 | ||||||
|  |    * @param onSuccess 登录成功后的回调函数 | ||||||
|    */ |    */ | ||||||
|   async function authLogin( |   async function authLogin( | ||||||
|  |     type: 'mobile' | 'username', | ||||||
|     params: Recordable<any>, |     params: Recordable<any>, | ||||||
|     onSuccess?: () => Promise<void> | void, |     onSuccess?: () => Promise<void> | void, | ||||||
|   ) { |   ) { | ||||||
|  | @ -32,7 +35,8 @@ export const useAuthStore = defineStore('auth', () => { | ||||||
|     let userInfo: null | UserInfo = null; |     let userInfo: null | UserInfo = null; | ||||||
|     try { |     try { | ||||||
|       loginLoading.value = true; |       loginLoading.value = true; | ||||||
|       const { accessToken, refreshToken } = await loginApi(params); |       const { accessToken, refreshToken } = type === 'mobile' ? await smsLogin(params as AuthApi.SmsLoginParams) | ||||||
|  |         : await loginApi(params); | ||||||
| 
 | 
 | ||||||
|       // 如果成功获取到 accessToken
 |       // 如果成功获取到 accessToken
 | ||||||
|       if (accessToken) { |       if (accessToken) { | ||||||
|  |  | ||||||
|  | @ -2,24 +2,102 @@ | ||||||
| import type { VbenFormSchema } from '@vben/common-ui'; | import type { VbenFormSchema } from '@vben/common-ui'; | ||||||
| import type { Recordable } from '@vben/types'; | import type { Recordable } from '@vben/types'; | ||||||
| 
 | 
 | ||||||
| import { computed, ref } from 'vue'; | import { computed, ref, onMounted } from 'vue'; | ||||||
| 
 | 
 | ||||||
| import { AuthenticationCodeLogin, z } from '@vben/common-ui'; | import { AuthenticationCodeLogin, z } from '@vben/common-ui'; | ||||||
| import { $t } from '@vben/locales'; | import { $t } from '@vben/locales'; | ||||||
|  | import { type AuthApi, sendSmsCode } from '#/api'; | ||||||
|  | import { useAppConfig } from '@vben/hooks'; | ||||||
|  | import { message } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { getTenantSimpleList, getTenantByWebsite } from '#/api/core/auth'; | ||||||
|  | import { useAccessStore } from '@vben/stores'; | ||||||
|  | import { useAuthStore } from '#/store'; | ||||||
|  | const { tenantEnable } = useAppConfig(import.meta.env, import.meta.env.PROD); | ||||||
| 
 | 
 | ||||||
| defineOptions({ name: 'CodeLogin' }); | defineOptions({ name: 'CodeLogin' }); | ||||||
| 
 | 
 | ||||||
|  | const authStore = useAuthStore(); | ||||||
|  | const accessStore = useAccessStore(); | ||||||
|  | 
 | ||||||
| const loading = ref(false); | const loading = ref(false); | ||||||
| const CODE_LENGTH = 6; | const CODE_LENGTH = 4; | ||||||
|  | 
 | ||||||
|  | const loginRef = ref(); | ||||||
|  | 
 | ||||||
|  | /** 获取租户列表,并默认选中 */ | ||||||
|  | const tenantList = ref<AuthApi.TenantResult[]>([]); // 租户列表 | ||||||
|  | const fetchTenantList = async () => { | ||||||
|  |   if (!tenantEnable) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   try { | ||||||
|  |     // 获取租户列表、域名对应租户 | ||||||
|  |     const websiteTenantPromise = getTenantByWebsite(window.location.hostname); | ||||||
|  |     tenantList.value = await getTenantSimpleList(); | ||||||
|  | 
 | ||||||
|  |     // 选中租户:域名 > store 中的租户 > 首个租户 | ||||||
|  |     let tenantId: number | null = null; | ||||||
|  |     const websiteTenant = await websiteTenantPromise; | ||||||
|  |     if (websiteTenant?.id) { | ||||||
|  |       tenantId = websiteTenant.id; | ||||||
|  |     } | ||||||
|  |     // 如果没有从域名获取到租户,尝试从 store 中获取 | ||||||
|  |     if (!tenantId && accessStore.tenantId) { | ||||||
|  |       tenantId = accessStore.tenantId; | ||||||
|  |     } | ||||||
|  |     // 如果还是没有租户,使用列表中的第一个 | ||||||
|  |     if (!tenantId && tenantList.value?.[0]?.id) { | ||||||
|  |       tenantId = tenantList.value[0].id; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 设置选中的租户编号 | ||||||
|  |     accessStore.setTenantId(tenantId); | ||||||
|  |     loginRef.value.getFormApi().setFieldValue('tenantId', tenantId); | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('获取租户列表失败:', error); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** 组件挂载时获取租户信息 */ | ||||||
|  | onMounted(() => { | ||||||
|  |   fetchTenantList(); | ||||||
|  | }); | ||||||
| 
 | 
 | ||||||
| const formSchema = computed((): VbenFormSchema[] => { | const formSchema = computed((): VbenFormSchema[] => { | ||||||
|   return [ |   return [ | ||||||
|  |     { | ||||||
|  |       component: 'VbenSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         options: tenantList.value.map((item) => ({ | ||||||
|  |           label: item.name, | ||||||
|  |           value: item.id, | ||||||
|  |         })), | ||||||
|  |         placeholder: $t('authentication.tenantTip'), | ||||||
|  |       }, | ||||||
|  |       fieldName: 'tenantId', | ||||||
|  |       label: $t('authentication.tenant'), | ||||||
|  |       rules: z | ||||||
|  |         .number() | ||||||
|  |         .nullable() | ||||||
|  |         .refine((val) => val != null && val > 0, $t('authentication.tenantTip')) | ||||||
|  |         .default(null), | ||||||
|  |       dependencies: { | ||||||
|  |         triggerFields: ['tenantId'], | ||||||
|  |         if: tenantEnable, | ||||||
|  |         trigger(values) { | ||||||
|  |           if (values.tenantId) { | ||||||
|  |             accessStore.setTenantId(values.tenantId); | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|       component: 'VbenInput', |       component: 'VbenInput', | ||||||
|       componentProps: { |       componentProps: { | ||||||
|         placeholder: $t('authentication.mobile'), |         placeholder: $t('authentication.mobile'), | ||||||
|       }, |       }, | ||||||
|       fieldName: 'phoneNumber', |       fieldName: 'mobile', | ||||||
|       label: $t('authentication.mobile'), |       label: $t('authentication.mobile'), | ||||||
|       rules: z |       rules: z | ||||||
|         .string() |         .string() | ||||||
|  | @ -40,6 +118,29 @@ const formSchema = computed((): VbenFormSchema[] => { | ||||||
|           return text; |           return text; | ||||||
|         }, |         }, | ||||||
|         placeholder: $t('authentication.code'), |         placeholder: $t('authentication.code'), | ||||||
|  |         handleSendCode: async () => { | ||||||
|  |           loading.value = true; | ||||||
|  |           try { | ||||||
|  |             const formApi = loginRef.value?.getFormApi(); | ||||||
|  |             if (!formApi) { | ||||||
|  |               throw new Error('表单未准备好'); | ||||||
|  |             } | ||||||
|  |             // 验证手机号 | ||||||
|  |             await formApi.validateField('mobile'); | ||||||
|  |             const isMobileValid = await formApi.isFieldValid('mobile'); | ||||||
|  |             if (!isMobileValid) { | ||||||
|  |               throw new Error('请输入有效的手机号码'); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // 发送验证码 | ||||||
|  |             const { mobile } = await formApi.getValues(); | ||||||
|  |             const scene = 21; // 场景:短信验证码登录 | ||||||
|  |             await sendSmsCode({ mobile, scene }); | ||||||
|  |             message.success('验证码发送成功'); | ||||||
|  |           } finally { | ||||||
|  |             loading.value = false; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|       }, |       }, | ||||||
|       fieldName: 'code', |       fieldName: 'code', | ||||||
|       label: $t('authentication.code'), |       label: $t('authentication.code'), | ||||||
|  | @ -55,13 +156,17 @@ const formSchema = computed((): VbenFormSchema[] => { | ||||||
|  * @param values 登录表单数据 |  * @param values 登录表单数据 | ||||||
|  */ |  */ | ||||||
| async function handleLogin(values: Recordable<any>) { | async function handleLogin(values: Recordable<any>) { | ||||||
|   // eslint-disable-next-line no-console |   try { | ||||||
|   console.log(values); |     await authStore.authLogin('mobile', values); | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('Error in handleLogin:', error); | ||||||
|  |   } | ||||||
| } | } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <AuthenticationCodeLogin |   <AuthenticationCodeLogin | ||||||
|  |     ref="loginRef" | ||||||
|     :form-schema="formSchema" |     :form-schema="formSchema" | ||||||
|     :loading="loading" |     :loading="loading" | ||||||
|     @submit="handleLogin" |     @submit="handleLogin" | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import type { VbenFormSchema } from '@vben/common-ui'; | import type { VbenFormSchema } from '@vben/common-ui'; | ||||||
| import {type AuthApi, checkCaptcha, getCaptcha } from '#/api/core/auth'; | import { type AuthApi, checkCaptcha, getCaptcha } from '#/api/core/auth'; | ||||||
| 
 | 
 | ||||||
| import { computed, markRaw, onMounted, ref } from 'vue'; | import { computed, onMounted, ref } from 'vue'; | ||||||
| 
 | 
 | ||||||
| import { AuthenticationLogin, Verification, z } from '@vben/common-ui'; | import { AuthenticationLogin, Verification, z } from '@vben/common-ui'; | ||||||
| import { $t } from '@vben/locales'; | import { $t } from '@vben/locales'; | ||||||
|  | @ -23,9 +23,9 @@ const loginRef = ref(); | ||||||
| const verifyRef = ref(); | const verifyRef = ref(); | ||||||
| 
 | 
 | ||||||
| const captchaType = 'blockPuzzle'; // 验证码类型:'blockPuzzle' | 'clickWord' | const captchaType = 'blockPuzzle'; // 验证码类型:'blockPuzzle' | 'clickWord' | ||||||
| const tenantList = ref<AuthApi.TenantResult[]>([]); // 租户列表 |  | ||||||
| 
 | 
 | ||||||
| /** 获取租户列表,并默认选中 */ | /** 获取租户列表,并默认选中 */ | ||||||
|  | const tenantList = ref<AuthApi.TenantResult[]>([]); // 租户列表 | ||||||
| const fetchTenantList = async () => { | const fetchTenantList = async () => { | ||||||
|   if (!tenantEnable) { |   if (!tenantEnable) { | ||||||
|     return; |     return; | ||||||
|  | @ -67,13 +67,13 @@ const handleLogin = async (values: any) => { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // 无验证码,直接登录 |   // 无验证码,直接登录 | ||||||
|   await authStore.authLogin(values); |   await authStore.authLogin('username', values); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 验证码通过,执行登录 */ | /** 验证码通过,执行登录 */ | ||||||
| const handleVerifySuccess = async ({ captchaVerification }: any) => { | const handleVerifySuccess = async ({ captchaVerification }: any) => { | ||||||
|   try { |   try { | ||||||
|     await authStore.authLogin({ |     await authStore.authLogin('username', { | ||||||
|       ...(await loginRef.value.getFormApi().getValues()), |       ...(await loginRef.value.getFormApi().getValues()), | ||||||
|       captchaVerification, |       captchaVerification, | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 YunaiV
						YunaiV