/** * 阿里云验证码2.0工具模块 * 用于小程序发送验证码前的行为验证 */ // 验证码插件实例 let AliyunCaptchaPluginInterface: any = null; // 计时器 let timer: number | null = null; // 页面实例引用 let pageInstance: any = null; // 是否在发送中(防止重复请求) let isSending: boolean = false; // 倒计时剩余秒数 let remainingSeconds: number = 0; // 发送验证码的接口配置 interface SendCodeConfig { url: string; mobileField?: string; extraData?: Record; } // 验证码配置选项 interface CaptchaOptions { sceneId: string; sendCodeConfig: SendCodeConfig; onSendSuccess?: () => void; onSendFail?: (err: any) => void; countdown?: number; } // 当前配置 let currentOptions: CaptchaOptions | null = null; /** * 初始化验证码插件 */ function initPlugin() { if (!AliyunCaptchaPluginInterface) { AliyunCaptchaPluginInterface = requirePlugin('AliyunCaptcha'); } } /** * 验证通过回调函数 * @param captchaVerifyParam 验证码验证参数 */ async function successCallback(captchaVerifyParam: string) { if (!pageInstance || !currentOptions) return; if (isSending) { wx.showToast({ title: '发送中,请稍候', icon: 'none', }); return; } const { sendCodeConfig, onSendSuccess, onSendFail, countdown = 60 } = currentOptions; const mobileField = sendCodeConfig.mobileField || 'mobile'; const mobile = pageInstance.data[mobileField]; if (!mobile) { wx.showToast({ title: '请输入手机号', icon: 'none', }); return; } isSending = true; updateCountdownText('发送中...'); try { const res = await wx.ajax({ method: 'POST', url: sendCodeConfig.url, data: { [mobileField]: mobile, captchaVerifyParam, ...sendCodeConfig.extraData, }, }); wx.showToast({ icon: 'none', title: '验证码已发送~', }); // 开始倒计时 startCountdown(countdown); // 执行成功回调 onSendSuccess?.(); return res; } catch (err: any) { // 发送失败,重置状态(包括阿里云插件,以便下次重新验证) resetCaptchaState(true); const errorMsg = err.data?.msg || err.message || '发送失败,请重试'; wx.showToast({ title: errorMsg, icon: 'none', duration: 2000, }); onSendFail?.(err); throw err; } finally { isSending = false; } } /** * 验证失败回调函数 * @param error 错误信息 */ function failCallback(error: any) { console.error('阿里云验证码验证失败:', error); wx.showToast({ title: '验证失败,请重试', icon: 'none', }); } /** * 开始倒计时 * @param seconds 倒计时秒数 */ function startCountdown(seconds: number) { // 清除之前的计时器 if (timer) { clearInterval(timer); timer = null; } remainingSeconds = seconds; updateCountdownText(`${remainingSeconds}s后重新发送`); timer = setInterval(() => { remainingSeconds--; if (remainingSeconds <= 0) { // 倒计时结束,重置状态(包括阿里云插件,以便下次重新验证) resetCaptchaState(true); } else { updateCountdownText(`${remainingSeconds}s后重新发送`); } }, 1000) as unknown as number; } /** * 重置验证码状态 * 在倒计时结束、发送失败或需要重置时使用 */ function resetCaptchaState(resetPluginToo: boolean = false) { if (timer) { clearInterval(timer); timer = null; } remainingSeconds = 0; isSending = false; updateCountdownText('发送验证码'); // 如果需要,同时重置阿里云插件状态(接口失败时使用) if (resetPluginToo) { refreshCaptchaComponent(); } } /** * 重置阿里云验证码插件 * 用于验证失败后需要重新验证的情况 */ function resetAliyunPlugin(): void { // 销毁插件实例,下次会自动重新初始化 AliyunCaptchaPluginInterface = null; } /** * 强制刷新验证码组件 * 通过卸载并重新加载组件来重置阿里云插件状态 */ export function refreshCaptchaComponent(callback?: () => void): void { if (!pageInstance) return; // 卸载组件 pageInstance.setData({ loadCaptcha: false, }); // 重置插件实例 resetAliyunPlugin(); // 延迟重新加载组件,确保卸载完成 setTimeout(() => { if (!pageInstance || !currentOptions) return; // 重新初始化配置 const pluginProps = { SceneId: currentOptions.sceneId, mode: 'popup', success: successCallback.bind(pageInstance), fail: failCallback.bind(pageInstance), slideStyle: { width: 540, height: 60, }, language: 'cn', region: 'cn', }; // 重新加载组件 pageInstance.setData({ loadCaptcha: true, pluginProps, }); if (callback) callback(); }, 100); } /** * 更新倒计时文本 * @param text 显示的文本 */ function updateCountdownText(text: string) { if (pageInstance) { pageInstance.setData({ codeText: text }); } } /** * 初始化验证码功能 * @param page 页面实例 * @param options 配置选项 * @returns 插件配置对象 */ export function initCaptcha(page: any, options: CaptchaOptions) { initPlugin(); pageInstance = page; currentOptions = options; const pluginProps = { SceneId: options.sceneId, mode: 'popup', success: successCallback.bind(page), fail: failCallback.bind(page), slideStyle: { width: 540, height: 60, }, language: 'cn', region: 'cn', }; return { loadCaptcha: true, pluginProps, }; } /** * 显示验证码弹窗 * @returns 是否成功触发 */ export function showCaptcha(): boolean { if (!AliyunCaptchaPluginInterface) { initPlugin(); } if (isCountingDown()) { return false; } wx.getNetworkType({ success: (res) => { if (res.networkType === 'none') { wx.showToast({ title: '网络连接不可用,请检查网络设置', icon: 'none', }); return; } AliyunCaptchaPluginInterface.show(); }, fail: () => { AliyunCaptchaPluginInterface.show(); }, }); return true; } /** * 显示验证码弹窗(带重置) * 在接口报错后再次点击时使用,会重置插件状态 * @returns 是否成功触发 */ export function showCaptchaWithReset(): boolean { // 重置插件状态,清除已验证的状态 resetAliyunPlugin(); if (isCountingDown()) { return false; } // 重新初始化后显示 initPlugin(); AliyunCaptchaPluginInterface.show(); return true; } /** * 检查是否正在倒计时 * @returns 是否正在倒计时 */ export function isCountingDown(): boolean { return timer !== null; } /** * 清除倒计时 * 完全重置所有状态,可在页面卸载或需要完全重置时调用 */ export function clearCountdown() { resetCaptchaState(); } /** * 获取验证码插件接口 * @returns 插件接口 */ export function getCaptchaPlugin() { if (!AliyunCaptchaPluginInterface) { initPlugin(); } return AliyunCaptchaPluginInterface; } /** * 页面卸载时清理资源 */ export function destroyCaptcha() { clearCountdown(); pageInstance = null; currentOptions = null; }