Files
deerflow-code/web-code/pages/ResultManagement.tsx
T
2026-05-27 19:43:15 +08:00

552 lines
22 KiB
TypeScript

import React, { useState } from 'react';
import {
FileText,
Search,
Filter,
Plus,
Download,
Edit3,
Send,
Clock,
CheckCircle,
AlertCircle,
ChevronRight,
Target,
Brain,
TrendingUp,
Compass,
Database,
MoreHorizontal,
FolderOpen,
Folder,
BarChart3,
} from 'lucide-react';
import { useTheme } from '../contexts/ThemeContext';
import { PageType } from '../components/Sidebar';
type ResultCategory = 'planning' | 'mnty' | 'effect-pg' | 'xk' | 'dq-research' | 'other';
interface ResultTask {
id: string;
title: string;
category: ResultCategory;
subCategory: string;
status: 'completed' | 'pending' | 'draft';
createdAt: string;
updatedAt: string;
reportType: 'strategy' | 'consult' | 'analysis' | 'summary';
agentCount: number;
description: string;
}
interface ResultManagementProps {
onNavigate: (page: PageType, taskId?: string) => void;
initialCategory?: ResultCategory;
}
// 任务类别配置
const categoryConfig: Record<ResultCategory, { label: string; icon: React.ElementType; color: string; subCategories: string[] }> = {
'planning': {
label: '规划任务',
icon: Target,
color: 'blue',
subCategories: ['目标分析', '品牌策略', '内容规划', '渠道规划', '叙事策略']
},
'mnty': {
label: 'MNTY任务',
icon: Brain,
color: 'purple',
subCategories: ['机制研究', '方案设计', 'ZSTY分析']
},
'effect-pg': {
label: '效果PG任务',
icon: TrendingUp,
color: 'amber',
subCategories: ['行为PG', '效果评估', '信息PG', '账号PG']
},
'xk': {
label: 'XK任务',
icon: Compass,
color: 'cyan',
subCategories: ['任务JC', '任务调度']
},
'dq-research': {
label: 'DQ研究类任务',
icon: Database,
color: 'green',
subCategories: ['受众分析', '知识库研究', '趋势研究']
},
'other': {
label: '其他类任务',
icon: MoreHorizontal,
color: 'slate',
subCategories: ['通用分析', '临时任务', '其他']
}
};
// 模拟任务数据
const mockTasks: ResultTask[] = [
{
id: 'task-001',
title: '五四青年节宣传活动策略',
category: 'planning',
subCategory: '叙事策略',
status: 'completed',
createdAt: '2026-03-19 10:30',
updatedAt: '2026-03-19 15:30',
reportType: 'strategy',
agentCount: 6,
description: '针对五四青年节的品牌传播策略方案'
},
{
id: 'task-002',
title: '品牌年轻化受众分析',
category: 'dq-research',
subCategory: '受众分析',
status: 'completed',
createdAt: '2026-03-18 14:20',
updatedAt: '2026-03-18 16:45',
reportType: 'analysis',
agentCount: 3,
description: '分析Z世代用户的消费行为和品牌偏好'
},
{
id: 'task-003',
title: 'UGC激励机制设计',
category: 'effect-pg',
subCategory: '效果评估',
status: 'completed',
createdAt: '2026-03-17 09:00',
updatedAt: '2026-03-17 11:20',
reportType: 'strategy',
agentCount: 4,
description: '设计用户生成内容激励机制方案'
},
{
id: 'task-004',
title: '短视频平台效果预测',
category: 'mnty',
subCategory: '方案设计',
status: 'pending',
createdAt: '2026-03-20 08:00',
updatedAt: '2026-03-20 08:00',
reportType: 'consult',
agentCount: 5,
description: '预测不同短视频平台的传播效果'
},
{
id: 'task-005',
title: '活动传播风险评估',
category: 'planning',
subCategory: '目标分析',
status: 'draft',
createdAt: '2026-03-20 10:00',
updatedAt: '2026-03-20 10:30',
reportType: 'analysis',
agentCount: 2,
description: '评估五四活动可能面临的传播风险'
},
{
id: 'task-006',
title: '竞品营销策略研究',
category: 'dq-research',
subCategory: '趋势研究',
status: 'completed',
createdAt: '2026-03-16 13:00',
updatedAt: '2026-03-16 18:00',
reportType: 'analysis',
agentCount: 3,
description: '分析主要竞品的营销策略和效果'
},
];
const ResultManagement: React.FC<ResultManagementProps> = ({ onNavigate, initialCategory = 'planning' }) => {
const { theme } = useTheme();
const isDark = theme === 'dark';
// 主题颜色类
const cardBg = isDark ? 'bg-slate-800/30' : 'bg-white';
const cardBorder = isDark ? 'border-blue-800/30' : 'border-gray-200';
const textPrimary = isDark ? 'text-white' : 'text-gray-900';
const textSecondary = isDark ? 'text-slate-400' : 'text-gray-600';
const textMuted = isDark ? 'text-slate-500' : 'text-gray-500';
const btnPrimary = isDark ? 'bg-blue-600 hover:bg-blue-500' : 'bg-red-600 hover:bg-red-500';
const btnSecondary = isDark ? 'bg-slate-700/50 hover:bg-slate-600/50 border-blue-800/30 text-slate-300' : 'bg-gray-100 hover:bg-gray-200 border-gray-300 text-gray-700';
const inputBg = isDark ? 'bg-slate-900/50' : 'bg-gray-50';
const inputBorder = isDark ? 'border-blue-800/30' : 'border-gray-300';
// 状态
const [selectedCategory, setSelectedCategory] = useState<ResultCategory>(initialCategory);
const [selectedSubCategory, setSelectedSubCategory] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState('');
const [statusFilter, setStatusFilter] = useState<'all' | 'completed' | 'pending' | 'draft'>('all');
const [expandedCategories, setExpandedCategories] = useState<Set<ResultCategory>>(
new Set([initialCategory])
);
// 切换分类展开状态
const toggleCategory = (category: ResultCategory) => {
setExpandedCategories(prev => {
const next = new Set(prev);
if (next.has(category)) {
next.delete(category);
} else {
next.add(category);
}
return next;
});
};
// 过滤任务
const filteredTasks = mockTasks.filter(task => {
const matchesCategory = task.category === selectedCategory;
const matchesSubCategory = !selectedSubCategory || task.subCategory === selectedSubCategory;
const matchesSearch = !searchQuery ||
task.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
task.description.toLowerCase().includes(searchQuery.toLowerCase());
const matchesStatus = statusFilter === 'all' || task.status === statusFilter;
return matchesCategory && matchesSubCategory && matchesSearch && matchesStatus;
});
// 获取状态样式
const getStatusStyle = (status: ResultTask['status']) => {
switch (status) {
case 'completed':
return {
bg: isDark ? 'bg-green-500/20 text-green-400' : 'bg-green-100 text-green-700',
icon: CheckCircle,
label: '已完成'
};
case 'pending':
return {
bg: isDark ? 'bg-yellow-500/20 text-yellow-400' : 'bg-yellow-100 text-yellow-700',
icon: Clock,
label: '进行中'
};
case 'draft':
return {
bg: isDark ? 'bg-slate-500/20 text-slate-400' : 'bg-gray-100 text-gray-700',
icon: Edit3,
label: '草稿'
};
}
};
// 获取报告类型样式
const getReportTypeStyle = (type: ResultTask['reportType']) => {
const styles = {
'strategy': { color: 'blue', label: '策略报告' },
'consult': { color: 'purple', label: '会商报告' },
'analysis': { color: 'cyan', label: '分析报告' },
'summary': { color: 'green', label: '总结报告' }
};
return styles[type];
};
// 查看任务详情
const handleViewDetail = (taskId: string) => {
onNavigate('result-detail', taskId);
};
const currentCategoryConfig = categoryConfig[selectedCategory];
const CategoryIcon = currentCategoryConfig.icon;
return (
<div className="h-full flex gap-6">
{/* 左侧分类导航 */}
<div className={`w-72 flex-shrink-0 ${cardBg} backdrop-blur-xl ${cardBorder} rounded-xl p-4 overflow-hidden flex flex-col`}>
<div className="flex items-center justify-between mb-4">
<h2 className={`${textPrimary} font-semibold text-base`}>结果管理</h2>
<button className={`p-1.5 rounded-lg ${btnSecondary}`}>
<Plus className="w-4 h-4" />
</button>
</div>
{/* 分类列表 */}
<div className="flex-1 overflow-y-auto space-y-2 custom-scrollbar">
{(Object.entries(categoryConfig) as [ResultCategory, typeof categoryConfig[ResultCategory]][]).map(([key, config]) => {
const Icon = config.icon;
const isExpanded = expandedCategories.has(key);
const isActive = selectedCategory === key;
return (
<div key={key} className="space-y-1">
{/* 一级分类 */}
<button
onClick={() => {
setSelectedCategory(key);
setSelectedSubCategory(null);
if (!expandedCategories.has(key)) {
toggleCategory(key);
}
}}
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all text-left ${
isActive
? isDark
? 'bg-blue-600/20 text-blue-400 border border-blue-500/30'
: 'bg-red-50 text-red-600 border border-red-200'
: isDark
? 'text-slate-400 hover:text-white hover:bg-slate-700/50'
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'
}`}
>
{isExpanded ? (
<FolderOpen className={`w-4 h-4 ${isActive ? '' : 'opacity-60'}`} />
) : (
<Folder className={`w-4 h-4 ${isActive ? '' : 'opacity-60'}`} />
)}
<span className="text-sm font-medium flex-1">{config.label}</span>
<ChevronRight className={`w-4 h-4 transition-transform ${isExpanded ? 'rotate-90' : ''} ${isActive ? '' : 'opacity-60'}`} />
</button>
{/* 二级分类 */}
{isExpanded && (
<div className="ml-4 pl-4 border-l space-y-1">
{/* 全部 */}
<button
onClick={() => {
setSelectedCategory(key);
setSelectedSubCategory(null);
}}
className={`w-full flex items-center gap-2 px-3 py-2 rounded-lg transition-all text-left ${
selectedCategory === key && !selectedSubCategory
? isDark
? 'bg-blue-500/20 text-blue-400'
: 'bg-red-100 text-red-600'
: isDark
? 'text-slate-500 hover:text-slate-300'
: 'text-gray-500 hover:text-gray-700'
}`}
>
<BarChart3 className="w-3.5 h-3.5" />
<span className="text-xs font-medium">全部</span>
<span className={`ml-auto px-1.5 py-0.5 rounded text-xs ${
isDark ? 'bg-slate-700 text-slate-400' : 'bg-gray-200 text-gray-500'
}`}>
{mockTasks.filter(t => t.category === key).length}
</span>
</button>
{/* 各子分类 */}
{config.subCategories.map(sub => {
const count = mockTasks.filter(t => t.category === key && t.subCategory === sub).length;
if (count === 0) return null;
return (
<button
key={sub}
onClick={() => {
setSelectedCategory(key);
setSelectedSubCategory(sub);
}}
className={`w-full flex items-center gap-2 px-3 py-2 rounded-lg transition-all text-left ${
selectedCategory === key && selectedSubCategory === sub
? isDark
? 'bg-blue-500/20 text-blue-400'
: 'bg-red-100 text-red-600'
: isDark
? 'text-slate-500 hover:text-slate-300'
: 'text-gray-500 hover:text-gray-700'
}`}
>
<div className={`w-1.5 h-1.5 rounded-full ${
isActive ? `bg-${config.color}-400` : (isDark ? 'bg-slate-600' : 'bg-gray-300')
}`} />
<span className="text-xs">{sub}</span>
<span className={`ml-auto px-1.5 py-0.5 rounded text-xs ${
isDark ? 'bg-slate-700 text-slate-400' : 'bg-gray-200 text-gray-500'
}`}>
{count}
</span>
</button>
);
})}
</div>
)}
</div>
);
})}
</div>
</div>
{/* 右侧任务列表 */}
<div className="flex-1 flex flex-col min-w-0">
{/* 顶部工具栏 */}
<div className={`${cardBg} backdrop-blur-xl ${cardBorder} rounded-xl p-4 mb-4`}>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-xl bg-${currentCategoryConfig.color}-500/20 flex items-center justify-center`}>
<CategoryIcon className={`w-5 h-5 text-${currentCategoryConfig.color}-400`} />
</div>
<div>
<h2 className={`${textPrimary} font-semibold`}>
{selectedSubCategory || currentCategoryConfig.label}
</h2>
<p className={`${textMuted} text-xs`}>
共 {filteredTasks.length} 个结果
</p>
</div>
</div>
<div className="flex items-center gap-2">
<button className={`flex items-center gap-2 px-3 py-2 rounded-lg ${btnSecondary} text-sm`}>
<Download className="w-4 h-4" />
批量导出
</button>
</div>
</div>
{/* 搜索和筛选 */}
<div className="flex items-center gap-3">
<div className={`flex-1 relative ${inputBg} rounded-lg`}>
<Search className={`absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 ${textMuted}`} />
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="搜索任务标题或描述..."
className={`w-full pl-10 pr-4 py-2.5 rounded-lg text-sm bg-transparent focus:outline-none ${
isDark ? 'text-white placeholder-slate-500' : 'text-gray-800 placeholder-gray-400'
}`}
/>
</div>
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value as typeof statusFilter)}
className={`px-3 py-2.5 rounded-lg text-sm ${inputBg} ${inputBorder} focus:outline-none ${
isDark ? 'text-slate-300' : 'text-gray-700'
}`}
>
<option value="all">全部状态</option>
<option value="completed">已完成</option>
<option value="pending">进行中</option>
<option value="draft">草稿</option>
</select>
</div>
</div>
{/* 任务列表 */}
<div className={`flex-1 ${cardBg} backdrop-blur-xl ${cardBorder} rounded-xl overflow-hidden`}>
{filteredTasks.length === 0 ? (
<div className="h-full flex flex-col items-center justify-center">
<FileText className={`w-16 h-16 ${textMuted} mb-4`} />
<p className={`${textSecondary} text-lg`}>暂无结果</p>
<p className={`${textMuted} text-sm mt-1`}>在对应任务中生成结果后,将在此显示</p>
</div>
) : (
<div className="h-full overflow-y-auto custom-scrollbar">
<table className="w-full">
<thead className={`sticky top-0 ${isDark ? 'bg-slate-800/80' : 'bg-gray-50'}`}>
<tr className={`text-xs ${textMuted} border-b ${cardBorder}`}>
<th className="text-left font-medium px-4 py-3">任务名称</th>
<th className="text-left font-medium px-4 py-3">报告类型</th>
<th className="text-left font-medium px-4 py-3">智能体数</th>
<th className="text-left font-medium px-4 py-3">状态</th>
<th className="text-left font-medium px-4 py-3">更新时间</th>
<th className="text-left font-medium px-4 py-3">操作</th>
</tr>
</thead>
<tbody>
{filteredTasks.map((task) => {
const statusInfo = getStatusStyle(task.status);
const StatusIcon = statusInfo.icon;
const reportTypeInfo = getReportTypeStyle(task.reportType);
return (
<tr
key={task.id}
className={`border-b cursor-pointer hover:${
isDark ? 'bg-slate-700/30' : 'bg-gray-50'
} transition-colors ${cardBorder}`}
onClick={() => handleViewDetail(task.id)}
>
<td className="px-4 py-4">
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-lg bg-${reportTypeInfo.color}-500/20 flex items-center justify-center flex-shrink-0`}>
<FileText className={`w-5 h-5 text-${reportTypeInfo.color}-400`} />
</div>
<div className="min-w-0">
<p className={`${textPrimary} text-sm font-medium truncate`}>{task.title}</p>
<p className={`${textMuted} text-xs truncate`}>{task.description}</p>
</div>
</div>
</td>
<td className="px-4 py-4">
<span className={`px-2 py-1 rounded-lg text-xs font-medium ${
isDark ? `bg-${reportTypeInfo.color}-500/20 text-${reportTypeInfo.color}-400` : `bg-${reportTypeInfo.color}-100 text-${reportTypeInfo.color}-700`
}`}>
{reportTypeInfo.label}
</span>
</td>
<td className="px-4 py-4">
<span className={`${textSecondary} text-sm`}>{task.agentCount}</span>
</td>
<td className="px-4 py-4">
<span className={`inline-flex items-center gap-1.5 px-2 py-1 rounded-lg text-xs font-medium ${statusInfo.bg}`}>
<StatusIcon className="w-3 h-3" />
{statusInfo.label}
</span>
</td>
<td className="px-4 py-4">
<span className={`${textMuted} text-sm`}>{task.updatedAt}</span>
</td>
<td className="px-4 py-4" onClick={(e) => e.stopPropagation()}>
<div className="flex items-center gap-1">
<button
onClick={() => handleViewDetail(task.id)}
className={`p-1.5 rounded-lg hover:${isDark ? 'bg-slate-700' : 'bg-gray-100'} transition-colors`}
title="查看详情"
>
<FileText className={`w-4 h-4 ${textSecondary}`} />
</button>
<button
className={`p-1.5 rounded-lg hover:${isDark ? 'bg-slate-700' : 'bg-gray-100'} transition-colors`}
title="编辑"
>
<Edit3 className={`w-4 h-4 ${textSecondary}`} />
</button>
<button
className={`p-1.5 rounded-lg hover:${isDark ? 'bg-slate-700' : 'bg-gray-100'} transition-colors`}
title="下载"
>
<Download className={`w-4 h-4 ${textSecondary}`} />
</button>
<button
className={`p-1.5 rounded-lg hover:${isDark ? 'bg-slate-700' : 'bg-gray-100'} transition-colors`}
title="呈报"
>
<Send className={`w-4 h-4 ${textSecondary}`} />
</button>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
</div>
</div>
{/* 自定义滚动条样式 */}
<style>{`
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: ${isDark ? 'rgba(148, 163, 184, 0.3)' : 'rgba(0, 0, 0, 0.15)'};
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: ${isDark ? 'rgba(148, 163, 184, 0.5)' : 'rgba(0, 0, 0, 0.25)'};
}
`}</style>
</div>
);
};
export default ResultManagement;