This commit is contained in:
董宏凯 committed 2026-06-09 19:45:51 +08:00
commit 57e286b8ad
5 files changed
+146 -365

No files matched your search

@@ -8,7 +8,6 @@ import { Col, Loading, Pagination, Progress, Row } from 'tdesign-react';
import { requestCrudService } from '@/service/request-axios-service';
import { ActionColumn } from './ActionColumn';
import { resolveStatusIcon } from './icons';
import { mockActionColumns, mockTaskList } from './actionMock';
interface ActionAnalyzesProps {
TaskType?: string;
@@ -70,14 +69,6 @@ export const ActionAnalyzes: React.FC<ActionAnalyzesProps> = ({ title, category,
await taskListSearch();
}
// 接口不可用 / 返回为空时回落到本地预览数据,方便联调看效果。
const fallbackToMock = () => {
setTaskList(mockTaskList);
setTotal(mockTaskList.length);
setActionColumnList(mockActionColumns);
if (mockTaskList[0]) setTaskId(mockTaskList[0].id);
};
// 任务查询
const taskListSearch = async () => {
const param = {
@@ -86,27 +77,19 @@ export const ActionAnalyzes: React.FC<ActionAnalyzesProps> = ({ title, category,
pageSize: state.current.pageSize
}
setTaskLoading(true);
try {
const res = await service.post(`${zncmUrl}/action/cop-action-tasks`, param);
const result: TaskListItem[] = Array.isArray(res?.data) ? res.data : [];
if (result.length === 0) {
fallbackToMock();
return;
}
setTaskLoading(false);
const result = res.data;
result.forEach((item: TaskListItem) => {
item.progress = item.endCount / item.sumAll || 0; // 获取已完成和未完成的占比
item.taskStatusData = taskStatusList[item.taskStatus] // 根据任务状态码获取相关信息
})
setTaskList(result); // 设置任务列表页
setTaskList(result || []); // 设置任务列表页
const totalRes = await service.post(`${zncmUrl}/action/cop-action-tasks-count`, param); // 查询任务总数
setTotal(totalRes?.data || result.length); // 设置总数
setTotal(totalRes.data || 0); // 设置总数
if (result.length > 0) {
setTaskId(result[0].id);
await actionListSearch(result[0].id);
} catch (err) {
console.warn('任务搜索失败,使用本地预览数据', err);
fallbackToMock();
} finally {
setTaskLoading(false);
}
}
@@ -116,23 +99,15 @@ export const ActionAnalyzes: React.FC<ActionAnalyzesProps> = ({ title, category,
taskId: taskId,
}
setActionLoading(true);
try {
const res = await service.get(`${zncmUrl}/action/cop-task-actions`, param);
const result = res?.data ?? {};
const columns = [
setActionLoading(false);
const result = res.data;
setActionColumnList([
{ columnName: '待分析', icon: '待CEH.svg', actionList: result.collect || [] },
{ columnName: 'CEH中', icon: '进行中.svg', actionList: result.collect1 || [] },
{ columnName: 'CEH完成', icon: '完成.svg', actionList: result.collect2 || [] },
{ columnName: '已终止', icon: '已终止.svg', actionList: result.endDate || [] },
];
const hasAny = columns.some((c) => c.actionList.length > 0);
setActionColumnList(hasAny ? columns : mockActionColumns);
} catch (err) {
console.warn('行动查询失败,使用本地预览数据', err);
setActionColumnList(mockActionColumns);
} finally {
setActionLoading(false);
}
]);
}
const taskDataChange = async (taskId: number) => {
@@ -1,13 +1,14 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { ArrowRight, Layers, Plus, Search, Users, X } from 'lucide-react';
import { ArrowRight, Bot, 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 { requestCrudService } from '@/service/request-axios-service';
import { toast } from 'sonner';
import { KanbanColumn } from './KanbanColumn';
import { TaskListItem } from './TaskManagement';
import { sentimentAnalyzeMock } from './kanbanMock';
import { listBusinessMappings } from '@/strategy-components/api/business-mapping';
interface TaskListPageProps {
title: string;
@@ -25,6 +26,8 @@ type ColumnType = 'unanalyzed' | 'running' | 'completed';
const service = requestCrudService;
const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
/** 本页对应的业务码(3QFX = 3Q);用于查业务映射拿到要自动选用的业务链 / 单智能体。 */
const BUSINESS_CODE = '3Q';
const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategory }) => {
const { theme } = useTheme();
@@ -79,19 +82,6 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
await taskDataChange();
};
const applyColumnData = (taskType: ColumnType, list: TaskListItem[], total: number) => {
if (taskType === 'unanalyzed') {
setUnanalyzedTotal(total);
setUnanalyzedList(list);
} else if (taskType === 'running') {
setRunningTotal(total);
setRunningList(list);
} else {
setCompletedTotal(total);
setCompletedList(list);
}
};
const taskListSearch = async (
taskType: ColumnType,
pageNum = 1,
@@ -119,19 +109,18 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
column: taskType,
taskLevel: item.taskLevel || 1,
}));
const total = Number(result.total ?? normalized.length);
if (normalized.length === 0) {
// 接口无数据时回落到本地预览数据,方便联调看效果
const mock = sentimentAnalyzeMock[taskType];
applyColumnData(taskType, mock, mock.length);
return;
if (taskType === 'unanalyzed') {
setUnanalyzedTotal(total);
setUnanalyzedList(normalized);
} else if (taskType === 'running') {
setRunningTotal(total);
setRunningList(normalized);
} else {
setCompletedTotal(total);
setCompletedList(normalized);
}
applyColumnData(taskType, normalized, Number(result.total ?? normalized.length));
} catch (err) {
console.warn('三情任务查询失败,使用本地预览数据', err);
const mock = sentimentAnalyzeMock[taskType];
applyColumnData(taskType, mock, mock.length);
} finally {
if (taskType === 'unanalyzed') setUnanalyzedLoading(false);
if (taskType === 'running') setRunningLoading(false);
@@ -173,7 +162,7 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
roundtablePrefill: {
taskTitle: task.taskName,
taskGoal: task.overview,
businessCode: '3Q',
businessCode: BUSINESS_CODE,
autoStart: true,
},
},
@@ -181,6 +170,41 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
setShowAnalysisModal(false);
};
// 单智能体分析:用「业务链条管理」里为本业务配置的智能体,跳转到其问答页(新会话),
// 把任务名称/简介作为开场消息暂存,由 AgentChatPage 读取 sessionStorage.pendingAgentMessage
// 自动发出并开始问答。未配置智能体则提示去配置。
const handleSingleAgentAnalysis = async () => {
const task = stateRef.current.task;
if (!task) return;
let agentId: string | null = null;
try {
const mappings = await listBusinessMappings();
agentId = mappings.find((m) => m.businessCode === BUSINESS_CODE)?.agentId ?? null;
} catch {
/* ignore → 下面按未配置处理 */
}
if (!agentId) {
toast.error('该业务尚未在「业务链条管理」中配置单智能体');
return;
}
const text = [
task.taskName ? `任务名称:${task.taskName}` : '',
task.overview ? `任务简介:${task.overview}` : '',
]
.filter(Boolean)
.join('\n');
try {
sessionStorage.setItem(
'pendingAgentMessage',
JSON.stringify({ agentId, text }),
);
} catch {
/* noop */
}
navigate(`/page/strategy/agents/${agentId}/chats/new`);
setShowAnalysisModal(false);
};
const handleKeyDown = async (event: React.KeyboardEvent<HTMLInputElement>) => {
if ((event.metaKey || event.ctrlKey || event.shiftKey) && event.key === 'Enter') {
event.preventDefault();
@@ -399,6 +423,23 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
</div>
<ArrowRight className="h-4 w-4 opacity-70" />
</button>
<button
type="button"
onClick={handleSingleAgentAnalysis}
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(167,139,250,0.46)]' : 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'}`}
>
<div className="flex items-start gap-3">
<Bot className={`mt-0.5 h-5 w-5 ${isDark ? 'text-[#C4B5FD]' : 'text-[#7C3AED]'}`} />
<div>
<div className="font-medium">单智能体分析</div>
<div className={`mt-1 text-sm ${isDark ? 'text-slate-300' : 'text-[#575757]'}`}>
由单个 AI 智能体直接对该任务进行分析问答。
</div>
</div>
</div>
<ArrowRight className="h-4 w-4 opacity-70" />
</button>
</div>
</div>
</div>
@@ -5,9 +5,10 @@ import dayjs from 'dayjs';
import { useNavigate } from 'react-router-dom';
import { useTheme } from '@/contexts/ThemeContext';
import { requestCrudService } from '@/service/request-axios-service';
import { toast } from 'sonner';
import { KanbanColumn } from './KanbanColumn';
import { TaskListItem } from './TaskManagement';
import { taskAnalyzeMock } from './kanbanMock';
import { listBusinessMappings } from '@/strategy-components/api/business-mapping';
interface TaskListPageProps {
title: string;
@@ -25,6 +26,8 @@ type ColumnType = 'unanalyzed' | 'running' | 'completed';
const service = requestCrudService;
const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
/** 本页对应的业务码(任务FX = 6BF);用于查业务映射拿到要自动选用的业务链 / 单智能体。 */
const BUSINESS_CODE = '6BF';
const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategory }) => {
const { theme } = useTheme();
@@ -79,19 +82,6 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
await taskDataChange();
};
const applyColumnData = (taskType: ColumnType, list: TaskListItem[], total: number) => {
if (taskType === 'unanalyzed') {
setUnanalyzedTotal(total);
setUnanalyzedList(list);
} else if (taskType === 'running') {
setRunningTotal(total);
setRunningList(list);
} else {
setCompletedTotal(total);
setCompletedList(list);
}
};
const taskListSearch = async (
taskType: ColumnType,
pageNum = 1,
@@ -119,19 +109,18 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
column: taskType,
taskLevel: item.taskLevel || 1,
}));
const total = Number(result.total ?? normalized.length);
if (normalized.length === 0) {
// 接口无数据时回落到本地预览数据,方便联调看效果
const mock = taskAnalyzeMock[taskType];
applyColumnData(taskType, mock, mock.length);
return;
if (taskType === 'unanalyzed') {
setUnanalyzedTotal(total);
setUnanalyzedList(normalized);
} else if (taskType === 'running') {
setRunningTotal(total);
setRunningList(normalized);
} else {
setCompletedTotal(total);
setCompletedList(normalized);
}
applyColumnData(taskType, normalized, Number(result.total ?? normalized.length));
} catch (err) {
console.warn('任务查询失败,使用本地预览数据', err);
const mock = taskAnalyzeMock[taskType];
applyColumnData(taskType, mock, mock.length);
} finally {
if (taskType === 'unanalyzed') setUnanalyzedLoading(false);
if (taskType === 'running') setRunningLoading(false);
@@ -173,7 +162,7 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
roundtablePrefill: {
taskTitle: task.taskName,
taskGoal: task.overview,
businessCode: '6BF',
businessCode: BUSINESS_CODE,
autoStart: true,
},
},
@@ -181,6 +170,41 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
setShowAnalysisModal(false);
};
// 单智能体分析:用「业务链条管理」里为本业务配置的智能体,跳转到其问答页(新会话),
// 把任务名称/简介作为开场消息暂存,由 AgentChatPage 读取 sessionStorage.pendingAgentMessage
// 自动发出并开始问答。未配置智能体则提示去配置。
const handleSingleAgentAnalysis = async () => {
const task = stateRef.current.task;
if (!task) return;
let agentId: string | null = null;
try {
const mappings = await listBusinessMappings();
agentId = mappings.find((m) => m.businessCode === BUSINESS_CODE)?.agentId ?? null;
} catch {
/* ignore → 下面按未配置处理 */
}
if (!agentId) {
toast.error('该业务尚未在「业务链条管理」中配置单智能体');
return;
}
const text = [
task.taskName ? `任务名称:${task.taskName}` : '',
task.overview ? `任务简介:${task.overview}` : '',
]
.filter(Boolean)
.join('\n');
try {
sessionStorage.setItem(
'pendingAgentMessage',
JSON.stringify({ agentId, text }),
);
} catch {
/* noop */
}
navigate(`/page/strategy/agents/${agentId}/chats/new`);
setShowAnalysisModal(false);
};
const handleKeyDown = async (event: React.KeyboardEvent<HTMLInputElement>) => {
if ((event.metaKey || event.ctrlKey || event.shiftKey) && event.key === 'Enter') {
event.preventDefault();
@@ -419,6 +443,25 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
</div>
<ArrowRight className="h-4 w-4 opacity-70 transition group-hover:translate-x-0.5" />
</button>
<button
type="button"
onClick={handleSingleAgentAnalysis}
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(167,139,250,0.48)] hover:bg-[rgba(28,24,58,0.96)]' : 'border-slate-200 bg-slate-50 hover:border-violet-200 hover:bg-violet-50'}`}
>
<div className="flex items-center gap-4">
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-[linear-gradient(180deg,#7C3AED,#A78BFA)]">
<Bot className="h-5 w-5 text-white" />
</div>
<div>
<div className="font-medium">单智能体分析</div>
<div className={`mt-1 text-sm ${isDark ? 'text-slate-300/72' : 'text-slate-500'}`}>
由单个 AI 智能体直接对该任务进行分析问答。
</div>
</div>
</div>
<ArrowRight className="h-4 w-4 opacity-70 transition group-hover:translate-x-0.5" />
</button>
</div>
</div>
</div>
@@ -1,130 +0,0 @@
/**
* 行动 CE 看板的本地预览数据。
*
* 仅在接口不可用 / 返回为空时回落使用(见 ActionAnalyzes 的 catch 分支),
* 后端正常返回数据时不会用到。纯前端联调 / 演示效果用。
*/
import {
actionColumnItem,
ActionListItem,
TaskListItem,
taskStatusList,
} from "./TaskManagement";
const statusMap = taskStatusList as Record<string, TaskListItem["taskStatusData"]>;
/** 左侧任务列表 —— 覆盖「待分析 / 分析中 / 审核通过(完成) / 审核未通过」四种状态图标。 */
export const mockTaskList: TaskListItem[] = (
[
{
id: 9001,
taskName: "京津冀区域舆情联动处置专项",
taskStartDate: "2026-06-08 09:12:00",
taskStatus: 20,
endCount: 3,
sumAll: 8,
},
{
id: 9002,
taskName: "重点人群引导策略分析",
taskStartDate: "2026-06-07 14:30:00",
taskStatus: 10,
endCount: 0,
sumAll: 5,
},
{
id: 9003,
taskName: "阶段性成效复盘与归档",
taskStartDate: "2026-06-05 16:45:00",
taskStatus: 110,
endCount: 6,
sumAll: 6,
},
{
id: 9004,
taskName: "审核未通过任务回溯",
taskStartDate: "2026-06-04 11:10:00",
taskStatus: 50,
endCount: 2,
sumAll: 7,
},
] as Array<Partial<TaskListItem>>
).map((t) => ({
...t,
progress: t.sumAll ? (t.endCount as number) / (t.sumAll as number) : 0,
taskStatusData: statusMap[String(t.taskStatus)],
})) as unknown as TaskListItem[];
function action(partial: Partial<ActionListItem>): ActionListItem {
return partial as unknown as ActionListItem;
}
/** 右侧四栏行动 —— 列与 ActionAnalyzes 里真实构建的四栏一一对应。 */
export const mockActionColumns: actionColumnItem[] = [
{
columnName: "待分析",
icon: "待CEH.svg",
actionList: [
action({
id: 1101,
actionName: "梳理重点平台舆情数据,输出待分析清单",
level: 1,
deptName: "情报研判处",
actionStatus: 0,
}),
action({
id: 1102,
actionName: "采集竞品动向与受众反馈样本",
level: 2,
deptName: "数据采集组",
actionStatus: 0,
}),
],
},
{
columnName: "CEH中",
icon: "进行中.svg",
actionList: [
action({
id: 1201,
actionName: "开展多智能体会商,形成初步处置共识",
level: 1,
deptName: "策略策划处",
actionStatus: 1,
}),
],
},
{
columnName: "CEH完成",
icon: "完成.svg",
actionList: [
action({
id: 1301,
actionName: "完成作战方案撰写并通过审核",
level: 1,
deptName: "指挥协调中心",
actionStatus: 1,
}),
action({
id: 1302,
actionName: "归档阶段性成效报告并同步相关单位",
level: 2,
deptName: "综合保障组",
actionStatus: 1,
}),
],
},
{
columnName: "已终止",
icon: "已终止.svg",
actionList: [
action({
id: 1401,
actionName: "因优先级调整终止的预备行动",
level: 2,
deptName: "综合保障组",
actionStatus: 1,
}),
],
},
];
@@ -1,148 +0,0 @@
/**
* 看板(任务分析 / 三情分析)的本地预览数据。
*
* 仅在接口不可用 / 返回为空时回落使用(见各页 taskListSearch 的空数据 / catch 分支),
* 后端正常返回数据时不会用到。纯前端联调 / 演示效果用。
*/
import { TaskListItem } from "./TaskManagement";
export type KanbanColumnType = "unanalyzed" | "running" | "completed";
interface Seed {
taskName: string;
overview: string;
taskDirection: string;
taskType?: number; // 1单任务 2场景 3会商 4多智能体 5检索
taskLevel?: number; // 1一般 2紧急 3加急
createTime?: string;
updateTime?: string;
}
function buildTask(seed: Seed, column: KanbanColumnType, id: number): TaskListItem {
return {
id,
column,
taskName: seed.taskName,
overview: seed.overview,
taskDirection: seed.taskDirection,
taskType: seed.taskType ?? 1,
taskLevel: seed.taskLevel ?? 1,
createTime: seed.createTime ?? "2026-06-08 09:30:00",
updateTime: seed.updateTime ?? "2026-06-09 10:15:00",
scene: "",
} as unknown as TaskListItem;
}
function makeMock(
baseId: number,
seeds: Record<KanbanColumnType, Seed[]>,
): Record<KanbanColumnType, TaskListItem[]> {
let id = baseId;
const build = (col: KanbanColumnType) => seeds[col].map((s) => buildTask(s, col, (id += 1)));
return {
unanalyzed: build("unanalyzed"),
running: build("running"),
completed: build("completed"),
};
}
/** 任务分析(plan-task-fx)预览数据。 */
export const taskAnalyzeMock = makeMock(7100, {
unanalyzed: [
{
taskName: "区域目标人群拆解专项",
overview: "对重点区域目标人群进行分层拆解,明确触达优先级与切入口。",
taskDirection: "策略策划处",
taskType: 4,
taskLevel: 2,
createTime: "2026-06-09 08:20:00",
updateTime: "2026-06-09 08:20:00",
},
{
taskName: "竞品动向监测立项",
overview: "梳理同类项目近期动向,输出可对照的策略阻力清单。",
taskDirection: "情报研判处",
taskType: 5,
taskLevel: 1,
createTime: "2026-06-08 16:45:00",
updateTime: "2026-06-08 17:02:00",
},
],
running: [
{
taskName: "阶段目标可行性会商",
overview: "多智能体围绕阶段目标的可达性与风险点开展讨论并形成初步结论。",
taskDirection: "指挥协调中心",
taskType: 3,
taskLevel: 3,
createTime: "2026-06-07 10:10:00",
updateTime: "2026-06-09 09:40:00",
},
],
completed: [
{
taskName: "重点议题策略路径分析",
overview: "完成重点议题的策略路径推演,已形成可执行的行动建议。",
taskDirection: "策略策划处",
taskType: 2,
taskLevel: 1,
createTime: "2026-06-04 14:00:00",
updateTime: "2026-06-06 11:25:00",
},
{
taskName: "受众态度归因复盘",
overview: "对上一阶段受众态度变化进行归因复盘,沉淀经验供后续复用。",
taskDirection: "综合保障组",
taskType: 1,
taskLevel: 2,
createTime: "2026-06-03 09:15:00",
updateTime: "2026-06-05 18:30:00",
},
],
});
/** 三情分析(plan-task-sentiment)预览数据。 */
export const sentimentAnalyzeMock = makeMock(7200, {
unanalyzed: [
{
taskName: "重点平台情绪监测专项",
overview: "采集重点平台舆论样本,识别情绪倾向与潜在风险信号。",
taskDirection: "情报研判处",
taskType: 5,
taskLevel: 2,
createTime: "2026-06-09 07:50:00",
updateTime: "2026-06-09 07:50:00",
},
{
taskName: "舆情走势预研立项",
overview: "围绕热点事件开展舆情走势预研,输出三情研判初判结论。",
taskDirection: "策略策划处",
taskType: 1,
taskLevel: 1,
createTime: "2026-06-08 13:20:00",
updateTime: "2026-06-08 13:48:00",
},
],
running: [
{
taskName: "群体情绪共识会商",
overview: "多智能体从社情、舆情、敌情多视角讨论群体情绪态势并形成共识。",
taskDirection: "指挥协调中心",
taskType: 3,
taskLevel: 3,
createTime: "2026-06-07 11:30:00",
updateTime: "2026-06-09 10:05:00",
},
],
completed: [
{
taskName: "阶段性情绪研判归档",
overview: "完成阶段性三情研判,结论已通过审核并归档同步相关单位。",
taskDirection: "综合保障组",
taskType: 2,
taskLevel: 1,
createTime: "2026-06-02 15:10:00",
updateTime: "2026-06-05 16:40:00",
},
],
});