feat(admin): Add user management and upgrade to module permission system
Features - User Management (Phase 4.1): - Database: Add user_modules table for fine-grained module permissions - Database: Add 4 user permissions (view/create/edit/delete) to role_permissions - Backend: UserService (780 lines) - CRUD with tenant isolation - Backend: UserController + UserRoutes (648 lines) - 13 API endpoints - Backend: Batch import users from Excel - Frontend: UserListPage (412 lines) - list/filter/search/pagination - Frontend: UserFormPage (341 lines) - create/edit with module config - Frontend: UserDetailPage (393 lines) - details/tenant/module management - Frontend: 3 modal components (592 lines) - import/assign/configure - API: GET/POST/PUT/DELETE /api/admin/users/* endpoints Architecture Upgrade - Module Permission System: - Backend: Add getUserModules() method in auth.service - Backend: Login API returns modules array in user object - Frontend: AuthContext adds hasModule() method - Frontend: Navigation filters modules based on user.modules - Frontend: RouteGuard checks requiredModule instead of requiredVersion - Frontend: Remove deprecated version-based permission system - UX: Only show accessible modules in navigation (clean UI) - UX: Smart redirect after login (avoid 403 for regular users) Fixes: - Fix UTF-8 encoding corruption in ~100 docs files - Fix pageSize type conversion in userService (String to Number) - Fix authUser undefined error in TopNavigation - Fix login redirect logic with role-based access check - Update Git commit guidelines v1.2 with UTF-8 safety rules Database Changes: - CREATE TABLE user_modules (user_id, tenant_id, module_code, is_enabled) - ADD UNIQUE CONSTRAINT (user_id, tenant_id, module_code) - INSERT 4 permissions + role assignments - UPDATE PUBLIC tenant with 8 module subscriptions Technical: - Backend: 5 new files (~2400 lines) - Frontend: 10 new files (~2500 lines) - Docs: 1 development record + 2 status updates + 1 guideline update - Total: ~4900 lines of code Status: User management 100% complete, module permission system operational
This commit is contained in:
@@ -1,18 +1,15 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useNavigate, useLocation } from 'react-router-dom'
|
||||
import { Dropdown, Avatar, Tooltip } from 'antd'
|
||||
import { Dropdown, Avatar } from 'antd'
|
||||
import {
|
||||
UserOutlined,
|
||||
LogoutOutlined,
|
||||
SettingOutlined,
|
||||
LockOutlined,
|
||||
ControlOutlined,
|
||||
BankOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import type { MenuProps } from 'antd'
|
||||
import { getAvailableModulesByCode } from '../modules/moduleRegistry'
|
||||
import { fetchUserModules } from '../auth/moduleApi'
|
||||
import { usePermission } from '../permission'
|
||||
import { MODULES } from '../modules/moduleRegistry'
|
||||
import { useAuth } from '../auth'
|
||||
import type { ModuleDefinition } from '../modules/types'
|
||||
|
||||
@@ -29,24 +26,15 @@ import type { ModuleDefinition } from '../modules/types'
|
||||
const TopNavigation = () => {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const { user: authUser, logout: authLogout } = useAuth()
|
||||
const { user, checkModulePermission, logout } = usePermission()
|
||||
const [availableModules, setAvailableModules] = useState<ModuleDefinition[]>([])
|
||||
const { user, logout: authLogout, hasModule } = useAuth()
|
||||
|
||||
// 加载用户可访问的模块
|
||||
useEffect(() => {
|
||||
const loadModules = async () => {
|
||||
try {
|
||||
const moduleCodes = await fetchUserModules()
|
||||
const modules = getAvailableModulesByCode(moduleCodes)
|
||||
setAvailableModules(modules)
|
||||
} catch (error) {
|
||||
console.error('加载模块权限失败', error)
|
||||
setAvailableModules([])
|
||||
}
|
||||
}
|
||||
loadModules()
|
||||
}, [authUser])
|
||||
// 根据用户模块权限过滤可显示的模块
|
||||
const availableModules = MODULES.filter(module => {
|
||||
// 没有 moduleCode 的模块跳过(占位模块)
|
||||
if (!module.moduleCode) return false;
|
||||
// 检查用户是否有权限访问
|
||||
return hasModule(module.moduleCode);
|
||||
});
|
||||
|
||||
// 获取当前激活的模块
|
||||
const activeModule = availableModules.find(module =>
|
||||
@@ -54,7 +42,7 @@ const TopNavigation = () => {
|
||||
)
|
||||
|
||||
// 检查用户权限,决定显示哪些切换入口
|
||||
const userRole = authUser?.role || ''
|
||||
const userRole = user?.role || ''
|
||||
const canAccessAdmin = ['SUPER_ADMIN', 'PROMPT_ENGINEER'].includes(userRole)
|
||||
const canAccessOrg = ['HOSPITAL_ADMIN', 'PHARMA_ADMIN', 'DEPARTMENT_ADMIN', 'SUPER_ADMIN'].includes(userRole)
|
||||
|
||||
@@ -97,7 +85,6 @@ const TopNavigation = () => {
|
||||
const handleUserMenuClick = ({ key }: { key: string }) => {
|
||||
if (key === 'logout') {
|
||||
authLogout()
|
||||
logout()
|
||||
navigate('/login')
|
||||
} else if (key === 'switch-admin') {
|
||||
navigate('/admin/dashboard')
|
||||
@@ -108,16 +95,6 @@ const TopNavigation = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理模块点击(检查权限)
|
||||
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 */}
|
||||
@@ -129,53 +106,42 @@ const TopNavigation = () => {
|
||||
<span className="text-xl font-bold text-blue-600">AI临床研究平台</span>
|
||||
</div>
|
||||
|
||||
{/* 导航菜单 - 根据用户权限动态显示 ⭐ Week 2 Day 7 更新 */}
|
||||
{/* 导航菜单 - 只显示有权限的模块 ⭐ 2026-01-16 更新为模块权限系统 */}
|
||||
<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
|
||||
<div
|
||||
key={module.id}
|
||||
title={!hasPermission ? `需要${module.requiredVersion}版本` : ''}
|
||||
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'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<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>
|
||||
{module.name}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</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>
|
||||
<span className="text-xs text-gray-400">{user?.modules?.length || 0} 个模块</span>
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
|
||||
Reference in New Issue
Block a user