From e581ced7e837665deb64b6a9d4c863ec6b386e60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=81=8C=E7=B3=96=E5=8C=85=E5=AD=90?= Date: Sat, 16 May 2026 13:21:55 +0800 Subject: [PATCH] =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=A0=81=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E5=B0=81=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components.d.ts | 1 + src/components/CaptchaPanel.vue | 126 ++++++++++++++++++ .../tianai-captcha/captcha/concat/concat.less | 1 - src/views/Login.vue | 77 ++++------- src/views/debug/CaptchaSandbox.vue | 114 +++++++--------- 5 files changed, 200 insertions(+), 119 deletions(-) create mode 100644 src/components/CaptchaPanel.vue diff --git a/components.d.ts b/components.d.ts index bbfb868..1ea6ec5 100644 --- a/components.d.ts +++ b/components.d.ts @@ -7,6 +7,7 @@ export {} declare module '@vue/runtime-core' { export interface GlobalComponents { + CaptchaPanel: typeof import('./src/components/CaptchaPanel.vue')['default'] ElAside: typeof import('element-plus/es')['ElAside'] ElButton: typeof import('element-plus/es')['ElButton'] ElCard: typeof import('element-plus/es')['ElCard'] diff --git a/src/components/CaptchaPanel.vue b/src/components/CaptchaPanel.vue new file mode 100644 index 0000000..3831009 --- /dev/null +++ b/src/components/CaptchaPanel.vue @@ -0,0 +1,126 @@ + + + \ No newline at end of file diff --git a/src/vendor/tianai-captcha/captcha/concat/concat.less b/src/vendor/tianai-captcha/captcha/concat/concat.less index 6013fe6..796e8d6 100644 --- a/src/vendor/tianai-captcha/captcha/concat/concat.less +++ b/src/vendor/tianai-captcha/captcha/concat/concat.less @@ -3,7 +3,6 @@ background-size: 100% 180px; position: absolute; transform: translate(0px, 0px); - /* border-bottom: 1px solid blue; */ z-index: 1; width: 100%; } diff --git a/src/views/Login.vue b/src/views/Login.vue index b92236f..69fa29a 100644 --- a/src/views/Login.vue +++ b/src/views/Login.vue @@ -53,7 +53,13 @@ @closed="handleCaptchaDialogClosed" >

请先完成验证码校验,再继续登录。

-
+ @@ -63,13 +69,9 @@ import { useStore } from 'vuex' import { useRouter } from 'vue-router' import { ElMessage } from 'element-plus' import type { VForm } from '../types' +import CaptchaPanel from '@/components/CaptchaPanel.vue' import http from '@/utils/http' -import { - createTianAiCaptcha, - type TianAiCaptchaConfig, - type TianAiCaptchaInstance, - type TianAiCaptchaResult -} from '@/vendor/tianai-captcha' +import type { TianAiCaptchaResult } from '@/vendor/tianai-captcha' type LoginFormState = { username: string | null, @@ -87,13 +89,17 @@ type LoginConfigResponse = { } } +type CaptchaPanelRef = { + destroy: () => void + init: () => Promise + reload: () => void +} + const store = useStore() const router = useRouter() const loginForm = ref() -const captchaBoxRef = ref(null) +const captchaRef = ref(null) const captchaDialogVisible = ref(false) -const captchaInstance = ref(null) -const requestCaptchaDataUrl = buildApiUrl('/captcha/gen') const captchaErrorPattern = /验证码|captcha/i const userInfo: LoginFormState = reactive({ @@ -119,7 +125,7 @@ localStorage.clear() async function openCaptchaDialog() { captchaDialogVisible.value = true await nextTick() - initCaptcha() + await captchaRef.value?.init() } async function handleLogin() { @@ -148,31 +154,6 @@ async function handleLogin() { }) } -function initCaptcha() { - if (!captchaBoxRef.value) { - captchaDialogVisible.value = false - ElMessage.error('验证码容器初始化失败') - return - } - - destroyCaptcha(false) - - const config: TianAiCaptchaConfig = { - bindEl: captchaBoxRef.value, - requestCaptchaDataUrl, - onDataReady: handleCaptchaSolved, - btnRefreshFun: (_el, tac) => { - tac.reloadCaptcha() - }, - btnCloseFun: () => { - closeCaptchaDialog() - } - } - - captchaInstance.value = createTianAiCaptcha(config) - captchaInstance.value.init() -} - async function handleCaptchaSolved(result: TianAiCaptchaResult) { if (loading.value) return @@ -186,7 +167,7 @@ async function handleCaptchaSolved(result: TianAiCaptchaResult) { }) } catch (error) { if (isCaptchaError(error)) { - captchaInstance.value?.reloadCaptcha() + captchaRef.value?.reload() } else { closeCaptchaDialog() } @@ -207,29 +188,19 @@ async function submitLogin(payload: LoginRequest) { function closeCaptchaDialog(resetLoading = true) { captchaDialogVisible.value = false - destroyCaptcha(false) + captchaRef.value?.destroy() if (resetLoading) { loading.value = false } } function handleCaptchaDialogClosed() { - destroyCaptcha(false) + captchaRef.value?.destroy() } -function destroyCaptcha(showMessage = false) { - if (!captchaInstance.value) return - captchaInstance.value.destroyWindow() - captchaInstance.value = null - if (showMessage) { - console.info('[login captcha] destroy captcha instance') - } -} - -function buildApiUrl(path: string) { - const normalizedBase = (import.meta.env.VITE_APP_API_BASE || '').replace(/\/+$/, '') - const normalizedPath = path.startsWith('/') ? path : `/${path}` - return `${normalizedBase}${normalizedPath}` +function handleCaptchaInitError() { + captchaDialogVisible.value = false + ElMessage.error('验证码初始化失败') } function isCaptchaError(error: unknown) { @@ -239,7 +210,7 @@ function isCaptchaError(error: unknown) { return false } -onBeforeUnmount(() => destroyCaptcha()) +onBeforeUnmount(() => captchaRef.value?.destroy())