update
This commit is contained in:
@@ -12,8 +12,8 @@ import './style.css';
|
|||||||
import '@fortawesome/fontawesome-free/css/all.min.css';
|
import '@fortawesome/fontawesome-free/css/all.min.css';
|
||||||
// 导入 splitpanes CSS
|
// 导入 splitpanes CSS
|
||||||
import 'splitpanes/dist/splitpanes.css';
|
import 'splitpanes/dist/splitpanes.css';
|
||||||
// 导入 vue-recaptcha (用于 v2)
|
// 恢复导入 reCAPTCHA v3
|
||||||
import VueRecaptchaPlugin from 'vue-recaptcha';
|
import { VueReCaptcha } from 'vue-recaptcha-v3';
|
||||||
|
|
||||||
const pinia = createPinia(); // 创建 Pinia 实例
|
const pinia = createPinia(); // 创建 Pinia 实例
|
||||||
pinia.use(piniaPluginPersistedstate); // 使用持久化插件
|
pinia.use(piniaPluginPersistedstate); // 使用持久化插件
|
||||||
@@ -24,8 +24,14 @@ app.use(pinia); // 使用配置好的 Pinia 实例
|
|||||||
// 注意:在状态初始化完成前,暂时不 use(router)
|
// 注意:在状态初始化完成前,暂时不 use(router)
|
||||||
app.use(i18n); // 使用 i18n
|
app.use(i18n); // 使用 i18n
|
||||||
|
|
||||||
// 注册 vue-recaptcha 插件,传递一个空选项对象
|
// 恢复初始化 reCAPTCHA v3
|
||||||
app.use(VueRecaptchaPlugin, {});
|
// 重要提示:请将 'YOUR_RECAPTCHA_V3_SITE_KEY' 替换为您从 Google reCAPTCHA 获取的实际 Site Key
|
||||||
|
app.use(VueReCaptcha, {
|
||||||
|
siteKey: 'YOUR_RECAPTCHA_V3_SITE_KEY', // <-- 在此处替换您的 Site Key
|
||||||
|
loaderOptions: {
|
||||||
|
autoHideBadge: true // 可选:自动隐藏 reCAPTCHA 徽章
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// --- 应用初始化逻辑 ---
|
// --- 应用初始化逻辑 ---
|
||||||
// 使用 async IIFE 来允许顶层 await
|
// 使用 async IIFE 来允许顶层 await
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import { storeToRefs } from 'pinia';
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useAuthStore } from '../stores/auth.store';
|
import { useAuthStore } from '../stores/auth.store';
|
||||||
import VueHcaptcha from '@hcaptcha/vue3-hcaptcha'; // <-- Import hCaptcha component
|
import VueHcaptcha from '@hcaptcha/vue3-hcaptcha'; // <-- Import hCaptcha component
|
||||||
// import { useReCaptcha } from 'vue-recaptcha-v3'; // <-- v3 hook, not needed for v2 widget
|
import { useReCaptcha } from 'vue-recaptcha-v3'; // <-- Restore reCAPTCHA v3 hook
|
||||||
import TheVueRecaptcha from 'vue-recaptcha'; // <-- Use a different name for import
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
@@ -21,9 +20,10 @@ const twoFactorToken = ref(''); // 用于存储 2FA 验证码
|
|||||||
const rememberMe = ref(false); // 新增:记住我状态,默认为 false
|
const rememberMe = ref(false); // 新增:记住我状态,默认为 false
|
||||||
const captchaToken = ref<string | null>(null); // NEW: Store CAPTCHA token
|
const captchaToken = ref<string | null>(null); // NEW: Store CAPTCHA token
|
||||||
const captchaError = ref<string | null>(null); // NEW: Store CAPTCHA specific error
|
const captchaError = ref<string | null>(null); // NEW: Store CAPTCHA specific error
|
||||||
const hcaptchaWidget = ref<InstanceType<typeof VueHcaptcha> | null>(null); // Ref for hCaptcha component instance
|
const hcaptchaWidget = ref<InstanceType<typeof VueHcaptcha> | null>(null); // NEW: Ref for hCaptcha component instance
|
||||||
// const recaptchaInstance = useReCaptcha(); // v3 instance, not needed for v2 widget
|
|
||||||
// No specific ref needed for VueRecaptcha v2 component usually, events handle token
|
// --- reCAPTCHA v3 Initialization ---
|
||||||
|
const recaptchaInstance = useReCaptcha(); // Restore v3 instance
|
||||||
|
|
||||||
|
|
||||||
// --- CAPTCHA Event Handlers ---
|
// --- CAPTCHA Event Handlers ---
|
||||||
@@ -46,7 +46,7 @@ const resetCaptchaWidget = () => {
|
|||||||
captchaToken.value = null;
|
captchaToken.value = null;
|
||||||
// Reset hCaptcha if it exists
|
// Reset hCaptcha if it exists
|
||||||
hcaptchaWidget.value?.reset();
|
hcaptchaWidget.value?.reset();
|
||||||
// vue-recaptcha v2 component might reset automatically or not have an explicit method easily accessible via ref
|
// reCAPTCHA v3 doesn't typically need explicit reset in the same way
|
||||||
};
|
};
|
||||||
// --- End CAPTCHA Event Handlers ---
|
// --- End CAPTCHA Event Handlers ---
|
||||||
|
|
||||||
@@ -56,11 +56,30 @@ const handleSubmit = async () => {
|
|||||||
captchaError.value = null; // Clear previous CAPTCHA error
|
captchaError.value = null; // Clear previous CAPTCHA error
|
||||||
|
|
||||||
// --- CAPTCHA Execution & Check ---
|
// --- CAPTCHA Execution & Check ---
|
||||||
// For v2/hCaptcha, the token should already be set by the component's @verify event
|
|
||||||
if (publicCaptchaConfig.value?.enabled && !loginRequires2FA.value) {
|
if (publicCaptchaConfig.value?.enabled && !loginRequires2FA.value) {
|
||||||
// No programmatic execution needed for v2/hCaptcha here.
|
// Restore If reCAPTCHA v3, execute it now to get the token
|
||||||
|
if (publicCaptchaConfig.value.provider === 'recaptcha') {
|
||||||
|
// Check if instance and methods are available
|
||||||
|
if (recaptchaInstance?.recaptchaLoaded && recaptchaInstance?.executeRecaptcha) {
|
||||||
|
try {
|
||||||
|
await recaptchaInstance.recaptchaLoaded(); // Ensure library is loaded
|
||||||
|
const token = await recaptchaInstance.executeRecaptcha('login'); // Execute with action 'login'
|
||||||
|
console.log('reCAPTCHA v3 token obtained:', token);
|
||||||
|
captchaToken.value = token; // Store the obtained token
|
||||||
|
} catch (reError: any) {
|
||||||
|
console.error('reCAPTCHA v3 execution failed:', reError);
|
||||||
|
captchaError.value = t('login.error.captchaLoadFailed');
|
||||||
|
return; // Stop submission if reCAPTCHA execution fails
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle case where reCAPTCHA is not ready/initialized
|
||||||
|
console.error('reCAPTCHA v3 not initialized or ready.');
|
||||||
|
captchaError.value = t('login.error.captchaLoadFailed'); // Or a more specific error
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if token exists (obtained via @verify callback from either component)
|
// Check if token exists (for both hCaptcha and reCAPTCHA)
|
||||||
if (!captchaToken.value) {
|
if (!captchaToken.value) {
|
||||||
captchaError.value = t('login.error.captchaRequired');
|
captchaError.value = t('login.error.captchaRequired');
|
||||||
return; // Stop submission if CAPTCHA is required but not completed/obtained
|
return; // Stop submission if CAPTCHA is required but not completed/obtained
|
||||||
@@ -163,20 +182,12 @@ onMounted(() => {
|
|||||||
theme="auto"
|
theme="auto"
|
||||||
></VueHcaptcha>
|
></VueHcaptcha>
|
||||||
</div>
|
</div>
|
||||||
<!-- Google reCAPTCHA v2 Component -->
|
<!-- reCAPTCHA v3 Info (usually invisible) -->
|
||||||
<div v-else-if="publicCaptchaConfig?.provider === 'recaptcha' && publicCaptchaConfig.recaptchaSiteKey">
|
<div v-else-if="publicCaptchaConfig?.provider === 'recaptcha'">
|
||||||
<!-- @ts-ignore - Bypassing type check due to potential library type definition issues -->
|
<p class="text-xs text-text-secondary italic">
|
||||||
<TheVueRecaptcha
|
{{ t('login.recaptchaV3Notice') }}
|
||||||
:sitekey="publicCaptchaConfig.recaptchaSiteKey"
|
|
||||||
@verify="handleCaptchaVerified"
|
|
||||||
@expired="handleCaptchaExpired"
|
|
||||||
@error="handleCaptchaError"
|
|
||||||
theme="light"
|
|
||||||
size="normal"
|
|
||||||
></TheVueRecaptcha>
|
|
||||||
<p class="text-xs text-text-secondary italic mt-1">
|
|
||||||
{{ t('login.recaptchaV3Notice') }} <!-- Keep the notice -->
|
|
||||||
</p>
|
</p>
|
||||||
|
<!-- v3 is typically invisible, token obtained programmatically on submit -->
|
||||||
</div>
|
</div>
|
||||||
<!-- CAPTCHA Error Message -->
|
<!-- CAPTCHA Error Message -->
|
||||||
<div v-if="captchaError" class="text-error text-sm">
|
<div v-if="captchaError" class="text-error text-sm">
|
||||||
|
|||||||
Reference in New Issue
Block a user