Files
FileHost/app/components/TurnstileCaptcha.vue
O96u 430c4de0f4
Publish Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Canceled after 0s
Publish Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Canceled after 0s
Publish Docker image / merge (push) Canceled after 0s
ci / ci (22, ubuntu-latest) (push) Canceled after 0s
GitHub Release / release (push) Canceled after 0s
release: v1.2.7 — 登录人机验证(滑块 / Turnstile / Cap)
2026-09-03 13:53:43 +08:00

121 lines
2.5 KiB
Vue

<script setup lang="ts">
const props = defineProps<{
siteKey: string
}>()
const emit = defineEmits<{
verified: [payload: { turnstileToken: string }]
cleared: []
}>()
interface TurnstileRenderOptions {
'sitekey': string
'action'?: string
'callback'?: (token: string) => void
'expired-callback'?: () => void
'error-callback'?: () => void
}
interface TurnstileApi {
render: (container: HTMLElement, options: TurnstileRenderOptions) => string
reset: (widgetId?: string) => void
remove: (widgetId: string) => void
}
declare global {
interface Window {
turnstile?: TurnstileApi
onTurnstileLoad?: () => void
}
}
const containerRef = ref<HTMLElement | null>(null)
const widgetId = ref<string | null>(null)
const scriptLoaded = ref(false)
function onVerified(token: string) {
if (token) {
emit('verified', { turnstileToken: token })
}
}
function onCleared() {
emit('cleared')
}
function renderWidget() {
if (!containerRef.value || !window.turnstile || !props.siteKey) return
if (widgetId.value) {
window.turnstile.remove(widgetId.value)
widgetId.value = null
}
widgetId.value = window.turnstile.render(containerRef.value, {
'sitekey': props.siteKey,
'action': 'login',
'callback': onVerified,
'expired-callback': onCleared,
'error-callback': onCleared
})
}
function tryRenderWidget() {
if (scriptLoaded.value) {
renderWidget()
}
}
function loadScript() {
if (window.turnstile) {
scriptLoaded.value = true
tryRenderWidget()
return
}
window.onTurnstileLoad = () => {
scriptLoaded.value = true
tryRenderWidget()
}
const existing = document.querySelector('script[data-turnstile]')
if (existing) {
if (window.turnstile) {
scriptLoaded.value = true
tryRenderWidget()
}
return
}
const script = document.createElement('script')
script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=onTurnstileLoad'
script.async = true
script.defer = true
script.dataset.turnstile = 'true'
document.head.appendChild(script)
}
function reset() {
if (window.turnstile && widgetId.value) {
window.turnstile.reset(widgetId.value)
}
onCleared()
}
onMounted(async () => {
loadScript()
await nextTick()
tryRenderWidget()
})
watch([containerRef, () => props.siteKey, scriptLoaded], () => {
tryRenderWidget()
}, { flush: 'post' })
defineExpose({ reset })
</script>
<template>
<ClientOnly>
<div
ref="containerRef"
class="flex min-h-16 items-center justify-center"
/>
</ClientOnly>
</template>