116 lines
3.9 KiB
JavaScript
116 lines
3.9 KiB
JavaScript
/**
|
||
* 表单提交 loading / 防重复点击 / 「未发起请求」超时守卫。
|
||
* 为什么抽成公共模块:门店录入与医生录入提交流程一致,避免两页各自维护一套定时器与异常收尾逻辑。
|
||
*/
|
||
|
||
/** 点击后若超过该毫秒仍未真正发起请求,则取消 loading 并提示 */
|
||
export const SUBMIT_PREP_TIMEOUT_MS = 10000
|
||
|
||
/**
|
||
* 清理「未发起请求」倒计时,避免页面销毁或重复 begin 时残留定时器。
|
||
* @param {object} ctx 页面/组件实例(需挂 _submitPrepTimer)
|
||
*/
|
||
function clearPrepTimer(ctx) {
|
||
if (ctx && ctx._submitPrepTimer) {
|
||
clearTimeout(ctx._submitPrepTimer)
|
||
ctx._submitPrepTimer = null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 开始提交:立刻 loading + 防重复 + 启动 10s「未发起请求」倒计时。
|
||
* @param {object} ctx 需有 data.submitting
|
||
* @param {{ title?: string }} [options]
|
||
* @returns {boolean} false 表示已在提交中,调用方应直接 return
|
||
*/
|
||
export function beginSubmit(ctx, options = {}) {
|
||
if (!ctx || ctx.submitting) return false
|
||
ctx.submitting = true
|
||
ctx._submitRequestStarted = false
|
||
clearPrepTimer(ctx)
|
||
uni.showLoading({ title: options.title || '提交中...', mask: true })
|
||
// 10 秒内若仍未 markRequestStarted,说明卡在校验/组 payload,需收起 loading 并友好提示
|
||
ctx._submitPrepTimer = setTimeout(() => {
|
||
ctx._submitPrepTimer = null
|
||
if (ctx._submitRequestStarted) return
|
||
endSubmit(ctx, { toast: '提交超时,请稍后重试' })
|
||
}, SUBMIT_PREP_TIMEOUT_MS)
|
||
return true
|
||
}
|
||
|
||
/**
|
||
* 标记已真正发起 API 请求:清除「未发起请求」倒计时,loading 继续等待接口返回。
|
||
* @param {object} ctx
|
||
*/
|
||
export function markRequestStarted(ctx) {
|
||
if (!ctx) return
|
||
ctx._submitRequestStarted = true
|
||
clearPrepTimer(ctx)
|
||
}
|
||
|
||
/**
|
||
* 结束提交:清定时器、复位 submitting;默认关 loading,可选 toast。
|
||
* 校验失败时 showError 已 showToast(会顶掉 loading),须传 skipHideLoading,否则 hideLoading 会把 toast 清掉。
|
||
* @param {object} ctx
|
||
* @param {{ toast?: string, skipHideLoading?: boolean }} [options]
|
||
*/
|
||
export function endSubmit(ctx, options = {}) {
|
||
if (!ctx) return
|
||
clearPrepTimer(ctx)
|
||
ctx._submitRequestStarted = false
|
||
ctx.submitting = false
|
||
if (!options.skipHideLoading) {
|
||
try {
|
||
uni.hideLoading()
|
||
} catch (e) {
|
||
// hideLoading 在无 loading 时偶发抛错,忽略以免二次异常
|
||
}
|
||
}
|
||
if (options.toast) {
|
||
uni.showToast({ title: options.toast, icon: 'none' })
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 统一跑完「校验 → 发请求 → 成功/失败/异常收尾」。
|
||
* @param {object} ctx
|
||
* @param {{
|
||
* validate: () => boolean,
|
||
* request: () => Promise<{ ok?: boolean, res?: any, msg?: string, data?: any }>,
|
||
* onSuccess: (w: any) => void,
|
||
* loadingTitle?: string,
|
||
* }} options
|
||
*/
|
||
export function runSubmit(ctx, options) {
|
||
const { validate, request, onSuccess, loadingTitle } = options || {}
|
||
if (!beginSubmit(ctx, { title: loadingTitle })) return
|
||
try {
|
||
if (typeof validate === 'function' && !validate()) {
|
||
// 校验失败:showError 已 toast(顶掉 loading),只复位状态,勿再 hideLoading
|
||
endSubmit(ctx, { skipHideLoading: true })
|
||
return
|
||
}
|
||
markRequestStarted(ctx)
|
||
const reqPromise = typeof request === 'function' ? request() : null
|
||
if (!reqPromise || typeof reqPromise.then !== 'function') {
|
||
endSubmit(ctx, { toast: '提交异常,请稍后重试' })
|
||
return
|
||
}
|
||
reqPromise
|
||
.then((w) => {
|
||
if (w && w.ok) {
|
||
endSubmit(ctx)
|
||
if (typeof onSuccess === 'function') onSuccess(w)
|
||
return
|
||
}
|
||
const msg = (w && w.res && w.res.msg) || (w && w.msg) || '提交失败'
|
||
endSubmit(ctx, { toast: msg })
|
||
})
|
||
.catch(() => {
|
||
endSubmit(ctx, { toast: '提交异常,请稍后重试' })
|
||
})
|
||
} catch (e) {
|
||
endSubmit(ctx, { toast: '提交异常,请稍后重试' })
|
||
}
|
||
}
|