Merge remote-tracking branch 'origin/master'

This commit is contained in:
MHW
2026-03-31 15:32:37 +08:00
22 changed files with 371 additions and 42 deletions

View File

@@ -0,0 +1,25 @@
package com.solution.web.controller.behaviour;
import com.solution.common.core.controller.BaseController;
import com.solution.common.core.domain.AjaxResult;
import com.solution.system.service.HbNodeCommandService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/node/command")
public class HbNodeCommandController extends BaseController {
private final HbNodeCommandService nodeCommandService;
public HbNodeCommandController(HbNodeCommandService nodeCommandService) {
this.nodeCommandService = nodeCommandService;
}
@GetMapping(value = "/all")
public AjaxResult all() {
return success(nodeCommandService.findAll());
}
}

View File

@@ -49,4 +49,10 @@ public class SceneController extends BaseController {
List<AfsimScenario> list = sceneService.selectSceneList();
return getDataTable(list);
}
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(sceneService.findOneById(id));
}
}

View File

@@ -0,0 +1,55 @@
package com.solution.system.domain;
/*
* 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 java.io.Serializable;
public class HbNodeCommand implements Serializable {
private Integer id;
private String command;
private String chineseName;
private String description;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getCommand() {
return command;
}
public void setCommand(String command) {
this.command = command;
}
public String getChineseName() {
return chineseName;
}
public void setChineseName(String chineseName) {
this.chineseName = chineseName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

View File

@@ -0,0 +1,18 @@
package com.solution.system.mapper;
/*
* 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 com.solution.system.domain.HbNodeCommand;
import java.util.List;
public interface HbNodeCommandMapper {
List<HbNodeCommand> findAll();
}

View File

@@ -0,0 +1,18 @@
package com.solution.system.service;
/*
* 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 com.solution.system.domain.HbNodeCommand;
import java.util.List;
public interface HbNodeCommandService {
List<HbNodeCommand> findAll();
}

View File

@@ -0,0 +1,32 @@
package com.solution.system.service.impl;
/*
* 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 com.solution.system.domain.HbNodeCommand;
import com.solution.system.mapper.HbNodeCommandMapper;
import com.solution.system.service.HbNodeCommandService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class HbNodeCommandServiceImpl implements HbNodeCommandService {
private final HbNodeCommandMapper hbNodeCommandMapper;
public HbNodeCommandServiceImpl(HbNodeCommandMapper hbNodeCommandMapper) {
this.hbNodeCommandMapper = hbNodeCommandMapper;
}
@Override
public List<HbNodeCommand> findAll() {
return hbNodeCommandMapper.findAll();
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.solution.system.mapper.HbNodeCommandMapper">
<resultMap type="com.solution.system.domain.HbNodeCommand" id="HbNodeCommandMapperMap">
<result property="id" column="id"/>
<result property="command" column="command"/>
<result property="description" column="description"/>
<result property="chineseName" column="chinese_name"/>
</resultMap>
<select id="findAll" resultMap="HbNodeCommandMapperMap">
select *
from bh_node_command
</select>
</mapper>

View File

@@ -24,4 +24,6 @@ public interface SceneMapper {
* @return
*/
List<AfsimScenario> selectSceneList();
AfsimScenario findOneById(Long id);
}

View File

@@ -23,4 +23,6 @@ public interface SceneService {
* @return
*/
List<AfsimScenario> selectSceneList();
AfsimScenario findOneById(Long id);
}

View File

@@ -64,4 +64,10 @@ public class SceneServiceImpl implements SceneService {
public List<AfsimScenario> selectSceneList() {
return sceneMapper.selectSceneList();
}
@Override
public AfsimScenario findOneById(Long id) {
return sceneMapper.findOneById(id);
}
}

View File

