refactor(asl): ASL frontend architecture refactoring with left navigation

- feat: Create ASLLayout component with 7-module left navigation
- feat: Implement Title Screening Settings page with optimized PICOS layout
- feat: Add placeholder pages for Workbench and Results
- fix: Fix nested routing structure for React Router v6
- fix: Resolve Spin component warning in MainLayout
- fix: Add QueryClientProvider to App.tsx
- style: Optimize PICOS form layout (P+I left, C+O+S right)
- style: Align Inclusion/Exclusion criteria side-by-side
- docs: Add architecture refactoring and routing fix reports

Ref: Week 2 Frontend Development
Scope: ASL module MVP - Title Abstract Screening
This commit is contained in:
2025-11-18 21:51:51 +08:00
parent e3e7e028e8
commit 3634933ece
213 changed files with 20054 additions and 442 deletions

View File

@@ -1,6 +1,7 @@
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { ConfigProvider } from 'antd'
import zhCN from 'antd/locale/zh_CN'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { PermissionProvider } from './framework/permission'
import { RouteGuard } from './framework/router'
import MainLayout from './framework/layout/MainLayout'
@@ -12,18 +13,34 @@ import { MODULES } from './framework/modules/moduleRegistry'
*
* @description
* - ConfigProvider: Ant Design国际化配置
* - QueryClientProvider: React Query状态管理Week 2 新增)⭐
* - PermissionProvider: 权限管理系统Week 2 Day 7新增
* - RouteGuard: 路由守卫保护Week 2 Day 7新增
* - BrowserRouter: 前端路由
*
* @version Week 2 Day 7 - 任务17完整版权限系统
* @version Week 2 Day 1 - 添加React Query支持
*/
// 创建React Query客户端
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5分钟
gcTime: 1000 * 60 * 10, // 10分钟原cacheTime
retry: 1, // 失败重试1次
refetchOnWindowFocus: false, // 窗口聚焦时不自动重新获取
},
},
})
function App() {
return (
<ConfigProvider locale={zhCN}>
{/* 权限提供者:提供全局权限状态 */}
<PermissionProvider>
<BrowserRouter>
{/* React Query状态管理 */}
<QueryClientProvider client={queryClient}>
{/* 权限提供者:提供全局权限状态 */}
<PermissionProvider>
<BrowserRouter>
<Routes>
<Route path="/" element={<MainLayout />}>
{/* 首页 */}
@@ -52,6 +69,7 @@ function App() {
</Routes>
</BrowserRouter>
</PermissionProvider>
</QueryClientProvider>
</ConfigProvider>
)
}

View File

@@ -27,7 +27,7 @@ const MainLayout = () => {
<Suspense
fallback={
<div className="flex-1 flex items-center justify-center">
<Spin size="large" tip="加载中..." />
<Spin size="large" />
</div>
}
>

View File

@@ -140,3 +140,5 @@ export const PermissionProvider = ({ children }: PermissionProviderProps) => {

View File

@@ -15,3 +15,5 @@ export { VERSION_LEVEL, checkVersionLevel } from './types'

View File

@@ -87,3 +87,5 @@ export const checkVersionLevel = (

View File

@@ -44,3 +44,5 @@ export type { UserInfo, UserVersion, PermissionContextType } from './types'

View File

@@ -154,3 +154,5 @@ export default PermissionDenied

View File

@@ -143,3 +143,5 @@ export default RouteGuard

View File

@@ -13,3 +13,5 @@ export { default as PermissionDenied } from './PermissionDenied'

View File

@@ -18,3 +18,5 @@ export default AIAModule

View File

@@ -0,0 +1,267 @@
/**
* ASL模块 - API客户端
*
* 负责所有与后端的API交互
*/
import type {
ScreeningProject,
CreateProjectRequest,
Literature,
ImportLiteraturesRequest,
ScreeningResult,
ScreeningTask,
ApiResponse,
PaginatedResponse,
ProjectStatistics
} from '../types';
// API基础URL
const API_BASE_URL = '/api/v1/asl';
// 通用请求函数
async function request<T = any>(
url: string,
options?: RequestInit
): Promise<T> {
const response = await fetch(`${API_BASE_URL}${url}`, {
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
});
if (!response.ok) {
const error = await response.json().catch(() => ({
message: 'Network error'
}));
throw new Error(error.message || `HTTP ${response.status}`);
}
return response.json();
}
// ==================== 项目管理API ====================
/**
* 创建筛选项目
*/
export async function createProject(
data: CreateProjectRequest
): Promise<ApiResponse<ScreeningProject>> {
return request('/projects', {
method: 'POST',
body: JSON.stringify(data),
});
}
/**
* 获取项目列表
*/
export async function listProjects(): Promise<ApiResponse<ScreeningProject[]>> {
return request('/projects');
}
/**
* 获取项目详情
*/
export async function getProject(
projectId: string
): Promise<ApiResponse<ScreeningProject>> {
return request(`/projects/${projectId}`);
}
/**
* 更新项目
*/
export async function updateProject(
projectId: string,
data: Partial<CreateProjectRequest>
): Promise<ApiResponse<ScreeningProject>> {
return request(`/projects/${projectId}`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
/**
* 删除项目
*/
export async function deleteProject(
projectId: string
): Promise<ApiResponse<void>> {
return request(`/projects/${projectId}`, {
method: 'DELETE',
});
}
// ==================== 文献管理API ====================
/**
* 批量导入文献JSON格式
*/
export async function importLiteratures(
projectId: string,
data: ImportLiteraturesRequest
): Promise<ApiResponse<{
imported: number;
duplicates: number;
failed: number;
}>> {
return request(`/projects/${projectId}/literatures/import-json`, {
method: 'POST',
body: JSON.stringify(data),
});
}
/**
* 获取文献列表
*/
export async function listLiteratures(
projectId: string,
params?: {
page?: number;
pageSize?: number;
}
): Promise<ApiResponse<PaginatedResponse<Literature>>> {
const queryString = new URLSearchParams(
params as Record<string, string>
).toString();
return request(`/projects/${projectId}/literatures?${queryString}`);
}
/**
* 删除文献
*/
export async function deleteLiterature(
projectId: string,
literatureId: string
): Promise<ApiResponse<void>> {
return request(`/projects/${projectId}/literatures/${literatureId}`, {
method: 'DELETE',
});
}
// ==================== 筛选任务API ====================
/**
* 启动筛选任务
*/
export async function startScreening(
projectId: string
): Promise<ApiResponse<{ taskId: string }>> {
return request(`/projects/${projectId}/screening/start`, {
method: 'POST',
});
}
/**
* 查询任务进度
*/
export async function getTaskProgress(
taskId: string
): Promise<ApiResponse<ScreeningTask>> {
return request(`/screening/tasks/${taskId}/progress`);
}
// ==================== 筛选结果API ====================
/**
* 获取筛选结果列表
*/
export async function getScreeningResults(
projectId: string,
params?: {
conflictOnly?: boolean;
finalDecision?: 'include' | 'exclude' | 'pending';
page?: number;
pageSize?: number;
}
): Promise<ApiResponse<PaginatedResponse<ScreeningResult>>> {
const queryString = new URLSearchParams(
params as Record<string, string>
).toString();
return request(`/projects/${projectId}/screening/results?${queryString}`);
}
/**
* 更新单个筛选结果
*/
export async function updateScreeningResult(
resultId: string,
data: {
finalDecision?: 'include' | 'exclude' | 'pending';
reviewComment?: string;
}
): Promise<ApiResponse<ScreeningResult>> {
return request(`/screening/results/${resultId}`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
/**
* 批量更新筛选结果
*/
export async function batchUpdateScreeningResults(data: {
resultIds: string[];
finalDecision: 'include' | 'exclude' | 'pending';
decisionMethod?: string;
reviewComment?: string;
}): Promise<ApiResponse<{ updated: number }>> {
return request('/screening/results/batch-update', {
method: 'POST',
body: JSON.stringify(data),
});
}
// ==================== 导出API ====================
/**
* 导出筛选结果为Excel
*/
export async function exportScreeningResults(
projectId: string,
params?: {
filter?: 'all' | 'included' | 'excluded' | 'pending';
}
): Promise<Blob> {
const queryString = new URLSearchParams(
params as Record<string, string>
).toString();
const response = await fetch(
`${API_BASE_URL}/projects/${projectId}/screening/results/export?${queryString}`
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.blob();
}
// ==================== 统计API ====================
/**
* 获取项目统计信息
*/
export async function getProjectStatistics(
projectId: string
): Promise<ApiResponse<ProjectStatistics>> {
return request(`/projects/${projectId}/statistics`);
}
// ==================== 健康检查API ====================
/**
* 模块健康检查
*/
export async function healthCheck(): Promise<ApiResponse<{
status: string;
module: string;
}>> {
return request('/health');
}

View File

@@ -0,0 +1,152 @@
/**
* ASL模块布局组件
*
* 左侧导航栏 + 右侧内容区
* 参考原型AI智能文献-标题摘要初筛原型.html
*/
import { Layout, Menu } from 'antd';
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
import {
FileTextOutlined,
SearchOutlined,
FolderOpenOutlined,
FilterOutlined,
FileSearchOutlined,
DatabaseOutlined,
BarChartOutlined,
SettingOutlined,
CheckSquareOutlined,
UnorderedListOutlined,
} from '@ant-design/icons';
import type { MenuProps } from 'antd';
const { Sider, Content } = Layout;
type MenuItem = Required<MenuProps>['items'][number];
const ASLLayout = () => {
const navigate = useNavigate();
const location = useLocation();
// 菜单项配置
const menuItems: MenuItem[] = [
{
key: 'research-plan',
icon: <FileTextOutlined />,
label: '1. 研究方案生成',
disabled: true,
title: '敬请期待'
},
{
key: 'literature-search',
icon: <SearchOutlined />,
label: '2. 智能文献检索',
disabled: true,
title: '敬请期待'
},
{
key: 'literature-management',
icon: <FolderOpenOutlined />,
label: '3. 文献管理',
disabled: true,
title: '敬请期待'
},
{
key: 'title-screening',
icon: <FilterOutlined />,
label: '4. 标题摘要初筛',
children: [
{
key: '/literature/screening/title/settings',
icon: <SettingOutlined />,
label: '设置与启动',
},
{
key: '/literature/screening/title/workbench',
icon: <CheckSquareOutlined />,
label: '审核工作台',
},
{
key: '/literature/screening/title/results',
icon: <UnorderedListOutlined />,
label: '初筛结果',
},
],
},
{
key: 'fulltext-screening',
icon: <FileSearchOutlined />,
label: '5. 全文复筛',
disabled: true,
title: '敬请期待'
},
{
key: 'data-extraction',
icon: <DatabaseOutlined />,
label: '6. 全文解析与数据提取',
disabled: true,
title: '敬请期待'
},
{
key: 'data-analysis',
icon: <BarChartOutlined />,
label: '7. 数据综合分析与报告',
disabled: true,
title: '敬请期待'
},
];
// 处理菜单点击
const handleMenuClick: MenuProps['onClick'] = ({ key }) => {
if (key.startsWith('/')) {
navigate(key);
}
};
// 获取当前选中的菜单项和展开的子菜单
const currentPath = location.pathname;
const selectedKeys = [currentPath];
const openKeys = currentPath.includes('screening/title') ? ['title-screening'] : [];
return (
<Layout className="h-screen">
{/* 左侧导航栏 */}
<Sider
width={250}
className="bg-gray-50 border-r border-gray-200"
theme="light"
>
<div className="p-4 border-b border-gray-200">
<h2 className="text-lg font-bold text-gray-800">
<FilterOutlined className="mr-2" />
AI智能文献
</h2>
<p className="text-xs text-gray-500 mt-1">
MVP
</p>
</div>
<Menu
mode="inline"
selectedKeys={selectedKeys}
defaultOpenKeys={openKeys}
items={menuItems}
onClick={handleMenuClick}
className="border-none"
style={{ height: 'calc(100vh - 80px)', overflowY: 'auto' }}
/>
</Sider>
{/* 右侧内容区 */}
<Layout>
<Content className="bg-white overflow-auto">
<Outlet />
</Content>
</Layout>
</Layout>
);
};
export default ASLLayout;

View File

@@ -1,16 +1,45 @@
import Placeholder from '@/shared/components/Placeholder'
/**
* ASL模块入口
* AI智能文献筛选模块
*/
import { Suspense, lazy } from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import { Spin } from 'antd';
// 懒加载组件
const ASLLayout = lazy(() => import('./components/ASLLayout'));
const TitleScreeningSettings = lazy(() => import('./pages/TitleScreeningSettings'));
const TitleScreeningWorkbench = lazy(() => import('./pages/ScreeningWorkbench'));
const TitleScreeningResults = lazy(() => import('./pages/ScreeningResults'));
const ASLModule = () => {
return (
<Placeholder
title="AI智能文献模块"
description="Week 3 开始开发支持4个LLM的智能文献筛选和分析"
moduleName="ASL - AI Smart Literature"
/>
)
}
<Suspense
fallback={
<div className="flex items-center justify-center h-screen">
<Spin size="large" />
</div>
}
>
<Routes>
<Route path="" element={<ASLLayout />}>
<Route index element={<Navigate to="screening/title/settings" replace />} />
<Route path="screening/title">
<Route index element={<Navigate to="settings" replace />} />
<Route path="settings" element={<TitleScreeningSettings />} />
<Route path="workbench" element={<TitleScreeningWorkbench />} />
<Route path="results" element={<TitleScreeningResults />} />
</Route>
</Route>
</Routes>
</Suspense>
);
};
export default ASLModule;
export default ASLModule

View File

@@ -0,0 +1,44 @@
/**
* 标题摘要初筛 - 初筛结果页面
* TODO: Week 2 Day 5 开发
*
* 功能:
* - 统计卡片(总数/纳入/排除)
* - PRISMA排除原因统计
* - Tab切换纳入/排除)
* - 结果表格
* - 批量操作
* - 导出Excel
*/
import { Card, Empty, Alert } from 'antd';
const TitleScreeningResults = () => {
return (
<div className="p-6">
<div className="mb-6">
<h1 className="text-2xl font-bold mb-2"> - </h1>
<p className="text-gray-500">
PRISMA流程图
</p>
</div>
<Card>
<Alert
message="功能开发中"
description="Week 2 Day 5 将实现统计卡片、结果表格、批量操作、Excel导出等功能"
type="info"
showIcon
className="mb-4"
/>
<Empty
description="初筛结果页(开发中)"
image={Empty.PRESENTED_IMAGE_SIMPLE}
/>
</Card>
</div>
);
};
export default TitleScreeningResults;

View File

@@ -0,0 +1,42 @@
/**
* 标题摘要初筛 - 审核工作台页面
* TODO: Week 2 Day 3-4 开发
*
* 功能:
* - 显示当前PICOS标准折叠面板
* - 双行表格(严格按照原型)
* - 点击PICO维度 → 弹出双视图Modal
* - 修改最终决策
*/
import { Card, Empty, Alert } from 'antd';
const TitleScreeningWorkbench = () => {
return (
<div className="p-6">
<div className="mb-6">
<h1 className="text-2xl font-bold mb-2"> - </h1>
<p className="text-gray-500">
</p>
</div>
<Card>
<Alert
message="功能开发中"
description="Week 2 Day 3-4 将实现双行表格、双视图Modal、人工复核等功能"
type="info"
showIcon
className="mb-4"
/>
<Empty
description="审核工作台(开发中)"
image={Empty.PRESENTED_IMAGE_SIMPLE}
/>
</Card>
</div>
);
};
export default TitleScreeningWorkbench;

View File

@@ -0,0 +1,347 @@
/**
* 标题摘要初筛 - 设置与启动页面
*
* 功能:
* 1. Excel文献导入上传 + 模板下载)
* 2. PICOS标准配置
* 3. 纳入/排除标准配置
* 4. 筛选风格选择
* 5. 启动AI筛选
*/
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Card,
Form,
Input,
Button,
Upload,
Radio,
Space,
Tooltip,
message,
Alert,
Divider,
Row,
Col,
} from 'antd';
import {
InboxOutlined,
QuestionCircleOutlined,
DownloadOutlined,
PlayCircleOutlined,
} from '@ant-design/icons';
const { TextArea } = Input;
const { Dragger } = Upload;
const TitleScreeningSettings = () => {
const navigate = useNavigate();
const [form] = Form.useForm();
const [fileList, setFileList] = useState<any[]>([]);
const [literatureCount, setLiteratureCount] = useState(0);
const [canStart, setCanStart] = useState(false);
// 处理Excel上传
const handleFileUpload = async (file: File) => {
try {
// TODO: Week 2 Day 2 实现Excel解析
// 这里只是占位逻辑
setFileList([file]);
setLiteratureCount(100); // 模拟导入100篇文献
setCanStart(true);
message.success(`成功导入 100 篇文献`);
return false; // 阻止自动上传
} catch (error) {
message.error('文献导入失败');
return false;
}
};
// 下载Excel模板
const handleDownloadTemplate = () => {
// TODO: Week 2 Day 2 实现模板下载
message.info('Excel模板下载功能开发中...');
};
// 启动筛选
const handleStartScreening = async () => {
try {
const values = await form.validateFields();
// TODO: Week 2 调用后端API启动筛选
console.log('启动筛选:', values);
message.success('AI筛选已启动正在处理中...');
// 跳转到审核工作台
navigate('/literature/screening/title/workbench');
} catch (error) {
message.error('请完整填写筛选标准');
}
};
return (
<div className="p-6 max-w-7xl mx-auto">
{/* 页面标题 */}
<div className="mb-6">
<h1 className="text-2xl font-bold mb-2"> - </h1>
<p className="text-gray-500">
PICOS标准/AI筛选
</p>
</div>
<Form
form={form}
layout="vertical"
initialValues={{
screeningStyle: 'standard',
}}
>
{/* 步骤1: 配置筛选标准 */}
<Card title="步骤1: 配置筛选标准" className="mb-6">
{/* PICOS标准 */}
<Alert
message="PICOS标准"
description="系统评价研究问题的标准化框架,请详细填写每个维度"
type="info"
showIcon
className="mb-4"
/>
<Row gutter={16}>
{/* 左侧P + I */}
<Col span={12}>
<Form.Item
label={
<span className="text-base font-semibold">
P - (Population)
<Tooltip title="研究对象的特征,如年龄、性别、疾病类型等。可以包含主要人群和亚组人群。">
<QuestionCircleOutlined className="ml-2 text-gray-400" />
</Tooltip>
</span>
}
name={['picoCriteria', 'P']}
rules={[{ required: true, message: '请输入人群标准' }]}
>
<TextArea
rows={10}
placeholder="例如:&#10;Patients with non-cardioembolic ischemic stroke (NCIS) 非心源性缺血性卒中、亚洲人群&#10;亚组人群:&#10;1. NIHSS评分亚组卒中人群mild/moderate stroke&#10;2. 不同TOAST分型different TOAST subtypesexcluding cardioembolic stroke&#10;3. 高危TIA人群high-risk TIA population&#10;..."
className="font-mono text-sm"
/>
</Form.Item>
<Form.Item
label={
<span className="text-base font-semibold">
I - (Intervention)
<Tooltip title="研究的干预措施或暴露因素">
<QuestionCircleOutlined className="ml-2 text-gray-400" />
</Tooltip>
</span>
}
name={['picoCriteria', 'I']}
rules={[{ required: true, message: '请输入干预措施' }]}
>
<TextArea
rows={5}
placeholder="例如:抗血小板治疗药物(阿司匹林,氯吡格雷等)、抗凝药物(华法林等)、溶栓药物(阿替普酶等)"
className="font-mono text-sm"
/>
</Form.Item>
</Col>
{/* 右侧C + O + S */}
<Col span={12}>
<Form.Item
label={
<span className="text-base font-semibold">
C - (Comparison)
<Tooltip title="对照组的干预措施">
<QuestionCircleOutlined className="ml-2 text-gray-400" />
</Tooltip>
</span>
}
name={['picoCriteria', 'C']}
rules={[{ required: true, message: '请输入对照措施' }]}
>
<TextArea
rows={5}
placeholder="例如:安慰剂、常规治疗、其他药物对照等"
className="font-mono text-sm"
/>
</Form.Item>
<Form.Item
label={
<span className="text-base font-semibold">
O - (Outcome)
<Tooltip title="研究的主要结局指标(可选)">
<QuestionCircleOutlined className="ml-2 text-gray-400" />
</Tooltip>
</span>
}
name={['picoCriteria', 'O']}
>
<TextArea
rows={5}
placeholder="例如卒中进展、卒中复发、死亡、NIHSS评分变化、疗效、安全性等"
className="font-mono text-sm"
/>
</Form.Item>
<Form.Item
label={
<span className="text-base font-semibold">
S - (Study Design)
<Tooltip title="纳入的研究类型">
<QuestionCircleOutlined className="ml-2 text-gray-400" />
</Tooltip>
</span>
}
name={['picoCriteria', 'S']}
rules={[{ required: true, message: '请输入研究设计' }]}
>
<Input
placeholder="例如系统评价SR、随机对照试验RCT、真实世界研究RWE、观察性研究OBS"
className="font-mono text-sm"
/>
</Form.Item>
</Col>
</Row>
<Divider />
{/* 纳入标准 & 排除标准 - 并排显示 */}
<Row gutter={16}>
<Col span={12}>
<Form.Item
label={
<span className="text-base font-semibold">
<Tooltip title="文献必须满足的条件才能被纳入">
<QuestionCircleOutlined className="ml-2 text-gray-400" />
</Tooltip>
</span>
}
name="inclusionCriteria"
rules={[{ required: true, message: '请输入纳入标准' }]}
>
<TextArea
rows={10}
placeholder="详细的纳入标准,例如:&#10;1. 非心源性缺血性卒中、亚洲患者&#10;2. 包含二级预防相关研究&#10;3. 涉及抗血小板或抗凝药物&#10;4. 研究类型SR、RCT、RWE、OBS&#10;5. 近五年2020年之后的文献&#10;..."
className="font-mono text-sm"
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label={
<span className="text-base font-semibold">
<Tooltip title="满足这些条件的文献将被排除">
<QuestionCircleOutlined className="ml-2 text-gray-400" />
</Tooltip>
</span>
}
name="exclusionCriteria"
rules={[{ required: true, message: '请输入排除标准' }]}
>
<TextArea
rows={10}
placeholder="详细的排除标准,例如:&#10;1. 心源性卒中患者、非亚洲人群&#10;2. 急性期治疗研究(无二级预防关键词)&#10;3. 病例报告、会议摘要&#10;4. 非中英文文献&#10;5. 混合人群研究&#10;..."
className="font-mono text-sm"
/>
</Form.Item>
</Col>
</Row>
<Divider />
{/* 筛选风格 */}
<Form.Item
label={<span className="text-base font-semibold"></span>}
name="screeningStyle"
extra="提示:初筛推荐宽松模式,精筛推荐严格模式"
>
<Radio.Group size="large">
<Space direction="vertical" className="w-full">
<Radio value="lenient">
🔓 -
</Radio>
<Radio value="standard">
-
</Radio>
<Radio value="strict">
🔒 -
</Radio>
</Space>
</Radio.Group>
</Form.Item>
</Card>
{/* 步骤2: 导入文献 */}
<Card title="步骤2: 导入文献" className="mb-6">
<div className="text-center">
<Dragger
accept=".xlsx,.xls"
maxCount={1}
fileList={fileList}
beforeUpload={handleFileUpload}
onRemove={() => {
setFileList([]);
setLiteratureCount(0);
setCanStart(false);
}}
>
<p className="ant-upload-drag-icon">
<InboxOutlined style={{ fontSize: 48, color: '#1890ff' }} />
</p>
<p className="ant-upload-text">Excel文件到此区域</p>
<p className="ant-upload-hint">
.xlsx .xls Title Abstract
</p>
</Dragger>
<div className="mt-4">
<Button
icon={<DownloadOutlined />}
onClick={handleDownloadTemplate}
>
Excel模板
</Button>
</div>
{literatureCount > 0 && (
<Alert
message={`已导入 ${literatureCount} 篇文献`}
type="success"
showIcon
className="mt-4"
/>
)}
</div>
</Card>
</Form>
{/* 启动按钮 */}
<div className="text-center">
<Button
type="primary"
size="large"
icon={<PlayCircleOutlined />}
onClick={handleStartScreening}
disabled={!canStart}
className="px-12"
>
{canStart ? '开始AI筛选' : '请先导入文献'}
</Button>
</div>
</div>
);
};
export default TitleScreeningSettings;

View File

@@ -0,0 +1,223 @@
/**
* ASL模块 - TypeScript类型定义
*
* 包含:
* - 项目管理类型
* - 文献管理类型
* - 筛选结果类型
*/
// ==================== 项目管理类型 ====================
/**
* PICOS标准
*/
export interface PICOSCriteria {
P: string; // 人群 Population
I: string; // 干预 Intervention
C: string; // 对照 Comparison
O?: string; // 结局 Outcome可选
S: string; // 研究设计 Study Design
}
/**
* 筛选配置
*/
export interface ScreeningConfig {
style: 'lenient' | 'standard' | 'strict'; // 筛选风格
models: string[]; // 使用的模型列表
}
/**
* 筛选项目
*/
export interface ScreeningProject {
id: string;
projectName: string;
picoCriteria: PICOSCriteria;
inclusionCriteria: string;
exclusionCriteria: string;
screeningConfig: ScreeningConfig;
status: 'draft' | 'screening' | 'completed';
createdAt: string;
updatedAt: string;
userId: string;
// 统计信息(可选)
stats?: {
totalLiteratures: number;
screenedCount: number;
includedCount: number;
excludedCount: number;
pendingCount: number;
};
}
/**
* 创建项目请求
*/
export interface CreateProjectRequest {
projectName: string;
picoCriteria: PICOSCriteria;
inclusionCriteria: string;
exclusionCriteria: string;
screeningConfig: ScreeningConfig;
}
// ==================== 文献管理类型 ====================
/**
* 文献条目
*/
export interface Literature {
id: string;
projectId: string;
title: string;
abstract: string;
pmid?: string;
authors?: string;
journal?: string;
publicationYear?: number;
doi?: string;
createdAt: string;
}
/**
* 批量导入文献请求
*/
export interface ImportLiteraturesRequest {
literatures: Omit<Literature, 'id' | 'projectId' | 'createdAt'>[];
}
// ==================== 筛选结果类型 ====================
/**
* PICO判断
*/
export interface PICOJudgment {
P: 'match' | 'mismatch' | 'unclear';
I: 'match' | 'mismatch' | 'unclear';
C: 'match' | 'mismatch' | 'unclear';
S: 'match' | 'mismatch' | 'unclear';
}
/**
* 模型判断结果
*/
export interface ModelResult {
modelName: string; // 'DeepSeek-V3' | 'Qwen-Max'
conclusion: 'include' | 'exclude';
confidence: number; // 0-1
reason: string; // 完整的判断理由
judgment: PICOJudgment;
evidence?: { // 提取的证据短语
P?: string;
I?: string;
C?: string;
S?: string;
};
}
/**
* 筛选结果
*/
export interface ScreeningResult {
id: string;
projectId: string;
literatureId: string;
// 文献信息
literature: Literature;
// 双模型结果
model1Result: ModelResult;
model2Result: ModelResult;
// 最终决策
finalDecision: 'include' | 'exclude' | 'pending';
decisionMethod: 'auto_agree' | 'auto_strict' | 'manual' | 'manual_batch';
// 冲突信息
hasConflict: boolean;
conflictFields: string[]; // ['conclusion', 'P', 'I', 'C', 'S']
// 人工复核
reviewedAt?: string;
reviewedBy?: string;
reviewComment?: string;
createdAt: string;
updatedAt: string;
}
/**
* 筛选任务
*/
export interface ScreeningTask {
id: string;
projectId: string;
status: 'pending' | 'processing' | 'completed' | 'failed';
totalItems: number;
processedItems: number;
successItems: number;
failedItems: number;
progress: number; // 0-100
startedAt?: string;
completedAt?: string;
errorMessage?: string;
}
// ==================== API响应类型 ====================
/**
* 通用API响应
*/
export interface ApiResponse<T = any> {
success: boolean;
data?: T;
message?: string;
error?: {
code: string;
message: string;
details?: any;
};
}
/**
* 分页响应
*/
export interface PaginatedResponse<T = any> {
items: T[];
total: number;
page: number;
pageSize: number;
hasMore: boolean;
}
// ==================== 导出统计类型 ====================
/**
* 项目统计
*/
export interface ProjectStatistics {
totalLiteratures: number;
screenedCount: number;
includedCount: number;
excludedCount: number;
pendingCount: number;
conflictCount: number;
reviewedCount: number;
}
/**
* 排除原因统计
*/
export interface ExclusionReasons {
[key: string]: number;
nonRCT: number;
populationMismatch: number;
interventionMismatch: number;
comparisonMismatch: number;
other: number;
}

View File

@@ -18,3 +18,5 @@ export default DCModule

View File

@@ -18,3 +18,5 @@ export default PKBModule

View File

@@ -22,3 +22,5 @@ export default SSAModule

View File

@@ -22,3 +22,5 @@ export default STModule

View File

@@ -48,3 +48,5 @@ export default Placeholder