|
| 1 | +import { useCallback, type DependencyList } from 'react' |
| 2 | +import { showMessage } from '@src/utils/message' |
| 3 | +import { RequestUtils } from '@src/service/request' |
| 4 | + |
| 5 | +type AnyFn = (...args: any[]) => any |
| 6 | +type TipHandler = (val: any) => void |
| 7 | + |
| 8 | +type UseAsyncTipOptions = { |
| 9 | + loadingText?: string |
| 10 | + successText?: string |
| 11 | + onError?: TipHandler |
| 12 | +} |
| 13 | + |
| 14 | +const isBusinessEnvelope = (val: any) => { |
| 15 | + return !!(val && typeof val === 'object' && Object.hasOwn(val, 'code')) |
| 16 | +} |
| 17 | + |
| 18 | +const isOk = (res: any) => { |
| 19 | + if (!res || typeof res !== 'object') return !!res |
| 20 | + // 兼容 { success: true/false } |
| 21 | + if (Object.hasOwn(res, 'success')) return !!res.success |
| 22 | + // 兼容 { code: 0/200 } |
| 23 | + if (isBusinessEnvelope(res)) return res.code === 0 || res.code === 200 |
| 24 | + // 兼容 axios error 分支可能返回 { status: 4xx/5xx, data: {...} } |
| 25 | + if (Object.hasOwn(res, 'status')) return res.status >= 200 && res.status < 300 |
| 26 | + return true |
| 27 | +} |
| 28 | + |
| 29 | +const getErrorMessage = (err: any) => { |
| 30 | + if (!err) return '请求失败' |
| 31 | + if (typeof err === 'string') return err |
| 32 | + |
| 33 | + // request.js 的自定义错误可能带 status / code / response |
| 34 | + const status = err.status || err.response?.status |
| 35 | + if (typeof status === 'number') { |
| 36 | + return err.message || RequestUtils.getHttpErrorMessage(status) || '请求失败' |
| 37 | + } |
| 38 | + |
| 39 | + // axios 网络错误会有 code |
| 40 | + if (typeof err.code === 'string') { |
| 41 | + return err.message || RequestUtils.getNetworkErrorMessage(err.code) || '网络连接异常' |
| 42 | + } |
| 43 | + |
| 44 | + // axios interceptor 里可能 resolve(err.response) 或抛出 { data: ... } |
| 45 | + const data = err.data || err.response?.data |
| 46 | + if (data && typeof data === 'object') { |
| 47 | + return data.message || data.msg || data.error || data.code || err.message || '请求失败' |
| 48 | + } |
| 49 | + |
| 50 | + return err.message || err.msg || (err.error && (err.error.message || err.error.code)) || '请求失败' |
| 51 | +} |
| 52 | + |
| 53 | +const shouldShowErrorByDefault = (err: any) => { |
| 54 | + // request.js/http.js 默认会 showError/isShowError=true 并自行 showMessage.error |
| 55 | + // 这里尽量避免重复弹错:如果错误包含明显的 service 痕迹(status/code/response),默认不再重复提示 |
| 56 | + if (err && typeof err === 'object') { |
| 57 | + if ('response' in err || 'status' in err || 'code' in err) return false |
| 58 | + } |
| 59 | + return true |
| 60 | +} |
| 61 | + |
| 62 | +export default (fn: AnyFn, options?: UseAsyncTipOptions, deps?: DependencyList) => { |
| 63 | + const callback = useCallback( |
| 64 | + (...args: any[]) => { |
| 65 | + const key = `async-tip-${Date.now()}-${Math.random().toString(16).slice(2)}` |
| 66 | + const loadingText = options?.loadingText || '加载中...' |
| 67 | + showMessage.loading(loadingText, key) |
| 68 | + |
| 69 | + let handled = false |
| 70 | + |
| 71 | + return Promise.resolve() |
| 72 | + .then(() => fn(...args)) |
| 73 | + .then((res) => { |
| 74 | + if (isOk(res)) { |
| 75 | + showMessage.open({ |
| 76 | + type: 'success', |
| 77 | + content: options?.successText || '完成', |
| 78 | + key, |
| 79 | + duration: 0.5, |
| 80 | + }) |
| 81 | + return res |
| 82 | + } |
| 83 | + |
| 84 | + handled = true |
| 85 | + if (typeof options?.onError === 'function') { |
| 86 | + showMessage.destroy(key) |
| 87 | + options.onError(res) |
| 88 | + } else if (shouldShowErrorByDefault(res)) { |
| 89 | + showMessage.open({ type: 'error', content: getErrorMessage(res), key, duration: 2 }) |
| 90 | + } else { |
| 91 | + showMessage.destroy(key) |
| 92 | + } |
| 93 | + return Promise.reject(res) |
| 94 | + }) |
| 95 | + .catch((err) => { |
| 96 | + if (!handled) { |
| 97 | + if (typeof options?.onError === 'function') { |
| 98 | + showMessage.destroy(key) |
| 99 | + options.onError(err) |
| 100 | + } else if (shouldShowErrorByDefault(err)) { |
| 101 | + showMessage.open({ type: 'error', content: getErrorMessage(err), key, duration: 2 }) |
| 102 | + } else { |
| 103 | + showMessage.destroy(key) |
| 104 | + } |
| 105 | + } |
| 106 | + return Promise.reject(err) |
| 107 | + }) |
| 108 | + }, |
| 109 | + Array.isArray(deps) ? deps : [fn, options] |
| 110 | + ) |
| 111 | + return callback |
| 112 | +} |
0 commit comments