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