Files
AIclinicalresearch/frontend-v2/src/pages/admin/tenants/api/tenantApi.ts
HaHafeng 83e395824b feat(rvw): complete journal config center MVP and tenant login routing
Deliver the RVW V4.0 journal configuration center across backend, frontend, migration, and docs with zh/en editorial baseline support and tenant-level prompt/template overrides. Unify tenant login to /:tenantCode/login and auto-enable RVW module when tenant type is JOURNAL to prevent post-login access gaps.

Made-with: Cursor
2026-03-15 11:51:35 +08:00

354 lines
8.2 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.
/**
* 租户管理 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' | 'JOURNAL' | 'INTERNAL' | 'PUBLIC';
export type TenantStatus = 'ACTIVE' | 'SUSPENDED' | 'EXPIRED';
export type JournalLanguage = 'ZH' | 'EN' | 'OTHER';
export interface TenantInfo {
id: string;
code: string;
name: string;
type: TenantType;
journalLanguage?: JournalLanguage | null;
journalFullName?: string | null;
logoUrl?: string | null;
brandColor?: string | null;
loginBackgroundUrl?: string | null;
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;
journalLanguage?: JournalLanguage;
journalFullName?: string;
logoUrl?: string;
brandColor?: string;
loginBackgroundUrl?: string;
contactName?: string;
contactPhone?: string;
contactEmail?: string;
expiresAt?: string;
modules?: string[];
}
export interface UpdateTenantRequest {
name?: string;
type?: TenantType;
journalLanguage?: JournalLanguage | null;
journalFullName?: string | null;
logoUrl?: string | null;
brandColor?: string | null;
loginBackgroundUrl?: string | null;
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;
}
// ==================== RVW Config API ====================
/** 租户审稿配置 */
export interface TenantRvwConfig {
id: string;
tenantId: string;
editorialBaseStandard: 'zh' | 'en';
editorialExpertPrompt: string | null;
editorialHandlebarsTemplate: string | null;
methodologyExpertPrompt: string | null;
methodologyHandlebarsTemplate: string | null;
dataForensicsExpertPrompt: string | null;
dataForensicsHandlebarsTemplate: string | null;
clinicalExpertPrompt: string | null;
clinicalHandlebarsTemplate: string | null;
createdAt: string;
updatedAt: string;
}
/** 更新审稿配置请求 */
export interface UpdateRvwConfigRequest {
editorialBaseStandard?: 'zh' | 'en';
editorialExpertPrompt?: string | null;
editorialHandlebarsTemplate?: string | null;
methodologyExpertPrompt?: string | null;
methodologyHandlebarsTemplate?: string | null;
dataForensicsExpertPrompt?: string | null;
dataForensicsHandlebarsTemplate?: string | null;
clinicalExpertPrompt?: string | null;
clinicalHandlebarsTemplate?: string | null;
}
/**
* 获取租户智能审稿配置
*/
export async function fetchRvwConfig(tenantId: string): Promise<TenantRvwConfig | null> {
const response = await fetch(`${API_BASE}/${tenantId}/rvw-config`, {
headers: getAuthHeaders(),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || '获取审稿配置失败');
}
const result = await response.json();
return result.data;
}
/**
* 保存UPSERT租户智能审稿配置
*/
export async function saveRvwConfig(tenantId: string, data: UpdateRvwConfigRequest): Promise<TenantRvwConfig> {
const response = await fetch(`${API_BASE}/${tenantId}/rvw-config`, {
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;
}