173 lines
6.4 KiB
JavaScript
173 lines
6.4 KiB
JavaScript
import { showToast, showLoadingToast, closeToast } from "vant";
|
||
import useApiFetch from "@/composables/useApiFetch";
|
||
|
||
// 阿里云验证码场景 ID
|
||
const ALIYUN_CAPTCHA_SCENE_ID = "wynt39to";
|
||
// 是否启用加密模式(通过环境变量控制,非加密模式时前端不调用后端获取 EncryptedSceneId)
|
||
const ENABLE_ENCRYPTED =
|
||
import.meta.env.VITE_ALIYUN_CAPTCHA_ENCRYPTED === "false";
|
||
|
||
let captchaInitialised = false;
|
||
/** 首次初始化后,SDK 会异步调用 getInstance,用此 Promise 在实例就绪后再 show */
|
||
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;
|
||
});
|
||
|
||
// 非加密模式:仅传 SceneId,不调用后端接口
|
||
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;
|
||
}
|
||
|
||
// 加密模式:先从后端获取 EncryptedSceneId,再初始化
|
||
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",
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 阿里云滑块验证码通用封装。
|
||
* 依赖 index.html 中已加载的 AliyunCaptcha.js;初始化在首次调起时执行。
|
||
*
|
||
* @param { (captchaVerifyParam: string) => Promise<{ data: Ref, error: Ref }> } bizVerify - 业务请求函数,接收滑块参数,返回 useApiFetch 的 { data, error }
|
||
* @param { (res: any) => void } [onSuccess] - 业务成功回调(code===200 时调用,传入接口返回的 data.value)
|
||
*/
|
||
export function useAliyunCaptcha() {
|
||
/**
|
||
* 先弹出滑块,通过后执行 bizVerify(captchaVerifyParam),再根据结果调用 onSuccess。
|
||
*/
|
||
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();
|
||
|
||
// 首次初始化时 SDK 会异步调用 getInstance,需等待实例就绪后再 show
|
||
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;
|