|
@@ -1,25 +1,43 @@
|
|
|
<template>
|
|
|
- <el-input text maxlength="4" placeholder="请输入验证码" clearable autocomplete="off" v-bind="$attrs">
|
|
|
- <template #prefix>
|
|
|
- <el-icon class="el-input__icon"><ele-Message /></el-icon>
|
|
|
- </template>
|
|
|
- <template #suffix>
|
|
|
- <el-link type="primary" :underline="false" v-show="state.status !== 'countdown'" :disabled="state.status === 'countdown'" @click="onClick">{{
|
|
|
- state.status === 'ready' ? state.startText : state.endText
|
|
|
- }}</el-link>
|
|
|
- <el-countdown
|
|
|
- v-show="state.status === 'countdown'"
|
|
|
- :format="state.changeText"
|
|
|
- :value="state.countdown"
|
|
|
- value-style="font-size:var(--el-font-size-base);color:var(--el-color-primary)"
|
|
|
- @change="onChange"
|
|
|
+ <div class="w100">
|
|
|
+ <el-input text maxlength="4" placeholder="请输入验证码" autocomplete="off" v-bind="$attrs">
|
|
|
+ <template #prefix>
|
|
|
+ <el-icon class="el-input__icon"><ele-Message /></el-icon>
|
|
|
+ </template>
|
|
|
+ <template #suffix>
|
|
|
+ <el-link type="primary" :underline="false" v-show="state.status !== 'countdown'" :disabled="state.status === 'countdown'" @click="onClick">{{
|
|
|
+ state.status === 'ready' ? state.startText : state.endText
|
|
|
+ }}</el-link>
|
|
|
+ <el-countdown
|
|
|
+ v-show="state.status === 'countdown'"
|
|
|
+ :format="state.changeText"
|
|
|
+ :value="state.countdown"
|
|
|
+ value-style="font-size:var(--el-font-size-base);color:var(--el-color-primary)"
|
|
|
+ @change="onChange"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
+ <el-dialog ref="dialogRef" v-model="state.showDialog" class="my-captcha" title="请完成安全验证" draggable append-to-body width="380px">
|
|
|
+ <MySlideCaptcha
|
|
|
+ ref="slideCaptchaRef"
|
|
|
+ :fail-tip="state.failTip"
|
|
|
+ :success-tip="state.successTip"
|
|
|
+ width="100%"
|
|
|
+ height="auto"
|
|
|
+ @refresh="onRefresh"
|
|
|
+ @finish="onFinish"
|
|
|
/>
|
|
|
- </template>
|
|
|
- </el-input>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<script lang="ts" setup name="my-input-code">
|
|
|
-import { reactive } from 'vue'
|
|
|
+import { ref, reactive, defineAsyncComponent } from 'vue'
|
|
|
+import { CaptchaApi } from '/@/api/admin/Captcha'
|
|
|
+import { isMobile } from '/@/utils/test'
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
+
|
|
|
+const MySlideCaptcha = defineAsyncComponent(() => import('/@/components/my-slide-captcha/index.vue'))
|
|
|
|
|
|
const props = defineProps({
|
|
|
seconds: {
|
|
@@ -38,24 +56,99 @@ const props = defineProps({
|
|
|
type: String,
|
|
|
default: '重新获取',
|
|
|
},
|
|
|
+ mobile: {
|
|
|
+ type: String,
|
|
|
+ default: '',
|
|
|
+ },
|
|
|
+ validate: {
|
|
|
+ type: Function,
|
|
|
+ default: null,
|
|
|
+ },
|
|
|
})
|
|
|
|
|
|
+const slideCaptchaRef = ref()
|
|
|
+const dialogRef = ref()
|
|
|
+
|
|
|
const state = reactive({
|
|
|
status: 'ready',
|
|
|
startText: props.startText,
|
|
|
changeText: props.changeText,
|
|
|
endText: props.endText,
|
|
|
countdown: Date.now(),
|
|
|
+
|
|
|
+ showDialog: false,
|
|
|
+ requestId: '',
|
|
|
+ failTip: '',
|
|
|
+ successTip: '',
|
|
|
})
|
|
|
|
|
|
-const onClick = () => {
|
|
|
+const startCountdown = () => {
|
|
|
state.status = 'countdown'
|
|
|
state.countdown = Date.now() + (props.seconds + 1) * 1000
|
|
|
}
|
|
|
|
|
|
+const onClick = () => {
|
|
|
+ if (state.status !== 'countdown') {
|
|
|
+ if (props.validate) {
|
|
|
+ props.validate(onGetCode)
|
|
|
+ } else {
|
|
|
+ onGetCode()
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
const onChange = (value: number) => {
|
|
|
if (value < 1000) state.status = 'finish'
|
|
|
}
|
|
|
+
|
|
|
+//刷新滑块验证码
|
|
|
+const onRefresh = async () => {
|
|
|
+ slideCaptchaRef.value.startRequestGenerate()
|
|
|
+ const res = await new CaptchaApi().generate().catch(() => {
|
|
|
+ slideCaptchaRef.value.endRequestGenerate(null, null)
|
|
|
+ })
|
|
|
+ if (res?.success && res.data) {
|
|
|
+ state.requestId = res.data.id || ''
|
|
|
+ slideCaptchaRef.value.endRequestGenerate(res.data.backgroundImage, res.data.sliderImage)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+//验证滑块验证码
|
|
|
+const onFinish = async (data: any) => {
|
|
|
+ slideCaptchaRef.value.startRequestVerify()
|
|
|
+ const res = await new CaptchaApi().check(data, { id: state.requestId }).catch(() => {
|
|
|
+ state.failTip = '服务异常,请稍后重试'
|
|
|
+ slideCaptchaRef.value.endRequestVerify(false)
|
|
|
+ })
|
|
|
+ if (res?.success && res.data) {
|
|
|
+ let success = res.data.result === 0
|
|
|
+ state.failTip = res.data.result == 1 ? '验证未通过,拖动滑块将悬浮图像正确合并' : '验证超时, 请重新操作'
|
|
|
+ state.successTip = '验证通过'
|
|
|
+ slideCaptchaRef.value.endRequestVerify(success)
|
|
|
+ if (success) {
|
|
|
+ state.showDialog = false
|
|
|
+ startCountdown()
|
|
|
+ //发送短信验证码
|
|
|
+ } else {
|
|
|
+ setTimeout(() => {
|
|
|
+ onRefresh()
|
|
|
+ }, 1000)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+//获得验证码
|
|
|
+const onGetCode = () => {
|
|
|
+ //验证手机号
|
|
|
+ if (!isMobile(props.mobile)) {
|
|
|
+ ElMessage.warning({ message: '请输入正确的手机号码', grouping: true })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ state.showDialog = true
|
|
|
+ //刷新验证码
|
|
|
+ slideCaptchaRef.value?.handleRefresh()
|
|
|
+}
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
@@ -63,3 +156,11 @@ const onChange = (value: number) => {
|
|
|
font-size: var(--el-font-size-base);
|
|
|
}
|
|
|
</style>
|
|
|
+<style lang="scss">
|
|
|
+.my-captcha .el-dialog__body {
|
|
|
+ padding-top: 10px;
|
|
|
+}
|
|
|
+.my-captcha .captcha__bar {
|
|
|
+ border-color: var(--el-border-color) !important;
|
|
|
+}
|
|
|
+</style>
|