@@ -12,6 +12,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="communicationGraph" column="communication_graph" />
</resultMap>
<select id="findOneById" resultMap="SceneMap">
SELECT * FROM afsim_scenario
WHERE id=#{id}
</select>
<insert id="insert" parameterType="com.solution.scene.domain.AfsimScenario" useGeneratedKeys="true" keyProperty="id">
INSERT INTO afsim_scenario (name, description, scenario_path, communication_graph)
VALUES (#{name}, #{description}, #{scenarioPath}, #{communicationGraph})

View File

@@ -1204,7 +1204,7 @@
.ant-tabs-content {
//padding: 15px;
padding: 4px;
background: #041b36db;
background: #041832;
}
&.settings-tab,
@@ -1508,9 +1508,7 @@
border-inline-end-width: 1px;
}
}
.ant-select:not(.ant-select-customize-input) .ant-select-selector{
border: 1px solid #2c2a2a;
}
.ant-select .ant-select-selection-placeholder,
.ant-select .ant-select-selection-search-input{
background: transparent
@@ -1832,3 +1830,50 @@
}
}
.ant-select .ant-select-clear {
color: rgb(153 168 180);
background: #475f71;
border-radius: 50%;
}
.ks-add-parameter-action{
color: #eee;
position: absolute;
right: 14px;
top: 6px;
cursor: pointer;
}
.ks-parameter-setting-tabs{
.ant-tabs-nav{
background: none;
}
.ant-tabs-nav-list{
margin-left: 0;
}
&.ant-tabs-left >.ant-tabs-content-holder,
&.ant-tabs-left >div>.ant-tabs-content-holder{
border-left-color: #09264b;
}
.ant-tabs-tab-remove{
//position: absolute;
//right: 10px;
//top: 7px;
.anticon{
color: rgb(173 206 224);
}
}
&.ant-tabs-left >.ant-tabs-nav .ant-tabs-tab{
border-radius: 0!important;
}
&.ant-tabs-card >.ant-tabs-nav .ant-tabs-tab-active,
&.ant-tabs-card >div>.ant-tabs-nav .ant-tabs-tab-active {
background: #09264c;
}
&.ant-tabs-left >.ant-tabs-content-holder >.ant-tabs-content>.ant-tabs-tabpane,
&.ant-tabs-left >div>.ant-tabs-content-holder >.ant-tabs-content>.ant-tabs-tabpane{
padding-left: 5px;
}
}

View File

