10 changed files with 456 additions and 32 deletions
|
After Width: | Height: | Size: 4.7 KiB |
@ -0,0 +1,350 @@ |
|||||||
|
/** |
||||||
|
* 阿里云验证码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<string, any>; |
||||||
|
} |
||||||
|
|
||||||
|
// 验证码配置选项
|
||||||
|
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; |
||||||
|
} |
||||||
@ -0,0 +1,63 @@ |
|||||||
|
/** |
||||||
|
* 网络状态管理工具 |
||||||
|
* 监听网络变化,断网时提示,恢复时引导刷新 |
||||||
|
*/ |
||||||
|
|
||||||
|
interface NetworkStatus { |
||||||
|
isConnected: boolean; |
||||||
|
networkType: string; |
||||||
|
} |
||||||
|
|
||||||
|
let networkListener: WechatMiniprogram.OnNetworkStatusChangeListener | null = null; |
||||||
|
let isOfflineShown = false; |
||||||
|
let isRecoveryShown = false; |
||||||
|
|
||||||
|
function showOfflineTip() { |
||||||
|
if (isOfflineShown) return; |
||||||
|
isOfflineShown = true; |
||||||
|
wx.showModal({ |
||||||
|
title: '网络连接失败', |
||||||
|
content: '当前网络不可用,请检查网络设置', |
||||||
|
showCancel: false, |
||||||
|
confirmText: '知道了', |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function showRecoveryTip() { |
||||||
|
if (isRecoveryShown) return; |
||||||
|
isRecoveryShown = true; |
||||||
|
wx.showModal({ |
||||||
|
title: '网络已恢复', |
||||||
|
content: '网络连接已恢复,是否刷新小程序?', |
||||||
|
confirmText: '立即刷新', |
||||||
|
cancelText: '暂不刷新', |
||||||
|
success: (res) => { |
||||||
|
if (res.confirm) { |
||||||
|
wx.reLaunch({ url: '/pages/home/index' }); |
||||||
|
} |
||||||
|
setTimeout(() => { |
||||||
|
isRecoveryShown = false; |
||||||
|
}, 3000); |
||||||
|
}, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
export function initNetworkMonitor(): void { |
||||||
|
wx.getNetworkType({ |
||||||
|
success: (res) => { |
||||||
|
if (res.networkType === 'none') showOfflineTip(); |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
networkListener = (res: NetworkStatus) => { |
||||||
|
if (res.isConnected) { |
||||||
|
isOfflineShown = false; |
||||||
|
showRecoveryTip(); |
||||||
|
} else { |
||||||
|
isRecoveryShown = false; |
||||||
|
showOfflineTip(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
wx.onNetworkStatusChange(networkListener); |
||||||
|
} |
||||||
Loading…
Reference in new issue