278 lines
6.4 KiB
Vue
278 lines
6.4 KiB
Vue
<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>
|