feat(admin): Complete tenant management and module access control system

Major Features:
- Tenant management CRUD (list, create, edit, delete, module configuration)
- Dynamic module management system (modules table with 8 modules)
- Multi-tenant module permission merging (ModuleService)
- Module access control middleware (requireModule)
- User module permission API (GET /api/v1/auth/me/modules)
- Frontend module permission filtering (HomePage + TopNavigation)

Module Integration:
- RVW module integrated with PromptService (editorial + methodology)
- All modules (RVW/PKB/ASL/DC) added authenticate + requireModule middleware
- Fixed ReviewTask foreign key constraint (cross-schema issue)
- Removed all MOCK_USER_ID, unified to request.user?.userId

Prompt Management Enhancements:
- Module names displayed in Chinese (RVW -> 智能审稿)
- Enhanced version history with view content and rollback features
- List page shows both activeVersion and draftVersion columns

Database Changes:
- Added platform_schema.modules table
- Modified tenant_modules table (added index and UUID)
- Removed ReviewTask foreign key to public.users (cross-schema fix)
- Seeded 8 modules: RVW, PKB, ASL, DC, IIT, AIA, SSA, ST

Documentation Updates:
- Updated ADMIN module development status
- Updated TODO checklist (89% progress)
- Updated Prompt management plan (Phase 3.5.5 completed)
- Added module authentication specification

Files Changed: 80+
Status: All features tested and verified locally
Next: User management module development
This commit is contained in:
2026-01-13 07:34:30 +08:00
parent 5523ef36ea
commit d595037316
51 changed files with 3550 additions and 287 deletions

View File

@@ -0,0 +1,246 @@
/**
* 租户管理 API
*/
import { getAccessToken } from '../../../../framework/auth/api';
const API_BASE = '/api/admin/tenants';
const MODULES_API = '/api/admin/modules';
function getAuthHeaders(): HeadersInit {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
const token = getAccessToken();
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
return headers;
}
// ==================== 类型定义 ====================
export type TenantType = 'HOSPITAL' | 'PHARMA' | 'INTERNAL' | 'PUBLIC';
export type TenantStatus = 'ACTIVE' | 'SUSPENDED' | 'EXPIRED';
export interface TenantInfo {
id: string;
code: string;
name: string;
type: TenantType;
status: TenantStatus;
contactName?: string | null;
contactPhone?: string | null;
contactEmail?: string | null;
expiresAt?: string | null;
createdAt: string;
updatedAt: string;
}
export interface TenantModuleConfig {
code: string;
name: string;
enabled: boolean;
expiresAt?: string | null;
}
export interface TenantDetail extends TenantInfo {
modules: TenantModuleConfig[];
userCount: number;
}
export interface ModuleInfo {
code: string;
name: string;
description?: string | null;
icon?: string | null;
}
export interface CreateTenantRequest {
code: string;
name: string;
type: TenantType;
contactName?: string;
contactPhone?: string;
contactEmail?: string;
expiresAt?: string;
modules?: string[];
}
export interface UpdateTenantRequest {
name?: string;
contactName?: string;
contactPhone?: string;
contactEmail?: string;
expiresAt?: string | null;
}
export interface TenantListResponse {
success: boolean;
data: TenantInfo[];
total: number;
page: number;
limit: number;
totalPages: number;
}
// ==================== API 函数 ====================
/**
* 获取租户列表
*/
export async function fetchTenantList(params?: {
type?: TenantType;
status?: TenantStatus;
search?: string;
page?: number;
limit?: number;
}): Promise<TenantListResponse> {
const searchParams = new URLSearchParams();
if (params?.type) searchParams.set('type', params.type);
if (params?.status) searchParams.set('status', params.status);
if (params?.search) searchParams.set('search', params.search);
if (params?.page) searchParams.set('page', String(params.page));
if (params?.limit) searchParams.set('limit', String(params.limit));
const url = `${API_BASE}?${searchParams.toString()}`;
const response = await fetch(url, {
method: 'GET',
headers: getAuthHeaders(),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || '获取租户列表失败');
}
return response.json();
}
/**
* 获取租户详情
*/
export async function fetchTenantDetail(id: string): Promise<TenantDetail> {
const response = await fetch(`${API_BASE}/${id}`, {
method: 'GET',
headers: getAuthHeaders(),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || '获取租户详情失败');
}
const result = await response.json();
return result.data;
}
/**
* 创建租户
*/
export async function createTenant(data: CreateTenantRequest): Promise<TenantInfo> {
const response = await fetch(API_BASE, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(data),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || '创建租户失败');
}
const result = await response.json();
return result.data;
}
/**
* 更新租户信息
*/
export async function updateTenant(id: string, data: UpdateTenantRequest): Promise<TenantInfo> {
const response = await fetch(`${API_BASE}/${id}`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(data),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || '更新租户失败');
}
const result = await response.json();
return result.data;
}
/**
* 更新租户状态
*/
export async function updateTenantStatus(id: string, status: TenantStatus): Promise<void> {
const response = await fetch(`${API_BASE}/${id}/status`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify({ status }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || '更新租户状态失败');
}
}
/**
* 删除租户
*/
export async function deleteTenant(id: string): Promise<void> {
const response = await fetch(`${API_BASE}/${id}`, {
method: 'DELETE',
headers: getAuthHeaders(),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || '删除租户失败');
}
}
/**
* 配置租户模块
*/
export async function configureModules(
tenantId: string,
modules: { code: string; enabled: boolean; expiresAt?: string | null }[]
): Promise<TenantModuleConfig[]> {
const response = await fetch(`${API_BASE}/${tenantId}/modules`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify({ modules }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || '配置租户模块失败');
}
const result = await response.json();
return result.data;
}
/**
* 获取所有可用模块列表
*/
export async function fetchModuleList(): Promise<ModuleInfo[]> {
const response = await fetch(MODULES_API, {
method: 'GET',
headers: getAuthHeaders(),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || '获取模块列表失败');
}
const result = await response.json();
return result.data;
}