代码合并

This commit is contained in:
fengxiangdebaozi committed 2026-06-09 14:05:51 +08:00
1 parent 1938c80061
commit 230fab013a
14 files changed
+273 -56

No files matched your search

+2 -1
View File
@@ -119,7 +119,8 @@
border: 1px solid;
border-color: rgba(43, 143, 254, 0.6);
border-radius: 4px;
backdrop-filter: blur(2px);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
position: absolute;
left: 108px;
top: 0;
@@ -211,6 +211,13 @@ export const HighFidelityReport: React.FC<HighFidelityReportProps> = ({
[artifactsBySeat],
);
// 只保留「有产出文件」的席位;加载中 / 出错的席位仍保留以展示其状态。
// 全部席位都没有文件(且都已加载完成、无错误)时,整块「生成的报告文件」隐藏。
const seatsWithFiles = useMemo(
() => artifactsBySeat.filter((s) => s.loading || s.error || s.files.length > 0),
[artifactsBySeat],
);
const toggleSeat = useCallback((key: string) => {
setExpandedSeats((prev) => {
const next = new Set(prev);
@@ -424,21 +431,20 @@ export const HighFidelityReport: React.FC<HighFidelityReportProps> = ({
)}
</section>
{/* Section 3: Generated files per seat —— 点击「查看」进入沙箱分栏预览 */}
<section className="bg-white border border-slate-200 rounded-2xl p-5 shadow-[0_2px_8px_rgba(0,0,0,0.02)]">
<h4 className="text-xs font-black text-slate-900 uppercase tracking-widest mb-3 flex items-center gap-2">
<Download className="w-4 h-4 text-primary" />
生成的报告文件
<span className="text-[10px] font-mono font-semibold text-slate-400 bg-slate-100 px-1.5 py-0.5 rounded">
{totalFileCount}
</span>
</h4>
{/* Section 3: Generated files per seat —— 点击「查看」进入沙箱分栏预览。
仅当存在「有产出文件 / 加载中 / 出错」的席位时才渲染;全部席位无文件则整块隐藏。 */}
{seatsWithFiles.length > 0 && (
<section className="bg-white border border-slate-200 rounded-2xl p-5 shadow-[0_2px_8px_rgba(0,0,0,0.02)]">
<h4 className="text-xs font-black text-slate-900 uppercase tracking-widest mb-3 flex items-center gap-2">
<Download className="w-4 h-4 text-primary" />
生成的报告文件
<span className="text-[10px] font-mono font-semibold text-slate-400 bg-slate-100 px-1.5 py-0.5 rounded">
{totalFileCount}
</span>
</h4>
{artifactsBySeat.length === 0 ? (
<p className="text-[11px] text-slate-400 italic">尚未初始化席位线程,无法列出文件。</p>
) : (
<div className="space-y-3">
{artifactsBySeat.map((seat) => (
{seatsWithFiles.map((seat) => (
<div key={seat.agentName} className="border border-slate-150 rounded-xl">
<div className="flex items-center justify-between gap-2 px-3 py-2 bg-slate-50 border-b border-slate-150 rounded-t-xl">
<span className="text-[11.5px] font-bold text-slate-800 truncate">{seat.displayName}</span>
@@ -501,8 +507,8 @@ export const HighFidelityReport: React.FC<HighFidelityReportProps> = ({
</div>
))}
</div>
)}
</section>
</section>
)}
{/* Footer */}
<div className="pt-2 border-t border-slate-100 flex items-center justify-between flex-wrap gap-4">
@@ -4,9 +4,11 @@ import { Bot, ChevronRight, Download, Edit, Eye, FileText, Layers, MoreHorizonta
import React, { useEffect, useRef, useState } from 'react';
import { format } from 'date-fns';
import { useNavigate } from 'react-router-dom';
import { Col, Empty, Loading, Pagination, Progress, Row } from 'tdesign-react';
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;
@@ -68,6 +70,14 @@ 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 = {
@@ -76,21 +86,27 @@ export const ActionAnalyzes: React.FC<ActionAnalyzesProps> = ({ title, category,
pageSize: state.current.pageSize
}
setTaskLoading(true);
const res = await service.post(`${zncmUrl}/action/cop-action-tasks`, param);
setTaskLoading(false);
console.log('任务搜索', res);
const result = res.data;
result.forEach((item: TaskListItem) => {
item.progress = item.endCount / item.sumAll || 0; // 获取已完成和未完成的占比
item.taskStatusData = taskStatusList[item.taskStatus] // 根据任务状态码获取相关信息
})
setTaskList(result || []); // 设置任务列表页
const totalRes = await service.post(`${zncmUrl}/action/cop-action-tasks-count`, param); // 查询任务总数
console.log('totalRes', totalRes)
setTotal(totalRes.data || 0); // 设置总数
if (result.length > 0) {
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;
}
result.forEach((item: TaskListItem) => {
item.progress = item.endCount / item.sumAll || 0; // 获取已完成和未完成的占比
item.taskStatusData = taskStatusList[item.taskStatus] // 根据任务状态码获取相关信息
})
setTaskList(result); // 设置任务列表页
const totalRes = await service.post(`${zncmUrl}/action/cop-action-tasks-count`, param); // 查询任务总数
setTotal(totalRes?.data || result.length); // 设置总数
setTaskId(result[0].id);
await actionListSearch(result[0].id);
} catch (err) {
console.warn('任务搜索失败,使用本地预览数据', err);
fallbackToMock();
} finally {
setTaskLoading(false);
}
}
@@ -100,16 +116,23 @@ export const ActionAnalyzes: React.FC<ActionAnalyzesProps> = ({ title, category,
taskId: taskId,
}
setActionLoading(true);
const res = await service.get(`${zncmUrl}/action/cop-task-actions`, param);
console.log('行动查询', res);
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 || [] },
]);
try {
const res = await service.get(`${zncmUrl}/action/cop-task-actions`, param);
const result = res?.data ?? {};
const columns = [
{ 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) => {
@@ -151,7 +174,7 @@ export const ActionAnalyzes: React.FC<ActionAnalyzesProps> = ({ title, category,
<div className="text-[var(--text-color)] text-[17px] mb-[5px] font-[500]">{item.taskName}</div>
<div className="text-[#7f7f7f] mb-[5px]">{format(new Date(item.taskStartDate), 'yyyy-MM-dd HH:mm:ss')}</div>
<div className="flex items-center mb-[8px]">
<img src={`/icon/${item?.taskStatusData?.icon}`} alt="" />
<img src={resolveStatusIcon(item?.taskStatusData?.icon)} className="w-[20px]" alt="" />
<span className="ml-[5px] leading-[16px]">{item?.taskStatusData?.label}</span>
</div>
<div className="pb-[10px] pt-[10px]">
@@ -195,7 +218,11 @@ export const ActionAnalyzes: React.FC<ActionAnalyzesProps> = ({ title, category,
))
}
{
actionColumnList.length === 0 && <Empty/>
actionColumnList.length === 0 && (
<div className={`col-span-4 flex items-center justify-center py-[60px] text-sm ${isDark ? 'text-slate-500' : 'text-[#bfbfbf]'}`}>
暂无数据
</div>
)
}
</div>
</Loading>
@@ -4,8 +4,9 @@ import { Bot, ChevronRight, Download, Edit, Eye, FileText, Layers, MoreHorizonta
import React, { useEffect, useRef, useState } from 'react';
import { format } from 'date-fns';
import { useNavigate } from 'react-router-dom';
import { Col, Empty, Pagination, Progress, Row } from 'tdesign-react';
import { Col, Pagination, Progress, Row } from 'tdesign-react';
import { requestCrudService } from '@/service/request-axios-service';
import { resolveStatusIcon } from './icons';
interface ActionColumnProps {
TaskType?: string;
@@ -66,7 +67,7 @@ export const ActionColumn: React.FC<ActionColumnProps> = ({ title, category, sub
<div className={`h-[calc(100vh-100px)] p-[10px] flex-1 ${isDark? 'dark-action-bg' : `bg-white action-bg${columnIndex} shadow-[2px_0px_6px_rgba(0,0,0,0.12)]`} rounded-[4px]`}>
<div className="mb-[10px]">
<div className="flex items-center mb-[20px]">
<img src={`/icon/${icon}`} className="w-[24px] mr-[10px]" alt="" />
<img src={resolveStatusIcon(icon)} className="w-[24px] mr-[10px]" alt="" />
<span className="text-[#222222] text-[18px] font-bold mr-[20px]">{columnName}</span>
<span className="text-[#222222] text-[18px] font-bold mr-[20px]">{actionCount}</span>
</div>
@@ -99,13 +100,11 @@ export const ActionColumn: React.FC<ActionColumnProps> = ({ title, category, sub
))
}
{
actionList.length === 0 && <Empty image={
<img
src="/icon/empty-list.png"
alt="empty"
className="w-[135px]"
/>
} />
actionList.length === 0 && (
<div className={`flex items-center justify-center py-[48px] text-sm ${isDark ? 'text-slate-500' : 'text-[#bfbfbf]'}`}>
暂无数据
</div>
)
}
</div>
</div>
@@ -236,9 +236,6 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
<div className="flex flex-col gap-4 xl:flex-row xl:items-end xl:justify-between">
<div className="space-y-3">
<div className="flex items-center gap-3">
<div className="flex h-11 w-11 items-center justify-center rounded-2xl border border-[rgba(96,165,250,0.28)] bg-[linear-gradient(180deg,rgba(22,119,255,0.28),rgba(14,63,135,0.18))] shadow-[0_10px_30px_rgba(14,63,135,0.24)]">
<Bot className="h-5 w-5 text-[#7CC9FF]" />
</div>
<div>
<h1 className={`text-[26px] font-semibold tracking-[0.02em] ${isDark ? 'text-slate-50' : 'text-[#222222]'}`}>
{title}
@@ -0,0 +1,130 @@
/**
* 行动 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,
}),
],
},
];
@@ -0,0 +1,30 @@
/**
* 行动 / 任务状态图标。
*
* 用 `import` 引入 SVG,让 Vite 把它们打进产物并生成带 hash 的最终 URL,
* 这样无论部署到子路径(base)还是带指纹的静态资源服务都能正确访问 ——
* 不能再用 `/icon/xxx.svg` 这种运行时绝对路径(部署后会 404)。
*
* key 沿用后端 / taskStatusList 里给的中文文件名,方便按名取用。
*/
import daiQianShou from "./待签收.svg";
import daiCEH from "./待CEH.svg";
import jinXingZhong from "./进行中.svg";
import wanCheng from "./完成.svg";
import weiTongGuo from "./未通过.svg";
import yiZhongZhi from "./已终止.svg";
export const statusIconMap: Record<string, string> = {
"待签收.svg": daiQianShou,
"待CEH.svg": daiCEH,
"进行中.svg": jinXingZhong,
"完成.svg": wanCheng,
"未通过.svg": weiTongGuo,
"已终止.svg": yiZhongZhi,
};
/** 按文件名取打包后的图标 URL;取不到返回 undefined(<img> 不渲染 broken icon)。 */
export function resolveStatusIcon(name?: string | null): string | undefined {
if (!name) return undefined;
return statusIconMap[name];
}
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="11" fill="#1eb07f"/>
<path d="M7.5 12.3 10.5 15.3 16.5 9" stroke="#fff" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 284 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="11" fill="#8c8c8c"/>
<rect x="8.7" y="8.7" width="6.6" height="6.6" rx="1.4" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 225 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="11" fill="#2f9acc"/>
<circle cx="11" cy="11" r="3.2" stroke="#fff" stroke-width="1.8" fill="none"/>
<path d="M13.4 13.4 16.2 16.2" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 325 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="11" fill="#0062d9"/>
<rect x="7.6" y="7.8" width="8.8" height="9.6" rx="1.6" stroke="#fff" stroke-width="1.5" fill="none"/>
<path d="M9.8 7.8v-.5a1 1 0 0 1 1-1h2.4a1 1 0 0 1 1 1v.5" stroke="#fff" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.8 11h4.4M9.8 13.6h2.8" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 507 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="11" fill="#cf1322"/>
<path d="M8.8 8.8 15.2 15.2M15.2 8.8 8.8 15.2" stroke="#fff" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 258 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="11" fill="#d97b00"/>
<path d="M10.2 8.4 15.4 12l-5.2 3.6z" fill="#fff" stroke="#fff" stroke-width="1.2" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 264 B