fix: Improve the problem of inaccurate captcha accuracy (#4401)
* feat: captcha example * fix: fix lint errors * chore: event handling and methods * chore: add accessibility features ARIA labels and roles * refactor: refactor code structure and improve captcha demo page * feat: add captcha internationalization * chore: 适配时间戳国际化展示 * fix: 1. 添加点击位置边界校验,防止点击外部导致x,y误差。2. 演示页面宽度过长添加滚动条。3. 添加hooks ---------pull/48/MERGE
parent
b8a4fba78c
commit
38fe6426a2
|
@ -0,0 +1,18 @@
|
|||
import type { CaptchaPoint } from '../types';
|
||||
|
||||
import { reactive } from 'vue';
|
||||
|
||||
export function useCaptchaPoints() {
|
||||
const points = reactive<CaptchaPoint[]>([]);
|
||||
function addPoint(point: CaptchaPoint) {
|
||||
points.push(point);
|
||||
}
|
||||
function clearPoints() {
|
||||
points.splice(0, points.length);
|
||||
}
|
||||
return {
|
||||
addPoint,
|
||||
clearPoints,
|
||||
points,
|
||||
};
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import type { CaptchaPoint, PointSelectionCaptchaProps } from './types';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { RotateCw } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
import { VbenButton, VbenIconButton } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { CaptchaCard } from '.';
|
||||
import { useCaptchaPoints } from './hooks/useCaptchaPoints';
|
||||
|
||||
const props = withDefaults(defineProps<PointSelectionCaptchaProps>(), {
|
||||
height: '220px',
|
||||
|
@ -19,44 +18,24 @@ const props = withDefaults(defineProps<PointSelectionCaptchaProps>(), {
|
|||
title: '',
|
||||
width: '300px',
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: [CaptchaPoint];
|
||||
confirm: [Array<CaptchaPoint>, clear: () => void];
|
||||
refresh: [];
|
||||
}>();
|
||||
const { addPoint, clearPoints, points } = useCaptchaPoints();
|
||||
|
||||
if (!props.hintImage && !props.hintText) {
|
||||
throw new Error('At least one of hint image or hint text must be provided');
|
||||
console.warn('At least one of hint image or hint text must be provided');
|
||||
}
|
||||
|
||||
const points = ref<CaptchaPoint[]>([]);
|
||||
const POINT_OFFSET = 11;
|
||||
|
||||
function getElementPosition(element: HTMLElement) {
|
||||
let posX = 0;
|
||||
let posY = 0;
|
||||
if (element.getBoundingClientRect) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const doc = document.documentElement;
|
||||
posX =
|
||||
rect.left +
|
||||
Math.max(doc.scrollLeft, document.body.scrollLeft) -
|
||||
doc.clientLeft;
|
||||
posY =
|
||||
rect.top +
|
||||
Math.max(doc.scrollTop, document.body.scrollTop) -
|
||||
doc.clientTop;
|
||||
} else {
|
||||
while (element !== document.body) {
|
||||
posX += element.offsetLeft;
|
||||
posY += element.offsetTop;
|
||||
element = element.offsetParent as HTMLElement;
|
||||
}
|
||||
}
|
||||
const rect = element.getBoundingClientRect();
|
||||
return {
|
||||
x: posX,
|
||||
y: posY,
|
||||
x: rect.left + window.scrollX,
|
||||
y: rect.top + window.scrollY,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -67,25 +46,35 @@ function handleClick(e: MouseEvent) {
|
|||
|
||||
const { x: domX, y: domY } = getElementPosition(dom);
|
||||
|
||||
const mouseX = e.pageX || e.clientX;
|
||||
const mouseY = e.pageY || e.clientY;
|
||||
const mouseX = e.clientX + window.scrollX;
|
||||
const mouseY = e.clientY + window.scrollY;
|
||||
|
||||
if (mouseX === undefined || mouseY === undefined)
|
||||
throw new Error('Mouse coordinates not found');
|
||||
if (typeof mouseX !== 'number' || typeof mouseY !== 'number') {
|
||||
throw new TypeError('Mouse coordinates not found');
|
||||
}
|
||||
|
||||
const xPos = mouseX - domX;
|
||||
const yPos = mouseY - domY;
|
||||
|
||||
const rect = dom.getBoundingClientRect();
|
||||
|
||||
// 点击位置边界校验
|
||||
if (xPos < 0 || yPos < 0 || xPos > rect.width || yPos > rect.height) {
|
||||
console.warn('Click position is out of the valid range');
|
||||
return;
|
||||
}
|
||||
|
||||
const x = Math.ceil(xPos);
|
||||
const y = Math.ceil(yPos);
|
||||
|
||||
const point = {
|
||||
i: points.value.length,
|
||||
i: points.length,
|
||||
t: Date.now(),
|
||||
x,
|
||||
y,
|
||||
};
|
||||
points.value.push(point);
|
||||
|
||||
addPoint(point);
|
||||
|
||||
emit('click', point);
|
||||
e.stopPropagation();
|
||||
|
@ -97,7 +86,7 @@ function handleClick(e: MouseEvent) {
|
|||
|
||||
function clear() {
|
||||
try {
|
||||
points.value = [];
|
||||
clearPoints();
|
||||
} catch (error) {
|
||||
console.error('Error in clear:', error);
|
||||
}
|
||||
|
@ -115,7 +104,7 @@ function handleRefresh() {
|
|||
function handleConfirm() {
|
||||
if (!props.showConfirm) return;
|
||||
try {
|
||||
emit('confirm', points.value, clear);
|
||||
emit('confirm', points, clear);
|
||||
} catch (error) {
|
||||
console.error('Error in handleConfirm:', error);
|
||||
}
|
||||
|
@ -164,6 +153,7 @@ function handleConfirm() {
|
|||
}"
|
||||
class="bg-primary text-primary-50 border-primary-50 absolute z-20 flex h-5 w-5 cursor-default items-center justify-center rounded-full border-2"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
|
|
|
@ -46,7 +46,10 @@ const handleClick = (point: CaptchaPoint) => {
|
|||
:description="$t('page.examples.captcha.pageDescription')"
|
||||
:title="$t('page.examples.captcha.pageTitle')"
|
||||
>
|
||||
<Card :title="$t('page.examples.captcha.basic')" class="mb-4">
|
||||
<Card
|
||||
:title="$t('page.examples.captcha.basic')"
|
||||
class="mb-4 overflow-x-auto"
|
||||
>
|
||||
<div class="mb-3 flex items-center justify-start">
|
||||
<Input
|
||||
v-model:value="params.title"
|
||||
|
|
Loading…
Reference in New Issue