refactor(strategy): 任务分析页移除演示数据并接入真实接口

舆情分析与规划任务分析看板统一改为后端任务列表拉取,去掉 mock/演示切换,并修正 running 列状态与日期筛选交互。
This commit is contained in:
fengxiangdebaozi committed 2026-06-06 20:23:58 +08:00
1 parent 2e7985639e
commit 9c9c912657
2 files changed
+293 -797

No files matched your search

@@ -15,175 +15,16 @@ interface TaskListPageProps {
subCategory: string; subCategory: string;
} }
interface StateItem {
startTime: string;
endTime: string;
task: TaskListItem | null;
}
type ColumnType = 'unanalyzed' | 'running' | 'completed'; type ColumnType = 'unanalyzed' | 'running' | 'completed';
const service = requestCrudService; const service = requestCrudService;
const zncmUrl = import.meta.env.VITE_API_BASE_URL || ''; const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
const ENABLE_SENTIMENT_DEMO_BY_DEFAULT = import.meta.env.DEV;
const buildMockTask = (
id: number,
taskName: string,
overview: string,
column: ColumnType,
taskType: number,
taskLevel: number,
taskDirection: string,
createTime: string,
updateTime: string,
): TaskListItem => ({
createBy: 0,
createTime,
deptId: null,
id,
isTop: 0,
issuingDept: '',
keywords: null,
overview,
planTaskTop: 0,
platform: null,
ptaskId: null,
recommendedBasis: null,
scene: JSON.stringify({
taskJson: JSON.stringify({
task_title: taskName,
task_goal: overview,
}),
}),
sourceType: 0,
step: null,
taskContent: overview,
taskDirection,
taskEndDate: '',
taskLevel,
taskName,
taskNo: `TASK-${id}`,
taskStartDate: '',
taskStatus: 0,
taskSuperiorFlag: 0,
taskTimeLimit: null,
taskType,
column,
updateBy: 0,
updateTime,
endCount: 0,
sumAll: 0,
progress: 0,
taskStatusData: null,
});
const MOCK_TASKS: Record<ColumnType, TaskListItem[]> = {
unanalyzed: [
buildMockTask(
1001,
'新能源价格舆情波动分析',
'聚焦近 7 日新能源整车与动力电池价格讨论热度,识别社媒与媒体端异常扩散信号。',
'unanalyzed',
1,
2,
'王晓楠',
'2026-06-05 09:10:00',
'2026-06-05 09:10:00',
),
buildMockTask(
1002,
'重点项目负面反馈聚类',
'从任务评论、审批意见和业务群消息中抽取高频情绪主题,辅助判断风险聚焦点。',
'unanalyzed',
3,
3,
'刘志诚',
'2026-06-05 08:40:00',
'2026-06-05 08:40:00',
),
buildMockTask(
1003,
'竞品发布会用户情绪追踪',
'围绕发布会直播、短视频评论与论坛帖子,建立情绪转折时间线并识别主要触发事件。',
'unanalyzed',
2,
1,
'陈思雨',
'2026-06-04 18:20:00',
'2026-06-04 18:20:00',
),
],
running: [
buildMockTask(
2001,
'新车上市口碑监测会商',
'正在联合多个智能体汇总平台评价、媒体测评与用户问答,生成阶段性情绪画像。',
'running',
4,
3,
'张雨桐',
'2026-06-05 07:50:00',
'2026-06-05 10:05:00',
),
buildMockTask(
2002,
'投诉热点归因拆解',
'拆分售后投诉中关于续航、交付和门店服务的负面占比,分析主要归因与蔓延路径。',
'running',
3,
2,
'何嘉成',
'2026-06-04 20:10:00',
'2026-06-05 09:48:00',
),
],
completed: [
buildMockTask(
3001,
'车展传播效果情绪复盘',
'已输出传播节点热度变化、核心评论主题以及 KOL 内容对情绪曲线的影响分析。',
'completed',
1,
1,
'孙怡宁',
'2026-06-03 14:20:00',
'2026-06-04 17:30:00',
),
buildMockTask(
3002,
'智能驾驶争议议题扫描',
'已完成争议议题聚类、情绪倾向分层和外部平台扩散链路分析,可直接查看结论。',
'completed',
2,
3,
'李承泽',
'2026-06-03 09:00:00',
'2026-06-04 11:45:00',
),
buildMockTask(
3003,
'品牌联名活动舆情总结',
'已完成用户态度分布统计与正负面声音对比,附带评论样本摘要和建议动作。',
'completed',
5,
2,
'周清越',
'2026-06-02 16:50:00',
'2026-06-03 19:12:00',
),
],
};
const paginateTasks = (tasks: TaskListItem[], pageIndex: number, pageSize: number) => {
const start = (pageIndex - 1) * pageSize;
return tasks.slice(start, start + pageSize);
};
const normalizeRangeValue = (range: DateRangeValue) => {
if (!Array.isArray(range) || range.length < 2) {
return { startTime: '', endTime: '' };
}
return {
startTime: dayjs(range[0]).format('YYYY-MM-DD HH:mm:ss'),
endTime: dayjs(range[1]).format('YYYY-MM-DD HH:mm:ss'),
};
};
const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategory }) => { const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategory }) => {
const { theme } = useTheme(); const { theme } = useTheme();
@@ -192,9 +33,8 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
const isDark = theme === 'dark'; const isDark = theme === 'dark';
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [useDemoData, setUseDemoData] = useState(ENABLE_SENTIMENT_DEMO_BY_DEFAULT);
const [selectedTask, setSelectedTask] = useState<TaskListItem | null>(null);
const [showAnalysisModal, setShowAnalysisModal] = useState(false); const [showAnalysisModal, setShowAnalysisModal] = useState(false);
const [selectedTask, setSelectedTask] = useState<TaskListItem | null>(null);
const [unanalyzedTotal, setUnanalyzedTotal] = useState(0); const [unanalyzedTotal, setUnanalyzedTotal] = useState(0);
const [runningTotal, setRunningTotal] = useState(0); const [runningTotal, setRunningTotal] = useState(0);
const [completedTotal, setCompletedTotal] = useState(0); const [completedTotal, setCompletedTotal] = useState(0);
@@ -205,7 +45,12 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
const [runningLoading, setRunningLoading] = useState(false); const [runningLoading, setRunningLoading] = useState(false);
const [completedLoading, setCompletedLoading] = useState(false); const [completedLoading, setCompletedLoading] = useState(false);
const [range, setRange] = useState<DateRangeValue>([]); const [range, setRange] = useState<DateRangeValue>([]);
const pageSizeRef = useRef(10);
const stateRef = useRef<StateItem>({
startTime: '',
endTime: '',
task: null,
});
const presets = useMemo<DateRangePickerProps['presets']>( const presets = useMemo<DateRangePickerProps['presets']>(
() => ({ () => ({
@@ -216,164 +61,132 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
[], [],
); );
const filteredMock = useMemo(() => { const taskStatusMap: Record<ColumnType, number[]> = {
const keyword = searchQuery.trim().toLowerCase(); unanalyzed: [0, 10],
const filterList = (list: TaskListItem[]) => { running: [20],
if (!keyword) return list; completed: [25, 30, 40, 60, 70, 80, 90, 100, 110],
return list.filter((item) => };
`${item.taskName} ${item.overview} ${item.taskDirection}`.toLowerCase().includes(keyword),
);
};
return { const setRangeChange = async (value: DateRangeValue) => {
unanalyzed: filterList(MOCK_TASKS.unanalyzed), setRange(value);
running: filterList(MOCK_TASKS.running), stateRef.current.startTime = '';
completed: filterList(MOCK_TASKS.completed), stateRef.current.endTime = '';
};
}, [searchQuery]);
const applyMockColumn = (column: ColumnType, pageIndex = 1) => { if (Array.isArray(value) && value.length > 1) {
const source = filteredMock[column]; stateRef.current.startTime = String(value[0] || '');
const pageList = paginateTasks(source, pageIndex, pageSizeRef.current); stateRef.current.endTime = String(value[1] || '');
if (column === 'unanalyzed') {
setUnanalyzedTotal(source.length);
setUnanalyzedList(pageList);
setUnanalyzedLoading(false);
return;
} }
if (column === 'running') { await taskDataChange();
setRunningTotal(source.length);
setRunningList(pageList);
setRunningLoading(false);
return;
}
setCompletedTotal(source.length);
setCompletedList(pageList);
setCompletedLoading(false);
}; };
const taskListSearch = async ( const taskListSearch = async (
column: ColumnType, taskType: ColumnType,
pageIndex = 1, pageNum = 1,
pageSize = pageSizeRef.current, pageSize = 10,
) => { ) => {
pageSizeRef.current = pageSize; if (taskType === 'unanalyzed') setUnanalyzedLoading(true);
if (taskType === 'running') setRunningLoading(true);
if (column === 'unanalyzed') setUnanalyzedLoading(true); if (taskType === 'completed') setCompletedLoading(true);
if (column === 'running') setRunningLoading(true);
if (column === 'completed') setCompletedLoading(true);
if (useDemoData) {
applyMockColumn(column, pageIndex);
return;
}
const taskStatusMap: Record<ColumnType, number[]> = {
unanalyzed: [0, 10],
running: [20],
completed: [25, 30, 40, 60, 70, 80, 90, 100, 110],
};
try { try {
const { startTime, endTime } = normalizeRangeValue(range);
const response = await service.post(`${zncmUrl}/three/cop-three-tasks`, { const response = await service.post(`${zncmUrl}/three/cop-three-tasks`, {
content: searchQuery.trim(), content: searchQuery,
pageNum: pageIndex, pageNum,
pageSize, pageSize,
taskStatus: taskStatusMap[column], taskStatus: taskStatusMap[taskType],
startTime, startTime: stateRef.current.startTime,
endTime, endTime: stateRef.current.endTime,
taskDirection: '', taskDirection: '',
}); });
const result = response?.data ?? response ?? {}; const result = response?.data ?? {};
const records = Array.isArray(result.records) ? result.records : []; const records = Array.isArray(result.records) ? result.records : [];
const total = Number(result.total ?? records.length); const normalized = records.map((item: TaskListItem) => ({
const mapped = records.map((item: TaskListItem) => ({
...item, ...item,
column, column: taskType,
taskLevel: item.taskLevel || 1, taskLevel: item.taskLevel || 1,
})); }));
const total = Number(result.total ?? normalized.length);
if (column === 'unanalyzed') { if (taskType === 'unanalyzed') {
setUnanalyzedList(mapped);
setUnanalyzedTotal(total); setUnanalyzedTotal(total);
} else if (column === 'running') { setUnanalyzedList(normalized);
setRunningList(mapped); } else if (taskType === 'running') {
setRunningTotal(total); setRunningTotal(total);
setRunningList(normalized);
} else { } else {
setCompletedList(mapped);
setCompletedTotal(total); setCompletedTotal(total);
setCompletedList(normalized);
} }
} catch (error) {
console.error('sentiment task list fetch failed', error);
setUseDemoData(true);
applyMockColumn(column, pageIndex);
} finally { } finally {
if (column === 'unanalyzed') setUnanalyzedLoading(false); if (taskType === 'unanalyzed') setUnanalyzedLoading(false);
if (column === 'running') setRunningLoading(false); if (taskType === 'running') setRunningLoading(false);
if (column === 'completed') setCompletedLoading(false); if (taskType === 'completed') setCompletedLoading(false);
} }
}; };
const refreshColumns = () => { const pageIndexChange = async (type: string, pageIndex: number) => {
taskListSearch('unanalyzed', 1); await taskListSearch(type as ColumnType, pageIndex);
taskListSearch('running', 1);
taskListSearch('completed', 1);
}; };
useEffect(() => { const taskDataChange = async (pageNum = 1, pageSize = 10) => {
refreshColumns(); await Promise.all([
}, [useDemoData]); taskListSearch('unanalyzed', pageNum, pageSize),
taskListSearch('running', pageNum, pageSize),
useEffect(() => { taskListSearch('completed', pageNum, pageSize),
const timer = window.setTimeout(() => { ]);
refreshColumns(); };
}, 200);
return () => window.clearTimeout(timer);
}, [searchQuery, range]);
const handleStartAnalysis = (task: TaskListItem) => { const handleStartAnalysis = (task: TaskListItem) => {
setSelectedTask(task); setSelectedTask(task);
stateRef.current.task = task;
setShowAnalysisModal(true); setShowAnalysisModal(true);
}; };
const handleOpenTaskDetail = () => { const handleOpenTaskDetail = () => {
if (!selectedTask) return; if (!stateRef.current.task) return;
window.open(`https://ch1.b.uat.4.cn/web/#/task/item-aicoh/step-info-search?id=${stateRef.current.task.id}`);
setStartConfig({
task_title: selectedTask.taskName,
task_goal: selectedTask.overview,
task_context: selectedTask.overview,
llm_preset_id: 'BAILING',
consistency_mode: 'soft',
phase_config: { diverge_rounds: 3 },
});
setShowAnalysisModal(false); setShowAnalysisModal(false);
navigate('/qa-center/mope-chat');
}; };
const handleOpenRoundtable = () => { const handleOpenConsultation = () => {
if (!selectedTask) return; const task = stateRef.current.task;
if (!task) return;
setStartConfig({ try {
task_title: selectedTask.taskName, const sceneItem = JSON.parse(task.scene || '{}');
task_goal: selectedTask.overview, if (sceneItem.taskJson) {
task_context: selectedTask.overview, const taskJson = JSON.parse(sceneItem.taskJson);
llm_preset_id: 'BAILING', setStartConfig({
consistency_mode: 'soft', ...taskJson,
phase_config: { diverge_rounds: 3 }, llm_preset_id: 'BAILING',
}); consistency_mode: 'soft',
phase_config: { diverge_rounds: 3 },
setShowAnalysisModal(false); task_context: task.overview,
navigate('/page/workspace/roundtable/planning'); });
navigate('/qa-center/mope-chat');
}
} finally {
setShowAnalysisModal(false);
}
}; };
const handleKeyDown = async (event: React.KeyboardEvent<HTMLInputElement>) => {
if ((event.metaKey || event.ctrlKey || event.shiftKey) && event.key === 'Enter') {
event.preventDefault();
} else if (event.key === 'Enter') {
event.preventDefault();
await taskDataChange();
} else if (event.key === '/') {
event.preventDefault();
}
};
useEffect(() => {
taskDataChange();
}, []);
const shellClass = isDark const shellClass = isDark
? 'strategy-data h-full overflow-y-auto bg-[linear-gradient(180deg,#081322_0%,#0d1d33_42%,#0a1628_100%)]' ? 'strategy-data h-full overflow-y-auto bg-[linear-gradient(180deg,#081322_0%,#0d1d33_42%,#0a1628_100%)]'
: 'strategy-data h-full overflow-y-auto bg-[linear-gradient(180deg,#FFFDFD_0%,#FFF7F7_46%,#FAFAFA_100%)]'; : 'strategy-data h-full overflow-y-auto bg-[linear-gradient(180deg,#FFFDFD_0%,#FFF7F7_46%,#FAFAFA_100%)]';
@@ -390,9 +203,8 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
? 'border-[rgba(67,89,124,0.46)] bg-[rgba(8,19,35,0.9)] text-white placeholder:text-slate-500' ? 'border-[rgba(67,89,124,0.46)] bg-[rgba(8,19,35,0.9)] text-white placeholder:text-slate-500'
: 'border-[#E8E8E8] bg-white text-[#222222] placeholder:text-[#A6A6A6]'; : 'border-[#E8E8E8] bg-white text-[#222222] placeholder:text-[#A6A6A6]';
const totalCount = unanalyzedTotal + runningTotal + completedTotal;
const metrics = [ const metrics = [
{ label: '全部任务', value: totalCount }, { label: '全部任务', value: unanalyzedTotal + runningTotal + completedTotal },
{ label: '未分析', value: unanalyzedTotal }, { label: '未分析', value: unanalyzedTotal },
{ label: '分析中', value: runningTotal }, { label: '分析中', value: runningTotal },
{ label: '已完成', value: completedTotal }, { label: '已完成', value: completedTotal },
@@ -405,38 +217,17 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
<div className="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between"> <div className="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center gap-2 text-xs"> <div className="flex items-center gap-2 text-xs">
<span <span className={`rounded-full px-3 py-1 ${isDark ? 'bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : 'bg-[#FFF1F0] text-[#CF1322]'}`}>
className={`rounded-full px-3 py-1 ${
isDark ? 'bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : 'bg-[#FFF1F0] text-[#CF1322]'
}`}
>
{category} {category}
</span> </span>
<ArrowRight className={`h-3.5 w-3.5 ${isDark ? 'text-slate-500' : 'text-[#A6A6A6]'}`} /> <ArrowRight className={`h-3.5 w-3.5 ${isDark ? 'text-slate-500' : 'text-[#A6A6A6]'}`} />
<span <span className={`rounded-full px-3 py-1 ${isDark ? 'bg-[rgba(34,197,94,0.14)] text-[#89E8B8]' : 'bg-[#FFF6E8] text-[#D46B08]'}`}>
className={`rounded-full px-3 py-1 ${
isDark ? 'bg-[rgba(34,197,94,0.14)] text-[#89E8B8]' : 'bg-[#FFF6E8] text-[#D46B08]'
}`}
>
{subCategory} {subCategory}
</span> </span>
</div> </div>
<div> <div>
<div className="flex items-center gap-3"> <h1 className={`text-[28px] font-semibold ${isDark ? 'text-white' : 'text-[#222222]'}`}>{title}</h1>
<h1 className={`text-[28px] font-semibold ${isDark ? 'text-white' : 'text-[#222222]'}`}>
{title}
</h1>
{useDemoData ? (
<span
className={`rounded-full px-3 py-1 text-xs ${
isDark ? 'bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : 'bg-[#FFF1F0] text-[#CF1322]'
}`}
>
演示数据
</span>
) : null}
</div>
<p className={`mt-2 text-sm ${isDark ? 'text-slate-300' : 'text-[#575757]'}`}> <p className={`mt-2 text-sm ${isDark ? 'text-slate-300' : 'text-[#575757]'}`}>
面向规划任务的情绪与倾向分析看板,支持快速检索、会商启动与结果回看。 面向规划任务的情绪与倾向分析看板,支持快速检索、会商启动与结果回看。
</p> </p>
@@ -445,13 +236,12 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
<div className="flex flex-col gap-3 xl:items-end"> <div className="flex flex-col gap-3 xl:items-end">
<div className="flex flex-wrap items-center gap-3"> <div className="flex flex-wrap items-center gap-3">
<label <label className={`flex h-11 min-w-[320px] items-center gap-3 rounded-xl border px-4 ${inputClass}`}>
className={`flex h-11 min-w-[320px] items-center gap-3 rounded-xl border px-4 ${inputClass}`}
>
<Search className={`h-4 w-4 ${isDark ? 'text-slate-400' : 'text-[#A6A6A6]'}`} /> <Search className={`h-4 w-4 ${isDark ? 'text-slate-400' : 'text-[#A6A6A6]'}`} />
<input <input
value={searchQuery} value={searchQuery}
onChange={(event) => setSearchQuery(event.target.value)} onChange={(event) => setSearchQuery(event.target.value)}
onKeyDown={handleKeyDown}
className="w-full bg-transparent text-sm outline-none" className="w-full bg-transparent text-sm outline-none"
placeholder="搜索任务名称" placeholder="搜索任务名称"
/> />
@@ -460,54 +250,20 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
className="sentiment-date-range" className="sentiment-date-range"
value={range} value={range}
presets={presets} presets={presets}
onChange={setRangeChange}
enableTimePicker enableTimePicker
clearable clearable
popupProps={{ overlayClassName: 'sentiment-date-range-popup' }} popupProps={{ overlayClassName: 'sentiment-date-range-popup' }}
onChange={(value) => setRange(value)}
/> />
</div> </div>
<div className="flex flex-wrap items-center gap-2"> <button
<button type="button"
type="button" className={`inline-flex items-center gap-2 rounded-xl px-4 py-2 text-sm ${isDark ? 'bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : 'bg-[#FFF1F0] text-[#CF1322]'}`}
onClick={() => setUseDemoData(true)} >
className={`rounded-xl px-4 py-2 text-sm transition ${ <Plus className="h-4 w-4" />
useDemoData 新建任务
? isDark </button>
? 'bg-[#1677FF] text-white'
: 'bg-[#F5222D] text-white'
: isDark
? 'bg-[rgba(8,19,35,0.9)] text-slate-300'
: 'bg-white text-[#575757] border border-[#E8E8E8]'
}`}
>
演示数据
</button>
<button
type="button"
onClick={() => setUseDemoData(false)}
className={`rounded-xl px-4 py-2 text-sm transition ${
!useDemoData
? isDark
? 'bg-[#0EA5E9] text-white'
: 'bg-[#CF1322] text-white'
: isDark
? 'bg-[rgba(8,19,35,0.9)] text-slate-300'
: 'bg-white text-[#575757] border border-[#E8E8E8]'
}`}
>
真实数据
</button>
<button
type="button"
className={`inline-flex items-center gap-2 rounded-xl px-4 py-2 text-sm ${
isDark ? 'bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : 'bg-[#FFF1F0] text-[#CF1322]'
}`}
>
<Plus className="h-4 w-4" />
新建任务
</button>
</div>
</div> </div>
</div> </div>
</section> </section>
@@ -518,27 +274,11 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className={`text-sm ${isDark ? 'text-slate-400' : 'text-[#8C8C8C]'}`}>{item.label}</p> <p className={`text-sm ${isDark ? 'text-slate-400' : 'text-[#8C8C8C]'}`}>{item.label}</p>
<p <p className={`mt-3 text-[36px] font-semibold ${isDark ? 'text-white' : index === 0 ? 'text-[#222222]' : 'text-[#F5222D]'}`}>
className={`mt-3 text-[36px] font-semibold ${
isDark
? index === 0
? 'text-white'
: 'text-slate-100'
: index === 0
? 'text-[#222222]'
: 'text-[#F5222D]'
}`}
>
{item.value} {item.value}
</p> </p>
</div> </div>
<div <div className={`flex h-12 w-12 items-center justify-center rounded-2xl ${isDark ? 'bg-[rgba(77,141,255,0.18)] text-[#9CCBFF]' : 'bg-[#FFF1F0] text-[#CF1322]'}`}>
className={`flex h-12 w-12 items-center justify-center rounded-2xl ${
isDark
? 'bg-[rgba(77,141,255,0.18)] text-[#9CCBFF]'
: 'bg-[#FFF1F0] text-[#CF1322]'
}`}
>
<Layers className="h-5 w-5" /> <Layers className="h-5 w-5" />
</div> </div>
</div> </div>
@@ -557,7 +297,7 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
tasks={unanalyzedList} tasks={unanalyzedList}
taskType="unanalyzed" taskType="unanalyzed"
taskDataType="sentiment" taskDataType="sentiment"
pageIndexSearch={taskListSearch} pageIndexSearch={pageIndexChange}
handleStartAnalysis={handleStartAnalysis} handleStartAnalysis={handleStartAnalysis}
/> />
</div> </div>
@@ -574,7 +314,7 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
tasks={runningList} tasks={runningList}
taskType="running" taskType="running"
taskDataType="sentiment" taskDataType="sentiment"
pageIndexSearch={taskListSearch} pageIndexSearch={pageIndexChange}
handleStartAnalysis={handleStartAnalysis} handleStartAnalysis={handleStartAnalysis}
/> />
</div> </div>
@@ -591,7 +331,7 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
tasks={completedList} tasks={completedList}
taskType="completed" taskType="completed"
taskDataType="sentiment" taskDataType="sentiment"
pageIndexSearch={taskListSearch} pageIndexSearch={pageIndexChange}
handleStartAnalysis={handleStartAnalysis} handleStartAnalysis={handleStartAnalysis}
/> />
</div> </div>
@@ -602,13 +342,7 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
{showAnalysisModal && selectedTask ? ( {showAnalysisModal && selectedTask ? (
<div className="fixed inset-0 z-[2000] flex items-center justify-center bg-black/45 px-4"> <div className="fixed inset-0 z-[2000] flex items-center justify-center bg-black/45 px-4">
<div <div className={`w-full max-w-[560px] rounded-[28px] border p-6 ${isDark ? 'border-[rgba(67,89,124,0.46)] bg-[rgba(10,23,42,0.98)] text-white' : 'border-[#E8E8E8] bg-white text-[#222222] shadow-[0_18px_36px_rgba(0,0,0,0.12)]'}`}>
className={`w-full max-w-[560px] rounded-[28px] border p-6 ${
isDark
? 'border-[rgba(67,89,124,0.46)] bg-[rgba(10,23,42,0.98)] text-white'
: 'border-[#E8E8E8] bg-white text-[#222222] shadow-[0_18px_36px_rgba(0,0,0,0.12)]'
}`}
>
<div className="flex items-start justify-between gap-4"> <div className="flex items-start justify-between gap-4">
<div> <div>
<h3 className="text-xl font-semibold">选择分析模式</h3> <h3 className="text-xl font-semibold">选择分析模式</h3>
@@ -629,16 +363,12 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
<button <button
type="button" type="button"
onClick={handleOpenTaskDetail} onClick={handleOpenTaskDetail}
className={`flex w-full items-center justify-between rounded-[22px] border px-5 py-4 text-left transition ${ className={`flex w-full items-center justify-between rounded-[22px] border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(67,89,124,0.46)] bg-[rgba(15,33,60,0.92)] hover:border-[rgba(110,168,255,0.46)]' : 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'}`}
isDark
? 'border-[rgba(67,89,124,0.46)] bg-[rgba(15,33,60,0.92)] hover:border-[rgba(110,168,255,0.46)]'
: 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'
}`}
> >
<div> <div>
<div className="font-medium">查看任务详情</div> <div className="font-medium">查看任务详情</div>
<div className={`mt-1 text-sm ${isDark ? 'text-slate-300' : 'text-[#575757]'}`}> <div className={`mt-1 text-sm ${isDark ? 'text-slate-300' : 'text-[#575757]'}`}>
进入单任务分析视图,查看步骤、结论和上下文信息。 进入 B1 系统查看任务详情。
</div> </div>
</div> </div>
<ArrowRight className="h-4 w-4 opacity-70" /> <ArrowRight className="h-4 w-4 opacity-70" />
@@ -646,19 +376,15 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
<button <button
type="button" type="button"
onClick={handleOpenRoundtable} onClick={handleOpenConsultation}
className={`flex w-full items-center justify-between rounded-[22px] border px-5 py-4 text-left transition ${ className={`flex w-full items-center justify-between rounded-[22px] border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(67,89,124,0.46)] bg-[rgba(15,33,60,0.92)] hover:border-[rgba(103,232,165,0.42)]' : 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'}`}
isDark
? 'border-[rgba(67,89,124,0.46)] bg-[rgba(15,33,60,0.92)] hover:border-[rgba(103,232,165,0.42)]'
: 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'
}`}
> >
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<Users className={`mt-0.5 h-5 w-5 ${isDark ? 'text-[#89E8B8]' : 'text-[#CF1322]'}`} /> <Users className={`mt-0.5 h-5 w-5 ${isDark ? 'text-[#89E8B8]' : 'text-[#CF1322]'}`} />
<div> <div>
<div className="font-medium">多智能体会商</div> <div className="font-medium">多智能体会商</div>
<div className={`mt-1 text-sm ${isDark ? 'text-slate-300' : 'text-[#575757]'}`}> <div className={`mt-1 text-sm ${isDark ? 'text-slate-300' : 'text-[#575757]'}`}>
启动会商模式,由多个智能体并行分析情绪走势与影响因素。 多个 AI 智能体从不同视角开展讨论并形成共识。
</div> </div>
</div> </div>
</div> </div>
@@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import { ArrowRight, Bot, Clock3, Layers, Search, Sparkles, Users, X } from 'lucide-react'; import { ArrowRight, Bot, Layers, Plus, Search, Users, X } from 'lucide-react';
import { DateRangePicker, DateRangeValue, Loading } from 'tdesign-react'; import { DateRangePicker, DateRangePickerProps, DateRangeValue, Loading } from 'tdesign-react';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useTheme } from '@/contexts/ThemeContext'; import { useTheme } from '@/contexts/ThemeContext';
@@ -15,177 +15,26 @@ interface TaskListPageProps {
subCategory: string; subCategory: string;
} }
type TaskType = 'single' | 'consultation' | 'scene' | 'multi-agent' | 'task-search'; interface StateItem {
type ColumnType = 'unanalyzed' | 'analyzing' | 'completed'; startTime: string;
endTime: string;
task: TaskListItem | null;
}
type ColumnType = 'unanalyzed' | 'running' | 'completed';
const service = requestCrudService; const service = requestCrudService;
const zncmUrl = import.meta.env.VITE_API_BASE_URL || ''; const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
const ENABLE_TASK_ANALYSIS_DEMO_BY_DEFAULT = import.meta.env.DEV;
const buildMockTask = (
id: string,
name: string,
overview: string,
column: ColumnType,
type: TaskType,
priority: 1 | 2 | 3,
progress: number,
createTime: string,
updateTime: string,
owner: string,
): TaskListItem => ({
id,
taskName: name,
overview,
column,
type,
priority,
progress,
createTime,
updateTime,
owner,
creator: owner,
executor: owner,
scene: JSON.stringify({
taskJson: JSON.stringify({
task_title: name,
task_goal: overview,
}),
}),
});
const MOCK_TASKS: Record<ColumnType, TaskListItem[]> = {
unanalyzed: [
buildMockTask(
'plan-fx-u-1',
'重点区域受众画像分析',
'识别华东与华南区域的核心受众特征、关注议题和任务接受门槛,为后续执行策略提供输入。',
'unanalyzed',
'single',
2,
0,
'2026-06-05 10:20:00',
'2026-06-05 10:20:00',
'赵嘉宁',
),
buildMockTask(
'plan-fx-u-2',
'项目目标拆解与阻力识别',
'围绕项目推进目标,提炼关键里程碑、潜在阻力和依赖条件,形成可执行分析框架。',
'unanalyzed',
'scene',
3,
0,
'2026-06-05 09:45:00',
'2026-06-05 09:45:00',
'程浩宇',
),
buildMockTask(
'plan-fx-u-3',
'传播路径假设验证',
'针对既定传播路径做初步假设验证,识别关键触点与信息衰减节点。',
'unanalyzed',
'task-search',
1,
0,
'2026-06-04 19:10:00',
'2026-06-04 19:10:00',
'徐可欣',
),
],
analyzing: [
buildMockTask(
'plan-fx-a-1',
'目标受众识别会商',
'正在联合多个智能体对目标受众、关键诉求和阻力点进行交叉分析,输出阶段性判断。',
'analyzing',
'multi-agent',
3,
62,
'2026-06-05 08:10:00',
'2026-06-05 10:18:00',
'林知远',
),
buildMockTask(
'plan-fx-a-2',
'关切提炼与策略匹配',
'拆分用户关切、媒体关切与执行侧约束,评估不同策略路径的匹配度。',
'analyzing',
'consultation',
2,
74,
'2026-06-04 17:50:00',
'2026-06-05 09:36:00',
'沈语禾',
),
],
completed: [
buildMockTask(
'plan-fx-c-1',
'目标受众识别报告',
'已完成核心受众划分、关键关切提炼和阻力分析,可直接查看结论与建议。',
'completed',
'single',
1,
100,
'2026-06-03 10:30:00',
'2026-06-04 16:40:00',
'郑书言',
),
buildMockTask(
'plan-fx-c-2',
'宣传策略初稿生成',
'已完成传播重点、内容方向与节奏建议,附带策略版本对比与风险提示。',
'completed',
'scene',
2,
100,
'2026-06-03 14:10:00',
'2026-06-04 18:20:00',
'余思成',
),
buildMockTask(
'plan-fx-c-3',
'多智能体会商总结',
'已形成多角色会商纪要,收敛主要分歧并给出后续推进建议。',
'completed',
'consultation',
3,
100,
'2026-06-02 16:00:00',
'2026-06-03 20:10:00',
'周奕辰',
),
],
};
const paginateTasks = (tasks: TaskListItem[], pageIndex: number, pageSize: number) => {
const start = (pageIndex - 1) * pageSize;
return tasks.slice(start, start + pageSize);
};
const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategory }) => { const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategory }) => {
const { theme } = useTheme(); const { theme } = useTheme();
const { setStartConfig } = useChatContext(); const { setStartConfig } = useChatContext();
const navigate = useNavigate(); const navigate = useNavigate();
const isDark = theme === 'dark'; const isDark = theme === 'dark';
const shellClass = isDark
? 'strategy-data h-full overflow-y-auto bg-[radial-gradient(circle_at_top_left,rgba(27,94,168,0.28),transparent_32%),linear-gradient(180deg,#081322_0%,#0d1d33_42%,#0a1628_100%)] text-slate-100'
: 'strategy-data h-full overflow-y-auto bg-[radial-gradient(circle_at_top_left,rgba(245,34,45,0.08),transparent_26%),linear-gradient(180deg,#fffdfd_0%,#fff7f7_42%,#fafafa_100%)] text-[#222222]';
const metricCardClass = isDark
? 'border border-[rgba(110,136,176,0.24)] bg-[linear-gradient(180deg,rgba(13,31,55,0.98),rgba(8,19,34,0.98))] shadow-[0_14px_42px_rgba(2,8,20,0.30)]'
: 'border border-[#E8E8E8] bg-white shadow-[0_12px_30px_rgba(245,34,45,0.06)]';
const inputClass = isDark
? 'border-[rgba(110,136,176,0.3)] bg-[rgba(8,23,42,0.92)] text-slate-100 placeholder:text-slate-500'
: 'border-[#E8E8E8] bg-white text-[#222222] placeholder:text-[#A6A6A6] shadow-[0_4px_14px_rgba(245,34,45,0.04)]';
const panelGlowClass = isDark
? 'before:pointer-events-none before:absolute before:inset-x-8 before:top-0 before:h-px before:bg-[linear-gradient(90deg,transparent,rgba(96,165,250,0.45),transparent)]'
: '';
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [showAnalysisModal, setShowAnalysisModal] = useState(false); const [showAnalysisModal, setShowAnalysisModal] = useState(false);
const [selectedTaskForAnalysis, setSelectedTaskForAnalysis] = useState<TaskListItem | null>(null); const [selectedTask, setSelectedTask] = useState<TaskListItem | null>(null);
const [useDemoData, setUseDemoData] = useState(ENABLE_TASK_ANALYSIS_DEMO_BY_DEFAULT);
const [unanalyzedTotal, setUnanalyzedTotal] = useState(0); const [unanalyzedTotal, setUnanalyzedTotal] = useState(0);
const [runningTotal, setRunningTotal] = useState(0); const [runningTotal, setRunningTotal] = useState(0);
const [completedTotal, setCompletedTotal] = useState(0); const [completedTotal, setCompletedTotal] = useState(0);
@@ -195,128 +44,164 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
const [unanalyzedLoading, setUnanalyzedLoading] = useState(false); const [unanalyzedLoading, setUnanalyzedLoading] = useState(false);
const [runningLoading, setRunningLoading] = useState(false); const [runningLoading, setRunningLoading] = useState(false);
const [completedLoading, setCompletedLoading] = useState(false); const [completedLoading, setCompletedLoading] = useState(false);
const [dateRange, setDateRange] = useState<DateRangeValue>([ const [dateRange, setDateRange] = useState<DateRangeValue>([]);
dayjs().startOf('month').format('YYYY-MM-DD'),
dayjs().endOf('day').format('YYYY-MM-DD'),
]);
const pageSizeRef = useRef(10); const stateRef = useRef<StateItem>({
startTime: '',
endTime: '',
task: null,
});
const filteredMock = useMemo(() => { const presets = useMemo<DateRangePickerProps['presets']>(
const keyword = searchQuery.trim().toLowerCase(); () => ({
const filterItems = (items: TaskListItem[]) => { 最近7天: [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()],
if (!keyword) return items; 最近3天: [dayjs().subtract(2, 'day').toDate(), dayjs().toDate()],
return items.filter((item) => { 今天: [dayjs().toDate(), dayjs().toDate()],
const text = `${item.taskName} ${item.overview} ${item.owner || ''}`.toLowerCase(); }),
return text.includes(keyword); [],
}); );
};
return { const taskStatusMap: Record<ColumnType, number[]> = {
unanalyzed: filterItems(MOCK_TASKS.unanalyzed), unanalyzed: [0, 10],
analyzing: filterItems(MOCK_TASKS.analyzing), running: [20],
completed: filterItems(MOCK_TASKS.completed), completed: [25, 30, 40, 60, 70, 80, 90, 100, 110],
}; };
}, [searchQuery]);
const applyMockColumn = (column: ColumnType, pageIndex = 1) => { const setRangeChange = async (value: DateRangeValue) => {
const source = filteredMock[column]; setDateRange(value);
const pageItems = paginateTasks(source, pageIndex, pageSizeRef.current); stateRef.current.startTime = '';
stateRef.current.endTime = '';
if (column === 'unanalyzed') { if (Array.isArray(value) && value.length > 1) {
setUnanalyzedTotal(source.length); stateRef.current.startTime = String(value[0] || '');
setUnanalyzedList(pageItems); stateRef.current.endTime = String(value[1] || '');
setUnanalyzedLoading(false);
} else if (column === 'analyzing') {
setRunningTotal(source.length);
setRunningList(pageItems);
setRunningLoading(false);
} else {
setCompletedTotal(source.length);
setCompletedList(pageItems);
setCompletedLoading(false);
} }
await taskDataChange();
}; };
const taskListSearch = async ( const taskListSearch = async (
taskStatus: ColumnType, taskType: ColumnType,
pageIndex = 1, pageNum = 1,
pageSize = pageSizeRef.current, pageSize = 10,
) => { ) => {
pageSizeRef.current = pageSize; if (taskType === 'unanalyzed') setUnanalyzedLoading(true);
if (taskType === 'running') setRunningLoading(true);
const setLoadingByType = (loading: boolean) => { if (taskType === 'completed') setCompletedLoading(true);
if (taskStatus === 'unanalyzed') setUnanalyzedLoading(loading);
if (taskStatus === 'analyzing') setRunningLoading(loading);
if (taskStatus === 'completed') setCompletedLoading(loading);
};
setLoadingByType(true);
if (useDemoData) {
applyMockColumn(taskStatus, pageIndex);
return;
}
try { try {
const [startTime, endTime] = Array.isArray(dateRange) ? dateRange : ['', '']; const response = await service.post(`${zncmUrl}/task/cop-tasks`, {
const payload = { content: searchQuery,
pageNum: pageIndex, pageNum,
pageSize, pageSize,
taskStatus, taskStatus: taskStatusMap[taskType],
taskCategoryName: category, startTime: stateRef.current.startTime,
taskTypeName: subCategory, endTime: stateRef.current.endTime,
searchKey: searchQuery.trim() || undefined, taskDirection: '',
startTime, });
endTime,
};
const response = await service.post(`${zncmUrl}/three/cop-three-tasks`, payload); const result = response?.data ?? {};
const rows = response?.rows || response?.data?.rows || []; const records = Array.isArray(result.records) ? result.records : [];
const total = response?.total || response?.data?.total || 0; const normalized = records.map((item: TaskListItem) => ({
const mappedList = rows.map((item: TaskListItem) => ({
...item, ...item,
column: taskStatus === 'analyzing' ? 'running' : taskStatus, column: taskType,
taskLevel: item.taskLevel || 1,
})); }));
const total = Number(result.total ?? normalized.length);
if (taskStatus === 'unanalyzed') { if (taskType === 'unanalyzed') {
setUnanalyzedList(mappedList);
setUnanalyzedTotal(total); setUnanalyzedTotal(total);
} else if (taskStatus === 'analyzing') { setUnanalyzedList(normalized);
setRunningList(mappedList); } else if (taskType === 'running') {
setRunningTotal(total); setRunningTotal(total);
setRunningList(normalized);
} else { } else {
setCompletedList(mappedList);
setCompletedTotal(total); setCompletedTotal(total);
setCompletedList(normalized);
} }
} catch (error) {
console.error('task analysis list fetch failed', error);
applyMockColumn(taskStatus, pageIndex);
setUseDemoData(true);
} finally { } finally {
setLoadingByType(false); if (taskType === 'unanalyzed') setUnanalyzedLoading(false);
if (taskType === 'running') setRunningLoading(false);
if (taskType === 'completed') setCompletedLoading(false);
} }
}; };
const refreshAllColumns = async () => { const pageIndexChange = async (type: string, pageIndex: number) => {
await taskListSearch(type as ColumnType, pageIndex);
};
const taskDataChange = async (pageNum = 1, pageSize = 10) => {
await Promise.all([ await Promise.all([
taskListSearch('unanalyzed', 1), taskListSearch('unanalyzed', pageNum, pageSize),
taskListSearch('analyzing', 1), taskListSearch('running', pageNum, pageSize),
taskListSearch('completed', 1), taskListSearch('completed', pageNum, pageSize),
]); ]);
}; };
useEffect(() => { const handleStartAnalysis = (task: TaskListItem) => {
refreshAllColumns(); setSelectedTask(task);
}, [useDemoData]); stateRef.current.task = task;
setShowAnalysisModal(true);
};
const handleOpenTaskDetail = () => {
if (!stateRef.current.task) return;
window.open(`https://ch1.b.uat.4.cn/web/#/task/item-aicoh/step-info-search?id=${stateRef.current.task.id}`);
setShowAnalysisModal(false);
};
const handleOpenConsultation = () => {
const task = stateRef.current.task;
if (!task) return;
try {
const sceneItem = JSON.parse(task.scene || '{}');
if (sceneItem.taskJson) {
const taskJson = JSON.parse(sceneItem.taskJson);
setStartConfig({
...taskJson,
llm_preset_id: 'BAILING',
consistency_mode: 'soft',
phase_config: { diverge_rounds: 3 },
task_context: task.overview,
});
navigate('/qa-center/mope-chat');
}
} finally {
setShowAnalysisModal(false);
}
};
const handleKeyDown = async (event: React.KeyboardEvent<HTMLInputElement>) => {
if ((event.metaKey || event.ctrlKey || event.shiftKey) && event.key === 'Enter') {
event.preventDefault();
} else if (event.key === 'Enter') {
event.preventDefault();
await taskDataChange();
} else if (event.key === '/') {
event.preventDefault();
}
};
useEffect(() => { useEffect(() => {
const timer = window.setTimeout(() => { taskDataChange();
refreshAllColumns(); }, []);
}, useDemoData ? 120 : 260);
return () => window.clearTimeout(timer); const shellClass = isDark
}, [searchQuery, dateRange, useDemoData]); ? 'strategy-data h-full overflow-y-auto bg-[radial-gradient(circle_at_top_left,rgba(27,94,168,0.28),transparent_32%),linear-gradient(180deg,#081322_0%,#0d1d33_42%,#0a1628_100%)] text-slate-100'
: 'strategy-data h-full overflow-y-auto bg-[radial-gradient(circle_at_top_left,rgba(245,34,45,0.08),transparent_26%),linear-gradient(180deg,#fffdfd_0%,#fff7f7_42%,#fafafa_100%)] text-[#222222]';
const metricCardClass = isDark
? 'border border-[rgba(110,136,176,0.24)] bg-[linear-gradient(180deg,rgba(13,31,55,0.98),rgba(8,19,34,0.98))] shadow-[0_14px_42px_rgba(2,8,20,0.30)]'
: 'border border-[#E8E8E8] bg-white shadow-[0_12px_30px_rgba(245,34,45,0.06)]';
const inputClass = isDark
? 'border-[rgba(110,136,176,0.3)] bg-[rgba(8,23,42,0.92)] text-slate-100 placeholder:text-slate-500'
: 'border-[#E8E8E8] bg-white text-[#222222] placeholder:text-[#A6A6A6] shadow-[0_4px_14px_rgba(245,34,45,0.04)]';
const panelGlowClass = isDark
? 'before:pointer-events-none before:absolute before:inset-x-8 before:top-0 before:h-px before:bg-[linear-gradient(90deg,transparent,rgba(96,165,250,0.45),transparent)]'
: '';
const metrics = [ const metrics = [
{ {
@@ -328,13 +213,13 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
{ {
label: '未分析', label: '未分析',
value: unanalyzedTotal, value: unanalyzedTotal,
icon: Clock3, icon: Bot,
accent: 'from-[#7C9BFF] to-[#5EEAD4]', accent: 'from-[#7C9BFF] to-[#5EEAD4]',
}, },
{ {
label: '分析中', label: '分析中',
value: runningTotal, value: runningTotal,
icon: Sparkles, icon: Layers,
accent: 'from-[#22C55E] to-[#67E8F9]', accent: 'from-[#22C55E] to-[#67E8F9]',
}, },
{ {
@@ -345,44 +230,9 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
}, },
]; ];
const handleStartAnalysis = (task: TaskListItem) => {
setSelectedTaskForAnalysis(task);
setShowAnalysisModal(true);
};
const handleViewTaskDetail = () => {
if (!selectedTaskForAnalysis) return;
setStartConfig({
task_title: selectedTaskForAnalysis.taskName,
task_goal: selectedTaskForAnalysis.overview,
task_context: selectedTaskForAnalysis.overview,
llm_preset_id: 'BAILING',
consistency_mode: 'soft',
phase_config: { diverge_rounds: 3 },
});
setShowAnalysisModal(false);
navigate('/qa-center/mope-chat');
};
const handleMultiAgentDiscussion = () => {
if (!selectedTaskForAnalysis) return;
setStartConfig({
task_title: selectedTaskForAnalysis.taskName,
task_goal: selectedTaskForAnalysis.overview,
task_context: selectedTaskForAnalysis.overview,
llm_preset_id: 'BAILING',
consistency_mode: 'soft',
phase_config: { diverge_rounds: 3 },
});
setShowAnalysisModal(false);
navigate('/page/workspace/roundtable/planning');
};
return ( return (
<div className={shellClass}> <div className={shellClass}>
<div <div className={`relative mx-auto flex min-h-full max-w-[1680px] flex-col gap-5 px-6 py-6 lg:px-8 ${panelGlowClass}`}>
className={`relative mx-auto flex min-h-full max-w-[1680px] flex-col gap-5 px-6 py-6 lg:px-8 ${panelGlowClass}`}
>
<div className="flex flex-col gap-4 xl:flex-row xl:items-end xl:justify-between"> <div className="flex flex-col gap-4 xl:flex-row xl:items-end xl:justify-between">
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@@ -390,26 +240,15 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
<Bot className="h-5 w-5 text-[#7CC9FF]" /> <Bot className="h-5 w-5 text-[#7CC9FF]" />
</div> </div>
<div> <div>
<div className="flex items-center gap-2"> <h1 className={`text-[26px] font-semibold tracking-[0.02em] ${isDark ? 'text-slate-50' : 'text-[#222222]'}`}>
<h1 className="text-[26px] font-semibold tracking-[0.02em] text-slate-50">{title}</h1> {title}
{useDemoData ? ( </h1>
<span className="rounded-full border border-[rgba(125,211,252,0.28)] bg-[rgba(56,189,248,0.14)] px-2.5 py-1 text-xs text-[#8EDCFF]">
演示数据
</span>
) : null}
</div>
<p className={`mt-1 text-sm ${isDark ? 'text-slate-300/80' : 'text-slate-500'}`}> <p className={`mt-1 text-sm ${isDark ? 'text-slate-300/80' : 'text-slate-500'}`}>
面向规划任务的任务分析看板,聚焦目标拆解、受众判断和策略阻力识别,支持快速检索、启动分析和查看会商结果。 面向规划任务的任务分析看板,聚焦目标拆解、受众判断和策略阻力识别,支持快速检索、启动分析和查看会商结果。
</p> </p>
</div> </div>
</div> </div>
<div <div className={`inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs ${isDark ? 'border-[rgba(110,136,176,0.28)] bg-[rgba(9,25,46,0.72)] text-slate-300' : 'border-slate-200 bg-white text-slate-600'}`}>
className={`inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs ${
isDark
? 'border-[rgba(110,136,176,0.28)] bg-[rgba(9,25,46,0.72)] text-slate-300'
: 'border-slate-200 bg-white text-slate-600'
}`}
>
<span>{category}</span> <span>{category}</span>
<ArrowRight className="h-3.5 w-3.5 opacity-70" /> <ArrowRight className="h-3.5 w-3.5 opacity-70" />
<span>{subCategory}</span> <span>{subCategory}</span>
@@ -418,13 +257,12 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
<div className="flex flex-col gap-3 xl:items-end"> <div className="flex flex-col gap-3 xl:items-end">
<div className="flex flex-wrap items-center gap-3"> <div className="flex flex-wrap items-center gap-3">
<label <label className={`flex h-11 min-w-[320px] items-center gap-3 rounded-2xl border px-4 ${inputClass}`}>
className={`flex h-11 min-w-[320px] items-center gap-3 rounded-2xl border px-4 ${inputClass}`}
>
<Search className="h-4 w-4 text-[#7CC9FF]" /> <Search className="h-4 w-4 text-[#7CC9FF]" />
<input <input
value={searchQuery} value={searchQuery}
onChange={(event) => setSearchQuery(event.target.value)} onChange={(event) => setSearchQuery(event.target.value)}
onKeyDown={handleKeyDown}
className="w-full bg-transparent text-sm outline-none" className="w-full bg-transparent text-sm outline-none"
placeholder="搜索任务名称、摘要或负责人" placeholder="搜索任务名称、摘要或负责人"
/> />
@@ -432,46 +270,20 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
<DateRangePicker <DateRangePicker
className="sentiment-date-range" className="sentiment-date-range"
value={dateRange} value={dateRange}
enableTimePicker={false} presets={presets}
clearable={false} enableTimePicker
clearable
popupProps={{ overlayClassName: 'sentiment-date-range-popup' }} popupProps={{ overlayClassName: 'sentiment-date-range-popup' }}
rangeInputProps={{ onChange={setRangeChange}
inputProps: [
{ inputClass: 'sentiment-date-range-input' },
{ inputClass: 'sentiment-date-range-input' },
],
}}
onChange={(value) => setDateRange(value)}
/> />
</div> </div>
<div className="flex flex-wrap items-center gap-2"> <button
<button type="button"
type="button" className={`inline-flex items-center gap-2 rounded-xl px-4 py-2 text-sm ${isDark ? 'bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : 'bg-[#FFF1F0] text-[#CF1322]'}`}
onClick={() => setUseDemoData(true)} >
className={`rounded-xl px-4 py-2 text-sm transition ${ <Plus className="h-4 w-4" />
useDemoData 新建任务
? 'bg-[#1677FF] text-white shadow-[0_8px_22px_rgba(22,119,255,0.28)]' </button>
: isDark
? 'bg-[rgba(10,30,54,0.9)] text-slate-300 hover:bg-[rgba(13,42,78,0.95)]'
: 'bg-white text-slate-700 hover:bg-slate-50'
}`}
>
演示数据
</button>
<button
type="button"
onClick={() => setUseDemoData(false)}
className={`rounded-xl px-4 py-2 text-sm transition ${
!useDemoData
? 'bg-[#0EA5E9] text-white shadow-[0_8px_22px_rgba(14,165,233,0.24)]'
: isDark
? 'bg-[rgba(10,30,54,0.9)] text-slate-300 hover:bg-[rgba(13,42,78,0.95)]'
: 'bg-white text-slate-700 hover:bg-slate-50'
}`}
>
真实数据
</button>
</div>
</div> </div>
</div> </div>
@@ -481,11 +293,11 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className={`text-sm ${isDark ? 'text-slate-300/78' : 'text-slate-500'}`}>{label}</p> <p className={`text-sm ${isDark ? 'text-slate-300/78' : 'text-slate-500'}`}>{label}</p>
<p className="mt-3 text-3xl font-semibold tracking-[0.03em] text-white">{value}</p> <p className={`mt-3 text-3xl font-semibold tracking-[0.03em] ${isDark ? 'text-white' : 'text-[#222222]'}`}>
{value}
</p>
</div> </div>
<div <div className={`flex h-12 w-12 items-center justify-center rounded-2xl bg-gradient-to-br ${accent} shadow-[inset_0_1px_0_rgba(255,255,255,0.18)]`}>
className={`flex h-12 w-12 items-center justify-center rounded-2xl bg-gradient-to-br ${accent} shadow-[inset_0_1px_0_rgba(255,255,255,0.18)]`}
>
<Icon className="h-5 w-5 text-white" /> <Icon className="h-5 w-5 text-white" />
</div> </div>
</div> </div>
@@ -493,26 +305,6 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
))} ))}
</div> </div>
<div
className={`rounded-[24px] border px-5 py-4 ${
isDark
? 'border-[rgba(110,136,176,0.18)] bg-[linear-gradient(90deg,rgba(13,33,58,0.88),rgba(10,25,45,0.72))] text-slate-200'
: 'border-slate-200 bg-white text-slate-700'
}`}
>
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<div className="text-sm font-medium">分析工作台</div>
<div className={`mt-1 text-xs ${isDark ? 'text-slate-400' : 'text-slate-500'}`}>
当前为演示任务数据,便于预览深色模式下的多列分析工作流。
</div>
</div>
<div className={`rounded-full px-3 py-1 text-xs ${isDark ? 'bg-[rgba(22,119,255,0.14)] text-[#8EDCFF]' : 'bg-sky-50 text-sky-700'}`}>
目标拆解 / 受众判断 / 会商收敛
</div>
</div>
</div>
<div className="grid auto-rows-auto grid-cols-1 items-stretch gap-4 xl:grid-cols-3"> <div className="grid auto-rows-auto grid-cols-1 items-stretch gap-4 xl:grid-cols-3">
<div className="h-full self-stretch"> <div className="h-full self-stretch">
<Loading className="block h-full" loading={unanalyzedLoading} showOverlay={false}> <Loading className="block h-full" loading={unanalyzedLoading} showOverlay={false}>
@@ -523,9 +315,7 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
color="bg-[#7C9BFF]" color="bg-[#7C9BFF]"
tasks={unanalyzedList} tasks={unanalyzedList}
taskType="unanalyzed" taskType="unanalyzed"
pageIndexSearch={(taskType, pageIndex) => pageIndexSearch={pageIndexChange}
taskListSearch(taskType as ColumnType, pageIndex)
}
handleStartAnalysis={handleStartAnalysis} handleStartAnalysis={handleStartAnalysis}
/> />
</div> </div>
@@ -540,10 +330,8 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
count={runningTotal} count={runningTotal}
color="bg-[#22C55E]" color="bg-[#22C55E]"
tasks={runningList} tasks={runningList}
taskType="analyzing" taskType="running"
pageIndexSearch={(taskType, pageIndex) => pageIndexSearch={pageIndexChange}
taskListSearch(taskType as ColumnType, pageIndex)
}
handleStartAnalysis={handleStartAnalysis} handleStartAnalysis={handleStartAnalysis}
/> />
</div> </div>
@@ -559,9 +347,7 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
color="bg-[#38BDF8]" color="bg-[#38BDF8]"
tasks={completedList} tasks={completedList}
taskType="completed" taskType="completed"
pageIndexSearch={(taskType, pageIndex) => pageIndexSearch={pageIndexChange}
taskListSearch(taskType as ColumnType, pageIndex)
}
handleStartAnalysis={handleStartAnalysis} handleStartAnalysis={handleStartAnalysis}
/> />
</div> </div>
@@ -570,28 +356,20 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
</div> </div>
</div> </div>
{showAnalysisModal && selectedTaskForAnalysis ? ( {showAnalysisModal && selectedTask ? (
<div className="fixed inset-0 z-[2000] flex items-center justify-center bg-[rgba(3,10,22,0.62)] px-4 backdrop-blur-sm"> <div className="fixed inset-0 z-[2000] flex items-center justify-center bg-[rgba(3,10,22,0.62)] px-4 backdrop-blur-sm">
<div <div className={`w-full max-w-[560px] rounded-[28px] border p-6 ${isDark ? 'border-[rgba(110,136,176,0.26)] bg-[linear-gradient(180deg,rgba(10,29,54,0.98),rgba(8,20,37,0.98))] text-slate-100 shadow-[0_28px_80px_rgba(2,8,20,0.42)]' : 'border-slate-200 bg-white text-slate-900 shadow-xl'}`}>
className={`w-full max-w-[560px] rounded-[28px] border p-6 ${
isDark
? 'border-[rgba(110,136,176,0.26)] bg-[linear-gradient(180deg,rgba(10,29,54,0.98),rgba(8,20,37,0.98))] text-slate-100 shadow-[0_28px_80px_rgba(2,8,20,0.42)]'
: 'border-slate-200 bg-white text-slate-900 shadow-xl'
}`}
>
<div className="flex items-start justify-between gap-4"> <div className="flex items-start justify-between gap-4">
<div> <div>
<h3 className="text-xl font-semibold">选择分析模式</h3> <h3 className="text-xl font-semibold">选择分析模式</h3>
<p className={`mt-2 text-sm ${isDark ? 'text-slate-300/78' : 'text-slate-500'}`}> <p className={`mt-2 text-sm ${isDark ? 'text-slate-300/78' : 'text-slate-500'}`}>
当前任务:{selectedTaskForAnalysis.taskName} 当前任务:{selectedTask.taskName}
</p> </p>
</div> </div>
<button <button
type="button" type="button"
onClick={() => setShowAnalysisModal(false)} onClick={() => setShowAnalysisModal(false)}
className={`rounded-xl p-2 transition ${ className={`rounded-xl p-2 transition ${isDark ? 'hover:bg-[rgba(15,41,74,0.88)]' : 'hover:bg-slate-100'}`}
isDark ? 'hover:bg-[rgba(15,41,74,0.88)]' : 'hover:bg-slate-100'
}`}
> >
<X className="h-4 w-4" /> <X className="h-4 w-4" />
</button> </button>
@@ -600,12 +378,8 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
<div className="mt-6 grid gap-3"> <div className="mt-6 grid gap-3">
<button <button
type="button" type="button"
onClick={handleViewTaskDetail} onClick={handleOpenTaskDetail}
className={`group flex items-center justify-between rounded-2xl border px-5 py-4 text-left transition ${ className={`group flex items-center justify-between rounded-2xl border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(110,136,176,0.24)] bg-[rgba(10,30,54,0.9)] hover:border-[rgba(96,165,250,0.48)] hover:bg-[rgba(12,38,69,0.96)]' : 'border-slate-200 bg-slate-50 hover:border-sky-200 hover:bg-sky-50'}`}
isDark
? 'border-[rgba(110,136,176,0.24)] bg-[rgba(10,30,54,0.9)] hover:border-[rgba(96,165,250,0.48)] hover:bg-[rgba(12,38,69,0.96)]'
: 'border-slate-200 bg-slate-50 hover:border-sky-200 hover:bg-sky-50'
}`}
> >
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-[linear-gradient(180deg,#1677FF,#0EA5E9)]"> <div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-[linear-gradient(180deg,#1677FF,#0EA5E9)]">
@@ -614,7 +388,7 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
<div> <div>
<div className="font-medium">查看任务详情</div> <div className="font-medium">查看任务详情</div>
<div className={`mt-1 text-sm ${isDark ? 'text-slate-300/72' : 'text-slate-500'}`}> <div className={`mt-1 text-sm ${isDark ? 'text-slate-300/72' : 'text-slate-500'}`}>
进入单任务分析视图,查看步骤、结论和上下文信息。 进入 B1 系统查看任务详情。
</div> </div>
</div> </div>
</div> </div>
@@ -623,12 +397,8 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
<button <button
type="button" type="button"
onClick={handleMultiAgentDiscussion} onClick={handleOpenConsultation}
className={`group flex items-center justify-between rounded-2xl border px-5 py-4 text-left transition ${ className={`group flex items-center justify-between rounded-2xl border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(110,136,176,0.24)] bg-[rgba(10,30,54,0.9)] hover:border-[rgba(45,212,191,0.48)] hover:bg-[rgba(10,42,58,0.96)]' : 'border-slate-200 bg-slate-50 hover:border-emerald-200 hover:bg-emerald-50'}`}
isDark
? 'border-[rgba(110,136,176,0.24)] bg-[rgba(10,30,54,0.9)] hover:border-[rgba(45,212,191,0.48)] hover:bg-[rgba(10,42,58,0.96)]'
: 'border-slate-200 bg-slate-50 hover:border-emerald-200 hover:bg-emerald-50'
}`}
> >
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-[linear-gradient(180deg,#14B8A6,#22C55E)]"> <div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-[linear-gradient(180deg,#14B8A6,#22C55E)]">
@@ -637,7 +407,7 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
<div> <div>
<div className="font-medium">多智能体会商</div> <div className="font-medium">多智能体会商</div>
<div className={`mt-1 text-sm ${isDark ? 'text-slate-300/72' : 'text-slate-500'}`}> <div className={`mt-1 text-sm ${isDark ? 'text-slate-300/72' : 'text-slate-500'}`}>
启动会商模式,由多个智能体并行分析任务目标、受众和阻力点。 多个 AI 智能体从不同视角开展讨论并形成共识。
</div> </div>
</div> </div>
</div> </div>