414 lines
16 KiB
TypeScript
414 lines
16 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { useSearchParams } from 'react-router-dom';
|
|
import {
|
|
FileText,
|
|
Search,
|
|
Download,
|
|
Edit3,
|
|
Clock,
|
|
CheckCircle,
|
|
Target,
|
|
Brain,
|
|
TrendingUp,
|
|
Compass,
|
|
Database,
|
|
MoreHorizontal,
|
|
Send,
|
|
} 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 }) => {
|
|
const { theme } = useTheme();
|
|
const [searchParams] = useSearchParams();
|
|
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 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 = initialCategory;
|
|
const selectedSubCategory = searchParams.get('subCategory');
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [statusFilter, setStatusFilter] = useState<'all' | 'completed' | 'pending' | 'draft'>('all');
|
|
|
|
// 过滤任务
|
|
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">
|
|
<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;
|