Browse Source

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/4405/head
Squall2017 5 days ago
committed by GitHub
parent
commit
38fe6426a2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 18
      packages/effects/common-ui/src/components/captcha/hooks/useCaptchaPoints.ts
  2. 60
      packages/effects/common-ui/src/components/captcha/point-selection-captcha.vue
  3. 5
      playground/src/views/examples/captcha/index.vue

18
packages/effects/common-ui/src/components/captcha/hooks/useCaptchaPoints.ts

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

60
packages/effects/common-ui/src/components/captcha/point-selection-captcha.vue

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

5
playground/src/views/examples/captcha/index.vue

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