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