Merge remote-tracking branch 'origin/master'

This commit is contained in:
MHW
2026-04-15 09:38:37 +08:00
6 changed files with 1654 additions and 14 deletions

View File

@@ -8,9 +8,10 @@
*/
import { HttpRequestClient } from '@/utils/request';
import type { Scenario, ScenarioDetailsResponse, ScenarioPageableResponse, ScenarioRequest } from './types';
import type { Scenario, ScenarioDetailsResponse, ScenarioPageableResponse, ScenarioRequest, CommunicationRelationsResponse } from './types';
import type { PlatformWithComponentsResponse } from '../types';
import type { BasicResponse } from '@/types';
import type { BehaviorTree } from '../designer/tree';
const req = HttpRequestClient.create<BasicResponse>({
baseURL: '/api',
@@ -32,6 +33,25 @@ export const findPlatformWithComponents = (id: number): Promise<PlatformWithComp
return req.get<PlatformWithComponentsResponse>(`/system/firerule/platforms/${id}`);
};
/**
* 获取场景的所有通信关系
* @param id 场景ID
* @returns 通信关系列表
*/
export const findRelations = (id: number): Promise<CommunicationRelationsResponse> => {
return req.get<CommunicationRelationsResponse>(`/system/scene/getAllRelation/${id}`);
};
export const saveScenario = (scenario: Scenario): Promise<BasicResponse> => {
return req.postJson<BasicResponse>(`/system/scene/saveSceneConfig`,scenario);
};
// 获取场景下的所有行为树列表
export const getAllBehaviorTreesBySceneId = (sceneId: number): Promise<{ code: number; msg: string; data: BehaviorTree[] }> => {
return req.get<{ code: number; msg: string; data: BehaviorTree[] }>(`/system/scene/getAllTree/${sceneId}`);
};
// 更新行为树(挂载到平台)
export const updateBehaviorTree = (behaviorTree: BehaviorTree): Promise<BasicResponse> => {
return req.putJson<BasicResponse>(`/system/behaviortree`, behaviorTree);
};

View File

@@ -21,6 +21,14 @@
<div class="ks-model-builder-content" style="width: calc(100% - 250px);">
<div class="ks-model-builder-actions">
<a-space>
<a-button v-if="graph && currentScenario" class="ks-model-builder-save" style="width: auto;" size="small" @click="handleGenerateRandom">
<ThunderboltOutlined />
<span>随机生成</span>
</a-button>
<a-button v-if="graph && currentScenario && currentScenario.id > 0" class="ks-model-builder-save" style="width: auto;" size="small" @click="handleLoadFromBackend">
<DatabaseOutlined />
<span>从后端加载</span>
</a-button>
<a-button v-if="graph && currentScenario" class="ks-model-builder-save" size="small" @click="handleSave">
<CheckOutlined />
<span>保存</span>
@@ -50,22 +58,24 @@ import { useRoute, useRouter } from 'vue-router';
import { message } from 'ant-design-vue';
import { getTeleport } from '@antv/x6-vue-shape';
import { Graph, Node, type NodeProperties } from '@antv/x6';
import { CheckCircleOutlined, CheckOutlined, RollbackOutlined, SaveOutlined } from '@ant-design/icons-vue';
import { CheckCircleOutlined, CheckOutlined, DatabaseOutlined, RollbackOutlined, SaveOutlined, ThunderboltOutlined } from '@ant-design/icons-vue';
import { Wrapper } from '@/components/wrapper';
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
import Header from '../header.vue';
import type { Scenario } from './types';
import type { PlatformWithComponents } from '../types';
import { createLineOptions, type GraphContainer, type GraphTaskElement, resolveGraph, useGraphCanvas } from '../graph';
import { createLineOptions, type GraphContainer, type GraphEdgeElement, type GraphTaskElement, resolveGraph, useGraphCanvas } from '../graph';
import { registerScenarioElement } from './register';
import { createGraphScenarioElement, createGraphTaskElementFromScenario } from './utils';
import PlatformCard from './platform-card.vue';
import NodesCard from './nodes-card.vue';
import { findOneScenarioById, saveScenario } from './api';
import { findOneScenarioById, saveScenario, findRelations, getAllBehaviorTreesBySceneId } from './api';
import { resolveConnectionRelation } from './relation';
import { generateRandomCommunicationData } from './random-data-generator';
import { convertRecordsToGraphContainer, type CommunicationRecord } from './data-converter';
const TeleportContainer = defineComponent(getTeleport());
@@ -81,6 +91,8 @@ export default defineComponent({
CheckCircleOutlined,
CheckOutlined,
RollbackOutlined,
ThunderboltOutlined,
DatabaseOutlined,
TeleportContainer,
},
setup() {
@@ -211,25 +223,95 @@ export default defineComponent({
}
};
const handleSelect = (scenario: Scenario) => {
const handleSelect = async (scenario: Scenario) => {
let nodeGraph: GraphContainer | null = null;
try {
nodeGraph = JSON.parse(scenario.communicationGraph as unknown as string) as unknown as GraphContainer;
} catch (e: any) {
console.error('parse error,cause:', e);
}
if (!nodeGraph) {
nodeGraph = {
nodes: [],
edges: [],
};
}
// 设置当前场景
currentScenario.value = {
...scenario,
graph: nodeGraph,
graph: nodeGraph || { nodes: [], edges: [] },
relations: []
};
console.log('选中场景:', currentScenario.value);
currentScenarioEditing.value = true;
// 并行加载通信关系和行为树列表
if (scenario.id > 0) {
try {
// 1. 加载通信关系(如果没有已保存的图数据)
if (!nodeGraph) {
message.loading({ content: '正在加载通信关系...', key: 'loading-relations' });
const response = await findRelations(scenario.id);
console.log('API完整响应:', response);
// 解析API响应支持多种格式
let relations: any[] = [];
if (Array.isArray(response.data)) {
relations = response.data;
} else if (response.data && Array.isArray((response.data as any).data)) {
relations = (response.data as any).data;
} else if (response.data && Array.isArray((response.data as any).rows)) {
relations = (response.data as any).rows;
} else if (response.data && Array.isArray((response.data as any).list)) {
relations = (response.data as any).list;
}
console.log('解析后的通信关系数量:', relations.length);
if (relations.length > 0) {
// 字段名标准化(驼峰转下划线)
const normalizedRelations = relations.map((item: any) => ({
id: item.id,
command_platform: item.commandPlatform || item.command_platform,
subordinate_platform: item.subordinatePlatform || item.subordinate_platform,
command_comm: item.commandComm || item.command_comm,
subordinate_comm: item.subordinateComm || item.subordinate_comm,
scenary_id: item.scenaryId || item.scenary_id,
}));
console.log('标准化后的第一条记录:', normalizedRelations[0]);
// 转换为图数据
const convertedGraph = convertRecordsToGraphContainer(normalizedRelations);
console.log('转换后的图数据:', convertedGraph);
// 更新当前场景的图数据
currentScenario.value.graph = convertedGraph;
currentScenario.value.communicationGraph = JSON.stringify(convertedGraph);
message.success({ content: `成功加载 ${normalizedRelations.length} 条通信关系`, key: 'loading-relations' });
} else {
message.warning({ content: '该场景暂无通信关系数据', key: 'loading-relations' });
}
}
// 2. 加载行为树列表并缓存到graph对象
const treesResponse = await getAllBehaviorTreesBySceneId(scenario.id);
if (treesResponse.code === 200 && treesResponse.data) {
console.log('[communication] 行为树列表加载完成:', treesResponse.data.length, '个');
// 将行为树列表存储到graph对象中供node.vue使用
if (graph.value) {
(graph.value as any).behaviorTrees = treesResponse.data;
}
} else {
console.warn('[communication] 行为树列表加载失败或为空');
if (graph.value) {
(graph.value as any).behaviorTrees = [];
}
}
} catch (error) {
console.error('从后端加载数据失败:', error);
message.error({ content: '加载数据失败', key: 'loading-relations' });
}
}
createElements();
};
@@ -261,6 +343,7 @@ export default defineComponent({
}, 100); // 延迟一会儿,免得连线错位
}
}
}, 100);
});
};
@@ -277,6 +360,12 @@ export default defineComponent({
nodes: [],
},
};
// 清空graph中的场景信息
if (graph.value) {
(graph.value as any).currentScenario = null;
}
currentGraph.value = {
edges: [],
nodes: [],
@@ -415,6 +504,193 @@ export default defineComponent({
});
};
// 随机生成节点流图
const handleGenerateRandom = () => {
if (!graph.value) {
message.error('画布未初始化');
return;
}
try {
// 生成随机数据
const { records, graph: randomGraph } = generateRandomCommunicationData(30);
console.log('生成的随机数据:', records);
console.log('转换后的图数据:', randomGraph);
// 清空现有内容
graph.value.clearCells();
// 设置当前场景
if (!currentScenario.value) {
currentScenario.value = {
id: 0,
name: `随机场景_${Date.now()}`,
description: '自动生成的测试场景',
communicationGraph: null,
relations: [],
graph: randomGraph,
};
} else {
currentScenario.value.graph = randomGraph;
currentScenario.value.communicationGraph = JSON.stringify(randomGraph);
}
// 渲染节点
setTimeout(() => {
if (randomGraph.nodes) {
randomGraph.nodes.forEach(ele => {
const node = createGraphScenarioElement(ele as GraphTaskElement);
graph.value?.addNode(node as Node);
});
}
// 延迟添加边,确保节点已渲染
setTimeout(() => {
if (randomGraph.edges) {
randomGraph.edges.forEach(edgeData => {
graph.value?.addEdge({
...edgeData,
...createLineOptions(),
});
});
}
// 自动适应视图
fitToScreen();
message.success(`已生成 ${randomGraph.nodes.length} 个节点和 ${randomGraph.edges.length} 条连接线`);
}, 100);
}, 50);
} catch (error) {
console.error('随机生成时出错:', error);
message.error('生成失败,请重试');
}
};
// 从后端加载平台数据并转换为通信关系图(当前使用模拟数据)
const handleLoadFromBackend = async () => {
if (!graph.value || !currentScenario.value) {
message.error('请先选择场景');
return;
}
try {
message.loading({ content: '正在加载通信关系数据...', key: 'loading' });
// 调用真实API获取通信关系
console.log(`正在从后端加载场景 ${currentScenario.value.id} 的通信关系...`);
const response = await findRelations(currentScenario.value.id);
console.log('API完整响应:', response);
console.log('response.data类型:', typeof response.data, Array.isArray(response.data) ? 'Array' : 'Object');
// API返回的是 CommunicationRelationRecord[],与 CommunicationRecord 结构兼容
// 处理可能的多种返回格式
let relations: any[] = [];
if (Array.isArray(response.data)) {
relations = response.data;
} else if (response.data && Array.isArray((response.data as any).data)) {
relations = (response.data as any).data;
} else if (response.data && Array.isArray((response.data as any).rows)) {
relations = (response.data as any).rows;
} else if (response.data && Array.isArray((response.data as any).list)) {
relations = (response.data as any).list;
}
console.log('解析后的通信关系数量:', relations.length);
if (relations.length > 0) {
console.log('第一条记录:', JSON.stringify(relations[0], null, 2));
}
// 后端返回的是驼峰命名,需要转换为下划线命名以匹配前端类型
const normalizedRelations = relations.map((item: any) => ({
id: item.id,
command_platform: item.commandPlatform || item.command_platform,
subordinate_platform: item.subordinatePlatform || item.subordinate_platform,
command_comm: item.commandComm || item.command_comm,
subordinate_comm: item.subordinateComm || item.subordinate_comm,
scenary_id: item.scenaryId || item.scenary_id,
}));
console.log('标准化后的第一条记录:', normalizedRelations[0]);
if (normalizedRelations.length === 0) {
console.warn('API未返回任何通信关系数据使用模拟数据作为fallback');
// Fallback到模拟数据保留以便测试
relations.push(
{ id: 6, command_platform: 'chief', subordinate_platform: 'task1_commander', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 7, command_platform: 'chief', subordinate_platform: 'task2_commander', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 8, command_platform: 'chief', subordinate_platform: 'task3_commander', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 9, command_platform: 'task1_commander', subordinate_platform: 'platform1', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 10, command_platform: 'task1_commander', subordinate_platform: 'platform3', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 11, command_platform: 'task1_commander', subordinate_platform: 'platform4', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 12, command_platform: 'task1_commander', subordinate_platform: 'platform5', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 13, command_platform: 'task1_commander', subordinate_platform: 'platform6', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 14, command_platform: 'task2_commander', subordinate_platform: 'platform3', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 15, command_platform: 'task2_commander', subordinate_platform: 'platform5', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 16, command_platform: 'task2_commander', subordinate_platform: 'platform4', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 17, command_platform: 'task3_commander', subordinate_platform: 'platform6', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 18, command_platform: 'task3_commander', subordinate_platform: 'platform6', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 19, command_platform: 'task3_commander', subordinate_platform: 'platform7', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 20, command_platform: 'task3_commander', subordinate_platform: 'platform8', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
);
}
console.log('最终使用的通信记录:', normalizedRelations);
// 使用数据进行转换
const convertedGraph = convertRecordsToGraphContainer(normalizedRelations);
console.log('转换后的图数据:', convertedGraph);
// 清空现有内容
graph.value.clearCells();
// 更新当前场景
currentScenario.value.graph = convertedGraph;
currentScenario.value.communicationGraph = JSON.stringify(convertedGraph);
// 渲染节点
setTimeout(() => {
if (convertedGraph.nodes) {
convertedGraph.nodes.forEach(ele => {
const node = createGraphScenarioElement(ele as GraphTaskElement);
graph.value?.addNode(node as Node);
});
}
// 延迟添加边,确保节点已渲染
setTimeout(() => {
if (convertedGraph.edges) {
convertedGraph.edges.forEach(edgeData => {
graph.value?.addEdge({
...edgeData,
...createLineOptions(),
});
});
}
// 自动适应视图
fitToScreen();
message.success({
content: `已从后端加载 ${convertedGraph.nodes.length} 个平台和 ${convertedGraph.edges.length} 条连接关系`,
key: 'loading'
});
}, 100);
}, 50);
} catch (error) {
console.error('从后端加载时出错:', error);
message.error({
content: error instanceof Error ? error.message : '加载失败,请重试',
key: 'loading'
});
}
};
// 初始化
onMounted(() => {
init();
@@ -444,6 +720,8 @@ export default defineComponent({
handleDrop,
isDraggingOver,
handleSave,
handleGenerateRandom,
handleLoadFromBackend,
handleUpdateElement,
handleSelect,
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,9 @@
<template>
<a-dropdown :trigger="['contextmenu']" @openChange="handleVisibleChange">
<a-dropdown
:trigger="['contextmenu']"
:getPopupContainer="getPopupContainer"
@openChange="handleVisibleChange"
>
<a-card
:class="[
'ks-scenario-node',
@@ -55,6 +59,27 @@
<template #overlay>
<a-menu @click="handleMenuClick">
<a-sub-menu key="mount">
<template #icon>
<LinkOutlined />
</template>
<template #title>挂载</template>
<a-menu-item
v-for="tree in availableTrees"
:key="`tree-${tree.id}`"
:disabled="isTreeMounted(tree.id)"
@click="() => handleMountTree(tree)"
>
<template #icon>
<CheckOutlined v-if="isTreeMounted(tree.id)" />
</template>
{{ tree.name }}
</a-menu-item>
<a-menu-item v-if="availableTrees.length === 0" disabled>
暂无可用行为树
</a-menu-item>
</a-sub-menu>
<a-menu-divider />
<a-menu-item key="delete">
<template #icon>
<DeleteOutlined />
@@ -70,15 +95,20 @@
import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
import { elementProps, type ModelElement } from '../graph';
import { DeleteOutlined, SettingOutlined } from '@ant-design/icons-vue';
import { DeleteOutlined, LinkOutlined, CheckOutlined, SettingOutlined } from '@ant-design/icons-vue';
import type { Graph } from '@antv/x6';
import { message } from 'ant-design-vue';
import { substring } from '@/utils/strings';
import { getAllBehaviorTreesBySceneId, updateBehaviorTree } from './api';
import type { BehaviorTree } from '../designer/tree';
export default defineComponent({
name: 'ModelElement',
components: {
SettingOutlined,
DeleteOutlined,
LinkOutlined,
CheckOutlined,
},
props: elementProps,
setup(_props) {
@@ -87,6 +117,17 @@ export default defineComponent({
);
const updateKey = ref(0);
const isMenuVisible = ref(false);
// 挂载行为树相关状态
const availableTrees = ref<BehaviorTree[]>([]);
// 获取 popup 容器
const getPopupContainer = () => {
if (typeof document !== 'undefined') {
return document.body;
}
return undefined;
};
// 获取画布实例
const getGraph = (): Graph | null => {
@@ -103,8 +144,35 @@ export default defineComponent({
updateKey.value++;
};
// 获取行为树名称
const getBehaviorTreeName = (treeId: number | undefined | null): string => {
if (!treeId) return '';
const tree = availableTrees.value.find(t => t.id === treeId);
return tree?.name || `行为树${treeId}`;
};
// 判断行为树是否已挂载到当前节点
const isTreeMounted = (treeId: number): boolean => {
if (!element.value) return false;
const currentTreeId = (element.value as any).behaviorTreeId as number | undefined;
return currentTreeId === treeId;
};
// 处理挂载行为树 - 当右键菜单打开时从graph中读取已缓存的行为树列表
const handleVisibleChange = (visible: boolean) => {
isMenuVisible.value = visible;
if (!visible || !element.value) return;
// 从graph对象中获取已缓存的行为树列表
const graph = _props.graph as any;
if (graph?.behaviorTrees) {
availableTrees.value = graph.behaviorTrees;
console.log('从缓存中读取行为树列表:', availableTrees.value.length, '个');
} else {
availableTrees.value = [];
console.warn('未找到缓存的行为树列表');
}
};
const handleMenuClick = ({ key }: { key: string }) => {
@@ -113,6 +181,37 @@ export default defineComponent({
}
};
// 处理挂载具体的行为树
const handleMountTree = async (tree: BehaviorTree) => {
if (!element.value) return;
try {
// 更新节点的behaviorTreeId属性
const updatedElement = { ...(element.value as any), behaviorTreeId: tree.id };
// 调用后端API更新行为树将platformId关联到该平台
const platformIdValue = (element.value as any).platformId as number | undefined;
const treeToUpdate = {
...tree,
platformId: platformIdValue ?? null
};
const updateResponse = await updateBehaviorTree(treeToUpdate);
if (updateResponse.code === 200) {
// 更新本地节点数据
if (_props.node) {
_props.node.setData(updatedElement);
}
message.success(`已成功挂载行为树: ${tree.name}`);
} else {
message.error(updateResponse.msg || '挂载失败');
}
} catch (error) {
console.error('挂载行为树失败:', error);
message.error('挂载行为树失败');
}
};
const handleDelete = () => {
if (!_props.node) return;
@@ -134,10 +233,37 @@ export default defineComponent({
onMounted(() => {
_props.node?.on('change:data', handleDataChange);
// 监听画布各种事件,操作时立即关闭菜单
const graph = getGraph();
if (graph) {
const closeMenuHandler = () => {
if (isMenuVisible.value) {
isMenuVisible.value = false;
}
};
// 监听多种可能导致菜单位置变化的事件
graph.on('pan', closeMenuHandler);
graph.on('translate', closeMenuHandler);
graph.on('scale', closeMenuHandler);
graph.on('zoom', closeMenuHandler);
graph.on('resize', closeMenuHandler);
}
});
onUnmounted(() => {
_props.node?.off('change:data', handleDataChange);
// 清理事件监听
const graph = getGraph();
if (graph) {
graph.off('pan');
graph.off('translate');
graph.off('scale');
graph.off('zoom');
graph.off('resize');
}
});
return {
@@ -145,6 +271,11 @@ export default defineComponent({
substring,
handleMenuClick,
handleVisibleChange,
availableTrees,
getBehaviorTreeName,
isTreeMounted,
handleMountTree,
getPopupContainer,
};
},
});

View File

@@ -0,0 +1,143 @@
/*
* This file is part of the kernelstudio package.
*
* (c) 2014-2026 zlin <admin@kernelstudio.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
import type { GraphContainer } from '../graph';
import { convertRecordsToGraphContainer, type CommunicationRecord } from './data-converter';
/**
* 生成随机的通信关系数据用于测试
* @param nodeCount 节点数量默认5-8个
* @param edgeDensity 边的密度0.3-0.7之间,表示连接概率)
* @returns 随机生成的通信关系记录和对应的GraphContainer
*/
export const generateRandomCommunicationData = (
nodeCount: number = Math.floor(Math.random() * 4) + 5, // 5-8个节点
edgeDensity: number = 0.5
): { records: CommunicationRecord[]; graph: GraphContainer } => {
// 生成随机平台名称
const platformTypes = ['指挥中心', '雷达站', '导弹阵地', '预警机', '战斗机', '驱逐舰', '潜艇', '电子战飞机'];
const platforms: string[] = [];
for (let i = 0; i < nodeCount; i++) {
const baseName = platformTypes[i % platformTypes.length];
const suffix = Math.floor(i / platformTypes.length) > 0 ? `-${Math.floor(i / platformTypes.length) + 1}` : '';
platforms.push(`${baseName}${suffix}`);
}
// 生成随机通信关系 - 改进版:更符合实际指挥层级
const records: CommunicationRecord[] = [];
let recordId = 1;
// 确定根节点(指挥中心)
const rootPlatformName = platforms.find(p => p.includes('指挥')) || platforms[0] || '默认平台';
const rootIndex = platforms.indexOf(rootPlatformName);
if (rootIndex === -1) {
console.warn('未找到根节点,使用第一个平台');
return { records: [], graph: { nodes: [], edges: [] } };
}
// 第一层:指挥中心直接管理的单位(通常是主要作战单元)
// 选择2-4个作为一级下属
const firstLevelCount = Math.min(Math.max(2, Math.floor(platforms.length / 2)), 4);
const firstLevelIndices: number[] = [];
for (let i = 0; i < platforms.length && firstLevelIndices.length < firstLevelCount; i++) {
if (i !== rootIndex) {
firstLevelIndices.push(i);
}
}
// 建立第一层连接:指挥中心 -> 一级下属
firstLevelIndices.forEach(idx => {
const subordinatePlatform = platforms[idx];
if (subordinatePlatform) {
records.push({
id: recordId++,
command_platform: rootPlatformName,
subordinate_platform: subordinatePlatform,
command_comm: '加密指挥链路',
subordinate_comm: '接收端',
scenary_id: 1,
});
}
});
// 第二层:一级下属可以有二级下属
const remainingIndices = platforms.map((_, i) => i).filter(i =>
i !== rootIndex && !firstLevelIndices.includes(i)
);
remainingIndices.forEach(idx => {
// 随机选择一个一级下属作为父节点
const parentIdx = firstLevelIndices[Math.floor(Math.random() * firstLevelIndices.length)];
if (parentIdx !== undefined) {
const parentPlatform = platforms[parentIdx];
const childPlatform = platforms[idx];
if (parentPlatform && childPlatform) {
records.push({
id: recordId++,
command_platform: parentPlatform,
subordinate_platform: childPlatform,
command_comm: Math.random() > 0.5 ? '战术数据链' : '无线通信',
subordinate_comm: '双向通信',
scenary_id: 1,
});
}
}
});
// 第三层添加少量横向协同连接不超过总边数的20%
const maxCrossLinks = Math.floor(records.length * 0.2);
let crossLinkCount = 0;
if (platforms.length > 4 && crossLinkCount < maxCrossLinks) {
// 在同级之间添加协同连接
for (let i = 0; i < firstLevelIndices.length - 1 && crossLinkCount < maxCrossLinks; i++) {
if (Math.random() < 0.4) { // 40%概率
const j = i + 1 + Math.floor(Math.random() * 2);
if (j < firstLevelIndices.length) {
const idxI = firstLevelIndices[i];
const idxJ = firstLevelIndices[j];
if (idxI !== undefined && idxJ !== undefined) {
const platformI = platforms[idxI];
const platformJ = platforms[idxJ];
if (platformI && platformJ) {
const exists = records.some(r =>
r.command_platform === platformI &&
r.subordinate_platform === platformJ
);
if (!exists) {
records.push({
id: recordId++,
command_platform: platformI,
subordinate_platform: platformJ,
command_comm: '协同通信',
subordinate_comm: '双向通信',
scenary_id: 1,
});
crossLinkCount++;
}
}
}
}
}
}
}
// 转换为GraphContainer
const graph = convertRecordsToGraphContainer(records);
return { records, graph };
};

View File

@@ -48,3 +48,22 @@ export interface ScenarioDetailsResponse extends ApiDataResponse<Scenario> {
}
/**
* 通信关系记录(对应数据库表结构)
*/
export interface CommunicationRelationRecord {
id: number;
command_platform: string; // 指挥平台名称
subordinate_platform: string; // 下属平台名称
command_comm?: string; // 指挥端通信方式
subordinate_comm?: string; // 下属端通信方式
scenary_id?: number; // 场景ID
}
/**
* 获取场景所有通信关系的响应
*/
export interface CommunicationRelationsResponse extends ApiDataResponse<CommunicationRelationRecord[]> {
}