From c9ef3f6f9cb79e411786ce824bf18c3e14d78eac Mon Sep 17 00:00:00 2001 From: Mrx <18278715334@163.com> Date: Fri, 27 Feb 2026 14:49:21 +0800 Subject: [PATCH] add --- .env | 4 +- .eslintrc-auto-import.json | 1 + auto-imports.d.ts | 2 + index.html | 9 + src/api/index.js | 8 + src/composables/useAliyunCaptcha.js | 176 ++++++++++++++++++ src/pages/auth/Login.vue | 20 +- src/pages/auth/Register.vue | 22 ++- src/pages/auth/ResetPassword.vue | 22 ++- .../components/CertificationComplete.vue | 2 +- .../components/EnterpriseInfo.vue | 89 ++++++++- src/stores/user.js | 9 +- 12 files changed, 335 insertions(+), 29 deletions(-) create mode 100644 src/composables/useAliyunCaptcha.js diff --git a/.env b/.env index 03cefb7..0bca3fa 100644 --- a/.env +++ b/.env @@ -1 +1,3 @@ -VITE_API_URL="https://api.tianyuanapi.com" \ No newline at end of file +VITE_API_URL="https://api.tianyuanapi.com" +VITE_CAPTCHA_SCENE_ID="wynt39to" +VITE_CAPTCHA_ENCRYPTED_MODE=false \ No newline at end of file diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json index 017fbb8..55a19f9 100644 --- a/.eslintrc-auto-import.json +++ b/.eslintrc-auto-import.json @@ -218,6 +218,7 @@ "until": true, "upperCase": true, "useActiveElement": true, + "useAliyunCaptcha": true, "useAnimate": true, "useAppStore": true, "useArrayDifference": true, diff --git a/auto-imports.d.ts b/auto-imports.d.ts index 17031ec..516d1ce 100644 --- a/auto-imports.d.ts +++ b/auto-imports.d.ts @@ -225,6 +225,7 @@ declare global { const until: typeof import('@vueuse/core')['until'] const upperCase: typeof import('lodash-es')['upperCase'] const useActiveElement: typeof import('@vueuse/core')['useActiveElement'] + const useAliyunCaptcha: typeof import('./src/composables/useAliyunCaptcha.js')['default'] const useAnimate: typeof import('@vueuse/core')['useAnimate'] const useAppStore: typeof import('./src/stores/app.js')['useAppStore'] const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference'] @@ -668,6 +669,7 @@ declare module 'vue' { readonly until: UnwrapRef readonly upperCase: UnwrapRef readonly useActiveElement: UnwrapRef + readonly useAliyunCaptcha: UnwrapRef readonly useAnimate: UnwrapRef readonly useAppStore: UnwrapRef readonly useArrayDifference: UnwrapRef diff --git a/index.html b/index.html index 5c42208..d5158bf 100644 --- a/index.html +++ b/index.html @@ -5,9 +5,18 @@ 天远数据 + + +
+
diff --git a/src/api/index.js b/src/api/index.js index c13720b..0930975 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -40,6 +40,14 @@ export const userApi = { getUserStats: () => request.get('/users/admin/stats') } +// 验证码(阿里云滑块)相关接口 +export const captchaApi = { + // 获取加密场景 ID,用于前端加密模式初始化滑块 + getEncryptedSceneId: (params) => request.post('/captcha/encryptedSceneId', params || {}), + // 获取验证码配置(是否启用、场景 ID) + getConfig: () => request.get('/captcha/config') +} + // 产品相关接口 export const productApi = { // 产品列表(用户端接口) diff --git a/src/composables/useAliyunCaptcha.js b/src/composables/useAliyunCaptcha.js new file mode 100644 index 0000000..b6dd7b0 --- /dev/null +++ b/src/composables/useAliyunCaptcha.js @@ -0,0 +1,176 @@ +import { ElMessage } from 'element-plus' +import { captchaApi } from '@/api' + +// 阿里云验证码场景 ID(需与后端 config.sms.scene_id 一致;加密模式可由后端 config 下发) +const ALIYUN_CAPTCHA_SCENE_ID = import.meta.env.VITE_CAPTCHA_SCENE_ID || "wynt39to" +// 是否启用加密模式:通过环境变量 VITE_CAPTCHA_ENCRYPTED_MODE 控制,为 'true' 不加密 +const ENABLE_ENCRYPTED = import.meta.env.VITE_CAPTCHA_ENCRYPTED_MODE === true + +let captchaInitialised = false +let captchaReadyPromise = null +let captchaReadyResolve = null + +async function ensureCaptchaInit() { + console.log("ENABLE_ENCRYPTED", ENABLE_ENCRYPTED) + if (captchaInitialised || typeof window === "undefined") return + if (typeof window.initAliyunCaptcha !== "function") return + + captchaInitialised = true + window.captcha = null + window.__lastBizResponse = null + window.__onCaptchaBizSuccess = null + captchaReadyPromise = new Promise((resolve) => { + captchaReadyResolve = resolve + }) + + // 非加密模式:仅传 SceneId,不调用后端接口 + if (!ENABLE_ENCRYPTED) { + console.log("NON-ENCRYPTED") + window.initAliyunCaptcha({ + SceneId: ALIYUN_CAPTCHA_SCENE_ID, + mode: "popup", + element: "#captcha-element", + getInstance(instance) { + window.captcha = instance + if (typeof captchaReadyResolve === "function") { + captchaReadyResolve() + captchaReadyResolve = null + } + }, + captchaVerifyCallback(param) { + console.log("captchaVerifyCallback", param) + return typeof window.__captchaVerifyCallback === "function" + ? window.__captchaVerifyCallback(param) + : Promise.resolve({ + captchaResult: false, + bizResult: false, + }) + }, + onBizResultCallback(bizResult) { + if (typeof window.__onBizResultCallback === "function") { + window.__onBizResultCallback(bizResult) + } + window.__lastBizResponse = null + window.__onCaptchaBizSuccess = null + }, + slideStyle: { width: 360, height: 40 }, + language: "cn", + }) + return + } + + // 加密模式:先从后端获取 EncryptedSceneId,再初始化 + try { + const resp = await captchaApi.getEncryptedSceneId() + const encryptedSceneId = resp?.data?.data?.encryptedSceneId ?? resp?.data?.encryptedSceneId + if (!encryptedSceneId) { + ElMessage.error("获取验证码参数失败,请稍后重试") + captchaInitialised = false + captchaReadyPromise = null + captchaReadyResolve = null + return + } + window.initAliyunCaptcha({ + SceneId: ALIYUN_CAPTCHA_SCENE_ID, + EncryptedSceneId: encryptedSceneId, + mode: "popup", + element: "#captcha-element", + getInstance(instance) { + window.captcha = instance + if (typeof captchaReadyResolve === "function") { + captchaReadyResolve() + captchaReadyResolve = null + } + }, + captchaVerifyCallback(param) { + return typeof window.__captchaVerifyCallback === "function" + ? window.__captchaVerifyCallback(param) + : Promise.resolve({ captchaResult: false, bizResult: false }) + }, + onBizResultCallback(bizResult) { + if (typeof window.__onBizResultCallback === "function") { + window.__onBizResultCallback(bizResult) + } + window.__lastBizResponse = null + window.__onCaptchaBizSuccess = null + }, + slideStyle: { width: 360, height: 40 }, + language: "cn", + }) + } catch (error) { + ElMessage.error("获取验证码参数失败,请稍后重试") + captchaInitialised = false + captchaReadyPromise = null + captchaReadyResolve = null + } +} + +/** + * 阿里云滑块验证码通用封装。 + * 依赖 index.html 中已加载的 AliyunCaptcha.js;初始化在首次调起时执行。 + */ +export function useAliyunCaptcha() { + /** + * 先弹出滑块,通过后执行 bizVerify(captchaVerifyParam),再根据结果调用 onSuccess。 + * @param { (captchaVerifyParam: string) => Promise<{ success: boolean, data: any, error?: any }> } bizVerify - 业务请求函数,接收滑块参数 + * @param { (res: any) => void } onSuccess - 业务成功回调 + */ + async function runWithCaptcha(bizVerify, onSuccess) { + if (typeof window === "undefined") { + ElMessage.error("验证码仅支持浏览器环境") + return + } + + const loadingInstance = ElMessage({ + message: "安全验证加载中...", + type: "info", + duration: 0, + iconClass: "el-icon-loading" + }) + + try { + window.__captchaVerifyCallback = async (captchaVerifyParam) => { + window.__lastBizResponse = null + try { + const result = await bizVerify(captchaVerifyParam) + window.__lastBizResponse = result + const captchaOk = result?.data?.captchaVerifyResult !== false + const bizOk = result.success === true + return { captchaResult: captchaOk, bizResult: bizOk } + } catch (error) { + return { captchaResult: false, bizResult: false } + } + } + + window.__onBizResultCallback = (bizResult) => { + if ( + bizResult === true && + window.__lastBizResponse && + typeof window.__onCaptchaBizSuccess === "function" + ) { + window.__onCaptchaBizSuccess(window.__lastBizResponse) + } + } + + await ensureCaptchaInit() + + // 首次初始化时 SDK 会异步调用 getInstance,需等待实例就绪后再 show + if (captchaReadyPromise) { + await captchaReadyPromise + captchaReadyPromise = null + } + if (!window.captcha) { + ElMessage.error("验证码未加载,请刷新页面重试") + return + } + window.__onCaptchaBizSuccess = onSuccess + window.captcha.show() + } finally { + loadingInstance.close() + } + } + + return { runWithCaptcha } +} + +export default useAliyunCaptcha diff --git a/src/pages/auth/Login.vue b/src/pages/auth/Login.vue index 0540527..eb2019b 100644 --- a/src/pages/auth/Login.vue +++ b/src/pages/auth/Login.vue @@ -115,10 +115,12 @@