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:
246
frontend-v2/src/pages/admin/tenants/api/tenantApi.ts
Normal file
246
frontend-v2/src/pages/admin/tenants/api/tenantApi.ts
Normal 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user