feat: Add Personal Center module and UI improvements
- Add ProfilePage with avatar upload, password change, and module status display - Update logo and favicon for login page and browser tab - Redirect Data Cleaning module default route to Tool C - Hide Settings button from top navigation for MVP - Add avatar display in top navigation bar with refresh sync - Add Prompt knowledge base integration development plan docs
This commit is contained in:
@@ -188,6 +188,19 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
return user.modules?.includes(moduleCode) || false;
|
||||
}, [user]);
|
||||
|
||||
/**
|
||||
* 刷新用户信息(头像更新等场景使用)
|
||||
*/
|
||||
const refreshUser = useCallback(async () => {
|
||||
try {
|
||||
const freshUser = await authApi.getCurrentUser();
|
||||
setUser(freshUser);
|
||||
authApi.saveUser(freshUser);
|
||||
} catch (err) {
|
||||
console.error('刷新用户信息失败:', err);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const value: AuthContextType = {
|
||||
user,
|
||||
isAuthenticated: !!user,
|
||||
@@ -201,7 +214,8 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
refreshToken,
|
||||
hasPermission,
|
||||
hasRole,
|
||||
hasModule, // 新增
|
||||
hasModule,
|
||||
refreshUser, // 2026-01-28: 头像更新等场景使用
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -198,6 +198,29 @@ export async function changePassword(request: ChangePasswordRequest): Promise<vo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新头像
|
||||
*/
|
||||
export async function updateAvatar(avatarUrl: string): Promise<{ avatarUrl: string }> {
|
||||
const response = await authFetch<{ avatarUrl: string }>(`${API_BASE}/me/avatar`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ avatarUrl }),
|
||||
});
|
||||
|
||||
if (!response.success || !response.data) {
|
||||
throw new Error(response.message || '更新头像失败');
|
||||
}
|
||||
|
||||
// 更新本地存储的用户信息
|
||||
const user = getSavedUser();
|
||||
if (user) {
|
||||
user.avatarUrl = avatarUrl;
|
||||
saveUser(user);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新Token
|
||||
*/
|
||||
|
||||
@@ -29,6 +29,13 @@ export interface AuthUser {
|
||||
isDefaultPassword: boolean;
|
||||
permissions: string[];
|
||||
modules: string[]; // 用户可访问的模块代码列表(如 ['AIA', 'PKB', 'RVW'])
|
||||
// 2026-01-28: 个人中心扩展字段
|
||||
avatarUrl?: string | null;
|
||||
status?: string;
|
||||
kbQuota?: number;
|
||||
kbUsed?: number;
|
||||
isTrial?: boolean;
|
||||
trialEndsAt?: string | null; // ISO date string
|
||||
}
|
||||
|
||||
/** Token信息 */
|
||||
@@ -100,6 +107,8 @@ export interface AuthContextType extends AuthState {
|
||||
hasRole: (...roles: UserRole[]) => boolean;
|
||||
/** 检查模块权限 */
|
||||
hasModule: (moduleCode: string) => boolean;
|
||||
/** 刷新用户信息(头像更新等场景使用) */
|
||||
refreshUser: () => Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useNavigate, useLocation } from 'react-router-dom'
|
||||
import { Dropdown, Avatar } from 'antd'
|
||||
import {
|
||||
UserOutlined,
|
||||
LogoutOutlined,
|
||||
SettingOutlined,
|
||||
// SettingOutlined, // MVP阶段暂时隐藏设置按钮
|
||||
ControlOutlined,
|
||||
BankOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import type { MenuProps } from 'antd'
|
||||
import { MODULES } from '../modules/moduleRegistry'
|
||||
import { useAuth } from '../auth'
|
||||
import type { ModuleDefinition } from '../modules/types'
|
||||
|
||||
/**
|
||||
* 顶部导航栏组件
|
||||
@@ -53,11 +51,12 @@ const TopNavigation = () => {
|
||||
icon: <UserOutlined />,
|
||||
label: '个人中心',
|
||||
},
|
||||
{
|
||||
key: 'settings',
|
||||
icon: <SettingOutlined />,
|
||||
label: '设置',
|
||||
},
|
||||
// MVP阶段暂时隐藏设置按钮
|
||||
// {
|
||||
// key: 'settings',
|
||||
// icon: <SettingOutlined />,
|
||||
// label: '设置',
|
||||
// },
|
||||
// 切换入口 - 根据权限显示
|
||||
...(canAccessOrg || canAccessAdmin ? [{ type: 'divider' as const }] : []),
|
||||
...(canAccessOrg ? [{
|
||||
@@ -103,9 +102,9 @@ const TopNavigation = () => {
|
||||
onClick={() => navigate('/')}
|
||||
>
|
||||
<img
|
||||
src="/logo.jpg"
|
||||
src="/logo-new.png"
|
||||
alt="AI临床研究平台"
|
||||
className="h-[52px] w-auto"
|
||||
className="h-[48px] w-auto"
|
||||
/>
|
||||
<span className="text-xl font-bold text-blue-600">AI临床研究平台</span>
|
||||
</div>
|
||||
@@ -133,14 +132,15 @@ const TopNavigation = () => {
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 用户菜单 - 显示真实用户信息 */}
|
||||
{/* 用户菜单 - 显示真实用户信息和头像 */}
|
||||
<Dropdown
|
||||
menu={{ items: userMenuItems, onClick: handleUserMenuClick }}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<div className="flex items-center gap-2 cursor-pointer px-3 py-2 rounded-md hover:bg-gray-50">
|
||||
<Avatar
|
||||
icon={<UserOutlined />}
|
||||
src={user?.avatarUrl}
|
||||
icon={!user?.avatarUrl && <UserOutlined />}
|
||||
size="small"
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
|
||||
Reference in New Issue
Block a user