Files
deerflow-code/web-code/components/taskManagement/TaskList.tsx
T
2026-05-27 19:43:15 +08:00

603 lines
25 KiB
TypeScript

import React, { useState } from 'react';
import { Plus, Search, Eye, Play, Pause, Trash2, Target, Clock, User, ChevronRight, Filter, Bot, Workflow, Users, MoreHorizontal, Edit, Copy, FileText } from 'lucide-react';
import { useTheme } from '@/contexts/ThemeContext';
import { PageType } from '@/components/Sidebar';
interface TaskListProps {
onViewWorkspace: (id: string) => void;
onNavigate: (page: PageType) => void;
}
interface Task {
id: string;
name: string;
type: 'single' | 'scene' | 'consultation';
agent?: string;
scene?: string;
participants?: string[];
status: 'draft' | 'pending' | 'running' | 'paused' | 'blocked' | 'completed';
currentPhase: string;
summary?: string;
creator: string;
createTime: string;
updateTime: string;
column: 'unanalyzed' | 'analyzing' | 'completed';
}
const TaskList: React.FC<TaskListProps> = ({ onViewWorkspace, onNavigate }) => {
const { theme } = useTheme();
const isDark = theme === 'dark';
const [tasks] = useState<Task[]>([
// 未分析列
{
id: '8',
name: '端午节品牌传播方案',
type: 'scene',
scene: '宣传策略生成',
status: 'draft',
currentPhase: '待配置',
summary: '针对端午节品牌活动制定完整传播策略方案',
creator: '李策划',
createTime: '2026-03-19 16:00',
updateTime: '2026-03-19 16:00',
column: 'unanalyzed',
},
{
id: '9',
name: '新品上市用户调研',
type: 'single',
agent: 'AudienceAgent',
status: 'pending',
currentPhase: '待启动',
summary: '对新品目标用户进行画像分析',
creator: '王运营',
createTime: '2026-03-19 14:30',
updateTime: '2026-03-19 15:00',
column: 'unanalyzed',
},
// 分析中列
{
id: '1',
name: '五四青年节宣传活动策略',
type: 'scene',
scene: '宣传策略生成',
status: 'running',
currentPhase: '行为分析',
summary: '正在按场景模板执行目标、受众、行为、叙事等分析...',
creator: '张策略',
createTime: '2026-03-19 10:30',
updateTime: '2026-03-19 15:45',
column: 'analyzing',
},
{
id: '7',
name: '品牌升级叙事方向研讨',
type: 'consultation',
participants: ['GoalAgent', 'NarrativeAgent', 'RiskAgent'],
status: 'running',
currentPhase: '第2轮讨论中',
summary: '围绕宣传方向、叙事方式和受众切入点开展多视角会商...',
creator: '王策划',
createTime: '2026-03-19 15:00',
updateTime: '2026-03-19 16:30',
column: 'analyzing',
},
{
id: '3',
name: '年度品牌升级方案推演',
type: 'scene',
scene: '宣传方案推演',
status: 'paused',
currentPhase: '叙事分析',
summary: '任务已暂停,等待人工介入确认叙事方向',
creator: '王策划',
createTime: '2026-03-17 14:00',
updateTime: '2026-03-19 11:30',
column: 'analyzing',
},
// 已完成列
{
id: '2',
name: '新产品发布会舆情应对预案',
type: 'scene',
scene: '舆情应对策略',
status: 'completed',
currentPhase: '已完成',
summary: '已完成舆情应对策略方案生成',
creator: '李分析师',
createTime: '2026-03-18 09:00',
updateTime: '2026-03-18 16:20',
column: 'completed',
},
{
id: '4',
name: '五四青年节受众快速分析',
type: 'single',
agent: 'AudienceAgent',
status: 'completed',
currentPhase: '已完成',
summary: '已完成目标受众识别、关切提炼和阻力分析',
creator: '赵运营',
createTime: '2026-03-19 09:00',
updateTime: '2026-03-19 09:15',
column: 'completed',
},
{
id: '6',
name: '五四青年节宣传策略会商',
type: 'consultation',
participants: ['GoalAgent', 'AudienceAgent', 'NarrativeAgent', 'RiskAgent'],
status: 'completed',
currentPhase: '已完成',
summary: '已完成多智能体会商,形成结论与分歧报告',
creator: '赵运营',
createTime: '2026-03-19 14:30',
updateTime: '2026-03-19 16:00',
column: 'completed',
},
]);
const [activeTab, setActiveTab] = useState<'all' | 'unanalyzed' | 'analyzing' | 'completed'>('all');
const [searchQuery, setSearchQuery] = useState('');
const [modeFilter, setModeFilter] = useState<'all' | 'single' | 'scene' | 'consultation'>('all');
const getTypeConfig = (type: string) => {
if (isDark) {
switch (type) {
case 'single':
return { label: '单智能体', color: 'bg-cyan-500/20 text-cyan-400', icon: Bot };
case 'scene':
return { label: '场景执行', color: 'bg-purple-500/20 text-purple-400', icon: Workflow };
case 'consultation':
return { label: '多智能体会商', color: 'bg-orange-500/20 text-orange-400', icon: Users };
default:
return { label: '未知', color: 'bg-slate-500/20 text-slate-400', icon: Bot };
}
} else {
switch (type) {
case 'single':
return { label: '单智能体', color: 'bg-red-100 text-red-600', icon: Bot };
case 'scene':
return { label: '场景执行', color: 'bg-red-100 text-red-600', icon: Workflow };
case 'consultation':
return { label: '多智能体会商', color: 'bg-red-100 text-red-600', icon: Users };
default:
return { label: '未知', color: 'bg-gray-100 text-gray-600', icon: Bot };
}
}
};
const getStatusConfig = (status: string) => {
if (isDark) {
switch (status) {
case 'draft':
return { label: '草稿', color: 'bg-slate-500/20 text-slate-400' };
case 'pending':
return { label: '待启动', color: 'bg-slate-500/20 text-slate-400' };
case 'running':
return { label: '运行中', color: 'bg-blue-500/20 text-blue-400' };
case 'paused':
return { label: '已暂停', color: 'bg-yellow-500/20 text-yellow-400' };
case 'blocked':
return { label: '阻塞中', color: 'bg-red-500/20 text-red-400' };
case 'completed':
return { label: '已完成', color: 'bg-green-500/20 text-green-400' };
default:
return { label: '未知', color: 'bg-slate-500/20 text-slate-400' };
}
} else {
switch (status) {
case 'draft':
return { label: '草稿', color: 'bg-gray-100 text-gray-600' };
case 'pending':
return { label: '待启动', color: 'bg-gray-100 text-gray-600' };
case 'running':
return { label: '运行中', color: 'bg-red-100 text-red-600' };
case 'paused':
return { label: '已暂停', color: 'bg-yellow-100 text-yellow-600' };
case 'blocked':
return { label: '阻塞中', color: 'bg-red-100 text-red-600' };
case 'completed':
return { label: '已完成', color: 'bg-green-100 text-green-600' };
default:
return { label: '未知', color: 'bg-gray-100 text-gray-600' };
}
}
};
const filteredTasks = tasks.filter(task => {
const matchesTab = activeTab === 'all' || task.column === activeTab;
const matchesSearch = task.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
task.id.toLowerCase().includes(searchQuery.toLowerCase());
const matchesMode = modeFilter === 'all' || task.type === modeFilter;
return matchesTab && matchesSearch && matchesMode;
});
const unanalyzedTasks = filteredTasks.filter(t => t.column === 'unanalyzed');
const analyzingTasks = filteredTasks.filter(t => t.column === 'analyzing');
const completedTasks = filteredTasks.filter(t => t.column === 'completed');
const totalCount = tasks.length;
const unanalyzedCount = tasks.filter(t => t.column === 'unanalyzed').length;
const analyzingCount = tasks.filter(t => t.column === 'analyzing').length;
const completedCount = tasks.filter(t => t.column === 'completed').length;
const handleTaskClick = (task: Task) => {
if (task.column === 'completed') {
if (task.type === 'consultation') {
onNavigate('task-consult-result');
} else {
onNavigate('strategy-result');
}
} else if (task.column === 'analyzing') {
if (task.type === 'scene') {
onNavigate('task-scene');
} else if (task.type === 'consultation') {
onNavigate('task-consult');
} else {
onNavigate('task-single');
}
} else {
// 未分析 - 进入创建/配置
onNavigate('task-create');
}
};
const TaskCard = ({ task }: { task: Task }) => {
const typeConfig = getTypeConfig(task.type);
const statusConfig = getStatusConfig(task.status);
const TypeIcon = typeConfig.icon;
return (
<div
className={`rounded-xl p-4 hover:border-red-400 transition-all duration-200 group cursor-pointer theme-card border ${
isDark
? 'bg-slate-800/40 backdrop-blur-xl border-blue-800/30 hover:border-blue-500/50'
: 'bg-white border-red-100 hover:shadow-md shadow-red-100'
}`}
onClick={() => handleTaskClick(task)}
>
{/* 卡片头部:模式标签 + 状态标签 */}
<div className="flex items-center justify-between mb-3">
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-medium ${typeConfig.color}`}>
<TypeIcon className="w-3 h-3" />
{typeConfig.label}
</span>
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${statusConfig.color}`}>
{statusConfig.label}
</span>
</div>
{/* 任务名称 */}
<h3 className={`font-semibold text-sm mb-1 line-clamp-2 group-hover:text-red-500 transition-colors ${
isDark ? 'text-white' : 'text-gray-900'
}`}>
{task.name}
</h3>
{/* 任务编号 */}
<p className={`text-xs mb-2 ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>TASK-{task.id.padStart(4, '0')}</p>
{/* 任务摘要 */}
<p className={`text-xs mb-3 line-clamp-2 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>{task.summary}</p>
{/* 补充信息 */}
<div className="space-y-1.5 mb-3">
{task.type === 'single' && (
<div className="flex items-center gap-2 text-xs">
<Bot className={`w-3 h-3 ${isDark ? 'text-cyan-400' : 'text-red-500'}`} />
<span className={isDark ? 'text-slate-400' : 'text-gray-500'}>执行智能体:</span>
<span className={isDark ? 'text-cyan-400' : 'text-red-600'}>{task.agent}</span>
</div>
)}
{task.type === 'scene' && (
<div className="flex items-center gap-2 text-xs">
<Workflow className={`w-3 h-3 ${isDark ? 'text-purple-400' : 'text-red-500'}`} />
<span className={isDark ? 'text-slate-400' : 'text-gray-500'}>关联场景:</span>
<span className={isDark ? 'text-purple-400' : 'text-red-600'}>{task.scene}</span>
</div>
)}
{task.type === 'consultation' && (
<>
<div className="flex items-center gap-2 text-xs">
<Users className={`w-3 h-3 ${isDark ? 'text-orange-400' : 'text-red-500'}`} />
<span className={isDark ? 'text-slate-400' : 'text-gray-500'}>参与智能体:</span>
<span className={isDark ? 'text-orange-400' : 'text-red-600'}>{task.participants?.length || 0}个</span>
</div>
<div className="flex items-center gap-2 text-xs">
<Clock className={`w-3 h-3 ${isDark ? 'text-slate-500' : 'text-gray-400'}`} />
<span className={isDark ? 'text-slate-400' : 'text-gray-500'}>当前轮次:</span>
<span className={isDark ? 'text-slate-300' : 'text-gray-700'}>{task.currentPhase}</span>
</div>
</>
)}
{task.column !== 'unanalyzed' && task.column !== 'completed' && (
<div className="flex items-center gap-2 text-xs">
<Target className={`w-3 h-3 ${isDark ? 'text-slate-500' : 'text-gray-400'}`} />
<span className={isDark ? 'text-slate-400' : 'text-gray-500'}>当前阶段:</span>
<span className={isDark ? 'text-slate-300' : 'text-gray-700'}>{task.currentPhase}</span>
</div>
)}
</div>
{/* 时间信息 */}
<div className={`flex items-center justify-between text-xs pt-3 border-t ${
isDark
? 'text-slate-500 border-blue-800/30'
: 'text-gray-400 border-red-100'
}`}>
<div className="flex items-center gap-1.5">
<User className="w-3 h-3" />
<span>{task.creator}</span>
</div>
<span>{task.column === 'completed' ? `完成于 ${task.updateTime}` : `更新于 ${task.updateTime}`}</span>
</div>
{/* 操作按钮区 */}
<div className={`flex items-center gap-2 mt-3 pt-3 border-t ${
isDark ? 'border-blue-800/30' : 'border-red-100'
}`}>
{task.column === 'unanalyzed' && (
<>
<button className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-white rounded-lg text-xs font-medium transition-colors ${
isDark
? 'bg-blue-600 hover:bg-blue-500'
: 'bg-red-600 hover:bg-red-500 shadow-md'
}`}>
<Play className="w-3 h-3" />
开始分析
</button>
<button className={`p-1.5 rounded-lg transition-colors ${
isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
}`} title="编辑">
<Edit className={`w-4 h-4 ${isDark ? 'text-slate-400' : 'text-red-400'}`} />
</button>
</>
)}
{(task.column === 'analyzing') && (
<>
<button className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-white rounded-lg text-xs font-medium transition-colors ${
isDark
? 'bg-blue-600 hover:bg-blue-500'
: 'bg-red-600 hover:bg-red-500 shadow-md'
}`}>
<Play className="w-3 h-3" />
继续处理
</button>
<button className={`px-2 py-1.5 rounded-lg text-xs transition-colors ${
isDark
? 'hover:bg-slate-700/50 border border-blue-800/30 text-slate-300'
: 'hover:bg-red-50 border border-red-200 text-red-600'
}`}>
查看进展
</button>
</>
)}
{task.column === 'completed' && (
<>
<button className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-white rounded-lg text-xs font-medium transition-colors ${
isDark
? 'bg-green-600 hover:bg-green-500'
: 'bg-green-600 hover:bg-green-500 shadow-md'
}`}>
<Eye className="w-3 h-3" />
查看结果
</button>
<button className={`px-2 py-1.5 rounded-lg text-xs transition-colors ${
isDark
? 'hover:bg-slate-700/50 border border-blue-800/30 text-slate-300'
: 'hover:bg-red-50 border border-red-200 text-red-600'
}`}>
导出
</button>
</>
)}
<button className={`p-1.5 rounded-lg transition-colors ${
isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
}`} title="更多">
<MoreHorizontal className={`w-4 h-4 ${isDark ? 'text-slate-400' : 'text-red-400'}`} />
</button>
</div>
</div>
);
};
const ColumnHeader = ({ title, count, color }: { title: string; count: number; color: string }) => (
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${color}`} />
<h2 className={`font-semibold text-sm ${isDark ? 'text-white' : 'text-gray-900'}`}>{title}</h2>
<span className={`px-2 py-0.5 rounded-full text-xs ${
isDark ? 'bg-slate-700/50 text-slate-400' : 'bg-red-50 text-red-500'
}`}>{count}</span>
</div>
<button className={`p-1 rounded transition-colors ${
isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
}`}>
<ChevronRight className={`w-4 h-4 ${isDark ? 'text-slate-500' : 'text-red-400'}`} />
</button>
</div>
);
return (
<div className="h-full flex flex-col">
{/* 顶部工具栏 */}
<div className={`rounded-xl p-4 flex-shrink-0 mb-4 theme-card border ${
isDark
? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30'
: 'bg-white border-red-100 shadow-sm'
}`}>
<div className="flex items-center justify-between mb-4">
<h1 className={`text-xl font-bold ${isDark ? 'text-white' : 'text-gray-900'}`}>任务中心</h1>
<button
onClick={() => onNavigate('task-create')}
className={`flex items-center gap-2 px-4 py-2 text-white rounded-lg text-sm font-medium transition-colors ${
isDark
? 'bg-blue-600 hover:bg-blue-500'
: 'bg-red-600 hover:bg-red-500 shadow-md'
}`}
>
<Plus className="w-4 h-4" />
新建任务
</button>
</div>
{/* 搜索和筛选 */}
<div className="flex items-center gap-4">
<div className="relative flex-1 max-w-md">
<Search className={`absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 ${isDark ? 'text-slate-500' : 'text-red-400'}`} />
<input
type="text"
placeholder="搜索任务名称 / 任务编号 / 关键词"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className={`w-full pl-10 pr-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-red-500/20 transition-colors theme-transition ${
isDark
? 'bg-slate-900/50 border-blue-800/30 text-white placeholder-slate-500'
: 'bg-white border-red-200 text-gray-900 placeholder-red-300'
}`}
/>
</div>
<select
value={modeFilter}
onChange={(e) => setModeFilter(e.target.value as any)}
className={`px-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-red-500/20 cursor-pointer theme-transition ${
isDark
? 'bg-slate-900/50 border-blue-800/30 text-slate-300'
: 'bg-white border-red-200 text-gray-700'
}`}
>
<option value="all">全部模式</option>
<option value="single">单智能体</option>
<option value="scene">场景执行</option>
<option value="consultation">多智能体会商</option>
</select>
<button className={`flex items-center gap-2 px-4 py-2 border rounded-lg text-sm transition-colors theme-transition ${
isDark
? 'bg-slate-800/50 border-blue-800/30 text-slate-300 hover:bg-slate-700/50'
: 'bg-white border-red-200 text-gray-700 hover:bg-red-50'
}`}>
<Clock className="w-4 h-4" />
时间范围
</button>
</div>
</div>
{/* 统计概览栏 */}
<div className="grid grid-cols-4 gap-4 mb-4 flex-shrink-0">
<button
onClick={() => setActiveTab('all')}
className={`p-4 rounded-xl border transition-all duration-200 ${
activeTab === 'all'
? isDark
? 'bg-blue-600/20 border-blue-500/50'
: 'bg-red-50 border-red-300'
: isDark
? 'bg-slate-800/30 border-blue-800/30 hover:border-blue-500/30'
: 'bg-white border-red-100 hover:border-red-300'
}`}
>
<p className={`text-2xl font-bold ${isDark ? 'text-white' : 'text-gray-900'}`}>{totalCount}</p>
<p className={`text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>全部任务</p>
</button>
<button
onClick={() => setActiveTab('unanalyzed')}
className={`p-4 rounded-xl border transition-all duration-200 ${
activeTab === 'unanalyzed'
? isDark
? 'bg-yellow-500/20 border-yellow-500/50'
: 'bg-yellow-50 border-yellow-300'
: isDark
? 'bg-slate-800/30 border-blue-800/30 hover:border-blue-500/30'
: 'bg-white border-red-100 hover:border-red-300'
}`}
>
<p className={`text-2xl font-bold ${isDark ? 'text-white' : 'text-gray-900'}`}>{unanalyzedCount}</p>
<p className={`text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>未分析</p>
</button>
<button
onClick={() => setActiveTab('analyzing')}
className={`p-4 rounded-xl border transition-all duration-200 ${
activeTab === 'analyzing'
? isDark
? 'bg-blue-500/20 border-blue-500/50'
: 'bg-red-50 border-red-300'
: isDark
? 'bg-slate-800/30 border-blue-800/30 hover:border-blue-500/30'
: 'bg-white border-red-100 hover:border-red-300'
}`}
>
<p className={`text-2xl font-bold ${isDark ? 'text-white' : 'text-gray-900'}`}>{analyzingCount}</p>
<p className={`text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>分析中</p>
</button>
<button
onClick={() => setActiveTab('completed')}
className={`p-4 rounded-xl border transition-all duration-200 ${
activeTab === 'completed'
? isDark
? 'bg-green-500/20 border-green-500/50'
: 'bg-green-50 border-green-300'
: isDark
? 'bg-slate-800/30 border-blue-800/30 hover:border-blue-500/30'
: 'bg-white border-red-100 hover:border-red-300'
}`}
>
<p className={`text-2xl font-bold ${isDark ? 'text-white' : 'text-gray-900'}`}>{completedCount}</p>
<p className={`text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>已完成</p>
</button>
</div>
{/* 看板区 */}
<div className="flex-1 grid grid-cols-3 gap-4 min-h-0 overflow-hidden">
{/* 未分析列 */}
<div className="flex flex-col min-h-0">
<ColumnHeader title="未分析" count={unanalyzedTasks.length} color={isDark ? 'bg-yellow-400' : 'bg-yellow-400'} />
<div className="flex-1 overflow-y-auto space-y-3 pr-1 scrollbar-thin">
{unanalyzedTasks.length === 0 ? (
<div className={`flex flex-col items-center justify-center py-8 ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
<FileText className="w-8 h-8 mb-2 opacity-50" />
<p className="text-sm">暂无待分析任务</p>
</div>
) : (
unanalyzedTasks.map(task => <TaskCard key={task.id} task={task} />)
)}
</div>
</div>
{/* 分析中列 */}
<div className="flex flex-col min-h-0">
<ColumnHeader title="分析中" count={analyzingTasks.length} color={isDark ? 'bg-blue-400' : 'bg-red-400'} />
<div className="flex-1 overflow-y-auto space-y-3 pr-1 scrollbar-thin">
{analyzingTasks.length === 0 ? (
<div className={`flex flex-col items-center justify-center py-8 ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
<Clock className="w-8 h-8 mb-2 opacity-50" />
<p className="text-sm">暂无分析中任务</p>
</div>
) : (
analyzingTasks.map(task => <TaskCard key={task.id} task={task} />)
)}
</div>
</div>
{/* 已完成列 */}
<div className="flex flex-col min-h-0">
<ColumnHeader title="已完成" count={completedTasks.length} color={isDark ? 'bg-green-400' : 'bg-green-400'} />
<div className="flex-1 overflow-y-auto space-y-3 pr-1 scrollbar-thin">
{completedTasks.length === 0 ? (
<div className={`flex flex-col items-center justify-center py-8 ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
<Target className="w-8 h-8 mb-2 opacity-50" />
<p className="text-sm">暂无已完成任务</p>
</div>
) : (
completedTasks.map(task => <TaskCard key={task.id} task={task} />)
)}
</div>
</div>
</div>
</div>
);
};
export default TaskList;