feat(frontend): add frontend-v2 modular architecture (Task 17)

- React 19 + TypeScript + Vite
- Module registration mechanism with dynamic loading
- Permission management system (basic/advanced/premium)
- Route guards for access control
- Error boundaries for module isolation
- 6 business module placeholders (AIA/ASL/PKB/DC/SSA/ST)
- Top navigation layout
- Tailwind CSS 3 + Ant Design 5
This commit is contained in:
2025-11-16 15:43:17 +08:00
parent 5579ffa78e
commit 11325f88a7
39 changed files with 8051 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
import { useNavigate, useLocation } from 'react-router-dom'
import { Dropdown, Avatar, Tooltip } from 'antd'
import { UserOutlined, LogoutOutlined, SettingOutlined, LockOutlined } from '@ant-design/icons'
import type { MenuProps } from 'antd'
import { getAvailableModules } from '../modules/moduleRegistry'
import { usePermission } from '../permission'
/**
* 顶部导航栏组件
*
* @description
* - 显示Logo和平台名称
* - 显示模块导航(根据用户权限过滤)⭐ Week 2 Day 7 新增
* - 显示用户菜单
*
* @version Week 2 Day 7 - 任务17集成权限系统
*/
const TopNavigation = () => {
const navigate = useNavigate()
const location = useLocation()
const { user, checkModulePermission, logout } = usePermission()
// 获取用户有权访问的模块列表(权限过滤)⭐ 新增
const availableModules = getAvailableModules(user?.version || 'basic')
// 获取当前激活的模块
const activeModule = availableModules.find(module =>
location.pathname.startsWith(module.path)
)
// 用户菜单
const userMenuItems: MenuProps['items'] = [
{
key: 'profile',
icon: <UserOutlined />,
label: '个人中心',
},
{
key: 'settings',
icon: <SettingOutlined />,
label: '设置',
},
{
type: 'divider',
},
{
key: 'logout',
icon: <LogoutOutlined />,
label: '退出登录',
danger: true,
},
]
// 处理用户菜单点击
const handleUserMenuClick = ({ key }: { key: string }) => {
if (key === 'logout') {
logout()
navigate('/')
} else {
navigate(`/user/${key}`)
}
}
// 处理模块点击(检查权限)
const handleModuleClick = (modulePath: string, requiredVersion?: string) => {
if (!checkModulePermission(requiredVersion as any)) {
// 理论上不会到这里,因为已经过滤了
console.warn('权限不足,无法访问该模块')
return
}
navigate(modulePath)
}
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('/')}
>
<div className="text-2xl">🏥</div>
<span className="text-xl font-bold text-blue-600">AI临床研究平台</span>
</div>
{/* 导航菜单 - 根据用户权限动态显示 ⭐ Week 2 Day 7 更新 */}
<div className="flex items-center gap-2">
{availableModules.map(module => {
const hasPermission = checkModulePermission(module.requiredVersion as any)
const isActive = activeModule?.id === module.id
return (
<Tooltip
key={module.id}
title={!hasPermission ? `需要${module.requiredVersion}版本` : ''}
>
<div
onClick={() => hasPermission && handleModuleClick(module.path, module.requiredVersion)}
className={`
px-4 py-2 rounded-md transition-all
${!hasPermission
? 'text-gray-400 cursor-not-allowed opacity-50'
: isActive
? 'bg-blue-50 text-blue-600 font-semibold cursor-pointer'
: 'text-gray-600 hover:bg-gray-50 hover:text-blue-600 cursor-pointer'
}
`}
>
<span className="flex items-center gap-2">
{!hasPermission && <LockOutlined className="text-xs" />}
{module.name}
</span>
</div>
</Tooltip>
)
})}
</div>
{/* 用户菜单 - 显示真实用户信息 ⭐ Week 2 Day 7 更新 */}
<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?.avatar}
icon={<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?.version || 'basic'}</span>
</div>
</div>
</Dropdown>
</div>
)
}
export default TopNavigation