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 { 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 { useAuthStore } from '#/store'; | ||||
|  | @ -78,6 +78,13 @@ const formSchema = computed((): VbenFormSchema[] => { | |||
|       label: $t('authentication.password'), | ||||
|       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> | ||||
|  |  | |||
|  | @ -2,9 +2,9 @@ | |||
| import type { VbenFormSchema } from '@vben/common-ui'; | ||||
| 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 { useAuthStore } from '#/store'; | ||||
|  | @ -78,6 +78,13 @@ const formSchema = computed((): VbenFormSchema[] => { | |||
|       label: $t('authentication.password'), | ||||
|       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> | ||||
|  |  | |||
|  | @ -1 +1,2 @@ | |||
| export * from './form'; | ||||
| export * from './naive'; | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ import { | |||
| } from '@vben/request'; | ||||
| import { useAccessStore } from '@vben/stores'; | ||||
| 
 | ||||
| import { message } from '#/naive'; | ||||
| import { message } from '#/adapter'; | ||||
| import { useAuthStore } from '#/store'; | ||||
| 
 | ||||
| import { refreshTokenApi } from './core'; | ||||
|  |  | |||
|  | @ -6,10 +6,10 @@ import type { | |||
| import { generateAccessible } from '@vben/access'; | ||||
| import { preferences } from '@vben/preferences'; | ||||
| 
 | ||||
| import { message } from '#/adapter'; | ||||
| import { getAllMenusApi } from '#/api'; | ||||
| import { BasicLayout, IFrameView } from '#/layouts'; | ||||
| import { $t } from '#/locales'; | ||||
| import { message } from '#/naive'; | ||||
| 
 | ||||
| const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,9 +9,9 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; | |||
| 
 | ||||
| import { defineStore } from 'pinia'; | ||||
| 
 | ||||
| import { notification } from '#/adapter'; | ||||
| import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api'; | ||||
| import { $t } from '#/locales'; | ||||
| import { notification } from '#/naive'; | ||||
| 
 | ||||
| export const useAuthStore = defineStore('auth', () => { | ||||
|   const accessStore = useAccessStore(); | ||||
|  |  | |||
|  | @ -2,9 +2,9 @@ | |||
| import type { VbenFormSchema } from '@vben/common-ui'; | ||||
| 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 { useAuthStore } from '#/store'; | ||||
|  | @ -78,6 +78,13 @@ const formSchema = computed((): VbenFormSchema[] => { | |||
|       label: $t('authentication.password'), | ||||
|       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> | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ const forwardedProps = useForwardProps(delegatedProps); | |||
|     v-bind="forwardedProps" | ||||
|     :class=" | ||||
|       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, | ||||
|       ) | ||||
|     " | ||||
|  |  | |||
|  | @ -70,9 +70,9 @@ watchEffect(() => { | |||
| }); | ||||
| 
 | ||||
| function getEventPageX(e: MouseEvent | TouchEvent): number { | ||||
|   if (e instanceof MouseEvent) { | ||||
|   if ('pageX' in e) { | ||||
|     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 0; | ||||
|  | @ -183,6 +183,8 @@ function resume() { | |||
|   const barEl = unref(barRef); | ||||
|   const contentEl = unref(contentRef); | ||||
|   if (!actionEl || !barEl || !contentEl) return; | ||||
| 
 | ||||
|   contentEl.getEl().style.width = '100%'; | ||||
|   state.toLeft = true; | ||||
|   useTimeoutFn(() => { | ||||
|     state.toLeft = false; | ||||
|  |  | |||
|  | @ -66,6 +66,10 @@ const getImgWrapStyleRef = computed(() => { | |||
| 
 | ||||
| const getFactorRef = computed(() => { | ||||
|   const { maxDegree, minDegree } = props; | ||||
|   if (minDegree > maxDegree) { | ||||
|     console.warn('minDegree should not be greater than maxDegree'); | ||||
|   } | ||||
| 
 | ||||
|   if (minDegree === maxDegree) { | ||||
|     return Math.floor(1 + Math.random() * 1) / 10 + 1; | ||||
|   } | ||||
|  | @ -116,6 +120,7 @@ function handleDragEnd() { | |||
|     checkPass(); | ||||
|   } | ||||
|   state.showTip = true; | ||||
|   state.dragging = false; | ||||
| } | ||||
| 
 | ||||
| function setImgRotate(deg: number) { | ||||
|  | @ -162,7 +167,7 @@ defineExpose({ | |||
|   <div class="relative flex flex-col items-center"> | ||||
|     <div | ||||
|       :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 | ||||
|         :class="imgCls" | ||||
|  | @ -185,7 +190,7 @@ defineExpose({ | |||
|         > | ||||
|           {{ verifyTip }} | ||||
|         </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') }} | ||||
|         </div> | ||||
|       </div> | ||||
|  |  | |||
|  | @ -103,6 +103,7 @@ | |||
|     "usernameTip": "Please enter username", | ||||
|     "passwordErrorTip": "Password is incorrect", | ||||
|     "passwordTip": "Please enter password", | ||||
|     "verifyRequiredTip": "Please complete the verification first", | ||||
|     "rememberMe": "Remember Me", | ||||
|     "createAnAccount": "Create an Account", | ||||
|     "createAccount": "Create Account", | ||||
|  |  | |||
|  | @ -102,6 +102,7 @@ | |||
|     "password": "密码", | ||||
|     "usernameTip": "请输入用户名", | ||||
|     "passwordTip": "请输入密码", | ||||
|     "verifyRequiredTip": "请先完成验证", | ||||
|     "passwordErrorTip": "密码错误", | ||||
|     "rememberMe": "记住账号", | ||||
|     "createAnAccount": "创建一个账号", | ||||
|  |  | |||
|  | @ -2,9 +2,9 @@ | |||
| import type { VbenFormSchema } from '@vben/common-ui'; | ||||
| 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 { useAuthStore } from '#/store'; | ||||
|  | @ -95,6 +95,13 @@ const formSchema = computed((): VbenFormSchema[] => { | |||
|       label: $t('authentication.password'), | ||||
|       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> | ||||
|  |  | |||
							
								
								
									
										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 | ||||
|   resolve.exports: ^2.0.2 | ||||
|   rimraf: ^6.0.1 | ||||
|   rollup: ^4.22.2 | ||||
|   rollup: ^4.22.4 | ||||
|   rollup-plugin-visualizer: ^5.12.0 | ||||
|   sass: ^1.79.3 | ||||
|   sortablejs: ^1.15.3 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Vben
						Vben