diff --git a/index.html b/index.html index 17a9caa..c37a3be 100644 --- a/index.html +++ b/index.html @@ -83,6 +83,12 @@ delete window.wx; + + + + @@ -181,6 +187,7 @@
加载中
+
diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts index 3a7330e..b965e9a 100644 --- a/src/auto-imports.d.ts +++ b/src/auto-imports.d.ts @@ -117,6 +117,7 @@ declare global { const useActiveElement: typeof import('@vueuse/core')['useActiveElement'] const useAgent: typeof import('./composables/useAgent.js')['useAgent'] const useAgentStore: typeof import('./stores/agentStore.js')['useAgentStore'] + const useAliyunCaptcha: typeof import('./composables/useAliyunCaptcha.js')['default'] const useAnimate: typeof import('@vueuse/core')['useAnimate'] const useApiFetch: typeof import('./composables/useApiFetch.js')['default'] const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference'] diff --git a/src/components/AgentApplicationForm.vue b/src/components/AgentApplicationForm.vue index bbbe28c..299dded 100644 --- a/src/components/AgentApplicationForm.vue +++ b/src/components/AgentApplicationForm.vue @@ -121,8 +121,11 @@ const router = useRouter(); const show = defineModel("show"); import { useCascaderAreaData } from "@vant/area-data"; +import { useAliyunCaptcha } from "@/composables/useAliyunCaptcha"; import { showToast } from "vant"; // 引入 showToast 方法 const emit = defineEmits(); // 确保 emit 可以正确使用 + +const { runWithCaptcha } = useAliyunCaptcha(); const props = defineProps({ ancestor: { type: String, @@ -169,22 +172,20 @@ const getSmsCode = async () => { return; } - loadingSms.value = true; - - const { data, error } = await useApiFetch("auth/sendSms") - .post({ mobile: form.value.mobile, actionType: "agentApply" }) - .json(); - - loadingSms.value = false; - - if (data.value && !error.value) { - if (data.value.code === 200) { - showToast({ message: "获取成功" }); - startCountdown(); // 启动倒计时 - } else { - showToast(data.value.msg); + runWithCaptcha( + (captchaVerifyParam) => + useApiFetch("auth/sendSms") + .post({ mobile: form.value.mobile, actionType: "agentApply", captchaVerifyParam }) + .json(), + (res) => { + if (res.code === 200) { + showToast({ message: "获取成功" }); + startCountdown(); + } else { + showToast(res.msg || "获取失败,请重试"); + } } - } + ); }; let timer = null; diff --git a/src/components/InquireForm.vue b/src/components/InquireForm.vue index 29c6f99..c4a2bd4 100644 --- a/src/components/InquireForm.vue +++ b/src/components/InquireForm.vue @@ -282,8 +282,11 @@ import { useRoute, useRouter } from "vue-router"; import { useUserStore } from "@/stores/userStore"; import { useDialogStore } from "@/stores/dialogStore"; import { useEnv } from "@/composables/useEnv"; +import { useAliyunCaptcha } from "@/composables/useAliyunCaptcha"; import { showConfirmDialog } from "vant"; +const { runWithCaptcha } = useAliyunCaptcha(); + import Payment from "@/components/Payment.vue"; import BindPhoneDialog from "@/components/BindPhoneDialog.vue"; import LoginDialog from "@/components/LoginDialog.vue"; @@ -631,22 +634,26 @@ async function sendVerificationCode() { return; } - const { data, error } = await useApiFetch("/auth/sendSms") - .post({ mobile: formData.mobile, actionType: "query" }) - .json(); - - if (!error.value && data.value.code === 200) { - showToast({ message: "验证码发送成功", type: "success" }); - startCountdown(); - nextTick(() => { - const verificationCodeInput = document.getElementById('verificationCode'); - if (verificationCodeInput) { - verificationCodeInput.focus(); + runWithCaptcha( + (captchaVerifyParam) => + useApiFetch("/auth/sendSms") + .post({ mobile: formData.mobile, actionType: "query", captchaVerifyParam }) + .json(), + (res) => { + if (res.code === 200) { + showToast({ message: "验证码发送成功", type: "success" }); + startCountdown(); + nextTick(() => { + const verificationCodeInput = document.getElementById('verificationCode'); + if (verificationCodeInput) { + verificationCodeInput.focus(); + } + }); + } else { + showToast({ message: res.msg || "验证码发送失败,请重试" }); } - }); - } else { - showToast({ message: "验证码发送失败,请重试" }); - } + } + ); } let timer = null; diff --git a/src/composables/useAliyunCaptcha.js b/src/composables/useAliyunCaptcha.js new file mode 100644 index 0000000..f78e7f0 --- /dev/null +++ b/src/composables/useAliyunCaptcha.js @@ -0,0 +1,155 @@ +import { showToast, showLoadingToast, closeToast } from "vant"; +import useApiFetch from "@/composables/useApiFetch"; + +const ALIYUN_CAPTCHA_SCENE_ID = "wynt39to"; +const ENABLE_ENCRYPTED = false; + +let captchaInitialised = false; +let captchaReadyPromise = null; +let captchaReadyResolve = null; + +async function ensureCaptchaInit() { + 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; + }); + + if (!ENABLE_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) { + 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; + } + + const { data, error } = await useApiFetch("/captcha/encryptedSceneId") + .post() + .json(); + const resp = data?.value; + const encryptedSceneId = resp?.data?.encryptedSceneId; + if (error?.value || !encryptedSceneId) { + showToast({ message: "获取验证码参数失败,请稍后重试" }); + 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", + }); +} + +export function useAliyunCaptcha() { + async function runWithCaptcha(bizVerify, onSuccess) { + if (typeof window === "undefined") { + showToast({ message: "验证码仅支持浏览器环境" }); + return; + } + + const loading = showLoadingToast({ + message: "安全验证加载中...", + forbidClick: true, + duration: 0, + loadingType: "spinner", + }); + + try { + window.__captchaVerifyCallback = async (captchaVerifyParam) => { + window.__lastBizResponse = null; + const { data, error } = await bizVerify(captchaVerifyParam); + const result = data?.value ?? data; + if (error?.value || !result) { + return { captchaResult: false, bizResult: false }; + } + window.__lastBizResponse = result; + const captchaOk = result.captchaVerifyResult !== false; + const bizOk = result.code === 200; + return { captchaResult: captchaOk, bizResult: bizOk }; + }; + + window.__onBizResultCallback = (bizResult) => { + if ( + bizResult === true && + window.__lastBizResponse && + typeof window.__onCaptchaBizSuccess === "function" + ) { + window.__onCaptchaBizSuccess(window.__lastBizResponse); + } + }; + + await ensureCaptchaInit(); + + if (captchaReadyPromise) { + await captchaReadyPromise; + captchaReadyPromise = null; + } + if (!window.captcha) { + showToast({ message: "验证码未加载,请刷新页面重试" }); + return; + } + window.__onCaptchaBizSuccess = onSuccess; + window.captcha.show(); + } finally { + closeToast(); + } + } + + return { runWithCaptcha }; +} + +export default useAliyunCaptcha; diff --git a/src/views/Login.vue b/src/views/Login.vue index 661bd8c..f583dc8 100644 --- a/src/views/Login.vue +++ b/src/views/Login.vue @@ -1,7 +1,9 @@