不用每个请求都写获取请求 类似无感刷新逻辑 uniapp vue3 react实现方案

stores 配置获取最新token

复制代码
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { IUserInfo } from '@/types/api/user/index'
import {userApi} from "@/api/user"// ← 你自己的接口

// 城市数据类型定义
export interface ICityData {
  cityName?: string
  cityId?: string
  [key: string]: any
}

export const useUserStore = defineStore(
  'user',
  () => {
    // --- State ---
    const userData = ref<IUserInfo | null>(null)
    const token = ref<string | null>(null)
    const userCityData = ref<ICityData | null>(null)

    /** 登录中的 Promise(关键) */
    const loginPromise = ref<Promise<any> | null>(null)

    // --- Getters ---
    const isLogin = computed(() => !!token.value)

    // --- Actions ---
    function setUserData(data: IUserInfo | null) {
      userData.value = data
    }

    function setUserCityData(data: ICityData | null) {
      userCityData.value = data
    }

    function setToken(t: string | null) {
      token.value = t
    }

    function logout() {
      userData.value = null
      token.value = null
      userCityData.value = null
    }
/**
     * 包装 uni.login 为 Promise
     */
    const getWxCode = () => {
      return new Promise<string>((resolve, reject) => {
        uni.login({
          provider: 'weixin',
          success: (res) => {
			  
			  resolve(res.code)
		  },
          fail: (err) => reject(err)
        })
      })
    }
    /**
     * ✅ 登录(Promise 化)
     * - 可 await
     * - 防止多次并发调用
     */
    async function login(force = false) {
      // 1. 如果已有 Token 且非强制刷新,直接返回
      if (token.value && !force) {
        return userData.value 
      }

      // 2. 如果正在登录中,复用 Promise
      if (loginPromise.value) {
        return loginPromise.value
      }

      // 3. 开启登录流程
      loginPromise.value = (async () => {
        try {
			
          const code = await getWxCode()
          const res = await userApi.wxLogin({ code })
          // 更新状态
          token.value = res.token
          userData.value = res
          return res
        } catch (err) {
          console.error("登录失败:", err)
          // 登录失败通常需要清除过期的 token
          logout() 
          throw err
        } finally {
          // 无论成功失败,结束后清空锁
          loginPromise.value = null
        }
      })()

      return loginPromise.value
    }

    return {
      // state
      userData,
      token,
      userCityData,

      // getters
      isLogin,

      // actions
      setUserData,
      setToken,
      setUserCityData,
      logout,
      login, // ← 用这个,别再用 onLaunch 里写一堆
    }
  },
  {
    persist: {
      storage: {
        getItem: (key) => uni.getStorageSync(key),
        setItem: (key, value) => uni.setStorageSync(key, value),
      },
    },
  }
)

request.ts请求封装

复制代码
import config from '@/config/index';
import { useUserStore } from "@/stores/user";

// --- 类型定义层 ---
interface IResponse<T = any> {
    code: number;
    data: T;
    msg: string;
    isOk:boolean
}

interface IRequestOptions {
    headers?: Record<string, string>;
    [key: string]: any;
}

// --- 核心请求函数 ---
async function baseRequest<T = any>(
    url: string, 
    method: UniApp.RequestOptions['method'], 
    data?: any, 
    param: IRequestOptions = {}
): Promise<T> {
    // 1. 定义白名单(登录、注册等不需要先校验 Token 的接口路径)
    const whiteList = ['/wxLogin', '/login', '/register'];
    const isWhiteApi = whiteList.some(item => url.includes(item));
    const Url = config.base;
     const userStore = useUserStore();
	 
	   /** ✅ 登录兜底(核心) */
	   if (!isWhiteApi && !userStore.isLogin) {
	     try {
	       await userStore.login() // 利用你写的 loginPromise 锁,多次并发也只会执行一次登录
	     } catch (e) {
	       return Promise.reject(e)
	     }
	   }
  // --- 1. 处理 Header 的合并策略 ---
    const header: Record<string, string> = {
        // 设置默认 Content-Type
        'Content-Type': 'application/json', 
        // 注入 Token
        [config.tokenkey]: `Bearer ${userStore.token}` || "",
        // 展开传入的自定义 headers,如果有同名 key(如 Content-Type),会覆盖默认值
        ...(param.headers || {})
    };
   
    
    // header[config.tokenkey] = userStore.token || "";
    
    return new Promise((resolve, reject) => {
        uni.request({
            url: `${Url}/api/${url}`,
            method: method,
            header: header,
            data: data || {},
            success: (res) => {
                console.log(res,"数据----")
                const resData = res.data as IResponse<T>;
                if (resData.isOk ) {
                    resolve(resData.data);
                } else if ([401].includes(resData.code)) {
                    userStore.logout();
                    uni.clearStorageSync();
                    uni.navigateTo({ url: "/pages/home/login/index" });
                    reject(resData);
                } else {
                    uni.showToast({ title: resData.msg || '系统错误', icon: 'none' });
                    reject(resData);
                }
            },
            fail: (err) => {
                uni.showToast({ title: '网络请求失败', icon: 'none' });
                reject(err);
            }
        });
    });
}



// 1. 定义明确的方法字面量,使用 as const 锁定类型
const methods = ['GET', 'POST', 'PUT', 'DELETE'] as const;

// 2. 定义 Request 对象的接口类型,让外部调用有提示
interface IRequest {
    get: <T = any>(url: string, data?: any, opt?: IRequestOptions) => Promise<T>;
    post: <T = any>(url: string, data?: any, opt?: IRequestOptions) => Promise<T>;
    put: <T = any>(url: string, data?: any, opt?: IRequestOptions) => Promise<T>;
    delete: <T = any>(url: string, data?: any, opt?: IRequestOptions) => Promise<T>;
}

// 3. 实例化对象
const request = {} as IRequest;

// 4. 循环挂载(TS 现在知道 method 绝不为 undefined)
methods.forEach((method) => {
    const key = method.toLowerCase() as keyof IRequest;
    request[key] = <T = any>(url: string, data?: any, opt?: IRequestOptions) => 
        baseRequest<T>(url, method, data, opt);
});

export default request;

页面中正常调用就可以了

为了实现无感刷新,我们需要处理两种情况:

  1. 预检拦截:发送请求前发现没 Token(你现在已经实现了)。
  2. 响应拦截:请求发出去后,后端返回 401(Token 刚好在这一秒过期了)
相关推荐
27669582922 小时前
京东最新滑块 分析
linux·前端·javascript·h5st·京东滑块·京东m端滑块·京东逆向
拖拉斯旋风2 小时前
🧠 `useRef`:React 中“默默记住状态却不打扰 UI”的利器
前端·javascript·react.js
POLITE32 小时前
Leetcode 3.无重复字符的最长子串 JavaScript (Day 4)
javascript·算法·leetcode
kuilaurence2 小时前
Node.js 中使用env文件
javascript
DongHao2 小时前
跨域问题及解决方案
前端·javascript·面试
持续升级打怪中2 小时前
Vue项目中Axios全面封装实战指南
前端·javascript·vue.js
百罹鸟2 小时前
【react 高频面试题—核心原理篇】:useEffect 的依赖项如果是数组或对象(引用类型),会有什么问题?如何解决?
前端·react.js·面试
hibear2 小时前
Smart Ticker - 支持任意字符的高性能文本差异动画滚动组件
前端·vue.js·react.js
a17798877122 小时前
print.js打印
前端·javascript·html