feat: add sliding verification to the login form (#4461)
							parent
							
								
									000172e482
								
							
						
					
					
						commit
						dac80703d9
					
				|  | @ -2,9 +2,9 @@ | ||||||
| import type { VbenFormSchema } from '@vben/common-ui'; | import type { VbenFormSchema } from '@vben/common-ui'; | ||||||
| import type { BasicOption } from '@vben/types'; | import type { BasicOption } from '@vben/types'; | ||||||
| 
 | 
 | ||||||
| import { computed } from 'vue'; | import { computed, markRaw } from 'vue'; | ||||||
| 
 | 
 | ||||||
| import { AuthenticationLogin, z } from '@vben/common-ui'; | import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui'; | ||||||
| import { $t } from '@vben/locales'; | import { $t } from '@vben/locales'; | ||||||
| 
 | 
 | ||||||
| import { useAuthStore } from '#/store'; | import { useAuthStore } from '#/store'; | ||||||
|  | @ -78,6 +78,13 @@ const formSchema = computed((): VbenFormSchema[] => { | ||||||
|       label: $t('authentication.password'), |       label: $t('authentication.password'), | ||||||
|       rules: z.string().min(1, { message: $t('authentication.passwordTip') }), |       rules: z.string().min(1, { message: $t('authentication.passwordTip') }), | ||||||
|     }, |     }, | ||||||
|  |     { | ||||||
|  |       component: markRaw(SliderCaptcha), | ||||||
|  |       fieldName: 'captcha', | ||||||
|  |       rules: z.boolean().refine((value) => value, { | ||||||
|  |         message: $t('authentication.verifyRequiredTip'), | ||||||
|  |       }), | ||||||
|  |     }, | ||||||
|   ]; |   ]; | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -2,9 +2,9 @@ | ||||||
| import type { VbenFormSchema } from '@vben/common-ui'; | import type { VbenFormSchema } from '@vben/common-ui'; | ||||||
| import type { BasicOption } from '@vben/types'; | import type { BasicOption } from '@vben/types'; | ||||||
| 
 | 
 | ||||||
| import { computed } from 'vue'; | import { computed, markRaw } from 'vue'; | ||||||
| 
 | 
 | ||||||
| import { AuthenticationLogin, z } from '@vben/common-ui'; | import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui'; | ||||||
| import { $t } from '@vben/locales'; | import { $t } from '@vben/locales'; | ||||||
| 
 | 
 | ||||||
| import { useAuthStore } from '#/store'; | import { useAuthStore } from '#/store'; | ||||||
|  | @ -78,6 +78,13 @@ const formSchema = computed((): VbenFormSchema[] => { | ||||||
|       label: $t('authentication.password'), |       label: $t('authentication.password'), | ||||||
|       rules: z.string().min(1, { message: $t('authentication.passwordTip') }), |       rules: z.string().min(1, { message: $t('authentication.passwordTip') }), | ||||||
|     }, |     }, | ||||||
|  |     { | ||||||
|  |       component: markRaw(SliderCaptcha), | ||||||
|  |       fieldName: 'captcha', | ||||||
|  |       rules: z.boolean().refine((value) => value, { | ||||||
|  |         message: $t('authentication.verifyRequiredTip'), | ||||||
|  |       }), | ||||||
|  |     }, | ||||||
|   ]; |   ]; | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -1 +1,2 @@ | ||||||
| export * from './form'; | export * from './form'; | ||||||
|  | export * from './naive'; | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ import { | ||||||
| } from '@vben/request'; | } from '@vben/request'; | ||||||
| import { useAccessStore } from '@vben/stores'; | import { useAccessStore } from '@vben/stores'; | ||||||
| 
 | 
 | ||||||
| import { message } from '#/naive'; | import { message } from '#/adapter'; | ||||||
| import { useAuthStore } from '#/store'; | import { useAuthStore } from '#/store'; | ||||||
| 
 | 
 | ||||||
