mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-17 16:56:38 +08:00
add Captcha components
This commit is contained in:
parent
c4a68076d6
commit
7fe50788a5
2
database/update-v4.1.3.sql
Normal file
2
database/update-v4.1.3.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `chatgpt_users` ADD `mobile` CHAR(11) NULL COMMENT '手机号' AFTER `username`;
|
||||||
|
ALTER TABLE `chatgpt_users` ADD `email` VARCHAR(50) NULL COMMENT '邮箱地址' AFTER `mobile`;
|
144
web/src/components/Captcha.vue
Normal file
144
web/src/components/Captcha.vue
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<template>
|
||||||
|
<el-container class="captcha-box">
|
||||||
|
<el-dialog
|
||||||
|
v-model="show"
|
||||||
|
:close-on-click-modal="true"
|
||||||
|
:show-close="false"
|
||||||
|
style="width: 360px;"
|
||||||
|
>
|
||||||
|
<slide-captcha
|
||||||
|
v-if="isMobile()"
|
||||||
|
:bg-img="bgImg"
|
||||||
|
:bk-img="bkImg"
|
||||||
|
:result="result"
|
||||||
|
@refresh="getSlideCaptcha"
|
||||||
|
@confirm="handleSlideConfirm"
|
||||||
|
@hide="show = false"/>
|
||||||
|
|
||||||
|
<captcha-plus
|
||||||
|
v-else
|
||||||
|
:max-dot="maxDot"
|
||||||
|
:image-base64="imageBase64"
|
||||||
|
:thumb-base64="thumbBase64"
|
||||||
|
width="300"
|
||||||
|
@close="show = false"
|
||||||
|
@refresh="handleRequestCaptCode"
|
||||||
|
@confirm="handleConfirm"
|
||||||
|
/>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
</el-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref} from "vue";
|
||||||
|
import lodash from 'lodash'
|
||||||
|
import {validateEmail, validateMobile} from "@/utils/validate";
|
||||||
|
import {httpGet, httpPost} from "@/utils/http";
|
||||||
|
import CaptchaPlus from "@/components/CaptchaPlus.vue";
|
||||||
|
import SlideCaptcha from "@/components/SlideCaptcha.vue";
|
||||||
|
import {isMobile} from "@/utils/libs";
|
||||||
|
import {showMessageError, showMessageOK} from "@/utils/dialog";
|
||||||
|
|
||||||
|
const show = ref(false)
|
||||||
|
const maxDot = ref(5)
|
||||||
|
const imageBase64 = ref('')
|
||||||
|
const thumbBase64 = ref('')
|
||||||
|
const captKey = ref('')
|
||||||
|
const dots = ref(null)
|
||||||
|
|
||||||
|
const emits = defineEmits(['success']);
|
||||||
|
const handleRequestCaptCode = () => {
|
||||||
|
|
||||||
|
httpGet('/api/captcha/get').then(res => {
|
||||||
|
const data = res.data
|
||||||
|
imageBase64.value = data.image
|
||||||
|
thumbBase64.value = data.thumb
|
||||||
|
captKey.value = data.key
|
||||||
|
}).catch(e => {
|
||||||
|
showMessageError('获取人机验证数据失败:' + e.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirm = (dts) => {
|
||||||
|
if (lodash.size(dts) <= 0) {
|
||||||
|
return showMessageError('请进行人机验证再操作')
|
||||||
|
}
|
||||||
|
|
||||||
|
let dotArr = []
|
||||||
|
lodash.forEach(dts, (dot) => {
|
||||||
|
dotArr.push(dot.x, dot.y)
|
||||||
|
})
|
||||||
|
dots.value = dotArr.join(',')
|
||||||
|
httpPost('/api/captcha/check', {
|
||||||
|
dots: dots.value,
|
||||||
|
key: captKey.value
|
||||||
|
}).then(() => {
|
||||||
|
// ElMessage.success('人机验证成功')
|
||||||
|
show.value = false
|
||||||
|
emits('success', {key:captKey.value, dots:dots.value})
|
||||||
|
}).catch(() => {
|
||||||
|
showMessageError('人机验证失败')
|
||||||
|
handleRequestCaptCode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadCaptcha = () => {
|
||||||
|
show.value = true
|
||||||
|
// 手机用滑动验证码
|
||||||
|
if (isMobile()) {
|
||||||
|
getSlideCaptcha()
|
||||||
|
} else {
|
||||||
|
handleRequestCaptCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滑动验证码
|
||||||
|
const bgImg = ref('')
|
||||||
|
const bkImg = ref('')
|
||||||
|
const result = ref(0)
|
||||||
|
|
||||||
|
const getSlideCaptcha = () => {
|
||||||
|
result.value = 0
|
||||||
|
httpGet("/api/captcha/slide/get").then(res => {
|
||||||
|
bkImg.value = res.data.bkImg
|
||||||
|
bgImg.value = res.data.bgImg
|
||||||
|
captKey.value = res.data.key
|
||||||
|
}).catch(e => {
|
||||||
|
showMessageError('获取人机验证数据失败:' + e.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSlideConfirm = (x) => {
|
||||||
|
httpPost("/api/captcha/slide/check", {
|
||||||
|
key: captKey.value,
|
||||||
|
x: x
|
||||||
|
}).then(() => {
|
||||||
|
result.value = 1
|
||||||
|
show.value = false
|
||||||
|
emits('success',{key:captKey.value, x:x})
|
||||||
|
}).catch(() => {
|
||||||
|
result.value = 2
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出方法以便父组件调用
|
||||||
|
defineExpose({
|
||||||
|
loadCaptcha
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus">
|
||||||
|
|
||||||
|
.captcha-box {
|
||||||
|
.el-dialog {
|
||||||
|
.el-dialog__header {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog__body {
|
||||||
|
padding 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,48 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-container class="captcha-box">
|
<el-container class="send-verify-code">
|
||||||
<el-button type="primary" class="send-btn" :size="props.size" :disabled="!canSend" @click="loadCaptcha" plain>
|
<el-button type="primary" class="send-btn" :size="props.size" :disabled="!canSend" @click="showCaptcha" plain>
|
||||||
{{ btnText }}
|
{{ btnText }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
<el-dialog
|
<captcha @success="sendMsg" ref="captchaRef"/>
|
||||||
v-model="showCaptcha"
|
|
||||||
:close-on-click-modal="true"
|
|
||||||
:show-close="false"
|
|
||||||
style="width: 360px;"
|
|
||||||
>
|
|
||||||
<slide-captcha
|
|
||||||
v-if="isMobile()"
|
|
||||||
:bg-img="bgImg"
|
|
||||||
:bk-img="bkImg"
|
|
||||||
:result="result"
|
|
||||||
@refresh="getSlideCaptcha"
|
|
||||||
@confirm="handleSlideConfirm"
|
|
||||||
@hide="showCaptcha = false"/>
|
|
||||||
|
|
||||||
<captcha-plus
|
|
||||||
v-else
|
|
||||||
:max-dot="maxDot"
|
|
||||||
:image-base64="imageBase64"
|
|
||||||
:thumb-base64="thumbBase64"
|
|
||||||
width="300"
|
|
||||||
@close="showCaptcha = false"
|
|
||||||
@refresh="handleRequestCaptCode"
|
|
||||||
@confirm="handleConfirm"
|
|
||||||
/>
|
|
||||||
</el-dialog>
|
|
||||||
</el-container>
|
</el-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
// 发送短信验证码组件
|
// 发送短信验证码组件
|
||||||
import {ref} from "vue";
|
import {ref} from "vue";
|
||||||
import lodash from 'lodash'
|
|
||||||
import {validateEmail, validateMobile} from "@/utils/validate";
|
import {validateEmail, validateMobile} from "@/utils/validate";
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import {httpPost} from "@/utils/http";
|
||||||
import CaptchaPlus from "@/components/CaptchaPlus.vue";
|
|
||||||
import SlideCaptcha from "@/components/SlideCaptcha.vue";
|
|
||||||
import {isMobile} from "@/utils/libs";
|
|
||||||
import {showMessageError, showMessageOK} from "@/utils/dialog";
|
import {showMessageError, showMessageOK} from "@/utils/dialog";
|
||||||
|
import Captcha from "@/components/Captcha.vue";
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -51,69 +23,22 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
const btnText = ref('发送验证码')
|
const btnText = ref('发送验证码')
|
||||||
const canSend = ref(true)
|
const canSend = ref(true)
|
||||||
const showCaptcha = ref(false)
|
const captchaRef = ref(null)
|
||||||
const maxDot = ref(5)
|
|
||||||
const imageBase64 = ref('')
|
|
||||||
const thumbBase64 = ref('')
|
|
||||||
const captKey = ref('')
|
|
||||||
const dots = ref(null)
|
|
||||||
|
|
||||||
const handleRequestCaptCode = () => {
|
const showCaptcha = () => {
|
||||||
|
|
||||||
httpGet('/api/captcha/get').then(res => {
|
|
||||||
const data = res.data
|
|
||||||
imageBase64.value = data.image
|
|
||||||
thumbBase64.value = data.thumb
|
|
||||||
captKey.value = data.key
|
|
||||||
}).catch(e => {
|
|
||||||
showMessageError('获取人机验证数据失败:' + e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleConfirm = (dots) => {
|
|
||||||
if (lodash.size(dots) <= 0) {
|
|
||||||
return showMessageError('请进行人机验证再操作')
|
|
||||||
}
|
|
||||||
|
|
||||||
let dotArr = []
|
|
||||||
lodash.forEach(dots, (dot) => {
|
|
||||||
dotArr.push(dot.x, dot.y)
|
|
||||||
})
|
|
||||||
dots.value = dotArr.join(',')
|
|
||||||
httpPost('/api/captcha/check', {
|
|
||||||
dots: dots.value,
|
|
||||||
key: captKey.value
|
|
||||||
}).then(() => {
|
|
||||||
// ElMessage.success('人机验证成功')
|
|
||||||
showCaptcha.value = false
|
|
||||||
sendMsg()
|
|
||||||
}).catch(() => {
|
|
||||||
showMessageError('人机验证失败')
|
|
||||||
handleRequestCaptCode()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadCaptcha = () => {
|
|
||||||
if (!validateMobile(props.receiver) && !validateEmail(props.receiver)) {
|
if (!validateMobile(props.receiver) && !validateEmail(props.receiver)) {
|
||||||
return showMessageError("请输入合法的手机号/邮箱地址")
|
return showMessageError("请输入合法的手机号/邮箱地址")
|
||||||
}
|
}
|
||||||
|
captchaRef.value.loadCaptcha()
|
||||||
showCaptcha.value = true
|
|
||||||
// 手机用滑动验证码
|
|
||||||
if (isMobile()) {
|
|
||||||
getSlideCaptcha()
|
|
||||||
} else {
|
|
||||||
handleRequestCaptCode()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendMsg = () => {
|
const sendMsg = (data) => {
|
||||||
if (!canSend.value) {
|
if (!canSend.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
canSend.value = false
|
canSend.value = false
|
||||||
httpPost('/api/sms/code', {receiver: props.receiver, key: captKey.value, dots: dots.value}).then(() => {
|
httpPost('/api/sms/code', {receiver: props.receiver, key: data.key, dots: data.dots, x:data.x}).then(() => {
|
||||||
showMessageOK('验证码发送成功')
|
showMessageOK('验证码发送成功')
|
||||||
let time = 120
|
let time = 120
|
||||||
btnText.value = time
|
btnText.value = time
|
||||||
@ -132,52 +57,13 @@ const sendMsg = () => {
|
|||||||
showMessageError('验证码发送失败:' + e.message)
|
showMessageError('验证码发送失败:' + e.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 滑动验证码
|
|
||||||
const bgImg = ref('')
|
|
||||||
const bkImg = ref('')
|
|
||||||
const result = ref(0)
|
|
||||||
|
|
||||||
const getSlideCaptcha = () => {
|
|
||||||
result.value = 0
|
|
||||||
httpGet("/api/captcha/slide/get").then(res => {
|
|
||||||
bkImg.value = res.data.bkImg
|
|
||||||
bgImg.value = res.data.bgImg
|
|
||||||
captKey.value = res.data.key
|
|
||||||
}).catch(e => {
|
|
||||||
showMessageError('获取人机验证数据失败:' + e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSlideConfirm = (x) => {
|
|
||||||
httpPost("/api/captcha/slide/check", {
|
|
||||||
key: captKey.value,
|
|
||||||
x: x
|
|
||||||
}).then(() => {
|
|
||||||
result.value = 1
|
|
||||||
showCaptcha.value = false
|
|
||||||
sendMsg()
|
|
||||||
}).catch(() => {
|
|
||||||
result.value = 2
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus" scoped>
|
||||||
|
|
||||||
.captcha-box {
|
.send-verify-code {
|
||||||
.send-btn {
|
.send-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-dialog {
|
|
||||||
.el-dialog__header {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-dialog__body {
|
|
||||||
padding 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
Loading…
Reference in New Issue
Block a user