import type { ILoginForm, } from '@/api/login' import type { IAuthLoginRes } from '@/api/types/login' import { defineStore } from 'pinia' import { computed, ref } from 'vue' // 修复:导入 computed import { login as _login, logout as _logout, refreshToken as _refreshToken, wxLogin as _wxLogin, getUserProfile, getWxCode, } from '@/api/login' import { isDoubleTokenRes, isSingleTokenRes } from '@/api/types/login' import { isDoubleTokenMode } from '@/utils' import { useUserStore } from './user' // 初始化状态 const tokenInfoState = isDoubleTokenMode ? { accessToken: '', accessExpiresIn: 0, refreshToken: '', refreshExpiresIn: 0, } : { token: '', expiresIn: 0, } export const useTokenStore = defineStore( 'token', () => { // 定义用户信息 const tokenInfo = ref({ ...tokenInfoState }) // 设置用户信息 const setTokenInfo = (val: IAuthLoginRes) => { tokenInfo.value = val // 计算并存储过期时间 const now = Date.now() if (isSingleTokenRes(val)) { // 单token模式 const expireTime = now + val.expiresIn * 1000 uni.setStorageSync('accessTokenExpireTime', expireTime) } else if (isDoubleTokenRes(val)) { // 双token模式 const accessExpireTime = now + val.accessExpiresIn * 1000 const refreshExpireTime = now + val.refreshExpiresIn * 1000 uni.setStorageSync('accessTokenExpireTime', accessExpireTime) uni.setStorageSync('refreshTokenExpireTime', refreshExpireTime) } } /** * 判断token是否过期 */ const isTokenExpired = computed(() => { if (!tokenInfo.value) { return true } const now = Date.now() const expireTime = uni.getStorageSync('accessTokenExpireTime') if (!expireTime) return true return now >= expireTime }) /** * 判断refreshToken是否过期 */ const isRefreshTokenExpired = computed(() => { if (!isDoubleTokenMode) return true const now = Date.now() const refreshExpireTime = uni.getStorageSync('refreshTokenExpireTime') if (!refreshExpireTime) return true return now >= refreshExpireTime }) /** * 登录成功后处理逻辑 * @param tokenInfo 登录返回的token信息 */ async function _postLogin(tokenInfo: IAuthLoginRes) { setTokenInfo(tokenInfo) // const userStore = useUserStore() // await userStore.fetchUserInfo() } /** * 用户登录 * 有的时候后端会用一个接口返回token和用户信息,有的时候会分开2个接口,一个获取token,一个获取用户信息 * (各有利弊,看业务场景和系统复杂度),这里使用2个接口返回的来模拟 * @param loginForm 登录参数 * @returns 登录结果 */ const login = async (loginForm: ILoginForm) => { try { const res = await _login(loginForm) console.log('普通登录-res: ', res) await _postLogin(res) uni.showToast({ title: '登录成功', icon: 'success', }) return res } catch (error) { console.error('登录失败:', error) uni.showToast({ title: '登录失败,请重试', icon: 'error', }) throw error } } /** * 微信登录 * 有的时候后端会用一个接口返回token和用户信息,有的时候会分开2个接口,一个获取token,一个获取用户信息 * (各有利弊,看业务场景和系统复杂度),这里使用2个接口返回的来模拟 * @returns 登录结果 */ const wxLogin = async () => { try { // 获取用户信息 const profile = await getUserProfile() console.log('微信登录-profile: ', profile) // 获取微信小程序登录的code const code = await getWxCode() console.log('微信登录-code: ', code.code) const res = await _wxLogin(code.code) console.log('微信登录-res: ', res) await _postLogin(res) uni.showToast({ title: '登录成功', icon: 'success', }) return res } catch (error) { console.error('微信登录失败:', error) uni.showToast({ title: '微信登录失败,请重试', icon: 'error', }) throw error } } /** * 退出登录 并 删除用户信息 */ const logout = async () => { try { // TODO 实现自己的退出登录逻辑 await _logout() } catch (error) { console.error('退出登录失败:', error) } finally { // 无论成功失败,都需要清除本地token信息 // 清除存储的过期时间 uni.removeStorageSync('accessTokenExpireTime') uni.removeStorageSync('refreshTokenExpireTime') console.log('退出登录-清除用户信息') tokenInfo.value = { ...tokenInfoState } uni.removeStorageSync('token') const userStore = useUserStore() userStore.clearUserInfo() } } /** * 刷新token * @returns 刷新结果 */ const refreshToken = async () => { if (!isDoubleTokenMode) { console.error('单token模式不支持刷新token') throw new Error('单token模式不支持刷新token') } try { // 安全检查,确保refreshToken存在 if (!isDoubleTokenRes(tokenInfo.value) || !tokenInfo.value.refreshToken) { throw new Error('无效的refreshToken') } const refreshToken = tokenInfo.value.refreshToken const res = await _refreshToken(refreshToken) console.log('刷新token-res: ', res) setTokenInfo(res) return res } catch (error) { console.error('刷新token失败:', error) throw error } } /** * 获取有效的token * 注意:在computed中不直接调用异步函数,只做状态判断 * 实际的刷新操作应由调用方处理 */ const getValidToken = computed(() => { // token已过期,返回空 if (isTokenExpired.value) { return '' } console.log('getValidToken', tokenInfo.value) if (!isDoubleTokenMode) { return isSingleTokenRes(tokenInfo.value) ? tokenInfo.value.token : '' } else { return isDoubleTokenRes(tokenInfo.value) ? tokenInfo.value.accessToken : '' } }) /** * 检查是否有登录信息(不考虑token是否过期) */ const hasLoginInfo = computed(() => { if (!tokenInfo.value) { return false } if (isDoubleTokenMode) { return isDoubleTokenRes(tokenInfo.value) && !!tokenInfo.value.accessToken } else { return isSingleTokenRes(tokenInfo.value) && !!tokenInfo.value.token } }) /** * 检查是否已登录且token有效 */ const hasValidLogin = computed(() => { console.log('hasValidLogin', hasLoginInfo.value && !isTokenExpired.value, hasLoginInfo.value, !isTokenExpired.value) return hasLoginInfo.value && !isTokenExpired.value }) /** * 尝试获取有效的token,如果过期且可刷新,则刷新token * @returns 有效的token或空字符串 */ const tryGetValidToken = async (): Promise => { if (!getValidToken.value && isDoubleTokenMode && !isRefreshTokenExpired.value) { try { await refreshToken() return getValidToken.value } catch (error) { console.error('尝试刷新token失败:', error) return '' } } return getValidToken.value } return { // 核心API方法 login, wxLogin, logout, // 认证状态判断(最常用的) hasLogin: hasValidLogin, // 内部系统使用的方法 refreshToken, tryGetValidToken, validToken: getValidToken, // 调试或特殊场景可能需要直接访问的信息 tokenInfo, setTokenInfo, } }, { // 添加持久化配置,确保刷新页面后token信息不丢失 persist: true, }, )