| import { refreshTokenApi } from './core'; | import { refreshTokenApi } from './core'; | ||||||
|  |  | ||||||
|  | @ -6,10 +6,10 @@ import type { | ||||||
| import { generateAccessible } from '@vben/access'; | import { generateAccessible } from '@vben/access'; | ||||||
| import { preferences } from '@vben/preferences'; | import { preferences } from '@vben/preferences'; | ||||||
| 
 | 
 | ||||||
|  | import { message } from '#/adapter'; | ||||||
| import { getAllMenusApi } from '#/api'; | import { getAllMenusApi } from '#/api'; | ||||||
| import { BasicLayout, IFrameView } from '#/layouts'; | import { BasicLayout, IFrameView } from '#/layouts'; | ||||||
| import { $t } from '#/locales'; | import { $t } from '#/locales'; | ||||||
| import { message } from '#/naive'; |  | ||||||
| 
 | 
 | ||||||
| const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); | const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,9 +9,9 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; | ||||||
| 
 | 
 | ||||||
| import { defineStore } from 'pinia'; | import { defineStore } from 'pinia'; | ||||||
| 
 | 
 | ||||||
|  | import { notification } from '#/adapter'; | ||||||
| import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api'; | import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api'; | ||||||
| import { $t } from '#/locales'; | import { $t } from '#/locales'; | ||||||
| import { notification } from '#/naive'; |  | ||||||
| 
 | 
 | ||||||