@@ -9,7 +9,7 @@
import { HttpRequestClient } from '@/utils/request';
import type { BasicResponse } from '@/types';
import type { PlatformListableResponse } from './types';
import type { NodeCommandListResponse, PlatformListableResponse } from './types';
const req = HttpRequestClient.create<BasicResponse>({
baseURL: '/api',
@@ -19,3 +19,7 @@ const req = HttpRequestClient.create<BasicResponse>({
export const findAllBasicPlatforms = (): Promise<PlatformListableResponse> => {
return req.get<PlatformListableResponse>('/system/firerule/platforms/basic');
};
export const findAllNodeCommands = (): Promise<NodeCommandListResponse> => {
return req.get<NodeCommandListResponse>('/node/command/all')
}

View File

@@ -8,7 +8,7 @@
*/
import { HttpRequestClient } from '@/utils/request';
import type { Scenario, ScenarioPageableResponse, ScenarioRequest } from './types';
import type { Scenario, ScenarioDetailsResponse, ScenarioPageableResponse, ScenarioRequest } from './types';
import type { PlatformWithComponentsResponse } from '../types';
import type { BasicResponse } from '@/types';
@@ -17,17 +17,21 @@ const req = HttpRequestClient.create<BasicResponse>({
});
export const findScenarioByQuery = (_query: Partial<ScenarioRequest> = {}): Promise<ScenarioPageableResponse> => {
return req.get('/system/scene/list', _query);
return req.get<ScenarioPageableResponse>('/system/scene/list', _query);
};
export const findOneScenarioById = (id: number): Promise<ScenarioDetailsResponse> => {
return req.get<ScenarioDetailsResponse>(`/system/scene/${id}`);
};
export const deleteOneScenarioById = (id: number): Promise<BasicResponse> => {
return req.delete(`/system/behaviortree/${id}`);
return req.delete<BasicResponse>(`/system/behaviortree/${id}`);
};
export const findPlatformWithComponents = (id: number): Promise<PlatformWithComponentsResponse> => {
return req.get(`/system/firerule/platforms/${id}`);
return req.get<PlatformWithComponentsResponse>(`/system/firerule/platforms/${id}`);
};
export const saveScenario = (scenario: Scenario): Promise<BasicResponse> => {
return req.postJson(`/system/scene/saveSceneConfig`,scenario);
return req.postJson<BasicResponse>(`/system/scene/saveSceneConfig`,scenario);
};

View File

@@ -46,7 +46,7 @@
<script lang="ts">
import { defineComponent, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
import {useRouter} from 'vue-router';
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';
@@ -64,7 +64,7 @@ import { createGraphScenarioElement, createGraphTaskElementFromScenario } from '
import PlatformCard from './platform-card.vue';
import NodesCard from './nodes-card.vue';
import { saveScenario } from './api';
import { findOneScenarioById, saveScenario } from './api';
import { resolveConnectionRelation } from './relation';
const TeleportContainer = defineComponent(getTeleport());
@@ -84,7 +84,7 @@ export default defineComponent({
TeleportContainer,
},
setup() {
const router = useRouter();
const currentRoute = useRoute();
const canvas = ref<HTMLDivElement | null>(null);
const graph = ref<Graph | null>(null);
const currentZoom = ref<number>(1);
@@ -97,6 +97,7 @@ export default defineComponent({
const selectedNodeTaskElement = ref<GraphTaskElement | null>(null);
const changed = ref<boolean>(false);
const scenariosCardRef = ref<InstanceType<typeof PlatformCard> | null>(null);
const currentScenarioId = ref<number | null>(null);
const {
handleGraphEvent,
@@ -313,11 +314,7 @@ export default defineComponent({
const node = args.node as Node<NodeProperties>;
const element = node.getData() as GraphTaskElement;
console.error('element',element)
if(element && element.platformId ){
window.location.href = `/app/decision/designer?platform=${element.platformId}`
} else {
window.location.href = '/app/decision/designer'
}
window.location.href = `/app/decision/designer?scenario=${currentScenario.value?.id}&platform=${element?.platformId?? ''}`
// destroy()
// window.location.href = '/app/decision/designer'
@@ -358,6 +355,15 @@ export default defineComponent({
initGraph();
window.addEventListener('resize', handleResize);
console.log('节点挂载完成');
let scenarioId = Number(currentRoute.query.scenario);
if (!isNaN(scenarioId)) {
findOneScenarioById(scenarioId).then(r=> {
if(r.data){
handleSelect(r.data)
}
})
}
});
};

View File

@@ -18,7 +18,7 @@
</div>
<a-list :data-source="scenario || []" size="small" style="min-height: 25vh">
<template #renderItem="{ item }">
<a-list-item>
<a-list-item @click="()=> handleSelect(item)">
<a-flex>
<a-tooltip placement="bottom">
<template #title>
@@ -28,7 +28,7 @@
<span>{{ substring(item.name, 15) }}</span>
</a-tooltip>
<a-flex class="ks-tree-actions">
<span style="margin-right: 10px" @click="()=> handleSelect(item)"><EditFilled /></span>
<!-- <span style="margin-right: 10px" @click="()=> handleSelect(item)"><EditFilled /></span>-->
<!-- <a-popconfirm-->
<!-- title="确定删除?"-->
<!-- @confirm="()=> handleDelete(item)"-->

View File

@@ -8,7 +8,7 @@
*/
import type { NullableString, PageableResponse } from '@/types';
import type { ApiDataResponse, NullableString, PageableResponse } from '@/types';
import type { GraphContainer } from '../graph';
import type { Platform, PlatformComponent } from '../types';
@@ -44,3 +44,7 @@ export interface ScenarioPageableResponse extends PageableResponse<Scenario> {
}
export interface ScenarioDetailsResponse extends ApiDataResponse<Scenario> {
}

View File

@@ -23,6 +23,11 @@
<CheckOutlined />
<span>保存</span>
</a-button>
<a-button v-if="currentScenarioId" class="ks-model-builder-save" size="small" @click="handleGoback">
<BackwardFilled />
<span>返回</span>
</a-button>
</a-space>
</div>
<div
@@ -38,6 +43,7 @@
<Properties
v-if="graph"
:platforms="platforms"
:nodeCommands="nodeCommands"
:element="selectedNodeTaskElement"
:graph="graph as any"
:node="selectedModelNode as any"
@@ -56,7 +62,7 @@ import { useRoute } 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, RollbackOutlined, SaveOutlined, BackwardFilled } from '@ant-design/icons-vue';
import { Wrapper } from '@/components/wrapper';
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
import Header from '../header.vue';
@@ -67,9 +73,9 @@ import { createGraphTaskElementFromTemplate } from './utils';
import { createGraphTaskElement, createLineOptions, type GraphContainer, type GraphTaskElement, hasElements, hasRootElementNode, resolveGraph, useGraphCanvas } from '../graph';
import { registerNodeElement } from './register';
import { findAllBasicPlatforms } from '../api';
import { findAllBasicPlatforms, findAllNodeCommands } from '../api';
import type { Platform } from '../types';
import { createTree, findOneTreeById, updateTree, findOneTreeByPlatformId } from './api';
import { createTree, findOneTreeById, findOneTreeByPlatformId, updateTree } from './api';
import TressCard from './trees-card.vue';
import NodesCard from './nodes-card.vue';
@@ -88,6 +94,7 @@ export default defineComponent({
CheckCircleOutlined,
CheckOutlined,
RollbackOutlined,
BackwardFilled,
TeleportContainer,
},
setup() {
@@ -105,6 +112,8 @@ export default defineComponent({
const changed = ref<boolean>(false);
const treesCardRef = ref<InstanceType<typeof TressCard> | null>(null);
const platforms = ref<Platform[]>([]);
const nodeCommands = ref<NodeCommand[]>([])
const currentScenarioId = ref<number | null>(null);
const {
handleGraphEvent,
@@ -131,6 +140,18 @@ export default defineComponent({
});
};
const loadNodeCommands = ()=> {
nodeCommands.value = []
findAllNodeCommands().then(r=> {
nodeCommands.value = r.data ?? []
})
}
const loadDatasource = ()=> {
loadPlatforms();
loadNodeCommands();
}
// 处理拖动开始
const handleDragStart = (nm: NodeDragTemplate) => {
draggedNodeData.value = nm;
@@ -335,6 +356,7 @@ export default defineComponent({
handleGraphEvent('node:click', (args: any) => {
const node = args.node as Node<NodeProperties>;
const newElement = node.getData() as GraphTaskElement;
console.error('ddd', args);
selectedModelNode.value = node;
selectedNodeTaskElement.value = JSON.parse(JSON.stringify(newElement || {})) as GraphTaskElement;
@@ -365,16 +387,24 @@ export default defineComponent({
window.addEventListener('resize', handleResize);
console.log('节点挂载完成');
const platformId = currentRoute.query.platform;
if (platformId) {
const id = Number(platformId);
if (!isNaN(id)) {
findOneTreeByPlatformId(id).then(r => {
if (r.data) {
handleSelectTree(r.data);
}
});
}
let scenarioId = Number(currentRoute.query.scenario);
if (!isNaN(scenarioId)) {
currentScenarioId.value = scenarioId;
} else {
currentScenarioId.value = null;
}
let platformId = Number(currentRoute.query.platform);
if (!isNaN(platformId)) {
findOneTreeByPlatformId(platformId).then(r => {
if (r.data) {
handleSelectTree(r.data);
} else {
handleCreateTree();
}
});
} else {
handleCreateTree();
}
});
};
@@ -390,6 +420,10 @@ export default defineComponent({
changed.value = true;
};
const handleGoback = ()=> {
window.location.href = `/app/decision/communication?scenario=${currentScenarioId.value}`
}
const handleSave = () => {
const graphData: GraphContainer = resolveGraph(graph.value as Graph);
console.info('handleSave', graphData);
@@ -429,13 +463,15 @@ export default defineComponent({
// 初始化
onMounted(() => {
init();
loadPlatforms();
loadDatasource();
});
// 清理
onBeforeUnmount(() => destroy());
return {
nodeCommands,
currentScenarioId,
platforms,
treesCardRef,
handleCreateTree,
@@ -459,6 +495,7 @@ export default defineComponent({
handleSave,
handleUpdateElement,
handleSelectTree,
handleGoback,
};
},
});

View File

@@ -111,6 +111,11 @@
v-else-if="setting.paramKey === 'platforms'" v-model:value="setting.defaultValue">
<a-select-option v-for="pl in platforms" :value="pl.name">{{pl.description}}</a-select-option>
</a-select>
<a-select :placeholder="`请选择${setting.description}`"
allow-clear
v-else-if="setting.paramKey === 'command'" v-model:value="setting.defaultValue">
<a-select-option v-for="pl in nodeCommands" :value="pl.command">{{pl.chineseName}}</a-select-option>
</a-select>
<a-input v-else v-model:value="setting.defaultValue"
:placeholder="setting.description" size="small" />
</a-form-item>
@@ -127,6 +132,16 @@
<a-form-item v-for="setting in currentElement.parameters" :label="setting.description">
<a-input-number v-if="setting.dataType === 'double'" v-model:value="setting.defaultValue"
:placeholder="setting.description" size="small" style="width:100%;" />
<a-select :placeholder="`请选择${setting.description}`"
allow-clear
v-else-if="setting.paramKey === 'platforms'" v-model:value="setting.defaultValue">
<a-select-option v-for="pl in platforms" :value="pl.name">{{ pl.description }}</a-select-option>
</a-select>
<a-select :placeholder="`请选择${setting.description}`"
allow-clear
v-else-if="setting.paramKey === 'command'" v-model:value="setting.defaultValue">
<a-select-option v-for="pl in nodeCommands" :value="pl.command">{{pl.chineseName}}</a-select-option>
</a-select>
<a-input v-else v-model:value="setting.defaultValue" :placeholder="setting.description" size="small" />
</a-form-item>
</template>
@@ -161,7 +176,7 @@ import type { ElementParameter, ElementVariable, GraphTaskElement } from '../gra
import type { BehaviorTree } from './tree';
import type { Graph, Node, NodeProperties } from '@antv/x6';
import { generateKey } from '@/utils/strings';
import type { Platform } from '@/views/decision/types';
import type { NodeCommand, Platform } from '../types';
const actionSpaceColumns = [
{ title: '序号', dataIndex: 'index', key: 'index', width: 40 },
@@ -179,10 +194,12 @@ export default defineComponent({
node: { type: [Object, null] as PropType<Node<NodeProperties> | null | undefined>, required: false },
graph: { type: [Object, null] as PropType<Graph | null | undefined>, required: true },
platforms: { type: Array as PropType<Platform[]>, required: true },
nodeCommands: { type: Array as PropType<NodeCommand[]>, required: true },
},
emits: ['update-element', 'update-tree'],
setup(props, { emit }) {
const platforms = ref<Platform[]>(props.platforms ?? []);
const nodeCommands = ref<NodeCommand[]>(props.nodeCommands ?? []);
const activeTopTabsKey = ref<string>('1');
const activeBottomTabsKey = ref<string>('1');
@@ -370,11 +387,13 @@ export default defineComponent({
watch(() => currentElement.value, () => updateNode(), { deep: true });
watch(() => props.nodeCommands, (n: NodeCommand[] | null | undefined) => nodeCommands.value = n ?? [], { deep: true, immediate: true });
watch(() => props.platforms, (n: Platform[] | null | undefined) => platforms.value = n ?? [], { deep: true, immediate: true });
onMounted(() => load());
return {
nodeCommands,
platforms,
addParameterTab,
groupedParametersActiveTab,

View File

@@ -18,7 +18,7 @@
</div>
<a-list :data-source="behaviorTrees || []" size="small" style="min-height: 25vh">
<template #renderItem="{ item }">
<a-list-item>
<a-list-item @click="()=> handleSelect(item)">
<a-flex>
<a-tooltip placement="bottom">
<template #title>
@@ -28,12 +28,12 @@
<span>{{ substring(item.name, 15) }}</span>
</a-tooltip>
<a-flex class="ks-tree-actions">
<span style="margin-right: 10px" @click="()=> handleSelect(item)"><EditFilled /></span>
<!-- <span style="margin-right: 10px"><EditFilled /></span>-->
<a-popconfirm
title="确定删除?"
@confirm="()=> handleDelete(item)"
>
<span class="ks-tree-action ks-tree-action-delete"><DeleteOutlined /></span>
<span class="ks-tree-action ks-tree-action-delete" @click.stop><DeleteOutlined /></span>
</a-popconfirm>
</a-flex>
</a-flex>

View File

@@ -0,0 +1,21 @@
/*
* 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 { ApiDataResponse, NullableString } from '@/types';
export interface NodeCommand {
id: number,
command: NullableString,
chineseName: NullableString,
description: NullableString,
}
export interface NodeCommandListResponse extends ApiDataResponse<NodeCommand[]> {
}

View File

@@ -8,3 +8,4 @@
*/
export * from './platform'
export * from './command'