Browse Source

feat: add sliding verification to the login form (#4461)

pull/4467/head
Vben 1 year ago
committed by GitHub
parent
commit
dac80703d9
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 11
      apps/web-antd/src/views/_core/authentication/login.vue
  2. 11
      apps/web-ele/src/views/_core/authentication/login.vue
  3. 1
      apps/web-naive/src/adapter/index.ts
  4. 0
      apps/web-naive/src/adapter/naive.ts
  5. 2
      apps/web-naive/src/api/request.ts
  6. 2
      apps/web-naive/src/router/access.ts
  7. 2
      apps/web-naive/src/store/auth.ts
  8. 11
      apps/web-naive/src/views/_core/authentication/login.vue
  9. 2
      packages/@core/ui-kit/shadcn-ui/src/components/ui/select/SelectTrigger.vue
  10. 6
      packages/effects/common-ui/src/components/captcha/slider-captcha/index.vue
  11. 9
      packages/effects/common-ui/src/components/captcha/slider-rotate-captcha/index.vue
  12. 1
      packages/locales/src/langs/en-US.json
  13. 1
      packages/locales/src/langs/zh-CN.json
  14. 11
      playground/src/views/_core/authentication/login.vue
  15. 664
      pnpm-lock.yaml
  16. 2
      pnpm-workspace.yaml

11
apps/web-antd/src/views/_core/authentication/login.vue

@ -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>

11
apps/web-ele/src/views/_core/authentication/login.vue

@ -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
apps/web-naive/src/adapter/index.ts

@ -1 +1,2 @@
export * from './form'; export * from './form';
export * from './naive';

0
apps/web-naive/src/naive.ts → apps/web-naive/src/adapter/naive.ts

2
apps/web-naive/src/api/request.ts

@ -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';

2
apps/web-naive/src/router/access.ts

@ -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');

2
apps/web-naive/src/store/auth.ts

@ -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();

11
apps/web-naive/src/views/_core/authentication/login.vue

@ -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
packages/@core/ui-kit/shadcn-ui/src/components/ui/select/SelectTrigger.vue

@ -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,
) )
" "

6
packages/effects/common-ui/src/components/captcha/slider-captcha/index.vue

@ -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;

9
packages/effects/common-ui/src/components/captcha/slider-rotate-captcha/index.vue

@ -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>

1
packages/locales/src/langs/en-US.json

@ -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",

1
packages/locales/src/langs/zh-CN.json

@ -102,6 +102,7 @@
"password": "密码", "password": "密码",
"usernameTip": "请输入用户名", "usernameTip": "请输入用户名",
"passwordTip": "请输入密码", "passwordTip": "请输入密码",
"verifyRequiredTip": "请先完成验证",
"passwordErrorTip": "密码错误", "passwordErrorTip": "密码错误",
"rememberMe": "记住账号", "rememberMe": "记住账号",
"createAnAccount": "创建一个账号", "createAnAccount": "创建一个账号",

11
playground/src/views/_core/authentication/login.vue

@ -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

File diff suppressed because it is too large

2
pnpm-workspace.yaml

@ -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…
Cancel
Save