style(frontend): 优化 Components 代码风格和结构

- 统一移除语句末尾分号,规范代码格式
- 优化组件类型定义和 props 声明
- 改进组件文档和示例代码
- 提升代码可读性和一致性
This commit is contained in:
ianshaw
2025-12-25 08:40:12 -08:00
parent 1ac8b1f03e
commit 5deef27e1d
38 changed files with 2582 additions and 1485 deletions

View File

@@ -5,158 +5,164 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { ref, onMounted, onUnmounted, watch } from 'vue'
interface TurnstileRenderOptions {
sitekey: string;
callback: (token: string) => void;
'expired-callback'?: () => void;
'error-callback'?: () => void;
theme?: 'light' | 'dark' | 'auto';
size?: 'normal' | 'compact' | 'flexible';
sitekey: string
callback: (token: string) => void
'expired-callback'?: () => void
'error-callback'?: () => void
theme?: 'light' | 'dark' | 'auto'
size?: 'normal' | 'compact' | 'flexible'
}
interface TurnstileAPI {
render: (container: HTMLElement, options: TurnstileRenderOptions) => string;
reset: (widgetId?: string) => void;
remove: (widgetId?: string) => void;
render: (container: HTMLElement, options: TurnstileRenderOptions) => string
reset: (widgetId?: string) => void
remove: (widgetId?: string) => void
}
declare global {
interface Window {
turnstile?: TurnstileAPI;
onTurnstileLoad?: () => void;
turnstile?: TurnstileAPI
onTurnstileLoad?: () => void
}
}
const props = withDefaults(defineProps<{
siteKey: string;
theme?: 'light' | 'dark' | 'auto';
size?: 'normal' | 'compact' | 'flexible';
}>(), {
theme: 'auto',
size: 'flexible',
});
const props = withDefaults(
defineProps<{
siteKey: string
theme?: 'light' | 'dark' | 'auto'
size?: 'normal' | 'compact' | 'flexible'
}>(),
{
theme: 'auto',
size: 'flexible'
}
)
const emit = defineEmits<{
(e: 'verify', token: string): void;
(e: 'expire'): void;
(e: 'error'): void;
}>();
(e: 'verify', token: string): void
(e: 'expire'): void
(e: 'error'): void
}>()
const containerRef = ref<HTMLElement | null>(null);
const widgetId = ref<string | null>(null);
const scriptLoaded = ref(false);
const containerRef = ref<HTMLElement | null>(null)
const widgetId = ref<string | null>(null)
const scriptLoaded = ref(false)
const loadScript = (): Promise<void> => {
return new Promise((resolve, reject) => {
if (window.turnstile) {
scriptLoaded.value = true;
resolve();
return;
scriptLoaded.value = true
resolve()
return
}
// Check if script is already loading
const existingScript = document.querySelector('script[src*="turnstile"]');
const existingScript = document.querySelector('script[src*="turnstile"]')
if (existingScript) {
window.onTurnstileLoad = () => {
scriptLoaded.value = true;
resolve();
};
return;
scriptLoaded.value = true
resolve()
}
return
}
const script = document.createElement('script');
script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onTurnstileLoad';
script.async = true;
script.defer = true;
const script = document.createElement('script')
script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onTurnstileLoad'
script.async = true
script.defer = true
window.onTurnstileLoad = () => {
scriptLoaded.value = true;
resolve();
};
scriptLoaded.value = true
resolve()
}
script.onerror = () => {
reject(new Error('Failed to load Turnstile script'));
};
reject(new Error('Failed to load Turnstile script'))
}
document.head.appendChild(script);
});
};
document.head.appendChild(script)
})
}
const renderWidget = () => {
if (!window.turnstile || !containerRef.value || !props.siteKey) {
return;
return
}
// Remove existing widget if any
if (widgetId.value) {
try {
window.turnstile.remove(widgetId.value);
window.turnstile.remove(widgetId.value)
} catch {
// Ignore errors when removing
}
widgetId.value = null;
widgetId.value = null
}
// Clear container
containerRef.value.innerHTML = '';
containerRef.value.innerHTML = ''
widgetId.value = window.turnstile.render(containerRef.value, {
sitekey: props.siteKey,
callback: (token: string) => {
emit('verify', token);
emit('verify', token)
},
'expired-callback': () => {
emit('expire');
emit('expire')
},
'error-callback': () => {
emit('error');
emit('error')
},
theme: props.theme,
size: props.size,
});
};
size: props.size
})
}
const reset = () => {
if (window.turnstile && widgetId.value) {
window.turnstile.reset(widgetId.value);
window.turnstile.reset(widgetId.value)
}
};
}
// Expose reset method to parent
defineExpose({ reset });
defineExpose({ reset })
onMounted(async () => {
if (!props.siteKey) {
return;
return
}
try {
await loadScript();
renderWidget();
await loadScript()
renderWidget()
} catch (error) {
console.error('Failed to initialize Turnstile:', error);
emit('error');
console.error('Failed to initialize Turnstile:', error)
emit('error')
}
});
})
onUnmounted(() => {
if (window.turnstile && widgetId.value) {
try {
window.turnstile.remove(widgetId.value);
window.turnstile.remove(widgetId.value)
} catch {
// Ignore errors when removing
}
}
});
})
// Re-render when siteKey changes
watch(() => props.siteKey, (newKey) => {
if (newKey && scriptLoaded.value) {
renderWidget();
watch(
() => props.siteKey,
(newKey) => {
if (newKey && scriptLoaded.value) {
renderWidget()
}
}
});
)
</script>
<style scoped>