diff --git a/openapi-ts-request.config.ts b/openapi-ts-request.config.ts index fd2fe11..37216cf 100644 --- a/openapi-ts-request.config.ts +++ b/openapi-ts-request.config.ts @@ -5,8 +5,8 @@ export default defineConfig([ describe: 'unibest-openapi-test', schemaPath: 'https://ukw0y1.laf.run/unibest-opapi-test.json', serversPath: './src/service', - requestLibPath: `import request from '@/http/vue-query';\n import { CustomRequestOptions } from '@/http/types';`, - requestOptionsType: 'CustomRequestOptions', + requestLibPath: `import request from '@/http/vue-query';\n import { CustomRequestOptions_ } from '@/http/types';`, + requestOptionsType: 'CustomRequestOptions_', isGenReactQuery: false, reactQueryMode: 'vue', isGenJavaScript: false, diff --git a/src/http/alova.ts b/src/http/alova.ts index 133f6af..b7b9ff6 100644 --- a/src/http/alova.ts +++ b/src/http/alova.ts @@ -4,7 +4,7 @@ import AdapterUniapp from '@alova/adapter-uniapp' import { createAlova } from 'alova' import { createServerTokenAuthentication } from 'alova/client' import VueHook from 'alova/vue' -import { LOGIN_PAGE } from '@/router/config' +import { toLoginPage } from '@/utils/toLoginPage' import { ContentTypeEnum, ResultEnum, ShowMessage } from './tools/enum' // 配置动态Tag @@ -31,7 +31,7 @@ const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthenticati } catch (error) { // 切换到登录页 - await uni.reLaunch({ url: LOGIN_PAGE }) + toLoginPage({ mode: 'reLaunch' }) throw error } }, diff --git a/src/http/http.ts b/src/http/http.ts index 2b736cd..88f8508 100644 --- a/src/http/http.ts +++ b/src/http/http.ts @@ -1,9 +1,9 @@ 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 { toLoginPage } from '@/utils/toLoginPage' import { ResultEnum } from './tools/enum' // 刷新 token 状态管理 @@ -32,7 +32,7 @@ export function http(options: CustomRequestOptions) { if (!isDoubleTokenMode) { // 未启用双token策略,清理用户信息,跳转到登录页 tokenStore.logout() - uni.navigateTo({ url: LOGIN_PAGE }) + toLoginPage() return reject(res) } @@ -80,7 +80,7 @@ export function http(options: CustomRequestOptions) { await tokenStore.logout() // 跳转到登录页 setTimeout(() => { - uni.navigateTo({ url: LOGIN_PAGE }) + toLoginPage() }, 2000) } finally { diff --git a/src/http/types.ts b/src/http/types.ts index 42ac8e9..8187822 100644 --- a/src/http/types.ts +++ b/src/http/types.ts @@ -7,6 +7,9 @@ export type CustomRequestOptions = UniApp.RequestOptions & { hideErrorToast?: boolean } & IUniUploadFileOptions // 添加uni.uploadFile参数类型 +/** 主要提供给 openapi-ts-request 生成的代码使用 */ +export type CustomRequestOptions_ = Omit + export interface HttpRequestResult { promise: Promise requestTask: UniApp.RequestTask diff --git a/src/router/interceptor.ts b/src/router/interceptor.ts index 714fea4..f87a2d8 100644 --- a/src/router/interceptor.ts +++ b/src/router/interceptor.ts @@ -7,6 +7,7 @@ import { isMp } from '@uni-helper/uni-env' import { useTokenStore } from '@/store/token' import { isPageTabbar, tabbarStore } from '@/tabbar/store' import { getAllPages, getLastPage, HOME_PAGE, parseUrlToObj } from '@/utils/index' +import { toLoginPage } from '@/utils/toLoginPage' import { EXCLUDE_LOGIN_PATH_LIST, isNeedLoginMode, LOGIN_PAGE, LOGIN_PAGE_ENABLE_IN_MP, NOT_FOUND_PAGE } from './config' export const FG_LOG_ENABLE = false @@ -83,7 +84,7 @@ export const navigateToInterceptor = { if (Object.keys(myQuery).length) { fullPath += `?${Object.keys(myQuery).map(key => `${key}=${myQuery[key]}`).join('&')}` } - const redirectUrl = `${LOGIN_PAGE}?redirect=${encodeURIComponent(fullPath)}` + const redirectQuery = `?redirect=${encodeURIComponent(fullPath)}` // #region 1/2 默认需要登录的情况(白名单策略) --------------------------- if (isNeedLoginMode) { @@ -96,8 +97,8 @@ export const navigateToInterceptor = { if (path === LOGIN_PAGE) { return true // 明确表示允许路由继续执行 } - FG_LOG_ENABLE && console.log('1 isNeedLogin(白名单策略) redirectUrl:', redirectUrl) - uni.navigateTo({ url: redirectUrl }) + FG_LOG_ENABLE && console.log('1 isNeedLogin(白名单策略) url:', fullPath) + toLoginPage({ queryString: redirectQuery }) return false // 明确表示阻止原路由继续执行 } } @@ -107,8 +108,8 @@ export const navigateToInterceptor = { else { // 不需要登录里面的 EXCLUDE_LOGIN_PATH_LIST 表示黑名单,需要重定向到登录页 if (judgeIsExcludePath(path)) { - FG_LOG_ENABLE && console.log('2 isNeedLogin(黑名单策略) redirectUrl:', redirectUrl) - uni.navigateTo({ url: redirectUrl }) + FG_LOG_ENABLE && console.log('2 isNeedLogin(黑名单策略) url:', fullPath) + toLoginPage({ queryString: redirectQuery }) return false // 修改为false,阻止原路由继续执行 } return true // 明确表示允许路由继续执行 diff --git a/src/service/info.ts b/src/service/info.ts index 8ef6951..fe09da5 100644 --- a/src/service/info.ts +++ b/src/service/info.ts @@ -1,12 +1,12 @@ /* eslint-disable */ // @ts-ignore import request from '@/http/vue-query'; -import { CustomRequestOptions } from '@/http/types'; +import { CustomRequestOptions_ } from '@/http/types'; import * as API from './types'; /** 用户信息 GET /user/info */ -export function infoUsingGet({ options }: { options?: CustomRequestOptions }) { +export function infoUsingGet({ options }: { options?: CustomRequestOptions_ }) { return request('/user/info', { method: 'GET', ...(options || {}), diff --git a/src/service/listAll.ts b/src/service/listAll.ts index 5847f44..bc1c683 100644 --- a/src/service/listAll.ts +++ b/src/service/listAll.ts @@ -1,7 +1,7 @@ /* eslint-disable */ // @ts-ignore import request from '@/http/vue-query'; -import { CustomRequestOptions } from '@/http/types'; +import { CustomRequestOptions_ } from '@/http/types'; import * as API from './types'; @@ -9,7 +9,7 @@ import * as API from './types'; export function listAllUsingGet({ options, }: { - options?: CustomRequestOptions; + options?: CustomRequestOptions_; }) { return request('/user/listAll', { method: 'GET', diff --git a/src/utils/debounce.ts b/src/utils/debounce.ts new file mode 100644 index 0000000..c13f470 --- /dev/null +++ b/src/utils/debounce.ts @@ -0,0 +1,166 @@ +// fork from https://github.com/toss/es-toolkit/blob/main/src/function/debounce.ts +// 文档可查看:https://es-toolkit.dev/reference/function/debounce.html +// 如需要 throttle 功能,可 copy https://github.com/toss/es-toolkit/blob/main/src/function/throttle.ts + +interface DebounceOptions { + /** + * An optional AbortSignal to cancel the debounced function. + */ + signal?: AbortSignal + + /** + * An optional array specifying whether the function should be invoked on the leading edge, trailing edge, or both. + * If `edges` includes "leading", the function will be invoked at the start of the delay period. + * If `edges` includes "trailing", the function will be invoked at the end of the delay period. + * If both "leading" and "trailing" are included, the function will be invoked at both the start and end of the delay period. + * @default ["trailing"] + */ + edges?: Array<'leading' | 'trailing'> +} + +export interface DebouncedFunction void> { + (...args: Parameters): void + + /** + * Schedules the execution of the debounced function after the specified debounce delay. + * This method resets any existing timer, ensuring that the function is only invoked + * after the delay has elapsed since the last call to the debounced function. + * It is typically called internally whenever the debounced function is invoked. + * + * @returns {void} + */ + schedule: () => void + + /** + * Cancels any pending execution of the debounced function. + * This method clears the active timer and resets any stored context or arguments. + */ + cancel: () => void + + /** + * Immediately invokes the debounced function if there is a pending execution. + * This method executes the function right away if there is a pending execution. + */ + flush: () => void +} + +/** + * Creates a debounced function that delays invoking the provided function until after `debounceMs` milliseconds + * have elapsed since the last time the debounced function was invoked. The debounced function also has a `cancel` + * method to cancel any pending execution. + * + * @template F - The type of function. + * @param {F} func - The function to debounce. + * @param {number} debounceMs - The number of milliseconds to delay. + * @param {DebounceOptions} options - The options object + * @param {AbortSignal} options.signal - An optional AbortSignal to cancel the debounced function. + * @returns A new debounced function with a `cancel` method. + * + * @example + * const debouncedFunction = debounce(() => { + * console.log('Function executed'); + * }, 1000); + * + * // Will log 'Function executed' after 1 second if not called again in that time + * debouncedFunction(); + * + * // Will not log anything as the previous call is canceled + * debouncedFunction.cancel(); + * + * // With AbortSignal + * const controller = new AbortController(); + * const signal = controller.signal; + * const debouncedWithSignal = debounce(() => { + * console.log('Function executed'); + * }, 1000, { signal }); + * + * debouncedWithSignal(); + * + * // Will cancel the debounced function call + * controller.abort(); + */ +export function debounce void>( + func: F, + debounceMs: number, + { signal, edges }: DebounceOptions = {}, +): DebouncedFunction { + let pendingThis: any + let pendingArgs: Parameters | null = null + + const leading = edges != null && edges.includes('leading') + const trailing = edges == null || edges.includes('trailing') + + const invoke = () => { + if (pendingArgs !== null) { + func.apply(pendingThis, pendingArgs) + pendingThis = undefined + pendingArgs = null + } + } + + const onTimerEnd = () => { + if (trailing) { + invoke() + } + + // eslint-disable-next-line ts/no-use-before-define + cancel() + } + + let timeoutId: ReturnType | null = null + + const schedule = () => { + if (timeoutId != null) { + clearTimeout(timeoutId) + } + + timeoutId = setTimeout(() => { + timeoutId = null + + onTimerEnd() + }, debounceMs) + } + + const cancelTimer = () => { + if (timeoutId !== null) { + clearTimeout(timeoutId) + timeoutId = null + } + } + + const cancel = () => { + cancelTimer() + pendingThis = undefined + pendingArgs = null + } + + const flush = () => { + invoke() + } + + const debounced = function (this: any, ...args: Parameters) { + if (signal?.aborted) { + return + } + + // eslint-disable-next-line ts/no-this-alias + pendingThis = this + pendingArgs = args + + const isFirstCall = timeoutId == null + + schedule() + + if (leading && isFirstCall) { + invoke() + } + } + + debounced.schedule = schedule + debounced.cancel = cancel + debounced.flush = flush + + signal?.addEventListener('abort', cancel, { once: true }) + + return debounced +} diff --git a/src/utils/toLoginPage.ts b/src/utils/toLoginPage.ts new file mode 100644 index 0000000..5e2d316 --- /dev/null +++ b/src/utils/toLoginPage.ts @@ -0,0 +1,42 @@ +import { LOGIN_PAGE } from '@/router/config' +import { getLastPage } from '@/utils' +import { debounce } from '@/utils/debounce' + +interface ToLoginPageOptions { + /** + * 跳转模式, uni.navigateTo | uni.reLaunch + * @default 'navigateTo' + */ + mode?: 'navigateTo' | 'reLaunch' + /** + * 查询参数 + * @example '?redirect=/pages/home/index' + */ + queryString?: string +} + +/** + * 跳转到登录页, 带防抖处理 + * + * 如果要立即跳转,不做延时,可以使用 `toLoginPage.flush()` 方法 + */ +export const toLoginPage = debounce((options: ToLoginPageOptions = {}) => { + const { mode = 'navigateTo', queryString = '' } = options + + const url = `${LOGIN_PAGE}${queryString}` + + // 获取当前页面路径 + const currentPage = getLastPage() + const currentPath = `/${currentPage.route}` + // 如果已经在登录页,则不跳转 + if (currentPath === LOGIN_PAGE) { + return + } + + if (mode === 'navigateTo') { + uni.navigateTo({ url }) + } + else { + uni.reLaunch({ url }) + } +}, 500)