175 lines
4.3 KiB
TypeScript
175 lines
4.3 KiB
TypeScript
/**
|
||
* 登录状态管理工具
|
||
*/
|
||
|
||
import router from '@/router';
|
||
|
||
const TOKEN_KEY = 'token';
|
||
const USER_INFO_KEY = 'userInfo';
|
||
const TOKEN_EXPIRE_TIME_KEY = 'tokenExpireTime';
|
||
|
||
const normalizeAvatarUrl = (avatar: any): any => {
|
||
if (typeof avatar !== 'string') return avatar;
|
||
const url = avatar.trim();
|
||
if (!url) return url;
|
||
if (!url.startsWith('http://')) return url;
|
||
|
||
const host = url.slice('http://'.length).split('/')[0] || '';
|
||
const isLocalhost = host.startsWith('localhost') || host.startsWith('127.0.0.1');
|
||
const isPrivateIp =
|
||
host.startsWith('10.') ||
|
||
host.startsWith('192.168.') ||
|
||
/^172\.(1[6-9]|2\d|3[0-1])\./.test(host);
|
||
if (isLocalhost || isPrivateIp) return url;
|
||
|
||
return 'https://' + url.slice('http://'.length);
|
||
};
|
||
|
||
const normalizeUserInfo = (userInfo: any): any => {
|
||
if (!userInfo || typeof userInfo !== 'object') return userInfo;
|
||
const next = { ...userInfo };
|
||
if ('avatar' in next) {
|
||
next.avatar = normalizeAvatarUrl((next as any).avatar);
|
||
}
|
||
return next;
|
||
};
|
||
|
||
/**
|
||
* 获取 token
|
||
*/
|
||
export const getToken = (): string | null => {
|
||
try {
|
||
return localStorage.getItem(TOKEN_KEY) || null;
|
||
} catch (e) {
|
||
console.error('获取 token 失败:', e);
|
||
return null;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 设置 token
|
||
*/
|
||
export const setToken = (token: string, expireTime?: number): void => {
|
||
try {
|
||
localStorage.setItem(TOKEN_KEY, token);
|
||
if (expireTime) {
|
||
localStorage.setItem(TOKEN_EXPIRE_TIME_KEY, String(expireTime));
|
||
}
|
||
} catch (e) {
|
||
console.error('设置 token 失败:', e);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 清除 token
|
||
*/
|
||
export const clearToken = (): void => {
|
||
try {
|
||
localStorage.removeItem(TOKEN_KEY);
|
||
localStorage.removeItem(TOKEN_EXPIRE_TIME_KEY);
|
||
localStorage.removeItem(USER_INFO_KEY);
|
||
} catch (e) {
|
||
console.error('清除 token 失败:', e);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 检查 token 是否存在
|
||
*/
|
||
export const hasToken = (): boolean => {
|
||
return !!getToken();
|
||
};
|
||
|
||
/**
|
||
* 检查 token 是否过期
|
||
* @param expireTime token 过期时间戳(毫秒),如果不传则从存储中读取
|
||
*/
|
||
export const isTokenExpired = (expireTime?: number): boolean => {
|
||
try {
|
||
const expireStr = expireTime ? String(expireTime) : localStorage.getItem(TOKEN_EXPIRE_TIME_KEY);
|
||
if (!expireStr) {
|
||
// 如果没有过期时间,认为 token 有效(由后端验证)
|
||
return false;
|
||
}
|
||
const expire = Number(expireStr);
|
||
return Date.now() >= expire;
|
||
} catch (e) {
|
||
console.error('检查 token 过期失败:', e);
|
||
return false;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 检查是否已登录(token 存在且未过期)
|
||
*/
|
||
export const isLoggedIn = (): boolean => {
|
||
return hasToken() && !isTokenExpired();
|
||
};
|
||
|
||
/**
|
||
* 获取用户信息
|
||
*/
|
||
export const getUserInfo = (): any | null => {
|
||
try {
|
||
const v = localStorage.getItem(USER_INFO_KEY);
|
||
if (!v) return null;
|
||
return normalizeUserInfo(JSON.parse(v));
|
||
} catch (e) {
|
||
console.error('获取用户信息失败:', e);
|
||
return null;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 设置用户信息
|
||
*/
|
||
export const setUserInfo = (userInfo: any): void => {
|
||
try {
|
||
localStorage.setItem(USER_INFO_KEY, JSON.stringify(normalizeUserInfo(userInfo)));
|
||
} catch (e) {
|
||
console.error('设置用户信息失败:', e);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 跳转到登录页
|
||
* @param force 为 true 时即使当前已在 /login 也 replace 一次(用于退出登录后刷新登录态)
|
||
*/
|
||
export const navigateToLogin = (options?: { redirect?: string; force?: boolean }): void => {
|
||
try {
|
||
const currentRoute = router.currentRoute.value.path;
|
||
|
||
if (options?.force || currentRoute !== '/login') {
|
||
const redirect = options?.redirect ?? (currentRoute !== '/login' ? currentRoute : '/');
|
||
router.replace({
|
||
path: '/login',
|
||
query: { redirect: encodeURIComponent(redirect) }
|
||
});
|
||
}
|
||
} catch (e) {
|
||
console.error('跳转登录页失败:', e);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 退出登录:仅前端清空 token / 用户信息并进入登录页(不请求后端)
|
||
*/
|
||
export const logout = (): void => {
|
||
clearToken();
|
||
navigateToLogin({ force: true, redirect: '/' });
|
||
};
|
||
|
||
/**
|
||
* 需要登录的页面路径列表(白名单外的页面都需要登录)
|
||
*/
|
||
const LOGIN_WHITELIST = ['/login'];
|
||
|
||
/**
|
||
* 检查页面是否需要登录
|
||
*/
|
||
export const isPageRequireLogin = (path: string): boolean => {
|
||
return !LOGIN_WHITELIST.some((whitelistPath) => path.includes(whitelistPath));
|
||
};
|
||
|
||
|