| export const useAuthStore = defineStore('auth', () => { | export const useAuthStore = defineStore('auth', () => { | ||||||
|   const accessStore = useAccessStore(); |   const accessStore = useAccessStore(); | ||||||
|  |  | ||||||
|  | @ -2,9 +2,9 @@ | ||||||
| import type { VbenFormSchema } from '@vben/common-ui'; | import type { VbenFormSchema } from '@vben/common-ui'; | ||||||
| import type { BasicOption } from '@vben/types'; | import type { BasicOption } from '@vben/types'; | ||||||
| 
 | 
 | ||||||
| import { computed } from 'vue'; | import { computed, markRaw } from 'vue'; | ||||||
| 
 | 
 | ||||||
| import { AuthenticationLogin, z } from '@vben/common-ui'; | import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui'; | ||||||
| import { $t } from '@vben/locales'; | import { $t } from '@vben/locales'; | ||||||
| 
 | 
 | ||||||
| import { useAuthStore } from '#/store'; | import { useAuthStore } from '#/store'; | ||||||
|  | @ -78,6 +78,13 @@ const formSchema = computed((): VbenFormSchema[] => { | ||||||
|       label: $t('authentication.password'), |       label: $t('authentication.password'), | ||||||
|       rules: z.string().min(1, { message: $t('authentication.passwordTip') }), |       rules: z.string().min(1, { message: $t('authentication.passwordTip') }), | ||||||
|     }, |     }, | ||||||
|  |     { | ||||||
|  |       component: markRaw(SliderCaptcha), | ||||||
|  |       fieldName: 'captcha', | ||||||
|  |       rules: z.boolean().refine((value) => value, { | ||||||
|  |         message: $t('authentication.verifyRequiredTip'), | ||||||
|  |       }), | ||||||
|  |     }, | ||||||
|   ]; |   ]; | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -27,7 +27,7 @@ const forwardedProps = useForwardProps(delegatedProps); | ||||||
|     v-bind="forwardedProps" |     v-bind="forwardedProps" | ||||||
|     :class=" |     :class=" | ||||||
|       cn( |       cn( | ||||||
|         'border-input ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border bg-transparent px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-1 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1', |         'border-input ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-10 w-full items-center justify-between whitespace-nowrap rounded-md border bg-transparent px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-1 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1', | ||||||
|         props.class, |         props.class, | ||||||
|       ) |       ) | ||||||
|     " |     " | ||||||
|  |  | ||||||
|  | @ -70,9 +70,9 @@ watchEffect(() => { | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| function getEventPageX(e: MouseEvent | TouchEvent): number { | function getEventPageX(e: MouseEvent | TouchEvent): number { | ||||||
|   if (e instanceof MouseEvent) { |   if ('pageX' in e) { | ||||||
|     return e.pageX; |     return e.pageX; | ||||||
|   } else if (e instanceof TouchEvent && e.touches[0]) { |   } else if ('touches' in e && e.touches[0]) { | ||||||
|     return e.touches[0].pageX; |     return e.touches[0].pageX; | ||||||
|   } |   } | ||||||
|   return 0; |   return 0; | ||||||
|  | @ -183,6 +183,8 @@ function resume() { | ||||||
|   const barEl = unref(barRef); |   const barEl = unref(barRef); | ||||||
|   const contentEl = unref(contentRef); |   const contentEl = unref(contentRef); | ||||||
|   if (!actionEl || !barEl || !contentEl) return; |   if (!actionEl || !barEl || !contentEl) return; | ||||||
|  | 
 | ||||||
|  |   contentEl.getEl().style.width = '100%'; | ||||||
|   state.toLeft = true; |   state.toLeft = true; | ||||||
|   useTimeoutFn(() => { |   useTimeoutFn(() => { | ||||||
|     state.toLeft = false; |     state.toLeft = false; | ||||||
|  |  | ||||||
|  | @ -66,6 +66,10 @@ const getImgWrapStyleRef = computed(() => { | ||||||
| 
 | 
 | ||||||
| const getFactorRef = computed(() => { | const getFactorRef = computed(() => { | ||||||
|   const { maxDegree, minDegree } = props; |   const { maxDegree, minDegree } = props; | ||||||
|  |   if (minDegree > maxDegree) { | ||||||
|  |     console.warn('minDegree should not be greater than maxDegree'); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   if (minDegree === maxDegree) { |   if (minDegree === maxDegree) { | ||||||
|     return Math.floor(1 + Math.random() * 1) / 10 + 1; |     return Math.floor(1 + Math.random() * 1) / 10 + 1; | ||||||
|   } |   } | ||||||
|  | @ -116,6 +120,7 @@ function handleDragEnd() { | ||||||
|     checkPass(); |     checkPass(); | ||||||
|   } |   } | ||||||
|   state.showTip = true; |   state.showTip = true; | ||||||
|  |   state.dragging = false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function setImgRotate(deg: number) { | function setImgRotate(deg: number) { | ||||||
|  | @ -162,7 +167,7 @@ defineExpose({ | ||||||
|   <div class="relative flex flex-col items-center"> |   <div class="relative flex flex-col items-center"> | ||||||
|     <div |     <div | ||||||
|       :style="getImgWrapStyleRef" |       :style="getImgWrapStyleRef" | ||||||
|       class="border-border relative overflow-hidden rounded-full border shadow-md" |       class="border-border relative cursor-pointer overflow-hidden rounded-full border shadow-md" | ||||||
|     > |     > | ||||||
|       <img |       <img | ||||||
|         :class="imgCls" |         :class="imgCls" | ||||||
|  | @ -185,7 +190,7 @@ defineExpose({ | ||||||
|         > |         > | ||||||
|           {{ verifyTip }} |           {{ verifyTip }} | ||||||
|         </div> |         </div> | ||||||
|         <div v-if="!state.showTip && !state.dragging" class="bg-black/30"> |         <div v-if="!state.dragging" class="bg-black/30"> | ||||||
|           {{ defaultTip || $t('ui.captcha.sliderRotateDefaultTip') }} |           {{ defaultTip || $t('ui.captcha.sliderRotateDefaultTip') }} | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|  | @ -103,6 +103,7 @@ | ||||||
|     "usernameTip": "Please enter username", |     "usernameTip": "Please enter username", | ||||||
|     "passwordErrorTip": "Password is incorrect", |     "passwordErrorTip": "Password is incorrect", | ||||||
|     "passwordTip": "Please enter password", |     "passwordTip": "Please enter password", | ||||||
|  |     "verifyRequiredTip": "Please complete the verification first", | ||||||
|     "rememberMe": "Remember Me", |     "rememberMe": "Remember Me", | ||||||
|     "createAnAccount": "Create an Account", |     "createAnAccount": "Create an Account", | ||||||
|     "createAccount": "Create Account", |     "createAccount": "Create Account", | ||||||
|  |  | ||||||
|  | @ -102,6 +102,7 @@ | ||||||
|     "password": "密码", |     "password": "密码", | ||||||
|     "usernameTip": "请输入用户名", |     "usernameTip": "请输入用户名", | ||||||
|     "passwordTip": "请输入密码", |     "passwordTip": "请输入密码", | ||||||
|  |     "verifyRequiredTip": "请先完成验证", | ||||||
|     "passwordErrorTip": "密码错误", |     "passwordErrorTip": "密码错误", | ||||||
|     "rememberMe": "记住账号", |     "rememberMe": "记住账号", | ||||||
|     "createAnAccount": "创建一个账号", |     "createAnAccount": "创建一个账号", | ||||||
|  |  | ||||||
|  | @ -2,9 +2,9 @@ | ||||||
| import type { VbenFormSchema } from '@vben/common-ui'; | import type { VbenFormSchema } from '@vben/common-ui'; | ||||||
| import type { BasicOption } from '@vben/types'; | import type { BasicOption } from '@vben/types'; | ||||||
| 
 | 
 | ||||||
| import { computed } from 'vue'; | import { computed, markRaw } from 'vue'; | ||||||
| 
 | 
 | ||||||
| import { AuthenticationLogin, z } from '@vben/common-ui'; | import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui'; | ||||||
| import { $t } from '@vben/locales'; | import { $t } from '@vben/locales'; | ||||||
| 
 | 
 | ||||||
| import { useAuthStore } from '#/store'; | import { useAuthStore } from '#/store'; | ||||||
|  | @ -95,6 +95,13 @@ const formSchema = computed((): VbenFormSchema[] => { | ||||||
|       label: $t('authentication.password'), |       label: $t('authentication.password'), | ||||||
|       rules: z.string().min(1, { message: $t('authentication.passwordTip') }), |       rules: z.string().min(1, { message: $t('authentication.passwordTip') }), | ||||||
|     }, |     }, | ||||||
|  |     { | ||||||
|  |       component: markRaw(SliderCaptcha), | ||||||
|  |       fieldName: 'captcha', | ||||||
|  |       rules: z.boolean().refine((value) => value, { | ||||||
|  |         message: $t('authentication.verifyRequiredTip'), | ||||||
|  |       }), | ||||||
|  |     }, | ||||||
|   ]; |   ]; | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
							
								
								
									
										664
									
								
								pnpm-lock.yaml
								
								
								
								
							
							
						
						
									
										664
									
								
								pnpm-lock.yaml
								
								
								
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -134,7 +134,7 @@ catalog: | ||||||
|   radix-vue: ^1.9.6 |   radix-vue: ^1.9.6 | ||||||
|   resolve.exports: ^2.0.2 |   resolve.exports: ^2.0.2 | ||||||
|   rimraf: ^6.0.1 |   rimraf: ^6.0.1 | ||||||
|   rollup: ^4.22.2 |   rollup: ^4.22.4 | ||||||
|   rollup-plugin-visualizer: ^5.12.0 |   rollup-plugin-visualizer: ^5.12.0 | ||||||
|   sass: ^1.79.3 |   sass: ^1.79.3 | ||||||
|   sortablejs: ^1.15.3 |   sortablejs: ^1.15.3 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Vben
						Vben