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;
页面中正常调用就可以了
为了实现无感刷新,我们需要处理两种情况:
- 预检拦截:发送请求前发现没 Token(你现在已经实现了)。
- 响应拦截:请求发出去后,后端返回 401(Token 刚好在这一秒过期了)