feat: add ReCaptchaV2 & Turnstile

This commit is contained in:
Tony Yang
2025-04-16 16:35:24 +08:00
parent 9ac3339557
commit 037ccb5781
23 changed files with 372 additions and 176 deletions
+109
View File
@@ -0,0 +1,109 @@
<script setup>
import { defineEmits, defineExpose, defineProps, watch, ref, onMounted } from 'vue';
import VueHcaptcha from '@hcaptcha/vue3-hcaptcha';
import { RecaptchaV2, useRecaptcha } from "vue3-recaptcha-v2";
import VueTurnstile from 'vue-turnstile';
const { handleReset: handleRecaptchaReset } = useRecaptcha();
const props = defineProps({
isVertical: {
type: Boolean,
default: false,
},
hcaptchaSitekey: {
type: String,
required: true,
},
recaptchaSitekey: {
type: String,
required: true,
},
turnstileSitekey: {
type: String,
required: true,
},
});
const emit = defineEmits(['captchaVerified']);
const hcaptchaResponse = ref('');
const recaptchaResponse = ref('');
const turnstileToken = ref('');
const hcaptchaRef = ref(null);
const recaptchaWidgetId = ref('');
const turnstileRef = ref(null);
const handleHcaptchaVerify = (token) => {
hcaptchaResponse.value = token;
};
const handleHcaptchaExpired = () => {
hcaptchaResponse.value = '';
};
const handleRecaptchaLoad = (response) => {
recaptchaResponse.value = response;
}
const handleRecaptchaExpired = () => {
recaptchaResponse.value = '';
};
const handleWidgetId = (widgetId) => {
recaptchaWidgetId.value = widgetId;
};
watch(
() => [hcaptchaResponse.value, recaptchaResponse.value, turnstileToken.value],
([hcaptcha, recaptcha, turnstile]) => {
if (hcaptcha && recaptcha && turnstile) {
// All tokens are available
emit('captchaVerified', {
hCaptchaResponse: hcaptcha,
recaptchaResponse: recaptcha,
turnstileResponse: turnstile
});
}
}
);
const reset = () => {
hcaptchaRef.value?.reset();
if (recaptchaWidgetId.value) handleRecaptchaReset(recaptchaWidgetId.value);
turnstileRef.value?.reset();
};
defineExpose({
reset,
});
onMounted(() => {
console.log(props);
});
</script>
<template>
<div class="ts-content is-horizontally-fitted">
<div class="ts-wrap is-center-aligned is-middle-aligned" :class="{ 'is-vertical': props.isVertical }">
<VueHcaptcha
ref="hcaptchaRef"
:sitekey="props.hcaptchaSitekey"
@verify="handleHcaptchaVerify"
@expired="handleHcaptchaExpired"
@reset="handleHcaptchaExpired"
:reCaptchaCompat="false"
:theme="$darkMode ? 'dark' : 'light'"
/>
<RecaptchaV2
:sitekey="props.recaptchaSitekey"
@widgetId="handleWidgetId"
@expired-callback="handleRecaptchaExpired"
@load-callback="handleRecaptchaLoad"
:theme="$darkMode ? 'dark' : 'light'"
/>
<VueTurnstile :site-key="props.turnstileSitekey" ref="turnstileRef" v-model="turnstileToken" />
</div>
</div>
</template>