This commit is contained in:
Mrx
2026-03-05 16:54:19 +08:00
parent 030ed260c1
commit 1652ee1e55
3 changed files with 103 additions and 152 deletions

View File

@@ -119,7 +119,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')['useAliyunCaptcha']
const useAliyunCaptcha: typeof import('./composables/useAliyunCaptcha.js')['default']
const useAnimate: typeof import('@vueuse/core')['useAnimate']
const useApiFetch: typeof import('./composables/useApiFetch.js')['default']
const useAppStore: typeof import('./stores/appStore.js')['useAppStore']

View File

@@ -448,8 +448,8 @@ async function sendVerificationCode() {
.post({ mobile: formData.mobile, actionType: "query", captchaVerifyParam })
.json(),
(result) => {
const { data, error } = result
if (!error.value && data.value?.code === 200) {
// result 已经是解包后的响应数据data.value
if (result && result.code === 200) {
showToast({ message: "验证码发送成功", type: "success" });
startCountdown();
nextTick(() => {
@@ -458,8 +458,8 @@ async function sendVerificationCode() {
verificationCodeInput.focus();
}
});
} else {
showToast({ message: data.value?.msg || "验证码发送失败,请重试" });
} else if (result) {
showToast({ message: result.msg || "验证码发送失败,请重试" });
}
}
);

View File

@@ -1,154 +1,103 @@
import { showToast, showLoadingToast, closeToast } from 'vant'
import useApiFetch from '@/composables/useApiFetch'
// 阿里云验证码场景ID从环境变量读取
const ALIYUN_CAPTCHA_SCENE_ID = import.meta.env.VITE_ALIYUN_CAPTCHA_SCENE_ID || 'wynt39to'
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'
const ENABLE_ENCRYPTED =
import.meta.env.VITE_ALIYUN_CAPTCHA_ENCRYPTED === "false";
let captchaInitialized = false
let captchaInitialised = false;
/** 首次初始化后SDK 会异步调用 getInstance用此 Promise 在实例就绪后再 show */
let captchaReadyPromise = null
let captchaReadyResolve = null
let captchaReadyPromise = null;
let captchaReadyResolve = null;
async function ensureCaptchaInit() {
console.log('[AliyunCaptcha] ====== ensureCaptchaInit 开始执行 ======');
// 1. 检查是否已经初始化或非浏览器环境
if (captchaInitialized) {
console.log('[AliyunCaptcha] 已初始化,直接返回');
return;
}
if (typeof window === 'undefined') {
console.log('[AliyunCaptcha] 非浏览器环境,直接返回');
return;
}
if (captchaInitialised || typeof window === "undefined") return;
if (typeof window.initAliyunCaptcha !== "function") return;
// 2. 检查 SDK 是否存在
if (typeof window.initAliyunCaptcha !== 'function') {
console.error('[AliyunCaptcha] ❌ initAliyunCaptcha 不存在SDK 可能未加载!请检查是否引入 captcha.js');
return;
}
console.log('[AliyunCaptcha] ✅ initAliyunCaptcha 函数存在SDK 已加载');
// 3. 设置初始化状态
captchaInitialized = true;
captchaInitialised = true;
window.captcha = null;
window.__lastBizResponse = null;
window.__onCaptchaBizSuccess = null;
captchaReadyPromise = new Promise((resolve) => {
captchaReadyResolve = resolve;
});
console.log('[AliyunCaptcha] 初始化状态已设置,创建 captchaReadyPromise');
// 4. 构建配置对象
const initConfig = {
// 非加密模式:仅传 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,
mode: 'popup',
element: '#captcha-element',
EncryptedSceneId: encryptedSceneId,
mode: "popup",
element: "#captcha-element",
getInstance(instance) {
console.log('[AliyunCaptcha] 🎯 getInstance 被调用,实例已创建:', instance);
window.captcha = instance;
if (typeof captchaReadyResolve === 'function') {
console.log('[AliyunCaptcha] ✅ 调用 captchaReadyResolve(),通知实例就绪');
if (typeof captchaReadyResolve === "function") {
captchaReadyResolve();
captchaReadyResolve = null;
} else {
console.warn('[AliyunCaptcha] ⚠️ captchaReadyResolve 不是函数,可能已被调用');
}
},
captchaVerifyCallback(param) {
console.log('[AliyunCaptcha] 📞 captchaVerifyCallback 被调用,参数:', JSON.stringify(param, null, 2));
const result = typeof window.__captchaVerifyCallback === 'function'
return typeof window.__captchaVerifyCallback === "function"
? window.__captchaVerifyCallback(param)
: Promise.resolve({ captchaResult: false, bizResult: false });
console.log('[AliyunCaptcha] 📤 captchaVerifyCallback 返回值:', result);
return result;
},
onBizResultCallback(bizResult) {
console.log('[AliyunCaptcha] 📬 onBizResultCallback 被调用bizResult:', bizResult);
if (typeof window.__onBizResultCallback === 'function') {
if (typeof window.__onBizResultCallback === "function") {
window.__onBizResultCallback(bizResult);
} else {
console.warn('[AliyunCaptcha] ⚠️ __onBizResultCallback 不是函数');
}
window.__lastBizResponse = null;
window.__onCaptchaBizSuccess = null;
},
slideStyle: { width: 360, height: 40 },
language: 'cn',
};
console.log('[AliyunCaptcha] 📋 构建的 initConfig:', JSON.stringify(initConfig, null, 2));
// 5. 判断加密模式
if (!ENABLE_ENCRYPTED) {
console.log('[AliyunCaptcha] 🔓 使用非加密模式ENABLE_ENCRYPTED = false');
console.log('[AliyunCaptcha] 📤 调用 window.initAliyunCaptcha(initConfig)');
try {
window.initAliyunCaptcha(initConfig);
console.log('[AliyunCaptcha] ✅ initAliyunCaptcha 调用完成(非加密模式)');
} catch (err) {
console.error('[AliyunCaptcha] ❌ initAliyunCaptcha 调用失败(非加密模式):', err);
}
return;
}
// 6. 加密模式:获取 encryptedSceneId
console.log('[AliyunCaptcha] 🔐 使用加密模式ENABLE_ENCRYPTED = true');
console.log('[AliyunCaptcha] 📡 请求后端接口获取 encryptedSceneId: /captcha/encryptedSceneId');
let resp, encryptedSceneId;
try {
const { data, error } = await useApiFetch('/captcha/encryptedSceneId')
.post()
.json();
console.log('[AliyunCaptcha] 📥 后端接口响应:', { data: data?.value, error: error?.value });
resp = data?.value;
encryptedSceneId = resp?.data?.encryptedSceneId;
if (error?.value) {
console.error('[AliyunCaptcha] ❌ 后端接口请求失败:', error.value);
}
if (!encryptedSceneId) {
console.error('[AliyunCaptcha] ❌ encryptedSceneId 为空,响应数据:', resp);
}
} catch (err) {
console.error('[AliyunCaptcha] ❌ 请求 encryptedSceneId 时发生异常:', err);
resp = null;
encryptedSceneId = null;
}
if (error?.value || !encryptedSceneId) {
console.error('[AliyunCaptcha] ❌ 获取 encryptedSceneId 失败error:', error?.value, 'encryptedSceneId:', encryptedSceneId);
showToast({ message: '获取验证码参数失败,请稍后重试' });
captchaInitialized = false;
captchaReadyPromise = null;
captchaReadyResolve = null;
console.log('[AliyunCaptcha] 🔄 重置初始化状态,等待下次重试');
return;
}
console.log('[AliyunCaptcha] ✅ 成功获取 encryptedSceneId:', encryptedSceneId);
initConfig.EncryptedSceneId = encryptedSceneId;
console.log('[AliyunCaptcha] 📤 调用 window.initAliyunCaptcha(initConfig)(加密模式)');
try {
console.log('[AliyunCaptcha] 开始调用 window.initAliyunCaptcha(initConfig)(加密模式)')
window.initAliyunCaptcha(initConfig)
console.log('[AliyunCaptcha] ✅ window.initAliyunCaptcha 调用完成(加密模式)')
} catch (err) {
console.error('[AliyunCaptcha] ❌ window.initAliyunCaptcha 调用失败(加密模式):', err)
}
language: "cn",
});
}
/**
@@ -163,59 +112,61 @@ export function useAliyunCaptcha() {
* 先弹出滑块,通过后执行 bizVerify(captchaVerifyParam),再根据结果调用 onSuccess。
*/
async function runWithCaptcha(bizVerify, onSuccess) {
if (typeof window === 'undefined') {
showToast({ message: '验证码仅支持浏览器环境' })
return
if (typeof window === "undefined") {
showToast({ message: "验证码仅支持浏览器环境" });
return;
}
showLoadingToast({
message: '安全验证加载中...',
const loading = showLoadingToast({
message: "安全验证加载中...",
forbidClick: true,
duration: 0,
loadingType: 'spinner',
})
loadingType: "spinner",
});
try {
window.__captchaVerifyCallback = async (captchaVerifyParam) => {
window.__lastBizResponse = null
const { data, error } = await bizVerify(captchaVerifyParam)
const result = data?.value ?? data
window.__lastBizResponse = null;
const { data, error } = await bizVerify(captchaVerifyParam);
const result = data?.value ?? data;
if (error?.value || !result) {
return { captchaResult: false, bizResult: false }
return { captchaResult: false, bizResult: false };
}
window.__lastBizResponse = result
const captchaOk = result.captchaVerifyResult !== false
const bizOk = result.code === 200
return { captchaResult: captchaOk, bizResult: bizOk }
}
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'
typeof window.__onCaptchaBizSuccess === "function"
) {
window.__onCaptchaBizSuccess(window.__lastBizResponse)
window.__onCaptchaBizSuccess(window.__lastBizResponse);
}
}
};
await ensureCaptchaInit()
await ensureCaptchaInit();
// 首次初始化时 SDK 会异步调用 getInstance需等待实例就绪后再 show
if (captchaReadyPromise) {
await captchaReadyPromise
captchaReadyPromise = null
await captchaReadyPromise;
captchaReadyPromise = null;
}
if (!window.captcha) {
showToast({ message: '验证码未加载,请刷新页面重试' })
return
showToast({ message: "验证码未加载,请刷新页面重试" });
return;
}
window.__onCaptchaBizSuccess = onSuccess
window.captcha.show()
window.__onCaptchaBizSuccess = onSuccess;
window.captcha.show();
} finally {
closeToast()
closeToast();
}
}
return { runWithCaptcha }
return { runWithCaptcha };
}
export default useAliyunCaptcha;