diff --git a/frontend-web/src/pages/StrategyRoutes.tsx b/frontend-web/src/pages/StrategyRoutes.tsx index ce98f16b..abb0b985 100644 --- a/frontend-web/src/pages/StrategyRoutes.tsx +++ b/frontend-web/src/pages/StrategyRoutes.tsx @@ -104,9 +104,9 @@ export default function StrategyRoutes() { path="plan-task/plan-task-fx" element={ } /> diff --git a/frontend-web/src/strategy-components/components/task-components/KanbanColumn.tsx b/frontend-web/src/strategy-components/components/task-components/KanbanColumn.tsx index 74522869..237c30d3 100644 --- a/frontend-web/src/strategy-components/components/task-components/KanbanColumn.tsx +++ b/frontend-web/src/strategy-components/components/task-components/KanbanColumn.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { ChevronRight, FileText } from 'lucide-react'; +import { FileText } from 'lucide-react'; import { Pagination } from 'tdesign-react'; import { useTheme } from '@/contexts/ThemeContext'; import { TaskListItem } from './TaskManagement'; @@ -11,8 +11,8 @@ interface KanbanColumnProps { color: string; tasks: TaskListItem[]; taskType: string; - TaskDataType?: string; - pageIndexSearch: (taskType: string, pageIndex: number) => void; + taskDataType?: string; + pageIndexSearch: (taskType: string, pageIndex: number) => void | Promise; handleStartAnalysis: (task: TaskListItem) => void; } @@ -22,7 +22,7 @@ export const KanbanColumn: React.FC = ({ color, tasks, taskType, - TaskDataType = 'task', + taskDataType = 'task', pageIndexSearch, handleStartAnalysis, }) => { @@ -30,94 +30,79 @@ export const KanbanColumn: React.FC = ({ const isDark = theme === 'dark'; const [pageNum, setPageNum] = useState(1); - const pageIndexChange = async (value: number) => { + const handlePageChange = async (value: number) => { setPageNum(value); await pageIndexSearch(taskType, value); }; + const columnClass = isDark + ? 'border border-[rgba(67,89,124,0.46)] bg-[linear-gradient(180deg,rgba(18,36,67,0.96),rgba(12,28,54,0.96))] shadow-[0_18px_40px_rgba(3,10,22,0.18)]' + : 'border border-[#E8E8E8] bg-[linear-gradient(180deg,#FFFFFF_0%,#FFF9F9_100%)] shadow-[0_14px_28px_rgba(245,34,45,0.06)]'; + return ( -
-
-
-
-
-
-

{title}

- - {count} - -
-

- 按时间倒序展示,支持分页切换。 -

-
-
- -
-
- -
- pageIndexChange(Number(page))} - /> -
-
- -
- {tasks.length === 0 ? ( -
-
+
+
+
+ +

- -

-
暂无任务
-
- 可以尝试调整筛选条件或切换数据模式。 -
+ {title} + + + {count} + +
+
+ + handlePageChange(Number(page))} + /> +
+ +

+ 按时间倒序展示,支持分页切换。 +

+ +
+ {tasks.length === 0 ? ( +
+ +

暂无任务

) : ( tasks.map((task) => ( )) )}
-
+ ); }; diff --git a/frontend-web/src/strategy-components/components/task-components/SentimentAnalyzes.tsx b/frontend-web/src/strategy-components/components/task-components/SentimentAnalyzes.tsx index b3f3fb56..5608ff14 100644 --- a/frontend-web/src/strategy-components/components/task-components/SentimentAnalyzes.tsx +++ b/frontend-web/src/strategy-components/components/task-components/SentimentAnalyzes.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { ArrowRight, Bot, Clock3, Layers, Search, Sparkles, Users, X } from 'lucide-react'; -import { DateRangePicker, DateRangeValue, Loading } from 'tdesign-react'; +import { ArrowRight, Layers, Plus, Search, Users, X } from 'lucide-react'; import dayjs from 'dayjs'; +import { DateRangePicker, DateRangePickerProps, DateRangeValue, Loading } from 'tdesign-react'; import { useNavigate } from 'react-router-dom'; import { useTheme } from '@/contexts/ThemeContext'; import { useChatContext } from '@/contexts/ChatContext'; @@ -15,158 +15,156 @@ interface TaskListPageProps { subCategory: string; } -type TaskType = 'single' | 'consultation' | 'scene' | 'multi-agent' | 'task-search'; -type ColumnType = 'unanalyzed' | 'analyzing' | 'completed'; +type ColumnType = 'unanalyzed' | 'running' | 'completed'; const service = requestCrudService; const zncmUrl = import.meta.env.VITE_API_BASE_URL || ''; const ENABLE_SENTIMENT_DEMO_BY_DEFAULT = import.meta.env.DEV; const buildMockTask = ( - id: string, - name: string, + id: number, + taskName: string, overview: string, column: ColumnType, - type: TaskType, - priority: 1 | 2 | 3, - progress: number, + taskType: number, + taskLevel: number, + taskDirection: string, createTime: string, updateTime: string, - owner: string, ): TaskListItem => ({ - id, - taskName: name, - overview, - column, - type, - priority, - progress, + createBy: 0, createTime, - updateTime, - owner, - creator: owner, - executor: owner, + 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: name, + 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 = { unanalyzed: [ buildMockTask( - 'demo-u-1', + 1001, '新能源价格舆情波动分析', '聚焦近 7 日新能源整车与动力电池价格讨论热度,识别社媒与媒体端异常扩散信号。', 'unanalyzed', - 'single', + 1, 2, - 0, - '2026-06-05 09:10:00', - '2026-06-05 09:10:00', '王晓楠', + '2026-06-05 09:10:00', + '2026-06-05 09:10:00', ), buildMockTask( - 'demo-u-2', + 1002, '重点项目负面反馈聚类', '从任务评论、审批意见和业务群消息中抽取高频情绪主题,辅助判断风险聚焦点。', 'unanalyzed', - 'consultation', 3, - 0, - '2026-06-05 08:40:00', - '2026-06-05 08:40:00', + 3, '刘志诚', + '2026-06-05 08:40:00', + '2026-06-05 08:40:00', ), buildMockTask( - 'demo-u-3', + 1003, '竞品发布会用户情绪追踪', '围绕发布会直播、短视频评论与论坛帖子,建立情绪转折时间线并识别主要触发事件。', 'unanalyzed', - 'scene', - 1, - 0, - '2026-06-04 18:20:00', - '2026-06-04 18:20:00', - '陈思雨', - ), - buildMockTask( - 'demo-u-4', - '区域渠道满意度扫描', - '基于周报、工单和售后回访文本,发现华东区域渠道服务满意度下行趋势。', - 'unanalyzed', - 'task-search', 2, - 0, - '2026-06-04 15:35:00', - '2026-06-04 15:35:00', - '赵明宇', + 1, + '陈思雨', + '2026-06-04 18:20:00', + '2026-06-04 18:20:00', ), ], - analyzing: [ + running: [ buildMockTask( - 'demo-a-1', + 2001, '新车上市口碑监测会商', '正在联合多个智能体汇总平台评价、媒体测评与用户问答,生成阶段性情绪画像。', - 'analyzing', - 'multi-agent', + 'running', + 4, 3, - 56, + '张雨桐', '2026-06-05 07:50:00', '2026-06-05 10:05:00', - '张雨桐', ), buildMockTask( - 'demo-a-2', + 2002, '投诉热点归因拆解', '拆分售后投诉中关于续航、交付和门店服务的负面占比,分析主要归因与蔓延路径。', - 'analyzing', - 'consultation', + 'running', + 3, 2, - 78, + '何嘉成', '2026-06-04 20:10:00', '2026-06-05 09:48:00', - '何嘉成', ), ], completed: [ buildMockTask( - 'demo-c-1', + 3001, '车展传播效果情绪复盘', '已输出传播节点热度变化、核心评论主题以及 KOL 内容对情绪曲线的影响分析。', 'completed', - 'single', 1, - 100, + 1, + '孙怡宁', '2026-06-03 14:20:00', '2026-06-04 17:30:00', - '孙怡宁', ), buildMockTask( - 'demo-c-2', + 3002, '智能驾驶争议议题扫描', '已完成争议议题聚类、情绪倾向分层和外部平台扩散链路分析,可直接查看结论。', 'completed', - 'scene', + 2, 3, - 100, + '李承泽', '2026-06-03 09:00:00', '2026-06-04 11:45:00', - '李承泽', ), buildMockTask( - 'demo-c-3', + 3003, '品牌联名活动舆情总结', '已完成用户态度分布统计与正负面声音对比,附带评论样本摘要和建议动作。', 'completed', - 'task-search', + 5, 2, - 100, + '周清越', '2026-06-02 16:50:00', '2026-06-03 19:12:00', - '周清越', ), ], }; @@ -176,28 +174,27 @@ const paginateTasks = (tasks: TaskListItem[], pageIndex: number, pageSize: numbe 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 = ({ title, category, subCategory }) => { const { theme } = useTheme(); const { setStartConfig } = useChatContext(); const navigate = useNavigate(); const isDark = theme === 'dark'; - const shellClass = isDark - ? 'strategy-data min-h-full 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 min-h-full bg-[#f8fafc] text-slate-900'; - const metricCardClass = isDark - ? 'border border-[rgba(110,136,176,0.24)] bg-[linear-gradient(180deg,rgba(12,32,58,0.96),rgba(9,22,39,0.96))] shadow-[0_14px_42px_rgba(2,8,20,0.30)]' - : 'border border-slate-200 bg-white shadow-sm'; - 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-slate-200 bg-white text-slate-900 placeholder:text-slate-400'; - 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 [showAnalysisModal, setShowAnalysisModal] = useState(false); - const [selectedTaskForAnalysis, setSelectedTaskForAnalysis] = useState(null); const [useDemoData, setUseDemoData] = useState(ENABLE_SENTIMENT_DEMO_BY_DEFAULT); + const [selectedTask, setSelectedTask] = useState(null); + const [showAnalysisModal, setShowAnalysisModal] = useState(false); const [unanalyzedTotal, setUnanalyzedTotal] = useState(0); const [runningTotal, setRunningTotal] = useState(0); const [completedTotal, setCompletedTotal] = useState(0); @@ -207,378 +204,465 @@ const SentimentAnalyzes: React.FC = ({ title, category, subCa const [unanalyzedLoading, setUnanalyzedLoading] = useState(false); const [runningLoading, setRunningLoading] = useState(false); const [completedLoading, setCompletedLoading] = useState(false); - const [dateRange, setDateRange] = useState([ - dayjs().startOf('month').format('YYYY-MM-DD'), - dayjs().endOf('day').format('YYYY-MM-DD'), - ]); - + const [range, setRange] = useState([]); const pageSizeRef = useRef(10); + const presets = useMemo( + () => ({ + 最近7天: [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()], + 最近3天: [dayjs().subtract(2, 'day').toDate(), dayjs().toDate()], + 今天: [dayjs().toDate(), dayjs().toDate()], + }), + [], + ); + const filteredMock = useMemo(() => { const keyword = searchQuery.trim().toLowerCase(); - const filterItems = (items: TaskListItem[]) => { - if (!keyword) return items; - return items.filter((item) => { - const text = `${item.taskName} ${item.overview} ${item.owner || ''}`.toLowerCase(); - return text.includes(keyword); - }); + const filterList = (list: TaskListItem[]) => { + if (!keyword) return list; + return list.filter((item) => + `${item.taskName} ${item.overview} ${item.taskDirection}`.toLowerCase().includes(keyword), + ); }; return { - unanalyzed: filterItems(MOCK_TASKS.unanalyzed), - analyzing: filterItems(MOCK_TASKS.analyzing), - completed: filterItems(MOCK_TASKS.completed), + unanalyzed: filterList(MOCK_TASKS.unanalyzed), + running: filterList(MOCK_TASKS.running), + completed: filterList(MOCK_TASKS.completed), }; }, [searchQuery]); const applyMockColumn = (column: ColumnType, pageIndex = 1) => { const source = filteredMock[column]; - const pageItems = paginateTasks(source, pageIndex, pageSizeRef.current); + const pageList = paginateTasks(source, pageIndex, pageSizeRef.current); if (column === 'unanalyzed') { setUnanalyzedTotal(source.length); - setUnanalyzedList(pageItems); + setUnanalyzedList(pageList); setUnanalyzedLoading(false); - } else if (column === 'analyzing') { - setRunningTotal(source.length); - setRunningList(pageItems); - setRunningLoading(false); - } else { - setCompletedTotal(source.length); - setCompletedList(pageItems); - setCompletedLoading(false); + return; } + + if (column === 'running') { + setRunningTotal(source.length); + setRunningList(pageList); + setRunningLoading(false); + return; + } + + setCompletedTotal(source.length); + setCompletedList(pageList); + setCompletedLoading(false); }; const taskListSearch = async ( - taskStatus: ColumnType, + column: ColumnType, pageIndex = 1, pageSize = pageSizeRef.current, ) => { pageSizeRef.current = pageSize; - const setLoadingByType = (loading: boolean) => { - if (taskStatus === 'unanalyzed') setUnanalyzedLoading(loading); - if (taskStatus === 'analyzing') setRunningLoading(loading); - if (taskStatus === 'completed') setCompletedLoading(loading); - }; - - setLoadingByType(true); + if (column === 'unanalyzed') setUnanalyzedLoading(true); + if (column === 'running') setRunningLoading(true); + if (column === 'completed') setCompletedLoading(true); if (useDemoData) { - applyMockColumn(taskStatus, pageIndex); + applyMockColumn(column, pageIndex); return; } + const taskStatusMap: Record = { + unanalyzed: [0, 10], + running: [20], + completed: [25, 30, 40, 60, 70, 80, 90, 100, 110], + }; + try { - const [startTime, endTime] = Array.isArray(dateRange) ? dateRange : ['', '']; - const payload = { + const { startTime, endTime } = normalizeRangeValue(range); + const response = await service.post(`${zncmUrl}/three/cop-three-tasks`, { + content: searchQuery.trim(), pageNum: pageIndex, pageSize, - taskStatus, - taskCategoryName: category, - taskTypeName: subCategory, - searchKey: searchQuery.trim() || undefined, + taskStatus: taskStatusMap[column], startTime, endTime, - }; + taskDirection: '', + }); - const response = await service.post(`${zncmUrl}/three/cop-three-tasks`, payload); - const rows = response?.rows || response?.data?.rows || []; - const total = response?.total || response?.data?.total || 0; - const mappedList = rows.map((item: TaskListItem) => ({ + const result = response?.data ?? response ?? {}; + const records = Array.isArray(result.records) ? result.records : []; + const total = Number(result.total ?? records.length); + const mapped = records.map((item: TaskListItem) => ({ ...item, - column: taskStatus === 'analyzing' ? 'running' : taskStatus, + column, + taskLevel: item.taskLevel || 1, })); - if (taskStatus === 'unanalyzed') { - setUnanalyzedList(mappedList); + if (column === 'unanalyzed') { + setUnanalyzedList(mapped); setUnanalyzedTotal(total); - } else if (taskStatus === 'analyzing') { - setRunningList(mappedList); + } else if (column === 'running') { + setRunningList(mapped); setRunningTotal(total); } else { - setCompletedList(mappedList); + setCompletedList(mapped); setCompletedTotal(total); } } catch (error) { console.error('sentiment task list fetch failed', error); - applyMockColumn(taskStatus, pageIndex); setUseDemoData(true); + applyMockColumn(column, pageIndex); } finally { - setLoadingByType(false); + if (column === 'unanalyzed') setUnanalyzedLoading(false); + if (column === 'running') setRunningLoading(false); + if (column === 'completed') setCompletedLoading(false); } }; - const refreshAllColumns = async () => { - await Promise.all([ - taskListSearch('unanalyzed', 1), - taskListSearch('analyzing', 1), - taskListSearch('completed', 1), - ]); + const refreshColumns = () => { + taskListSearch('unanalyzed', 1); + taskListSearch('running', 1); + taskListSearch('completed', 1); }; useEffect(() => { - refreshAllColumns(); + refreshColumns(); }, [useDemoData]); useEffect(() => { const timer = window.setTimeout(() => { - refreshAllColumns(); - }, useDemoData ? 120 : 260); + refreshColumns(); + }, 200); return () => window.clearTimeout(timer); - }, [searchQuery, dateRange, useDemoData]); - - const metrics = [ - { - label: '全部任务', - value: unanalyzedTotal + runningTotal + completedTotal, - icon: Layers, - accent: 'from-[#4D8DFF] to-[#76C2FF]', - }, - { - label: '未分析', - value: unanalyzedTotal, - icon: Clock3, - accent: 'from-[#38BDF8] to-[#7DD3FC]', - }, - { - label: '分析中', - value: runningTotal, - icon: Sparkles, - accent: 'from-[#2DD4BF] to-[#67E8F9]', - }, - { - label: '已完成', - value: completedTotal, - icon: Bot, - accent: 'from-[#60A5FA] to-[#A78BFA]', - }, - ]; + }, [searchQuery, range]); const handleStartAnalysis = (task: TaskListItem) => { - setSelectedTaskForAnalysis(task); + setSelectedTask(task); setShowAnalysisModal(true); }; - const handleViewTaskDetail = () => { - if (!selectedTaskForAnalysis) return; + const handleOpenTaskDetail = () => { + if (!selectedTask) return; + setStartConfig({ - task_title: selectedTaskForAnalysis.taskName, - task_goal: selectedTaskForAnalysis.overview, - task_context: selectedTaskForAnalysis.overview, + 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); navigate('/qa-center/mope-chat'); }; - const handleMultiAgentDiscussion = () => { - if (!selectedTaskForAnalysis) return; + const handleOpenRoundtable = () => { + if (!selectedTask) return; + setStartConfig({ - task_title: selectedTaskForAnalysis.taskName, - task_goal: selectedTaskForAnalysis.overview, - task_context: selectedTaskForAnalysis.overview, + 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); navigate('/page/workspace/roundtable/planning'); }; + 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,#FFFDFD_0%,#FFF7F7_46%,#FAFAFA_100%)]'; + + const toolbarClass = isDark + ? 'border border-[rgba(67,89,124,0.46)] bg-[rgba(10,23,42,0.94)] text-white shadow-[0_16px_36px_rgba(3,10,22,0.2)]' + : 'border border-[#E8E8E8] bg-white text-[#222222] shadow-[0_14px_30px_rgba(245,34,45,0.06)]'; + + const metricClass = isDark + ? 'border border-[rgba(67,89,124,0.46)] bg-[rgba(12,29,54,0.94)]' + : 'border border-[#E8E8E8] bg-white'; + + const inputClass = isDark + ? '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]'; + + const totalCount = unanalyzedTotal + runningTotal + completedTotal; + const metrics = [ + { label: '全部任务', value: totalCount }, + { label: '未分析', value: unanalyzedTotal }, + { label: '分析中', value: runningTotal }, + { label: '已完成', value: completedTotal }, + ]; + return (
-
-
-
-
-
- +
+
+
+
+
+ + {category} + + + + {subCategory} +
+
-
-

{title}

+
+

+ {title} +

{useDemoData ? ( - + 演示数据 ) : null}
-

+

面向规划任务的情绪与倾向分析看板,支持快速检索、会商启动与结果回看。

-
- {category} - - {subCategory} -
-
-
-
-
-
+
-
- {metrics.map(({ label, value, icon: Icon, accent }) => ( -
+
+ {metrics.map((item, index) => ( +
-

{label}

-

{value}

+

{item.label}

+

+ {item.value} +

-
- +
+
))} -
+
-
-
- - taskListSearch(taskType as ColumnType, pageIndex)} - handleStartAnalysis={handleStartAnalysis} - /> +
+
+ +
+ +
-
- - taskListSearch(taskType as ColumnType, pageIndex)} - handleStartAnalysis={handleStartAnalysis} - /> + +
+ +
+ +
-
- - taskListSearch(taskType as ColumnType, pageIndex)} - handleStartAnalysis={handleStartAnalysis} - /> + +
+ +
+ +
-
+
- {showAnalysisModal && selectedTaskForAnalysis ? ( -
-
+ {showAnalysisModal && selectedTask ? ( +
+

选择分析模式

-

- 当前任务:{selectedTaskForAnalysis.taskName} +

+ 为任务“{selectedTask.taskName}”选择分析方式

-
+
diff --git a/frontend-web/src/strategy-components/components/task-components/TaskAnalyzes.tsx b/frontend-web/src/strategy-components/components/task-components/TaskAnalyzes.tsx index 0f394db2..5d1278ac 100644 --- a/frontend-web/src/strategy-components/components/task-components/TaskAnalyzes.tsx +++ b/frontend-web/src/strategy-components/components/task-components/TaskAnalyzes.tsx @@ -1,38 +1,13 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { - Plus, - Search, - Eye, - Play, - Pause, - Clock, - User, - ChevronRight, - Filter, - FileText, - AlertTriangle, - CheckCircle, - XCircle, - MoreHorizontal, - Edit, - Download, - Bot, - Users, - Workflow, - X, - ArrowRight, - Target, - Layers, -} from 'lucide-react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { ArrowRight, Bot, Clock3, Layers, Search, Sparkles, Users, X } from 'lucide-react'; +import { DateRangePicker, DateRangeValue, Loading } from 'tdesign-react'; +import dayjs from 'dayjs'; +import { useNavigate } from 'react-router-dom'; import { useTheme } from '@/contexts/ThemeContext'; -// 导入请求服务,用于与后端 API 交互 +import { useChatContext } from '@/contexts/ChatContext'; import { requestCrudService } from '@/service/request-axios-service'; import { KanbanColumn } from './KanbanColumn'; import { TaskListItem } from './TaskManagement'; -import { DateRangePicker, DateRangePickerProps, DateRangeValue, Loading } from 'tdesign-react'; -import { useNavigate } from 'react-router-dom'; -import dayjs from 'dayjs'; -import { useChatContext } from '@/contexts/ChatContext'; interface TaskListPageProps { title: string; @@ -41,606 +16,637 @@ interface TaskListPageProps { } type TaskType = 'single' | 'consultation' | 'scene' | 'multi-agent' | 'task-search'; -type TaskStatus = 'draft' | 'pending' | 'running' | 'paused' | 'blocked' | 'completed'; type ColumnType = 'unanalyzed' | 'analyzing' | 'completed'; -interface Task { - id: string; - name: string; - type: TaskType; - status: TaskStatus; - agent?: string; - scene?: string; - participants?: string[]; - currentPhase?: string; - currentRound?: number; - summary?: string; - creator: string; - createTime: string; - updateTime: string; - column: ColumnType; -} - -interface stateItem { - startTime: string, - endTime: string, - task: TaskListItem, -} - const service = requestCrudService; const zncmUrl = import.meta.env.VITE_API_BASE_URL || ''; +const ENABLE_TASK_ANALYSIS_DEMO_BY_DEFAULT = import.meta.env.DEV; -const TaskAnalyzes: React.FC = ({ title, category, subCategory}) => { +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 = { + 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 = ({ title, category, subCategory }) => { const { theme } = useTheme(); const { setStartConfig } = useChatContext(); - const router = useNavigate(); + const navigate = useNavigate(); const isDark = theme === 'dark'; - - // 主题颜色变量 - const cardBg = isDark ? 'bg-slate-800/30' : 'bg-white'; - const cardBorder = isDark ? 'border-blue-800/30' : 'border-red-100'; - 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-400'; - const inputBg = isDark ? 'bg-slate-900/50' : 'bg-white'; - - // 生成模拟数据 - const [tasks] = useState([ - // 未分析列 - { - id: '2026-0320-001', - name: '端午节品牌传播方案', - type: 'scene', - scene: '宣传策略生成', - status: 'draft', - column: 'unanalyzed', - summary: '针对端午节品牌活动制定完整传播策略方案', - creator: '李策划', - createTime: '2026-03-20 10:00', - updateTime: '2026-03-20 10:00', - }, - { - id: '2026-0320-002', - name: '新品上市用户调研', - type: 'single', - agent: 'AudienceAgent', - status: 'pending', - column: 'unanalyzed', - summary: '对新品目标用户进行画像分析', - creator: '王运营', - createTime: '2026-03-20 09:30', - updateTime: '2026-03-20 09:30', - }, - // 分析中列 - { - id: '2026-0319-001', - name: '五四青年节宣传活动策略', - type: 'scene', - scene: '宣传策略生成', - status: 'running', - currentPhase: '行为分析', - column: 'analyzing', - summary: '正在按场景模板执行目标、受众、行为、叙事等分析...', - creator: '张策略', - createTime: '2026-03-19 10:30', - updateTime: '2026-03-20 09:45', - }, - { - id: '2026-0319-002', - name: '青年传播方向会商分析', - type: 'consultation', - participants: ['GoalAgent', 'AudienceAgent', 'NarrativeAgent'], - status: 'running', - currentRound: 2, - column: 'analyzing', - summary: '围绕宣传方向、叙事方式和受众切入点开展多视角会商...', - creator: '王策划', - createTime: '2026-03-19 14:00', - updateTime: '2026-03-20 09:00', - }, - { - id: '2026-0318-001', - name: '年度品牌升级方案推演', - type: 'scene', - scene: '宣传方案推演', - status: 'paused', - currentPhase: '叙事分析', - column: 'analyzing', - summary: '任务已暂停,等待人工介入确认叙事方向', - creator: '王策划', - createTime: '2026-03-18 14:00', - updateTime: '2026-03-20 08:30', - }, - // 已完成列 - { - id: '2026-0318-002', - name: '核心受众画像分析', - type: 'single', - agent: 'AudienceAgent', - status: 'completed', - column: 'completed', - summary: '已完成目标受众识别、关切提炼和阻力分析', - creator: '赵运营', - createTime: '2026-03-18 09:00', - updateTime: '2026-03-18 16:30', - }, - { - id: '2026-0319-003', - name: '五四青年节宣传策略', - type: 'scene', - scene: '宣传策略生成', - status: 'completed', - column: 'completed', - summary: '已完成宣传策略方案生成', - creator: '张策略', - createTime: '2026-03-19 10:00', - updateTime: '2026-03-19 18:00', - }, - { - id: '2026-0319-004', - name: '青年品牌传播路径会商', - type: 'consultation', - participants: ['GoalAgent', 'NarrativeAgent', 'RiskAgent'], - status: 'completed', - column: 'completed', - summary: '已完成多智能体会商,形成结论与分歧报告', - creator: '王策划', - createTime: '2026-03-19 14:30', - updateTime: '2026-03-19 17:00', - }, - ]); + 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 [modeFilter, setModeFilter] = useState('all'); const [showAnalysisModal, setShowAnalysisModal] = useState(false); const [selectedTaskForAnalysis, setSelectedTaskForAnalysis] = useState(null); - const [unanalyzedTotal, setUnanalyzedTotal] = useState(0); // 未分析 - const [runningTotal, setRunningTotal] = useState(0); // 分析中 - const [completedTotal, setCompletedTotal] = useState(0); // 已完成 - const [unanalyzedList, setUnanalyzedList] = useState([]); // 未分析 - const [runningList, setRunningList] = useState([]); // 分析中 - const [completedList, setCompletedList] = useState([]); // 已完成 - const [unanalyzedLoading, setUnanalyzedLoading] = useState(false); // 未分析 - const [runningLoading, setRunningLoading] = useState(false); // 分析中 - const [completedLoading, setCompletedLoading] = useState(false); // 已完成 - const [presets] = useState({ - 最近7天: [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()], - 最近3天: [dayjs().subtract(2, 'day').toDate(), dayjs().toDate()], - 今天: [dayjs().toDate(), dayjs().toDate()], - }); - const [range, setRange] = useState([]); - const state = useRef({ - startTime: '', - endTime: '', - task: {} as TaskListItem, - }) + const [useDemoData, setUseDemoData] = useState(ENABLE_TASK_ANALYSIS_DEMO_BY_DEFAULT); + const [unanalyzedTotal, setUnanalyzedTotal] = useState(0); + const [runningTotal, setRunningTotal] = useState(0); + const [completedTotal, setCompletedTotal] = useState(0); + const [unanalyzedList, setUnanalyzedList] = useState([]); + const [runningList, setRunningList] = useState([]); + const [completedList, setCompletedList] = useState([]); + const [unanalyzedLoading, setUnanalyzedLoading] = useState(false); + const [runningLoading, setRunningLoading] = useState(false); + const [completedLoading, setCompletedLoading] = useState(false); + const [dateRange, setDateRange] = useState([ + dayjs().startOf('month').format('YYYY-MM-DD'), + dayjs().endOf('day').format('YYYY-MM-DD'), + ]); - // 时间日期选择器查询 - const setRangeChange = async (val) => { - setRange(val); - console.log('时间日期', val); - state.current.startTime = ''; - state.current.endTime = ''; - if (val.length > 0) { - state.current.startTime = val[0]; - state.current.endTime = val[1]; - } + const pageSizeRef = useRef(10); - await taskDataChange(); - } + const filteredMock = useMemo(() => { + const keyword = searchQuery.trim().toLowerCase(); + const filterItems = (items: TaskListItem[]) => { + if (!keyword) return items; + return items.filter((item) => { + const text = `${item.taskName} ${item.overview} ${item.owner || ''}`.toLowerCase(); + return text.includes(keyword); + }); + }; - // 获取任务类型配置 - const getTypeConfig = (type: TaskType) => { - 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 }; - case 'multi-agent': - return { label: '多智能体协同', color: 'bg-green-500/20 text-green-400', icon: Layers }; - } - } else { - switch (type) { - case 'single': - return { label: '单智能体', color: 'bg-blue-100 text-blue-600', icon: Bot }; - case 'scene': - return { label: '场景执行', color: 'bg-purple-100 text-purple-600', icon: Workflow }; - case 'consultation': - return { label: '多智能体会商', color: 'bg-orange-100 text-orange-600', icon: Users }; - case 'multi-agent': - return { label: '多智能体协同', color: 'bg-green-100 text-green-600', icon: Layers }; - } - } - }; + return { + unanalyzed: filterItems(MOCK_TASKS.unanalyzed), + analyzing: filterItems(MOCK_TASKS.analyzing), + completed: filterItems(MOCK_TASKS.completed), + }; + }, [searchQuery]); + + const applyMockColumn = (column: ColumnType, pageIndex = 1) => { + const source = filteredMock[column]; + const pageItems = paginateTasks(source, pageIndex, pageSizeRef.current); - // 获取状态配置 - const getStatusConfig = (status: TaskStatus, column: ColumnType) => { if (column === 'unanalyzed') { - switch (status) { - case 'draft': - return { label: '草稿', color: isDark ? 'bg-slate-500/20 text-slate-400' : 'bg-gray-100 text-gray-600' }; - case 'pending': - return { label: '待启动', color: isDark ? 'bg-slate-500/20 text-slate-400' : 'bg-gray-100 text-gray-600' }; - default: - return { label: '待配置', color: isDark ? 'bg-yellow-500/20 text-yellow-400' : 'bg-yellow-100 text-yellow-600' }; - } - } else if (column === 'analyzing') { - switch (status) { - case 'running': - return { label: '运行中', color: isDark ? 'bg-blue-500/20 text-blue-400' : 'bg-red-100 text-red-600' }; - case 'paused': - return { label: '已暂停', color: isDark ? 'bg-yellow-500/20 text-yellow-400' : 'bg-yellow-100 text-yellow-600' }; - case 'blocked': - return { label: '阻塞中', color: isDark ? 'bg-red-500/20 text-red-400' : 'bg-orange-100 text-orange-600' }; - default: - return { label: '待处理', color: isDark ? 'bg-purple-500/20 text-purple-400' : 'bg-purple-100 text-purple-600' }; - } - } else { - return { label: '已完成', color: isDark ? 'bg-green-500/20 text-green-400' : 'bg-green-100 text-green-600' }; - } - }; - - // 筛选任务 - const filteredTasks = tasks.filter(task => { - const matchesSearch = task.name.toLowerCase().includes(searchQuery.toLowerCase()) || - task.id.toLowerCase().includes(searchQuery.toLowerCase()); - const matchesMode = modeFilter === 'all' || task.type === modeFilter; - return 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 stats = { - total: tasks.length, - unanalyzed: unanalyzedTasks.length, - analyzing: analyzingTasks.length, - completed: completedTasks.length, - }; - - // 处理开始分析按钮点击 - const handleStartAnalysis = (task: TaskListItem) => { - setSelectedTaskForAnalysis(task); - state.current.task = task; - setShowAnalysisModal(true); - }; - - // 处理分析模式选择 - const handleSelectAnalysisMode = (mode: TaskType) => { - setShowAnalysisModal(false); - console.log('mode', mode); - if (mode === 'consultation') { - console.log('state.current.task', state.current.task) - const sceneItem = JSON.parse(state.current.task.scene); - if (sceneItem.taskJson) { - sceneItem.taskJson = JSON.parse(sceneItem.taskJson); - setStartConfig({ - ...sceneItem.taskJson, - llm_preset_id: 'BAILING', - consistency_mode: 'soft', - phase_config: {diverge_rounds: 3}, - task_context: state.current.task.overview - }); - router('/qa-center/mope-chat') - } - console.log('sceneItem', sceneItem); - } else if (mode === 'task-search') { - window.open(`https://ch1.b.uat.4.cn/web/#/task/item-aicoh/step-info-search?id=${state.current.task.id}`) - } - }; - - const taskListSearch = async (taskType: string, pageNum: number = 1, pageSize: number = 10) => { - let taskStatus: number[] = [] - if (taskType === 'unanalyzed') { // 未分析 - taskStatus = [0, 10]; - setUnanalyzedLoading(true); - } else if (taskType === 'running') { // 分析中 - taskStatus = [20]; - setRunningLoading(true); - } else if (taskType === 'completed') { // 已完成 - taskStatus = [25, 30, 40, 60, 70, 80, 90, 100, 110]; - setCompletedLoading(true); - } - const param = { - content: searchQuery, - pageNum: pageNum, - pageSize: pageSize, - taskStatus: taskStatus, - startTime: state.current.startTime, - endTime: state.current.endTime, - taskDirection: '' - } - const res = await service.post(`${zncmUrl}/task/cop-tasks`, param); - console.log('任务列表', res); - const result = res.data; - result.records.forEach((item: TaskListItem) => { - item.column = taskType; - if (!item.taskLevel) item.taskLevel = 1; - }) - if (taskType === 'unanalyzed') { + setUnanalyzedTotal(source.length); + setUnanalyzedList(pageItems); setUnanalyzedLoading(false); - setUnanalyzedTotal(result.total); - setUnanalyzedList(result.records); - } else if (taskType === 'running') { + } else if (column === 'analyzing') { + setRunningTotal(source.length); + setRunningList(pageItems); setRunningLoading(false); - setRunningTotal(result.total); - setRunningList(result.records); - } else if (taskType === 'completed') { + } else { + setCompletedTotal(source.length); + setCompletedList(pageItems); setCompletedLoading(false); - setCompletedTotal(result.total); - setCompletedList(result.records); } - } + }; - /** - * 页码变化时的回调函数 - * 当用户切换页码时触发,更新页码并重新获取数据 - * @param value - 新的页码值 - */ - const pageIndexChange = async (type: string, pageIndex: number) => { - // 更新 ref 中存储的页码值 - // 更新页码状态,触发组件重新渲染 - await taskListSearch(type, pageIndex); - } + const taskListSearch = async ( + taskStatus: ColumnType, + pageIndex = 1, + pageSize = pageSizeRef.current, + ) => { + pageSizeRef.current = pageSize; - const taskDataChange = async (pageNum: number = 1, pageSize: number = 10) => { - taskListSearch('unanalyzed', pageNum, pageSize); - taskListSearch('running', pageNum, pageSize); - taskListSearch('completed', pageNum, pageSize); - } + const setLoadingByType = (loading: boolean) => { + if (taskStatus === 'unanalyzed') setUnanalyzedLoading(loading); + if (taskStatus === 'analyzing') setRunningLoading(loading); + if (taskStatus === 'completed') setCompletedLoading(loading); + }; - // 回车处理 - const handleKeyDown = async (e) => { - if ((e.metaKey || e.ctrlKey || e.shiftKey) && e.key === "Enter") { - e.preventDefault(); - } else if (e.keyCode === 13) { - e.preventDefault(); - await taskDataChange(); - } else if (e.key === "/") { - e.preventDefault(); + setLoadingByType(true); + + if (useDemoData) { + applyMockColumn(taskStatus, pageIndex); + return; } + + try { + const [startTime, endTime] = Array.isArray(dateRange) ? dateRange : ['', '']; + const payload = { + pageNum: pageIndex, + pageSize, + taskStatus, + taskCategoryName: category, + taskTypeName: subCategory, + searchKey: searchQuery.trim() || undefined, + startTime, + endTime, + }; + + const response = await service.post(`${zncmUrl}/three/cop-three-tasks`, payload); + const rows = response?.rows || response?.data?.rows || []; + const total = response?.total || response?.data?.total || 0; + const mappedList = rows.map((item: TaskListItem) => ({ + ...item, + column: taskStatus === 'analyzing' ? 'running' : taskStatus, + })); + + if (taskStatus === 'unanalyzed') { + setUnanalyzedList(mappedList); + setUnanalyzedTotal(total); + } else if (taskStatus === 'analyzing') { + setRunningList(mappedList); + setRunningTotal(total); + } else { + setCompletedList(mappedList); + setCompletedTotal(total); + } + } catch (error) { + console.error('task analysis list fetch failed', error); + applyMockColumn(taskStatus, pageIndex); + setUseDemoData(true); + } finally { + setLoadingByType(false); + } + }; + + const refreshAllColumns = async () => { + await Promise.all([ + taskListSearch('unanalyzed', 1), + taskListSearch('analyzing', 1), + taskListSearch('completed', 1), + ]); }; useEffect(() => { - taskDataChange(); - }, []); + refreshAllColumns(); + }, [useDemoData]); - // 渲染看板列 + useEffect(() => { + const timer = window.setTimeout(() => { + refreshAllColumns(); + }, useDemoData ? 120 : 260); + + return () => window.clearTimeout(timer); + }, [searchQuery, dateRange, useDemoData]); + + const metrics = [ + { + label: '全部任务', + value: unanalyzedTotal + runningTotal + completedTotal, + icon: Layers, + accent: 'from-[#4D8DFF] to-[#7CC9FF]', + }, + { + label: '未分析', + value: unanalyzedTotal, + icon: Clock3, + accent: 'from-[#7C9BFF] to-[#5EEAD4]', + }, + { + label: '分析中', + value: runningTotal, + icon: Sparkles, + accent: 'from-[#22C55E] to-[#67E8F9]', + }, + { + label: '已完成', + value: completedTotal, + icon: Bot, + accent: 'from-[#38BDF8] to-[#A78BFA]', + }, + ]; + + 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 ( -
- {/* 顶部工具栏 */} -
-
-
-
- - {category} - - / - - {subCategory} - -
-

任务中心

-
- -
- - {/* 搜索和筛选 */} -
-
- - setSearchQuery(e.target.value)} - onKeyDown={(e) => handleKeyDown(e)} - 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 ${inputBg} ${cardBorder} ${textPrimary} placeholder:${textMuted}`} - /> -
- {/* */} - {/* */} - setRangeChange(val)} enableTimePicker={true} clearable /> -
-
- - {/* 统计概览栏 */} -
- - - - -
- - {/* 看板区 */} -
- - - - - - - - - -
- - {/* 选择分析模式弹窗 */} - {showAnalysisModal && selectedTaskForAnalysis && ( -
setShowAnalysisModal(false)}> -
e.stopPropagation()} - > -
+
+
+
+
+
+
+ +
-

选择分析模式

-

为任务「{selectedTaskForAnalysis.taskName}」选择分析方式

+
+

{title}

+ {useDemoData ? ( + + 演示数据 + + ) : null} +
+

+ 面向规划任务的任务分析看板,聚焦目标拆解、受众判断和策略阻力识别,支持快速检索、启动分析和查看会商结果。 +

+
+
+
+ {category} + + {subCategory} +
+
+ +
+
+ + setDateRange(value)} + /> +
+
+ + +
+
+
+ +
+ {metrics.map(({ label, value, icon: Icon, accent }) => ( +
+
+
+

{label}

+

{value}

+
+
+ +
+
+
+ ))} +
+ +
+
+
+
分析工作台
+
+ 当前为演示任务数据,便于预览深色模式下的多列分析工作流。 +
+
+
+ 目标拆解 / 受众判断 / 会商收敛 +
+
+
+ +
+
+ +
+ + taskListSearch(taskType as ColumnType, pageIndex) + } + handleStartAnalysis={handleStartAnalysis} + /> +
+
+
+ +
+ +
+ + taskListSearch(taskType as ColumnType, pageIndex) + } + handleStartAnalysis={handleStartAnalysis} + /> +
+
+
+ +
+ +
+ + taskListSearch(taskType as ColumnType, pageIndex) + } + handleStartAnalysis={handleStartAnalysis} + /> +
+
+
+
+
+ + {showAnalysisModal && selectedTaskForAnalysis ? ( +
+
+
+
+

选择分析模式

+

+ 当前任务:{selectedTaskForAnalysis.taskName} +

-
- {/* 单智能体分析 */} - {/* */} - - {/* 多智能体协同分析 */} - {/* */} +
- {/* 多智能体会商 */} - - {/* 场景执行 */} - {/* */}
- )} + ) : null}
); }; diff --git a/frontend-web/src/strategy-components/components/task-components/TaskCard.tsx b/frontend-web/src/strategy-components/components/task-components/TaskCard.tsx index 62edd86b..2eabb746 100644 --- a/frontend-web/src/strategy-components/components/task-components/TaskCard.tsx +++ b/frontend-web/src/strategy-components/components/task-components/TaskCard.tsx @@ -1,20 +1,54 @@ import React from 'react'; import { format } from 'date-fns'; -import { useNavigate } from 'react-router-dom'; import { Bot, Eye, Layers, Play, User, Users, Workflow } from 'lucide-react'; +import { useNavigate } from 'react-router-dom'; import { useTheme } from '@/contexts/ThemeContext'; import { useChatContext } from '@/contexts/ChatContext'; import { TaskListItem } from './TaskManagement'; interface TaskCardProps { - TaskType?: string; task: TaskListItem; + taskDataType?: string; handleStartAnalysis: (task: TaskListItem) => void; } +const getTaskTypeConfig = (taskType?: number) => { + switch (taskType) { + case 2: + return { label: '场景', icon: Workflow }; + case 3: + return { label: '会商', icon: Users }; + case 4: + return { label: '多智能体', icon: Layers }; + case 5: + return { label: '检索', icon: Layers }; + case 1: + default: + return { label: '单任务', icon: Bot }; + } +}; + +const getTaskLevelLabel = (taskLevel?: number) => { + switch (taskLevel) { + case 2: + return '紧急'; + case 3: + return '加急'; + case 1: + default: + return '一般'; + } +}; + +const getColumnLabel = (column?: string) => { + if (column === 'running' || column === 'analyzing') return '分析中'; + if (column === 'completed') return '已完成'; + return '待分析'; +}; + export const TaskCard: React.FC = ({ task, - TaskType = 'task', + taskDataType = 'task', handleStartAnalysis, }) => { const { theme } = useTheme(); @@ -22,209 +56,159 @@ export const TaskCard: React.FC = ({ const navigate = useNavigate(); const isDark = theme === 'dark'; - const sceneDataChange = (currentTask: TaskListItem) => { - if (!currentTask.scene) return; + const typeConfig = getTaskTypeConfig(task.taskType); + const TypeIcon = typeConfig.icon; + const isCompleted = task.column === 'completed'; + const isRunning = task.column === 'running' || task.column === 'analyzing'; + + const openConversation = () => { + try { + const sceneItem = task.scene ? JSON.parse(task.scene) : {}; + const taskJson = sceneItem?.taskJson ? JSON.parse(sceneItem.taskJson) : {}; - const sceneItem = JSON.parse(currentTask.scene); - if (sceneItem.taskJson) { - sceneItem.taskJson = JSON.parse(sceneItem.taskJson); setStartConfig({ - ...sceneItem.taskJson, + ...taskJson, + task_title: task.taskName, + task_goal: task.overview, + task_context: task.overview, + llm_preset_id: 'BAILING', + consistency_mode: 'soft', + phase_config: { diverge_rounds: 3 }, + }); + } catch (error) { + setStartConfig({ + task_title: task.taskName, + task_goal: task.overview, + task_context: task.overview, llm_preset_id: 'BAILING', consistency_mode: 'soft', phase_config: { diverge_rounds: 3 }, - task_context: currentTask.overview, }); - navigate('/qa-center/mope-chat'); } + + navigate('/qa-center/mope-chat'); }; - const handleTaskClick = (currentTask: TaskListItem) => { - if (TaskType === 'sentiment') { - sceneDataChange(currentTask); + const handleCardClick = () => { + if (taskDataType === 'sentiment') { + openConversation(); return; } - if (currentTask.column === 'completed') { + if (isCompleted) { navigate('/strategy-result'); return; } - if (currentTask.column === 'running' || currentTask.column === 'analyzing') { - sceneDataChange(currentTask); + if (isRunning) { + openConversation(); } }; - const handleStartChange = (currentTask: TaskListItem) => { - handleStartAnalysis(currentTask); - }; + const handleActionClick = (event: React.MouseEvent) => { + event.stopPropagation(); - const getTypeConfig = (type: string) => { - const common = isDark ? 'border border-white/10 text-slate-200' : 'border border-slate-200 text-slate-700'; - switch (type) { - case 'single': - return { - label: '单任务', - icon: Layers, - className: `${common} ${isDark ? 'bg-[rgba(22,119,255,0.14)]' : 'bg-sky-50'}`, - }; - case 'consultation': - return { - label: '会商', - icon: Users, - className: `${common} ${isDark ? 'bg-[rgba(20,184,166,0.14)]' : 'bg-emerald-50'}`, - }; - case 'scene': - return { - label: '场景', - icon: Workflow, - className: `${common} ${isDark ? 'bg-[rgba(168,85,247,0.14)]' : 'bg-violet-50'}`, - }; - case 'multi-agent': - return { - label: '多智能体', - icon: Bot, - className: `${common} ${isDark ? 'bg-[rgba(99,102,241,0.16)]' : 'bg-indigo-50'}`, - }; - default: - return { - label: '检索', - icon: Layers, - className: `${common} ${isDark ? 'bg-[rgba(59,130,246,0.12)]' : 'bg-slate-100'}`, - }; + if (isCompleted) { + handleCardClick(); + return; } + + handleStartAnalysis(task); }; - const getLevel = (priority: number = 1) => { - if (priority >= 3) return '加急'; - if (priority === 2) return '紧急'; - return '一般'; - }; + const panelClass = isDark + ? 'border border-[rgba(67,89,124,0.46)] bg-[rgba(19,39,72,0.9)] shadow-[0_14px_34px_rgba(3,10,22,0.2)] hover:border-[rgba(110,168,255,0.52)]' + : 'border border-[#E8E8E8] bg-white shadow-[0_10px_24px_rgba(245,34,45,0.06)] hover:border-[#F6B0B4] hover:shadow-[0_16px_32px_rgba(245,34,45,0.12)]'; - const getStatusConfig = (column: string) => { - switch (column) { - case 'completed': - return { - label: '已完成', - className: isDark - ? 'bg-[rgba(34,197,94,0.16)] text-emerald-300 border border-emerald-400/20' - : 'bg-emerald-50 text-emerald-600 border border-emerald-100', - }; - case 'running': - case 'analyzing': - return { - label: '分析中', - className: isDark - ? 'bg-[rgba(14,165,233,0.16)] text-sky-300 border border-sky-400/20' - : 'bg-sky-50 text-sky-600 border border-sky-100', - }; - default: - return { - label: '待分析', - className: isDark - ? 'bg-[rgba(96,165,250,0.14)] text-blue-200 border border-blue-300/15' - : 'bg-blue-50 text-blue-600 border border-blue-100', - }; - } - }; + const typeBadgeClass = isDark + ? 'bg-[rgba(77,141,255,0.14)] text-[#A9D2FF] border border-[rgba(108,161,255,0.24)]' + : 'bg-[#FFF1F0] text-[#CF1322] border border-[#FFD6D6]'; - const typeConfig = getTypeConfig(task.type || 'single'); - const statusConfig = getStatusConfig(task.column || 'unanalyzed'); - const TypeIcon = typeConfig.icon; - const progressValue = Math.max(0, Math.min(task.progress || 0, 100)); + const levelClass = isDark + ? 'text-slate-400' + : 'text-[#8C8C8C]'; + + const statusClass = + task.column === 'completed' + ? isDark + ? 'bg-[rgba(18,184,134,0.16)] text-[#59E2B1] border border-[rgba(89,226,177,0.2)]' + : 'bg-[#EEFCF7] text-[#0F9D6C] border border-[#B7F0D5]' + : task.column === 'running' || task.column === 'analyzing' + ? isDark + ? 'bg-[rgba(34,197,94,0.12)] text-[#67E8A5] border border-[rgba(103,232,165,0.2)]' + : 'bg-[#EAF4FF] text-[#1677FF] border border-[#CDE2FF]' + : isDark + ? 'bg-[rgba(96,165,250,0.12)] text-[#8FD1FF] border border-[rgba(143,209,255,0.2)]' + : 'bg-[#FFF1F0] text-[#F5222D] border border-[#FFD6D6]'; + + const actionClass = isCompleted + ? isDark + ? 'bg-[rgba(12,48,82,0.95)] text-white hover:bg-[rgba(15,60,104,0.98)]' + : 'bg-[#FFF1F0] text-[#CF1322] hover:bg-[#FFE4E6]' + : isDark + ? 'bg-[linear-gradient(90deg,#1677FF,#22A8F0)] text-white hover:brightness-110' + : 'bg-[linear-gradient(90deg,#F5222D,#FF6B73)] text-white hover:brightness-105'; return ( -
-
+ - {typeConfig.label} -
- - {statusConfig.label} + {typeConfig.label} + + {getColumnLabel(task.column)} + +
+ +

+ {task.taskName} +

+ +

+ {task.overview || '暂无任务摘要'} +

+ +
+
+ + {task.taskDirection || '未分配'} +
+
优先级:{getTaskLevelLabel(task.taskLevel)}
+
+ +
+
创建于 {format(new Date(task.createTime), 'MM-dd HH:mm')}
+
更新于 {format(new Date(task.updateTime), 'MM-dd HH:mm')}
- -
-
-
- - {task.owner || task.creator || '未分配'} -
-
优先级:{getLevel(task.priority)}
-
- -
-
- 分析进度 - {progressValue}% -
-
-
-
-
- -
- - 创建于 {task.createTime ? format(new Date(task.createTime), 'MM-dd HH:mm') : '--'} - - - 更新于 {task.updateTime ? format(new Date(task.updateTime), 'MM-dd HH:mm') : '--'} - -
-
- -
- {task.column === 'completed' ? ( - - ) : ( - - )} -
-
+ ); }; diff --git a/frontend-web/src/strategy-components/components/task-components/css/strategy-data.css b/frontend-web/src/strategy-components/components/task-components/css/strategy-data.css index 7ef260bc..5d356922 100644 --- a/frontend-web/src/strategy-components/components/task-components/css/strategy-data.css +++ b/frontend-web/src/strategy-components/components/task-components/css/strategy-data.css @@ -77,28 +77,16 @@ border-radius: 3px; } -.strategy-data::-webkit-scrollbar, -.strategy-data *::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -.strategy-data::-webkit-scrollbar-thumb, -.strategy-data *::-webkit-scrollbar-thumb { - background: rgba(203, 213, 225, 0.75); - border-radius: 999px; -} - -.strategy-data::-webkit-scrollbar-track, -.strategy-data *::-webkit-scrollbar-track { - background: rgba(241, 245, 249, 0.9); - border-radius: 999px; -} - .dark .strategy-data .scrollbar-thin { scrollbar-color: rgba(148, 163, 184, 0.45) transparent; } +.dark .strategy-data::-webkit-scrollbar, +.dark .strategy-data *::-webkit-scrollbar { + width: 8px; + height: 8px; +} + .dark .strategy-data, .dark .strategy-data * { scrollbar-width: thin; @@ -262,27 +250,39 @@ min-width: 320px; } -.sentiment-date-range-popup .t-date-picker__panel, -.sentiment-date-range-popup .t-date-range-picker__panel, -.sentiment-date-range-popup .t-date-picker__panel-body, -.sentiment-date-range-popup .t-date-picker__panel-content, -.sentiment-date-range-popup .t-date-picker__table, -.sentiment-date-range-popup .t-time-picker__panel { +html.dark .sentiment-date-range-popup .t-date-picker__panel, +html.dark .sentiment-date-range-popup .t-date-range-picker__panel, +html.dark .sentiment-date-range-popup .t-date-picker__panel-body, +html.dark .sentiment-date-range-popup .t-date-picker__panel-content, +html.dark .sentiment-date-range-popup .t-date-picker__table, +html.dark .sentiment-date-range-popup .t-time-picker__panel, +html[theme-mode='dark'] .sentiment-date-range-popup .t-date-picker__panel, +html[theme-mode='dark'] .sentiment-date-range-popup .t-date-range-picker__panel, +html[theme-mode='dark'] .sentiment-date-range-popup .t-date-picker__panel-body, +html[theme-mode='dark'] .sentiment-date-range-popup .t-date-picker__panel-content, +html[theme-mode='dark'] .sentiment-date-range-popup .t-date-picker__table, +html[theme-mode='dark'] .sentiment-date-range-popup .t-time-picker__panel { background: #0d1d33 !important; color: #e6f4ff !important; } -.sentiment-date-range-popup .t-popup__content { +html.dark .sentiment-date-range-popup .t-popup__content, +html[theme-mode='dark'] .sentiment-date-range-popup .t-popup__content { background: #0d1d33 !important; border: 1px solid rgba(110, 136, 176, 0.24); box-shadow: 0 24px 60px rgba(2, 8, 20, 0.42); } -.sentiment-date-range-popup .t-date-picker__cell, -.sentiment-date-range-popup .t-date-picker__cell-inner, -.sentiment-date-range-popup .t-date-picker__header, -.sentiment-date-range-popup .t-date-picker__text, -.sentiment-date-range-popup .t-button { +html.dark .sentiment-date-range-popup .t-date-picker__cell, +html.dark .sentiment-date-range-popup .t-date-picker__cell-inner, +html.dark .sentiment-date-range-popup .t-date-picker__header, +html.dark .sentiment-date-range-popup .t-date-picker__text, +html.dark .sentiment-date-range-popup .t-button, +html[theme-mode='dark'] .sentiment-date-range-popup .t-date-picker__cell, +html[theme-mode='dark'] .sentiment-date-range-popup .t-date-picker__cell-inner, +html[theme-mode='dark'] .sentiment-date-range-popup .t-date-picker__header, +html[theme-mode='dark'] .sentiment-date-range-popup .t-date-picker__text, +html[theme-mode='dark'] .sentiment-date-range-popup .t-button { color: #e6f4ff !important; } @@ -296,7 +296,7 @@ color: #ffffff; } -.strategy-data .dark-theme:hover { +.dark .strategy-data .dark-theme:hover { background: #0062d966; color: #ffffff; border-color: #0062d9 !important; @@ -307,8 +307,8 @@ border-color: var(--card-border); } -.strategy-data .dark-theme.task-active, -.strategy-data .dark-theme .task-active { +.dark .strategy-data .dark-theme.task-active, +.dark .strategy-data .dark-theme .task-active { border-color: #0062d9 !important; } @@ -351,20 +351,20 @@ background: linear-gradient(180deg, #f5f5f5, #fff 17.89% 100%); } -.strategy-data .dark-action-bg { +.dark .strategy-data .dark-action-bg { background: rgba(3, 38, 80, 0.6) !important; border: 1px solid rgba(43, 143, 254, 0.6) !important; border-radius: 8px !important; box-shadow: 0 1px 16px rgba(0, 0, 0, 0.08) !important; } -.strategy-data .dark-action-item { +.dark .strategy-data .dark-action-item { background: #0062d91a; border: 1px solid #2b8ffe99; border-radius: 8px; } -.strategy-data .dark-action-item:hover { +.dark .strategy-data .dark-action-item:hover { background: #0062d966; border-color: #0062d9; } @@ -373,6 +373,16 @@ .strategy-data .t-loading, .strategy-data .t-loading__parent { min-height: 0; + height: 100%; +} + +.strategy-data .t-loading { + display: block; +} + +.strategy-data .t-loading__parent { + display: flex; + flex-direction: column; } .strategy-data .t-loading--full {