UPDATE: VERSION-20260316

This commit is contained in:
libertyspy
2026-03-16 15:48:33 +08:00
parent c17197d6e5
commit 8dc867acb6
12 changed files with 239 additions and 34 deletions

View File

@@ -63,6 +63,7 @@ import { createGraphScenarioElement, createGraphTaskElementFromScenario } from '
import PlatformCard from './platform-card.vue'; import PlatformCard from './platform-card.vue';
import NodesCard from './nodes-card.vue'; import NodesCard from './nodes-card.vue';
import { saveScenario } from './api'; import { saveScenario } from './api';
import {resolveConnectionRelation} from './relation'
const TeleportContainer = defineComponent(getTeleport()); const TeleportContainer = defineComponent(getTeleport());
@@ -194,7 +195,6 @@ export default defineComponent({
}; };
const handleSelect = (scenario: Scenario) => { const handleSelect = (scenario: Scenario) => {
console.info('handleSelect', scenario);
let nodeGraph: GraphContainer | null = null; let nodeGraph: GraphContainer | null = null;
try { try {
nodeGraph = JSON.parse(scenario.communicationGraph as unknown as string) as unknown as GraphContainer; nodeGraph = JSON.parse(scenario.communicationGraph as unknown as string) as unknown as GraphContainer;
@@ -210,10 +210,10 @@ export default defineComponent({
currentScenario.value = { currentScenario.value = {
...scenario, ...scenario,
graph: nodeGraph, graph: nodeGraph,
relations: []
}; };
currentScenarioEditing.value = true; currentScenarioEditing.value = true;
createElements(); createElements();
}; };
const createElements = () => { const createElements = () => {
@@ -228,7 +228,6 @@ export default defineComponent({
if (currentScenario.value?.graph.nodes) { if (currentScenario.value?.graph.nodes) {
currentScenario.value?.graph.nodes.forEach(ele => { currentScenario.value?.graph.nodes.forEach(ele => {
const node = createGraphScenarioElement(ele as GraphTaskElement); const node = createGraphScenarioElement(ele as GraphTaskElement);
console.info('create node: ', ele);
// 将节点添加到画布 // 将节点添加到画布
graph.value?.addNode(node as Node); graph.value?.addNode(node as Node);
}); });
@@ -255,10 +254,11 @@ export default defineComponent({
name: null, name: null,
description: null, description: null,
communicationGraph: null, communicationGraph: null,
relations: [],
graph: { graph: {
edges: [], edges: [],
nodes: [], nodes: [],
} },
}; };
currentGraph.value = { currentGraph.value = {
edges: [], edges: [],
@@ -341,6 +341,10 @@ export default defineComponent({
const handleSave = () => { const handleSave = () => {
const graphData: GraphContainer = resolveGraph(graph.value as Graph); const graphData: GraphContainer = resolveGraph(graph.value as Graph);
const relations = resolveConnectionRelation(graph.value as Graph);
console.error('relations',relations)
console.info('handleSave', graphData); console.info('handleSave', graphData);
if (!currentScenario.value) { if (!currentScenario.value) {
message.error('当前决策树不存在'); message.error('当前决策树不存在');
@@ -350,6 +354,7 @@ export default defineComponent({
...currentScenario.value, ...currentScenario.value,
graph: graphData, graph: graphData,
communicationGraph: JSON.stringify(graphData), communicationGraph: JSON.stringify(graphData),
relations: relations
}; };
if (!newScenario.name) { if (!newScenario.name) {
message.error('场景名称不能为空.'); message.error('场景名称不能为空.');

View File

@@ -23,9 +23,11 @@
> >
<div <div
:data-port="`in-${item.id || index}`" :data-port="`in-${item.id || index}`"
:port="`in-${item.id || index}`"
:title="`入桩: ${item.name}`" :title="`入桩: ${item.name}`"
class="port port-in" class="port port-in"
magnet="passive" magnet="passive"
:data-item="JSON.stringify(item)"
> >
<div class="triangle-left"></div> <div class="triangle-left"></div>
</div> </div>
@@ -38,9 +40,11 @@
<!-- 右侧出桩只能作为连线源 --> <!-- 右侧出桩只能作为连线源 -->
<div <div
:data-port="`out-${item.id || index}`" :data-port="`out-${item.id || index}`"
:port="`out-${item.id || index}`"
:title="`出桩: ${item.name}`" :title="`出桩: ${item.name}`"
class="port port-out" class="port port-out"
magnet="active" magnet="active"
:data-item="JSON.stringify(item)"
> >
<div class="triangle-right" ></div> <div class="triangle-right" ></div>
</div> </div>
@@ -130,7 +134,6 @@ export default defineComponent({
onMounted(() => { onMounted(() => {
_props.node?.on('change:data', handleDataChange); _props.node?.on('change:data', handleDataChange);
console.error('element',element.value)
}); });
onUnmounted(() => { onUnmounted(() => {

View File

@@ -27,7 +27,7 @@
</template> </template>
<script lang="ts"> <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 { safePreventDefault, safeStopPropagation } from '@/utils/event';
import {findPlatformWithComponents} from './api' import {findPlatformWithComponents} from './api'
import { type PlatformWithComponents, type Scenario } from './types'; import { type PlatformWithComponents, type Scenario } from './types';
@@ -40,20 +40,13 @@ export default defineComponent({
required: true, required: true,
} }
}, },
setup(_props, { emit }) { setup(props, { emit }) {
const activeKey = ref<number>(1); const activeKey = ref<number>(1);
const templateData = ref<PlatformWithComponents[]>([]); const templateData = ref<PlatformWithComponents[]>([]);
const isDraggingOver = ref<boolean>(false); const isDraggingOver = ref<boolean>(false);
const draggedNodeData = ref<PlatformWithComponents | null>(null); const draggedNodeData = ref<PlatformWithComponents | null>(null);
const currentScenario = ref<Scenario|null>(_props.scenario) const currentScenario = ref<Scenario|null>(props.scenario)
const loadTress = () => {
templateData.value = []
findPlatformWithComponents(_props.scenario?.id).then(r => {
templateData.value = r.data ?? []
});
};
const handleDragStart = (e: DragEvent, nm: PlatformWithComponents) => { const handleDragStart = (e: DragEvent, nm: PlatformWithComponents) => {
let dragNode: PlatformWithComponents = { ...nm }; let dragNode: PlatformWithComponents = { ...nm };
@@ -92,16 +85,17 @@ export default defineComponent({
}; };
const load = ()=> { const load = (id: number)=> {
findPlatformWithComponents(1).then(re=> { findPlatformWithComponents(id).then(re=> {
console.error(re); templateData.value = re.data ?? []
}) })
} }
onMounted(() => { watch(()=> props.scenario,(n: Scenario|null|undefined )=> {
loadTress(); if(n){
load(); load(n.id);
}); }
}, {deep: true, immediate: true} )
return { return {
activeKey, activeKey,

View File

@@ -79,6 +79,7 @@ export default defineComponent({
findScenarioByQuery(scenarioQuery.value).then(r => { findScenarioByQuery(scenarioQuery.value).then(r => {
scenario.value = r.rows; scenario.value = r.rows;
totalTress.value = r.total ?? 0; totalTress.value = r.total ?? 0;
emit('select', r.rows[0]);
}); });
}; };

View 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;
}

View File

@@ -11,6 +11,19 @@
import type { ApiDataResponse, NullableString, PageableResponse } from '@/types'; import type { ApiDataResponse, NullableString, PageableResponse } from '@/types';
import type { GraphContainer } from '../graph'; 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 { export interface Scenario {
id: number, id: number,
name: NullableString, name: NullableString,
@@ -18,6 +31,7 @@ export interface Scenario {
// 用于存储场景中的通讯关系 // 用于存储场景中的通讯关系
communicationGraph: NullableString, communicationGraph: NullableString,
graph: GraphContainer graph: GraphContainer
relations: PlatformRelation[]
} }
export interface ScenarioRequest extends Scenario { export interface ScenarioRequest extends Scenario {
@@ -34,6 +48,7 @@ export interface Platform {
name: NullableString, name: NullableString,
description: NullableString, description: NullableString,
scenarioId: number, scenarioId: number,
[key: string]: unknown;
} }
export interface PlatformComponent { export interface PlatformComponent {
@@ -51,3 +66,4 @@ export interface PlatformWithComponents extends Platform {
export interface PlatformWithComponentsResponse extends ApiDataResponse<PlatformWithComponents[]> { export interface PlatformWithComponentsResponse extends ApiDataResponse<PlatformWithComponents[]> {
} }

View File

@@ -44,6 +44,33 @@ export const createGraphTaskElementFromScenario = (
} as GraphTaskElement; } 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 => { export const createGraphScenarioElement = (element: GraphTaskElement): any => {
let realHeight = 120; let realHeight = 120;
let width: number = 250; 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 { return {
shape: 'scenario', shape: 'scenario',
id: element.key, id: element.key,
@@ -75,5 +120,9 @@ export const createGraphScenarioElement = (element: GraphTaskElement): any => {
}, },
}, },
data: element, data: element,
// ports: {
// groups: portsGroups,
// items: portsItems,
// },
}; };
}; };

View File

@@ -58,7 +58,7 @@ export const createGraphConnectingAttributes = (): Partial<Connecting> => {
...lineOptions.attrs?.line, ...lineOptions.attrs?.line,
targetMarker: null, targetMarker: null,
sourceMarker: null, sourceMarker: null,
} },
}, },
animation: lineOptions.animation, animation: lineOptions.animation,
markup: lineOptions.markup, markup: lineOptions.markup,

View File

@@ -53,7 +53,7 @@ export interface GraphBaseElement {
position: GraphPosition; position: GraphPosition;
category: NullableString; category: NullableString;
element?: GraphDraggableElement; element?: GraphDraggableElement;
components?: GraphComponentElement[] components?: GraphComponentElement[];
[key: string]: unknown; [key: string]: unknown;
} }

View File

@@ -224,6 +224,7 @@ export const useGraphCanvas = (readonly: boolean = false): UseGraphCanvas => {
const createCanvas = (c: HTMLDivElement): Graph => { const createCanvas = (c: HTMLDivElement): Graph => {
container.value = c; container.value = c;
graph.value = createGraphCanvas(c, readonly); graph.value = createGraphCanvas(c, readonly);
initEvents(); initEvents();

View File

@@ -7,10 +7,10 @@
* that was distributed with this source code. * that was distributed with this source code.
*/ */
export * from './line' export * from './line';
export * from './ports' export * from './ports';
export * from './element' export * from './element';
export * from './canvas' export * from './canvas';
export * from './hooks' export * from './hooks';
export * from './props' export * from './props';
export * from './utils' export * from './utils';

View File

@@ -18,7 +18,7 @@ export const createGraphTaskElement = (element: GraphTaskElement, width: number
if (!realHeight) { if (!realHeight) {
realHeight = 120; realHeight = 120;
} }
if(element.group === 'condition' || element.group === 'control') { if (element.group === 'condition' || element.group === 'control') {
realHeight = 60; realHeight = 60;
} }
return { return {
@@ -74,8 +74,10 @@ export const resolveGraphEdgeElements = (graph: Graph): GraphEdgeElement[] => {
key: edge.id, key: edge.id,
type: 'edge', type: 'edge',
status: nodeData?.status, status: nodeData?.status,
sourcePort: edge.getSourcePortId(),
source: edge.getSource() ? edge.getSource() as unknown as string : null, source: edge.getSource() ? edge.getSource() as unknown as string : null,
target: edge.getSource() ? edge.getTarget() as unknown as string : null, target: edge.getSource() ? edge.getTarget() as unknown as string : null,
targetPort: edge.getTargetPortId(),
attrs: edge.getAttrs() ?? {}, attrs: edge.getAttrs() ?? {},
router: edge.getRouter() ?? {}, router: edge.getRouter() ?? {},
connector: edge.getConnector() ?? null, connector: edge.getConnector() ?? null,