feat: 1. 添加axios全局路由拦截进行自动添加请求jwt。 2.重新整理路由表。 3. 文档列表新增版本差异对比。 4.菜单路由可访问列表通过对接接口返回,添加全局路由检测。
5. 修改统一认证登录和管理员登录是通过接口形式进行,存储返回的accessToken。 6. 修改交叉评查的部分样式
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 认证信息存储工具
|
||||
* 用于在客户端(浏览器)存储和管理 JWT token 和用户信息
|
||||
*/
|
||||
|
||||
/**
|
||||
* 存储的用户信息接口
|
||||
*/
|
||||
export interface StoredUserInfo {
|
||||
user_id: string;
|
||||
username: string;
|
||||
nick_name: string;
|
||||
email?: string;
|
||||
phone_number?: string;
|
||||
ou_id: string;
|
||||
ou_name: string;
|
||||
is_leader: boolean;
|
||||
user_role: string;
|
||||
sub: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储 JWT token 到 localStorage
|
||||
*/
|
||||
export function storeAccessToken(token: string): void {
|
||||
if (typeof window === 'undefined') {
|
||||
console.warn('⚠️ storeAccessToken 只能在浏览器环境中使用');
|
||||
return;
|
||||
}
|
||||
localStorage.setItem('access_token', token);
|
||||
console.log('✅ Token 已存储到 localStorage');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储的 JWT token
|
||||
*/
|
||||
export function getAccessToken(): string | null {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
return localStorage.getItem('access_token');
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储用户信息到 localStorage
|
||||
*/
|
||||
export function storeUserInfo(userInfo: StoredUserInfo): void {
|
||||
if (typeof window === 'undefined') {
|
||||
console.warn('⚠️ storeUserInfo 只能在浏览器环境中使用');
|
||||
return;
|
||||
}
|
||||
localStorage.setItem('user_info', JSON.stringify(userInfo));
|
||||
console.log('✅ 用户信息已存储到 localStorage');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储的用户信息
|
||||
*/
|
||||
export function getUserInfo(): StoredUserInfo | null {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const userInfoStr = localStorage.getItem('user_info');
|
||||
if (!userInfoStr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(userInfoStr) as StoredUserInfo;
|
||||
} catch (error) {
|
||||
console.error('❌ 解析用户信息失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有认证信息
|
||||
*/
|
||||
export function clearAuth(): void {
|
||||
if (typeof window === 'undefined') {
|
||||
console.warn('⚠️ clearAuth 只能在浏览器环境中使用');
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('user_info');
|
||||
console.log('✅ 认证信息已清除');
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已登录(有有效的 token)
|
||||
*/
|
||||
export function isAuthenticated(): boolean {
|
||||
const token = getAccessToken();
|
||||
return !!token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储完整的登录响应数据
|
||||
*/
|
||||
export function storeLoginData(accessToken: string, userInfo: StoredUserInfo): void {
|
||||
storeAccessToken(accessToken);
|
||||
storeUserInfo(userInfo);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 服务端认证错误处理工具
|
||||
*
|
||||
* 用于在 Remix loader/action 中统一处理 AuthenticationError(token 过期)
|
||||
*/
|
||||
|
||||
import { redirect } from "@remix-run/node";
|
||||
|
||||
/**
|
||||
* 包装服务端异步函数,自动处理 AuthenticationError
|
||||
*
|
||||
* @param fn - 需要执行的异步函数
|
||||
* @param pathname - 当前路径(用于保存重定向目标)
|
||||
* @returns 函数执行结果,或在 token 过期时重定向到登录页
|
||||
*
|
||||
* @example
|
||||
* // 在 loader 中使用
|
||||
* export async function loader({ request }: LoaderFunctionArgs) {
|
||||
* const url = new URL(request.url);
|
||||
*
|
||||
* return handleServerAuth(async () => {
|
||||
* const { getUserSession } = await import("~/api/login/auth.server");
|
||||
* const { frontendJWT } = await getUserSession(request);
|
||||
*
|
||||
* // 调用需要认证的 API
|
||||
* const response = await getDocumentTypes({}, frontendJWT);
|
||||
*
|
||||
* return Response.json({ data: response.data });
|
||||
* }, url.pathname);
|
||||
* }
|
||||
*/
|
||||
export async function handleServerAuth<T>(
|
||||
fn: () => Promise<T>,
|
||||
pathname: string
|
||||
): Promise<T> {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
// 检查是否是 AuthenticationError(token 过期)
|
||||
if (error instanceof Error && error.name === 'AuthenticationError') {
|
||||
console.warn(`⚠️ [Server Auth Handler] Token 过期,重定向到登录页 (from: ${pathname})`);
|
||||
|
||||
// 保存当前路径,登录后可以跳转回来
|
||||
const redirectTo = pathname !== '/login' ? pathname : '/';
|
||||
throw redirect(`/login?redirect=${encodeURIComponent(redirectTo)}`);
|
||||
}
|
||||
|
||||
// 其他错误继续抛出
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查错误是否是 AuthenticationError
|
||||
*
|
||||
* @param error - 要检查的错误对象
|
||||
* @returns 是否是 AuthenticationError
|
||||
*/
|
||||
export function isAuthenticationError(error: unknown): boolean {
|
||||
return error instanceof Error && error.name === 'AuthenticationError';
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 AuthenticationError 创建登录重定向
|
||||
*
|
||||
* @param pathname - 当前路径
|
||||
* @returns Remix redirect Response
|
||||
*/
|
||||
export function redirectToLogin(pathname: string) {
|
||||
const redirectTo = pathname !== '/login' ? pathname : '/';
|
||||
return redirect(`/login?redirect=${encodeURIComponent(redirectTo)}`);
|
||||
}
|
||||
Reference in New Issue
Block a user