UPDATE: VERSION-20260316
This commit is contained in:
@@ -63,6 +63,7 @@ import { createGraphScenarioElement, createGraphTaskElementFromScenario } from '
|
||||
import PlatformCard from './platform-card.vue';
|
||||
import NodesCard from './nodes-card.vue';
|
||||
import { saveScenario } from './api';
|
||||
import {resolveConnectionRelation} from './relation'
|
||||
|
||||
const TeleportContainer = defineComponent(getTeleport());
|
||||
|
||||
@@ -194,7 +195,6 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
const handleSelect = (scenario: Scenario) => {
|
||||
console.info('handleSelect', scenario);
|
||||
let nodeGraph: GraphContainer | null = null;
|
||||
try {
|
||||
nodeGraph = JSON.parse(scenario.communicationGraph as unknown as string) as unknown as GraphContainer;
|
||||
@@ -210,10 +210,10 @@ export default defineComponent({
|
||||
currentScenario.value = {
|
||||
...scenario,
|
||||
graph: nodeGraph,
|
||||
relations: []
|
||||
};
|
||||
currentScenarioEditing.value = true;
|
||||
createElements();
|
||||
|
||||
};
|
||||
|
||||
const createElements = () => {
|
||||
@@ -228,7 +228,6 @@ export default defineComponent({
|
||||
if (currentScenario.value?.graph.nodes) {
|
||||
currentScenario.value?.graph.nodes.forEach(ele => {
|
||||
const node = createGraphScenarioElement(ele as GraphTaskElement);
|
||||
console.info('create node: ', ele);
|
||||
// 将节点添加到画布
|
||||
graph.value?.addNode(node as Node);
|
||||
});
|
||||
@@ -255,10 +254,11 @@ export default defineComponent({
|
||||
name: null,
|
||||
description: null,
|
||||
communicationGraph: null,
|
||||
relations: [],
|
||||
graph: {
|
||||
edges: [],
|
||||
nodes: [],
|
||||
}
|
||||
},
|
||||
};
|
||||
currentGraph.value = {
|
||||
edges: [],
|
||||
@@ -341,6 +341,10 @@ export default defineComponent({
|
||||
|
||||
const handleSave = () => {
|
||||
const graphData: GraphContainer = resolveGraph(graph.value as Graph);
|
||||
|
||||
const relations = resolveConnectionRelation(graph.value as Graph);
|
||||
console.error('relations',relations)
|
||||
|
||||
console.info('handleSave', graphData);
|
||||
if (!currentScenario.value) {
|
||||
message.error('当前决策树不存在');
|
||||
@@ -350,6 +354,7 @@ export default defineComponent({
|
||||
...currentScenario.value,
|
||||
graph: graphData,
|
||||
communicationGraph: JSON.stringify(graphData),
|
||||
relations: relations
|
||||
};
|
||||
if (!newScenario.name) {
|
||||
message.error('场景名称不能为空.');
|
||||
|
||||
@@ -23,9 +23,11 @@
|
||||
>
|
||||
<div
|
||||
:data-port="`in-${item.id || index}`"
|
||||
:port="`in-${item.id || index}`"
|
||||
:title="`入桩: ${item.name}`"
|
||||
class="port port-in"
|
||||
magnet="passive"
|
||||
:data-item="JSON.stringify(item)"
|
||||
>
|
||||
<div class="triangle-left"></div>
|
||||
</div>
|
||||
@@ -38,9 +40,11 @@
|
||||
<!-- 右侧出桩:只能作为连线源 -->
|
||||
<div
|
||||
:data-port="`out-${item.id || index}`"
|
||||
:port="`out-${item.id || index}`"
|
||||
:title="`出桩: ${item.name}`"
|
||||
class="port port-out"
|
||||
magnet="active"
|
||||
:data-item="JSON.stringify(item)"
|
||||
>
|
||||
<div class="triangle-right" ></div>
|
||||
</div>
|
||||
@@ -130,7 +134,6 @@ export default defineComponent({
|
||||
|
||||
onMounted(() => {
|
||||
_props.node?.on('change:data', handleDataChange);
|
||||
console.error('element',element.value)
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, type PropType, ref } from 'vue';
|
||||
import { defineComponent, onMounted, type PropType, ref, watch } from 'vue';
|
||||
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
|
||||
import {findPlatformWithComponents} from './api'
|
||||
import { type PlatformWithComponents, type Scenario } from './types';
|
||||
@@ -40,21 +40,14 @@ export default defineComponent({
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
setup(_props, { emit }) {
|
||||
setup(props, { emit }) {
|
||||
|
||||
const activeKey = ref<number>(1);
|
||||
const templateData = ref<PlatformWithComponents[]>([]);
|
||||
const isDraggingOver = ref<boolean>(false);
|
||||
const draggedNodeData = ref<PlatformWithComponents | null>(null);
|
||||
const currentScenario = ref<Scenario|null>(_props.scenario)
|
||||
|
||||
const loadTress = () => {
|
||||
templateData.value = []
|
||||
findPlatformWithComponents(_props.scenario?.id).then(r => {
|
||||
templateData.value = r.data ?? []
|
||||
});
|
||||
};
|
||||
|
||||
const currentScenario = ref<Scenario|null>(props.scenario)
|
||||
|
||||
const handleDragStart = (e: DragEvent, nm: PlatformWithComponents) => {
|
||||
let dragNode: PlatformWithComponents = { ...nm };
|
||||
draggedNodeData.value = dragNode as PlatformWithComponents;
|
||||
@@ -92,16 +85,17 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
|
||||
const load = ()=> {
|
||||
findPlatformWithComponents(1).then(re=> {
|
||||
console.error(re);
|
||||
const load = (id: number)=> {
|
||||
findPlatformWithComponents(id).then(re=> {
|
||||
templateData.value = re.data ?? []
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadTress();
|
||||
load();
|
||||
});
|
||||
watch(()=> props.scenario,(n: Scenario|null|undefined )=> {
|
||||
if(n){
|
||||
load(n.id);
|
||||
}
|
||||
}, {deep: true, immediate: true} )
|
||||
|
||||
return {
|
||||
activeKey,
|
||||
|
||||
@@ -79,6 +79,7 @@ export default defineComponent({
|
||||
findScenarioByQuery(scenarioQuery.value).then(r => {
|
||||
scenario.value = r.rows;
|
||||
totalTress.value = r.total ?? 0;
|
||||
emit('select', r.rows[0]);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
134
modeler/src/views/decision/communication/relation.ts
Normal file
134
modeler/src/views/decision/communication/relation.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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 { Graph, Node,Edge } from '@antv/x6';
|
||||
import type { Platform, PlatformComponent, PlatformRelation } from './types';
|
||||
import type { GraphTaskElement } from '../graph';
|
||||
|
||||
/**
|
||||
* 解析节点端口连接关系(去重版)
|
||||
* @param graph X6 画布实例
|
||||
* @returns 去重后的端口连接关系列表
|
||||
*/
|
||||
export function resolveConnectionRelation(graph: Graph): PlatformRelation[] {
|
||||
const edges: Edge[] = graph.getEdges();
|
||||
const items: PlatformRelation[] = [];
|
||||
const existsKeys: Set<string> = new Set(); // 改用 Set 提升查询性能
|
||||
const tempEdgeIds: Set<string> = new Set(); // 存储临时边 ID
|
||||
|
||||
// 过滤无效/临时边
|
||||
const validEdges = edges.filter(edge => {
|
||||
// 过滤临时边(X6 拖拽连线时生成的未完成边)
|
||||
const isTempEdge = edge?.attr('line/stroke') === 'transparent' || edge.id.includes('temp');
|
||||
if (isTempEdge) {
|
||||
tempEdgeIds.add(edge.id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 过滤未正确关联节点的边
|
||||
const sourceCell = edge.getSourceCell();
|
||||
const targetCell = edge.getTargetCell();
|
||||
if (!sourceCell || !targetCell || !(sourceCell instanceof Node) || !(targetCell instanceof Node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 过滤端口 ID 为空的边
|
||||
const sourcePortId = edge.getSourcePortId();
|
||||
const targetPortId = edge.getTargetPortId();
|
||||
if (!sourcePortId || !targetPortId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
validEdges.forEach(edge => {
|
||||
try {
|
||||
const sourceCell = edge.getSourceCell() as Node;
|
||||
const targetCell = edge.getTargetCell() as Node;
|
||||
|
||||
const sourcePortId = edge.getSourcePortId()!;
|
||||
const targetPortId = edge.getTargetPortId()!;
|
||||
|
||||
// 1. 获取端口 DOM 元素和数据
|
||||
const sourceView = graph.findViewByCell(sourceCell);
|
||||
const targetView = graph.findViewByCell(targetCell);
|
||||
if (!sourceView || !targetView) return;
|
||||
|
||||
const sourcePortEl = sourceView.container.querySelector(`[data-port="${sourcePortId}"]`);
|
||||
const targetPortEl = targetView.container.querySelector(`[data-port="${targetPortId}"]`);
|
||||
if (!sourcePortEl || !targetPortEl) return;
|
||||
|
||||
// 2. 解析端口数据
|
||||
let sourcePortData: PlatformComponent | null = null;
|
||||
let targetPortData: PlatformComponent | null = null;
|
||||
try {
|
||||
const sourceDataAttr = sourcePortEl.getAttribute('data-item');
|
||||
sourcePortData = sourceDataAttr ? JSON.parse(sourceDataAttr) : null;
|
||||
} catch (e) {
|
||||
console.warn(`解析源节点 ${sourceCell.id} 端口 ${sourcePortId} 数据失败`, e);
|
||||
return; // 数据解析失败直接跳过
|
||||
}
|
||||
try {
|
||||
const targetDataAttr = targetPortEl.getAttribute('data-item');
|
||||
targetPortData = targetDataAttr ? JSON.parse(targetDataAttr) : null;
|
||||
} catch (e) {
|
||||
console.warn(`解析目标节点 ${targetCell.id} 端口 ${targetPortId} 数据失败`, e);
|
||||
return; // 数据解析失败直接跳过
|
||||
}
|
||||
|
||||
// 过滤端口数据为空的情况
|
||||
if (!sourcePortData || !targetPortData) return;
|
||||
|
||||
// 解析节点平台数据
|
||||
const sourceData = sourceCell.getData() as GraphTaskElement;
|
||||
const targetData = targetCell.getData() as GraphTaskElement;
|
||||
|
||||
// 过滤平台数据不完整的节点
|
||||
if (!sourceData.platformId || !targetData.platformId) return;
|
||||
|
||||
const sourcePlatform: Platform = {
|
||||
id: sourceData.platformId as number,
|
||||
key: sourceData.key,
|
||||
name: sourceData.name,
|
||||
description: sourceData.description,
|
||||
scenarioId: sourceData.scenarioId as number,
|
||||
};
|
||||
|
||||
const targetPlatform: Platform = {
|
||||
id: targetData.platformId as number,
|
||||
key: targetData.key,
|
||||
name: targetData.name,
|
||||
description: targetData.description,
|
||||
scenarioId: targetData.scenarioId as number,
|
||||
};
|
||||
|
||||
// 生成唯一标识(支持单向/双向去重
|
||||
const uniqueKey = `${sourceCell.id}@${sourcePortId}->${targetCell.id}@${targetPortId}`;
|
||||
|
||||
if (!existsKeys.has(uniqueKey)) {
|
||||
existsKeys.add(uniqueKey);
|
||||
items.push({
|
||||
sourceId: sourceCell.id,
|
||||
sourcePort: sourcePortId,
|
||||
sourcePlatform: sourcePlatform,
|
||||
sourceComponent: sourcePortData,
|
||||
targetId: targetCell.id,
|
||||
targetPort: targetPortId,
|
||||
targetPlatform: targetPlatform,
|
||||
targetComponent: targetPortData,
|
||||
edgeId: edge.id,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`解析边 ${edge.id} 连接关系失败`, error);
|
||||
}
|
||||
});
|
||||
return items;
|
||||
}
|
||||
@@ -11,6 +11,19 @@
|
||||
import type { ApiDataResponse, NullableString, PageableResponse } from '@/types';
|
||||
import type { GraphContainer } from '../graph';
|
||||
|
||||
export interface PlatformRelation {
|
||||
sourceId: NullableString,
|
||||
sourcePort: NullableString,
|
||||
sourcePlatform: Platform,
|
||||
sourceComponent: PlatformComponent,
|
||||
targetId: NullableString,
|
||||
targetPort: NullableString,
|
||||
targetPlatform: Platform,
|
||||
targetComponent: PlatformComponent,
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
|
||||
export interface Scenario {
|
||||
id: number,
|
||||
name: NullableString,
|
||||
@@ -18,6 +31,7 @@ export interface Scenario {
|
||||
// 用于存储场景中的通讯关系
|
||||
communicationGraph: NullableString,
|
||||
graph: GraphContainer
|
||||
relations: PlatformRelation[]
|
||||
}
|
||||
|
||||
export interface ScenarioRequest extends Scenario {
|
||||
@@ -34,6 +48,7 @@ export interface Platform {
|
||||
name: NullableString,
|
||||
description: NullableString,
|
||||
scenarioId: number,
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface PlatformComponent {
|
||||
@@ -50,4 +65,5 @@ export interface PlatformWithComponents extends Platform {
|
||||
|
||||
export interface PlatformWithComponentsResponse extends ApiDataResponse<PlatformWithComponents[]> {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,33 @@ export const createGraphTaskElementFromScenario = (
|
||||
} as GraphTaskElement;
|
||||
};
|
||||
|
||||
const portsGroups = {
|
||||
in: {
|
||||
position: 'left', // 入桩在左侧
|
||||
attrs: {
|
||||
circle: {
|
||||
r: 6,
|
||||
magnet: 'passive', // 被动吸附(仅作为连线目标)
|
||||
stroke: '#5da1df',
|
||||
strokeWidth: 2,
|
||||
fill: '#fff',
|
||||
},
|
||||
},
|
||||
},
|
||||
out: {
|
||||
position: 'right', // 出桩在右侧
|
||||
attrs: {
|
||||
circle: {
|
||||
r: 6,
|
||||
magnet: 'active', // 主动吸附(作为连线源)
|
||||
stroke: '#5da1df',
|
||||
strokeWidth: 2,
|
||||
fill: '#5da1df',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const createGraphScenarioElement = (element: GraphTaskElement): any => {
|
||||
let realHeight = 120;
|
||||
let width: number = 250;
|
||||
@@ -58,6 +85,24 @@ export const createGraphScenarioElement = (element: GraphTaskElement): any => {
|
||||
}
|
||||
}
|
||||
|
||||
// const portsItems = (element.components || []).map((comp, index) => {
|
||||
// const compId = comp.id || index;
|
||||
// return [
|
||||
// // 入桩(对应 DOM: data-port="in-${compId}")
|
||||
// {
|
||||
// id: `in-${compId}`, // portId 必须和 DOM 的 data-port 一致
|
||||
// group: 'in',
|
||||
// data: comp, // 直接存储 port 数据,避免后续解析 DOM
|
||||
// },
|
||||
// // 出桩(对应 DOM: data-port="out-${compId}")
|
||||
// {
|
||||
// id: `out-${compId}`,
|
||||
// group: 'out',
|
||||
// data: comp,
|
||||
// },
|
||||
// ];
|
||||
// }).flat(); // 扁平化数组
|
||||
|
||||
return {
|
||||
shape: 'scenario',
|
||||
id: element.key,
|
||||
@@ -75,5 +120,9 @@ export const createGraphScenarioElement = (element: GraphTaskElement): any => {
|
||||
},
|
||||
},
|
||||
data: element,
|
||||
// ports: {
|
||||
// groups: portsGroups,
|
||||
// items: portsItems,
|
||||
// },
|
||||
};
|
||||
};
|
||||
@@ -58,7 +58,7 @@ export const createGraphConnectingAttributes = (): Partial<Connecting> => {
|
||||
...lineOptions.attrs?.line,
|
||||
targetMarker: null,
|
||||
sourceMarker: null,
|
||||
}
|
||||
},
|
||||
},
|
||||
animation: lineOptions.animation,
|
||||
markup: lineOptions.markup,
|
||||
|
||||
@@ -53,7 +53,7 @@ export interface GraphBaseElement {
|
||||
position: GraphPosition;
|
||||
category: NullableString;
|
||||
element?: GraphDraggableElement;
|
||||
components?: GraphComponentElement[]
|
||||
components?: GraphComponentElement[];
|
||||
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
@@ -224,6 +224,7 @@ export const useGraphCanvas = (readonly: boolean = false): UseGraphCanvas => {
|
||||
|
||||
const createCanvas = (c: HTMLDivElement): Graph => {
|
||||
container.value = c;
|
||||
|
||||
graph.value = createGraphCanvas(c, readonly);
|
||||
initEvents();
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
* that was distributed with this source code.
|
||||
*/
|
||||
|
||||
export * from './line'
|
||||
export * from './ports'
|
||||
export * from './element'
|
||||
export * from './canvas'
|
||||
export * from './hooks'
|
||||
export * from './props'
|
||||
export * from './utils'
|
||||
export * from './line';
|
||||
export * from './ports';
|
||||
export * from './element';
|
||||
export * from './canvas';
|
||||
export * from './hooks';
|
||||
export * from './props';
|
||||
export * from './utils';
|
||||
@@ -18,7 +18,7 @@ export const createGraphTaskElement = (element: GraphTaskElement, width: number
|
||||
if (!realHeight) {
|
||||
realHeight = 120;
|
||||
}
|
||||
if(element.group === 'condition' || element.group === 'control') {
|
||||
if (element.group === 'condition' || element.group === 'control') {
|
||||
realHeight = 60;
|
||||
}
|
||||
return {
|
||||
@@ -74,8 +74,10 @@ export const resolveGraphEdgeElements = (graph: Graph): GraphEdgeElement[] => {
|
||||
key: edge.id,
|
||||
type: 'edge',
|
||||
status: nodeData?.status,
|
||||
sourcePort: edge.getSourcePortId(),
|
||||
source: edge.getSource() ? edge.getSource() as unknown as string : null,
|
||||
target: edge.getSource() ? edge.getTarget() as unknown as string : null,
|
||||
targetPort: edge.getTargetPortId(),
|
||||
attrs: edge.getAttrs() ?? {},
|
||||
router: edge.getRouter() ?? {},
|
||||
connector: edge.getConnector() ?? null,
|
||||
|
||||
Reference in New Issue
Block a user