feat(frontend): add batch processing and review features
- Add batch processing API and mode - Add deep read mode for full-text analysis - Add document selector and switcher components - Add review page with editorial and methodology assessment - Add capacity indicator and usage info modal - Add custom hooks for batch tasks and chat modes - Update layouts and routing - Add TypeScript types for chat features
This commit is contained in:
192
frontend/src/components/review/EditorialReview.tsx
Normal file
192
frontend/src/components/review/EditorialReview.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
import { Card, Collapse, Space, Typography, Tag, Alert, Empty, List, Divider } from 'antd';
|
||||
import {
|
||||
CheckCircleOutlined,
|
||||
WarningOutlined,
|
||||
CloseCircleOutlined,
|
||||
FileTextOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import type { EditorialReview as EditorialReviewType, EditorialItem } from '../../api/reviewApi';
|
||||
import ScoreCard from './ScoreCard';
|
||||
|
||||
const { Title, Text, Paragraph } = Typography;
|
||||
const { Panel } = Collapse;
|
||||
|
||||
interface EditorialReviewProps {
|
||||
data: EditorialReviewType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 稿约规范性评估详情组件
|
||||
* 展示11个评估标准的详细结果
|
||||
*/
|
||||
const EditorialReview = ({ data }: EditorialReviewProps) => {
|
||||
if (!data) {
|
||||
return <Empty description="暂无评估数据" />;
|
||||
}
|
||||
|
||||
// 获取状态图标和颜色
|
||||
const getStatusDisplay = (status: 'pass' | 'warning' | 'fail') => {
|
||||
const config = {
|
||||
pass: {
|
||||
icon: <CheckCircleOutlined />,
|
||||
color: 'success' as const,
|
||||
text: '通过',
|
||||
},
|
||||
warning: {
|
||||
icon: <WarningOutlined />,
|
||||
color: 'warning' as const,
|
||||
text: '警告',
|
||||
},
|
||||
fail: {
|
||||
icon: <CloseCircleOutlined />,
|
||||
color: 'error' as const,
|
||||
text: '不通过',
|
||||
},
|
||||
};
|
||||
return config[status];
|
||||
};
|
||||
|
||||
// 统计数据
|
||||
const stats = {
|
||||
total: data.items.length,
|
||||
pass: data.items.filter((item) => item.status === 'pass').length,
|
||||
warning: data.items.filter((item) => item.status === 'warning').length,
|
||||
fail: data.items.filter((item) => item.status === 'fail').length,
|
||||
};
|
||||
|
||||
return (
|
||||
<Card title={
|
||||
<Space>
|
||||
<FileTextOutlined />
|
||||
<span>稿约规范性评估详情</span>
|
||||
</Space>
|
||||
}>
|
||||
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||
{/* 总体评分 */}
|
||||
<ScoreCard
|
||||
title="总体评分"
|
||||
score={data.overall_score}
|
||||
description="稿约规范性综合评分"
|
||||
size="large"
|
||||
/>
|
||||
|
||||
{/* 总结 */}
|
||||
<Alert
|
||||
message="评估总结"
|
||||
description={data.summary}
|
||||
type="info"
|
||||
showIcon
|
||||
/>
|
||||
|
||||
{/* 统计信息 */}
|
||||
<Card size="small">
|
||||
<Space size="large">
|
||||
<div>
|
||||
<Text type="secondary">评估标准:</Text>
|
||||
<Text strong style={{ fontSize: 16, marginLeft: 8 }}>{stats.total}</Text>
|
||||
</div>
|
||||
<Divider type="vertical" />
|
||||
<div>
|
||||
<Text type="success">通过:</Text>
|
||||
<Text strong style={{ fontSize: 16, marginLeft: 8, color: '#52c41a' }}>{stats.pass}</Text>
|
||||
</div>
|
||||
<Divider type="vertical" />
|
||||
<div>
|
||||
<Text type="warning">警告:</Text>
|
||||
<Text strong style={{ fontSize: 16, marginLeft: 8, color: '#faad14' }}>{stats.warning}</Text>
|
||||
</div>
|
||||
<Divider type="vertical" />
|
||||
<div>
|
||||
<Text type="danger">不通过:</Text>
|
||||
<Text strong style={{ fontSize: 16, marginLeft: 8, color: '#f5222d' }}>{stats.fail}</Text>
|
||||
</div>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
{/* 详细评估结果 */}
|
||||
<Card title="详细评估(11项标准)" size="small">
|
||||
<Collapse defaultActiveKey={data.items.map((_, index) => index.toString())}>
|
||||
{data.items.map((item: EditorialItem, index: number) => {
|
||||
const statusDisplay = getStatusDisplay(item.status);
|
||||
|
||||
return (
|
||||
<Panel
|
||||
header={
|
||||
<Space>
|
||||
<Tag icon={statusDisplay.icon} color={statusDisplay.color}>
|
||||
{statusDisplay.text}
|
||||
</Tag>
|
||||
<Text strong>{item.criterion}</Text>
|
||||
<Text type="secondary">({item.score}分)</Text>
|
||||
</Space>
|
||||
}
|
||||
key={index}
|
||||
>
|
||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||
{/* 评分 */}
|
||||
<div>
|
||||
<Text strong>评分:</Text>
|
||||
<Text style={{ fontSize: 18, marginLeft: 8, color: '#1890ff' }}>
|
||||
{item.score} / 100
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{/* 问题列表 */}
|
||||
{item.issues && item.issues.length > 0 && (
|
||||
<div>
|
||||
<Text strong style={{ color: '#f5222d' }}>发现的问题:</Text>
|
||||
<List
|
||||
size="small"
|
||||
bordered
|
||||
dataSource={item.issues}
|
||||
renderItem={(issue: string) => (
|
||||
<List.Item>
|
||||
<CloseCircleOutlined style={{ color: '#f5222d', marginRight: 8 }} />
|
||||
{issue}
|
||||
</List.Item>
|
||||
)}
|
||||
style={{ marginTop: 8 }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 改进建议 */}
|
||||
{item.suggestions && item.suggestions.length > 0 && (
|
||||
<div>
|
||||
<Text strong style={{ color: '#52c41a' }}>改进建议:</Text>
|
||||
<List
|
||||
size="small"
|
||||
bordered
|
||||
dataSource={item.suggestions}
|
||||
renderItem={(suggestion: string) => (
|
||||
<List.Item>
|
||||
<CheckCircleOutlined style={{ color: '#52c41a', marginRight: 8 }} />
|
||||
{suggestion}
|
||||
</List.Item>
|
||||
)}
|
||||
style={{ marginTop: 8 }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 无问题提示 */}
|
||||
{(!item.issues || item.issues.length === 0) && item.status === 'pass' && (
|
||||
<Alert
|
||||
message="该项检查通过,无需修改"
|
||||
type="success"
|
||||
showIcon
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
</Panel>
|
||||
);
|
||||
})}
|
||||
</Collapse>
|
||||
</Card>
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditorialReview;
|
||||
|
||||
207
frontend/src/components/review/MethodologyReview.tsx
Normal file
207
frontend/src/components/review/MethodologyReview.tsx
Normal file
@@ -0,0 +1,207 @@
|
||||
import { Card, Collapse, Space, Typography, Tag, Alert, Empty, List, Divider } from 'antd';
|
||||
import {
|
||||
ExperimentOutlined,
|
||||
WarningOutlined,
|
||||
InfoCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import type { MethodologyReview as MethodologyReviewType, MethodologyPart, MethodologyIssue } from '../../api/reviewApi';
|
||||
import ScoreCard from './ScoreCard';
|
||||
|
||||
const { Title, Text, Paragraph } = Typography;
|
||||
const { Panel } = Collapse;
|
||||
|
||||
interface MethodologyReviewProps {
|
||||
data: MethodologyReviewType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法学评估详情组件
|
||||
* 展示3个部分的方法学评估结果
|
||||
*/
|
||||
const MethodologyReview = ({ data }: MethodologyReviewProps) => {
|
||||
if (!data) {
|
||||
return <Empty description="暂无评估数据" />;
|
||||
}
|
||||
|
||||
// 获取严重程度显示
|
||||
const getSeverityDisplay = (severity: 'major' | 'minor') => {
|
||||
const config = {
|
||||
major: {
|
||||
color: 'error' as const,
|
||||
text: '严重',
|
||||
},
|
||||
minor: {
|
||||
color: 'warning' as const,
|
||||
text: '轻微',
|
||||
},
|
||||
};
|
||||
return config[severity];
|
||||
};
|
||||
|
||||
// 统计数据
|
||||
const stats = {
|
||||
totalParts: data.parts.length,
|
||||
totalIssues: data.parts.reduce((sum, part) => sum + part.issues.length, 0),
|
||||
majorIssues: data.parts.reduce((sum, part) => {
|
||||
return sum + part.issues.filter((issue) => issue.severity === 'major').length;
|
||||
}, 0),
|
||||
minorIssues: data.parts.reduce((sum, part) => {
|
||||
return sum + part.issues.filter((issue) => issue.severity === 'minor').length;
|
||||
}, 0),
|
||||
};
|
||||
|
||||
return (
|
||||
<Card title={
|
||||
<Space>
|
||||
<ExperimentOutlined />
|
||||
<span>方法学评估详情</span>
|
||||
</Space>
|
||||
}>
|
||||
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||
{/* 总体评分 */}
|
||||
<ScoreCard
|
||||
title="总体评分"
|
||||
score={data.overall_score}
|
||||
description="方法学综合评分"
|
||||
size="large"
|
||||
/>
|
||||
|
||||
{/* 总结 */}
|
||||
<Alert
|
||||
message="评估总结"
|
||||
description={data.summary}
|
||||
type="info"
|
||||
showIcon
|
||||
/>
|
||||
|
||||
{/* 统计信息 */}
|
||||
<Card size="small">
|
||||
<Space size="large">
|
||||
<div>
|
||||
<Text type="secondary">评估部分:</Text>
|
||||
<Text strong style={{ fontSize: 16, marginLeft: 8 }}>{stats.totalParts}</Text>
|
||||
</div>
|
||||
<Divider type="vertical" />
|
||||
<div>
|
||||
<Text type="secondary">发现问题:</Text>
|
||||
<Text strong style={{ fontSize: 16, marginLeft: 8 }}>{stats.totalIssues}</Text>
|
||||
</div>
|
||||
<Divider type="vertical" />
|
||||
<div>
|
||||
<Text type="danger">严重:</Text>
|
||||
<Text strong style={{ fontSize: 16, marginLeft: 8, color: '#f5222d' }}>{stats.majorIssues}</Text>
|
||||
</div>
|
||||
<Divider type="vertical" />
|
||||
<div>
|
||||
<Text type="warning">轻微:</Text>
|
||||
<Text strong style={{ fontSize: 16, marginLeft: 8, color: '#faad14' }}>{stats.minorIssues}</Text>
|
||||
</div>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
{/* 详细评估结果(3个部分) */}
|
||||
<Card title="详细评估(3个部分)" size="small">
|
||||
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||
{data.parts.map((part: MethodologyPart, partIndex: number) => (
|
||||
<Card
|
||||
key={partIndex}
|
||||
title={
|
||||
<Space>
|
||||
<Text strong style={{ fontSize: 16 }}>{part.part}</Text>
|
||||
<Tag color="blue">{part.score}分</Tag>
|
||||
<Tag color={part.issues.length === 0 ? 'success' : 'warning'}>
|
||||
{part.issues.length}个问题
|
||||
</Tag>
|
||||
</Space>
|
||||
}
|
||||
size="small"
|
||||
>
|
||||
{part.issues.length === 0 ? (
|
||||
<Alert
|
||||
message="该部分检查通过,未发现问题"
|
||||
type="success"
|
||||
showIcon
|
||||
/>
|
||||
) : (
|
||||
<Collapse defaultActiveKey={part.issues.map((_, issueIndex) => issueIndex.toString())}>
|
||||
{part.issues.map((issue: MethodologyIssue, issueIndex: number) => {
|
||||
const severityDisplay = getSeverityDisplay(issue.severity);
|
||||
|
||||
return (
|
||||
<Panel
|
||||
header={
|
||||
<Space>
|
||||
<Tag color={severityDisplay.color}>
|
||||
{severityDisplay.text}
|
||||
</Tag>
|
||||
<Text strong>{issue.type}</Text>
|
||||
</Space>
|
||||
}
|
||||
key={issueIndex}
|
||||
>
|
||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||
{/* 问题描述 */}
|
||||
<div>
|
||||
<Text strong style={{ color: '#f5222d' }}>
|
||||
<WarningOutlined style={{ marginRight: 8 }} />
|
||||
问题描述:
|
||||
</Text>
|
||||
<Paragraph style={{ marginTop: 8, marginBottom: 0, paddingLeft: 24 }}>
|
||||
{issue.description}
|
||||
</Paragraph>
|
||||
</div>
|
||||
|
||||
{/* 位置 */}
|
||||
<div>
|
||||
<Text strong style={{ color: '#1890ff' }}>
|
||||
<InfoCircleOutlined style={{ marginRight: 8 }} />
|
||||
问题位置:
|
||||
</Text>
|
||||
<Paragraph style={{ marginTop: 8, marginBottom: 0, paddingLeft: 24 }}>
|
||||
{issue.location}
|
||||
</Paragraph>
|
||||
</div>
|
||||
|
||||
{/* 改进建议 */}
|
||||
<div>
|
||||
<Text strong style={{ color: '#52c41a' }}>
|
||||
改进建议:
|
||||
</Text>
|
||||
<Alert
|
||||
message={issue.suggestion}
|
||||
type="success"
|
||||
showIcon
|
||||
style={{ marginTop: 8 }}
|
||||
/>
|
||||
</div>
|
||||
</Space>
|
||||
</Panel>
|
||||
);
|
||||
})}
|
||||
</Collapse>
|
||||
)}
|
||||
</Card>
|
||||
))}
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
{/* 提示信息 */}
|
||||
<Alert
|
||||
message="方法学评估说明"
|
||||
description={
|
||||
<Space direction="vertical">
|
||||
<Text>• <strong>科研设计评估</strong>:检查研究类型、对象、设计要素等</Text>
|
||||
<Text>• <strong>统计学方法描述</strong>:检查软件、资料类型、统计方法等描述是否完整</Text>
|
||||
<Text>• <strong>统计分析评估</strong>:检查统计方法使用和结果描述是否正确</Text>
|
||||
</Space>
|
||||
}
|
||||
type="info"
|
||||
showIcon
|
||||
/>
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default MethodologyReview;
|
||||
|
||||
122
frontend/src/components/review/ScoreCard.tsx
Normal file
122
frontend/src/components/review/ScoreCard.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import { Card, Progress, Space, Typography, Tag } from 'antd';
|
||||
import { TrophyOutlined, CheckCircleOutlined, WarningOutlined, CloseCircleOutlined } from '@ant-design/icons';
|
||||
import { getScoreLevel } from '../../api/reviewApi';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
interface ScoreCardProps {
|
||||
title: string;
|
||||
score: number;
|
||||
maxScore?: number;
|
||||
description?: string;
|
||||
showProgress?: boolean;
|
||||
size?: 'small' | 'default' | 'large';
|
||||
}
|
||||
|
||||
/**
|
||||
* 评分卡片组件
|
||||
* 用于展示单项评分
|
||||
*/
|
||||
const ScoreCard = ({
|
||||
title,
|
||||
score,
|
||||
maxScore = 100,
|
||||
description,
|
||||
showProgress = true,
|
||||
size = 'default',
|
||||
}: ScoreCardProps) => {
|
||||
const { level, text, color } = getScoreLevel(score);
|
||||
|
||||
// 获取等级图标
|
||||
const getLevelIcon = () => {
|
||||
switch (level) {
|
||||
case 'excellent':
|
||||
return <TrophyOutlined style={{ color }} />;
|
||||
case 'good':
|
||||
return <CheckCircleOutlined style={{ color }} />;
|
||||
case 'fair':
|
||||
return <WarningOutlined style={{ color }} />;
|
||||
case 'poor':
|
||||
return <CloseCircleOutlined style={{ color }} />;
|
||||
}
|
||||
};
|
||||
|
||||
// 根据size调整字体大小
|
||||
const scoreFontSize = size === 'large' ? 56 : size === 'small' ? 32 : 44;
|
||||
const titleSize = size === 'large' ? 3 : size === 'small' ? 5 : 4;
|
||||
|
||||
return (
|
||||
<Card hoverable>
|
||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||
{/* 标题 */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Title level={titleSize as 3 | 4 | 5} style={{ margin: 0 }}>
|
||||
{title}
|
||||
</Title>
|
||||
<Tag icon={getLevelIcon()} color={color} style={{ margin: 0 }}>
|
||||
{text}
|
||||
</Tag>
|
||||
</div>
|
||||
|
||||
{/* 分数 */}
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{ fontSize: scoreFontSize, fontWeight: 'bold', color }}>
|
||||
{score.toFixed(1)}
|
||||
</div>
|
||||
<Text type="secondary" style={{ fontSize: size === 'small' ? 12 : 14 }}>
|
||||
满分 {maxScore} 分
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{/* 进度条 */}
|
||||
{showProgress && (
|
||||
<Progress
|
||||
percent={Math.round((score / maxScore) * 100)}
|
||||
strokeColor={color}
|
||||
status="active"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 描述 */}
|
||||
{description && (
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScoreCard;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user