代码提交

This commit is contained in:
董宏凯 committed 2026-07-15 16:13:03 +08:00
1 parent 27e6b0b23e
commit d7ad0624b7
36 files changed
+2194 -287

No files matched your search

@@ -0,0 +1,85 @@
/**
* 按任务保存的研判情报报告详情。
*
* 后端 `/api/task-reports/by-task/{taskId}` 返回的外壳和原 Consumer
* `selectSqReport` 完全一致:`{ state, msg, data }`。其中 `contentJson`
* 保持为 JSON 字符串,由使用方按 `categoryType` 反序列化。
*/
import { apiFetch } from "@/core/api/fetch-client";
import { getBackendBaseURL } from "@/core/config";
export type TaskReportTaskId = string | number;
export interface TaskReportRecord {
id: string;
taskId: TaskReportTaskId;
createTime: string;
updateTime: string;
sessionId: string;
contentJson: string;
categoryType: string;
}
export interface TaskReportResponse {
state: string;
msg: string;
data: TaskReportRecord[];
}
/** 保存/修改时可省略由后端生成或维护的 id、创建/更新时间。 */
export type TaskReportRecordDraft = Omit<
TaskReportRecord,
"id" | "taskId" | "createTime" | "updateTime"
> &
Partial<Pick<TaskReportRecord, "id" | "taskId" | "createTime" | "updateTime">>;
const BASE = () => `${getBackendBaseURL()}/api/task-reports/by-task`;
function endpoint(taskId: TaskReportTaskId) {
return `${BASE()}/${encodeURIComponent(String(taskId))}`;
}
async function readResponse(res: Response, context: string): Promise<TaskReportResponse> {
const body = (await res.json().catch(() => null)) as TaskReportResponse | { detail?: unknown } | null;
if (!res.ok) {
const detail = body && typeof body === "object" && "detail" in body ? String(body.detail) : "";
throw new Error(detail || `${context} failed (${res.status})`);
}
if (!body || !Array.isArray((body as TaskReportResponse).data)) {
throw new Error(`${context} returned an invalid report payload`);
}
return body as TaskReportResponse;
}
/** 查询任务详情;尚未保存时后端返回 `{ state: "200", data: [] }`。 */
export async function getTaskReport(taskId: TaskReportTaskId): Promise<TaskReportResponse> {
const res = await apiFetch(endpoint(taskId), { method: "GET" });
return readResponse(res, "get task report");
}
/** 首次保存一个任务的完整 `data` 数组。任务已有详情时会返回 409。 */
export async function saveTaskReport(
taskId: TaskReportTaskId,
data: TaskReportRecordDraft[],
): Promise<TaskReportResponse> {
const res = await apiFetch(endpoint(taskId), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ data }),
});
return readResponse(res, "save task report");
}
/** 修改一个任务的完整 `data` 数组。保留同 id 项的原始 createTime。 */
export async function updateTaskReport(
taskId: TaskReportTaskId,
data: TaskReportRecordDraft[],
): Promise<TaskReportResponse> {
const res = await apiFetch(endpoint(taskId), {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ data }),
});
return readResponse(res, "update task report");
}
@@ -1,63 +1,67 @@
import sqReportMock from './sq-report-mock.json';
import sqReportMock from "./sq-report-mock.json";
/**
* 「研判情报报告」接口 `/cop/selectSqReport` 的假数据 —— 给「大屏绘制智能体」独立页面
* (深链 `?taskId=` 模式)在真实接口未就绪时本地预览。
* 研判情报报告的契约样例。
*
* 真实接口(GET `{VITE_AIPI_CONSUMER_URL}/cop/selectSqReport?taskId=`)返回结构:
* `{ state, msg, data: [{ id, taskId, createTime, updateTime, sessionId, contentJson, categoryType }] }`
* 其中 `contentJson` 是**被转义的 JSON 字符串**,`categoryType` 形如「敌情关键事件 JSON」。
* 假数据 `sq-report-mock.json` 即后端给出的样例响应(taskId=3332),原样保留 contentJson 字符串。
*
* 启停由环境变量 `VITE_MOCK_SQ_REPORT` 控制(见 `utils/request.ts` 请求拦截器):
* - `VITE_MOCK_SQ_REPORT=true`(默认,.env.development) → 走本地假数据;
* - `VITE_MOCK_SQ_REPORT=false`(.env.production / .env.network / .env.sit) → 走真实接口。
* 与 `VITE_MOCK_COP_TASK_DETAIL`(cop-task-detail)独立,机制完全一致。
* ⚠️ 真实接口联调通过后删除本 mock 及其引用。
* `sq-report-mock.json` 是外部系统提供的真实字段格式,保留在仓库中供接口联调、
* 手工保存测试和解析器测试使用。运行时不再将它注入请求;大屏与 rwfx 检查统一通过
* `/api/task-reports/by-task/{taskId}` 读取数据库保存的数据。
*/
/** 命中本 mock 的接口路径。 */
export const SQ_REPORT_ROUTE = '/cop/selectSqReport';
export interface SqReportMockItem {
id: string;
taskId: string | number;
createTime: string;
updateTime: string;
sessionId: string;
contentJson: string;
categoryType: string;
}
/** 判断某请求 url 是否是 selectSqReport。 */
export const SQ_REPORT_MOCK_RESPONSE = sqReportMock as {
state: string;
msg: string;
data: SqReportMockItem[];
};
/** 大屏 mock 命中的真实接口路径。 */
export const SQ_REPORT_ROUTE = "/cop/selectSqReport";
/** 供 axios 请求拦截器判断是否需要注入大屏样例数据。 */
export function matchSqReportMock(url?: string): boolean {
return !!url && url.includes(SQ_REPORT_ROUTE);
return Boolean(url?.includes(SQ_REPORT_ROUTE));
}
/** 从 axios config 取 taskId(优先 params,其次 url query)。 */
function readTaskIdFromConfig(config: { url?: string; params?: Record<string, unknown> }): string {
function readTaskId(config: { url?: string; params?: Record<string, unknown> }): string {
const fromParams = config.params?.taskId;
if (fromParams != null && fromParams !== '') return String(fromParams);
if (config.url) {
const m = config.url.match(/[?&]taskId=([^&]+)/);
if (m) return decodeURIComponent(m[1]);
}
return '';
if (fromParams != null && fromParams !== "") return String(fromParams);
const matched = config.url?.match(/[?&]taskId=([^&]+)/);
return matched ? decodeURIComponent(matched[1]) : "";
}
/**
* 模拟 selectSqReport 的 body:返回后端给的样例响应,并把 data 里每条的 taskId 改成
* 当前请求的 taskId(让任意 taskId 进来都能看到这份演示报告)。
* 以真实 selectSqReport 的响应结构返回样例数据;请求 taskId 会写回每条记录,
* 让任意深链(例如 demo1)都能加载同一套可解析的大屏数据。
*/
function buildSqReportBody(taskId: string) {
const tid = taskId || String((sqReportMock as { data?: Array<{ taskId?: unknown }> }).data?.[0]?.taskId ?? '');
const src = sqReportMock as { state: string; msg: string; data: Array<Record<string, unknown>> };
return {
state: src.state,
msg: src.msg,
data: (src.data ?? []).map((item) => ({ ...item, taskId: tid })),
};
}
/** 临时假数据 axios adapter:命中 selectSqReport 时直接返回 mock body,不发真实请求。 */
export function sqReportMockAdapter(config: {
url?: string;
params?: Record<string, unknown>;
}): Promise<{ data: unknown; status: number; statusText: string; headers: Record<string, unknown>; config: unknown }> {
const body = buildSqReportBody(readTaskIdFromConfig(config));
return new Promise((resolve) => {
setTimeout(() => {
resolve({ data: body, status: 200, statusText: 'OK', headers: {}, config });
}, 220);
}): Promise<{
data: typeof SQ_REPORT_MOCK_RESPONSE;
status: number;
statusText: string;
headers: Record<string, unknown>;
config: unknown;
}> {
const taskId = readTaskId(config);
return Promise.resolve({
data: {
...SQ_REPORT_MOCK_RESPONSE,
data: SQ_REPORT_MOCK_RESPONSE.data.map((item) => ({ ...item, taskId: taskId || item.taskId })),
},
status: 200,
statusText: "OK",
headers: {},
config,
});
}