UNPKG

vite-uni-dev-tool

Version:

vite-uni-dev-tool, debug, uni-app, 一处编写,到处调试

574 lines (506 loc) 15.1 kB
import { onMounted, ref, shallowReactive, watch, type Ref } from 'vue'; import { debounce, throttle } from './utils'; // 添加分页响应数据类型 // type PaginationResponse<T = any> = { // list?: T[]; // data?: T[]; // total: number; // current: number; // pageSize: number; // }; type Option<R, P> = { /** 是否需要uni.showLoading */ useUniLoading?: boolean; /** * 每次 ready 从 false 变为 true 时, 都会自动发起请求 * 为 false 时,请求永远都不会发出 */ ready?: boolean | Ref<boolean>; /** 立即执行 */ manual?: boolean; /** * 默认参数 * 分页参数在请放入 分页参数配置中 */ defaultParams?: P; /** 依赖刷新 */ refreshDeps?: any[]; /** * 延迟结束loading * 单位毫秒 * 防止请求过快造成渲染闪烁 */ loadingDelay?: number; /** * 轮询间隔 * 通过 run/runAsync 来启动轮询 * 通过 cancel 取消轮询 */ pollingInterval?: number; /** * 轮询错误重试次数 */ pollingErrorRetryCount?: number; /** * 错误重试次数 */ errorRetryCount?: number; /** * 错误重试间隔 */ errorRetryDelay?: number; /** * 防抖等待时间 */ debounceWait?: number; /** * 在防抖开始前执行调用 */ debounceLeading?: boolean; /** * 在防抖结束后执行调用 */ debounceTrailing?: boolean; /** * 节流等待时间 */ throttleWait?: number; /** * 在节流开始前执行调用 */ throttleLeading?: boolean; /** * 在节流结束后执行调用 */ throttleTrailing?: boolean; /** * 使用分页 */ usePagination?: boolean; /** * 分页配置 * */ paginationConfig?: { /** 当前页码参数名,默认 'current' */ currentKey?: string; /** 每页数量参数名,默认 'pageSize' */ pageSizeKey?: string; /** 总数字段名,默认 'total' */ totalKey?: string; /** 默认当前页,默认 1 */ defaultCurrent?: number; /** 默认每页数量,默认 10 */ defaultPageSize?: number; /** 列表字段名,默认 'list' */ listKey?: string; }; /** 请求前 */ onBefore?: () => Promise<void> | void; /** 请求成功 */ onSuccess?: (result: R | undefined, params?: P) => void; /** 请求失败 */ onError?: (error: any) => void; /** 请求完成 */ onFinally?: () => void; }; export type Service<R, P extends any[]> = (...args: P) => Promise<R>; const useRequest = <R, P extends any[]>( service: Service<R, P>, option?: Option<R, P>, ) => { const loading = ref(false); const data = ref<R>(); /** * 启用 usePagination 之后每页数据合并之后的结果 * 为开启默认为 [] * 只适合从第一页开始获取数据 */ const list = ref<R[]>([]); const pagination = shallowReactive({ current: 1, pageSize: 10, total: 0, totalPage: 0, hasMore: true, // 分页操作方法 setCurrent: (current: number) => { pagination.current = Math.max(1, current); refresh(); }, setPageSize: (pageSize: number) => { pagination.pageSize = pageSize; pagination.current = 1; // 重置到第一页 refresh(); }, reset: () => { pagination.current = option?.paginationConfig?.defaultCurrent ?? 1; pagination.pageSize = option?.paginationConfig?.defaultPageSize ?? 10; refresh(); }, prev: () => { if (pagination.current > 1) { pagination.current--; refresh(); } }, next: () => { if (pagination.current < pagination.totalPage) { pagination.current++; refresh(); } }, change: (current: number, pageSize: number) => { pagination.current = current; pagination.pageSize = pageSize; refresh(); }, }); /** 最后一次请求的参数 */ let lastParams: P | undefined; let loadingTimer: any; let intervalTimer: any; let allowPolling = true; let pollingErrorCount = 0; let errorCount = 0; let intervalErrorRetry: ReturnType<typeof setTimeout> | undefined = undefined; /** 记录上次 ready 的状态 */ let previousReady = false; /** 获取 ready 值 */ const getReadyValue = (): boolean => { if (option?.ready === undefined) return true; return typeof option.ready === 'boolean' ? option.ready : option.ready.value; }; /** * * @param params 请求参数 */ function run(...params: P) { if (getReadyValue()) { execServiceFunction(params) ?.catch((error) => { errorRetry(params); throw error; }) ?.finally(() => { if (option?.pollingInterval && option.pollingInterval > 0) { startPolling(params); } }); } } async function runAsync(...params: P) { // 只有在 ready 为 true 时才执行请求 if (getReadyValue()) { try { const result = await execServiceFunction?.(params); return result; } catch (error) { errorRetry(params); throw error; } finally { if (option?.pollingInterval && option.pollingInterval > 0) { startPolling(params); } } } } /** * 刷新 * */ function refresh() { // 只有在 ready 为 true 时才执行请求 if (getReadyValue()) { const fallbackParams: P = ((lastParams || option?.defaultParams) ?? []) as P; execServiceFunction(fallbackParams); } } /** * 刷新异步 * * @return {*} */ function refreshAsync() { // 只有在 ready 为 true 时才执行请求 if (getReadyValue()) { const fallbackParams: P = ((lastParams || option?.defaultParams) ?? []) as P; return execServiceFunction(fallbackParams); } } async function execService(params: P) { // 只有在 ready 为 true 时才执行请求 if (getReadyValue()) { try { loading.value = true; if (option?.useUniLoading) { uni.showLoading({ title: '加载中...', mask: true, }); } lastParams = params; await option?.onBefore?.(); const result = await service(...params); data.value = result; // 如果启用了分页功能,处理分页数据 if (option?.usePagination) { const config = option.paginationConfig || {}; const currentKey = config.currentKey || 'current'; const pageSizeKey = config.pageSizeKey || 'pageSize'; const totalKey = config.totalKey || 'total'; const listKey = config.listKey || 'list'; // 检查结果是否为分页格式 if (result && typeof result === 'object') { const paginationResult = result as any; pagination.current = paginationResult[currentKey] || pagination.current; pagination.pageSize = paginationResult[pageSizeKey] || pagination.pageSize; pagination.total = paginationResult[totalKey] || 0; pagination.totalPage = Math.ceil( pagination.total / pagination.pageSize, ); pagination.hasMore = pagination.current < pagination.totalPage; // 获取列表数据 const resultData = paginationResult[listKey] || paginationResult.data || paginationResult.list || []; // 根据当前页码决定是否合并列表数据 if (pagination.current === 1) { // 第一页时,替换列表 list.value = [...resultData]; } else { // 非第一页时,合并列表 list.value = [...list.value, ...resultData]; } } else { pagination.hasMore = false; } } option?.onSuccess?.(data.value, params); return data.value; } catch (error) { pollingErrorCount++; errorCount++; option?.onError?.(error); throw error; } finally { option?.onFinally?.(); // 结束loading if (option?.useUniLoading) { // 延迟结束loading if (option?.loadingDelay && option.loadingDelay > 0) { clearTimeout(loadingTimer); loadingTimer = setTimeout(() => { loading.value = false; uni.hideLoading(); }, option.loadingDelay); } else { loading.value = false; uni.hideLoading(); } } } } } const execServiceDebounce = debounce(execService, option?.debounceWait ?? 0, { leading: option?.debounceLeading, trailing: option?.debounceTrailing, }); const execServiceThrottle = throttle(execService, option?.throttleWait ?? 0, { leading: option?.throttleLeading, trailing: option?.throttleTrailing, }); function execServiceFunction(params: P) { if (option?.usePagination) { const config = option.paginationConfig || {}; const currentKey = config.currentKey || 'current'; const pageSizeKey = config.pageSizeKey || 'pageSize'; // 将分页参数注入到请求参数中 const paramsWithPagination = [...params] as any[]; // 如果参数是对象,添加分页字段 if ( paramsWithPagination.length > 0 && typeof paramsWithPagination[0] === 'object' && paramsWithPagination[0] !== null ) { paramsWithPagination[0] = { ...paramsWithPagination[0], [currentKey]: pagination.current, [pageSizeKey]: pagination.pageSize, }; } else { // 如果没有参数或参数不是对象,创建一个包含分页信息的对象 paramsWithPagination.unshift({ [currentKey]: pagination.current, [pageSizeKey]: pagination.pageSize, }); } if (option?.debounceWait) { return execServiceDebounce(paramsWithPagination as P); } else if (option?.throttleWait) { return execServiceThrottle(paramsWithPagination as P); } else { return execService(paramsWithPagination as P); } } else { if (option?.debounceWait) { return execServiceDebounce(params); } else if (option?.throttleWait) { return execServiceThrottle(params); } else { return execService(params); } } } /** * 开启轮询 * * @param {P} params */ function startPolling(params: P) { cancel(); allowPolling = true; if (!getReadyValue()) return; // 使用递归函数实现轮询,确保请求完成后才开始计时下一次请求 const poll = async () => { if (!allowPolling) return; // 检查 ready 状态 try { await execService(params); // 检查错误重试次数限制 if ( option?.pollingErrorRetryCount && pollingErrorCount >= option.pollingErrorRetryCount ) { cancel(); return; } // 如果仍然允许轮询,则设置定时器等待下次轮询 if (allowPolling) { intervalTimer = setTimeout(poll, option?.pollingInterval); } } catch (error) { // execService 内部已经处理了错误,这里只需要确保继续轮询或停止 if ( option?.pollingErrorRetryCount && pollingErrorCount >= option.pollingErrorRetryCount ) { cancel(); return; } // 如果仍然允许轮询,则设置定时器等待下次轮询 if (allowPolling) { intervalTimer = setTimeout(poll, option?.pollingInterval); } } }; // 先等待轮询间隔时间,然后再开始执行请求 intervalTimer = setTimeout(() => { // 开始轮询 poll(); }, option?.pollingInterval); } /** 取消轮询 */ function cancel() { allowPolling = false; pollingErrorCount = 0; errorCount = 0; clearInterval(intervalTimer); clearInterval(intervalErrorRetry); } /** * 错误重试 * * @param {P} params * @return {*} */ function errorRetry(params: P) { // 存在则不进入错误重试 if (option?.pollingErrorRetryCount || option?.pollingInterval) return; // 不存在 或者 次数小于 0 不进入错误重试 if (!option?.errorRetryCount || option.errorRetryCount <= 0) return; if (!getReadyValue()) return; const poll = async () => { try { await execService(params); cancel(); } catch (error) { if (option.errorRetryCount && option.errorRetryCount < errorCount) { cancel(); return; } intervalErrorRetry = setTimeout(poll, option?.errorRetryDelay ?? 0); } }; intervalErrorRetry = setTimeout(poll, option?.errorRetryDelay ?? 0); } function setData(value: R) { data.value = value; } function setLoading(value: boolean) { loading.value = value; } onMounted(() => { // 记录初始 ready 状态 previousReady = getReadyValue(); // 初始化分页配置 if (option?.usePagination) { const config = option.paginationConfig || {}; pagination.current = config.defaultCurrent ?? 1; pagination.pageSize = config.defaultPageSize ?? 10; } if (!option?.manual && option?.ready !== false) { const defaultParams = (option?.defaultParams || []) as P; run(...defaultParams); } }); // 监听 ready 参数变化 watch( () => getReadyValue(), (currentReady) => { // 只有当 ready 从 false 变为 true 时才执行请求 if (currentReady && !previousReady) { const params = (lastParams || option?.defaultParams || []) as P; // run(...params); execService(params).catch(() => { errorRetry(params); }); } else { cancel(); } // 更新 previousReady 状态 previousReady = !!currentReady; }, ); watch( () => option?.refreshDeps, () => { refresh(); }, { deep: true, }, ); return { data, setData, loading, setLoading, run, runAsync, refresh, refreshAsync, cancel, pagination: option?.usePagination ? pagination : undefined, list, }; }; export default useRequest;