Files
AIclinicalresearch/frontend-v2/src/framework/layout/TopNavigation.tsx
HaHafeng 3a4aa9123c 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
2026-01-28 18:18:09 +08:00

157 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useNavigate, useLocation } from 'react-router-dom'
import { Dropdown, Avatar } from 'antd'
import {
UserOutlined,
LogoutOutlined,
// SettingOutlined, // MVP阶段暂时隐藏设置按钮
ControlOutlined,
BankOutlined,
} from '@ant-design/icons'
import type { MenuProps } from 'antd'
import { MODULES } from '../modules/moduleRegistry'
import { useAuth } from '../auth'
/**
* 顶部导航栏组件
*
* @description
* - 显示Logo和平台名称
* - 显示模块导航(根据用户权限过滤)⭐ Week 2 Day 7 新增
* - 显示用户菜单
*
* @version Week 2 Day 7 - 任务17集成权限系统
*/
const TopNavigation = () => {
const navigate = useNavigate()
const location = useLocation()
const { user, logout: authLogout, hasModule } = useAuth()
// 根据用户模块权限过滤可显示的模块
const availableModules = MODULES.filter(module => {
// 没有 moduleCode 的模块跳过(占位模块)
if (!module.moduleCode) return false;
// 检查用户是否有权限访问
return hasModule(module.moduleCode);
});
// 获取当前激活的模块
const activeModule = availableModules.find(module =>
location.pathname.startsWith(module.path)
)
// 检查用户权限,决定显示哪些切换入口
const userRole = user?.role || ''
const canAccessAdmin = ['SUPER_ADMIN', 'PROMPT_ENGINEER'].includes(userRole)
const canAccessOrg = ['HOSPITAL_ADMIN', 'PHARMA_ADMIN', 'DEPARTMENT_ADMIN', 'SUPER_ADMIN'].includes(userRole)
// 用户菜单 - 动态构建
const userMenuItems: MenuProps['items'] = [
{
key: 'profile',
icon: <UserOutlined />,
label: '个人中心',
},
// MVP阶段暂时隐藏设置按钮
// {
// key: 'settings',
// icon: <SettingOutlined />,
// label: '设置',
// },
// 切换入口 - 根据权限显示
...(canAccessOrg || canAccessAdmin ? [{ type: 'divider' as const }] : []),
...(canAccessOrg ? [{
key: 'switch-org',
icon: <BankOutlined />,
label: '切换到机构管理',
}] : []),
...(canAccessAdmin ? [{
key: 'switch-admin',
icon: <ControlOutlined />,
label: '切换到运营管理',
}] : []),
{
type: 'divider',
},
{
key: 'logout',
icon: <LogoutOutlined />,
label: '退出登录',
danger: true,
},
]
// 处理用户菜单点击
const handleUserMenuClick = ({ key }: { key: string }) => {
if (key === 'logout') {
authLogout()
navigate('/login')
} else if (key === 'switch-admin') {
navigate('/admin/dashboard')
} else if (key === 'switch-org') {
navigate('/org/dashboard')
} else {
navigate(`/user/${key}`)
}
}
return (
<div className="h-16 bg-white border-b border-gray-200 px-6 flex items-center justify-between">
{/* Logo */}
<div
className="flex items-center gap-3 cursor-pointer"
onClick={() => navigate('/')}
>
<img
src="/logo-new.png"
alt="AI临床研究平台"
className="h-[48px] w-auto"
/>
<span className="text-xl font-bold text-blue-600">AI临床研究平台</span>
</div>
{/* 导航菜单 - 只显示有权限的模块 ⭐ 2026-01-16 更新为模块权限系统 */}
<div className="flex items-center gap-2">
{availableModules.map(module => {
const isActive = activeModule?.id === module.id
return (
<div
key={module.id}
onClick={() => navigate(module.path)}
className={`
px-4 py-2 rounded-md transition-all cursor-pointer
${isActive
? 'bg-blue-50 text-blue-600 font-semibold'
: 'text-gray-600 hover:bg-gray-50 hover:text-blue-600'
}
`}
>
{module.name}
</div>
)
})}
</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
src={user?.avatarUrl}
icon={!user?.avatarUrl && <UserOutlined />}
size="small"
/>
<div className="flex flex-col">
<span className="text-gray-700 text-sm">{user?.name || '访客'}</span>
<span className="text-xs text-gray-400">{user?.modules?.length || 0} </span>
</div>
</div>
</Dropdown>
</div>
)
}
export default TopNavigation