RuralDatabase/apps/web/components/common/captcha/captcha.vue

278 lines
6.4 KiB
Vue
Raw Normal View History

2024-06-30 21:39:37 +08:00
<script setup lang="ts">
const {
value,
width = '300px',
height = '240px',
calcPosType = 'dom',
maxDot = 5,
imageBase64,
thumbBase64,
} = defineProps<{
value?: boolean
width?: string
height?: string
calcPosType?: string
maxDot?: number
imageBase64?: string
thumbBase64?: string
}>()
const emit = defineEmits<{
(e: 'close'): void
(e: 'success', value: { CaptchaCode: string }): void
(e: 'refresh'): void
}>()
const { data, refresh } = useAsyncData(API.common.auth.capthcha)
const toast = useToastHandle()
const state = reactive({
dots: ref<any[]>([]),
imageBase64Code: imageBase64,
thumbBase64Code: thumbBase64,
style: computed(() => {
return `width:${width}; height:${height};`
}),
/**
* @Description: 处理关闭事件
*/
handleCloseEvent() {
emit('close')
state.dots = []
state.imageBase64Code = ''
state.thumbBase64Code = ''
},
/**
* @Description: 处理刷新事件
*/
handleRefreshEvent() {
state.dots = []
refresh()
emit('refresh')
},
/**
* @Description: 处理确认事件
*/
async handleConfirmEvent() {
try {
// 验证验证码
const res = await API.common.auth.checkCaptcha({
UUID: data.value?.UUID,
Check: state.dots.map(i => [i.x, i.y].join(',')).join(','),
})
emit('success', { CaptchaCode: res?.Code ?? '' })
} catch (err: any) {
toast.error(err.message)
state.handleRefreshEvent()
}
},
/**
* @Description: 处理dot
* @param ev
*/
handleClickPos(ev: any) {
if (state.dots.length >= maxDot)
return
const e = ev || window.event
e.preventDefault()
const dom = e.currentTarget
const { domX, domY } = state.getDomXY(dom)
// ===============================================
// @notice 如 getDomXY 不准确可尝试使用 calcLocationLeft 或 calcLocationTop
// const domX = state.calcLocationLeft(dom)
// const domY = state.calcLocationTop(dom)
// ===============================================
let mouseX = navigator.appName === 'Netscape'
? e.pageX
: e.x + document.body.offsetTop
let mouseY = navigator.appName === 'Netscape'
? e.pageY
: e.y + document.body.offsetTop
if (calcPosType === 'screen') {
mouseX = navigator.appName === 'Netscape' ? e.clientX : e.x
mouseY = navigator.appName === 'Netscape' ? e.clientY : e.y
}
// 计算点击的相对位置
const xPos = mouseX - domX
const yPos = mouseY - domY
// 转整形
const xp = Number.parseInt(xPos.toString())
const yp = Number.parseInt(yPos.toString())
// 减去点的一半
state.dots.push({
x: xp - 11,
y: yp - 11,
index: state.dots.length + 1,
})
return false
},
/**
* @Description: 找到元素的屏幕位置
* @param el
*/
calcLocationLeft(el: any) {
let tmp = el.offsetLeft
let val = el.offsetParent
while (val != null) {
tmp += val.offsetLeft
val = val.offsetParent
}
return tmp
},
/**
* @Description: 找到元素的屏幕位置
* @param el
*/
calcLocationTop(el: any) {
let tmp = el.offsetTop
let val = el.offsetParent
while (val != null) {
tmp += val.offsetTop
val = val.offsetParent
}
return tmp
},
/**
* @Description: 找到元素的屏幕位置
* @param dom
*/
getDomXY(dom: any) {
let x = 0
let y = 0
if (dom.getBoundingClientRect) {
const box = dom.getBoundingClientRect()
const D = document.documentElement
x = box.left
+ Math.max(D.scrollLeft, document.body.scrollLeft)
- D.clientLeft
y = box.top
+ Math.max(D.scrollTop, document.body.scrollTop)
- D.clientTop
}
else {
while (dom !== document.body) {
x += dom.offsetLeft
y += dom.offsetTop
dom = dom.offsetParent
}
}
return {
domX: x,
domY: y,
}
},
})
effect(() => {
if (data.value) {
const res = (data?.value)
// const res = JSON.parse(data?.value)
state.imageBase64Code = res?.BackgroundImage
state.thumbBase64Code = res?.CheckImage
}
})
watch(() => value, () => {
state.dots = []
state.imageBase64Code = ''
state.thumbBase64Code = ''
})
watch(() => imageBase64, (val) => {
state.dots = []
state.imageBase64Code = val
})
watch(() => thumbBase64, (val) => {
state.dots = []
state.thumbBase64Code = val
})
</script>
<template>
<UCard>
<template #header>
<div class="flex items-center justify-center">
<span>
请在下图
<em>依次</em>
点击
</span>
<img v-if="state.thumbBase64Code" :src="state.thumbBase64Code" alt=" ">
</div>
</template>
<div class="wg-cap-wrap__body">
<img
v-if="state.imageBase64Code"
class="wg-cap-wrap__picture"
:src="state.imageBase64Code"
alt=" "
@click="state.handleClickPos($event)"
>
<template v-for="(dot, key) in state.dots" :key="key">
<div class="wg-cap-wrap__dot" :style="`top: ${dot.y}px; left:${dot.x}px;`">
<span>{{ dot.index }}</span>
</div>
</template>
</div>
<template #footer>
<div class="flex justify-between">
<div>
<!-- <UButton icon="i-solar-close-circle-line-duotone" variant="ghost" color="gray" title="关闭" @click="state.handleCloseEvent" /> -->
<UButton
icon="i-solar-refresh-circle-line-duotone"
variant="ghost"
color="gray"
title="刷新"
@click="state.handleRefreshEvent"
/>
</div>
<div>
<UButton @click="state.handleConfirmEvent">
确认
</UButton>
</div>
</div>
</template>
</UCard>
</template>
<style>
.wg-cap-wrap__body {
position: relative;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
background: #34383e;
margin: auto;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
overflow: hidden;
}
.wg-cap-wrap__body .wg-cap-wrap__dot {
position: absolute;
z-index: 10;
width: 22px;
height: 22px;
color: #cedffe;
background: #3e7cff;
border: 2px solid #f7f9fb;
line-height: 20px;
text-align: center;
-webkit-border-radius: 22px;
-moz-border-radius: 22px;
border-radius: 22px;
cursor: default;
}
</style>