update
This commit is contained in:
Generated
+7
-106
@@ -745,21 +745,6 @@
|
|||||||
"node": ">=18.12.0"
|
"node": ">=18.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nuxt/schema": {
|
|
||||||
"version": "3.16.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.16.2.tgz",
|
|
||||||
"integrity": "sha512-2HZPM372kuI/uw9VU/hOoYuzv803oZAtyoEKC5dQCQTKAQ293AjypF3WljMXUSReFS/hcbBSgGzYUPHr3Qo+pg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"consola": "^3.4.2",
|
|
||||||
"defu": "^6.1.4",
|
|
||||||
"pathe": "^2.0.3",
|
|
||||||
"std-env": "^3.8.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^14.18.0 || >=16.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@peculiar/asn1-android": {
|
"node_modules/@peculiar/asn1-android": {
|
||||||
"version": "2.3.16",
|
"version": "2.3.16",
|
||||||
"resolved": "https://registry.npmjs.org/@peculiar/asn1-android/-/asn1-android-2.3.16.tgz",
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-android/-/asn1-android-2.3.16.tgz",
|
||||||
@@ -1931,18 +1916,6 @@
|
|||||||
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
|
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@vueuse/shared": {
|
|
||||||
"version": "10.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz",
|
|
||||||
"integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"vue-demi": ">=0.14.8"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@xmldom/xmldom": {
|
"node_modules/@xmldom/xmldom": {
|
||||||
"version": "0.8.10",
|
"version": "0.8.10",
|
||||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
||||||
@@ -5113,18 +5086,6 @@
|
|||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/p-defer": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-Mr5KC5efvAK5VUptYEIopP1bakB85k2IWXaRC0rsh1uwn1L6M0LVml8OIQ4Gudg4oyZakf7FmeRLkMMtZW1i5A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/p-limit": {
|
"node_modules/p-limit": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||||
@@ -6685,18 +6646,6 @@
|
|||||||
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
|
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
|
||||||
"license": "Unlicense"
|
"license": "Unlicense"
|
||||||
},
|
},
|
||||||
"node_modules/type-fest": {
|
|
||||||
"version": "3.13.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
|
|
||||||
"integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
|
|
||||||
"license": "(MIT OR CC0-1.0)",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.16"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/type-is": {
|
"node_modules/type-is": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
|
||||||
@@ -7082,32 +7031,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vue-demi": {
|
|
||||||
"version": "0.14.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
|
||||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
|
||||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@vue/composition-api": "^1.0.0-rc.1",
|
|
||||||
"vue": "^3.0.0-0 || ^2.6.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@vue/composition-api": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vue-i18n": {
|
"node_modules/vue-i18n": {
|
||||||
"version": "9.14.4",
|
"version": "9.14.4",
|
||||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.4.tgz",
|
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.4.tgz",
|
||||||
@@ -7134,34 +7057,6 @@
|
|||||||
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vue-recaptcha": {
|
|
||||||
"version": "3.0.0-alpha.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/vue-recaptcha/-/vue-recaptcha-3.0.0-alpha.6.tgz",
|
|
||||||
"integrity": "sha512-hwxxAXENLN6GKJhH6s+NJV1f6llSFEQ0jMTxjEunpR6CMbQ5xc7DAHFtwrsDutGnbS8wl9yp30jsYcCToZEhTQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"workspaces": [
|
|
||||||
".",
|
|
||||||
"docs"
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"@nuxt/kit": "^3.4.3",
|
|
||||||
"@nuxt/schema": "^3.4.3",
|
|
||||||
"@vueuse/shared": "^10.1.0",
|
|
||||||
"defu": "^6.1.2",
|
|
||||||
"p-defer": "^4.0.0",
|
|
||||||
"std-env": "^3.3.2",
|
|
||||||
"type-fest": "^3.9.0",
|
|
||||||
"vue-demi": "^0.14.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"vue": "^3.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@vue/composition-api": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vue-recaptcha-v3": {
|
"node_modules/vue-recaptcha-v3": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/vue-recaptcha-v3/-/vue-recaptcha-v3-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/vue-recaptcha-v3/-/vue-recaptcha-v3-2.0.1.tgz",
|
||||||
@@ -7212,6 +7107,12 @@
|
|||||||
"typescript": ">=5.0.0"
|
"typescript": ">=5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue3-recaptcha2": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue3-recaptcha2/-/vue3-recaptcha2-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-lICGpLGv9wsdVjgWZ0usbZ+Ki52YX5kOzjt8l/ADun9GxdjJ5/AkGpxE7dZ+mG3VcE4ASZG2B/nYWB4wWFadvQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/vuedraggable": {
|
"node_modules/vuedraggable": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
|
||||||
@@ -7494,9 +7395,9 @@
|
|||||||
"vite-plugin-monaco-editor": "^1.1.0",
|
"vite-plugin-monaco-editor": "^1.1.0",
|
||||||
"vue": "^3.3.0",
|
"vue": "^3.3.0",
|
||||||
"vue-i18n": "^9.14.4",
|
"vue-i18n": "^9.14.4",
|
||||||
"vue-recaptcha": "^3.0.0-alpha.6",
|
|
||||||
"vue-recaptcha-v3": "^2.0.1",
|
"vue-recaptcha-v3": "^2.0.1",
|
||||||
"vue-router": "^4.5.0",
|
"vue-router": "^4.5.0",
|
||||||
|
"vue3-recaptcha2": "^1.8.0",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
"xterm": "^5.3.0",
|
"xterm": "^5.3.0",
|
||||||
"xterm-addon-fit": "^0.8.0",
|
"xterm-addon-fit": "^0.8.0",
|
||||||
|
|||||||
@@ -23,9 +23,9 @@
|
|||||||
"vite-plugin-monaco-editor": "^1.1.0",
|
"vite-plugin-monaco-editor": "^1.1.0",
|
||||||
"vue": "^3.3.0",
|
"vue": "^3.3.0",
|
||||||
"vue-i18n": "^9.14.4",
|
"vue-i18n": "^9.14.4",
|
||||||
"vue-recaptcha": "^3.0.0-alpha.6",
|
|
||||||
"vue-recaptcha-v3": "^2.0.1",
|
"vue-recaptcha-v3": "^2.0.1",
|
||||||
"vue-router": "^4.5.0",
|
"vue-router": "^4.5.0",
|
||||||
|
"vue3-recaptcha2": "^1.8.0",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
"xterm": "^5.3.0",
|
"xterm": "^5.3.0",
|
||||||
"xterm-addon-fit": "^0.8.0",
|
"xterm-addon-fit": "^0.8.0",
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ 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';
|
||||||
// 恢复导入 reCAPTCHA v3
|
|
||||||
import { VueReCaptcha } from 'vue-recaptcha-v3';
|
|
||||||
|
|
||||||
const pinia = createPinia(); // 创建 Pinia 实例
|
const pinia = createPinia(); // 创建 Pinia 实例
|
||||||
pinia.use(piniaPluginPersistedstate); // 使用持久化插件
|
pinia.use(piniaPluginPersistedstate); // 使用持久化插件
|
||||||
@@ -24,14 +23,7 @@ app.use(pinia); // 使用配置好的 Pinia 实例
|
|||||||
// 注意:在状态初始化完成前,暂时不 use(router)
|
// 注意:在状态初始化完成前,暂时不 use(router)
|
||||||
app.use(i18n); // 使用 i18n
|
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
|
// 使用 async IIFE 来允许顶层 await
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { reactive, ref, onMounted, computed } from 'vue'; // computed 已导入
|
|||||||
import { storeToRefs } from 'pinia';
|
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 { useReCaptcha } from 'vue-recaptcha-v3'; // <-- Restore reCAPTCHA v3 hook
|
import VueRecaptcha from 'vue3-recaptcha2'; // 使用默认导入
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
@@ -21,9 +21,10 @@ 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); // NEW: Ref for hCaptcha component instance
|
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 ---
|
// --- reCAPTCHA v3 Initialization ---
|
||||||
const recaptchaInstance = useReCaptcha(); // Restore v3 instance
|
// const recaptchaInstance = useReCaptcha(); // 移除 v3 实例,因为我们将使用 v2 组件
|
||||||
|
|
||||||
|
|
||||||
// --- CAPTCHA Event Handlers ---
|
// --- CAPTCHA Event Handlers ---
|
||||||
@@ -46,7 +47,8 @@ const resetCaptchaWidget = () => {
|
|||||||
captchaToken.value = null;
|
captchaToken.value = null;
|
||||||
// Reset hCaptcha if it exists
|
// Reset hCaptcha if it exists
|
||||||
hcaptchaWidget.value?.reset();
|
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 ---
|
// --- End CAPTCHA Event Handlers ---
|
||||||
|
|
||||||
@@ -56,36 +58,16 @@ const handleSubmit = async () => {
|
|||||||
captchaError.value = null; // Clear previous CAPTCHA error
|
captchaError.value = null; // Clear previous CAPTCHA error
|
||||||
|
|
||||||
// --- CAPTCHA Execution & Check ---
|
// --- CAPTCHA Execution & Check ---
|
||||||
|
// --- CAPTCHA Check (v2/hCaptcha) ---
|
||||||
if (publicCaptchaConfig.value?.enabled && !loginRequires2FA.value) {
|
if (publicCaptchaConfig.value?.enabled && !loginRequires2FA.value) {
|
||||||
// Restore If reCAPTCHA v3, execute it now to get the token
|
// Check if token exists (obtained via component event for v2/hCaptcha)
|
||||||
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)
|
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// --- End CAPTCHA Check ---
|
// --- End CAPTCHA Check ---
|
||||||
|
// --- End CAPTCHA Check ---
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (loginRequires2FA.value) {
|
if (loginRequires2FA.value) {
|
||||||
@@ -169,8 +151,8 @@ onMounted(() => {
|
|||||||
<!-- CAPTCHA Area -->
|
<!-- CAPTCHA Area -->
|
||||||
<!-- 恢复原始的 v-if 条件 -->
|
<!-- 恢复原始的 v-if 条件 -->
|
||||||
<div v-if="publicCaptchaConfig?.enabled && !loginRequires2FA" class="space-y-2">
|
<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 -->
|
<!-- hCaptcha Component -->
|
||||||
<div v-if="publicCaptchaConfig?.provider === 'hcaptcha' && publicCaptchaConfig.hcaptchaSiteKey">
|
<div v-if="publicCaptchaConfig?.provider === 'hcaptcha' && publicCaptchaConfig.hcaptchaSiteKey">
|
||||||
<VueHcaptcha
|
<VueHcaptcha
|
||||||
@@ -182,12 +164,19 @@ onMounted(() => {
|
|||||||
theme="auto"
|
theme="auto"
|
||||||
></VueHcaptcha>
|
></VueHcaptcha>
|
||||||
</div>
|
</div>
|
||||||
<!-- reCAPTCHA v3 Info (usually invisible) -->
|
<!-- reCAPTCHA v2 Component -->
|
||||||
<div v-else-if="publicCaptchaConfig?.provider === 'recaptcha'">
|
<div v-else-if="publicCaptchaConfig?.provider === 'recaptcha' && publicCaptchaConfig.recaptchaSiteKey">
|
||||||
<p class="text-xs text-text-secondary italic">
|
<VueRecaptcha
|
||||||
{{ t('login.recaptchaV3Notice') }}
|
ref="recaptchaWidget"
|
||||||
</p>
|
:sitekey="publicCaptchaConfig.recaptchaSiteKey"
|
||||||
<!-- v3 is typically invisible, token obtained programmatically on submit -->
|
@verify="handleCaptchaVerified"
|
||||||
|
@expire="handleCaptchaExpired"
|
||||||
|
@fail="handleCaptchaError"
|
||||||
|
theme="light"
|
||||||
|
/>
|
||||||
|
<!-- 注意: 根据 vue3-recaptcha2 文档调整事件名 @expire, @fail -->
|
||||||
|
<!-- 注意: publicCaptchaConfig 需要包含 recaptchaSiteKey -->
|
||||||
|
<!-- theme 可以是 'light' 或 'dark' -->
|
||||||
</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