UPDATE: VERSION-20260314
This commit is contained in:
@@ -32,7 +32,15 @@ export const routes: RouteRecordRaw[] = [
|
||||
meta: {
|
||||
title: '决策树',
|
||||
},
|
||||
component: () => import('@/views/decision/designer.vue'),
|
||||
component: () => import('@/views/decision/designer/designer.vue'),
|
||||
},
|
||||
{
|
||||
name: 'decision-communication',
|
||||
path: '/app/decision/communication',
|
||||
meta: {
|
||||
title: '通信',
|
||||
},
|
||||
component: () => import('@/views/decision/communication/communication.vue'),
|
||||
},
|
||||
{
|
||||
name: 'decision-algorithm-management',
|
||||
|
||||
@@ -1767,4 +1767,55 @@
|
||||
line-height: 26px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ant-collapse {
|
||||
.ant-list-sm {
|
||||
.ant-list-item {
|
||||
padding: 4px 15px;
|
||||
cursor: pointer;
|
||||
color: rgb(130 196 233);
|
||||
position: relative;
|
||||
|
||||
.ks-tree-actions {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #0d2d4e;
|
||||
|
||||
.ks-tree-actions {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&.ks-trees-collapse {
|
||||
|
||||
.ant-collapse-content-box {
|
||||
padding: 0;
|
||||
height: 40vh;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.create-tree-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ant-list-item {
|
||||
padding: 3px 5px;
|
||||
cursor: pointer;
|
||||
color: rgb(130 196 233);
|
||||
|
||||
&:hover {
|
||||
background: #0d2d4e;
|
||||
}
|
||||
}
|
||||
37
modeler/src/views/decision/communication/api.ts
Normal file
37
modeler/src/views/decision/communication/api.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 { HttpRequestClient } from '@/utils/request';
|
||||
import type { ScenarioPageableResponse, ScenarioRequest } from './types';
|
||||
import type { BasicResponse } from '@/types';
|
||||
|
||||
const req = HttpRequestClient.create<BasicResponse>({
|
||||
baseURL: '/api',
|
||||
});
|
||||
|
||||
export const findScenarioByQuery = (_query: Partial<ScenarioRequest> = {}): Promise<ScenarioPageableResponse> => {
|
||||
return new Promise((resolve) => {
|
||||
resolve({
|
||||
code: 200,
|
||||
msg: null,
|
||||
total: 1,
|
||||
rows: [
|
||||
{
|
||||
id: 1,
|
||||
name: '空战场景',
|
||||
description: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteOneScenarioById = (id: number): Promise<BasicResponse> => {
|
||||
return req.delete(`/system/behaviortree/${id}`);
|
||||
};
|
||||
429
modeler/src/views/decision/communication/communication.vue
Normal file
429
modeler/src/views/decision/communication/communication.vue
Normal file
@@ -0,0 +1,429 @@
|
||||
<template>
|
||||
<Wrapper>
|
||||
<a-layout class="bg-transparent" style="background: transparent">
|
||||
<Header />
|
||||
<a-layout class="ks-layout-body">
|
||||
<div class="ks-model-builder-body">
|
||||
<div class="ks-model-builder-left">
|
||||
<PlatformCard
|
||||
ref="treesCardRef"
|
||||
@create-tree="handleCreateTree"
|
||||
@select-tree="handleSelectTree"
|
||||
/>
|
||||
<NodesCard
|
||||
@drag-item-start="handleDragStart"
|
||||
@drag-item-end="handleDragEnd"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ks-model-builder-content" style="width: calc(100% - 250px);">
|
||||
<div class="ks-model-builder-actions">
|
||||
<a-space>
|
||||
<a-button v-if="graph && currentBehaviorTree" class="ks-model-builder-save" size="small" @click="handleSave">
|
||||
<CheckOutlined />
|
||||
<span>保存</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
<!-- 画布容器,添加拖放事件 -->
|
||||
<div
|
||||
ref="canvas"
|
||||
class="ks-model-builder-canvas"
|
||||
@dragenter="handleDragEnter"
|
||||
@dragleave="handleDragLeave"
|
||||
@drop="handleDrop"
|
||||
@dragover.prevent
|
||||
></div>
|
||||
<TeleportContainer />
|
||||
</div>
|
||||
</div>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</Wrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
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 { Wrapper } from '@/components/wrapper';
|
||||
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
|
||||
import Header from '../header.vue';
|
||||
import type { BehaviorTree, NodeDragTemplate, NodeTemplate } from '../types';
|
||||
import type { GraphTaskElement, NodeGraph } from '../builder/element';
|
||||
import { useGraphCanvas } from '../builder/hooks';
|
||||
import { registerNodeElement } from '../builder/register';
|
||||
import { createLineOptions } from '../builder/line';
|
||||
import { createTree, findOneTreeById, updateTree } from '../designer/api';
|
||||
import { createGraphTaskElement, hasElements, hasRootElementNode, resolveNodeGraph } from '../builder/utils';
|
||||
import { createGraphTaskElementFromTemplate } from '../utils/node';
|
||||
import PlatformCard from './platform-card.vue';
|
||||
import NodesCard from './nodes-card.vue';
|
||||
|
||||
const TeleportContainer = defineComponent(getTeleport());
|
||||
|
||||
registerNodeElement();
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
PlatformCard,
|
||||
NodesCard,
|
||||
Wrapper,
|
||||
Header,
|
||||
SaveOutlined,
|
||||
CheckCircleOutlined,
|
||||
CheckOutlined,
|
||||
RollbackOutlined,
|
||||
TeleportContainer,
|
||||
},
|
||||
setup() {
|
||||
const canvas = ref<HTMLDivElement | null>(null);
|
||||
const graph = ref<Graph | null>(null);
|
||||
const currentZoom = ref<number>(1);
|
||||
const draggedNodeData = ref<NodeDragTemplate | null>(null);
|
||||
const isDraggingOver = ref(false);
|
||||
const currentTreeEditing = ref<boolean>(false);
|
||||
const currentBehaviorTree = ref<BehaviorTree | null>(null);
|
||||
const currentNodeGraph = ref<NodeGraph | null>(null);
|
||||
const selectedModelNode = ref<Node<NodeProperties> | null>(null);
|
||||
const selectedNodeTaskElement = ref<GraphTaskElement | null>(null);
|
||||
const changed = ref<boolean>(false);
|
||||
const treesCardRef = ref<InstanceType<typeof PlatformCard> | null>(null);
|
||||
|
||||
const {
|
||||
handleGraphEvent,
|
||||
createCanvas,
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
fitToScreen,
|
||||
centerContent,
|
||||
resizeCanvas,
|
||||
} = useGraphCanvas();
|
||||
|
||||
// 处理拖动开始
|
||||
const handleDragStart = (nm: NodeDragTemplate) => {
|
||||
draggedNodeData.value = nm;
|
||||
};
|
||||
|
||||
// 处理拖动结束
|
||||
const handleDragEnd = () => {
|
||||
isDraggingOver.value = false;
|
||||
};
|
||||
|
||||
// 处理拖动进入
|
||||
const handleDragEnter = (e: DragEvent) => {
|
||||
safePreventDefault(e);
|
||||
safeStopPropagation(e);
|
||||
isDraggingOver.value = true;
|
||||
};
|
||||
|
||||
// 处理拖动离开
|
||||
const handleDragLeave = (e: DragEvent) => {
|
||||
safePreventDefault(e);
|
||||
safeStopPropagation(e);
|
||||
|
||||
if (canvas.value && e.relatedTarget &&
|
||||
typeof e.relatedTarget === 'object' &&
|
||||
'nodeType' in e.relatedTarget) {
|
||||
// 使用 Element 类型而不是 x6 的 Node 类型
|
||||
if (!canvas.value.contains(e.relatedTarget as Element)) {
|
||||
isDraggingOver.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 处理放置
|
||||
const handleDrop = (e: DragEvent) => {
|
||||
console.info('handleDrop', e);
|
||||
safePreventDefault(e);
|
||||
safeStopPropagation(e);
|
||||
isDraggingOver.value = false;
|
||||
currentTreeEditing.value = false;
|
||||
|
||||
if (!currentBehaviorTree.value) {
|
||||
message.error('请先选择或者创建行为树.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!graph.value || !canvas.value || !draggedNodeData.value) {
|
||||
message.error('无法放置节点,缺少必要数据');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取拖动的数据
|
||||
const template = draggedNodeData.value as NodeDragTemplate;
|
||||
|
||||
if (!hasElements(graph.value as Graph) && template.type !== 'root') {
|
||||
message.error('请先添加根节点.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasRootElementNode(graph.value as Graph) && template.type === 'root') {
|
||||
message.error('根节点已经存在.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算相对于画布的位置(考虑缩放)
|
||||
const rect = canvas.value.getBoundingClientRect();
|
||||
const scale = currentZoom.value || 1;
|
||||
const x = (e.clientX - rect.left) / scale;
|
||||
const y = (e.clientY - rect.top) / scale;
|
||||
|
||||
console.log('放置节点:', { ...template, x, y });
|
||||
|
||||
// 创建节点数据
|
||||
const settingTaskElement: GraphTaskElement = createGraphTaskElementFromTemplate(template, { x, y });
|
||||
// 创建节点
|
||||
const settingTaskNode = createGraphTaskElement(settingTaskElement);
|
||||
console.info('create settingTaskNode: ', settingTaskElement, settingTaskNode);
|
||||
|
||||
// 将节点添加到画布
|
||||
graph.value?.addNode(settingTaskNode as any);
|
||||
console.log('节点已添加到画布:', settingTaskNode.id);
|
||||
|
||||
// 重置拖动数据
|
||||
draggedNodeData.value = null;
|
||||
} catch (error) {
|
||||
console.error('放置节点时出错:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectTree = (tree: BehaviorTree) => {
|
||||
console.info('handleSelectTree', tree);
|
||||
findOneTreeById(tree.id).then(r => {
|
||||
if (r.data) {
|
||||
let nodeGraph: NodeGraph | null = null;
|
||||
try {
|
||||
nodeGraph = JSON.parse(r.data?.xmlContent as unknown as string) as unknown as NodeGraph;
|
||||
} catch (e: any) {
|
||||
console.error('parse error,cause:', e);
|
||||
}
|
||||
if (!nodeGraph) {
|
||||
nodeGraph = {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
};
|
||||
}
|
||||
currentBehaviorTree.value = {
|
||||
...r.data,
|
||||
graph: nodeGraph,
|
||||
};
|
||||
currentTreeEditing.value = true;
|
||||
createElements();
|
||||
} else {
|
||||
message.error(r.msg ?? '行为树不存在.');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const createElements = () => {
|
||||
nextTick(() => {
|
||||
try {
|
||||
graph.value?.clearCells();
|
||||
} catch (e: any) {
|
||||
console.error('clear cells error, cause:', e);
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (currentBehaviorTree.value?.graph && graph.value) {
|
||||
if (currentBehaviorTree.value?.graph.nodes) {
|
||||
currentBehaviorTree.value?.graph.nodes.forEach(ele => {
|
||||
const node = createGraphTaskElement(ele as GraphTaskElement);
|
||||
console.info('create node: ', ele);
|
||||
// 将节点添加到画布
|
||||
graph.value?.addNode(node as Node);
|
||||
});
|
||||
}
|
||||
if (currentBehaviorTree.value?.graph.edges) {
|
||||
// 然后添加所有边,确保包含桩点信息
|
||||
setTimeout(() => {
|
||||
currentBehaviorTree.value?.graph.edges.forEach(edgeData => {
|
||||
graph.value?.addEdge({
|
||||
...edgeData,
|
||||
...createLineOptions(),
|
||||
});
|
||||
});
|
||||
}, 100); // 延迟一会儿,免得连线错位
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
};
|
||||
|
||||
const handleCreateTree = () => {
|
||||
currentBehaviorTree.value = {
|
||||
id: 0,
|
||||
name: '行为树',
|
||||
description: null,
|
||||
englishName: null,
|
||||
xmlContent: null,
|
||||
createdAt: null,
|
||||
graph: {
|
||||
edges: [],
|
||||
nodes: [],
|
||||
},
|
||||
updatedAt: null,
|
||||
};
|
||||
currentNodeGraph.value = {
|
||||
edges: [],
|
||||
nodes: [],
|
||||
};
|
||||
selectedModelNode.value = null;
|
||||
selectedNodeTaskElement.value = null;
|
||||
|
||||
createElements();
|
||||
};
|
||||
|
||||
// 初始化X6画布
|
||||
const initGraph = () => {
|
||||
if (!canvas.value) {
|
||||
console.error('画布容器不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
graph.value = createCanvas(canvas.value);
|
||||
console.log('画布初始化成功');
|
||||
createElements();
|
||||
|
||||
// 监听缩放变化
|
||||
handleGraphEvent('scale', ({ sx }: { sx: number }) => {
|
||||
currentZoom.value = sx;
|
||||
});
|
||||
|
||||
handleGraphEvent('blank:click', () => {
|
||||
selectedModelNode.value = null;
|
||||
selectedNodeTaskElement.value = null;
|
||||
currentTreeEditing.value = null !== currentBehaviorTree.value;
|
||||
});
|
||||
|
||||
handleGraphEvent('node:click', (args: any) => {
|
||||
const node = args.node as Node<NodeProperties>;
|
||||
const newElement = node.getData() as GraphTaskElement;
|
||||
|
||||
selectedModelNode.value = node;
|
||||
selectedNodeTaskElement.value = JSON.parse(JSON.stringify(newElement || {})) as GraphTaskElement;
|
||||
});
|
||||
|
||||
// 监听节点鼠标事件,显示/隐藏连接点
|
||||
handleGraphEvent('node:mouseenter', (_ctx: any) => {
|
||||
});
|
||||
|
||||
handleGraphEvent('node:mouseleave', (_ctx: any) => {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('初始化画布失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 监听窗口大小变化
|
||||
const handleResize = () => {
|
||||
nextTick(() => {
|
||||
resizeCanvas();
|
||||
});
|
||||
};
|
||||
|
||||
const init = () => {
|
||||
console.info('init');
|
||||
nextTick(() => {
|
||||
initGraph();
|
||||
window.addEventListener('resize', handleResize);
|
||||
console.log('节点挂载完成');
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateElement = (element: GraphTaskElement) => {
|
||||
// 更新选中的节点数据
|
||||
if (selectedModelNode.value) {
|
||||
selectedModelNode.value.replaceData(element);
|
||||
}
|
||||
console.info('handleUpdateElement', element);
|
||||
// 更新本地引用
|
||||
selectedNodeTaskElement.value = element;
|
||||
changed.value = true;
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const graphData: NodeGraph = resolveNodeGraph(graph.value as Graph);
|
||||
console.info('handleSave', graphData);
|
||||
if (!currentBehaviorTree.value) {
|
||||
message.error('当前决策树不存在');
|
||||
return;
|
||||
}
|
||||
const newTree: BehaviorTree = {
|
||||
...currentBehaviorTree.value,
|
||||
graph: graphData,
|
||||
xmlContent: JSON.stringify(graphData),
|
||||
};
|
||||
if (!newTree.name) {
|
||||
message.error('行为树名称不能为空.');
|
||||
return;
|
||||
}
|
||||
if (!newTree.englishName) {
|
||||
message.error('行为树英文名称不能为空.');
|
||||
return;
|
||||
}
|
||||
let res = null;
|
||||
if (currentBehaviorTree.value.id > 0) {
|
||||
res = createTree(newTree);
|
||||
} else {
|
||||
res = updateTree(newTree);
|
||||
}
|
||||
res.then(r => {
|
||||
if (r.code === 200) {
|
||||
treesCardRef.value?.refresh();
|
||||
message.success(r.msg ?? '操作成功.');
|
||||
} else {
|
||||
message.error(r.msg ?? '操作失败.');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
// 清理
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
if (graph.value) {
|
||||
try {
|
||||
graph.value.clearCells();
|
||||
} catch (error) {
|
||||
console.warn('销毁画布时出错:', error);
|
||||
}
|
||||
graph.value = null;
|
||||
console.log('画布已销毁');
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
treesCardRef,
|
||||
handleCreateTree,
|
||||
currentTreeEditing,
|
||||
currentBehaviorTree,
|
||||
currentNodeGraph,
|
||||
selectedNodeTaskElement,
|
||||
selectedModelNode,
|
||||
graph,
|
||||
canvas,
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
fitToScreen,
|
||||
centerContent,
|
||||
handleDragStart,
|
||||
handleDragEnd,
|
||||
handleDragEnter,
|
||||
handleDragLeave,
|
||||
handleDrop,
|
||||
isDraggingOver,
|
||||
handleSave,
|
||||
handleUpdateElement,
|
||||
handleSelectTree,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
125
modeler/src/views/decision/communication/nodes-card.vue
Normal file
125
modeler/src/views/decision/communication/nodes-card.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<a-collapse v-model:activeKey="activeKey" :accordion="true">
|
||||
<a-collapse-panel key="1">
|
||||
<template #header>
|
||||
<span class="ks-model-builder-title-icon icon-model"></span>控制节点
|
||||
</template>
|
||||
<div class="w-full h-full">
|
||||
<a-row>
|
||||
<a-col v-for="nm in controlTemplates" :span="12">
|
||||
<div
|
||||
:key="nm.id"
|
||||
:data-type="nm.type"
|
||||
class="ks-model-drag-item"
|
||||
@dragend="handleDragEnd"
|
||||
@dragstart="handleDragStart($event, nm, 'control')"
|
||||
>
|
||||
<img :alt="nm.name ?? ''" class="icon" src="@/assets/icons/model-4.svg" />
|
||||
<span class="desc">{{ nm.name }}</span>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref } from 'vue';
|
||||
import type { NodeDragTemplate, NodeTemplate } from '../types';
|
||||
import { findNodeTemplates } from '../designer/api';
|
||||
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
|
||||
|
||||
export default defineComponent({
|
||||
emits: ['drag-item-start', 'drag-item-end'],
|
||||
setup(_props, { emit }) {
|
||||
|
||||
const activeKey = ref<number>(1);
|
||||
const templateData = ref<NodeTemplate[]>([]);
|
||||
const isDraggingOver = ref<boolean>(false);
|
||||
const draggedNodeData = ref<NodeDragTemplate | null>(null);
|
||||
// 控制节点
|
||||
const controlTemplates = ref<NodeTemplate[]>([]);
|
||||
// 条件节点
|
||||
const conditionTemplates = ref<NodeTemplate[]>([]);
|
||||
// 行为节点
|
||||
const actionsTemplates = ref<NodeTemplate[]>([]);
|
||||
|
||||
const loadTress = () => {
|
||||
controlTemplates.value = [];
|
||||
conditionTemplates.value = [];
|
||||
actionsTemplates.value = [];
|
||||
|
||||
findNodeTemplates().then(r => {
|
||||
templateData.value = r.data;
|
||||
if (r.data) {
|
||||
r.data.forEach(tpl => {
|
||||
if (tpl.type === 'action') {
|
||||
actionsTemplates.value.push(tpl);
|
||||
} else if (tpl.type === 'parallel' || tpl.type === 'sequence' || tpl.type === 'select'|| tpl.type === 'root') {
|
||||
controlTemplates.value.push(tpl);
|
||||
} else {
|
||||
conditionTemplates.value.push(tpl);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDragStart = (e: DragEvent, nm: NodeTemplate, group: String) => {
|
||||
let dragNode: NodeDragTemplate = { ...nm, group: group };
|
||||
draggedNodeData.value = dragNode as NodeDragTemplate;
|
||||
|
||||
if (e.dataTransfer) {
|
||||
e.dataTransfer.setData('text/plain', JSON.stringify(draggedNodeData.value));
|
||||
e.dataTransfer.effectAllowed = 'copyMove';
|
||||
|
||||
const dragPreview = document.createElement('div');
|
||||
dragPreview.textContent = dragNode.name || '';
|
||||
dragPreview.style.cssText = `
|
||||
position: absolute;
|
||||
top: -1000px;
|
||||
padding: 6px 12px;
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
`;
|
||||
document.body.appendChild(dragPreview);
|
||||
e.dataTransfer.setDragImage(dragPreview, dragPreview.offsetWidth / 2, dragPreview.offsetHeight / 2);
|
||||
emit('drag-item-start', dragNode, group, isDraggingOver.value, e);
|
||||
console.log('开始拖动:', dragNode);
|
||||
setTimeout(() => document.body.removeChild(dragPreview), 0);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragEnd = (e: DragEvent) => {
|
||||
safePreventDefault(e);
|
||||
safeStopPropagation(e);
|
||||
isDraggingOver.value = false;
|
||||
console.log('拖动结束');
|
||||
emit('drag-item-end', isDraggingOver.value, e);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadTress();
|
||||
});
|
||||
|
||||
return {
|
||||
activeKey,
|
||||
|
||||
controlTemplates,
|
||||
conditionTemplates,
|
||||
actionsTemplates,
|
||||
|
||||
handleDragStart,
|
||||
handleDragEnd,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
141
modeler/src/views/decision/communication/platform-card.vue
Normal file
141
modeler/src/views/decision/communication/platform-card.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<a-collapse v-model:activeKey="activeKey" :accordion="false" class="ks-trees-collapse">
|
||||
<a-collapse-panel key="1" style="position: relative">
|
||||
<template #header>
|
||||
<a-flex>
|
||||
<span class="ks-model-builder-title-icon icon-model"></span>
|
||||
<span style="color: #82c4e9;font-size: 16px;">场景</span>
|
||||
<!-- <span style="position: absolute; right: 15px;"><PlusOutlined @click="$emit('create-tree')"/></span>-->
|
||||
</a-flex>
|
||||
</template>
|
||||
<div class="w-full" style="padding: 5px;">
|
||||
<a-flex>
|
||||
<a-input-search v-model:value="scenarioQuery.name" allowClear placeholder="场景名称" size="small" @search="loadTress" />
|
||||
<!-- <a-button size="small" style="margin-left: 10px;">-->
|
||||
<!-- <PlusOutlined style="margin-top: 0px;display: block;" @click="$emit('create-tree')" />-->
|
||||
<!-- </a-button>-->
|
||||
</a-flex>
|
||||
</div>
|
||||
<a-list :data-source="scenario || []" size="small" style="min-height: 25vh">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a-flex>
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>
|
||||
<p>名称: {{ item.name }}</p>
|
||||
<p>说明: {{ item.description }}</p>
|
||||
</template>
|
||||
<span>{{ substring(item.name, 15) }}</span>
|
||||
</a-tooltip>
|
||||
<a-flex class="ks-tree-actions">
|
||||
<span style="margin-right: 10px" @click="()=> handleSelect(item)"><EditFilled /></span>
|
||||
<a-popconfirm
|
||||
title="确定删除?"
|
||||
@confirm="()=> handleDelete(item)"
|
||||
>
|
||||
<span class="ks-tree-action ks-tree-action-delete"><DeleteOutlined /></span>
|
||||
</a-popconfirm>
|
||||
</a-flex>
|
||||
</a-flex>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
<a-pagination
|
||||
v-model:current="scenarioQuery.pageNum"
|
||||
:page-size="scenarioQuery.pageSize"
|
||||
:total="totalTress"
|
||||
simple size="small" @change="handleChange" />
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref } from 'vue';
|
||||
import { CheckOutlined, DeleteOutlined, EditFilled, PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { Scenario, ScenarioRequest } from './types';
|
||||
import { findScenarioByQuery ,deleteOneScenarioById } from './api';
|
||||
import { substring } from '@/utils/strings';
|
||||
|
||||
export default defineComponent({
|
||||
emits: ['select-tree', 'create-tree'],
|
||||
components: {
|
||||
CheckOutlined,
|
||||
PlusOutlined,
|
||||
DeleteOutlined,
|
||||
EditFilled,
|
||||
},
|
||||
setup(_props, { emit }) {
|
||||
const scenario = ref<Scenario[]>([]);
|
||||
const scenarioQuery = ref<Partial<ScenarioRequest>>({
|
||||
name: null,
|
||||
pageNum: 1,
|
||||
pageSize: 8,
|
||||
});
|
||||
const activeKey = ref<number>(1);
|
||||
const totalTress = ref<number>(0);
|
||||
|
||||
const loadTress = () => {
|
||||
findScenarioByQuery(scenarioQuery.value).then(r => {
|
||||
scenario.value = r.rows;
|
||||
totalTress.value = r.total ?? 0;
|
||||
});
|
||||
};
|
||||
|
||||
const handleChange = (page: number, pageSize: number) => {
|
||||
scenarioQuery.value.pageNum = page;
|
||||
scenarioQuery.value.pageSize = pageSize;
|
||||
loadTress();
|
||||
};
|
||||
|
||||
const handleDelete = (item: Scenario) => {
|
||||
deleteOneScenarioById(item.id).then(r => {
|
||||
if (r.code === 200) {
|
||||
loadTress();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
];
|
||||
|
||||
const handleSelect = (record: Scenario) => {
|
||||
emit('select', record);
|
||||
};
|
||||
|
||||
const customRow = (record: Scenario) => {
|
||||
return {
|
||||
onClick: (event: any) => {
|
||||
emit('select', record, event);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const refresh = () => loadTress();
|
||||
|
||||
onMounted(() => {
|
||||
loadTress();
|
||||
});
|
||||
|
||||
return {
|
||||
refresh,
|
||||
totalTress,
|
||||
substring,
|
||||
activeKey,
|
||||
scenario,
|
||||
scenarioQuery,
|
||||
loadTress,
|
||||
columns,
|
||||
customRow,
|
||||
handleSelect,
|
||||
handleChange,
|
||||
handleDelete,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
27
modeler/src/views/decision/communication/types.ts
Normal file
27
modeler/src/views/decision/communication/types.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 { NullableString, PageableResponse } from '@/types';
|
||||
import type { FireRule } from '@/views/decision/rule/types.ts';
|
||||
|
||||
export interface Scenario {
|
||||
id: number,
|
||||
name: NullableString,
|
||||
description: NullableString,
|
||||
}
|
||||
|
||||
export interface ScenarioRequest extends FireRule {
|
||||
pageNum: number,
|
||||
pageSize: number,
|
||||
}
|
||||
|
||||
export interface ScenarioPageableResponse extends PageableResponse<Scenario> {
|
||||
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import { HttpRequestClient } from '@/utils/request';
|
||||
import type { BehaviorTree, BehaviorTreeDetailsResponse, BehaviorTreePageResponse, BehaviorTreeRequest, NodeTemplatesResponse } from './types';
|
||||
import type { BehaviorTree, BehaviorTreeDetailsResponse, BehaviorTreePageResponse, BehaviorTreeRequest, NodeTemplatesResponse } from '../types';
|
||||
import type { BasicResponse } from '@/types';
|
||||
|
||||
const req = HttpRequestClient.create<BasicResponse>({
|
||||
@@ -72,16 +72,16 @@ import { Graph, Node, type NodeProperties } from '@antv/x6';
|
||||
import { CheckCircleOutlined, CheckOutlined, RollbackOutlined, SaveOutlined } from '@ant-design/icons-vue';
|
||||
import { Wrapper } from '@/components/wrapper';
|
||||
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
|
||||
import Header from './header.vue';
|
||||
import Header from '../header.vue';
|
||||
import Properties from './properties.vue';
|
||||
import type { BehaviorTree, NodeDragTemplate, NodeTemplate } from './types';
|
||||
import type { GraphTaskElement, NodeGraph } from './builder/element';
|
||||
import { useGraphCanvas } from './builder/hooks';
|
||||
import { registerNodeElement } from './builder/register';
|
||||
import { createLineOptions } from './builder/line';
|
||||
import type { BehaviorTree, NodeDragTemplate, NodeTemplate } from '../types';
|
||||
import type { GraphTaskElement, NodeGraph } from '../builder/element';
|
||||
import { useGraphCanvas } from '../builder/hooks';
|
||||
import { registerNodeElement } from '../builder/register';
|
||||
import { createLineOptions } from '../builder/line';
|
||||
import { createTree, findOneTreeById, updateTree } from './api';
|
||||
import { createGraphTaskElement, hasElements, hasRootElementNode, resolveNodeGraph } from './builder/utils';
|
||||
import { createGraphTaskElementFromTemplate } from './utils/node';
|
||||
import { createGraphTaskElement, hasElements, hasRootElementNode, resolveNodeGraph } from '../builder/utils';
|
||||
import { createGraphTaskElementFromTemplate } from '../utils/node';
|
||||
import TressCard from './trees-card.vue';
|
||||
import NodesCard from './nodes-card.vue';
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref } from 'vue';
|
||||
import type { NodeDragTemplate, NodeTemplate } from './types';
|
||||
import type { NodeDragTemplate, NodeTemplate } from '../types';
|
||||
import { findNodeTemplates } from './api';
|
||||
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
|
||||
|
||||
@@ -147,8 +147,8 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, type PropType, ref, watch } from 'vue';
|
||||
import { CheckOutlined } from '@ant-design/icons-vue';
|
||||
import type { ElementVariable, GraphTaskElement } from './builder/element';
|
||||
import type { BehaviorTree } from './types';
|
||||
import type { ElementVariable, GraphTaskElement } from '../builder/element';
|
||||
import type { BehaviorTree } from '../types';
|
||||
import type { Graph, Node, NodeProperties } from '@antv/x6';
|
||||
import { generateKey } from '@/utils/strings';
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref } from 'vue';
|
||||
import { CheckOutlined, DeleteOutlined, EditFilled, PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { BehaviorTree, BehaviorTreeRequest } from './types';
|
||||
import type { BehaviorTree, BehaviorTreeRequest } from '../types';
|
||||
import { deleteOneTreeById, findTreesByQuery } from './api';
|
||||
import { substring } from '@/utils/strings';
|
||||
|
||||
@@ -139,54 +139,3 @@ export default defineComponent({
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ant-collapse {
|
||||
.ant-list-sm {
|
||||
.ant-list-item {
|
||||
padding: 4px 15px;
|
||||
cursor: pointer;
|
||||
color: rgb(130 196 233);
|
||||
position: relative;
|
||||
|
||||
.ks-tree-actions {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #0d2d4e;
|
||||
|
||||
.ks-tree-actions {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&.ks-trees-collapse {
|
||||
|
||||
.ant-collapse-content-box {
|
||||
padding: 0;
|
||||
height: 40vh;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.create-tree-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ant-list-item {
|
||||
padding: 3px 5px;
|
||||
cursor: pointer;
|
||||
color: rgb(130 196 233);
|
||||
|
||||
&:hover {
|
||||
background: #0d2d4e;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user