验证码接口适配调整
This commit is contained in:
parent
f9b0b05c7e
commit
25e80d73ae
4
components.d.ts
vendored
4
components.d.ts
vendored
@ -11,6 +11,8 @@ declare module '@vue/runtime-core' {
|
|||||||
ElAside: typeof import('element-plus/es')['ElAside']
|
ElAside: typeof import('element-plus/es')['ElAside']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
ElCard: typeof import('element-plus/es')['ElCard']
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
|
ElCheckboxButton: typeof import('element-plus/es')['ElCheckboxButton']
|
||||||
|
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
@ -34,8 +36,6 @@ declare module '@vue/runtime-core' {
|
|||||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||||
ElOption: typeof import('element-plus/es')['ElOption']
|
ElOption: typeof import('element-plus/es')['ElOption']
|
||||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
|
||||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
|
||||||
ElRow: typeof import('element-plus/es')['ElRow']
|
ElRow: typeof import('element-plus/es')['ElRow']
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="captchaBoxRef" class="captcha-panel"></div>
|
<div ref="captchaBoxRef"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, onBeforeUnmount, ref } from 'vue'
|
import { nextTick, onBeforeUnmount, ref } from 'vue'
|
||||||
import {
|
import {
|
||||||
createTianAiCaptcha,
|
createTianAiCaptcha,
|
||||||
type TianAiCaptchaConfig,
|
type TianAiCaptchaConfig,
|
||||||
@ -13,17 +13,15 @@ import {
|
|||||||
} from '@/vendor/tianai-captcha'
|
} from '@/vendor/tianai-captcha'
|
||||||
|
|
||||||
type CaptchaMode = 'data' | 'validate'
|
type CaptchaMode = 'data' | 'validate'
|
||||||
type CaptchaType = 'RANDOM' | 'SLIDER' | 'ROTATE' | 'CONCAT' | 'WORD_IMAGE_CLICK'
|
type CaptchaType = 'SLIDER' | 'ROTATE' | 'CONCAT' | 'WORD_IMAGE_CLICK'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
mode?: CaptchaMode
|
mode?: CaptchaMode
|
||||||
type?: CaptchaType
|
types?: CaptchaType[]
|
||||||
validCaptchaUrl?: string
|
|
||||||
requestHeaders?: Record<string, string>
|
|
||||||
styleConfig?: TianAiCaptchaStyle
|
styleConfig?: TianAiCaptchaStyle
|
||||||
}>(), {
|
}>(), {
|
||||||
mode: 'data',
|
mode: 'data',
|
||||||
type: 'RANDOM'
|
types: () => []
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@ -38,17 +36,8 @@ const emit = defineEmits<{
|
|||||||
const captchaBoxRef = ref<HTMLElement | null>(null)
|
const captchaBoxRef = ref<HTMLElement | null>(null)
|
||||||
const captchaInstance = ref<TianAiCaptchaInstance | null>(null)
|
const captchaInstance = ref<TianAiCaptchaInstance | null>(null)
|
||||||
const active = ref(false)
|
const active = ref(false)
|
||||||
const requestCaptchaBaseUrl = buildCaptchaApiUrl('/captcha/gen')
|
const requestCaptchaDataUrl = buildCaptchaApiUrl('/captcha/generate')
|
||||||
const resolvedValidCaptchaUrl = computed(() => {
|
const validCaptchaUrl = buildCaptchaApiUrl('/captcha/check')
|
||||||
return props.validCaptchaUrl || buildCaptchaApiUrl('/captcha/check')
|
|
||||||
})
|
|
||||||
const requestCaptchaDataUrl = computed(() => {
|
|
||||||
if (props.type === 'RANDOM') {
|
|
||||||
return requestCaptchaBaseUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${requestCaptchaBaseUrl}?type=${encodeURIComponent(props.type)}`
|
|
||||||
})
|
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
@ -62,8 +51,8 @@ async function init() {
|
|||||||
|
|
||||||
const config: TianAiCaptchaConfig = {
|
const config: TianAiCaptchaConfig = {
|
||||||
bindEl: captchaBoxRef.value,
|
bindEl: captchaBoxRef.value,
|
||||||
requestCaptchaDataUrl: requestCaptchaDataUrl.value,
|
requestCaptchaDataUrl,
|
||||||
requestHeaders: props.requestHeaders,
|
types: props.types,
|
||||||
btnRefreshFun: (_el, tac) => {
|
btnRefreshFun: (_el, tac) => {
|
||||||
tac.reloadCaptcha()
|
tac.reloadCaptcha()
|
||||||
},
|
},
|
||||||
@ -74,7 +63,7 @@ async function init() {
|
|||||||
}
|
}
|
||||||
switch (props.mode) {
|
switch (props.mode) {
|
||||||
case 'validate':
|
case 'validate':
|
||||||
config.validCaptchaUrl = resolvedValidCaptchaUrl.value
|
config.validCaptchaUrl = validCaptchaUrl
|
||||||
config.validSuccess = res => emit('valid-success', res)
|
config.validSuccess = res => emit('valid-success', res)
|
||||||
config.validFail = res => emit('valid-fail', res)
|
config.validFail = res => emit('valid-fail', res)
|
||||||
break
|
break
|
||||||
|
|||||||
@ -14,6 +14,7 @@ class CaptchaConfig {
|
|||||||
this.bindEl = args.bindEl;
|
this.bindEl = args.bindEl;
|
||||||
this.domBindEl = Dom(args.bindEl);
|
this.domBindEl = Dom(args.bindEl);
|
||||||
this.requestCaptchaDataUrl = args.requestCaptchaDataUrl;
|
this.requestCaptchaDataUrl = args.requestCaptchaDataUrl;
|
||||||
|
this.types = Array.isArray(args.types) ? args.types.filter(Boolean) : [];
|
||||||
this.validCaptchaUrl = args.validCaptchaUrl;
|
this.validCaptchaUrl = args.validCaptchaUrl;
|
||||||
if (args.validSuccess) {
|
if (args.validSuccess) {
|
||||||
this.validSuccess = args.validSuccess;
|
this.validSuccess = args.validSuccess;
|
||||||
@ -64,7 +65,7 @@ class CaptchaConfig {
|
|||||||
requestCaptchaData() {
|
requestCaptchaData() {
|
||||||
const requestParam = {};
|
const requestParam = {};
|
||||||
requestParam.headers = this.requestHeaders || {};
|
requestParam.headers = this.requestHeaders || {};
|
||||||
requestParam.data = {};
|
requestParam.data = { types: this.types };
|
||||||
// 设置默认值
|
// 设置默认值
|
||||||
requestParam.headers["Content-Type"] = "application/json;charset=UTF-8";
|
requestParam.headers["Content-Type"] = "application/json;charset=UTF-8";
|
||||||
requestParam.method = "POST";
|
requestParam.method = "POST";
|
||||||
|
|||||||
2
src/vendor/tianai-captcha/index.ts
vendored
2
src/vendor/tianai-captcha/index.ts
vendored
@ -17,8 +17,8 @@ export interface TianAiCaptchaStyle {
|
|||||||
export interface TianAiCaptchaConfig {
|
export interface TianAiCaptchaConfig {
|
||||||
bindEl: TianAiCaptchaBindTarget
|
bindEl: TianAiCaptchaBindTarget
|
||||||
requestCaptchaDataUrl: string
|
requestCaptchaDataUrl: string
|
||||||
|
types?: string[]
|
||||||
validCaptchaUrl?: string
|
validCaptchaUrl?: string
|
||||||
requestHeaders?: Record<string, string>
|
|
||||||
timeToTimestamp?: boolean
|
timeToTimestamp?: boolean
|
||||||
validSuccess?: (res: any, captcha: any, tac: TianAiCaptchaInstance) => void
|
validSuccess?: (res: any, captcha: any, tac: TianAiCaptchaInstance) => void
|
||||||
validFail?: (res: any, captcha: any, tac: TianAiCaptchaInstance) => void
|
validFail?: (res: any, captcha: any, tac: TianAiCaptchaInstance) => void
|
||||||
|
|||||||
@ -10,11 +10,19 @@
|
|||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
<template #header>验证码类型</template>
|
<template #header>验证码类型</template>
|
||||||
|
|
||||||
<el-radio-group v-model="selectedType" size="large">
|
<p class="type-hint">
|
||||||
<el-radio-button v-for="item in captchaTypeOptions" :key="item.value" :label="item.value">
|
可同时勾选多种类型;不选时将按随机类型请求。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<el-checkbox-group v-model="selectedTypes" size="large">
|
||||||
|
<el-checkbox-button v-for="item in captchaTypeOptions" :key="item.value" :value="item.value">
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</el-radio-button>
|
</el-checkbox-button>
|
||||||
</el-radio-group>
|
</el-checkbox-group>
|
||||||
|
|
||||||
|
<div class="type-actions">
|
||||||
|
<el-button link type="primary" @click="clearTypes" :disabled="selectedTypes.length === 0">恢复随机</el-button>
|
||||||
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card class="preview-card" shadow="never">
|
<el-card class="preview-card" shadow="never">
|
||||||
@ -23,7 +31,7 @@
|
|||||||
ref="captchaRef"
|
ref="captchaRef"
|
||||||
class="captcha-box"
|
class="captcha-box"
|
||||||
mode="validate"
|
mode="validate"
|
||||||
:type="selectedType"
|
:types="selectedTypes"
|
||||||
@close="handleCaptchaClose"
|
@close="handleCaptchaClose"
|
||||||
@state-change="handleCaptchaStateChange"
|
@state-change="handleCaptchaStateChange"
|
||||||
@valid-fail="handleValidFail"
|
@valid-fail="handleValidFail"
|
||||||
@ -38,7 +46,7 @@
|
|||||||
import { onBeforeUnmount, ref } from 'vue'
|
import { onBeforeUnmount, ref } from 'vue'
|
||||||
import CaptchaPanel from '@/components/CaptchaPanel.vue'
|
import CaptchaPanel from '@/components/CaptchaPanel.vue'
|
||||||
|
|
||||||
type CaptchaType = 'RANDOM' | 'SLIDER' | 'ROTATE' | 'CONCAT' | 'WORD_IMAGE_CLICK'
|
type CaptchaType = 'SLIDER' | 'ROTATE' | 'CONCAT' | 'WORD_IMAGE_CLICK'
|
||||||
|
|
||||||
type CaptchaPanelRef = {
|
type CaptchaPanelRef = {
|
||||||
destroy: () => void
|
destroy: () => void
|
||||||
@ -47,7 +55,6 @@ type CaptchaPanelRef = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const captchaTypeOptions: Array<{ label: string, value: CaptchaType }> = [
|
const captchaTypeOptions: Array<{ label: string, value: CaptchaType }> = [
|
||||||
{ label: '随机', value: 'RANDOM' },
|
|
||||||
{ label: '滑块拼图', value: 'SLIDER' },
|
{ label: '滑块拼图', value: 'SLIDER' },
|
||||||
{ label: '旋转', value: 'ROTATE' },
|
{ label: '旋转', value: 'ROTATE' },
|
||||||
{ label: '拼接', value: 'CONCAT' },
|
{ label: '拼接', value: 'CONCAT' },
|
||||||
@ -56,17 +63,21 @@ const captchaTypeOptions: Array<{ label: string, value: CaptchaType }> = [
|
|||||||
|
|
||||||
const captchaRef = ref<CaptchaPanelRef | null>(null)
|
const captchaRef = ref<CaptchaPanelRef | null>(null)
|
||||||
const captchaActive = ref(false)
|
const captchaActive = ref(false)
|
||||||
const selectedType = ref<CaptchaType>('RANDOM')
|
const selectedTypes = ref<CaptchaType[]>([])
|
||||||
|
|
||||||
async function startCaptcha() {
|
async function startCaptcha() {
|
||||||
console.info('[captcha sandbox] init captcha', {
|
console.info('[captcha sandbox] init captcha', {
|
||||||
type: selectedType.value,
|
types: selectedTypes.value,
|
||||||
mode: 'validate'
|
mode: 'validate'
|
||||||
})
|
})
|
||||||
|
|
||||||
await captchaRef.value?.init()
|
await captchaRef.value?.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearTypes() {
|
||||||
|
selectedTypes.value = []
|
||||||
|
}
|
||||||
|
|
||||||
function reloadCaptcha() {
|
function reloadCaptcha() {
|
||||||
if (!captchaActive.value) return
|
if (!captchaActive.value) return
|
||||||
console.info('[captcha sandbox] manual reload')
|
console.info('[captcha sandbox] manual reload')
|
||||||
@ -107,6 +118,16 @@ onBeforeUnmount(() => captchaRef.value?.destroy())
|
|||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.type-hint {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
color: #606266;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-actions {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.sandbox-grid {
|
.sandbox-grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user