This commit is contained in:
Baobhan Sith
2025-04-26 00:09:31 +08:00
parent 41fa93e6e8
commit 3874df18d4
4 changed files with 35 additions and 153 deletions
+1 -1
View File
@@ -23,9 +23,9 @@
"vite-plugin-monaco-editor": "^1.1.0",
"vue": "^3.3.0",
"vue-i18n": "^9.14.4",
"vue-recaptcha": "^3.0.0-alpha.6",
"vue-recaptcha-v3": "^2.0.1",
"vue-router": "^4.5.0",
"vue3-recaptcha2": "^1.8.0",
"vuedraggable": "^4.1.0",
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0",
+2 -10
View File
@@ -12,8 +12,7 @@ import './style.css';
import '@fortawesome/fontawesome-free/css/all.min.css';
// 导入 splitpanes CSS
import 'splitpanes/dist/splitpanes.css';
// 恢复导入 reCAPTCHA v3
import { VueReCaptcha } from 'vue-recaptcha-v3';
const pinia = createPinia(); // 创建 Pinia 实例
pinia.use(piniaPluginPersistedstate); // 使用持久化插件
@@ -24,14 +23,7 @@ app.use(pinia); // 使用配置好的 Pinia 实例
// 注意:在状态初始化完成前,暂时不 use(router)
app.use(i18n); // 使用 i18n
// 恢复初始化 reCAPTCHA v3
// 重要提示:请将 '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
+25 -36
View File
@@ -3,8 +3,8 @@ import { reactive, ref, onMounted, computed } from 'vue'; // computed 已导入
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useAuthStore } from '../stores/auth.store';
import VueHcaptcha from '@hcaptcha/vue3-hcaptcha'; // <-- Import hCaptcha component
import { useReCaptcha } from 'vue-recaptcha-v3'; // <-- Restore reCAPTCHA v3 hook
import VueHcaptcha from '@hcaptcha/vue3-hcaptcha';
import VueRecaptcha from 'vue3-recaptcha2'; // 使用默认导入
const { t } = useI18n();
const authStore = useAuthStore();
@@ -21,9 +21,10 @@ const rememberMe = ref(false); // 新增:记住我状态,默认为 false
const captchaToken = ref<string | null>(null); // NEW: Store CAPTCHA token
const captchaError = ref<string | null>(null); // NEW: Store CAPTCHA specific error
const hcaptchaWidget = ref<InstanceType<typeof VueHcaptcha> | null>(null); // NEW: Ref for hCaptcha component instance
const recaptchaWidget = ref<InstanceType<typeof VueRecaptcha> | null>(null); // 更新 Ref 类型以匹配新导入
// --- reCAPTCHA v3 Initialization ---
const recaptchaInstance = useReCaptcha(); // Restore v3 instance
// const recaptchaInstance = useReCaptcha(); // 移除 v3 实例,因为我们将使用 v2 组件
// --- CAPTCHA Event Handlers ---
@@ -46,7 +47,8 @@ const resetCaptchaWidget = () => {
captchaToken.value = null;
// Reset hCaptcha if it exists
hcaptchaWidget.value?.reset();
// reCAPTCHA v3 doesn't typically need explicit reset in the same way
// Reset reCAPTCHA v2 if it exists
recaptchaWidget.value?.reset();
};
// --- End CAPTCHA Event Handlers ---
@@ -56,36 +58,16 @@ const handleSubmit = async () => {
captchaError.value = null; // Clear previous CAPTCHA error
// --- CAPTCHA Execution & Check ---
// --- CAPTCHA Check (v2/hCaptcha) ---
if (publicCaptchaConfig.value?.enabled && !loginRequires2FA.value) {
// 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 (for both hCaptcha and reCAPTCHA)
// Check if token exists (obtained via component event for v2/hCaptcha)
if (!captchaToken.value) {
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
}
}
// --- End CAPTCHA Check ---
// --- End CAPTCHA Check ---
try {
if (loginRequires2FA.value) {
@@ -169,8 +151,8 @@ onMounted(() => {
<!-- CAPTCHA Area -->
<!-- 恢复原始的 v-if 条件 -->
<div v-if="publicCaptchaConfig?.enabled && !loginRequires2FA" class="space-y-2">
<!-- 只在非 reCAPTCHA v3 时显示提示标签 -->
<label v-if="publicCaptchaConfig?.provider !== 'recaptcha'" class="block text-sm font-medium text-text-secondary">{{ t('login.captchaPrompt') }}</label>
<!-- 提示标签 -->
<label class="block text-sm font-medium text-text-secondary">{{ t('login.captchaPrompt') }}</label>
<!-- hCaptcha Component -->
<div v-if="publicCaptchaConfig?.provider === 'hcaptcha' && publicCaptchaConfig.hcaptchaSiteKey">
<VueHcaptcha
@@ -182,12 +164,19 @@ onMounted(() => {
theme="auto"
></VueHcaptcha>
</div>
<!-- reCAPTCHA v3 Info (usually invisible) -->
<div v-else-if="publicCaptchaConfig?.provider === 'recaptcha'">
<p class="text-xs text-text-secondary italic">
{{ t('login.recaptchaV3Notice') }}
</p>
<!-- v3 is typically invisible, token obtained programmatically on submit -->
<!-- reCAPTCHA v2 Component -->
<div v-else-if="publicCaptchaConfig?.provider === 'recaptcha' && publicCaptchaConfig.recaptchaSiteKey">
<VueRecaptcha
ref="recaptchaWidget"
:sitekey="publicCaptchaConfig.recaptchaSiteKey"
@verify="handleCaptchaVerified"
@expire="handleCaptchaExpired"
@fail="handleCaptchaError"
theme="light"
/>
<!-- 注意: 根据 vue3-recaptcha2 文档调整事件名 @expire, @fail -->
<!-- 注意: publicCaptchaConfig 需要包含 recaptchaSiteKey -->
<!-- theme 可以是 'light' 'dark' -->
</div>
<!-- CAPTCHA Error Message -->
<div v-if="captchaError" class="text-error text-sm">