diff --git a/src/hooks/useRequest.ts b/src/hooks/useRequest.ts index 1290dbd..8ac4bfe 100644 --- a/src/hooks/useRequest.ts +++ b/src/hooks/useRequest.ts @@ -1,5 +1,4 @@ import type { Ref } from 'vue' -import type { HttpRequestResult } from '@/http/types' import { ref } from 'vue' interface IUseRequestOptions { @@ -14,7 +13,6 @@ interface IUseRequestReturn { error: Ref data: Ref run: (args?: P) => Promise - cancel: () => void } /** @@ -26,79 +24,31 @@ interface IUseRequestReturn { * @returns 返回一个对象{loading, error, data, run},包含请求的加载状态、错误信息、响应数据和手动触发请求的函数。 */ export default function useRequest( - func: (args?: P) => Promise | Promise> | HttpRequestResult | T, + func: (args?: P) => Promise, options: IUseRequestOptions = { immediate: false }, ): IUseRequestReturn { const loading = ref(false) - const error = ref(false) + const error = ref(false) const data = ref(options.initialData) as Ref - let requestTask: UniApp.RequestTask | undefined - const isCancelled = ref(false) - - const run = async (args?: P): Promise => { + const run = async (args?: P) => { loading.value = true - error.value = false - isCancelled.value = false - let promise: Promise - const result = func(args) - - if (result instanceof Promise) { - promise = result.then((res) => { - if (res && typeof (res as HttpRequestResult).promise === 'object' && typeof (res as HttpRequestResult).requestTask === 'object') { - const { promise: p, requestTask: task } = res as HttpRequestResult - requestTask = task - if (isCancelled.value) { - task.abort() - throw new Error('Request cancelled') - } - return p - } - if (isCancelled.value) { - throw new Error('Request cancelled') - } - return res as T | undefined - }) as Promise - } - else if (result && typeof (result as HttpRequestResult).promise === 'object' && typeof (result as HttpRequestResult).requestTask === 'object') { - const { promise: p, requestTask: task } = result as HttpRequestResult - requestTask = task - promise = p - } - else { - promise = Promise.resolve(result as T | undefined) - } - - return promise + return func(args) .then((res) => { - if (isCancelled.value) { - return - } data.value = res + error.value = false return data.value }) .catch((err) => { - if (!isCancelled.value) { - error.value = err - throw err - } - return Promise.resolve(undefined) + error.value = err + throw err }) .finally(() => { loading.value = false }) } - const cancel = () => { - isCancelled.value = true - if (requestTask) { - requestTask.abort() - } - loading.value = false - error.value = new Error('Request cancelled') - } - if (options.immediate) { - (run as (args?: P) => Promise)({} as P) + (run as (args: P) => Promise)({} as P) } - return { loading, error, data, run, cancel } + return { loading, error, data, run } } diff --git a/src/http/http.ts b/src/http/http.ts index 147f532..98c7324 100644 --- a/src/http/http.ts +++ b/src/http/http.ts @@ -1,96 +1,26 @@ -import type { IDoubleTokenRes } from '@/api/types/login' -import type { CustomRequestOptions, IResponse } from '@/http/types' -import { nextTick } from 'vue' -import { LOGIN_PAGE } from '@/router/config' -import { useTokenStore } from '@/store/token' -import { isDoubleTokenMode } from '@/utils' -import { ResultEnum } from './tools/enum' - -// 刷新 token 状态管理 -let refreshing = false // 防止重复刷新 token 标识 -let taskQueue: { resolve: (value: any) => void, reject: (reason?: any) => void, options: CustomRequestOptions }[] = [] as { resolve: (value: any) => void, reject: (reason?: any) => void, options: CustomRequestOptions }[] // 刷新 token 请求队列 +import type { CustomRequestOptions } from '@/http/types' export function http(options: CustomRequestOptions) { - let requestTask: UniApp.RequestTask | undefined - const promise = new Promise((resolve, reject) => { - requestTask = uni.request({ + // 1. 返回 Promise 对象 + return new Promise>((resolve, reject) => { + uni.request({ ...options, dataType: 'json', // #ifndef MP-WEIXIN responseType: 'json', // #endif // 响应成功 - success: async (res) => { + success(res) { // 状态码 2xx,参考 axios 的设计 if (res.statusCode >= 200 && res.statusCode < 300) { - // 2.1 处理业务逻辑错误 - const { code = 0, message = '', msg = '', data = null } = res.data as IResponse - // 0和200当做成功都很普遍,这里直接兼容两者,见 ResultEnum - if (code !== ResultEnum.Success0 && code !== ResultEnum.Success200) { - throw new Error(`请求错误[${code}]:${message || msg}`) - } - return resolve(data as T) + // 2.1 提取核心数据 res.data + resolve(res.data as IResData) } - const resData: IResData = res.data as IResData - if ((res.statusCode === 401) || (resData.code === 401)) { - const tokenStore = useTokenStore() - if (!isDoubleTokenMode) { - // 未启用双token策略,清理用户信息,跳转到登录页 - tokenStore.logout() - uni.navigateTo({ url: LOGIN_PAGE }) - return reject(res) - } - /* -------- 无感刷新 token ----------- */ - const { refreshToken } = tokenStore.tokenInfo as IDoubleTokenRes || {} - // token 失效的,且有刷新 token 的,才放到请求队列里 - if ((res.statusCode === 401 || resData.code === 401) && refreshToken) { - taskQueue.push({ resolve, reject, options }) - } - // 如果有 refreshToken 且未在刷新中,发起刷新 token 请求 - if ((res.statusCode === 401 || resData.code === 401) && refreshToken && !refreshing) { - refreshing = true - try { - // 发起刷新 token 请求(使用 store 的 refreshToken 方法) - await tokenStore.refreshToken() - // 刷新 token 成功 - refreshing = false - nextTick(() => { - // 关闭其他弹窗 - uni.hideToast() - uni.showToast({ - title: 'token 刷新成功', - icon: 'none', - }) - }) - // 将任务队列的所有任务重新请求 - taskQueue.forEach((task) => { - http(task.options).promise.then(task.resolve, task.reject) - }) - } - catch (refreshErr) { - console.error('刷新 token 失败:', refreshErr) - refreshing = false - // 刷新 token 失败,跳转到登录页 - nextTick(() => { - // 关闭其他弹窗 - uni.hideToast() - uni.showToast({ - title: '登录已过期,请重新登录', - icon: 'none', - }) - }) - // 清除用户信息 - await tokenStore.logout() - // 跳转到登录页 - setTimeout(() => { - uni.navigateTo({ url: LOGIN_PAGE }) - }, 2000) - } - finally { - // 不管刷新 token 成功与否,都清空任务队列 - taskQueue = [] - } - } + else if (res.statusCode === 401) { + // 401错误 -> 清理用户信息,跳转到登录页 + // userStore.clearUserInfo() + // uni.navigateTo({ url: '/pages/login/login' }) + reject(res) } else { // 其他错误 -> 根据后端错误信息轻提示 @@ -103,12 +33,7 @@ export function http(options: CustomRequestOptions) { } }, // 响应失败 - fail(err: UniApp.RequestSuccessCallbackResult | UniApp.GeneralCallbackResult) { - console.log(`🚀 - fail - err:`, err) - // 如果是请求取消,则不显示错误提示 - if (err.errMsg === 'request:fail abort') { - return reject(new Error('Request cancelled')) - } + fail(err) { uni.showToast({ icon: 'none', title: '网络错误,换个网络试试', @@ -117,7 +42,6 @@ export function http(options: CustomRequestOptions) { }, }) }) - return { promise, requestTask: requestTask! } } /** @@ -182,7 +106,6 @@ export function httpDelete(url: string, query?: Record, header?: }) } -// 支持与 axios 类似的API调用 http.get = httpGet http.post = httpPost http.put = httpPut diff --git a/src/pages/about/components/request.vue b/src/pages/about/components/request.vue index 2196a2a..f36f759 100644 --- a/src/pages/about/components/request.vue +++ b/src/pages/about/components/request.vue @@ -8,7 +8,7 @@ import { getFooAPI } from '@/api/foo' // } const initialData = undefined -const { loading, error, data, run, cancel } = useRequest(() => getFooAPI('菲鸽'), { +const { loading, error, data, run } = useRequest(() => getFooAPI('菲鸽'), { immediate: true, initialData, })